A menudo, es conveniente reproducir contenido multimedia mientras una app no está en primer plano. Por ejemplo, un reproductor de música suele seguir reproduciendo música cuando el usuario bloquea su dispositivo o usa otra app. La biblioteca Media3 proporciona una serie de interfaces que te permiten admitir la reproducción en segundo plano.
Cómo usar un MediaSessionService
Para habilitar la reproducción en segundo plano, debes contener Player
y
MediaSession
dentro de un servicio independiente.
Esto permite que el dispositivo siga entregando contenido multimedia incluso cuando la app no está en primer plano.

MediaSessionService
permite que la sesión de medios se ejecute por separado de la actividad de la app.Cuando alojes un jugador dentro de un servicio, debes usar un MediaSessionService
.
Para ello, crea una clase que extienda MediaSessionService
y crea tu sesión de contenido multimedia dentro de ella.
Usar MediaSessionService
permite que los clientes externos, como Google Assistant, los controles multimedia del sistema, los botones multimedia en dispositivos periféricos o los dispositivos complementarios, como Wear OS, descubran tu servicio, se conecten a él y controlen la reproducción, todo sin tener que acceder a la actividad de la IU de tu app. De hecho, pueden conectarse varias apps cliente al mismo MediaSessionService
a la vez, cada una con su propio MediaController
.
Cómo implementar el ciclo de vida del servicio
Debes implementar dos métodos de ciclo de vida de tu servicio:
- Se llama a
onCreate()
cuando el primer controlador está a punto de conectarse y se crea una instancia del servicio y se inicia. Es el mejor lugar para compilarPlayer
yMediaSession
. - Se llama a
onDestroy()
cuando se detiene el servicio. Se deben liberar todos los recursos, incluido el reproductor y la sesión.
De manera opcional, puedes anular onTaskRemoved(Intent)
para personalizar lo que sucede cuando el usuario descarta la app de las tareas recientes. De forma predeterminada, el servicio se deja en ejecución si la reproducción está en curso y se detiene de lo contrario.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // Create your player and media session in the onCreate lifecycle event override fun onCreate() { super.onCreate() val player = ExoPlayer.Builder(this).build() mediaSession = MediaSession.Builder(this, player).build() } // Remember to release the player and media session in onDestroy override fun onDestroy() { mediaSession?.run { player.release() release() mediaSession = null } super.onDestroy() } }
Java
public class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // Create your Player and MediaSession in the onCreate lifecycle event @Override public void onCreate() { super.onCreate(); ExoPlayer player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player).build(); } // Remember to release the player and media session in onDestroy @Override public void onDestroy() { mediaSession.getPlayer().release(); mediaSession.release(); mediaSession = null; super.onDestroy(); } }
Como alternativa para mantener la reproducción en segundo plano, puedes detener el servicio en cualquier caso cuando el usuario descarte la app:
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
Para cualquier otra implementación manual de onTaskRemoved
, puedes usar isPlaybackOngoing()
para verificar si la reproducción se considera en curso y si se inició el servicio en primer plano.
Proporciona acceso a la sesión multimedia
Anula el método onGetSession()
para otorgar a otros clientes acceso a tu sesión de media que se compiló cuando se creó el servicio.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
public class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // [...] lifecycle methods omitted @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
Cómo declarar el servicio en el manifiesto
Una app requiere los permisos FOREGROUND_SERVICE
y FOREGROUND_SERVICE_MEDIA_PLAYBACK
para ejecutar un servicio en primer plano de reproducción:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
También debes declarar tu clase Service
en el manifiesto con un filtro de intents de MediaSessionService
y un foregroundServiceType
que incluya mediaPlayback
.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
Controla la reproducción con un MediaController
En la actividad o el fragmento que contiene la IU de tu reproductor, puedes establecer un vínculo entre la IU y tu sesión multimedia con un MediaController
. Tu IU usa el controlador multimedia para enviar comandos de la IU al reproductor dentro de la sesión. Consulta la guía Crea un MediaController
para obtener detalles sobre cómo crear y usar un MediaController
.
Controla los comandos MediaController
El MediaSession
recibe comandos del controlador a través de su MediaSession.Callback
. Si inicializas un MediaSession
, se crea una implementación predeterminada de MediaSession.Callback
que controla automáticamente todos los comandos que un MediaController
envía a tu reproductor.
Notificación
Un MediaSessionService
crea automáticamente un MediaNotification
que debería funcionar en la mayoría de los casos. De forma predeterminada, la notificación publicada es una notificación MediaStyle
que se mantiene actualizada con la información más reciente de tu sesión multimedia y muestra los controles de reproducción. MediaNotification
reconoce tu sesión y se puede usar para controlar la reproducción de cualquier otra app que esté conectada a la misma sesión.
Por ejemplo, una app de transmisión de música que usa un MediaSessionService
crearía un MediaNotification
que muestre el título, el artista y el material gráfico del álbum del elemento multimedia actual que se está reproduciendo junto con los controles de reproducción según la configuración de MediaSession
.
Los metadatos obligatorios se pueden proporcionar en el contenido multimedia o declararse como parte del elemento multimedia, como en el siguiente fragmento:
Kotlin
val mediaItem = MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build() ) .build() mediaController.setMediaItem(mediaItem) mediaController.prepare() mediaController.play()
Java
MediaItem mediaItem = new MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( new MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build()) .build(); mediaController.setMediaItem(mediaItem); mediaController.prepare(); mediaController.play();
Ciclo de vida de las notificaciones
La notificación se crea en cuanto Player
tiene instancias de MediaItem
en su playlist.
Todas las actualizaciones de notificaciones se realizan automáticamente según el estado de Player
y MediaSession
.
No se puede quitar la notificación mientras se ejecuta el servicio en primer plano. Para quitar la notificación de inmediato, debes llamar a Player.release()
o borrar la playlist con Player.clearMediaItems()
.
Si el reproductor se pausa, se detiene o falla durante más de 10 minutos sin más interacciones del usuario, el servicio se transfiere automáticamente del estado de servicio en primer plano para que el sistema pueda destruirlo. Puedes implementar la reanudación de la reproducción para permitir que un usuario reinicie el ciclo de vida del servicio y reanude la reproducción más adelante.
Personalización de notificaciones
Para personalizar los metadatos del elemento que se está reproduciendo, modifica MediaItem.MediaMetadata
. Si deseas actualizar los metadatos de un elemento existente, puedes usar Player.replaceMediaItem
para actualizarlos sin interrumpir la reproducción.
También puedes personalizar algunos de los botones que se muestran en la notificación configurando preferencias de botones multimedia personalizados para los controles multimedia de Android. Obtén más información para personalizar los controles multimedia de Android.
Para personalizar aún más la notificación, crea un MediaNotification.Provider
con DefaultMediaNotificationProvider.Builder
o crea una implementación personalizada de la interfaz del proveedor. Agrega tu proveedor a tu MediaSessionService
con setMediaNotificationProvider
.
Reanudación de la reproducción
Después de que se cancele MediaSessionService
y, incluso, después de que se reinicie el dispositivo, es posible ofrecer la reanudación de la reproducción para permitir que los usuarios reinicien el servicio y reanuden la reproducción desde donde la dejaron. De forma predeterminada, la reanudación de la reproducción está desactivada. Esto significa que el usuario no puede reanudar la reproducción cuando tu servicio no se está ejecutando. Para habilitar esta función, debes declarar un receptor de botones multimedia y, luego, implementar el método onPlaybackResumption
.
Declara el receptor de botones de medios Media3
Comienza por declarar MediaButtonReceiver
en tu manifiesto:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Implementa la devolución de llamada de reanudación de la reproducción
Cuando un dispositivo Bluetooth o la función de reanudación de la IU del sistema Android solicitan la reanudación de la reproducción, se llama al método de devolución de llamada onPlaybackResumption()
.
Kotlin
override fun onPlaybackResumption( mediaSession: MediaSession, controller: ControllerInfo ): ListenableFuture<MediaItemsWithStartPosition> { val settable = SettableFuture.create<MediaItemsWithStartPosition>() scope.launch { // Your app is responsible for storing the playlist and the start position // to use here val resumptionPlaylist = restorePlaylist() settable.set(resumptionPlaylist) } return settable }
Java
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller ) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener(() -> { // Your app is responsible for storing the playlist and the start position // to use here MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
Si almacenaste otros parámetros, como la velocidad de reproducción, el modo de repetición o el modo aleatorio, onPlaybackResumption()
es un buen lugar para configurar el reproductor con estos parámetros antes de que Media3 prepare el reproductor y comience la reproducción cuando se complete la devolución de llamada.
Configuración avanzada del controlador y retrocompatibilidad
Una situación común es usar un MediaController
en la IU de la app para controlar la reproducción y mostrar la playlist. Al mismo tiempo, la sesión se expone a clientes externos, como los controles multimedia de Android y Asistente en dispositivos móviles o TVs, Wear OS para relojes y Android Auto en automóviles. La app de demostración de sesión de Media3 es un ejemplo de una app que implementa una situación de este tipo.
Estos clientes externos pueden usar APIs como MediaControllerCompat
de la biblioteca de AndroidX heredada o android.media.session.MediaController
de la plataforma de Android. Media3 es totalmente retrocompatible con la biblioteca heredada y proporciona interoperabilidad con la API de la plataforma de Android.
Usa el controlador de notificaciones multimedia
Es importante comprender que estos controladores heredados y de la plataforma comparten el mismo estado y que la visibilidad no se puede personalizar por controlador (por ejemplo, los PlaybackState.getActions()
y PlaybackState.getCustomActions()
disponibles). Puedes usar el controlador de notificaciones multimedia para configurar el estado establecido en la sesión multimedia de la plataforma para que sea compatible con estos controladores heredados y de la plataforma.
Por ejemplo, una app puede proporcionar una implementación de MediaSession.Callback.onConnect()
para establecer los comandos disponibles y las preferencias de los botones multimedia específicamente para la sesión de la plataforma de la siguiente manera:
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { if (session.isMediaNotificationController(controller)) { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() val playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build() // Custom button preferences and commands to configure the platform session. return AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default button preferences for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); Player.Commands playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS .buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build(); // Custom button preferences and commands to configure the platform session. return new AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands with default button preferences for all other controllers. return new AcceptedResultBuilder(session).build(); }
Otorga autorización a Android Auto para que envíe comandos personalizados
Cuando se usa un MediaLibraryService
y se admite Android Auto con la app para dispositivos móviles, el controlador de Android Auto requiere los comandos disponibles adecuados. De lo contrario, Media3 rechazaría los comandos personalizados entrantes de ese controlador:
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available session commands to accept incoming custom commands from Auto. return AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return new AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands for all other controllers. return new AcceptedResultBuilder(session).build(); }
La app de demostración de la sesión tiene un módulo automotriz que demuestra la compatibilidad con el SO Automotive que requiere un APK independiente.