MediaPlayer para reproducir audios en Android

15-03-2018 - Andrés Cruz

In english

Como su nombre indica, los MediaPlayer son empleados para reproducir y controlar la reproducción de audios y/o videos en Android; aunque parezca complejo su uso, son bastante sencillo y configurables, esta entrada no pretende ser una guía para emplear los MediaPlayer, en Android, ni tampoco crear un reproductor (aunque se da una estructura de la cual puedes partir), la idea es ejemplificar su uso más básico, dar un esquema de cómo se pueden emplear los recursos multimedias mediante servicios y explicar el propósito de dos componente muy importantes que son el método prepare y prepareAsync que aunque sus nombre se parezca, el propósito de cada uno es distinto y el uso de uno o el otro pueden tener resultados muy distintos en nuestra aplicación.

Para reproducir un audio primero debemos crear un objeto de la clase MediaPlayer:

player = new MediaPlayer();

Ahora debemos de indicar la fuente de los datos, los cuales pueden ser internos, es decir localizados en el mismo dispositivo Android o en una web y la accedemos vía una URL como la siguiente:

player.setDataSource("http://example.net/uploads/fuente/"
+ cancion.getFuente());

Android se encarga de descargar el audio; una vez establecido el fuente preparamos el audio e iniciamos la reproducción:

player.prepare();
player.start();

Finalmente nuestro código queda de la siguiente forma:

try {
    player.setDataSource("http://example.net/uploads/fuente/"
    + cancion.getFuente());
    player.prepare();
    player.start();
} catch (Exception e) {
    e.printStackTrace();
}

Audios cargados de manera asíncrona

Dependiendo del propósito de su aplicación puede que te de algunos problemas el código mostrado anteriormente, la razón se basa en que los audios hasta que no estén completamente preparado prepare() para su reproducción la aplicación se nos puede "colgar" dependiendo en que hilo en donde invoquemos dicho método que generalmente es en hilo principal y es posible que aparezca la famosa ventana de "No responde":

No responde app android

En la cual nos indica que la aplicación no responde y si deseas cerrarla; cosa que no es positiva en la experiencia que tenga el usuario con nuestra aplicación.

prepareAsync para preparar el audio de manera asíncrona

El problema se hace mayor si por ejemplo todos los audios nos los traemos desde Internet y la aplicación consiste en un listado de audios, y si al usuario empieza a seleccionar varios de ellos el cuelgue de nuestra aplicación se hace inminente...

La solución es realmente sencilla y no tenemos que crear servicios, hilos, etc y colocar todo el procesamiento dentro de los mismos ni nada por el estilo; simplemente tenemos que emplear el método prepareAsync y cambiar unas pequeñas cosas del código anterior y agregar otras.

Primero el método prepareAsync permite preparar al audio igual que el método prepare pero lo realiza de manera asíncrona, por lo tanto el audio deberá reproducirse (dependiendo de la acción que queramos hacer sobre el mismo) una vez que el audio esté listo o preparado; para eso empleamos un evento escuchador onPrepared y dentro del mismo movemos nuestro método play():

try {
    player.setDataSource("http://example.net/uploads/fuente/"
    + cancion.getFuente());
    player.setOnPreparedListener(this);
    player.prepareAsync();
} catch (Exception e) {
    e.printStackTrace();
}

Y definimos nuestro evento escuchador quedando de la siguiente forma:

@Override
public void onPrepared(MediaPlayer mediaPlayer) {
    if (mediaPlayer.isPlaying()) {
        mediaPlayer.pause();
    }
    mediaPlayer.start();
}

Y con esto adiós cuelgue (al menos los ocasionados por el método prepare); como vemos, con el método prepareAsync el cual no necesita ser definido en un hilo o proceso aparte y es Android el que se encarga del fuerte de manejar los procesos en segundo plano necesarios.

Caso práctico: Realizando un pequeño reproductor

Un enfoque que nos permite emplear los MediaPlayer para la reproducción paralela de recursos multimedias como audios y/o videos es definir una clase aparte que extienda de los servicios, de esta forma podemos tener una clase madre que será nuestra actividad que invoque (y controle según necesidades) al servicio, además de esto el servicio permitirá realizar una serie de funcionalidades mediante métodos predefinidos o ya definidos en otras clases que conforman la API, y esto se hace mediante la implementación a estas clases (OnPreparedListener,OnErrorListener y OnCompletionListener).

Crearemos una clase que para este ejemplo llamaremos MusicService:

public class MusicService extends Service implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { }

Ahora definimos unas variables globales que nos permitirán realizar el funcionamiento de una lista de canciones; además sobreescribimos los métodos correspondientes de acuerdo a las clases heredades e implementadas:

public class MusicService  extends Service implements
MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener {

    //media player
    private MediaPlayer player;
    //song list
    private ArrayList<Cancion> canciones;
    private Cancion cancion;
    //current position
    private int songPosn;

    private final IBinder musicBind = new MusicBinder();

    @Override
    public void onCreate() {
        super.onCreate();
        initMusicPlayer();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return musicBind;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Bundle extras = intent.getExtras();
        int accion = 0;
        if (extras != NULL) {
            accion = extras.getInt("accion");
        }

        return START_NOT_STICKY;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        try {
            player.stop();
            player.release();
        } catch (Exception e) {
            Log.e("ERROR", e.toString());
        }
        return false;
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        mp.reset();
        return false;
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        mp.reset();
    }

    @Override
    public void onPrepared(MediaPlayer mediaPlayer) {
        if(mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }

        mediaPlayer.start();
    }


}

Como vemos, sobreescribimos varios métodos que se ejecutan en el ciclo de vida de un video o audio, como son si ocurre algún error en la reproducción mediante el método onError, cuando se completó la reproducción de la música mediante el método onCompletion, cuando el video o música está listo para su reproducción mediante el método onPrepared.

También tenemos algunos métodos del servicio como el onUnbind que se invoca cuando el cliente se conecta con el servicio o el método onStartCommand que se invoca cuando la actividad inicia el servicio; con esto definimos otros métodos que nos permitirán automatizar la reproducción de nuestra música que empleamos en los métodos sobrescritos por las clases implementadas anteriores:

public class MusicService  extends Service implements
MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener {

    private MediaPlayer player;

    private ArrayList<Cancion> canciones;
    private Cancion cancion;

    private int songPosn;

    private final IBinder musicBind = new MusicBinder();

    @Override
    public void onCreate() {
        super.onCreate();
        initMusicPlayer();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return musicBind;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Bundle extras = intent.getExtras();
        int accion = 0;
        if (extras != NULL) {
            accion = extras.getInt("accion");

            switch (accion) {
                case 1:
                    if (isPng()) {
                    pausePlayer();
                } else {
                    go();
                }
                break;
                case 3:
                    playNext();
                break;
                case 4:
                    playPrev();
                break;
            }
        }

        return START_NOT_STICKY;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        try {
            player.stop();
            player.release();
        } catch (Exception e) {
            Log.e("ERROR", e.toString());
        }
        return false;
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        mp.reset();
        return false;
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        mp.reset();
        playNext();
    }

    @Override
    public void onPrepared(MediaPlayer mediaPlayer) {
        if(mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }

        mediaPlayer.start();
    }

    public void initMusicPlayer() {
        songPosn = 0;
        player = new MediaPlayer();
        player.setWakeMode(getApplicationContext(),
        PowerManager.PARTIAL_WAKE_LOCK);
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        player.setOnPreparedListener(this);
        player.setOnCompletionListener(this);
        player.setOnErrorListener(this);
    }

    public void setList(ArrayList<Cancion> canciones) {
        this.canciones = canciones;
    }

    public void playSong() {

        if (songPosn >= canciones.size())
        return;

        try {
            player.reset();
        } catch (Exception e) {
            Log.i("Error", e.toString());
        }

        cancion = canciones.get(songPosn);

        try {
            player.setDataSource("http://url/"
            + cancion.getFuente());
            player.setOnPreparedListener(this);
            player.prepareAsync();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            Log.e("MUSIC SERVICE", "Error setting data source", e);
        }
    }

    public class MusicBinder extends Binder {
        public MusicBinder() {
        }

        public MusicService  getService() {
            return MusicService .this;
        }
    }

    public int getPosn() {
        return player.getCurrentPosition();
    }

    public int getDur() {
        return player.getDuration();
    }

    public boolean isPng() {
        return player.isPlaying();
    }

    public void pausePlayer() {
        player.pause();
    }

    public void seek(int posn) {
        player.seekTo(posn);
    }

    public void go() {
        player.start();
    }

    public void playPrev() {
        songPosn--;
        if (songPosn < 0) songPosn = canciones.size() - 1;
        playSong();
    }

    //skip to next
    public void playNext() {

        songPosn++;
        if (songPosn >= canciones.size()) songPosn = 0;

        playSong();
    }

    @Override
    public void onDestroy() {
        stopForeground(true);
    }
}

Ya con esto tendríamos el servicio completo, se crearon algunos métodos que permiten verificar el estatus de la reproducción del audio, pasar a la siguiente música, iniciar la reproducción, colocar en pausa, etc.

Ahora, como indicamos en un inicio, el servicio es iniciado desde otro componente que en este caso representa a una actividad; para iniciar el servicio anterior desde una actividad tenemos:

public MusicService musicSrv;
public boolean musicBound = false;

private ServiceConnection musicConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
        //get service
        musicSrv = binder.getService();
        //pass list
        musicSrv.setList(canciones);
        musicBound = true;

        current = 0;
        try {
            songPicked(NULL);
        } catch (Exception e) {
            Log.e("ERROR SERVICE", e.toString());
        }

    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        musicBound = false;
        Log.i("onServiceDisconnected", "Desconectado");
    }
};

Para mandar si deseamos reproducir, pausar, pasar al siguiente audio desde la actividad que invocó el servicio o cualquier otra comunicación que deba ocurrir entre la actividad y el servicio tenemos:

private void playNext() {
    musicSrv.playNext();
    current++;
    songPicked();
}

private void playPrev() {
    musicSrv.playPrev();
    current--;
    songPicked();
}

Quedaría es asociar esas funciones a los botones u otro elemento para su acción.

Canciones es una clase modelo con la siguiente estructura:

public class Cancion {

    int cancion_id;
    String fuente;
    String titulo;
    String anombre;
    String imagen_url;

    public String getAnombre() {
        return anombre;
    }

    public void setAnombre(String anombre) {
        this.anombre = anombre;
    }
    public int getCancion_id() {
        return cancion_id;
    }

    public void setCancion_id(int cancion_id) {
        this.cancion_id = cancion_id;
    }

    public String getFuente() {
        return fuente;
    }

    public void setFuente(String fuente) {
        this.fuente = fuente;
    }

    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }

    public String getImagen_url() {
        return imagen_url;
    }

    public void setImagen_url(String imagen_url) {
        this.imagen_url = imagen_url;
    }
}

Andrés Cruz
Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.

!Cursos a!

10$

En Udemy

Quedan 5 días!

Ver los cursos
¡Hazte afiliado en Gumroad!

!Cursos desde!

4$

En Academia

Ver los cursos

!Libros desde!

1$

Ver los libros
!Web Alojada en Hostinger!