Sterowanie odtwarzaniem i reklamowanie go za pomocą sesji MediaSession

Sesje multimedialne zapewniają uniwersalny sposób interakcji z odtwarzaczem audio lub wideo. W Media3 domyślnym odtwarzaczem jest klasa ExoPlayer, która implementuje interfejs Player. Połączenie sesji multimediów z odtwarzaczem umożliwia aplikacji wyświetlanie reklam odtwarzania multimediów na zewnątrz i odbieranie poleceń odtwarzania z zewnętrznych źródeł.

Polecenia mogą pochodzić z przycisków fizycznych, takich jak przycisk odtwarzania na słuchawkach lub pilocie do telewizora. Mogą one też pochodzić z aplikacji klienckich, które mają kontroler multimediów, np. polecenie „wstrzymaj” dla Asystenta Google. Sesja multimediów deleguje te polecenia do odtwarzacza aplikacji multimedialnej.

Kiedy wybrać sesję multimediów

Gdy użyjesz MediaSession, użytkownicy będą mogli kontrolować odtwarzanie:

  • za pomocą słuchawek. Często w słuchawkach są przyciski lub funkcje dotykowe, które umożliwiają użytkownikowi odtwarzanie lub wstrzymywanie multimediów albo przechodzenie do poprzedniego lub następnego utworu.
  • przez rozmowę z Asystentem Google. Typowym poleceniem jest „OK Google, wstrzymaj”, aby wstrzymać odtwarzanie multimediów na urządzeniu.
  • za pomocą zegarka z Wear OS, Dzięki temu użytkownicy będą mogli łatwiej korzystać z najczęstszych elementów sterowania odtwarzaniem podczas oglądania na telefonie.
  • za pomocą opcji sterowania multimediami. Ten pokaz slajdów zawiera elementy sterujące dla każdej trwającej sesji multimediów.
  • Na telewizorze. Umożliwia wykonywanie działań przy użyciu fizycznych przycisków odtwarzania, sterowania odtwarzaniem na platformie i zarządzania energią (np. jeśli telewizor, soundbar lub odbiornik A/V wyłączy się lub zmieni wejście, odtwarzanie w aplikacji powinno się zatrzymać).
  • oraz inne procesy zewnętrzne, które muszą wpływać na odtwarzanie.

Jest to przydatne w wielu przypadkach. Szczególnie zalecamy używanie MediaSession w tych przypadkach:

  • Odtwarzasz długie filmy, takie jak filmy czy telewizja na żywo.
  • Odtwarzasz długie treści audio, takie jak podcasty czy playlisty muzyczne.
  • Tworzysz aplikację na telewizor.

Nie wszystkie przypadki użycia jednak pasują do MediaSession. W tych sytuacjach możesz użyć tylko Player:

  • wyświetlasz krótkie treści, które nie wymagają zewnętrznego sterowania ani odtwarzania w tle;
  • Nie ma pojedynczego aktywnego filmu, np. użytkownik przewija listę, a na ekranie wyświetla się kilka filmów jednocześnie.
  • Odtwarzasz jednorazowy film wprowadzający lub wyjaśniający, który użytkownik powinien aktywnie obejrzeć bez potrzeby korzystania z zewnętrznych elementów sterujących odtwarzaniem.
  • Twoje treści są poufne i nie chcesz, aby procesy zewnętrzne miały dostęp do metadanych multimediów (np. w trybie incognito w przeglądarce).

Jeśli Twój przypadek użycia nie pasuje do żadnego z wymienionych powyżej, zastanów się, czy zgadzasz się na kontynuowanie odtwarzania przez aplikację, gdy użytkownik nie jest aktywnie zaangażowany w treści. Jeśli odpowiedź brzmi „tak”, prawdopodobnie wybierzesz MediaSession. Jeśli odpowiedź jest przecząca, użyj operatora Player.

Tworzenie sesji multimedialnej

Sesja multimedialna jest związana z odtwarzaczem, którym zarządza. Sesję multimedialną możesz utworzyć za pomocą obiektu Context i Player. W razie potrzeby należy utworzyć i zainicjować sesję multimediów, na przykład za pomocą metody cyklu życia onStart() lub onResume() obiektu Activity lub Fragment albo metody onCreate() obiektu Service, który jest właścicielem sesji multimediów i powiązanego z nią odtwarzacza.

Aby utworzyć sesję multimediów, zainicjuj Player i prześlij ją do MediaSession.Builder w ten sposób:

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

Automatyczne zarządzanie stanem

Biblioteka Media3 automatycznie aktualizuje sesję multimediów na podstawie stanu odtwarzacza. W związku z tym nie musisz ręcznie mapować odtwarzacza na sesję.

Różnica polega na tym, że w przypadku sesji mediów na platformie musisz tworzyć i utrzymywać PlaybackState niezależnie od samego odtwarzacza, aby na przykład wskazywać błędy.

Unikalny identyfikator sesji

Domyślnie funkcja MediaSession.Builder tworzy sesję z pustym ciągiem znaków jako identyfikatorem sesji. Wystarczy to, jeśli aplikacja ma utworzyć tylko 1 sesję (co jest najczęstszym przypadkiem).

Jeśli aplikacja chce zarządzać kilkoma instancjami sesji jednocześnie, musi zadbać o to, aby identyfikator każdej sesji był unikalny. Identyfikator sesji można ustawić podczas tworzenia sesji za pomocą funkcji MediaSession.Builder.setId(String id).

Jeśli widzisz, że IllegalStateException powoduje awarię aplikacji z komunikatem o błędzie IllegalStateException: Session ID must be unique. ID=, prawdopodobnie sesja została utworzona nieoczekiwanie, zanim wcześniej utworzona instancja o tym samym identyfikatorze została zwolniona. Aby uniknąć wycieku informacji o sesjach z powodu błędu programistycznego, takie przypadki są wykrywane i zgłaszane przez wyjątek.

Przyznawanie kontroli innym klientom

Sesja multimediów jest kluczowa dla sterowania odtwarzaniem. Umożliwia ona kierowanie poleceń z zewnętrznych źródeł do odtwarzacza, który odtwarza Twoje multimedia. Źródłami mogą być przyciski fizyczne, takie jak przycisk odtwarzania na zestawie słuchawkowym lub pilocie telewizora, albo polecenia pośrednie, takie jak polecenie „zatrzymaj” skierowane do Asystenta Google. Możesz też przyznać dostęp do systemu Android, aby ułatwić obsługę powiadomień i ekranu blokady, lub do zegarka z Wear OS, aby móc sterować odtwarzaniem z tarczy zegarka. Klienci zewnętrzni mogą używać kontrolera multimediów, aby wydawać polecenia odtwarzania do aplikacji multimedialnej. Są one odbierane przez sesję multimediów, która ostatecznie deleguje polecenia do odtwarzacza multimediów.

Diagram pokazujący interakcję między MediaSession a MediaController.
Ilustracja 1. Sterownik multimediów ułatwia przekazywanie poleceń z zewnętrznych źródeł do sesji multimediów.
.

Gdy kontroler ma połączyć się z sesją multimediów, wywoływana jest metoda onConnect(). Możesz użyć informacji zawartych w ControllerInfo, aby zdecydować, czy zaakceptować, czy odrzucić prośbę. Przykład zaakceptowania prośby o połączenie znajdziesz w sekcji Zadeklaruj polecenia niestandardowe.

Po połączeniu kontroler może wysyłać do sesji polecenia odtwarzania. Sesja przekazuje te polecenia odtwarzaczowi. Polecenia odtwarzania i playlist zdefiniowane w interfejsie Player są automatycznie obsługiwane przez sesję.

Inne metody wywołania zwrotnego umożliwiają obsługę np. żądań komend niestandardowychmodyfikowania playlisty. Te funkcje zwrotne zawierają też obiekt ControllerInfo, dzięki czemu możesz modyfikować sposób odpowiadania na poszczególne żądania w zależności od kontrolera.

Modyfikowanie playlisty

Sesja multimediów może bezpośrednio modyfikować playlistę odtwarzacza zgodnie z opisem w przewodniku po playlistach ExoPlayera. Kontroler może też zmodyfikować playlistę, jeśli ma dostęp do opcji COMMAND_SET_MEDIA_ITEM lub COMMAND_CHANGE_MEDIA_ITEMS.

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

Jeśli chcesz dostosować instancje MediaItem dodane do odtwarzacza, możesz je zastąpić, używając parametru onAddMediaItems(). Ten krok jest potrzebny, gdy chcesz obsługiwać kontrolery, które żądają multimediów bez zdefiniowanego identyfikatora URI. Zwykle zawiera ona co najmniej 1 z tych pól, które opisują żądane media:MediaItem

  • MediaItem.id: ogólny identyfikator mediów.
  • MediaItem.RequestMetadata.mediaUri: Identyfikator URI żądania, który może używać niestandardowego schematu i niekoniecznie jest bezpośrednio odtwarzany przez odtwarzacz.
  • MediaItem.RequestMetadata.searchQuery: tekstowe zapytanie, np. z Asystenta Google.
  • MediaItem.MediaMetadata: uporządkowane metadane, takie jak „title” (tytuł) czy „artist” (artysta).

Aby uzyskać więcej opcji dostosowywania zupełnie nowych playlist, możesz dodatkowo zastąpić onSetMediaItems(), co pozwala określić element początkowy i jego pozycję na playliście. Możesz na przykład rozszerzyć pojedynczy żądany element do całej playlisty i poprosić odtwarzacz, aby zaczął od indeksu pierwotnie żądanego elementu. Przykładowa implementacja onSetMediaItems() z tą funkcją jest dostępna w aplikacji demonstracyjnej sesji.

Zarządzanie ustawieniami przycisków multimediów

Każdy kontroler, np. interfejs systemowy, Android Auto czy Wear OS, może samodzielnie decydować, które przyciski wyświetlić użytkownikowi. Aby wskazać, które elementy sterujące odtwarzaniem chcesz udostępnić użytkownikowi, możesz określić preferencje przycisków multimediów na stronie MediaSession. Te ustawienia składają się z uporządkowanej listy instancji CommandButton, z których każda definiuje ustawienie przycisku w interfejsie użytkownika.

Definiowanie przycisków poleceń

CommandButton służą do definiowania ustawień przycisków multimediów. Każdy przycisk określa 3 aspekty pożądanego elementu interfejsu:

  1. Ikona określająca wygląd wizualny. Podczas tworzenia CommandButton.Builder ikona musi być ustawiona na jedną z wstępnie zdefiniowanych stałych. Pamiętaj, że nie jest to prawdziwa mapa bitowa ani zasób obrazów. Stałe wartości ogólne pomagają kontrolerom wybrać odpowiedni zasób, aby zapewnić spójny wygląd i wrażenia w ich interfejsie. Jeśli żadna z wstępnie zdefiniowanych stałych ikon nie pasuje do Twojego przypadku użycia, możesz użyć zamiast niej setCustomIconResId.
  2. Polecenie określające działanie, które zostanie wykonane, gdy użytkownik wejdzie w interakcję z przyciskiem. Możesz użyć setPlayerCommand dla Player.Command lub setSessionCommand dla wstępnie zdefiniowanego lub niestandardowego SessionCommand.
  3. Slot określający, gdzie przycisk powinien znajdować się w interfejsie sterowania kontrolerem. To pole jest opcjonalne i jest automatycznie ustawiane na podstawie ikonypolecenia. Na przykład pozwala określić, że przycisk powinien być wyświetlany w obszarze nawigacji „do przodu” w interfejsie zamiast w domyślnym obszarze „wypełniania”.

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

Gdy preferencje przycisków multimediów zostaną rozwiązane, zostanie zastosowany ten algorytm:

  1. W przypadku każdego CommandButtonustawieniach przycisków multimedialnych umieść przycisk w pierwszym dostępnym i dozwolonym miejscu.
  2. Jeśli którykolwiek z centralnych, do przodu i do tyłu miejsc jest pusty, dodaj do niego domyślne przyciski.

Za pomocą CommandButton.DisplayConstraints możesz wygenerować podgląd tego, jak preferencje przycisków multimediów będą rozwiązywane w zależności od ograniczeń wyświetlania interfejsu.

Konfigurowanie ustawień przycisku multimediów

Najprostszym sposobem ustawienia preferencji przycisków multimediów jest zdefiniowanie listy podczas tworzenia MediaSession. Możesz też zastąpić ustawienie MediaSession.Callback.onConnect, aby dostosować ustawienia przycisków multimediów dla każdego podłączonego kontrolera.

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

Aktualizowanie ustawień przycisku multimediów po interakcji użytkownika

Po obsłudze interakcji z odtwarzaczem możesz zaktualizować przyciski wyświetlane w interfejsie kontrolera. Typowym przykładem jest przełącznik, który zmienia swoją ikonę i działanie po wywołaniu działania powiązanego z tym przyciskiem. Aby zaktualizować ustawienia przycisków multimediów, możesz użyć opcji MediaSession.setMediaButtonPreferences, aby zaktualizować ustawienia wszystkich kontrolerów lub konkretnego kontrolera:

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

Dodawanie niestandardowych poleceń i dostosowywanie domyślnego zachowania

Dostępne polecenia odtwarzacza można rozszerzyć o polecenia niestandardowe. Można też przechwycić przychodzące polecenia odtwarzacza i przyciski multimediów, aby zmienić domyślne działanie.

Deklarowanie i obsługa komend niestandardowych

Aplikacje multimedialne mogą definiować polecenia niestandardowe, które można na przykład używać w ustawieniach przycisku multimediów. Możesz na przykład zaimplementować przyciski, które umożliwiają użytkownikowi zapisanie elementu multimedialnego na liście ulubionych. Urządzenie MediaController wysyła polecenia niestandardowe, a urządzenie MediaSession.Callback je odbiera.

Aby zdefiniować polecenia niestandardowe, musisz zastąpić MediaSession.Callback.onConnect(), aby ustawić dostępne polecenia niestandardowe dla każdego podłączonego kontrolera.

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

Aby otrzymywać żądania niestandardowych poleceń z poziomu MediaController, zastąpij metodę onCustomCommand() w klasie 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)
      );
    }
    ...
  }
}

Korzystając z właściwości packageName obiektu MediaSession.ControllerInfo, możesz śledzić, który kontroler multimediów wysyła żądanie. Właściwość ta jest przekazywana do metod Callback. Pozwala to dostosować działanie aplikacji na podstawie danego polecenia, jeśli pochodzi ono z systemu, Twojej aplikacji lub innej aplikacji klienta.

Dostosowywanie domyślnych poleceń odtwarzacza

Wszystkie domyślne polecenia i obsługa stanu są delegowane do Player, który znajduje się na MediaSession. Aby dostosować działanie polecenia zdefiniowanego w interfejsie Player, np. play() lub seekToNext(), owiń PlayerForwardingSimpleBasePlayer, zanim przekażesz go do 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();

Więcej informacji o ForwardingSimpleBasePlayer znajdziesz w przewodniku ExoPlayer dotyczącym personalizacji.

Określanie żądającego kontrolera polecenia gracza

Gdy wywołanie metody Player pochodzi z poziomu MediaController, możesz zidentyfikować źródło za pomocą MediaSession.controllerForCurrentRequesti uzyskać ControllerInfo dla bieżącego żądania:

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

Dostosowywanie obsługi przycisków multimediów

Przyciski multimedialne to przyciski sprzętowe dostępne na urządzeniach z Androidem i innych urządzeniach peryferyjnych, np. przycisk odtwarzania/pauzowania w słuchawkach Bluetooth. Media3 obsługuje zdarzenia przycisku multimediów, gdy docierają do sesji, i wywołuje odpowiednią metodę Playerodtwarzaczu sesji.

Zalecamy obsługę wszystkich przychodzących zdarzeń przycisku multimedialnego w odpowiedniej metodzie Player. W zaawansowanych przypadkach użycia zdarzenia przycisku multimediów można przechwytywać w komponentach MediaSession.Callback.onMediaButtonEvent(Intent).

Obsługa i zgłaszanie błędów

Sesja może generować i przekazywać kontrolerom 2 rodzaje błędów. Błędy krytyczne wskazują na techniczną awarię odtwarzacza sesji, która przerywa odtwarzanie. Błędy krytyczne są automatycznie zgłaszane sterownikowi, gdy tylko wystąpią. Błędy niekrytyczne to błędy nietechniczne lub błędy związane z przestrzeganiem zasad, które nie przerywają odtwarzania i są wysyłane do kontrolerów przez aplikację ręcznie.

Krytyczne błędy odtwarzania

Odtwarzacz zgłasza do sesji krytyczny błąd odtwarzania, a potem do sterowników, aby wywołać Player.Listener.onPlayerError(PlaybackException)Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException).

W takim przypadku stan odtwarzania zmienia się na STATE_IDLE, a MediaController.getPlaybackError() zwraca wartość PlaybackException, która spowodowała przejście. Kontroler może sprawdzić PlayerException.errorCode, aby uzyskać informacje o przyczynie błędu.

W celu zapewnienia interoperacyjności błąd krytyczny jest replikowany do sesji platformy przez przejście do stanu STATE_ERROR i ustawienie kodu błędu oraz komunikatu zgodnie z PlaybackException.

Dostosowywanie błędów krytycznych

Aby wyświetlać użytkownikowi lokalizowane i przydatne informacje, kod błędu, komunikat o błędzie i informacje dodatkowe dotyczące krytycznego błędu odtwarzania można dostosować, używając ForwardingPlayer podczas tworzenia 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 do przekierowywania może używać elementu ForwardingSimpleBasePlayer do przechwytywania błędów i dostosowywania kodu błędu, komunikatu lub dodatkowych informacji. W ten sam sposób możesz generować nowe błędy, które nie występują w oryginalnym odtwarzaczu:

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

Błędy niekrytyczne

Aplikacja może wysłać błędy niekrytyczne, które nie pochodzą z wyjątku technicznego, do wszystkich lub do konkretnego kontrolera:

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

Gdy do kontrolera powiadomień o mediach zostanie wysłany błąd niekrytyczny, kod błędu i komunikat o błędzie zostaną powielone do sesji mediów na platformie, a wartość PlaybackState.state nie zostanie zmieniona na STATE_ERROR.

Otrzymywanie błędów niekrytycznych

Aplikacja MediaController otrzymuje błąd niekrytyczny, ponieważ implementuje 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.
              }
            });