Les commandes multimédias sous Android se trouvent à côté des Réglages rapides. Sessions depuis plusieurs applications sont organisées dans un carrousel à faire glisser. Le carrousel répertorie les sessions dans cet ordre:
- Diffusions en cours en local sur le téléphone
- Les flux distants, tels que ceux détectés sur des appareils externes ou de sessions Cast
- Sessions avec reprise précédentes, dans l'ordre de leur dernière utilisation
À partir d'Android 13 (niveau d'API 33), pour garantir aux utilisateurs l'accès à des
ensemble de commandes multimédias pour les applications qui lisent des contenus multimédias, boutons d'action sur les commandes multimédias
sont dérivées de l'état Player
.
Vous pouvez ainsi présenter un ensemble cohérent de commandes multimédias du contrôle des contenus multimédias sur tous les appareils.
La figure 1 illustre ce à quoi cela ressemble sur un téléphone et une tablette. respectivement.
<ph type="x-smartling-placeholder">Le système affiche jusqu'à cinq boutons d'action en fonction de l'état Player
, comme suit :
décrites dans le tableau suivant. En mode compact, seules les trois premières actions
d'emplacements s'affichent. Cela correspond à l'affichage des commandes multimédias dans d'autres
Plates-formes Android telles que Auto, Assistant et Wear OS.
Encoche | Critères | Action |
---|---|---|
1 |
playWhenReady
est "false" ou la lecture en cours
est STATE_ENDED .
|
Lire |
playWhenReady est défini sur "true" et l'état de lecture actuel est STATE_BUFFERING .
|
Boucle de chargement | |
playWhenReady est défini sur "true" et l'état de lecture actuel est STATE_READY . |
Mettre en pause | |
2 | La commande de lecteur COMMAND_SEEK_TO_PREVIOUS ou COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM est disponible. |
Précédent |
Aucune commande de lecteur COMMAND_SEEK_TO_PREVIOUS ni COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM n'est disponible, et une commande personnalisée de la mise en page personnalisée qui n'a pas encore été placée est disponible pour occuper l'emplacement. |
Personnalisé | |
(pas encore pris en charge avec Media3) PlaybackState Les extras incluent une valeur booléenne true pour la clé EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV . |
Vide | |
3 | La commande de lecteur COMMAND_SEEK_TO_NEXT ou COMMAND_SEEK_TO_NEXT_MEDIA_ITEM est disponible. |
Suivant |
Aucune commande de lecteur COMMAND_SEEK_TO_NEXT ni COMMAND_SEEK_TO_NEXT_MEDIA_ITEM n'est disponible, et une commande personnalisée de la mise en page personnalisée qui n'a pas encore été placée est disponible pour occuper l'emplacement. |
Personnalisé | |
(pas encore pris en charge avec Media3) PlaybackState Les extras incluent une valeur booléenne true pour la clé EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT . |
Vide | |
4 | Une commande personnalisée issue de la mise en page personnalisée qui n'a pas encore été placée est disponible pour remplir l'emplacement. | Personnalisé |
5 | Une commande personnalisée issue de la mise en page personnalisée qui n'a pas encore été placée est disponible pour remplir l'emplacement. | Personnalisé |
Les commandes personnalisées sont placées dans l'ordre dans lequel elles ont été ajoutées au mise en page personnalisée.
Personnaliser les boutons de commande
Pour personnaliser les commandes multimédias du système avec Jetpack Media3,
vous pouvez définir la mise en page personnalisée de la session et les commandes disponibles pour
contrôleurs en conséquence, lors de l'implémentation d'un MediaSessionService
:
Dans
onCreate()
, créez unMediaSession
. et définir la mise en page personnalisée de boutons de commande.Dans
MediaSession.Callback.onConnect()
, des contrôleurs en définissant leurs commandes disponibles, y compris des commandes personnalisées, dans leConnectionResult
.Dans
MediaSession.Callback.onCustomCommand()
, répondre à 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() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(customCommandFavorites) .build() val player = ExoPlayer.Builder(this).build() // Build the session with a custom layout. mediaSession = MediaSession.Builder(this, player) .setCallback(MyCallback()) .setCustomLayout(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) .setAvailablePlayerCommands( ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .build() ) .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() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .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()) .setCustomLayout(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) .setAvailablePlayerCommands( ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .build()) .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 comme le
peut se connecter à votre application multimédia, consultez
Accorder le contrôle à d'autres clients.
Avec Jetpack Media3, lorsque vous implémentez un MediaSession
, votre PlaybackState
est automatiquement mis à jour avec le lecteur multimédia. De même, lorsque vous
implémenter un MediaSessionService
, la bibliothèque publie automatiquement
MediaStyle
notification
pour vous et le met à jour.
Répondre aux boutons d'action
Lorsqu'un utilisateur appuie sur un bouton d'action dans les commandes multimédias du système, l'événement
MediaController
envoie une commande de lecture à votre MediaSession
. La
MediaSession
délègue ensuite ces commandes au joueur. Commandes
défini dans les Player
de Media3
sont gérés automatiquement par le service
session.
Consultez Ajouter des commandes personnalisées. pour savoir comment répondre à une commande personnalisée.
Comportement des versions antérieures à Android 13
Pour assurer la rétrocompatibilité, l'UI du système continue de fournir une autre mise en page
qui utilise des actions de notification pour les applications qui ne se mettent pas à jour pour cibler Android 13,
ou qui n'incluent pas d'informations PlaybackState
. Les boutons d'action sont
dérivée de la liste Notification.Action
associée à MediaStyle
. Le système affiche jusqu'à cinq actions, dans l'ordre dans lequel elles
ont été ajoutées. En mode compact, trois boutons maximum s'affichent. Ils sont déterminés par le
valeurs transmises dans setShowActionsInCompactView()
.
Les actions personnalisées sont placées dans l'ordre dans lequel elles ont été ajoutées au
PlaybackState
L'exemple de code suivant montre comment ajouter des actions au MediaStyle. notification :
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) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setSmallIcon(R.drawable.ic_stat_player) .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) .addAction(R.drawable.ic_next, "Next", nextPendingIntent) .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession) .setShowActionsInCompactView(1 /* #1: pause button */)) .setContentTitle("Wonderful music") .setContentText("My Awesome Band") .setLargeIcon(albumArtBitmap) .build();
Permettre la reprise de la lecture de contenus multimédias
La reprise de lecture de contenus multimédias permet aux utilisateurs de redémarrer des sessions précédentes à partir du carrousel sans avoir à lancer l'application. Lorsque la lecture commence, l'utilisateur interagit avec les commandes multimédias comme d'habitude.
Vous pouvez activer ou désactiver la reprise de lecture dans l'application Paramètres, sous Son > Options multimédias. L'utilisateur peut également accéder aux paramètres en appuyant sur l'icône en forme de roue dentée qui apparaît après avoir balayé le carrousel développé.
Media3 propose des API permettant de faciliter la reprise de la lecture de contenus multimédias. Consultez le Reprise de la lecture avec Media3 pour obtenir des conseils sur l'implémentation de cette fonctionnalité.
Utiliser les anciennes API multimédias
Cette section explique comment intégrer les commandes multimédias du système à l'aide de les anciennes API MediaCompat.
Le système récupère les informations suivantes à partir du
MediaMetadata
de MediaSession
, et l'affiche lorsqu'il est disponible:
METADATA_KEY_ALBUM_ART_URI
METADATA_KEY_TITLE
METADATA_KEY_DISPLAY_TITLE
METADATA_KEY_ARTIST
METADATA_KEY_DURATION
(Si la durée n'est pas définie, la barre de recherche afficher la progression)
Pour vous assurer de disposer d'une notification
valide et précise avec les commandes multimédias,
définissez la valeur de METADATA_KEY_TITLE
ou METADATA_KEY_DISPLAY_TITLE
au titre du contenu multimédia en cours de lecture.
Le lecteur multimédia affiche le temps écoulé pour la lecture
contenu multimédia, ainsi qu'une barre de recherche mappée au MediaSession
.
PlaybackState
Le lecteur multimédia affiche la progression du contenu multimédia en cours de lecture, ainsi que
Une barre de recherche mappée au PlaybackState
MediaSession
Barre de recherche
permet aux utilisateurs de changer de position et affiche le temps écoulé pour le contenu multimédia
élément. Pour activer la barre de recherche, 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:
<ph type="x-smartling-placeholder">
|
Boucle de chargement |
L'état actuel de PlaybackState est l'un des suivants:
<ph type="x-smartling-placeholder">
|
|
Mettre en pause | L'état actuel de PlaybackState n'est aucun des éléments ci-dessus. |
|
2 | Précédent | Les actions PlaybackState incluent ACTION_SKIP_TO_PREVIOUS . |
Personnalisé | Les actions PlaybackState n'incluent pas les actions personnalisées ACTION_SKIP_TO_PREVIOUS et PlaybackState , qui incluent une action personnalisée qui n'a pas encore été placée. |
|
Vide | Les extras PlaybackState incluent une valeur booléenne true pour la clé SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV . |
|
3 | Suivant | Les actions PlaybackState incluent ACTION_SKIP_TO_NEXT . |
Personnalisé | Les actions PlaybackState n'incluent pas les actions personnalisées ACTION_SKIP_TO_NEXT et PlaybackState , qui incluent une action personnalisée qui n'a pas encore été placée. |
|
Vide | Les extras PlaybackState incluent une valeur booléenne true pour la clé SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT . |
|
4 | Personnalisé | Les actions personnalisées PlaybackState incluent une action personnalisée qui n'a pas encore été placée. |
5 | Personnalisé | Les actions personnalisées PlaybackState 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 le standard PlaybackState
et
des actions personnalisées.
Pour lire, mettre en pause, précédent et suivant, définissez ces actions 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 voulez pas de boutons à l'emplacement précédent ou suivant, n'ajoutez pas
ACTION_SKIP_TO_PREVIOUS
ou ACTION_SKIP_TO_NEXT
, et ajoutez à la place 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 un
PlaybackStateCompat.CustomAction
et l'ajouter à PlaybackState
à la place. Ces actions sont indiquées dans la section
dans l'ordre où 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
capables de 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 apparaisse dans la zone des paramètres rapides, procédez comme suit :
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 permettre la reprise de la lecture, les applications doivent implémenter un MediaBrowserService
.
et MediaSession
. Votre MediaSession
doit implémenter le rappel onPlay()
.
Implémentation de MediaBrowserService
Après le démarrage de l'appareil, le système recherche les cinq derniers supports utilisés. applications et fournit des commandes permettant de reprendre la lecture depuis chaque application.
Le système tente de contacter votre MediaBrowserService
via une connexion provenant de
SystemUI (UI du système). Votre application doit autoriser de telles connexions, sinon elle n'est pas compatible
pour reprendre la lecture.
Les connexions de SystemUI peuvent être identifiées et vérifiées à l'aide du nom du package
com.android.systemui
et signature. L'UI System
est signée avec la plate-forme
signature. Voici un exemple de vérification par rapport à la signature de la plate-forme :
disponible dans l'application UAMP.
Pour permettre la reprise de la lecture, votre MediaBrowserService
doit
implémenter ces comportements:
onGetRoot()
doit renvoyer rapidement une racine non nulle. D'autres logiques complexes être géré dansonLoadChildren()
Quand ?
onLoadChildren()
est appelé sur l'ID d'élément multimédia racine, le résultat doit contenir un FLAG_PLAYABLE enfant.MediaBrowserService
doit renvoyer le dernier élément multimédia lu lorsque elle reçoit une EXTRA_RÉCENT requête. La valeur renvoyée doit être un élément multimédia réel et non générique. .MediaBrowserService
doit fournir un MediaDescription avec title et sous-titre. Vous devez également définir URI de l'icône ou bitmap de l'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); }