Puedes reproducir contenido multimedia en segundo plano, incluso cuando tu app no está en la pantalla, por ejemplo, mientras el usuario interactúa con otras apps.
Para ello, incorpora el MediaPlayer en un servicio MediaBrowserServiceCompat
y haz que interactúe con un MediaBrowserCompat
en otra actividad.
Ten cuidado con la implementación de esta configuración de cliente y servidor. Existen expectativas acerca de cómo un reproductor que se ejecuta en un servicio en segundo plano debería interactuar con el resto del sistema. Si tu app no cumple con esas expectativas, el usuario puede tener una mala experiencia. Consulta Cómo crear una app de audio para obtener más información.
En esta página, se describen instrucciones especiales para administrar un MediaPlayer cuando lo implementas dentro de un servicio.
Ejecución asíncrona
Al igual que con un Activity
, todo el trabajo de un Service
se realiza en un solo subproceso de forma predeterminada. De hecho, cuando ejecutas una actividad y un servicio desde la misma aplicación, ambos usan el mismo subproceso (el "subproceso principal") de forma predeterminada.
Los servicios deben procesar intents entrantes con rapidez y nunca realizar cálculos prolongados cuando responden a ellos. Debes realizar cualquier trabajo pesado o llamadas de bloqueo de forma asíncrona: desde otro subproceso que implementes por tu cuenta o mediante las diversas herramientas del framework para el procesamiento asíncrono.
Por ejemplo, cuando usas MediaPlayer
desde el subproceso principal, debes hacer lo siguiente:
- Llama a
prepareAsync()
en lugar de aprepare()
. - Implementa un
MediaPlayer.OnPreparedListener
para recibir una notificación cuando se complete la preparación y puedas comenzar a jugar.
Por ejemplo:
Kotlin
private const val ACTION_PLAY: String = "com.example.action.PLAY"
class MyService: Service(), MediaPlayer.OnPreparedListener {
private var mMediaPlayer: MediaPlayer? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
...
val action: String = intent.action
when(action) {
ACTION_PLAY -> {
mMediaPlayer = ... // initialize it here
mMediaPlayer?.apply {
setOnPreparedListener(this@MyService)
prepareAsync() // prepare async to not block main thread
}
}
}
...
}
/** Called when MediaPlayer is ready */
override fun onPrepared(mediaPlayer: MediaPlayer) {
mediaPlayer.start()
}
}
Java
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final String ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mediaPlayer = null;
public int onStartCommand(Intent intent, int flags, int startId) {
...
if (intent.getAction().equals(ACTION_PLAY)) {
mediaPlayer = ... // initialize it here
mediaPlayer.setOnPreparedListener(this);
mediaPlayer.prepareAsync(); // prepare async to not block main thread
}
}
/** Called when MediaPlayer is ready */
public void onPrepared(MediaPlayer player) {
player.start();
}
}
Cómo controlar errores asíncronos
En las operaciones síncronas, los errores se señalan con una excepción o un código de error. Sin embargo, cuando usas recursos asíncronos, debes notificar los errores a tu aplicación de forma adecuada. En el caso de un MediaPlayer
, implementas un MediaPlayer.OnErrorListener
y lo configuras en tu instancia de MediaPlayer
:
Kotlin
class MyService : Service(), MediaPlayer.OnErrorListener {
private var mediaPlayer: MediaPlayer? = null
fun initMediaPlayer() {
// ...initialize the MediaPlayer here...
mediaPlayer?.setOnErrorListener(this)
}
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
// ... react appropriately ...
// The MediaPlayer has moved to the Error state, must be reset!
}
}
Java
public class MyService extends Service implements MediaPlayer.OnErrorListener {
MediaPlayer mediaPlayer;
public void initMediaPlayer() {
// ...initialize the MediaPlayer here...
mediaPlayer.setOnErrorListener(this);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// ... react appropriately ...
// The MediaPlayer has moved to the Error state, must be reset!
}
}
Cuando se produce un error, el MediaPlayer
pasa al estado Error. Debes restablecerlo antes de poder usarlo de nuevo. Para obtener más información, consulta el diagrama de estado completo de la clase MediaPlayer
.
Cómo usar bloqueos de activación
Cuando reproduces o transmites música en segundo plano, debes usar bloqueos de activación para evitar que el sistema interfiera en la reproducción, por ejemplo, haciendo que el dispositivo entre en estado de suspensión.
Un bloqueo de activación es una señal para el sistema de que tu app está usando funciones que deberían permanecer disponibles incluso cuando el teléfono está inactivo.
Para asegurarte de que la CPU siga ejecutándose mientras se reproduce el MediaPlayer
, llama al método setWakeMode()
cuando inicialices el MediaPlayer
. El MediaPlayer
retiene el bloqueo especificado durante la reproducción y libera el bloqueo cuando se pone en pausa o se detiene la reproducción:
Kotlin
mediaPlayer = MediaPlayer().apply {
// ... other initialization here ...
setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
}
Java
mediaPlayer = new MediaPlayer();
// ... other initialization here ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
Sin embargo, el bloqueo de activación adquirido en este ejemplo solo garantiza que la CPU permanezca activa. Si estás transmitiendo contenido multimedia a través de la red y utilizas Wi-Fi, tal vez también desees mantener un WifiLock
, que debes adquirir y liberar de forma manual. Por lo tanto, cuando comiences a preparar el MediaPlayer
con la URL remota, debes crear y adquirir el bloqueo de Wi-Fi.
Por ejemplo:
Kotlin
val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiLock: WifiManager.WifiLock =
wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")
wifiLock.acquire()
Java
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
Cuando pauses o detengas la reproducción del contenido, o cuando ya no necesites la red, deberás liberar el bloqueo:
Kotlin
wifiLock.release()
Java
wifiLock.release();
Cómo realizar la limpieza
Como se mencionó anteriormente, un objeto MediaPlayer
puede consumir una cantidad significativa de recursos del sistema, por lo que debes conservarlo solo el tiempo necesario y llamar a release()
cuando termines. Es importante llamar a este método de limpieza de forma explícita, en lugar de depender de la recolección de elementos no utilizados, porque el recolector de elementos no utilizados reclama el MediaPlayer
, ya que solo es sensible a las necesidades de memoria y no a otros recursos relacionados con medios. Entonces, si estás usando un servicio, siempre debes anular el método onDestroy()
para asegurarte de estar liberando el MediaPlayer
:
Kotlin
class MyService : Service() {
private var mediaPlayer: MediaPlayer? = null
// ...
override fun onDestroy() {
super.onDestroy()
mediaPlayer?.release()
}
}
Java
public class MyService extends Service {
MediaPlayer mediaPlayer;
// ...
@Override
public void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) mediaPlayer.release();
}
}
Siempre debes buscar otras oportunidades para liberar tu MediaPlayer
, además de liberarlo cuando se cierra. Por ejemplo, si no podrás reproducir archivos multimedia durante un período prolongado (después de perder el enfoque de audio, por ejemplo), sin duda debes liberar el MediaPlayer
existente y crearlo de nuevo más tarde. Por otro lado, si solo detendrás la reproducción por un tiempo muy corto, tal vez deberías mantener el MediaPlayer
para evitar la sobrecarga que se produce cuando se crea y prepara de nuevo.
Más información
Jetpack Media3 es la solución recomendada para la reproducción de contenido multimedia en tu app. Obtén más información al respecto.
En estas páginas, se analizan temas relacionados con la grabación, el almacenamiento y la reproducción de audio y video: