Contrôler et annoncer la lecture avec MediaSession

Les sessions multimédias offrent un moyen universel d'interagir avec un lecteur audio ou vidéo. Dans Media3, le lecteur par défaut est la classe ExoPlayer, qui implémente l'interface Player. La connexion de la session multimédia au lecteur permet à une application d'annoncer la lecture multimédia en externe et de recevoir des commandes de lecture de sources externes.

Les commandes peuvent provenir de boutons physiques, tels que le bouton de lecture d'un casque ou de la télécommande d'un téléviseur. Elles peuvent également provenir d'applications clientes dotées d'un contrôleur multimédia, par exemple en demandant à l'Assistant Google de "mettre en pause". La session multimédia délègue ces commandes au lecteur de l'application multimédia.

Quand choisir une session multimédia ?

Lorsque vous implémentez MediaSession, vous autorisez les utilisateurs à contrôler la lecture:

  • À l'aide d'écouteurs. L'utilisateur peut souvent utiliser des boutons ou des interactions tactiles avec son casque pour lire ou mettre en pause du contenu multimédia, ou passer à la piste suivante ou précédente.
  • En parlant à l'Assistant Google Une pratique courante consiste à dire Ok Google, pause pour suspendre la lecture d'un contenu multimédia sur l'appareil.
  • Via sa montre Wear OS. Cela permet d'accéder plus facilement aux commandes de lecture les plus courantes pendant une lecture sur leur téléphone.
  • Via les commandes multimédias Ce carrousel affiche les commandes de chaque session multimédia en cours d'exécution.
  • Sur un téléviseur. Autorise les actions avec des boutons de lecture physiques, le contrôle de la lecture de la plate-forme et la gestion de l'alimentation (par exemple, si le téléviseur, la barre de son ou le récepteur A/V s'éteint ou si l'entrée est modifiée, la lecture doit s'arrêter dans l'application).
  • et tout autre processus externe devant influencer la lecture.

C'est une solution idéale pour de nombreux cas d'utilisation. En particulier, nous vous conseillons vivement d'utiliser MediaSession dans les cas suivants:

  • Vous diffusez du contenu vidéo long, comme des films ou la télévision en direct.
  • Vous diffusez en streaming des contenus audio longs, tels que des podcasts ou des playlists musicales.
  • Vous êtes en train de créer une application TV.

Cependant, tous les cas d'utilisation ne sont pas adaptés à MediaSession. Vous pouvez n'utiliser que l'élément Player dans les cas suivants:

  • Vous diffusez des contenus courts, pour lesquels l'engagement et les interactions des utilisateurs sont essentiels.
  • Il n'y a pas de vidéo active (par exemple, lorsque l'utilisateur fait défiler une liste) et plusieurs vidéos s'affichent en même temps à l'écran.
  • Vous regardez une vidéo d'introduction ou d'explication ponctuelle que vous souhaitez que l'utilisateur regarde activement.
  • Votre contenu est sensible à la confidentialité et vous ne souhaitez pas que des processus externes accèdent aux métadonnées multimédias (par exemple, en mode navigation privée dans un navigateur).

Si votre cas d'utilisation ne correspond à aucun des cas listés ci-dessus, déterminez si votre application peut poursuivre la lecture lorsque l'utilisateur n'interagit pas activement avec le contenu. Si la réponse est "Oui", vous devriez probablement choisir MediaSession. Si la réponse est non, utilisez plutôt Player.

Créer une session multimédia

Une session multimédia est également exécutée en même temps que le lecteur qu'elle gère. Vous pouvez créer une session multimédia avec un objet Context et un objet Player. Vous devez créer et initialiser une session multimédia en cas de besoin, par exemple la méthode de cycle de vie onStart() ou onResume() de Activity ou Fragment, ou la méthode onCreate() du Service propriétaire de la session multimédia et du lecteur associé.

Pour créer une session multimédia, initialisez un Player et fournissez-le à MediaSession.Builder comme suit:

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

Gestion automatique de l'état

La bibliothèque Media3 met automatiquement à jour la session multimédia en fonction de l'état du lecteur. Ainsi, vous n'avez pas besoin de gérer manuellement le mappage du lecteur à la session.

Il s'agit d'une rupture par rapport à l'ancienne approche, où vous deviez créer et gérer un PlaybackStateCompat indépendamment du lecteur lui-même, par exemple pour signaler toute erreur.

ID de session unique

Par défaut, MediaSession.Builder crée une session avec une chaîne vide comme ID de session. Cela suffit si une application a l'intention de créer une seule instance de session, ce qui est le cas le plus courant.

Si une application souhaite gérer plusieurs instances de session en même temps, elle doit s'assurer que l'ID de chaque session est unique. L'ID de session peut être défini lors de la création de la session avec MediaSession.Builder.setId(String id).

Si une IllegalStateException fait planter votre application avec le message d'erreur IllegalStateException: Session ID must be unique. ID=, il est probable qu'une session ait été créée de manière inattendue avant la publication d'une instance précédemment créée avec le même ID. Pour éviter que des sessions ne soient divulguées par une erreur de programmation, de tels cas sont détectés et avertis en générant une exception.

Accorder le contrôle à d'autres clients

La session multimédia est la clé pour contrôler la lecture. Il vous permet de router des commandes depuis des sources externes vers le lecteur qui lit vos contenus multimédias. Il peut s'agir de boutons physiques, comme le bouton de lecture d'un casque ou d'une télécommande de téléviseur, ou de commandes indirectes telles que l'instruction de "pause" à l'Assistant Google. De même, vous pouvez autoriser l'accès au système Android pour faciliter les commandes de notification et de l'écran de verrouillage, ou à une montre Wear OS pour contrôler la lecture depuis le cadran. Les clients externes peuvent utiliser un contrôleur multimédia pour envoyer des commandes de lecture à votre application multimédia. Celles-ci sont reçues par votre session multimédia, qui délègue les commandes au lecteur multimédia.

Schéma illustrant l'interaction entre MediaSession et MediaController
Figure 1: Le contrôleur multimédia facilite la transmission de commandes depuis des sources externes à la session multimédia.

Lorsqu'une manette est sur le point de se connecter à votre session multimédia, la méthode onConnect() est appelée. Vous pouvez utiliser le ControllerInfo fourni pour décider d'accepter ou de rejeter la requête. Consultez un exemple d'acceptation d'une requête de connexion dans la section Déclarer les commandes disponibles.

Une fois connectée, la manette peut envoyer des commandes de lecture à la session. La session délègue ensuite ces commandes au joueur. Les commandes de lecture et de playlist définies dans l'interface Player sont automatiquement gérées par la session.

D'autres méthodes de rappel vous permettent de gérer, par exemple, les requêtes de commandes de lecture personnalisées et la modification de la playlist. Ces rappels incluent également un objet ControllerInfo afin que vous puissiez modifier la façon dont vous répondez à chaque requête pour chaque contrôleur.

Modifier la playlist

Une session multimédia peut modifier directement la playlist de son lecteur, comme expliqué dans le guide ExoPlayer pour les playlists. Les contrôleurs peuvent également modifier la playlist si COMMAND_SET_MEDIA_ITEM ou COMMAND_CHANGE_MEDIA_ITEMS est disponible.

Lors de l'ajout de nouveaux éléments à la playlist, le lecteur a généralement besoin d'instances MediaItem avec un URI défini pour les rendre lisibles. Par défaut, les éléments nouvellement ajoutés sont automatiquement transférés vers les méthodes du lecteur telles que player.addMediaItem si un URI est défini.

Si vous souhaitez personnaliser les instances MediaItem ajoutées au lecteur, vous pouvez remplacer onAddMediaItems(). Cette étape est nécessaire lorsque vous souhaitez prendre en charge les contrôleurs qui demandent des contenus multimédias sans URI défini. Au lieu de cela, le MediaItem comporte généralement un ou plusieurs des champs suivants définis pour décrire le contenu multimédia demandé:

  • MediaItem.id: ID générique identifiant le support.
  • MediaItem.RequestMetadata.mediaUri: URI de requête pouvant utiliser un schéma personnalisé et que le joueur ne peut pas nécessairement lire directement.
  • MediaItem.RequestMetadata.searchQuery: requête de recherche textuelle, par exemple de l'Assistant Google.
  • MediaItem.MediaMetadata: métadonnées structurées, telles que "titre" ou "artiste".

Pour accéder à davantage d'options de personnalisation pour les nouvelles playlists, vous pouvez également remplacer onSetMediaItems(), qui vous permet de définir l'élément de début et la position dans la playlist. Par exemple, vous pouvez développer un élément demandé dans une playlist entière et demander au joueur de commencer à l'index de l'élément demandé à l'origine. Vous trouverez un exemple d'implémentation de onSetMediaItems() avec cette fonctionnalité dans l'application de démonstration de session.

Gérer la mise en page et les commandes personnalisées

Les sections suivantes décrivent comment annoncer une mise en page personnalisée de boutons de commande personnalisés dans les applications clientes et comment autoriser les contrôleurs à envoyer les commandes personnalisées.

Définir la mise en page personnalisée de la session

Pour indiquer aux applications clientes les commandes de lecture que vous souhaitez proposer à l'utilisateur, définissez la mise en page personnalisée de la session lors de la création du MediaSession dans la méthode onCreate() de votre service.

Kotlin

override fun onCreate() {
  super.onCreate()

  val likeButton = CommandButton.Builder()
    .setDisplayName("Like")
    .setIconResId(R.drawable.like_icon)
    .setSessionCommand(SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
    .build()
  val favoriteButton = CommandButton.Builder()
    .setDisplayName("Save to favorites")
    .setIconResId(R.drawable.favorite_icon)
    .setSessionCommand(SessionCommand(SAVE_TO_FAVORITES, Bundle()))
    .build()

  session =
    MediaSession.Builder(this, player)
      .setCallback(CustomMediaSessionCallback())
      .setCustomLayout(ImmutableList.of(likeButton, favoriteButton))
      .build()
}

Java

@Override
public void onCreate() {
  super.onCreate();

  CommandButton likeButton = new CommandButton.Builder()
    .setDisplayName("Like")
    .setIconResId(R.drawable.like_icon)
    .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
    .build();
  CommandButton favoriteButton = new CommandButton.Builder()
    .setDisplayName("Save to favorites")
    .setIconResId(R.drawable.favorite_icon)
    .setSessionCommand(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
    .build();

  Player player = new ExoPlayer.Builder(this).build();
  mediaSession =
      new MediaSession.Builder(this, player)
          .setCallback(new CustomMediaSessionCallback())
          .setCustomLayout(ImmutableList.of(likeButton, favoriteButton))
          .build();
}

Déclarer les commandes personnalisées et celles du lecteur disponibles

Les applications multimédias peuvent définir des commandes personnalisées qui, par exemple, peuvent être utilisées dans une mise en page personnalisée. Par exemple, vous pouvez souhaiter implémenter des boutons permettant à l'utilisateur d'enregistrer un élément multimédia dans une liste d'éléments favoris. MediaController envoie des commandes personnalisées, et MediaSession.Callback les reçoit.

Vous pouvez définir les commandes de session personnalisées disponibles pour un MediaController lorsqu'il se connecte à votre session multimédia. Pour ce faire, remplacez MediaSession.Callback.onConnect(). Configurez et renvoyez l'ensemble des commandes disponibles lorsque vous acceptez une requête de connexion d'un MediaController dans la méthode de rappel onConnect:

Kotlin

private inner class CustomMediaSessionCallback: MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  override fun onConnect(
    session: MediaSession,
    controller: MediaSession.ControllerInfo
  ): MediaSession.ConnectionResult {
    val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build()
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  @Override
  public ConnectionResult onConnect(
    MediaSession session,
    ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
}

Pour recevoir des requêtes de commande personnalisées à partir d'un MediaController, remplacez la méthode onCustomCommand() dans Callback.

Kotlin

private inner class CustomMediaSessionCallback: MediaSession.Callback {
  ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: MediaSession.ControllerInfo,
    customCommand: SessionCommand,
    args: Bundle
  ): ListenableFuture<SessionResult> {
    if (customCommand.customAction == SAVE_TO_FAVORITES) {
      // Do custom logic here
      saveToFavorites(session.player.currentMediaItem)
      return Futures.immediateFuture(
        SessionResult(SessionResult.RESULT_SUCCESS)
      )
    }
    ...
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  ...
  @Override
  public ListenableFuture<SessionResult> onCustomCommand(
    MediaSession session, 
    ControllerInfo controller,
    SessionCommand customCommand,
    Bundle args
  ) {
    if(customCommand.customAction.equals(SAVE_TO_FAVORITES)) {
      // Do custom logic here
      saveToFavorites(session.getPlayer().getCurrentMediaItem());
      return Futures.immediateFuture(
        new SessionResult(SessionResult.RESULT_SUCCESS)
      );
    }
    ...
  }
}

Vous pouvez identifier le contrôleur multimédia qui envoie une requête à l'aide de la propriété packageName de l'objet MediaSession.ControllerInfo transmis aux méthodes Callback. Cela vous permet d'adapter le comportement de votre application en réponse à une commande donnée si elle provient du système, de votre propre application ou d'autres applications clientes.

Mettre à jour la mise en page personnalisée après une interaction de l'utilisateur

Après avoir géré une commande personnalisée ou toute autre interaction avec votre lecteur, vous pouvez mettre à jour la mise en page affichée dans l'interface utilisateur de la manette. Un exemple typique est un bouton d'activation qui change son icône après avoir déclenché l'action associée à ce bouton. Pour mettre à jour la mise en page, vous pouvez utiliser MediaSession.setCustomLayout:

Kotlin

val removeFromFavoritesButton = CommandButton.Builder()
  .setDisplayName("Remove from favorites")
  .setIconResId(R.drawable.favorite_remove_icon)
  .setSessionCommand(SessionCommand(REMOVE_FROM_FAVORITES, Bundle()))
  .build()
mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton))

Java

CommandButton removeFromFavoritesButton = new CommandButton.Builder()
  .setDisplayName("Remove from favorites")
  .setIconResId(R.drawable.favorite_remove_icon)
  .setSessionCommand(new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle()))
  .build();
mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton));

Personnaliser le comportement des commandes de lecture

Pour personnaliser le comportement d'une commande définie dans l'interface Player, telle que play() ou seekToNext(), encapsulez votre Player dans un ForwardingPlayer.

Kotlin

val player = ExoPlayer.Builder(context).build()

val forwardingPlayer = object : ForwardingPlayer(player) {
  override fun play() {
    // Add custom logic
    super.play()
  }

  override fun setPlayWhenReady(playWhenReady: Boolean) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady)
  }
}

val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) {
  @Override
  public void play() {
    // Add custom logic
    super.play();
  }

  @Override
  public void setPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady);
  }
};

MediaSession mediaSession = 
  new MediaSession.Builder(context, forwardingPlayer).build();

Pour en savoir plus sur ForwardingPlayer, consultez le guide ExoPlayer sur la personnalisation.

Identifier le contrôleur demandeur d'une commande du joueur

Lorsqu'un appel à une méthode Player est initié par un MediaController, vous pouvez identifier la source d'origine avec MediaSession.controllerForCurrentRequest et acquérir le ControllerInfo pour la requête actuelle:

Kotlin

class CallerAwareForwardingPlayer(player: Player) :
  ForwardingPlayer(player) {

  override fun seekToNext() {
    Log.d(
      "caller",
      "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}"
    )
    super.seekToNext()
  }
}

Java

public class CallerAwareForwardingPlayer extends ForwardingPlayer {
  public CallerAwareForwardingPlayer(Player player) {
    super(player);
  }

  @Override
  public void seekToNext() {
    Log.d(
        "caller",
        "seekToNext called from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    super.seekToNext();
  }
}

Réagir aux boutons multimédias

Les boutons multimédias sont des boutons physiques que l'on trouve sur les appareils Android et d'autres appareils périphériques, comme le bouton lecture/pause d'un casque Bluetooth. Media3 gère les événements du bouton multimédia à votre place lorsqu'ils arrivent à la session et appelle la méthode Player appropriée sur le lecteur de session.

Une application peut ignorer le comportement par défaut en remplaçant MediaSession.Callback.onMediaButtonEvent(Intent). Dans ce cas, l'application peut ou doit gérer elle-même toutes les spécificités de l'API.