Il est souvent souhaitable de lire des contenus multimédias lorsqu'une application n'est pas au premier plan. Par exemple, un lecteur de musique continue généralement à lire de la musique lorsque l'utilisateur a verrouillé son appareil ou utilise une autre application. La bibliothèque Media3 fournit une série d' interfaces qui vous permettent de prendre en charge la lecture en arrière-plan.
Utiliser un MediaSessionService
Pour activer la lecture en arrière-plan, vous devez inclure le Player et
MediaSession dans un Service distinct.
Cela permet à l'appareil de continuer à diffuser des contenus multimédias même lorsque votre application n'est pas au
premier plan.
MediaSessionService permet à la session multimédia
de s'exécuter séparément de l'activité de l'applicationLorsque vous hébergez un lecteur dans un service, vous devez utiliser un MediaSessionService.
Pour ce faire, créez une classe qui étend MediaSessionService et créez votre
session multimédia à l'intérieur.
L'utilisation de MediaSessionService permet aux clients externes tels que Google
Assistant, les commandes multimédias du système, les boutons multimédias sur les périphériques ou les
appareils associés comme Wear OS de découvrir votre service, de s'y connecter et de
contrôler la lecture, le tout sans accéder à l'activité de l'interface utilisateur de votre application. En fait,
plusieurs applications clientes peuvent être connectées au même MediaSessionService en
même temps, chaque application ayant son propre MediaController.
Implémenter le cycle de vie du service
Vous devez implémenter deux méthodes de cycle de vie de votre service :
onCreate()est appelé lorsque le premier contrôleur est sur le point de se connecter, et que le service est instancié et démarré. C'est le meilleur endroit pour créerPlayeretMediaSession.onDestroy()est appelé lorsque le service est arrêté. Toutes les ressources y compris le lecteur et la session, doivent être libérées.
Vous pouvez éventuellement remplacer onTaskRemoved(Intent) pour personnaliser ce qui se passe
lorsque l'utilisateur ferme l'application à partir des tâches récentes. Par défaut, le service
continue de s'exécuter si la lecture est en cours et s'arrête dans le cas contraire.
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(); } }
Au lieu de maintenir la lecture en arrière-plan, vous pouvez arrêter le service dans tous les cas lorsque l'utilisateur ferme l'application :
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
Pour toute autre implémentation manuelle de onTaskRemoved, vous pouvez utiliser
isPlaybackOngoing() pour vérifier si la lecture est considérée comme en cours et si le
service de premier plan est démarré.
Fournir un accès à la session multimédia
Remplacez la méthode onGetSession() pour donner à d'autres clients l'accès à votre session multimédia
créée lors de la création du service.
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; } }
Déclarer le service dans le fichier manifeste
Une application nécessite les autorisations FOREGROUND_SERVICE et FOREGROUND_SERVICE_MEDIA_PLAYBACK
pour exécuter un service de lecture au premier plan :
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
Vous devez également déclarer votre classe Service dans le fichier manifeste avec un filtre d'intent
de MediaSessionService et un foregroundServiceType qui inclut
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>
Contrôler la lecture à l'aide d'un MediaController
Dans l'activité ou le fragment contenant l'interface utilisateur de votre lecteur, vous pouvez établir un lien
entre l'interface utilisateur et votre session multimédia à l'aide d'un MediaController. Votre interface utilisateur utilise
le contrôleur multimédia pour envoyer des commandes de votre interface utilisateur au lecteur de la
session. Pour en savoir plus sur la création et l'utilisation d'un MediaController, consultez le
guide Créer un MediaController.
Gérer les commandes MediaController
Le MediaSession reçoit des commandes du contrôleur via son
MediaSession.Callback. L'initialisation d'un MediaSession crée une implémentation par défaut
de MediaSession.Callback qui gère automatiquement toutes les
commandes qu'un MediaController envoie à votre lecteur.
Notification
Un MediaSessionService crée automatiquement un MediaNotification qui
devrait fonctionner dans la plupart des cas. Par défaut, la notification publiée est une
MediaStyle notification qui reste à jour avec les dernières
informations de votre session multimédia et affiche les commandes de lecture. Le
MediaNotification connaît votre session et peut être utilisé pour contrôler la lecture
de toutes les autres applications connectées à la même session.
Par exemple, une application de streaming musical utilisant un MediaSessionService crée un
MediaNotification qui affiche le titre, l'artiste et la pochette de l'album de l'
élément multimédia en cours de lecture, ainsi que des commandes de lecture basées sur la configuration de votre
MediaSession.
Les métadonnées requises peuvent être fournies dans le contenu multimédia ou déclarées dans l'élément multimédia, comme dans l'extrait suivant :
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();
Cycle de vie des notifications
La notification est créée dès que le Player comporte des instances MediaItem
dans sa playlist.
Toutes les mises à jour des notifications sont effectuées automatiquement en fonction de l'état du Player et
MediaSession.
La notification ne peut pas être supprimée pendant l'exécution du service de premier plan. Pour
supprimer immédiatement la notification, vous devez appeler Player.release() ou effacer
la playlist à l'aide de Player.clearMediaItems().
Si le lecteur est mis en pause, arrêté ou en échec pendant plus de 10 minutes sans autre interaction de l'utilisateur, le service passe automatiquement de l'état de service de premier plan à l'état de service d'arrière-plan afin de pouvoir être détruit par le système. Vous pouvez implémenter la reprise de la lecture pour permettre à un utilisateur de redémarrer le cycle de vie du service et reprendre la lecture ultérieurement.
Personnalisation des notifications
Les métadonnées de l'élément en cours de lecture peuvent être personnalisées en modifiant
le MediaItem.MediaMetadata. Si vous souhaitez mettre à jour les métadonnées d'un élément existant, vous pouvez utiliser Player.replaceMediaItem pour les mettre à jour sans
interrompre la lecture.
Vous pouvez également personnaliser certains des boutons affichés dans la notification en définissant des préférences de boutons multimédias personnalisées pour les commandes multimédias Android. En savoir plus sur la personnalisation des commandes multimédias Android.
Pour personnaliser davantage la notification elle-même, créez un
MediaNotification.Provider
avec DefaultMediaNotificationProvider.Builder
ou en créant une implémentation personnalisée de l'interface du fournisseur. Ajoutez votre
fournisseur à votre MediaSessionService avec
setMediaNotificationProvider.
Reprise de la lecture
Une fois le MediaSessionService arrêté, et même après le redémarrage de l'appareil, il est possible de proposer la reprise de la lecture pour permettre aux utilisateurs
de redémarrer le service et de reprendre la lecture là où ils l'avaient laissée. Par défaut,
la reprise de la lecture est désactivée. Cela signifie que l'utilisateur ne peut pas reprendre la lecture
lorsque votre service n'est pas en cours d'exécution. Pour activer cette fonctionnalité, vous devez déclarer
un récepteur de boutons multimédias et implémenter la méthode onPlaybackResumption.
Déclarer le récepteur de boutons multimédias Media3
Commencez par déclarer le MediaButtonReceiver dans votre fichier manifeste :
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Implémenter le rappel de reprise de la lecture
Lorsque la reprise de la lecture est demandée par un appareil Bluetooth ou par la
fonctionnalité de reprise de l'interface utilisateur du système Android ,
la méthode de rappel onPlaybackResumption() est
appelée.
Kotlin
override fun onPlaybackResumption( mediaSession: MediaSession, controller: ControllerInfo, isForPlayback: Boolean, ): ListenableFuture<MediaItemsWithStartPosition> { val settable = SettableFuture.create<MediaItemsWithStartPosition>() scope.launch { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item 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, boolean isForPlayback ) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener(() -> { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item and the start position to use here. MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
Si vous avez stocké d'autres paramètres tels que la vitesse de lecture, le mode de répétition ou
le mode aléatoire, onPlaybackResumption() est un bon endroit pour configurer le lecteur
avec ces paramètres avant que Media3 ne prépare le lecteur et ne démarre la lecture lorsque
le rappel est terminé.
Cette méthode est appelée au moment du démarrage pour créer la notification de reprise de l'interface utilisateur du système Android
après le redémarrage de l'appareil avec isForPlayback défini sur
false. Pour une notification enrichie, il est recommandé de renseigner les MediaMetadata
champs tels que title et artworkData ou artworkUri de l'élément actuel avec
des valeurs disponibles localement, car l'accès au réseau n'est peut-être pas encore disponible. Vous pouvez
également ajouter MediaConstants.EXTRAS_KEY_COMPLETION_STATUS et
MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE au MediaMetadata.extras
pour indiquer la position de reprise de la lecture.
Configuration avancée du contrôleur et rétrocompatibilité
Un scénario courant consiste à utiliser un MediaController dans l'interface utilisateur de l'application pour contrôler
la lecture et afficher la playlist. En même temps, la session est exposée
à des clients externes tels que les commandes multimédias Android et l'Assistant sur mobile ou TV,
Wear OS pour les montres et Android Auto dans les voitures. L'application de démonstration de session
Media3 est un exemple d'application qui implémente un tel scénario.
Ces clients externes peuvent utiliser des API telles que MediaControllerCompat de l'ancienne
bibliothèque AndroidX ou android.media.session.MediaController de la plate-forme Android. Media3 est entièrement rétrocompatible avec l'ancienne bibliothèque et
assure l'interopérabilité avec l'API de la plate-forme Android.
Identifier les contrôleurs approuvés
N'importe quelle application peut tenter de se connecter à votre session ou bibliothèque multimédia. Si vous souhaitez
limiter l'accès aux contrôleurs système, aux contrôleurs disposant de l'autorisation de contrôle des contenus multimédias
et à votre propre application, vous pouvez utiliser ControllerInfo.isTrusted() pour une
vérification d'accès de base. Vous pouvez également identifier des contrôleurs plus spécifiques
tels que le contrôleur de notification multimédia ou les contrôleurs Android Auto, comme décrit
dans les sections suivantes.
Utiliser le contrôleur de notification multimédia
Il est important de comprendre que ces contrôleurs de plate-forme et hérités partagent
le même état et que la visibilité ne peut pas être personnalisée par contrôleur (par exemple, les
disponibles PlaybackState.getActions() et PlaybackState.getCustomActions()).
Vous pouvez utiliser le contrôleur de notification multimédia pour
configurer l'ensemble d'états dans la session multimédia de la plate-forme afin d'assurer la compatibilité avec ces
contrôleurs de plate-forme et hérités.
Par exemple, une application peut fournir une implémentation de
MediaSession.Callback.onConnect() pour définir des commandes disponibles et
des préférences de boutons multimédias spécifiques à la session de la plate-forme, comme suit :
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { if (session.isMediaNotificationController(controller)) { 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( listOf(seekBackButton, seekForwardButton) ) .setAvailablePlayerCommands(playerCommands) .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)) { 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(seekBackButton, seekForwardButton)) .setAvailablePlayerCommands(playerCommands) .build(); } // Default commands with default button preferences for all other controllers. return new AcceptedResultBuilder(session).build(); }
Autoriser Android Auto à envoyer des commandes personnalisées
Lorsque vous utilisez un MediaLibraryService
et que vous souhaitez prendre en charge Android Auto avec l'application mobile, le contrôleur Android Auto
nécessite des commandes disponibles appropriées. Sinon, Media3 refuserait
les commandes personnalisées entrantes de ce contrôleur :
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon() .add(customCommand) .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(customCommand) .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(); }
L'application de démonstration de session comporte un module automobile, qui illustre la prise en charge d'Automotive OS, qui nécessite un APK distinct.