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 disposant 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:
- Via leurs écouteurs. Un utilisateur peut souvent effectuer des interactions tactiles ou des boutons sur ses écouteurs pour lire ou mettre en pause des contenus multimédias, ou passer au titre suivant ou précédent.
- 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 leur montre Wear OS. Cela permet d'accéder plus facilement aux commandes de lecture les plus courantes lorsqu'ils regardent des contenus 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 Télévision. Permet d'effectuer des actions avec les 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 audio-vidéo s'éteint 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 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.
Cependant, tous les cas d'utilisation ne conviennent pas à MediaSession
. Vous pouvez utiliser uniquement Player
dans les cas suivants:
- Vous diffusez des contenus courts, pour lesquels aucune commande externe ni lecture en arrière-plan n'est nécessaire.
- Il n'y a pas une seule vidéo active, par exemple si l'utilisateur fait défiler une liste et que plusieurs vidéos sont affichées à l'écran en même temps.
- Vous diffusez une vidéo d'introduction ou d'explication ponctuelle que vous attendez de votre utilisateur de regarder activement sans avoir besoin de commandes de lecture externes.
- 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, vous préférerez probablement utiliser Player
à la place.
Créer une session multimédia
Une session multimédia coexiste avec 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 lorsque cela est 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 est propriétaire de la session multimédia et de son 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.
Cela diffère de la session multimédia de la plate-forme, où vous deviez créer et gérer un PlaybackState
indépendamment du lecteur lui-même, par exemple pour indiquer 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 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 des sessions ne soient divulguées par une erreur de programmation, ces cas sont détectés et signalé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.

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 demande de connexion dans la section Déclarer des commandes personnalisées.
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 personnalisées et de modification de la playlist. Ces rappels incluent également un objet ControllerInfo
afin que vous puissiez modifier la manière dont vous répondez à chaque requête par 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 le contrôleur.
Lorsque vous ajoutez des éléments à la playlist, le lecteur nécessite généralement des instances MediaItem
avec un URI défini pour les lire. 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, l'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 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 les préférences des boutons multimédias
Chaque contrôleur, par exemple l'UI système, Android Auto ou Wear OS, peut prendre ses propres décisions concernant les boutons à afficher à l'utilisateur. Pour indiquer les commandes de lecture que vous souhaitez exposer à l'utilisateur, vous pouvez spécifier les préférences des boutons multimédias sur MediaSession
. Ces préférences consistent en une liste ordonnée d'instances CommandButton
, chacune définissant une préférence pour un bouton dans l'interface utilisateur.
Définir des boutons de commande
Les instances CommandButton
permettent de définir les préférences des boutons multimédias. Chaque bouton définit trois aspects de l'élément d'interface utilisateur souhaité:
- L'icône, qui définit l'apparence visuelle. L'icône doit être définie sur l'une des constantes prédéfinies lors de la création d'un
CommandButton.Builder
. Notez qu'il ne s'agit pas d'une ressource Bitmap ou d'image réelle. Une constante générique aide les contrôleurs à choisir une ressource appropriée pour une apparence cohérente dans leur propre UI. Si aucune des constantes d'icône prédéfinies ne convient à votre cas d'utilisation, vous pouvez utilisersetCustomIconResId
à la place. - La commande, qui définit l'action déclenchée lorsque l'utilisateur interagit avec le bouton. Vous pouvez utiliser
setPlayerCommand
pour unPlayer.Command
ousetSessionCommand
pour unSessionCommand
prédéfini ou personnalisé. - Slot (Emplacement), qui définit l'emplacement du bouton dans l'interface utilisateur du contrôleur. Ce champ est facultatif et est automatiquement défini en fonction de l'icône et de la commande. Par exemple, il permet de spécifier qu'un bouton doit s'afficher dans la zone de navigation "avant" de l'UI au lieu de la zone "dépassement" par défaut.
Kotlin
val button = CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15) .setSessionCommand(SessionCommand(CUSTOM_ACTION_ID, Bundle.EMPTY)) .setSlots(CommandButton.SLOT_FORWARD) .build()
Java
CommandButton button = new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15) .setSessionCommand(new SessionCommand(CUSTOM_ACTION_ID, Bundle.EMPTY)) .setSlots(CommandButton.SLOT_FORWARD) .build();
Lorsque les préférences des boutons multimédias sont résolues, l'algorithme suivant est appliqué:
- Pour chaque
CommandButton
dans les préférences des boutons multimédias, placez le bouton dans le premier emplacement disponible et autorisé. - Si l'un des emplacements centraux, avant et arrière n'est pas associé à un bouton, ajoutez des boutons par défaut pour cet emplacement.
Vous pouvez utiliser CommandButton.DisplayConstraints
pour générer un aperçu de la résolution des préférences des boutons multimédias en fonction des contraintes d'affichage de l'UI.
Définir les préférences des boutons multimédias
Le moyen le plus simple de définir les préférences des boutons multimédias consiste à définir la liste lors de la création de MediaSession
. Vous pouvez également remplacer MediaSession.Callback.onConnect
pour personnaliser les préférences des boutons multimédias pour chaque manette connectée.
Kotlin
val mediaSession = MediaSession.Builder(context, player) .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton)) .build()
Java
MediaSession mediaSession = new MediaSession.Builder(context, player) .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton)) .build();
Modifier les préférences des boutons multimédias après une interaction de l'utilisateur
Après avoir géré une interaction avec votre lecteur, vous pouvez mettre à jour les boutons affichés dans l'interface utilisateur du contrôleur. Un exemple typique est un bouton d'activation/de désactivation qui change d'icône et d'action après avoir déclenché l'action associée à ce bouton. Pour mettre à jour les préférences des boutons multimédias, vous pouvez utiliser MediaSession.setMediaButtonPreferences
pour mettre à jour les préférences de tous les contrôleurs ou d'un contrôleur spécifique:
Kotlin
// Handle "favoritesButton" action, replace by opposite button mediaSession.setMediaButtonPreferences( ImmutableList.of(likeButton, removeFromFavoritesButton))
Java
// Handle "favoritesButton" action, replace by opposite button mediaSession.setMediaButtonPreferences( ImmutableList.of(likeButton, removeFromFavoritesButton));
Ajouter des commandes personnalisées et personnaliser le comportement par défaut
Les commandes de lecteur disponibles peuvent être étendues par des commandes personnalisées. Il est également possible d'intercepter les commandes entrantes du lecteur et les boutons multimédias pour modifier le comportement par défaut.
Déclarer et gérer des commandes personnalisées
Les applications multimédias peuvent définir des commandes personnalisées qui peuvent être utilisées, par exemple, dans les préférences des boutons multimédias. 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.
Pour définir des commandes personnalisées, vous devez remplacer MediaSession.Callback.onConnect()
pour définir les commandes personnalisées disponibles pour chaque contrôleur connecté.
Kotlin
private 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 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.
Personnaliser les commandes par défaut des joueurs
Toutes les commandes par défaut et la gestion de l'état sont déléguées à l'Player
qui se trouve sur le MediaSession
. Pour personnaliser le comportement d'une commande définie dans l'interface Player
, telle que play()
ou seekToNext()
, encapsulez votre Player
dans un ForwardingSimpleBasePlayer
avant de le transmettre à MediaSession
:
Kotlin
val player = (logic to build a Player instance) val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) { // Customizations } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ExoPlayer player = (logic to build a Player instance) ForwardingSimpleBasePlayer forwardingPlayer = new ForwardingSimpleBasePlayer(player) { // Customizations }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
Pour en savoir plus sur ForwardingSimpleBasePlayer
, 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 CallerAwarePlayer(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSeek( mediaItemIndex: Int, positionMs: Long, seekCommand: Int, ): ListenableFuture<*> { Log.d( "caller", "seek operation from package ${session.controllerForCurrentRequest?.packageName}", ) return super.handleSeek(mediaItemIndex, positionMs, seekCommand) } }
Java
public class CallerAwarePlayer extends ForwardingSimpleBasePlayer { public CallerAwarePlayer(Player player) { super(player); } @Override protected ListenableFuture<?> handleSeek( int mediaItemIndex, long positionMs, int seekCommand) { Log.d( "caller", "seek operation from package: " + session.getControllerForCurrentRequest().getPackageName()); return super.handleSeek(mediaItemIndex, positionMs, seekCommand); } }
Personnaliser la gestion des boutons multimédias
Les boutons multimédias sont des boutons physiques que vous trouverez sur les appareils Android et d'autres périphériques, comme le bouton de lecture/pause d'un casque Bluetooth. Media3 gère les événements de bouton multimédia pour vous lorsqu'ils arrivent dans la session et appelle la méthode Player
appropriée sur le lecteur de session.
Il est recommandé de gérer tous les événements de bouton multimédia entrants dans la méthode Player
correspondante. Pour les cas d'utilisation plus avancés, les événements des boutons multimédias peuvent être interceptés dans MediaSession.Callback.onMediaButtonEvent(Intent)
.
Traitement et création de rapports d'erreur
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 à appeler 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 cause de l'erreur.
Pour l'interopérabilité, une erreur fatale est répliquée dans la session de la plate-forme en définissant son état sur STATE_ERROR
et en définissant le code et le message d'erreur en fonction de PlaybackException
.
Personnalisation des erreurs fatales
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 peut utiliser ForwardingSimpleBasePlayer
pour intercepter l'erreur et personnaliser le code d'erreur, le message ou les extras. De même, vous pouvez également générer de nouvelles erreurs qui n'existent pas dans le lecteur d'origine:
Kotlin
class ErrorForwardingPlayer (private val context: Context, player: Player) : ForwardingSimpleBasePlayer(player) { override fun getState(): State { var state = super.getState() if (state.playerError != null) { state = state.buildUpon() .setPlayerError(customizePlaybackException(state.playerError!!)) .build() } return state } 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) } 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) } }
Java
class ErrorForwardingPlayer extends ForwardingSimpleBasePlayer { private final Context context; public ErrorForwardingPlayer(Context context, Player player) { super(player); this.context = context; } @Override protected State getState() { State state = super.getState(); if (state.playerError != null) { state = state.buildUpon() .setPlayerError(customizePlaybackException(state.playerError)) .build(); } return state; } private PlaybackException customizePlaybackException(PlaybackException error) { 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; 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); } }
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), ) // Option 1: Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError) // Option 2: Sending a nonfatal error to the media notification controller only // 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)); // Option 1: Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError); // Option 2: Sending a nonfatal error to the media notification controller only // 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); }
Lorsqu'une erreur non fatale est envoyée au contrôleur de notification multimédia, le code d'erreur et le message d'erreur sont répliqués dans la session multimédia de la plate-forme, tandis que PlaybackState.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. } });