Wiedergabe mit einer MediaSession steuern und bewerben

Mediensitzungen bieten eine universelle Möglichkeit, mit einem Audio oder Video zu interagieren. Player. In Media3 ist der Standardplayer die Klasse ExoPlayer, mit der der Player-Schnittstelle. Wenn die Mediensitzung mit dem Player verbunden wird, kann eine App um die Medienwiedergabe extern zu bewerben und Befehle zur Wiedergabe von externen Quellen.

Befehle können sich auf physische Schaltflächen wie die Wiedergabeschaltfläche auf einem Headset oder der Fernbedienung des Fernsehers. Sie können auch von Client-Apps stammen, Mediencontroller (z. B. zum Pausieren) mit Google Assistant. Die Medien -Sitzung delegiert diese Befehle an den Player der Medien-App.

Wann sollte eine Mediensitzung ausgewählt werden?

Wenn du MediaSession implementierst, erlaubst du Nutzern, die Wiedergabe zu steuern:

  • Über ihre Kopfhörer Oft werden Schaltflächen oder Berührungen können Nutzer Medien über ihre Kopfhörer abspielen oder pausieren oder zum nächsten oder dem vorherigen Titel.
  • Durch Sprechen mit Google Assistant Ein gängiges Muster ist: "OK Google, Pause", um alle Medien zu pausieren, die gerade auf dem Gerät abgespielt werden.
  • Über die Wear OS-Smartwatch. Dies ermöglicht einen einfachen Zugriff auf die wichtigsten die gängigen Wiedergabesteuerungen bei der Wiedergabe auf dem Smartphone.
  • Über die Mediensteuerung Dieses Karussell zeigt Steuerelemente für jede laufende Mediensitzung.
  • Auf Fernsehern. Ermöglicht Aktionen mit physischen Wiedergabeschaltflächen und der Plattformwiedergabe Steuerung und Energieverwaltung (z. B. wenn der Fernseher, die Soundbar oder der AV-Receiver ausgeschaltet oder der Eingang gewechselt wird, sollte die Wiedergabe in der App beendet werden.
  • Und alle anderen externen Prozesse, die die Wiedergabe beeinflussen müssen.

Dies eignet sich für viele Anwendungsfälle. Sie sollten sich insbesondere Verwendung von MediaSession in folgenden Fällen:

  • Sie streamen Videos im Langformat, z. B. Filme oder Live-TV.
  • Sie streamen Audioinhalte im Langformat wie Podcasts oder Musik. Playlists.
  • Sie erstellen eine TV-App.

Allerdings eignen sich nicht alle Anwendungsfälle für MediaSession. Vielleicht möchten Sie Verwenden Sie in den folgenden Fällen nur das Player:

  • Sie präsentieren Kurzinhalte mit Nutzerinteraktionen und -interaktionen. ist entscheidend.
  • Es ist kein einzelnes aktives Video vorhanden, z. B. wenn der Nutzer durch eine Liste scrollt. und mehrere Videos gleichzeitig angezeigt werden.
  • Sie spielen ein einmaliges Einführungs- oder Erklärungsvideo ab, das dass sich die Nutzer aktiv Videos ansehen.
  • Bei deinen Inhalten wird der Datenschutz berücksichtigt und du möchtest nicht, dass externe Prozesse Auf Medienmetadaten zugreifen (z. B. Inkognitomodus in einem Browser)

Wenn Ihr Anwendungsfall keinem der oben aufgeführten Fälle entspricht, überlegen Sie, Die Wiedergabe wird fortgesetzt, wenn der Nutzer nicht aktiv mit der App interagiert. mit den Inhalten. Wenn die Antwort „Ja“ lautet, wählen Sie MediaSession Wenn die Antwort „nein“ lautet, sollten Sie die Player verwenden. .

Mediensitzung erstellen

Neben dem von ihm verwalteten Player befindet sich eine Mediensitzung. Sie können eine Mediensitzung mit einem Context- und einem Player-Objekt. Sie sollten eine Mediensitzung bei Bedarf initialisieren, z. B. die onStart() oder onResume()-Lebenszyklusmethode von Activity oder Fragment oder onCreate() -Methode von Service, zu der die Mediensitzung und der zugehörige Player gehören.

Um eine Mediensitzung zu erstellen, initialisieren Sie eine Player und geben Sie sie an MediaSession.Builder:

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

Automatische Statusverarbeitung

Die Media3-Bibliothek aktualisiert die Mediensitzung automatisch mithilfe der Spielerstatus angezeigt. So müssen Sie die Zuordnung von Spieler zu Sitzung wechseln.

Dies ist eine Abweichung vom Legacy-Ansatz, bei dem Sie das Erstellen und Verwalten ein PlaybackStateCompat unabhängig vom Player selbst, zum Beispiel für auf Fehler hinweisen.

Eindeutige Sitzungs-ID

Standardmäßig erstellt MediaSession.Builder eine Sitzung mit einem leeren String wie folgt: die Sitzungs-ID. Dies ist ausreichend, wenn mit einer App nur eine einzige Sitzungsinstanz. Dies ist der häufigste Fall.

Wenn eine App mehrere Sitzungsinstanzen gleichzeitig verwalten möchte, dass die Sitzungs-ID jeder Sitzung eindeutig ist. Die Sitzungs-ID kann beim Erstellen der Sitzung mit MediaSession.Builder.setId(String id) festgelegt werden.

Wenn ein IllegalStateException Ihre App mit dem Fehler abstürzt Nachricht IllegalStateException: Session ID must be unique. ID= schreiben, dann ist es Wahrscheinlichkeit, dass eine Sitzung unerwartet vor einer zuvor erstellten -Instanz mit derselben ID freigegeben wurde. Damit Sitzungen nicht von einem Programmierfehler auftreten, werden solche Fälle erkannt und Ausnahme.

Anderen Kunden Kontrolle gewähren

Die Mediensitzung ist der Schlüssel zur Steuerung der Wiedergabe. Damit können Sie Befehle von externen Quellen an den Player, über den Medien. Bei diesen Quellen kann es sich um physische Schaltflächen wie die Wiedergabeschaltfläche auf einem Headset oder Fernbedienung des Fernsehers oder indirekte Befehle wie „Pause“ mit Google Assistant. Eventuell möchten Sie auch Zugriff auf die Android- für die Steuerung von Benachrichtigungen und Sperrbildschirm um die Wiedergabe über das Zifferblatt zu steuern. Externe Kunden können Verwenden Sie einen Mediencontroller, um Wiedergabebefehle an Ihre Medien-App zu senden. Dies sind in Ihrer Mediensitzung erhalten, wodurch letztendlich Befehle an die Mediaplayer.

<ph type="x-smartling-placeholder">
</ph> Ein Diagramm, das die Interaktion zwischen MediaSession und MediaController veranschaulicht.
Abbildung 1: Medien-Controller erleichtert die Übergabe Befehle von externen Quellen an die Mediensitzung.

Wenn ein Controller eine Verbindung zu Ihrer Mediensitzung herstellen möchte, onConnect() aufgerufen wird. Sie können die bereitgestellte ControllerInfo verwenden um zu entscheiden, ob sie akzeptieren möchten, oder ablehnen der Anfrage. Ein Beispiel für das Annehmen einer Verbindungsanfrage finden Sie unter Deklarieren. verfügbare Befehle.

Nach dem Verbinden kann ein Controller Wiedergabebefehle an die Sitzung senden. Die und delegiert diese Befehle an den Player. Wiedergabe und Playlist Befehle, die in der Player-Schnittstelle definiert sind, werden automatisch vom Sitzung.

Mit anderen Callback-Methoden können Sie zum Beispiel Anfragen für benutzerdefinierte Wiedergabebefehle und die Playlist ändern). Diese Callbacks enthalten ähnlich ein ControllerInfo-Objekt, sodass Sie wie Sie auf jede Anfrage für einen Verantwortlichen reagieren.

Playlist ändern

Eine Mediensitzung kann die Playlist ihres Players direkt ändern, wie unter die ExoPlayer-Leitfaden für Playlists Controller können die Playlist auch ändern, wenn COMMAND_SET_MEDIA_ITEM oder COMMAND_CHANGE_MEDIA_ITEMS für den Controller verfügbar.

Beim Hinzufügen neuer Elemente zur Playlist benötigt der Player normalerweise MediaItem Instanzen mit einer definierter URI damit sie abgespielt werden können. Standardmäßig werden neu hinzugefügte Elemente automatisch weitergeleitet zu Spielermethoden wie player.addMediaItem, wenn dafür ein URI definiert ist.

Wenn du die MediaItem-Instanzen anpassen möchtest, die dem Player hinzugefügt wurden, hast du folgende Möglichkeiten: überschreiben onAddMediaItems() Dieser Schritt ist erforderlich, wenn Sie Controller unterstützen möchten, die Medien anfordern ohne definierten URI. Stattdessen hat die MediaItem normalerweise Mindestens eines der folgenden Felder ist festgelegt, um die angeforderten Medien zu beschreiben:

  • MediaItem.id: Eine generische ID zur Identifizierung des Mediums.
  • MediaItem.RequestMetadata.mediaUri: Ein Anfrage-URI, der einen benutzerdefinierten Schema und kann nicht unbedingt direkt vom Player wiedergegeben werden.
  • MediaItem.RequestMetadata.searchQuery: Eine Textsuchanfrage, z. B. von Google Assistant.
  • MediaItem.MediaMetadata: Strukturierte Metadaten wie „title“ oder "Interpret".

Weitere Anpassungsoptionen für völlig neue Playlists finden Sie zusätzlich überschreiben onSetMediaItems() können Sie das Startelement und die Position in der Playlist festlegen. Beispiel: können Sie ein einzelnes angefordertes Element auf eine ganze Playlist erweitern und das Player so, dass er beim Index des ursprünglich angeforderten Elements beginnt. A Beispielimplementierung von onSetMediaItems() finden Sie in der Session-Demo-App.

Benutzerdefiniertes Layout und benutzerdefinierte Befehle verwalten

In den folgenden Abschnitten wird beschrieben, wie Sie ein benutzerdefiniertes Layout für benutzerdefinierte Befehlstaste an Client-Apps und autorisieren die Controller, die benutzerdefinierten .

Benutzerdefiniertes Layout der Sitzung definieren

Um Client-Apps mitzuteilen, welche Steuerelemente für die Wiedergabe auf dem Gerät angezeigt werden sollen können Sie das benutzerdefinierte Layout der Sitzung festlegen. wenn Sie die MediaSession in der onCreate()-Methode Ihres .

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

Verfügbare Player- und benutzerdefinierte Befehle deklarieren

Medienanwendungen können benutzerdefinierte Befehle definieren, die zum Beispiel in ein benutzerdefiniertes Layout. Beispielsweise können Sie Schaltflächen implementieren, um ein Medienelement in einer Liste mit Lieblingsinhalten zu speichern. Das MediaController sendet benutzerdefinierte Befehle und MediaSession.Callback empfängt diese.

Sie können festlegen, welche benutzerdefinierten Sitzungsbefehle einem MediaController, wenn eine Verbindung zu Ihrer Mediensitzung hergestellt wird. Das erreichen Sie, MediaSession.Callback.onConnect() wird überschrieben. Konfigurieren und zurückgeben die verfügbaren Befehle beim Akzeptieren einer Verbindungsanfrage von einer MediaController in der Callback-Methode 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();
  }
}

Wenn Sie benutzerdefinierte Befehlsanfragen von einem MediaController erhalten möchten, überschreiben Sie die onCustomCommand()-Methode in der 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)
      );
    }
    ...
  }
}

Sie können verfolgen, welcher Medienverantwortliche eine Anfrage stellt, indem Sie das Feld Eigenschaft packageName des Objekts MediaSession.ControllerInfo, das an Callback-Methoden übergeben. So können Sie die Leistung Ihrer App auf einen bestimmten Befehl reagieren, wenn dieser vom System stammt, oder andere Client-Apps.

Benutzerdefiniertes Layout nach einer Nutzerinteraktion aktualisieren

Nachdem du einen benutzerdefinierten Befehl oder eine andere Interaktion mit deinem Player ausgeführt hast, das auf der Benutzeroberfläche des Controllers angezeigte Layout zu aktualisieren. Ein typisches Beispiel ist eine Ein/Aus-Schaltfläche, die ihr Symbol ändert, nachdem sie die zugehörige Aktion ausgelöst hat. mit dieser Schaltfläche. Zum Aktualisieren des Layouts können Sie 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));

Verhalten von Wiedergabebefehlen anpassen

Zum Anpassen des Verhaltens eines in der Player-Oberfläche definierten Befehls, z. B. als play() oder seekToNext() verwenden, schließen Sie Player in ForwardingPlayer ein.

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

Weitere Informationen zu ForwardingPlayer findest du im ExoPlayer-Handbuch unter Anpassung.

Anfordernden Controller eines Spielerbefehls identifizieren

Wenn ein Aufruf einer Player-Methode von MediaController eingeht, können Sie die Herkunftsquelle mit MediaSession.controllerForCurrentRequest identifizieren und rufen Sie die ControllerInfo für die aktuelle Anfrage ab:

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

Auf Medienschaltflächen reagieren

Medienschaltflächen sind Hardwaretasten auf Android-Geräten und anderen Peripheriegeräten (z. B. die Wiedergabe-/Pause-Taste an einem Bluetooth-Headset). Media3-Handles Medienschaltflächen für Sie, wenn sie zur Sitzung kommen und die Entsprechende Player-Methode im Session-Player

Eine App kann das Standardverhalten überschreiben, MediaSession.Callback.onMediaButtonEvent(Intent) In einem solchen Fall kann die App kann/muss alle API-Besonderheiten eigenständig bearbeiten.

Fehlerbehandlung und Berichterstellung

Es gibt zwei Arten von Fehlern, die eine Sitzung ausgibt und den Controllern meldet. Schwerwiegende Fehler melden einen technischen Fehler bei der Wiedergabe der Sitzung Player, der die Wiedergabe unterbricht. Schwerwiegende Fehler werden dem Controller gemeldet wenn sie auftreten. Nicht schwerwiegende Fehler sind nicht technisch oder richtlinienbedingt. Fehler, die die Wiedergabe nicht unterbrechen und vom manuell angewendet.

Schwerwiegende Wiedergabefehler

Der Player meldet einen schwerwiegenden Wiedergabefehler den Controllern gemeldet, Player.Listener.onPlayerError(PlaybackException) und Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException).

In einem solchen Fall wird der Wiedergabestatus auf STATE_IDLE umgestellt und MediaController.getPlaybackError() gibt die PlaybackException zurück, die für den Übergang. Ein Controller kann das PlayerException.errorCode prüfen, um Informationen zur Ursache des Fehlers.

Aus Interoperabilität wird ein schwerwiegender Fehler in den PlaybackStateCompat repliziert der Plattformsitzung, indem der Status in STATE_ERROR geändert wird und Fehlercode und Fehlermeldung gemäß PlaybackException angezeigt.

Schwerwiegende Fehler anpassen

Um dem Nutzer lokalisierte und aussagekräftige Informationen zur Verfügung zu stellen, werden der Fehlercode, Fehlermeldungen und Fehlerextras von schwerwiegenden Wiedergabefehlern können angepasst werden, mithilfe von ForwardingPlayer beim Erstellen der Sitzung:

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

Der Weiterleitungsplayer registriert ein Player.Listener beim tatsächlichen Spieler. und fängt Callbacks ab, die einen Fehler melden. Eine benutzerdefinierte PlaybackException wird dann an die Listener delegiert, die im Weiterleitungsplayer registriert sind. Dazu muss der Weiterleitungsplayer überschreibt Player.addListener und Player.removeListener, um Zugriff auf Listener, mit denen benutzerdefinierte Fehlercodes, Nachrichten oder Extras gesendet werden:

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

Nicht schwerwiegende Fehler

Nicht schwerwiegende Fehler, die nicht auf eine technische Ausnahme zurückzuführen sind, können gesendet werden. durch eine App an alle oder einen bestimmten Controller:

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

Ein nicht schwerwiegender Fehler, der an den Media Notification Controller gesendet wurde, wird an den PlaybackStateCompat der Plattformsitzung. Daher werden nur der Fehlercode und wird die Fehlermeldung entsprechend auf PlaybackStateCompat gesetzt, während PlaybackStateCompat.state wurde nicht in STATE_ERROR geändert.

Nicht schwerwiegende Fehler erhalten

Ein MediaController erhält einen nicht schwerwiegenden Fehler durch Implementierung 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.
              }
            });