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, debes incorporar el MediaPlayer en un MediaBrowserServiceCompat
servicio y hacer que interactúe con un MediaBrowserCompat en otra
actividad.
Ten cuidado al implementar 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 detalles.
En esta página, se incluyen instrucciones especiales para administrar un MediaPlayer cuando se implementa dentro de un servicio.
Se ejecutan de manera asíncrona.
Al igual que 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 app, ambos usan el mismo subproceso (el "subproceso principal") de forma predeterminada.
Los servicios deben procesar intents entrantes con rapidez y nunca realizan 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:
- Llamar a
prepareAsync()en lugar deprepare(). - Implementar un
MediaPlayer.OnPreparedListenerpara recibir una notificación cuando se complete la preparación y puedas comenzar la reproducción.
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 manejar errores asíncronos
En 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 a tu app los errores 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 detalles, consulta el diagrama de estado completo
para la MediaPlayer clase.
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 con 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 que indica que tu app está usando funciones que deberían permanecer disponibles incluso cuando el teléfono está inactivo.
Para asegurarte de que la CPU continúe ejecutándose mientras se reproduce el MediaPlayer, llama al método setWakeMode() cuando inicialices el
MediaPlayer. El MediaPlayer retendrá el bloqueo especificado durante la reproducción y retirará el bloqueo cuando se ponga en pausa o detenga 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 de 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 también, que debes
adquirir y retirar de manera 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 pongas en pausa o detengas la reproducción del contenido, o cuando ya no necesites la red, deberás retirar el bloqueo:
Kotlin
wifiLock.release()
Java
wifiLock.release();
Cómo realizar la limpieza
Como se mencionó anteriormente, un objeto MediaPlayer puede consumir una cantidad importante
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 la escasez de otros recursos relacionados con medios. Entonces, si estás usando un servicio,
siempre debes anular el método onDestroy() para asegurarte de estar
retirando 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 retirar tu
MediaPlayer, además de retirarlo cuando se cierra. Por
ejemplo, si no podrás reproducir archivos multimedia durante un período prolongado de
tiempo (después de perder el enfoque de audio, por ejemplo), sin duda debes retirar el
existente MediaPlayer 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: