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. Connecter la session multimédia au lecteur permet à une application de diffuser la lecture multimédia en externe et de recevoir des commandes de lecture à partir de sources externes.

Les commandes peuvent provenir de boutons physiques, tels que le bouton de lecture d'un casque ou d'une télécommande de 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 permettez aux utilisateurs de contrôler la lecture:

  • Via leur casque Il existe souvent des boutons ou des interactions tactiles qu'un utilisateur peut effectuer sur son casque pour lire ou mettre en pause un contenu multimédia, ou pour passer à la piste suivante ou précédente.
  • En parlant à l'Assistant Google Pour mettre en pause un contenu multimédia en cours de lecture sur l'appareil, vous pouvez dire "Ok Google, mets en pause".
  • Via sa montre Wear OS Cela permet d'accéder plus facilement aux commandes de lecture les plus courantes lorsqu'ils jouent sur leur téléphone.
  • Via les commandes multimédias. Ce carrousel présente des commandes pour chaque session multimédia en cours d'exécution.
  • Sur un téléviseur Autorise les actions avec les boutons de lecture physiques, les commandes de 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'éteignent ou que l'entrée est modifiée, la lecture doit s'arrêter dans l'application).
  • Et tous les autres processus externes qui doivent influencer la lecture.

Cette approche est idéale pour de nombreux cas d'utilisation. En particulier, vous devez fortement envisager d'utiliser MediaSession dans les cas suivants:

  • Vous regardez des contenus vidéo longs, comme des films ou des programmes TV en direct.
  • Vous regardez des contenus audio longs, comme des podcasts ou des playlists musicales.
  • Vous créez une application TV.

Toutefois, tous les cas d'utilisation ne conviennent pas à MediaSession. Vous pouvez n'utiliser que la propriété Player dans les cas suivants:

  • Vous diffusez des contenus courts, pour lesquels l'engagement et l'interaction des utilisateurs sont essentiels.
  • Il n'y a pas une seule vidéo active (par exemple, un utilisateur fait défiler une liste et plusieurs vidéos s'affichent en même temps à l'écran).
  • Vous diffusez une vidéo d'introduction ou d'explication ponctuelle que vous attendez de votre utilisateur de regarder 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 de ceux listés ci-dessus, demandez-vous si vous acceptez que votre application continue la lecture lorsque l'utilisateur n'interagit pas activement avec le contenu. Si la réponse est oui, vous devez probablement choisir MediaSession. Si la réponse est non, utilisez plutôt Player.

Créer une session multimédia

Une session multimédia coexiste avec le lecteur qu'elle gère. Vous pouvez construire une session multimédia avec les objets Context et Player. Vous devez créer et initialiser une session multimédia si nécessaire, par exemple la méthode de cycle de vie onStart() ou onResume() de Activity ou Fragment, ou la méthode onCreate() de Service qui possède la session multimédia et le 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 à l'aide de l'état du lecteur. Vous n'avez donc pas besoin de gérer manuellement le mappage du lecteur à la session.

Il s'agit d'une rupture avec l'ancienne approche, qui consistait à créer et à gérer un PlaybackStateCompat indépendamment du lecteur lui-même, par exemple pour indiquer les erreurs.

ID de session unique

Par défaut, MediaSession.Builder crée une session avec une chaîne vide comme ID de session. C'est suffisant si une application ne prévoit de créer qu'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 session 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 un IllegalStateException plante votre application avec le message d'erreur IllegalStateException: Session ID must be unique. ID=, il est probable qu'une session a été créée de manière inattendue avant qu'une instance créée précédemment avec le même ID ne soit libérée. Pour éviter que les sessions ne soient divulguées par une erreur de programmation, de tels cas sont détectés et notifiés en générant une exception.

Accorder le contrôle à d'autres clients

La session multimédia est essentielle pour contrôler la lecture. Il vous permet de router les commandes provenant de sources externes vers le lecteur qui lit vos contenus multimédias. Ces sources peuvent être des boutons physiques tels que le bouton de lecture d'un casque ou d'une télécommande de téléviseur, ou des commandes indirectes telles que l'instruction "Mets en pause" à l'Assistant Google. De même, vous pouvez accorder 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 pouvoir contrôler la lecture depuis le cadran. Les clients externes peuvent utiliser un contrôleur multimédia pour émettre des commandes de lecture à votre application multimédia. Elles sont reçues par votre session multimédia, qui délègue finalement les commandes au lecteur multimédia.

Schéma illustrant l'interaction entre un MediaSession et un MediaController.
Figure 1: Le contrôleur multimédia facilite la transmission des commandes de 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 l'ControllerInfo fourni pour décider d'accepter ou de refuser 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é, une manette peut envoyer des commandes de lecture à la session. La session délègue ensuite ces commandes au lecteur. Les commandes de lecture et de playlist définies dans l'interface Player sont gérées automatiquement 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 de 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 pour les manettes.

Lorsque vous ajoutez des é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 de lecteur telles que player.addMediaItem s'ils ont un URI 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, MediaItem définit généralement un ou plusieurs des champs suivants pour décrire le contenu multimédia demandé:

  • MediaItem.id: ID générique identifiant le contenu multimédia.
  • MediaItem.RequestMetadata.mediaUri: URI de requête pouvant utiliser un schéma personnalisé et qui n'est pas nécessairement directement lisible par le lecteur.
  • MediaItem.RequestMetadata.searchQuery: requête de recherche textuelle, par exemple issue de l'Assistant Google.
  • MediaItem.MediaMetadata: métadonnées structurées telles que "titre" ou "artiste".

Pour plus d'options de personnalisation pour de 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 seul élément demandé en une playlist complète et demander au lecteur 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 la session.

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

Les sections suivantes expliquent comment annoncer une disposition personnalisée de boutons de commande personnalisés aux applications clientes et autoriser les manettes à envoyer les commandes personnalisées.

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

Pour indiquer aux applications clientes les commandes de lecture que vous souhaitez présenter à l'utilisateur, définissez la mise en page personnalisée de la session lors de la création de 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 disponibles pour le lecteur et les commandes personnalisées

Les applications multimédias peuvent définir des commandes personnalisées qui peuvent être utilisées, par exemple, dans une mise en page personnalisée. Par exemple, vous pouvez implémenter des boutons permettant à l'utilisateur d'enregistrer un élément multimédia dans une liste d'éléments favoris. Le MediaController envoie des commandes personnalisées et le 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 à partir 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 suivre 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 utilisateur

Après avoir géré une commande personnalisée ou toute autre interaction avec votre joueur, vous pouvez mettre à jour la mise en page affichée dans l'interface utilisateur du contrôleur. Un exemple typique est un bouton d'activation/de désactivation qui change d'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 à l'origine d'une commande de joueur

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

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 présents sur les appareils Android et d'autres périphériques, comme le bouton lecture/pause d'un casque Bluetooth. Media3 gère les événements de boutons multimédias à votre place lorsqu'ils arrivent à la session et appelle la méthode Player appropriée sur le lecteur de session.

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

Traitement des erreurs et création de rapports

Une session émet et signale deux types d'erreurs aux contrôleurs. Les erreurs fatales signalent un échec technique de la lecture du lecteur de session qui interrompt la lecture. Les erreurs fatales sont signalées automatiquement au contrôleur lorsqu'elles se produisent. Les erreurs non fatales sont des erreurs non techniques ou liées aux règles qui n'interrompent pas la lecture et sont envoyées manuellement aux contrôleurs par l'application.

Erreurs de lecture fatales

Une erreur de lecture fatale est signalée à la session par le lecteur, puis aux contrôleurs pour être appelée via Player.Listener.onPlayerError(PlaybackException) et Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException).

Dans ce cas, l'état de lecture passe à STATE_IDLE et MediaController.getPlaybackError() renvoie le PlaybackException à l'origine de la transition. Un contrôleur peut inspecter PlayerException.errorCode pour obtenir des informations sur la raison de l'erreur.

Pour l'interopérabilité, une erreur fatale est répliquée dans le PlaybackStateCompat de la session de la plate-forme en faisant passer son état à STATE_ERROR et en définissant le code et le message d'erreur en fonction de PlaybackException.

Personnalisation d'une erreur fatale

Pour fournir des informations localisées et pertinentes à l'utilisateur, vous pouvez personnaliser le code d'erreur, le message d'erreur et les éléments supplémentaires d'une erreur de lecture fatale à l'aide d'un ForwardingPlayer lors de la création de la session:

Kotlin

val forwardingPlayer = ErrorForwardingPlayer(player)
val session = MediaSession.Builder(context, forwardingPlayer).build()

Java

Player forwardingPlayer = new ErrorForwardingPlayer(player);
MediaSession session =
    new MediaSession.Builder(context, forwardingPlayer).build();

Le lecteur de transfert enregistre un Player.Listener pour le joueur réel et intercepte les rappels qui signalent une erreur. Un PlaybackException personnalisé est ensuite délégué aux écouteurs enregistrés sur le lecteur de transfert. Pour que cela fonctionne, le lecteur de transfert remplace Player.addListener et Player.removeListener pour avoir accès aux écouteurs avec lesquels envoyer un code d'erreur, un message ou des éléments supplémentaires personnalisés:

Kotlin

class ErrorForwardingPlayer(private val context: Context, player: Player) :
  ForwardingPlayer(player) {

  private val listeners: MutableList<Player.Listener> = mutableListOf()

  private var customizedPlaybackException: PlaybackException? = null

  init {
    player.addListener(ErrorCustomizationListener())
  }

  override fun addListener(listener: Player.Listener) {
    listeners.add(listener)
  }

  override fun removeListener(listener: Player.Listener) {
    listeners.remove(listener)
  }

  override fun getPlayerError(): PlaybackException? {
    return customizedPlaybackException
  }

  private inner class ErrorCustomizationListener : Player.Listener {

    override fun onPlayerErrorChanged(error: PlaybackException?) {
      customizedPlaybackException = error?.let { customizePlaybackException(it) }
      listeners.forEach { it.onPlayerErrorChanged(customizedPlaybackException) }
    }

    override fun onPlayerError(error: PlaybackException) {
      listeners.forEach { it.onPlayerError(customizedPlaybackException!!) }
    }

    private fun customizePlaybackException(
      error: PlaybackException,
    ): PlaybackException {
      val buttonLabel: String
      val errorMessage: String
      when (error.errorCode) {
        PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> {
          buttonLabel = context.getString(R.string.err_button_label_restart_stream)
          errorMessage = context.getString(R.string.err_msg_behind_live_window)
        }
        // Apps can customize further error messages by adding more branches.
        else -> {
          buttonLabel = context.getString(R.string.err_button_label_ok)
          errorMessage = context.getString(R.string.err_message_default)
        }
      }
      val extras = Bundle()
      extras.putString("button_label", buttonLabel)
      return PlaybackException(errorMessage, error.cause, error.errorCode, extras)
    }

    override fun onEvents(player: Player, events: Player.Events) {
      listeners.forEach {
        it.onEvents(player, events)
      }
    }
    // Delegate all other callbacks to all listeners without changing arguments like onEvents.
  }
}

Java

private static class ErrorForwardingPlayer extends ForwardingPlayer {

  private final Context context;
  private List<Player.Listener> listeners;
  @Nullable private PlaybackException customizedPlaybackException;

  public ErrorForwardingPlayer(Context context, Player player) {
    super(player);
    this.context = context;
    listeners = new ArrayList<>();
    player.addListener(new ErrorCustomizationListener());
  }

  @Override
  public void addListener(Player.Listener listener) {
    listeners.add(listener);
  }

  @Override
  public void removeListener(Player.Listener listener) {
    listeners.remove(listener);
  }

  @Nullable
  @Override
  public PlaybackException getPlayerError() {
    return customizedPlaybackException;
  }

  private class ErrorCustomizationListener implements Listener {

    @Override
    public void onPlayerErrorChanged(@Nullable PlaybackException error) {
      customizedPlaybackException =
          error != null ? customizePlaybackException(error, context) : null;
      for (int i = 0; i < listeners.size(); i++) {
        listeners.get(i).onPlayerErrorChanged(customizedPlaybackException);
      }
    }

    @Override
    public void onPlayerError(PlaybackException error) {
      for (int i = 0; i < listeners.size(); i++) {
        listeners.get(i).onPlayerError(checkNotNull(customizedPlaybackException));
      }
    }

    private PlaybackException customizePlaybackException(
        PlaybackException error, Context context) {
      String buttonLabel;
      String errorMessage;
      switch (error.errorCode) {
        case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW:
          buttonLabel = context.getString(R.string.err_button_label_restart_stream);
          errorMessage = context.getString(R.string.err_msg_behind_live_window);
          break;
        // Apps can customize further error messages by adding more case statements.
        default:
          buttonLabel = context.getString(R.string.err_button_label_ok);
          errorMessage = context.getString(R.string.err_message_default);
          break;
      }
      Bundle extras = new Bundle();
      extras.putString("button_label", buttonLabel);
      return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras);
    }

    @Override
    public void onEvents(Player player, Events events) {
      for (int i = 0; i < listeners.size(); i++) {
        listeners.get(i).onEvents(player, events);
      }
    }
    // Delegate all other callbacks to all listeners without changing arguments like onEvents.
  }
}

Erreurs non fatales

Les erreurs non fatales qui ne proviennent pas d'une exception technique peuvent être envoyées par une application à tous les contrôleurs ou à un contrôleur spécifique:

Kotlin

val sessionError = SessionError(
  SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
  context.getString(R.string.error_message_authentication_expired),
)

// Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError)

// Interoperability: Sending a nonfatal error to the media notification controller to set the
// error code and error message in the playback state of the platform media session.
mediaSession.mediaNotificationControllerInfo?.let {
  mediaSession.sendError(it, sessionError)
}

Java

SessionError sessionError = new SessionError(
    SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
    context.getString(R.string.error_message_authentication_expired));

// Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError);

// Interoperability: Sending a nonfatal error to the media notification controller to set the
// error code and error message in the playback state of the platform media session.
ControllerInfo mediaNotificationControllerInfo =
    mediaSession.getMediaNotificationControllerInfo();
if (mediaNotificationControllerInfo != null) {
  mediaSession.sendError(mediaNotificationControllerInfo, sessionError);
}

Une erreur non fatale envoyée au contrôleur de notification multimédia est répliquée dans le PlaybackStateCompat de la session de la plate-forme. Par conséquent, seul le code d'erreur et le message d'erreur sont définis sur PlaybackStateCompat en conséquence, tandis que PlaybackStateCompat.state n'est pas remplacé par STATE_ERROR.

Recevoir des erreurs non fatales

Un MediaController reçoit une erreur non fatale en implémentant MediaController.Listener.onError:

Kotlin

val future = MediaController.Builder(context, sessionToken)
  .setListener(object : MediaController.Listener {
    override fun onError(controller: MediaController, sessionError: SessionError) {
      // Handle nonfatal error.
    }
  })
  .buildAsync()

Java

MediaController.Builder future =
    new MediaController.Builder(context, sessionToken)
        .setListener(
            new MediaController.Listener() {
              @Override
              public void onError(MediaController controller, SessionError sessionError) {
                // Handle nonfatal error.
              }
            });