Controllare e pubblicizzare la riproduzione utilizzando una MediaSession

Le sessioni multimediali forniscono un modo universale di interagire con un audio o un video player. In Media3, il player predefinito è la classe ExoPlayer, che implementa l'interfaccia Player. Il collegamento della sessione multimediale al player consente a un'app di pubblicizzare la riproduzione dei contenuti multimediali esternamente e di ricevere comandi di riproduzione da origini esterne.

I comandi possono provenire da pulsanti fisici, ad esempio il pulsante di riproduzione su un telecomando per cuffie o TV. Potrebbero anche provenire da app client che hanno un controller multimediale, ad esempio il comando "metti in pausa" per l'Assistente Google. La sessione multimediale 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 ci sono pulsanti o interazioni tattili che un utente può eseguire sulle cuffie per riprodurre o mettere in pausa i contenuti multimediali o passare alla traccia successiva o precedente.
  • Parlando con l'Assistente Google. Un comando comune è "Ok Google, metti in pausa" per mettere in pausa i contenuti multimediali in riproduzione sul dispositivo.
  • Tramite il loro 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.
  • Su 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 spegne o l'ingresso viene cambiato, la riproduzione deve interrompersi nell'app).
  • Tramite i controlli multimediali di Android Auto. In questo modo è possibile controllare la riproduzione in sicurezza durante la guida.
  • E qualsiasi altro processo esterno che deve influenzare la riproduzione.

Questa funzionalità è ideale per molti casi d'uso. In particolare, ti consigliamo vivamente di utilizzare MediaSession quando:

  • Stai riproducendo in streaming contenuti video nel formato lungo, come film o TV in diretta.
  • Stai riproducendo in streaming contenuti audio nel formato lungo, come podcast o playlist musicali.
  • Stai creando un'app TV.

Tuttavia, non tutti i casi d'uso si adattano bene a MediaSession. Potresti voler utilizzare solo Player nei seguenti casi:

  • Stai mostrando contenuti brevi, per i quali non è necessario alcun controllo esterno o riproduzione in background.
  • Non è presente un singolo video attivo, ad esempio l'utente scorre un elenco e vengono visualizzati più video sullo schermo contemporaneamente.
  • 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 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 rientra in quelli elencati sopra, valuta se vuoi che la tua app continui la riproduzione quando l'utente non interagisce attivamente con i contenuti. Se la risposta è sì, probabilmente vorrai scegliere MediaSession. Se la risposta è no, probabilmente ti conviene utilizzare Player.

Crea una sessione media

Una sessione multimediale si trova accanto al player che gestisce. Puoi creare una sessione multimediale con un oggetto Context e un oggetto Player. Devi creare e inizializzare una sessione multimediale quando è necessario, ad esempio il metodo del 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 nel seguente modo:

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. Pertanto, non è necessario gestire manualmente la mappatura dal player alla sessione.

Ciò è diverso dalla sessione multimediale della piattaforma, in cui era necessario 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 visualizzi un IllegalStateException che causa l'arresto anomalo della tua app con il messaggio di errore IllegalStateException: Session ID must be unique. ID=, è probabile che sia stata creata in modo imprevisto una sessione prima che venga rilasciata un'istanza creata in precedenza con lo stesso ID. Per evitare che le sessioni vengano divulgate a causa di un errore di programmazione, questi casi vengono rilevati e notificati tramite la generazione di un'eccezione.

Concedere il controllo ad altri client

La sessione multimediale è la chiave per controllare la riproduzione. Consente di indirizzare i comandi da fonti esterne al player che riproduce i tuoi contenuti multimediali. Queste fonti possono essere pulsanti fisici, ad esempio il pulsante di riproduzione su un telecomando per cuffie o TV, o comandi indiretti, ad esempio il comando "metti in pausa" all'Assistente Google. Allo stesso modo, potresti voler concedere l'accesso al sistema Android per facilitare i controlli delle notifiche e della schermata di blocco o a uno smartwatch Wear OS per controllare la riproduzione dal quadrante. I client esterni possono utilizzare un controller multimediale per inviare comandi di riproduzione all'app multimediale. Questi vengono ricevuti dalla sessione multimediale, che delega i comandi al lettore multimediale.

Un diagramma che mostra l'interazione tra MediaSession e MediaController.
Figura 1: il controller multimediale facilita il passaggio dei comandi da origini esterne alla sessione 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. Vedi un esempio di accettazione di una richiesta di connessione nella sezione Dichiarare comandi personalizzati.

Una volta connesso, un controller può inviare comandi di riproduzione alla sessione. La sessione delega quindi questi comandi al giocatore. I comandi di riproduzione e delle 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. Questi callback includono in modo simile un oggetto ControllerInfo, in modo da poter 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 suo 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, il player in genere richiede MediaItem istanze con un URI definito per renderli riproducibili. Per impostazione predefinita, gli elementi appena aggiunti vengono inoltrati automaticamente ai metodi del player come player.addMediaItem se hanno un URI definito.

Se vuoi personalizzare le istanze MediaItem aggiunte al player, puoi ignorare onAddMediaItems(). Questo passaggio è necessario quando vuoi supportare i controller che richiedono contenuti multimediali senza un URI definito. Al contrario, MediaItem in genere 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 testuale, ad esempio dall'Assistente Google.
  • MediaItem.MediaMetadata: metadati strutturati come "titolo" o "artista".

Per ulteriori opzioni di personalizzazione per playlist completamente nuove, puoi anche eseguire l'override 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 indicare al player di iniziare dall'indice dell'elemento originariamente richiesto. 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 in merito ai pulsanti da mostrare all'utente. Per indicare quali controlli di riproduzione vuoi mostrare all'utente, puoi specificare le preferenze dei pulsanti multimediali in MediaSession. Queste preferenze sono costituite da un elenco ordinato di istanze CommandButton, ognuna delle quali definisce una preferenza per un pulsante nell'interfaccia utente.

Definisci i pulsanti dei comandi

Le istanze CommandButton vengono utilizzate per definire le preferenze dei pulsanti multimediali. Ogni pulsante definisce tre aspetti dell'elemento UI desiderato:

  1. 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 dell'icona predefinite si adatta al tuo caso d'uso, puoi utilizzare setCustomIconResId.
  2. Il comando, che definisce l'azione attivata quando l'utente interagisce con il pulsante. Puoi utilizzare setPlayerCommand per un Player.Command o setSessionCommand per un SessionCommand predefinito o personalizzato.
  3. Lo Slot, che definisce la posizione in cui deve essere posizionato il pulsante nell'interfaccia utente 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();

Una volta risolte le preferenze dei pulsanti multimediali, viene applicato il seguente algoritmo:

  1. Per ogni CommandButton nelle preferenze dei pulsanti multimediali, posiziona il pulsante nel primo slot disponibile e consentito.
  2. Se uno degli slot centrali, di avanzamento e di riproduzione all'indietro non è riempito 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 a seconda dei vincoli di visualizzazione della UI.

Impostare le preferenze del pulsante multimediale

Il modo più semplice per impostare le preferenze del pulsante multimediale è definire l'elenco durante la creazione dell'MediaSession. In alternativa, puoi ignorare 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 del pulsante dei contenuti multimediali dopo un'interazione dell'utente

Dopo aver gestito un'interazione con il tuo giocatore, potresti voler 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 a questo 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 e i pulsanti multimediali in entrata per modificare il comportamento predefinito.

Dichiarare e gestire comandi personalizzati

Le applicazioni multimediali possono definire comandi personalizzati che possono essere utilizzati, ad esempio, nelle preferenze dei pulsanti multimediali. Ad esempio, potresti implementare pulsanti che consentano all'utente di salvare un elemento multimediale in un elenco di elementi preferiti. Il MediaController invia comandi personalizzati e il MediaSession.Callback li riceve.

Per definire 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, esegui l'override del 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 quale controller multimediale sta effettuando una richiesta utilizzando la proprietà packageName dell'oggetto MediaSession.ControllerInfo passato 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 predefiniti e la gestione dello stato vengono delegati all'Player che si trova sull'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 in Personalizzazione.

Identificare il controller richiedente di un comando del giocatore

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, come il pulsante di riproduzione/pausa su un headset 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 entrata nel metodo Player corrispondente. Per casi d'uso più avanzati, gli eventi del pulsante multimediale possono essere intercettati in MediaSession.Callback.onMediaButtonEvent(Intent).

Gestione e segnalazione degli errori

Esistono due tipi di errori che una sessione emette e segnala ai controller. Gli errori irreversibili segnalano un errore tecnico di riproduzione della sessione del player che interrompe la riproduzione. Gli errori irreversibili vengono segnalati automaticamente al controller quando si verificano. Gli errori non irreversibili 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

Il player segnala alla sessione un errore di riproduzione irreversibile, che viene poi segnalato ai controller per chiamare 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 l'PlaybackException che ha causato la transizione. Un controller può esaminare PlayerException.errorCode per ottenere informazioni sul motivo dell'errore.

Per l'interoperabilità, un errore irreversibile viene replicato nella 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 localizzate e significative, il codice di errore, il messaggio di errore e gli extra di errore di un errore di riproduzione irreversibile 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 di errore, il messaggio o gli extra. Allo stesso modo, puoi anche generare nuovi errori che non esistono nel giocatore 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 irreversibili

Gli errori non irreversibili che non derivano da un'eccezione tecnica possono essere inviati da un'app a tutti i controller o a un controller 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 un errore non fatale viene inviato al controller di notifica dei contenuti multimediali, il codice di errore e il messaggio di errore vengono replicati nella sessione multimediale della piattaforma, mentre PlaybackState.state non viene modificato in STATE_ERROR.

Ricevere errori non irreversibili

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.
              }
            });