A menudo, se recomienda reproducir contenido multimedia mientras una app no está en primer plano. Para 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 ofrece una serie de 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 Service independiente
Esto permite que el dispositivo siga entregando contenido multimedia incluso cuando la app no esté en
en primer plano.
![MediaSessionService permite que la sesión multimedia se ejecute por separado
a partir de la actividad de la app](https://developer.android.google.cn/static/guide/topics/media/images/mediasessionservice.png?authuser=7&hl=es-419)
MediaSessionService
permite el contenido multimedia
sesión para que se ejecute independientemente de la actividad de la appCuando alojas un jugador dentro de un Service, debes usar un MediaSessionService
.
Para ello, crea una clase que extienda MediaSessionService
y crea tu
o una sesión multimedia.
Usar MediaSessionService
posibilita que los clientes externos como Google
Asistente, controles multimedia del sistema o dispositivos complementarios, como Wear OS, para descubrir
servicio, conectarte a él y controlar la reproducción, todo sin acceder a tu
la actividad de la IU de la app. De hecho, puede haber
varias apps cliente conectadas
a la misma MediaSessionService
al mismo tiempo, cada app con su propia
MediaController
Cómo implementar el ciclo de vida del servicio
Debes implementar tres métodos de ciclo de vida de tu servicio:
- Se llama a
onCreate()
cuando el primer control está por conectarse y el se crea una instancia y se inicia. Es el mejor lugar para crearPlayer
yMediaSession
- Se llama a
onTaskRemoved(Intent)
cuando el usuario descarta la app del tareas recientes. Si la reproducción está en curso, la app puede optar por mantener el servicio se ejecutan en primer plano. Si el reproductor está pausado, el servicio no está en y debe detenerse. - Se llama a
onDestroy()
cuando se detiene el servicio. Todos los recursos incluidos el reproductor y la sesión.
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() } // The user dismissed the app from the recent tasks override fun onTaskRemoved(rootIntent: Intent?) { val player = mediaSession?.player!! if (!player.playWhenReady || player.mediaItemCount == 0 || player.playbackState == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf() } } // 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(); } // The user dismissed the app from the recent tasks @Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 || player.getPlaybackState() == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf(); } } // 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 a mantener la reproducción continua en segundo plano, una app puede Detén el servicio en cualquier caso cuando el usuario descarte la app:
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { val player = mediaSession.player if (player.playWhenReady) { // Make sure the service is not in foreground. player.pause() } stopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (player.getPlayWhenReady()) { // Make sure the service is not in foreground. player.pause(); } stopSelf(); }
Proporciona acceso a la sesión multimedia
Anula el método onGetSession()
para dar acceso a tu contenido multimedia a otros clientes
que se creó 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 permiso para ejecutar un servicio en primer plano. Agrega el
FOREGROUND_SERVICE
al manifiesto, y si orientas tu app al nivel de API 34 y
arriba también FOREGROUND_SERVICE_MEDIA_PLAYBACK
:
<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
.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
Debes definir un
foregroundServiceType
que incluye mediaPlayback
cuando la app se ejecuta en un dispositivo con Android
10 (nivel de API 29) y versiones posteriores.
Controlar la reproducción con un MediaController
En la actividad o el fragmento que contiene la IU del 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 desde la IU al reproductor dentro del
sesión. Consulta la
Cómo crear un MediaController
para obtener información sobre cómo crear y usar MediaController
.
Cómo controlar los comandos de la IU
MediaSession
recibe comandos del controlador a través de su
MediaSession.Callback
Cuando inicializas un MediaSession
, se crea un
implementación de MediaSession.Callback
que controla automáticamente todas
comandos que un MediaController
envía a tu reproductor.
Notificación
Un MediaSessionService
crea automáticamente un MediaNotification
para ti que
debería funcionar en la mayoría de los casos. De forma predeterminada, la notificación publicada es una
Notificación de MediaStyle
que se actualiza con la información más reciente
de tu sesión multimedia y muestra los controles de reproducción. El MediaNotification
reconoce tu sesión y puede usarse para controlar la reproducción de cualquier otra app
que están conectadas a la misma sesión.
Por ejemplo, una app de streaming de música con un MediaSessionService
crearía un
MediaNotification
, que muestra el título, el artista y la imagen del álbum de la
elemento multimedia actual que se reproduce junto con los controles de reproducción según tu
Configuración de MediaSession
.
Los metadatos necesarios se pueden proporcionar en el contenido multimedia o declarar 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();
Las apps pueden personalizar los botones de comando de los controles multimedia de Android. Más información sobre la personalización de Android Media controles de acceso.
Personalización de notificaciones
Para personalizar la notificación, crea una
MediaNotification.Provider
con DefaultMediaNotificationProvider.Builder
o creando una implementación personalizada de la interfaz del proveedor. Agrega tu
proveedor a tu MediaSessionService
con
setMediaNotificationProvider
Reanudación de la reproducción
Los botones de medios son botones de hardware que se encuentran en dispositivos Android y otros periféricos. como el botón de reproducción o pausa de los auriculares Bluetooth. Contenido multimedia3 se encarga de las entradas del botón multimedia cuando se ejecuta el servicio.
Cómo declarar el receptor del botón multimedia de Media3
Media3 incluye una API que permite a los usuarios reanudar
la reproducción después de que se cierre una app e incluso después de que el dispositivo
reiniciar. De forma predeterminada, la reanudación de la reproducción está desactivada. Esto significa que el usuario
no se puede reanudar la reproducción cuando no se esté ejecutando el servicio. Para participar, comienza por
Declara el 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>
Cómo implementar la devolución de llamada para reanudar la reproducción
Cuando un dispositivo Bluetooth o el
Función de reanudación de la IU del sistema Android
el onPlaybackResumption()
de devolución de llamada.
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
modo aleatorio, onPlaybackResumption()
es un buen lugar para configurar el reproductor
con estos parámetros antes de que Media3 prepare el reproductor e inicie la reproducción cuando
se complete la devolución de llamada.
Configuración avanzada del control y retrocompatibilidad
Una situación común es usar un MediaController
en la IU de la app para controlar.
reproducir y mostrar la playlist. Al mismo tiempo, la sesión se expone
a clientes externos, como los controles multimedia
de Android y el Asistente en dispositivos móviles o TVs
Wear OS para relojes y Android Auto en vehículos La app de demostración de sesiones de Media3
es un ejemplo de una app en la que se implementa esa situación.
Estos clientes externos pueden usar APIs como MediaControllerCompat
de la heredada
Biblioteca de AndroidX o android.media.session.MediaController
de Android
en un framework de aplicaciones. Media3 es totalmente retrocompatible con la biblioteca heredada y
proporciona interoperabilidad con la API del framework de Android.
Cómo usar el controlador de notificaciones multimedia
Es importante entender que estos controladores heredados o del framework leen el
mismos valores del framework PlaybackState.getActions()
y
PlaybackState.getCustomActions()
Para determinar las acciones y las acciones personalizadas de
la sesión del framework, una app puede usar el controlador de notificaciones multimedia
y establecer los comandos disponibles
y el diseño personalizado. El servicio conecta los medios
de notificaciones a tu sesión, que usará el
El onConnect()
de tu devolución de llamada muestra ConnectionResult
para configurarlo.
y las acciones personalizadas de la sesión del framework.
En una situación solo para dispositivos móviles, una aplicación puede proporcionar una implementación de
MediaSession.Callback.onConnect()
para establecer los comandos disponibles y
diseño personalizado específicamente para la sesión del framework 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 layout and available commands to configure the legacy/framework session. return AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default custom layout 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 layout and available commands to configure the legacy/framework session. return new AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands without default custom layout for all other controllers. return new AcceptedResultBuilder(session).build(); }
Autoriza a Android Auto para enviar comandos personalizados
Cuando uses un MediaLibraryService
y admitir Android Auto con la app para dispositivos móviles, el controlador de Android Auto
requiere comandos disponibles adecuados; de lo contrario, Media3 denegaría
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 with default custom layout 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 without default custom layout for all other controllers. return new AcceptedResultBuilder(session).build(); }
La app de demo de sesión tiene un módulo automotriz, que demuestra la compatibilidad con Automotive OS, que requiere un APK independiente.