Controllare e pubblicizzare la riproduzione utilizzando una MediaSession

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, in cui il coinvolgimento e l'interazione degli utenti sono fondamentali.
  • Non è presente un singolo video attivo, ad esempio l'utente sta scorrendo un elenco e sullo schermo vengono visualizzati più video contemporaneamente.
  • Stai riproducendo un video introduttivo o esplicativo una tantum, che prevedi che l'utente guardi attivamente.
  • 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.

Si tratta di un cambiamento rispetto all'approccio precedente, in cui era necessario creare e gestire un PlaybackStateCompat 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 trapelate a causa di un errore di programmazione, questi casi vengono rilevati e avvisati lanciando 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. Analogamente, 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.

Un diagramma che mostra l'interazione tra MediaSession e MediaController.
Figura 1: il controller multimediale semplifica 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. Guarda un esempio di accettazione di una richiesta di connessione nella sezione Dichiara comandi disponibili.

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 di riproduzione personalizzati e di modificare la playlist. Analogamente, questi callback includono un oggetto ControllerInfo per consentirti di modificare la modalità di risposta 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 è disponibile 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 layout e comandi personalizzati

Le seguenti sezioni descrivono come pubblicizzare un layout personalizzato dei pulsanti di comando personalizzati per le app client e autorizzare i controller a inviare i comandi personalizzati.

Definire il layout personalizzato della sessione

Per indicare alle app client quali controlli di riproduzione vuoi mostrare all'utente, imposta il layout personalizzato della sessione quando crei MediaSession nel metodo onCreate() del tuo servizio.

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

Dichiara i comandi personalizzati e del player disponibili

Le applicazioni multimediali possono definire comandi personalizzati che, ad esempio, possono essere utilizzati in un layout personalizzato. 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.

Puoi definire i comandi di sessione personalizzati disponibili per un MediaController quando si connette alla tua sessione multimediale. Per farlo, devi eseguire l'override di MediaSession.Callback.onConnect(). Configura e restituisci il set di comandi disponibili quando si accetta una richiesta di connessione da un MediaController nel metodo di callback 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();
  }
}

Per ricevere richieste di comandi personalizzati da un MediaController, sostituisci il metodo onCustomCommand() in 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)
      );
    }
    ...
  }
}

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.

Aggiornare il layout personalizzato dopo un'interazione dell'utente

Dopo aver gestito un comando personalizzato o qualsiasi altra interazione con il player, potresti voler aggiornare il layout visualizzato nell'interfaccia utente del controller. Un esempio tipico è un pulsante di attivazione/disattivazione che cambia icona dopo l'attivazione dell'azione associata. Per aggiornare il layout, puoi utilizzare 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));

Personalizzare il comportamento dei comandi di riproduzione

Per personalizzare il comportamento di un comando definito nell'interfaccia Player, ad esempio play() o seekToNext(), racchiudi Player in 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();

Per ulteriori informazioni su ForwardingPlayer, consulta la guida di ExoPlayer su 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 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();
  }
}

Rispondere ai pulsanti dei contenuti 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.

Un'app può ignorare il comportamento predefinito sostituendo MediaSession.Callback.onMediaButtonEvent(Intent). In questo caso, l'app deve/può gestire autonomamente tutte le specifiche dell'API.

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 irreversibili 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 nel PlaybackStateCompat della sessione della piattaforma passando il relativo stato a STATE_ERROR e impostando il codice e il messaggio di errore in base al PlaybackException.

Personalizzazione di un errore irreversibile

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 registra un Player.Listener per il player effettivo e intercetta i callback che segnalano un errore. Un valore personalizzato PlaybackException viene poi delegato agli ascoltatori che sono registrati sul player di inoltro. Affinché ciò funzioni, il player di inoltro supera Player.addListener e Player.removeListener per avere accesso agli ascoltatori con cui inviare codice di errore, messaggio o extra personalizzati:

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

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),
)

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

Un errore non fatale inviato al controller di notifica multimediale viene replicato nel PlaybackStateCompat della sessione della piattaforma. Di conseguenza, solo il codice di errore e il messaggio di errore vengono impostati su PlaybackStateCompat, mentre PlaybackStateCompat.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.
              }
            });