Sterowanie odtwarzaniem i reklamowanie go za pomocą sesji MediaSession

Sesje multimedialne to uniwersalny sposób na interakcję z dźwiękiem lub filmem . W Media3 domyślnym odtwarzaczem jest klasa ExoPlayer, która implementuje w interfejsie Player. Połączenie sesji multimediów z odtwarzaczem zezwala aplikacji do reklamowania treści multimedialnych podczas odtwarzania na zewnątrz i otrzymywania poleceń odtwarzania ze źródeł zewnętrznych.

Polecenia mogą pochodzić z fizycznych przycisków, takich jak przycisk odtwarzania na zestawu słuchawkowego lub pilota do telewizora. Mogą one też pochodzić z aplikacji klienckich, które mają kontroler multimediów, np. instrukcja „wstrzymanie” do Asystenta Google. Media sesja przekazuje te polecenia do odtwarzacza aplikacji do multimediów.

Kiedy wybrać sesję multimediów

Gdy wdrożysz MediaSession, umożliwisz użytkownikom sterowanie odtwarzaniem:

  • Przez słuchawki. Często są to przyciski albo interakcje dotykowe, użytkownik może wykonać na słuchawkach, aby odtworzyć lub wstrzymać multimedia albo przejść do następnego elementu lub poprzedniego utworu.
  • Rozmawiając z Asystentem Google. Częstym wzorcem jest mówienie „OK” Google, wstrzymaj", aby wstrzymać multimedia odtwarzane aktualnie na urządzeniu.
  • Na zegarku z Wear OS. Ułatwia to dostęp do typowe elementy sterujące odtwarzaniem podczas grania na telefonie.
  • Za pomocą opcji sterowania multimediami. Ta karuzela przedstawia elementy sterujące: podczas prowadzenia sesji multimediów.
  • Na telewizorze. Umożliwia wykonywanie działań za pomocą fizycznych przycisków odtwarzania, odtwarzanie na platformie sterowania i zarządzania zasilaniem (jeśli na przykład telewizor, soundbar lub amplituner A/V wyłączy się lub źródło sygnału zostanie przełączone, odtwarzanie w aplikacji powinno zostać zatrzymane).
  • oraz wszelkie inne procesy zewnętrzne, które muszą wpłynąć na odtwarzanie.

Taka konfiguracja sprawdza się w wielu zastosowaniach. Szczególnie ważne jest, przy użyciu funkcji MediaSession, gdy:

  • Prowadzisz długie treści wideo, np. filmy lub telewizję na żywo.
  • Odtwarzasz długie treści audio, np. podcasty lub muzykę playlistach.
  • Tworzysz aplikację telewizyjną.

Jednak nie wszystkie przypadki użycia dobrze pasują do MediaSession. Możesz użyj Player tylko w tych przypadkach:

  • Wyświetlasz krótkie treści, w których interakcja i zaangażowanie użytkowników ma kluczowe znaczenie.
  • nie ma żadnego aktywnego filmu, na przykład użytkownik przewija listę. Na ekranie wyświetla się wiele filmów w tym samym czasie.
  • Grasz w jednorazowy film z wprowadzeniem lub wyjaśnieniem, oczekuje, że użytkownik będzie aktywnie oglądać treści.
  • Twoje treści mają ochronę prywatności i nie chcesz, aby zewnętrzne procesy wykonywały dostęp do metadanych multimediów (np. w trybie incognito w przeglądarce),

Jeśli Twój przypadek użycia nie pasuje do żadnej z powyższych kategorii, a Twoja aplikacja może kontynuować odtwarzanie, gdy użytkownik nie wykonuje aktywnie działań z treścią. Jeśli odpowiedź jest twierdząca, MediaSession Jeśli odpowiedź jest przecząca, skorzystaj prawdopodobnie z narzędzia Player .

Tworzenie sesji multimediów

Sesja multimedialna działa razem z odtwarzaczem, którym zarządza. Możesz utworzyć sesja multimediów z obiektami Context i Player. Należy tworzyć zainicjować sesję multimediów, gdy jest potrzebna, np. onStart() lub Metoda cyklu życia onResume() Activity, Fragment lub onCreate() metody Service, do której należy sesja multimediów i powiązany z nią odtwarzacz.

Aby utworzyć sesję multimediów, zainicjuj obiekt Player i przekaż go do funkcji MediaSession.Builder lubi to:

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

Automatyczna obsługa stanów

Biblioteka Media3 automatycznie aktualizuje sesję multimediów za pomocą o stanie gracza. W związku z tym nie musisz ręcznie obsługiwać mapowania z do sesji.

To przerwa od starszego podejścia, które wymagało tworzenia i utrzymywania PlaybackStateCompat niezależnie od odtwarzacza, na przykład do i wskazywać jakieś błędy.

Unikalny identyfikator sesji

Domyślnie MediaSession.Builder tworzy sesję z pustym ciągiem znaków: identyfikator sesji. To wystarczy, jeśli aplikacja zamierza utworzyć tylko jeden wystąpienia sesji, co jest najczęstszym przypadkiem.

Jeśli aplikacja chce zarządzać wieloma wystąpieniami sesji jednocześnie, musi zapewnić niepowtarzalny identyfikator każdej sesji. Identyfikator sesji może być ustawiana podczas tworzenia sesji w narzędziu MediaSession.Builder.setId(String id).

Jeśli widzisz błąd IllegalStateException powodujący awarię aplikacji wyślij wiadomość do: IllegalStateException: Session ID must be unique. ID=, to jest sesja została nieoczekiwanie utworzona przed opublikowano instancję o tym samym identyfikatorze. Aby uniknąć wycieku sesji przez błąd programowania, takie przypadki są wykrywane i powiadamiane wyjątek.

Przyznaj kontrolę innym klientom

Sesja multimediów to klucz do sterowania odtwarzaniem. Umożliwia kierowanie poleceń ze źródeł zewnętrznych do odtwarzacza, który odpowiada za odtwarzanie multimediów. Źródłem tych mogą być przyciski fizyczne, takie jak przycisk odtwarzania na zestaw słuchawkowy lub pilot do telewizora, a także polecenia pośrednie, takie jak instrukcja „wstrzymaj” do Asystenta Google. Możesz też zechcieć udzielić dostępu do konta do obsługi powiadomień i ekranu blokady lub Wear OS i sterować odtwarzaniem z tarczy zegarka. Klienci zewnętrzni mogą używać kontrolera multimediów do wydawania poleceń odtwarzania w aplikacji do multimediów. Są to w ramach sesji multimediów, która przekazuje polecenia do odtwarzacza multimediów.

Diagram prezentujący interakcję między obiektem MediaSession i MediaController.
Rysunek 1. Kontroler multimediów polecenia ze źródeł zewnętrznych do sesji multimediów.
.

Gdy kontroler ma się połączyć z sesją multimediów, onConnect() . Za pomocą tego narzędzia możesz: ControllerInfo podjąć decyzję o zaakceptowaniu lub odrzuć do danej prośby. Przykład akceptowania prośby o połączenie znajdziesz w sekcji Deklarowanie dostępnych poleceń.

Po połączeniu kontroler może wysyłać do sesji polecenia odtwarzania. sesja, następnie przekazuje te polecenia do odtwarzacza. Odtwarzanie i playlista polecenia zdefiniowane w interfejsie Player są automatycznie obsługiwane przez .

Inne metody wywołania zwrotnego umożliwiają obsługę na przykład żądań dotyczących własnych poleceń odtwarzania zmieniając playlistę). Te wywołania zwrotne podobnie zawierają obiekt ControllerInfo, więc możesz go zmodyfikować jak reagować na poszczególne żądania z uwzględnieniem każdego administratora.

Modyfikowanie playlisty

Sesja multimediów może bezpośrednio modyfikować playlistę w odtwarzaczu, jak opisano w tym artykule: Przewodnik po playlistach w ExoPlayer. Kontrolery mogą też modyfikować playlistę, jeśli: COMMAND_SET_MEDIA_ITEM lub COMMAND_CHANGE_MEDIA_ITEMS jest dostępna dla kontrolera.

Podczas dodawania nowych elementów do playlisty odtwarzacz zwykle wymaga dodatku MediaItem z instancjami z zdefiniowany identyfikator URI aby można było grać w nich. Domyślnie nowo dodane elementy są automatycznie przekazywane dalej do metod odtwarzacza, takich jak player.addMediaItem, jeśli mają zdefiniowany identyfikator URI.

Jeśli chcesz dostosować instancje MediaItem dodane do odtwarzacza, możesz zastąpić onAddMediaItems() Ten krok jest potrzebny, gdy chcesz obsługiwać kontrolery, które wysyłają żądania multimediów bez zdefiniowanego identyfikatora URI. Zamiast tego MediaItem zwykle ma ustawiono co najmniej jedno z tych pól opisujących żądane multimedia:

  • MediaItem.id: ogólny identyfikator identyfikujący multimedia.
  • MediaItem.RequestMetadata.mediaUri: identyfikator URI żądania, który może zawierać niestandardowy i nie musi być bezpośrednio odtwarzany przez odtwarzacz.
  • MediaItem.RequestMetadata.searchQuery: zapytanie tekstowe, np. za pomocą Asystenta Google.
  • MediaItem.MediaMetadata: uporządkowane metadane, np. „tytuł”. lub „artist”.

Aby zyskać więcej opcji dostosowywania zupełnie nowych playlist, dodatkowo zastąp onSetMediaItems() który pozwala określić początek playlisty i jej pozycję. Przykład: możesz rozwinąć żądaną pozycję do całej playlisty i poinstruować się w celu rozpoczęcia od indeksu pierwotnie żądanego elementu. O przykładowa implementacja onSetMediaItems() z tą funkcją znajdziesz w aplikacji demonstracyjnej sesji.

Zarządzanie układem niestandardowym i poleceniami niestandardowymi

W poniższych sekcjach opisano, jak reklamować niestandardowy układ strony do aplikacji klienckich i autoryzacji kontrolerów do wysyłania poleceń.

Zdefiniuj niestandardowy układ sesji

Aby wskazać aplikacjom klienckim, które elementy sterujące odtwarzaniem chcesz udostępnić ustaw niestandardowy układ sesji. podczas tworzenia MediaSession w metodzie onCreate() Twojego posprzedażna.

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

Zadeklaruj dostępny odtwarzacz i polecenia niestandardowe

Aplikacje multimedialne mogą definiować polecenia niestandardowe, których można używać na przykład lub układ niestandardowy. Możesz na przykład utworzyć przyciski, które umożliwiają użycie aby zapisać element multimedialny na liście ulubionych. MediaController wysyła polecenia niestandardowe, a MediaSession.Callback je otrzymuje.

Możesz określić, które niestandardowe polecenia sesji będą dostępne dla MediaController, gdy połączy się z sesją multimediów. Jest to możliwe dzięki zastępując MediaSession.Callback.onConnect(). Skonfiguruj i zwróć zestawu poleceń dostępnych podczas akceptowania żądania połączenia MediaController w metodzie wywołania zwrotnego 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();
  }
}

Aby otrzymywać niestandardowe polecenia z MediaController, zastąp onCustomCommand() w 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)
      );
    }
    ...
  }
}

Możesz prześledzić, który kontroler multimediów wysyła żądanie, używając Właściwość packageName obiektu MediaSession.ControllerInfo, który jest przekazywane do metod Callback. Dzięki temu możesz dostosować w odpowiedzi na dane polecenie, jeśli pochodzi ono z systemu, do własnych aplikacji lub do innych aplikacji klienckich.

Aktualizacja układu niestandardowego po interakcji użytkownika

Po obsługi polecenia niestandardowego lub innej interakcji z odtwarzaczem może chcieć zaktualizować układ wyświetlany w interfejsie kontrolera. Typowy przykład to przycisk przełączania, który zmienia swoją ikonę po uruchomieniu powiązanego działania klikając ten przycisk. Aby zaktualizować układ, możesz użyć 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));

Dostosowywanie działania poleceń odtwarzania

Aby dostosować działanie polecenia zdefiniowanego w interfejsie Player: jako play() lub seekToNext(), umieść Player w: 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();

Więcej informacji na temat ForwardingPlayer znajdziesz w przewodniku ExoPlayer na stronie Dostosowywanie.

Identyfikowanie kontrolera żądającego polecenia odtwarzacza

Jeśli wywołanie metody Player jest inicjowane przez usługę MediaController, możesz określ źródło pochodzenia za pomocą parametru MediaSession.controllerForCurrentRequest i pobierz ControllerInfo dla bieżącego żądania:

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

Reagowanie na przyciski multimediów

Przyciski multimediów to przyciski sprzętowe dostępne na urządzeniach z Androidem i innych urządzeniach peryferyjnych urządzeniach takich jak przycisk odtwarzania/wstrzymywania na słuchawkach Bluetooth. Uchwyty Media3 zdarzenia związane z przyciskiem multimedialnym po dotarciu do sesji i wywołaniu funkcji odpowiednią metodę Player w odtwarzaczu w sesji.

Aplikacja może zastąpić domyślne działanie przez zastąpienie MediaSession.Callback.onMediaButtonEvent(Intent) W takim przypadku aplikacja może/musi samodzielnie radzić sobie ze wszystkimi specyfikami interfejsu API.

Obsługa błędów i raportowanie

Istnieją 2 typy błędów, które sesja generuje i zgłasza kontrolery. Błędy krytyczne zgłaszają niepowodzenie techniczne podczas odtwarzania sesji. który przerywa odtwarzanie. Błędy krytyczne są zgłaszane do kontrolera automatycznie, gdy tylko wystąpią. Błędy niekrytyczne nie są związane z naruszeniem zasad ani technicznymi które nie przerywają odtwarzania i są wysyłane do kontrolerów przez aplikacji ręcznie.

Krytyczne błędy odtwarzania

Odtwarzacz zgłasza do sesji błąd krytyczny podczas odtwarzania, a następnie przekazywane kontrolerom na potrzeby połączenia Player.Listener.onPlayerError(PlaybackException) i Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException)

W takim przypadku stan odtwarzania jest zmieniany na STATE_IDLE i MediaController.getPlaybackError() zwraca błąd PlaybackException, który spowodował o przeniesieniu. Kontroler może sprawdzać PlayerException.errorCode, aby uzyskać o przyczynie błędu.

W przypadku interoperacyjności błąd krytyczny jest replikowany w PlaybackStateCompat sesji platformy przez zmianę jej stanu na STATE_ERROR i ustawienie z kodem błędu i komunikatem zgodnie z dokumentem PlaybackException.

Dostosowywanie błędu krytycznego

Aby udostępnić użytkownikowi zlokalizowane i wartościowe informacje, kod błędu w komunikatach o błędach i elementach dodatkowych o krytycznym błędzie odtwarzania można dostosować, za pomocą funkcji ForwardingPlayer podczas budowania sesji:

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

Odtwarzacz przekierowujący rejestruje Player.Listener w odtwarzaczu i przechwytuje wywołania zwrotne, które zgłaszają błąd. Profil niestandardowy Pole PlaybackException jest przekazywane detektorom, są zarejestrowane w odtwarzaczu przekierowania. Aby to działało, odtwarzacz przekierowujący zastępuje Player.addListener i Player.removeListener, aby mieć dostęp do detektory, za pomocą których można wysłać dostosowany kod błędu, komunikat lub dodatki:

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

Błędy niekrytyczne

Mogą zostać wysłane błędy niekrytyczne, które nie pochodzą z wyjątku technicznego. przez aplikację do wszystkich lub określonego kontrolera:

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

Błąd niekrytyczny wysyłany do kontrolera powiadomień o multimediach jest replikowany do PlaybackStateCompat sesji na platformie. Oznacza to, że jedynie kod błędu komunikat o błędzie jest odpowiednio ustawiony na PlaybackStateCompat, podczas gdy Pole PlaybackStateCompat.state nie zostało zmienione na wartość STATE_ERROR.

Otrzymuj błędy niekrytyczne

W przypadku MediaController występuje błąd niekrytyczny po wdrożeniu 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.
              }
            });