Les commandes multimédias d'Android se trouvent à proximité des Réglages rapides. Les sessions de plusieurs applications sont organisées dans un carrousel que vous pouvez balayer. Le carrousel liste les sessions dans l'ordre suivant :
- Flux lus localement sur le téléphone
- Flux à distance, tels que ceux détectés sur des appareils externes ou les sessions Cast
- Sessions précédentes pouvant être reprises, dans l'ordre de leur dernière lecture
À partir d'Android 13 (niveau d'API 33), pour s'assurer que les utilisateurs peuvent accéder à un ensemble complet de commandes multimédias pour les applications qui lisent des contenus multimédias, les boutons d'action des commandes multimédias sont dérivés de l'état Player.
Vous pouvez ainsi présenter un ensemble cohérent de commandes multimédias et une expérience plus soignée sur tous les appareils.
La figure 1 montre un exemple de ce à quoi cela ressemble sur un téléphone et une tablette.
Le système affiche jusqu'à cinq boutons d'action en fonction de l'état Player, comme décrit dans le tableau suivant. En mode compact, seuls les trois premiers emplacements d'action sont affichés. Cela correspond à la façon dont les commandes multimédias sont affichées sur d'autres plates-formes Android, telles qu'Auto, Assistant et Wear OS.
| Encoche | Critères | Action |
|---|---|---|
| 1 |
playWhenReady est défini sur "false" ou l'état de lecture actuel
est STATE_ENDED.
|
Lire |
playWhenReady est défini sur "true" et l'état de lecture actuel est STATE_BUFFERING.
|
Icône de chargement en cours | |
playWhenReady est défini sur "true" et l'état de lecture actuel
est STATE_READY.
|
Pause | |
| 2 |
Les préférences des boutons multimédias contiennent un bouton personnalisé pour
CommandButton.SLOT_BACK
|
Personnalisé |
La commande du lecteur
COMMAND_SEEK_TO_PREVIOUS ou
COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM est disponible.
|
Précédent | |
| Aucun bouton personnalisé ni aucune des commandes listées n'est disponible. | Vide | |
| 3 |
Les préférences des boutons multimédias contiennent un bouton personnalisé pour
CommandButton.SLOT_FORWARD
|
Personnalisé |
La commande du lecteur
COMMAND_SEEK_TO_NEXT ou
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM est disponible.
|
Suivant | |
| Aucun bouton personnalisé ni aucune des commandes listées n'est disponible. | Vide | |
| 4 |
Les
préférences des boutons multimédias contiennent un bouton personnalisé pour
CommandButton.SLOT_OVERFLOW qui n'a pas encore été placé.
|
Personnalisé |
| 5 |
Les
préférences des boutons multimédias contiennent un bouton personnalisé pour
CommandButton.SLOT_OVERFLOW qui n'a pas encore été placé.
|
Personnalisé |
Les boutons de dépassement personnalisés sont placés dans l'ordre dans lequel ils ont été ajoutés aux préférences des boutons multimédias.
Personnaliser les boutons de commande
Pour personnaliser les commandes multimédias du système avec Jetpack Media3, vous pouvez définir les préférences des boutons multimédias de la session et les commandes disponibles des contrôleurs en conséquence :
Créez une
MediaSessionet définissez les préférences des boutons multimédias pour les boutons de commande personnalisés.Dans
MediaSession.Callback.onConnect(), autorisez les contrôleurs en définissant leurs commandes disponibles, y compris les commandes personnalisées, dans leConnectionResult.Dans
MediaSession.Callback.onCustomCommand(), répondez à la commande personnalisée sélectionnée par l'utilisateur.
Kotlin
class PlaybackService : MediaSessionService() { private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY) private var mediaSession: MediaSession? = null override fun onCreate() { super.onCreate() val favoriteButton = CommandButton.Builder(CommandButton.ICON_HEART_UNFILLED) .setDisplayName("Save to favorites") .setSessionCommand(customCommandFavorites) .build() val player = ExoPlayer.Builder(this).build() // Build the session with a custom layout. mediaSession = MediaSession.Builder(this, player) .setCallback(MyCallback()) .setMediaButtonPreferences(ImmutableList.of(favoriteButton)) .build() } private inner class MyCallback : MediaSession.Callback { override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { // Set available player and session commands. return AcceptedResultBuilder(session) .setAvailableSessionCommands( ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(customCommandFavorites) .build() ) .build() } override fun onCustomCommand( session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle ): ListenableFuture{ if (customCommand.customAction == ACTION_FAVORITES) { // Do custom logic here saveToFavorites(session.player.currentMediaItem) return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } return super.onCustomCommand(session, controller, customCommand, args) } } }
Java
public class PlaybackService extends MediaSessionService { private static final SessionCommand CUSTOM_COMMAND_FAVORITES = new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY); @Nullable private MediaSession mediaSession; public void onCreate() { super.onCreate(); CommandButton favoriteButton = new CommandButton.Builder(CommandButton.ICON_HEART_UNFILLED) .setDisplayName("Save to favorites") .setSessionCommand(CUSTOM_COMMAND_FAVORITES) .build(); Player player = new ExoPlayer.Builder(this).build(); // Build the session with a custom layout. mediaSession = new MediaSession.Builder(this, player) .setCallback(new MyCallback()) .setMediaButtonPreferences(ImmutableList.of(favoriteButton)) .build(); } private static class MyCallback implements MediaSession.Callback { @Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { // Set available player and session commands. return new AcceptedResultBuilder(session) .setAvailableSessionCommands( ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(CUSTOM_COMMAND_FAVORITES) .build()) .build(); } public ListenableFutureonCustomCommand( MediaSession session, MediaSession.ControllerInfo controller, SessionCommand customCommand, Bundle args) { if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) { // Do custom logic here saveToFavorites(session.getPlayer().getCurrentMediaItem()); return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS)); } return MediaSession.Callback.super.onCustomCommand( session, controller, customCommand, args); } } }
Pour en savoir plus sur la configuration de votre MediaSession afin que les clients tels que le
système puissent se connecter à votre application multimédia, consultez
Accorder le contrôle à d'autres clients.
Avec Jetpack Media3, lorsque vous implémentez une MediaSession, votre PlaybackState est automatiquement mis à jour avec le lecteur multimédia. De même, lorsque vous
implémentez un MediaSessionService, la bibliothèque publie automatiquement une
MediaStyle notification
pour vous et la tient à jour.
Répondre aux boutons d'action
Lorsqu'un utilisateur appuie sur un bouton d'action dans les commandes multimédias du système, le MediaController du système envoie une commande de lecture à votre MediaSession. La MediaSession délègue ensuite ces commandes au lecteur. Les commandes
définies dans l'interface Player
`Player` de Media3 sont automatiquement gérées par la session
multimédia.
Pour savoir comment répondre à une commande personnalisée, consultez Ajouter des commandes personnalisées.
Prendre en charge la reprise de la lecture du contenu multimédia
La reprise de la lecture du contenu multimédia permet aux utilisateurs de redémarrer des sessions précédentes à partir du carrousel sans avoir à démarrer l'application. Lorsque la lecture commence, l'utilisateur interagit avec les commandes multimédias de manière habituelle.
La fonctionnalité de reprise de la lecture peut être activée et désactivée à l'aide de l'application Réglages, sous les options Son > Multimédia. L'utilisateur peut également accéder aux paramètres en appuyant sur l'icône en forme de roue dentée qui s'affiche après avoir balayé le carrousel développé.
Media3 propose des API pour faciliter la prise en charge de la reprise de la lecture du contenu multimédia. Pour savoir comment implémenter cette fonctionnalité, consultez la documentation Reprise de la lecture avec Media3.
Utiliser les anciennes API multimédias
Cette section explique comment intégrer les commandes multimédias du système à l'aide des anciennes API MediaCompat.
Le système récupère les informations suivantes à partir de MediaMetadata de MediaSession et les affiche lorsqu'elles sont disponibles :
METADATA_KEY_ALBUM_ART_URIMETADATA_KEY_TITLEMETADATA_KEY_DISPLAY_TITLEMETADATA_KEY_ARTISTMETADATA_KEY_DURATION(si la durée n'est pas définie, la barre de recherche n'affiche pas la progression)
Pour vous assurer de disposer d'une notification de commande multimédia valide et précise, définissez la valeur des métadonnées METADATA_KEY_TITLE ou METADATA_KEY_DISPLAY_TITLE sur le titre du contenu multimédia en cours de lecture.
Le lecteur multimédia affiche le temps écoulé pour le contenu multimédia en cours de lecture, ainsi qu'une barre de recherche mappée à PlaybackState de MediaSession.
Le lecteur multimédia affiche la progression du contenu multimédia en cours de lecture, ainsi qu'une barre de recherche mappée à PlaybackState de MediaSession. La barre de recherche permet aux utilisateurs de modifier la position et affiche le temps écoulé pour l'élément multimédia. Pour que la barre de recherche soit activée, vous devez implémenter PlaybackState.Builder#setActions et inclure ACTION_SEEK_TO.
| Encoche | Action | Critères |
|---|---|---|
| 1 | Lire |
L'état actuel de PlaybackState est l'un des suivants :
|
| Icône de chargement en cours |
L'état actuel du PlaybackState est l'un des suivants :
|
|
| Pause | L'état actuel de state du PlaybackState n'est aucun des états ci-dessus. |
|
| 2 | Précédent | PlaybackState actions incluent ACTION_SKIP_TO_PREVIOUS. |
| Personnalisé | PlaybackState actions n'incluent pas ACTION_SKIP_TO_PREVIOUS, et les PlaybackState actions personnalisées incluent une action personnalisée qui n'a pas encore été placée. |
|
| Vide | PlaybackState extras incluent une valeur booléenne true pour la clé SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV. |
|
| 3 | Suivant | PlaybackState actions incluent ACTION_SKIP_TO_NEXT. |
| Personnalisé | PlaybackState actions n'incluent pas ACTION_SKIP_TO_NEXT, et les PlaybackState actions personnalisées incluent une action personnalisée qui n'a pas encore été placée. |
|
| Vide | PlaybackState extras incluent une valeur booléenne true pour la clé SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT. |
|
| 4 | Personnalisé | PlaybackState actions personnalisées incluent une action personnalisée qui n'a pas encore été placée. |
| 5 | Personnalisé | PlaybackState actions personnalisées incluent une action personnalisée qui n'a pas encore été placée. |
Ajouter des actions standards
Les exemples de code suivants montrent comment ajouter des actions standards et personnalisées PlaybackState.
Pour les actions "Lire", "Pause", "Précédent" et "Suivant", définissez-les dans PlaybackState pour la session multimédia.
Kotlin
val session = MediaSessionCompat(context, TAG) val playbackStateBuilder = PlaybackStateCompat.Builder() val style = NotificationCompat.MediaStyle() // For this example, the media is currently paused: val state = PlaybackStateCompat.STATE_PAUSED val position = 0L val playbackSpeed = 1f playbackStateBuilder.setState(state, position, playbackSpeed) // And the user can play, skip to next or previous, and seek val stateActions = PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or PlaybackStateCompat.ACTION_SKIP_TO_NEXT or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar playbackStateBuilder.setActions(stateActions) // ... do more setup here ... session.setPlaybackState(playbackStateBuilder.build()) style.setMediaSession(session.sessionToken) notificationBuilder.setStyle(style)
Java
MediaSessionCompat session = new MediaSessionCompat(context, TAG); PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder(); NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle(); // For this example, the media is currently paused: int state = PlaybackStateCompat.STATE_PAUSED; long position = 0L; float playbackSpeed = 1f; playbackStateBuilder.setState(state, position, playbackSpeed); // And the user can play, skip to next or previous, and seek long stateActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb playbackStateBuilder.setActions(stateActions); // ... do more setup here ... session.setPlaybackState(playbackStateBuilder.build()); style.setMediaSession(session.getSessionToken()); notificationBuilder.setStyle(style);
Si vous ne souhaitez aucun bouton dans les emplacements précédents ou suivants, n'ajoutez pas ACTION_SKIP_TO_PREVIOUS ni ACTION_SKIP_TO_NEXT, mais ajoutez plutôt des extras à la session :
Kotlin
session.setExtras(Bundle().apply { putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true) putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true) })
Java
Bundle extras = new Bundle(); extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true); extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true); session.setExtras(extras);
Ajouter des actions personnalisées
Pour les autres actions que vous souhaitez afficher sur les commandes multimédias, vous pouvez créer une
PlaybackStateCompat.CustomAction
et l'ajouter à PlaybackState à la place. Ces actions sont affichées dans l'ordre dans lequel elles ont été ajoutées.
Kotlin
val customAction = PlaybackStateCompat.CustomAction.Builder( "com.example.MY_CUSTOM_ACTION", // action ID "Custom Action", // title - used as content description for the button R.drawable.ic_custom_action ).build() playbackStateBuilder.addCustomAction(customAction)
Java
PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder( "com.example.MY_CUSTOM_ACTION", // action ID "Custom Action", // title - used as content description for the button R.drawable.ic_custom_action ).build(); playbackStateBuilder.addCustomAction(customAction);
Répondre aux actions PlaybackState
Lorsqu'un utilisateur appuie sur un bouton, SystemUI utilise
MediaController.TransportControls
pour renvoyer une commande à MediaSession. Vous devez enregistrer un rappel qui peut répondre correctement à ces événements.
Kotlin
val callback = object: MediaSession.Callback() { override fun onPlay() { // start playback } override fun onPause() { // pause playback } override fun onSkipToPrevious() { // skip to previous } override fun onSkipToNext() { // skip to next } override fun onSeekTo(pos: Long) { // jump to position in track } override fun onCustomAction(action: String, extras: Bundle?) { when (action) { CUSTOM_ACTION_1 -> doCustomAction1(extras) CUSTOM_ACTION_2 -> doCustomAction2(extras) else -> { Log.w(TAG, "Unknown custom action $action") } } } } session.setCallback(callback)
Java
MediaSession.Callback callback = new MediaSession.Callback() { @Override public void onPlay() { // start playback } @Override public void onPause() { // pause playback } @Override public void onSkipToPrevious() { // skip to previous } @Override public void onSkipToNext() { // skip to next } @Override public void onSeekTo(long pos) { // jump to position in track } @Override public void onCustomAction(String action, Bundle extras) { if (action.equals(CUSTOM_ACTION_1)) { doCustomAction1(extras); } else if (action.equals(CUSTOM_ACTION_2)) { doCustomAction2(extras); } else { Log.w(TAG, "Unknown custom action " + action); } } };
Reprise de la lecture du contenu multimédia
Pour que votre application de lecteur s'affiche dans la zone des paramètres rapides, vous devez créer une notification MediaStyle avec un jeton MediaSession valide.
Pour afficher le titre de la notification MediaStyle, utilisez NotificationBuilder.setContentTitle().
Pour afficher l'icône de la marque du lecteur multimédia, utilisez NotificationBuilder.setSmallIcon().
Pour prendre en charge la reprise de la lecture, les applications doivent implémenter un MediaBrowserService et une MediaSession. Votre MediaSession doit implémenter le rappel onPlay().
Implémentation de MediaBrowserService
Une fois l'appareil démarré, le système recherche les cinq applications multimédias les plus récemment utilisées et fournit des commandes permettant de reprendre la lecture à partir de chaque application.
Le système tente de contacter votre MediaBrowserService avec une connexion depuis SystemUI. Votre application doit autoriser ces connexions, sinon elle ne peut pas prendre en charge la reprise de la lecture.
Les connexions de SystemUI peuvent être identifiées et vérifiées à l'aide du nom de package com.android.systemui et de la signature. SystemUI est signé avec la signature de la plate-forme. Vous trouverez un exemple de vérification par rapport à la signature de la plate-forme dans l'application UAMP.
Pour prendre en charge la reprise de la lecture, votre MediaBrowserService doit implémenter les comportements suivants :
onGetRoot()doit renvoyer rapidement une racine non nulle. Les autres logiques complexes doivent être gérées dansonLoadChildren().Lorsque
onLoadChildren()est appelé sur l'ID de contenu multimédia racine, le résultat doit contenir un enfant FLAG_PLAYABLE.MediaBrowserServicedoit renvoyer l'élément multimédia le plus récemment lu lorsqu' il reçoit une requête EXTRA_RECENT. La valeur renvoyée doit être un élément multimédia réel plutôt qu'une fonction générique.MediaBrowserServicedoit fournir une MediaDescription appropriée avec un titre et un sous-titre non vides. Il doit également définir un URI d'icône ou un bitmap d'icône.
Les exemples de code suivants montrent comment implémenter onGetRoot().
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot? { ... // Verify that the specified package is SystemUI. You'll need to write your // own logic to do this. if (isSystem(clientPackageName, clientUid)) { rootHints?.let { if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) { // Return a tree with a single playable media item for resumption. val extras = Bundle().apply { putBoolean(BrowserRoot.EXTRA_RECENT, true) } return BrowserRoot(MY_RECENTS_ROOT_ID, extras) } } // You can return your normal tree if the EXTRA_RECENT flag is not present. return BrowserRoot(MY_MEDIA_ROOT_ID, null) } // Return an empty tree to disallow browsing. return BrowserRoot(MY_EMPTY_ROOT_ID, null)
Java
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { ... // Verify that the specified package is SystemUI. You'll need to write your // own logic to do this. if (isSystem(clientPackageName, clientUid)) { if (rootHints != null) { if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) { // Return a tree with a single playable media item for resumption. Bundle extras = new Bundle(); extras.putBoolean(BrowserRoot.EXTRA_RECENT, true); return new BrowserRoot(MY_RECENTS_ROOT_ID, extras); } } // You can return your normal tree if the EXTRA_RECENT flag is not present. return new BrowserRoot(MY_MEDIA_ROOT_ID, null); } // Return an empty tree to disallow browsing. return new BrowserRoot(MY_EMPTY_ROOT_ID, null); }
Comportement antérieur à Android 13
Pour assurer la rétrocompatibilité, l'UI du système continue de fournir une mise en page alternative qui utilise des actions de notification pour les applications qui ne sont pas mises à jour pour cibler Android 13 ou qui n'incluent pas d'informations PlaybackState. Les boutons d'action sont dérivés de la liste Notification.Action associée à la notification MediaStyle. Le système affiche jusqu'à cinq actions dans l'ordre dans lequel elles ont été ajoutées. En mode compact, jusqu'à trois boutons sont affichés, déterminés par les
valeurs transmises à
setShowActionsInCompactView().
Les actions personnalisées sont placées dans l'ordre dans lequel elles ont été ajoutées à PlaybackState.
L'exemple de code suivant montre comment ajouter des actions à la notification MediaStyle :
Kotlin
import androidx.core.app.NotificationCompat import androidx.media3.session.MediaStyleNotificationHelper var notification = NotificationCompat.Builder(context, CHANNEL_ID) // Show controls on lock screen even when user hides sensitive content. .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setSmallIcon(R.drawable.ic_stat_player) // Add media control buttons that invoke intents in your media service .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0 .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1 .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2 // Apply the media style template .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession) .setShowActionsInCompactView(1 /* #1: pause button */)) .setContentTitle("Wonderful music") .setContentText("My Awesome Band") .setLargeIcon(albumArtBitmap) .build()
Java
import androidx.core.app.NotificationCompat; import androidx.media3.session.MediaStyleNotificationHelper; NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID) // Show controls on lock screen even when user hides sensitive content. .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setSmallIcon(R.drawable.ic_stat_player) // Add media control buttons that invoke intents in your media service .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0 .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1 .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2 // Apply the media style template .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession) .setShowActionsInCompactView(1 /* #1: pause button */)) .setContentTitle("Wonderful music") .setContentText("My Awesome Band") .setLargeIcon(albumArtBitmap) .build();