Le sessioni multimediali offrono un modo universale per interagire con un audio o video
player. In Media3, il player predefinito è la classe ExoPlayer
, che implementa l'interfaccia Player
. La connessione della sessione multimediale al player consente a un'app di pubblicizzare la riproduzione dei contenuti multimediali all'esterno e di ricevere comandi di riproduzione da fonti esterne.
I comandi possono provenire da pulsanti fisici, ad esempio il pulsante di riproduzione su un auricolare o il telecomando della TV. Potrebbero anche provenire da app client che dispongono di un controllo multimediale, ad esempio l'istruzione "metti in pausa" all'Assistente Google. La sessione media delega questi comandi al player dell'app multimediale.
Quando scegliere una sessione multimediale
Quando implementi MediaSession
, consenti agli utenti di controllare la riproduzione:
- Tramite le cuffie. Spesso sono presenti pulsanti o interazioni touch che un utente può eseguire sulle cuffie per riprodurre o mettere in pausa i contenuti multimediali o passare al brano successivo o precedente.
- Parlando con l'Assistente Google. Un pattern comune è dire "Ok Google, metti in pausa" per mettere in pausa i contenuti multimediali attualmente in riproduzione sul dispositivo.
- Tramite il proprio smartwatch Wear OS. In questo modo, è più facile accedere ai controlli di riproduzione più comuni durante la riproduzione sullo smartphone.
- Tramite i Controlli multimediali. Questo carosello mostra i controlli per ogni sessione multimediale in esecuzione.
- Sulla TV. Consente azioni con i pulsanti di riproduzione fisici, il controllo della riproduzione della piattaforma e la gestione dell'alimentazione (ad esempio, se la TV, la soundbar o il ricevitore A/V si spengono o se viene cambiato l'ingresso, la riproduzione dovrebbe interrompersi nell'app).
- E qualsiasi altro processo esterno che deve influire sulla riproduzione.
Questo è ottimo per molti casi d'uso. In particolare, ti consigliamo vivamente di utilizzare MediaSession
quando:
- Stai trasmettendo in streaming contenuti video nel formato lungo, come film o TV in diretta.
- Trasmetti in streaming contenuti audio nel formato lungo, come podcast o playlist musicali.
- Stai creando un'app per TV.
Tuttavia, non tutti i casi d'uso si adattano bene a MediaSession
. Ti consigliamo di
utilizzare solo Player
nei seguenti casi:
- Mostri contenuti nel formato breve, per i quali non è necessario alcun controllo esterno o riproduzione in background.
- Non è presente un singolo video attivo, ad esempio l'utente sta scorrendo un elenco e vengono visualizzati più video contemporaneamente sullo schermo.
- Stai riproducendo un video introduttivo o esplicativo una tantum che prevedi che l'utente guardi attivamente senza bisogno di controlli di riproduzione esterni.
- I tuoi contenuti sono sensibili alla privacy e non vuoi che i processi esterni accedano ai metadati dei contenuti multimediali (ad esempio la modalità di navigazione in incognito in un browser).
Se il tuo caso d'uso non corrisponde a nessuno di quelli elencati sopra, valuta se è opportuno che la tua app continui la riproduzione quando l'utente non interagisce attivamente con i contenuti. Se la risposta è sì, probabilmente ti consigliamo di scegliere
MediaSession
. Se la risposta è no, probabilmente ti consigliamo di utilizzare Player
.
Crea una sessione multimediale
Una sessione multimediale è associata al player che gestisce. Puoi creare una sessione media con un oggetto Context
e un oggetto Player
. Devi creare e inizializzare una sessione multimediale quando è necessario, ad esempio il metodo di ciclo di vita onStart()
o onResume()
di Activity
o Fragment
oppure il metodo onCreate()
di Service
che possiede la sessione multimediale e il relativo player.
Per creare una sessione multimediale, inizializza un Player
e forniscilo a
MediaSession.Builder
come segue:
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();
Gestione automatica dello stato
La libreria Media3 aggiorna automaticamente la sessione multimediale utilizzando lo stato del player. Di conseguenza, non è necessario gestire manualmente la mappatura dal player alla sessione.
È diverso dalla sessione multimediale della piattaforma, in cui dovevi creare e gestire un PlaybackState
indipendentemente dal player stesso, ad esempio per indicare eventuali errori.
ID sessione univoco
Per impostazione predefinita, MediaSession.Builder
crea una sessione con una stringa vuota come ID sessione. Questo è sufficiente se un'app intende creare una sola istanza di sessione, che è il caso più comune.
Se un'app vuole gestire più istanze di sessione contemporaneamente, deve assicurarsi che l'ID sessione di ogni sessione sia univoco. L'ID sessione può essere impostato durante la creazione della sessione con MediaSession.Builder.setId(String id)
.
Se vedi un IllegalStateException
che arresta in modo anomalo la tua app con il messaggio di errore IllegalStateException: Session ID must be unique. ID=
, è probabile che una sessione sia stata creata inaspettatamente prima che sia stata rilasciata un'istanza creata in precedenza con lo stesso ID. Per evitare che le sessioni vengano divulgate da un
errore di programmazione, questi casi vengono rilevati e notificati generando un
eccezione.
Concedere il controllo ad altri client
La sessione multimediale è fondamentale per controllare la riproduzione. Ti consente di instradare comandi da sorgenti esterne al player che si occupa di riprodurre i contenuti media. Queste sorgenti possono essere pulsanti fisici come il pulsante di riproduzione su un auricolare o il telecomando della TV oppure comandi indiretti come l'istruzione "metti in pausa" all'Assistente Google. Allo stesso modo, ti consigliamo di concedere l'accesso al sistema Android per semplificare i controlli delle notifiche e della schermata di blocco oppure a uno smartwatch Wear OS per poter controllare la riproduzione dal quadrante. I client esterni possono utilizzare un controllo multimediale per emettere comandi di riproduzione all'app multimediale. Questi comandi vengono ricevuti dalla sessione multimediale, che alla fine li delega al lettore multimediale.

Quando un controller sta per connettersi alla sessione multimediale, viene chiamato il metodo
onConnect()
. Puoi utilizzare il ControllerInfo
fornito per decidere se accettare o rifiutare la richiesta. Per un esempio di accettazione di una richiesta di connessione, consulta la sezione Dichiara
comandi personalizzati.
Dopo la connessione, un controller può inviare comandi di riproduzione alla sessione. La sessione poi delega questi comandi al player. I comandi di riproduzione e playlist definiti nell'interfaccia Player
vengono gestiti automaticamente dalla sessione.
Altri metodi di callback ti consentono di gestire, ad esempio, le richieste di comandi personalizzati e la modifica della playlist. Allo stesso modo, questi callback includono un oggetto ControllerInfo
per consentirti di modificare il modo in cui rispondi a ogni richiesta in base al controller.
Modificare la playlist
Una sessione multimediale può modificare direttamente la playlist del proprio player, come spiegato nella
guida di ExoPlayer per le playlist.
I controller possono anche modificare la playlist se COMMAND_SET_MEDIA_ITEM
o COMMAND_CHANGE_MEDIA_ITEMS
sono disponibili per il controller.
Quando aggiungi nuovi elementi alla playlist, in genere il player richiede MediaItem
istanze con un
URI definito
per poterli riprodurre. Per impostazione predefinita, gli elementi appena aggiunti vengono inoltrati automaticamente ai metodi del player come player.addMediaItem
se è definito un URI.
Se vuoi personalizzare le istanze MediaItem
aggiunte al player, puoi override
onAddMediaItems()
.
Questo passaggio è necessario se vuoi supportare i controller che richiedono contenuti multimediali senza un URI definito. In genere, MediaItem
ha uno o più dei seguenti campi impostati per descrivere i contenuti multimediali richiesti:
MediaItem.id
: un ID generico che identifica i contenuti multimediali.MediaItem.RequestMetadata.mediaUri
: un URI richiesta che può utilizzare uno schema personalizzato e non è necessariamente riproducibile direttamente dal player.MediaItem.RequestMetadata.searchQuery
: una query di ricerca di testo, ad esempio proveniente dall'Assistente Google.MediaItem.MediaMetadata
: metadati strutturati come "title" o "artist".
Per altre opzioni di personalizzazione per playlist completamente nuove, puoi anche eseguire l'override di onSetMediaItems()
, che ti consente di definire l'elemento iniziale e la posizione nella playlist. Ad esempio, puoi espandere un singolo elemento richiesto in un'intera playlist e chiedere al player di iniziare dall'indice dell'elemento richiesto in origine. Un'implementazione di esempio di onSetMediaItems()
con questa funzionalità è disponibile nell'app demo della sessione.
Gestire le preferenze dei pulsanti multimediali
Ogni controller, ad esempio l'interfaccia utente di sistema, Android Auto o Wear OS, può prendere le proprie decisioni su quali pulsanti mostrare all'utente. Per indicare quali controlli di riproduzione vuoi mostrare all'utente, puoi specificare le preferenze per i pulsanti media in MediaSession
. Queste preferenze consistono in un elenco ordinato di istanze CommandButton
, ciascuna delle quali definisce una preferenza per un pulsante nell'interfaccia utente.
Definire i pulsanti di comando
Le istanze CommandButton
vengono utilizzate per definire le preferenze dei pulsanti multimediali. Ogni pulsante definisce tre aspetti dell'elemento dell'interfaccia utente desiderato:
- L'icona, che definisce l'aspetto visivo. L'icona deve essere impostata su una delle costanti predefinite durante la creazione di un
CommandButton.Builder
. Tieni presente che non si tratta di una risorsa bitmap o immagine effettiva. Una costante generica aiuta i controller a scegliere una risorsa appropriata per un aspetto coerente all'interno della propria UI. Se nessuna delle costanti di icone predefinite è adatta al tuo caso d'uso, puoi utilizzaresetCustomIconResId
. - Il comando, che definisce l'azione attivata quando l'utente interagisce con il pulsante. Puoi utilizzare
setPlayerCommand
per unPlayer.Command
osetSessionCommand
per unSessionCommand
predefinito o personalizzato. - Lo slot, che definisce la posizione del pulsante nell'UI del controller. Questo campo è facoltativo e viene impostato automaticamente in base a Icona e Comando. Ad esempio, consente di specificare che un pulsante deve essere visualizzato nell'area di navigazione "Avanti" dell'interfaccia utente anziché nell'area "Overflow" predefinita.
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();
Quando le preferenze dei pulsanti multimediali vengono risolte, viene applicato il seguente algoritmo:
- Per ogni
CommandButton
nelle preferenze dei pulsanti multimediali, posiziona il pulsante nel primo slot disponibile e consentito. - Se uno degli slot centrali, avanti e indietro non è completato con un pulsante, aggiungi i pulsanti predefiniti per questo slot.
Puoi utilizzare CommandButton.DisplayConstraints
per generare un'anteprima di come verranno risolte le preferenze dei pulsanti multimediali in base alle limitazioni di visualizzazione dell'interfaccia utente.
Impostare le preferenze dei pulsanti multimediali
Il modo più semplice per impostare le preferenze dei pulsanti multimediali è definire l'elenco durante la creazione del MediaSession
. In alternativa, puoi eseguire l'override di MediaSession.Callback.onConnect
per personalizzare le preferenze dei pulsanti multimediali per ogni controller connesso.
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();
Aggiornare le preferenze dei pulsanti multimediali dopo un'interazione dell'utente
Dopo aver gestito un'interazione con il player, ti consigliamo di aggiornare i pulsanti visualizzati nell'interfaccia utente del controller. Un esempio tipico è un pulsante di attivazione/disattivazione
che cambia icona e azione dopo aver attivato l'azione associata al pulsante. Per aggiornare le preferenze dei pulsanti multimediali, puoi utilizzare MediaSession.setMediaButtonPreferences
per aggiornare le preferenze per tutti i controller o per un controller specifico:
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));
Aggiungere comandi personalizzati e personalizzare il comportamento predefinito
I comandi del player disponibili possono essere estesi con comandi personalizzati ed è anche possibile intercettare i comandi del player in arrivo e i pulsanti multimediali per modificare il comportamento predefinito.
Dichiarare e gestire comandi personalizzati
Le applicazioni multimediali possono definire comandi personalizzati che, ad esempio, possono essere utilizzati nelle preferenze dei pulsanti multimediali. Ad esempio, potresti voler implementare pulsanti che consentano all'utente di salvare un elemento multimediale in un elenco di elementi preferiti. MediaController
invia comandi personalizzati e MediaSession.Callback
li riceve.
Per definire i comandi personalizzati, devi eseguire l'override di MediaSession.Callback.onConnect()
per impostare i comandi personalizzati disponibili per ogni controller connesso.
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(); } }
Per ricevere richieste di comandi personalizzati da un MediaController
, sostituisci il metodo onCustomCommand()
in 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) ); } ... } }
Puoi monitorare il controller multimediale che effettua una richiesta utilizzando la proprietà packageName
dell'oggetto MediaSession.ControllerInfo
passata ai metodi Callback
. In questo modo puoi personalizzare il comportamento della tua app in risposta a un determinato comando se proviene dal sistema, dalla tua app o da altre app client.
Personalizzare i comandi predefiniti del player
Tutti i comandi e la gestione dello stato predefiniti vengono delegati al Player
sul
MediaSession
. Per personalizzare il comportamento di un comando definito nell'interfaccia Player
, ad esempio play()
o seekToNext()
, racchiudi Player
in un ForwardingSimpleBasePlayer
prima di passarlo a 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();
Per ulteriori informazioni su ForwardingSimpleBasePlayer
, consulta la guida di ExoPlayer sulla personalizzazione.
Identificare il controller che richiede un comando del player
Quando una chiamata a un metodo Player
ha origine da un MediaController
, puoi identificare l'origine con MediaSession.controllerForCurrentRequest
e acquisire il ControllerInfo
per la richiesta corrente:
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); } }
Personalizzare la gestione dei pulsanti multimediali
I pulsanti multimediali sono pulsanti hardware presenti sui dispositivi Android e su altre periferiche, ad esempio il pulsante di riproduzione/pausa su un auricolare Bluetooth. Media3 gestisce gli eventi dei pulsanti multimediali quando arrivano alla sessione e chiama il metodo Player
appropriato sul player della sessione.
Ti consigliamo di gestire tutti gli eventi dei pulsanti multimediali in arrivo nel metodo Player
corrispondente. Per casi d'uso più avanzati, gli eventi dei pulsanti multimediali possono essere intercettati in MediaSession.Callback.onMediaButtonEvent(Intent)
.
Gestione degli errori e generazione di report
Una sessione emette e segnala ai controller due tipi di errori. Gli errori irreversibili segnalano un errore tecnico di riproduzione del Media Player della sessione che interrompe la riproduzione. Gli errori fatali vengono segnalati al controller automaticamente quando si verificano. Gli errori non fatali sono errori non tecnici o relativi alle norme che non interrompono la riproduzione e vengono inviati manualmente ai controller dall'applicazione.
Errori di riproduzione irreversibili
Un errore di riproduzione irreversibile viene segnalato alla sessione dal player e poi ai controller per essere chiamato tramite Player.Listener.onPlayerError(PlaybackException)
e Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException)
.
In questo caso, lo stato di riproduzione passa a STATE_IDLE
e
MediaController.getPlaybackError()
restituisce il PlaybackException
che ha causato
la transizione. Un controllore può ispezionare il PlayerException.errorCode
per ottenere informazioni sul motivo dell'errore.
Per l'interoperabilità, un errore fatale viene replicato alla sessione della piattaforma impostando il relativo stato su STATE_ERROR
e impostando il codice e il messaggio di errore in base a PlaybackException
.
Personalizzazione degli errori irreversibili
Per fornire all'utente informazioni significative e localizzate, il codice di errore, il messaggio di errore e gli extra di errore di un errore di riproduzione fatale possono essere personalizzati utilizzando un ForwardingPlayer
durante la creazione della sessione:
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();
Il player di inoltro può utilizzare ForwardingSimpleBasePlayer
per intercettare l'errore e personalizzare il codice, il messaggio o gli extra di errore. Allo stesso modo, puoi anche generare nuovi errori che non esistono nel player originale:
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); } }
Errori non fatali
Gli errori non fatali che non hanno origine da un'eccezione tecnica possono essere inviati da un'app a tutti i controller o a uno specifico:
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); }
Quando viene inviato un errore non fatale al controller di notifica dei contenuti multimediali, il codice e il messaggio di errore vengono replicati nella sessione multimediale della piattaforma, mentre PlaybackState.state
non viene modificato in STATE_ERROR
.
Ricevere errori non fatali
Un MediaController
riceve un errore non fatale implementando
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. } });