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":
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;
}
}
Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter