Cómo reproducir contenido multimedia en segundo plano

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:

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: