1. Code
  2. Mobile Development
  3. Android Development

Crear un reproductor de música en Android: Controles de usuario

Scroll to top

Spanish (Español) translation by CYC (you can also view the original English article)

Estamos construyendo una aplicación simple de reproductor de música para Android en esta serie. Hasta ahora, hemos presentado una lista de las canciones en el dispositivo y le permitimos al usuario hacer selecciones de ella, comenzando la reproducción usando la clase MediaPlayer en una clase de Service. En esta parte final de la serie, dejaremos que el usuario controle la reproducción, incluido el salto a las pistas siguiente y anterior, el avance rápido, el rebobinado, la reproducción, la pausa y la búsqueda de puntos particulares en la pista. También mostraremos una notificación durante la reproducción para que el usuario pueda regresar al reproductor de música después de usar otras aplicaciones.

Introducción

La funcionalidad de control del reproductor de música se implementará utilizando la clase MediaController, en la que una instancia de SeekBar muestra el progreso de la reproducción y permite que el usuario salte a ubicaciones particulares en una pista. Utilizaremos las clases Notification y PendingIntent para mostrar el título de la pista que se está reproduciendo actualmente y permitir que el usuario vuelva a la aplicación.

Así es como debería verse la aplicación cuando completes este tutorial:

Android Music PlayerAndroid Music PlayerAndroid Music Player

Después de esta serie, también exploraremos la funcionalidad relacionada que tal vez desees utilizar para mejorar la aplicación de reproductor de música. Esto incluirá reproducción de video, transmisión de medios, administración de enfoque de audio y presentación de datos de medios de diferentes maneras.

1. Crea un controlador

Paso 1

Abre tu clase principal Activity y agrega la siguiente instrucción de importación:

1
2
import android.widget.MediaController.MediaPlayerControl;

Extiende la línea de apertura de la declaración de clase de la siguiente manera, de modo que podamos usar la clase Activity para proporcionar control de reproducción:

1
2
public class MainActivity extends Activity implements MediaPlayerControl {

Desplázate sobre el nombre de la clase y selecciona Agregar métodos no implementados o en inglés Add unimplemented methods. Eclipse agregará varios métodos para el control de reproducción, los cuales adaptaremos a medida que avanzamos.

Paso 2

La clase MediaController presenta un widget estándar con botones reproducir/pausar, rebobinar, avanzar rápido y omitir (anterior/siguiente) en él. El widget también contiene una barra de búsqueda, que se actualiza a medida que se reproduce la canción y contiene texto que indica la duración de la canción y la posición actual del reproductor. Para que podamos configurar los detalles del control, implementaremos una clase para ampliarlo. Agrega una nueva clase a tu proyecto y asígnale el nombre de MusicController. En Eclipse, elige android.widget.MediaController como la superclase al crearlo.

Dale a la clase el siguiente contenido:

1
2
public class MusicController extends MediaController {
3
4
  public MusicController(Context c){
5
    super(c);
6
  }
7
8
  public void hide(){}
9
10
}

Puedes adaptar la clase MediaController de varias maneras. Todo lo que queremos hacer es evitar que se oculte automáticamente después de tres segundos anulando el método hide.

Sugerencia: Es posible que necesites modificar el tema que utiliza la aplicación para asegurarte de que el texto del controlador multimedia sea claramente visible.

Paso 3

Volviendo a tu clase de principal Activity, agrega una nueva variable de instancia:

1
2
private MusicController controller;

Vamos a configurar el controlador más de una vez en el ciclo de vida de la aplicación, así que hagámoslo en un método de ayuda. Agrega el siguiente fragmento de código a tu clase Activity:

1
2
private void setController(){
3
  //set the controller up

4
}

Dentro del método, crea una instancia del controlador:

1
2
controller = new MusicController(this);

Puedes configurar varios aspectos de la instancia de MediaController. Por ejemplo, necesitaremos determinar qué sucederá cuando el usuario presione los botones anterior / siguiente. Después de crear instancias del controlador, estos receptores hacen clic:

1
2
controller.setPrevNextListeners(new View.OnClickListener() {
3
  @Override
4
  public void onClick(View v) {
5
    playNext();
6
  }
7
}, new View.OnClickListener() {
8
  @Override
9
  public void onClick(View v) {
10
    playPrev();
11
  }
12
});

Implementaremos playNext y playPrev un poco más tarde, así que por el momento simplemente ignora los errores. Aún dentro del método setController, configura el controlador para trabajar en la reproducción de medios en la aplicación, con su vista de anclaje refiriéndose a la lista que incluimos en el diseño:

1
2
controller.setMediaPlayer(this);
3
controller.setAnchorView(findViewById(R.id.song_list));
4
controller.setEnabled(true);

De vuelta en onCreate, llama al método:

1
2
setController();

También lo llamaremos en otro lugar de la clase más tarde.

2. Implementar el control de reproducción

Paso 1

Recuerda que la reproducción de medios está ocurriendo en la clase Service, pero la interfaz de usuario proviene de la clase Activity. En el tutorial anterior, vinculamos la instancia Activity a la instancia Service, de modo que pudiéramos controlar la reproducción desde la interfaz del usuario. Se invocarán los métodos de nuestra clase Activity que agregamos para implementar la interfaz MediaPlayerControl cuando el usuario intente controlar la reproducción. Necesitaremos la clase Service para actuar sobre este control, por lo tanto, abre tu clase Service para agregarle algunos métodos más:

1
2
public int getPosn(){
3
  return player.getCurrentPosition();
4
}
5
6
public int getDur(){
7
  return player.getDuration();
8
}
9
10
public boolean isPng(){
11
  return player.isPlaying();
12
}
13
14
public void pausePlayer(){
15
  player.pause();
16
}
17
18
public void seek(int posn){
19
  player.seekTo(posn);
20
}
21
22
public void go(){
23
  player.start();
24
}

Todos estos métodos se aplican a las funciones de control de reproducción estándar que el usuario esperará.

Paso 2

Ahora agreguemos métodos a la clase Service para saltar a las pistas Siguiente y Anterior. Comienza con la función Anterior:

1
2
public void playPrev(){
3
  songPosn--;
4
  if(songPosn<0) songPosn=songs.size()-1;
5
  playSong();
6
}

Disminuimos la variable del índice de la canción, verificamos que no hemos salido del rango de la lista y llamamos al método playSong que agregamos. Ahora agrega el método para saltar a la siguiente pista:

1
2
//skip to next

3
public void playNext(){
4
  songPosn++;
5
  if(songPosn>=songs.size()) songPosn=0;
6
  playSong();
7
}

Esto es análogo al método para reproducir la pista anterior en este momento, pero modificaremos este método más adelante para implementar la funcionalidad de reproducción aleatoria.

Paso 3

Ahora vuelve a tu clase Activity para que podamos hacer uso de estos métodos. Primero agrega los métodos que llamamos cuando configuramos el controlador:

1
2
//play next

3
private void playNext(){
4
  musicSrv.playNext();
5
  controller.show(0);
6
}
7
8
//play previous

9
private void playPrev(){
10
  musicSrv.playPrev();
11
  controller.show(0);
12
}

Llamamos a los métodos que agregamos a la clase Service. Vamos a agregar más código a estos más adelante para encargarnos de situaciones particulares. Ahora veamos los métodos de interfaz MediaPlayerControl, que serán llamados por el sistema durante la reproducción y cuando el usuario interactúa con los controles. Estos métodos ya deberían estar en tu clase Activity, por lo que solo modificaremos su implementación.

Comienza con el método canPause, estableciéndolo en true:

1
2
@Override
3
public boolean canPause() {
4
  return true;
5
}

Ahora haz lo mismo con los métodos canSeekBackward y canSeekForward:

1
2
@Override
3
public boolean canSeekBackward() {
4
  return true;
5
}
6
7
@Override
8
public boolean canSeekForward() {
9
  return true;
10
}

Puedes dejar los métodos getAudioSessionId y getBufferPercentage tal como están. Modifica el método getCurrentPosition de la siguiente manera:

1
2
@Override
3
public int getCurrentPosition() {
4
  if(musicSrv!=null && musicBound && musicSrv.isPng())
5
    return musicSrv.getPosn();
6
  else return 0;
7
}

Las pruebas condicionales son para evitar varias excepciones que pueden ocurrir al usar las clases MediaPlayer y MediaController. Si intentas mejorar la aplicación de alguna manera, es probable que descubras que debes seguir estos pasos, ya que las clases de reproducción multimedia arrojan muchas excepciones. Ten en cuenta que llamamos al método getPosn de la clase Service.

Modifica el método getDuration de manera similar:

1
2
@Override
3
public int getDuration() {
4
  if(musicSrv!=null && musicBound && musicSrv.isPng())
5
    return musicSrv.getDur();
6
  else return 0;
7
}

Modifica el método isPlaying invocando el método isPng de nuestra clase Service:

1
2
@Override
3
public boolean isPlaying() {
4
  if(musicSrv!=null && musicBound)
5
    return musicSrv.isPng();
6
  return false;
7
}

Haz lo mismo con los métodos de pause, seekTo y start:

1
2
@Override
3
public void pause() {
4
  musicSrv.pausePlayer();
5
}
6
7
@Override
8
public void seekTo(int pos) {
9
  musicSrv.seek(pos);
10
}
11
12
@Override
13
public void start() {
14
  musicSrv.go();
15
}

3. Maneja la navegación de regreso a la aplicación

Paso 1

Recuerda que vamos a continuar la reproducción incluso cuando el usuario navega fuera de la aplicación. Para facilitar esto, mostraremos una notificación que muestra el título de la pista que se está reproduciendo. Al hacer clic en la notificación, el usuario volverá a la aplicación. Vuelve a tu clase Service y agrega las siguientes importaciones adicionales:

1
2
import java.util.Random;
3
import android.app.Notification;
4
import android.app.PendingIntent;

Ahora ve al método onPrepared, en el que actualmente simplemente comenzamos la reproducción. Después de la llamada a player.start (), agrega el siguiente código:

1
2
Intent notIntent = new Intent(this, MainActivity.class);
3
notIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
4
PendingIntent pendInt = PendingIntent.getActivity(this, 0,
5
  notIntent, PendingIntent.FLAG_UPDATE_CURRENT);
6
7
Notification.Builder builder = new Notification.Builder(this);
8
9
builder.setContentIntent(pendInt)
10
  .setSmallIcon(R.drawable.play)
11
  .setTicker(songTitle)
12
  .setOngoing(true)
13
  .setContentTitle("Playing")
14
  .setContentText(songTitle);
15
Notification not = builder.build();
16
17
startForeground(NOTIFY_ID, not);

Añadiremos las variables faltantes a continuación. La clase PendingIntent llevará al usuario a la clase principal Activity cuando seleccionen la notificación. Agrega variables para el título de la canción y el ID de notificación en la parte superior de la clase:

1
2
private String songTitle="";
3
private static final int NOTIFY_ID=1;

Ahora necesitamos establecer el título de la canción, en el método playSong, después de la línea en la que recuperamos la canción de la lista (Song playSong = songs.get(songPosn);):

1
2
songTitle=playSong.getTitle();

Como hemos llamado a setForeground en la notificación, debemos asegurarnos de detenerlo cuando se destruya la instancia Service. Anula el siguiente método:

1
2
@Override
3
public void onDestroy() {
4
  stopForeground(true);
5
}

4. Reproducción aleatoria

Paso 1

Recuerda que agregamos un botón para mezclar, así que vamos a implementarlo ahora. Primero agrega nuevas variables de instancia a la clase Service:

1
2
private boolean shuffle=false;
3
private Random rand;

Crea una instancia del generador de números aleatorios en onCreate:

1
2
rand=new Random();

Ahora agrega un método para establecer el indicador de reproducción aleatoria:

1
2
public void setShuffle(){
3
  if(shuffle) shuffle=false;
4
  else shuffle=true;
5
}

Simplemente activaremos y desactivaremos la configuración aleatoria. Comprobaremos este indicador cuando el usuario salte a la siguiente pista o cuando una pista finalice y comience la siguiente. Modifica el método playNext de la siguiente manera:

1
2
public void playNext(){
3
  if(shuffle){
4
    int newSong = songPosn;
5
    while(newSong==songPosn){
6
      newSong=rand.nextInt(songs.size());
7
    }
8
    songPosn=newSong;
9
  }
10
  else{
11
    songPosn++;
12
    if(songPosn>=songs.size()) songPosn=0;
13
  }
14
  playSong();
15
}

Si la bandera de reproducción aleatoria está activada, elegimos una nueva canción de la lista al azar, asegurándonos de no repetir la última canción reproducida. Puedes mejorar esta funcionalidad utilizando una cola de canciones y evitando que se repita cualquier canción hasta que se hayan reproducido todas las canciones.

Paso 2

Ahora podemos dejar que el usuario seleccione la función de reproducción aleatoria. Vuelve a tu clase principal Activity en el método onOptionsItemSelected, modifica la sección de la acción de mezcla para llamar al nuevo método que agregamos a la clase Service:

1
2
case R.id.action_shuffle:
3
  musicSrv.setShuffle();
4
  break;

Ahora el usuario podrá usar el elemento del menú para alternar la funcionalidad de mezcla.

5. Colocar en orden

Paso 1

Ya casi hemos terminado, pero aún necesitamos agregar algunos bits de procesamiento para encargarnos de ciertos cambios, como que el usuario salga de la aplicación o pause la reproducción. En tu clase Activity, agrega un par de variables de instancia más:

1
2
private boolean paused=false, playbackPaused=false;

Las utilizaremos para hacer frente al usuario que vuelve a la aplicación después de abandonarla e interactúa con los controles cuando la reproducción está pausada. Anula OnPause para establecer uno de estos indicadores:

1
2
@Override
3
protected void onPause(){
4
  super.onPause();
5
  paused=true;
6
}

Ahora anula onResume:

1
2
@Override
3
protected void onResume(){
4
  super.onResume();
5
  if(paused){
6
    setController();
7
    paused=false;
8
  }
9
}

Esto asegurará que el controlador se muestre cuando el usuario regrese a la aplicación. Anula onStop para ocultarlo:

1
2
@Override
3
protected void onStop() {
4
  controller.hide();
5
  super.onStop();
6
}

Paso 2

Si el usuario interactúa con los controles mientras la reproducción está en pausa, el objeto MediaPlayer puede comportarse de manera impredecible. Para hacer frente a esto, configuraremos y usaremos el indicador playbackPaused. Primero modifica los métodos playNext y playPrev:

1
2
private void playNext(){
3
  musicSrv.playNext();
4
  if(playbackPaused){
5
    setController();
6
    playbackPaused=false;
7
  }
8
  controller.show(0);
9
}
10
11
private void playPrev(){
12
  musicSrv.playPrev();
13
  if(playbackPaused){
14
    setController();
15
    playbackPaused=false;
16
  }
17
  controller.show(0);
18
}

Restablecemos el controlador y actualizamos el indicador playbackPaused cuando la reproducción ha sido pausada. Ahora haz cambios similares al método playSong:

1
2
public void songPicked(View view){
3
  musicSrv.setSong(Integer.parseInt(view.getTag().toString()));
4
  musicSrv.playSong();
5
  if(playbackPaused){
6
    setController();
7
    playbackPaused=false;
8
  }
9
  controller.show(0);
10
}

Ahora configura playbackPaused en true en el método pause:

1
2
@Override
3
public void pause() {
4
  playbackPaused=true;
5
  musicSrv.pausePlayer();
6
}

Al trabajar con las clases MediaPlayer y MediaController, encontrarás que este tipo de procesamiento es un requisito necesario para evitar errores. Por ejemplo, a veces encontrarás que la barra de búsqueda del controlador no se actualiza hasta que el usuario interactúa con ella. Estos recursos se comportan de manera diferente en diferentes niveles de API, por lo que es esencial realizar pruebas y modificaciones exhaustivas si planeas lanzar tu aplicación al público. La aplicación que estamos creando en esta serie es realmente solo una base.

Paso 3

Tomemos algunos pasos finales para que la aplicación se comporte de forma coherente. De regreso en la clase Service, modifica el método onError:

1
2
@Override
3
public boolean onError(MediaPlayer mp, int what, int extra) {
4
  mp.reset();
5
  return false;
6
}

Simplemente reiniciamos el reproductor, pero por supuesto puedes desear mejorar este enfoque.

El método onCompletion se activará cuando finalice una pista, incluidos los casos en que el usuario haya elegido una nueva pista o salteado a las pistas siguiente / anterior, así como cuando la pista llegue al final de su reproducción. En este último caso, queremos continuar la reproducción tocando la siguiente pista. Para hacer esto, necesitamos verificar el estado de la reproducción. Cambia tu método onCompletion:

1
2
@Override
3
public void onCompletion(MediaPlayer mp) {
4
  if(player.getCurrentPosition()>0){
5
    mp.reset();
6
    playNext();
7
  }
8
}

Llamamos al método playNext si la pista actual ha llegado a su fin.

Consejo: Para asegurarte de que tu aplicación no interfiera con otros servicios de audio en el dispositivo del usuario, debes mejorarla para manejar el enfoque de audio con elegancia. Haz que la clase Service implemente la interfaz AudioManager.OnAudioFocusChangeListener. En el método onCreate, crea una instancia de la clase AudioManager y llama requestAudioFocus en ella. Finalmente, implementa el método onAudioFocusChange en tu clase para controlar lo que debería suceder cuando la aplicación gane o pierda el foco de audio. Consulta la sección de Enfoque de audio en la Guía del desarrollador para obtener más detalles.

¡Esa es la aplicación básica completa! Sin embargo, es posible que necesites llevar a cabo mejoras adicionales para que funcione de manera confiable en todos los dispositivos de usuario y niveles de API. Los controles deberían aparecer siempre que interactúes con la aplicación.

Android Music PlayerAndroid Music PlayerAndroid Music Player

La notificación debería permitirte regresar a la aplicación mientras continúa la reproducción.

Android Music PlayerAndroid Music PlayerAndroid Music Player

Conclusión

Ahora hemos completado el reproductor de música básico para Android. Hay muchas maneras en que puedes mejorar la aplicación, como agregar soporte para transmisión de medios, video, enfoque de audio y proporcionar diferentes métodos para interactuar con las pistas de música en el dispositivo. Veremos algunas de estas mejoras en futuros tutoriales, describiendo cómo puedes agregarlos a la aplicación o a otros proyectos de reproducción multimedia. Mientras tanto, fíjate si puedes extender la aplicación para crear funcionalidades adicionales o mejorar la confiabilidad en diferentes dispositivos. Consulta la sección Reproducción de medios en la Guía del desarrollador de Android y así obtener más información.