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 multimedialnej z odtwarzaczem umożliwia aplikacji przekazywanie informacji o odtwarzaniu multimediów źródłom zewnętrznym i otrzymywanie poleceń odtwarzania ze źródeł zewnętrznych.

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

Kiedy wybrać sesję multimedialną

Gdy zaimplementujesz MediaSession, użytkownicy będą mogli sterować odtwarzaniem:

  • Przez słuchawki. Słuchawki często mają przyciski lub funkcje dotykowe, które umożliwiają odtwarzanie i wstrzymywanie multimediów oraz przechodzenie do następnego lub poprzedniego utworu.
  • Rozmawiając z Asystentem Google. Często używane polecenie to „OK Google, wstrzymaj”, które wstrzymuje multimedia odtwarzane w danym momencie na urządzeniu.
  • Za pomocą zegarka z Wear OS. Ułatwia to dostęp do najczęściej używanych elementów sterujących odtwarzaniem podczas korzystania z telefonu.
  • Za pomocą opcji sterowania multimediami. Ta karuzela zawiera elementy sterujące dla każdej aktywnej sesji multimedialnej.
  • Na telewizorze. Umożliwia to wykonywanie działań za pomocą fizycznych przycisków odtwarzania, sterowania odtwarzaniem na platformie i zarządzania zasilaniem (np. jeśli telewizor, soundbar lub amplituner wyłączy się lub zmieni wejście, odtwarzanie w aplikacji powinno się zatrzymać).
  • Za pomocą elementów sterujących multimediami w Androidzie Auto. Umożliwia to bezpieczne sterowanie odtwarzaniem podczas jazdy.
  • Za pomocą wszystkich innych procesów zewnętrznych, które mają wpływ na odtwarzanie.

Jest to przydatne w wielu zastosowaniach. W szczególności warto rozważyć użycie MediaSession, gdy:

  • przesyłasz strumieniowo długie treści wideo, takie jak filmy czy telewizja na żywo;
  • przesyłasz strumieniowo długie treści audio, takie jak podcasty lub playlisty muzyczne;
  • tworzysz aplikację TV.

Nie wszystkie przypadki użycia pasują jednak do MediaSession. W tych przypadkach warto użyć tylko interfejsu Player:

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

Jeśli Twój przypadek użycia nie pasuje do żadnego z wymienionych powyżej, zastanów się, czy chcesz, aby aplikacja kontynuowała odtwarzanie, gdy użytkownik nie ogląda treści w sposób aktywny. Jeśli tak, prawdopodobnie lepiej wybrać MediaSession. Jeśli nie, prawdopodobnie zamiast tego lepiej użyć Player.

Tworzenie sesji multimedialnej

Sesja multimedialna jest powiązana z odtwarzaczem, którym zarządza. Sesję multimedialną możesz utworzyć za pomocą obiektów ContextPlayer. Sesję multimedialną należy utworzyć i zainicjować, gdy jest to potrzebne, np. w metodzie cyklu życia onStart() lub onResume() komponentu Activity lub Fragment albo w metodzie onCreate() komponentu Service, który jest właścicielem sesji multimedialnej i powiązanego z nią odtwarzacza.

Aby utworzyć sesję multimedialną, zainicjuj obiekt Player i przekaż go do obiektu 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();

Automatyczna obsługa stanu

Biblioteka Media3 automatycznie aktualizuje sesję multimedialną na podstawie stanu odtwarzacza. W związku z tym nie musisz ręcznie obsługiwać mapowania odtwarzacza na sesję.

Różni się to od sesji multimedialnej platformy, w której trzeba było utworzyć i utrzymywać PlaybackState niezależnie od samego odtwarzacza, np. aby wskazać błędy.

Unikalny identyfikator sesji

Domyślnie klasa MediaSession.Builder tworzy sesję z pustym ciągiem znaków jako identyfikatorem sesji. Jest to wystarczające, jeśli aplikacja ma utworzyć tylko jedną instancję sesji, 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ł niepowtarzalny. Identyfikator sesji można ustawić podczas tworzenia sesji za pomocą metody MediaSession.Builder.setId(String id).

Jeśli widzisz IllegalStateException, który powoduje awarię aplikacji z komunikatem o błędzie IllegalStateException: Session ID must be unique. ID=, prawdopodobnie sesja została nieoczekiwanie utworzona przed zwolnieniem wcześniej utworzonej instancji o tym samym identyfikatorze. Aby uniknąć wycieku sesji z powodu błędu programowania, takie przypadki są wykrywane i zgłaszane przez zgłoszenie wyjątku.

Przyznawanie kontroli innym klientom

Sesja multimedialna jest kluczem do sterowania odtwarzaniem. Umożliwia kierowanie poleceń ze źródeł zewnętrznych do odtwarzacza, który odtwarza multimedia. Źródłami mogą być przyciski fizyczne, np. przycisk odtwarzania na zestawie słuchawkowym lub pilocie do telewizora, albo polecenia pośrednie, np. polecenie „wstrzymaj” wydane Asystentowi Google. Możesz też przyznać dostęp do systemu Android, aby ułatwić sterowanie powiadomieniami i ekranem blokady, lub do zegarka z Wear OS, aby sterować odtwarzaniem z poziomu tarczy zegarka. Klienty zewnętrzne mogą używać kontrolera multimediów do wydawania poleceń odtwarzania aplikacji multimedialnej. Są one odbierane przez sesję multimedialną, która ostatecznie przekazuje polecenia do odtwarzacza.

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

Gdy kontroler ma się połączyć z sesją multimedialną, wywoływana jest metoda onConnect(). Na podstawie podanego obiektu ControllerInfo możesz zaakceptować lub odrzucić prośbę. Przykład akceptowania prośby o połączenie znajdziesz w sekcji Deklarowanie poleceń niestandardowych.

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

Inne metody wywołania zwrotnego umożliwiają obsługę np. próśb o niestandardowe poleceniamodyfikowanie playlisty. Te wywołania zwrotne również zawierają obiekt ControllerInfo, dzięki czemu możesz modyfikować sposób odpowiadania na poszczególne żądania w przypadku każdego kontrolera.

Modyfikowanie playlisty

Sesja multimedialna może bezpośrednio modyfikować playlistę odtwarzacza, jak wyjaśniono w przewodniku dotyczącym playlist w bibliotece ExoPlayer. Kontrolery mogą też modyfikować playlistę, jeśli mają dostęp do COMMAND_SET_MEDIA_ITEM lub COMMAND_CHANGE_MEDIA_ITEMS.

Podczas dodawania nowych elementów do playlisty odtwarzacz zwykle wymaga instancji MediaItemokreślonymi identyfikatorami 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 dodawane do odtwarzacza, możesz zastąpić onAddMediaItems(). Ten krok jest potrzebny, jeśli chcesz obsługiwać kontrolery, które żądają multimediów bez zdefiniowanego identyfikatora URI. Zamiast tego w MediaItem zwykle ustawia się co najmniej jedno z tych pól, aby opisać żądane multimedia:

  • MediaItem.id: ogólny identyfikator multimediów.
  • MediaItem.RequestMetadata.mediaUri: identyfikator URI żądania, który może używać niestandardowego schematu i nie musi być bezpośrednio odtwarzany przez odtwarzacz.
  • MediaItem.RequestMetadata.searchQuery: tekst wyszukiwanego hasła, np. z Asystenta Google.
  • MediaItem.MediaMetadata: uporządkowane metadane, takie jak „tytuł” lub „wykonawca”.

Aby uzyskać więcej opcji dostosowywania zupełnie nowych playlist, możesz dodatkowo zastąpić metodę onSetMediaItems(), która pozwala zdefiniować element początkowy i jego pozycję na playliście. Możesz na przykład rozszerzyć pojedynczy żądany element na całą playlistę i polecić odtwarzaczowi rozpoczęcie od indeksu pierwotnie żądanego elementu. Przykładową implementację onSetMediaItems() z tą funkcją znajdziesz w aplikacji demonstracyjnej sesji.

Zarządzanie ustawieniami przycisków multimedialnych

Każdy kontroler, np. interfejs systemu, Android Auto lub Wear OS, może podejmować własne decyzje o tym, które przyciski mają być wyświetlane użytkownikowi. Aby wskazać, które elementy sterujące odtwarzaniem chcesz udostępnić użytkownikowi, możesz określić ustawienia przycisków multimedialnychMediaSession. Te preferencje to uporządkowana lista instancji CommandButton, z których każda określa preferencje dotyczące przycisku w interfejsie.

Definiowanie przycisków poleceń

Instancje CommandButton służą do określania preferencji dotyczących przycisków multimedialnych. Każdy przycisk określa 3 aspekty elementu interfejsu:

  1. Ikona definiująca wygląd. Podczas tworzenia CommandButton.Builder ikona musi być ustawiona na jedną ze wstępnie zdefiniowanych stałych. Pamiętaj, że nie jest to rzeczywista bitmapa ani obraz. Ogólna stała pomaga kontrolerom wybrać odpowiedni zasób, aby zapewnić spójny wygląd i działanie w ich interfejsie. Jeśli żadna ze stałych wartości ikon nie pasuje do Twojego przypadku użycia, możesz zamiast niej użyć setCustomIconResId.
  2. Polecenie określające działanie wywoływane, gdy użytkownik wejdzie w interakcję z przyciskiem. Możesz użyć setPlayerCommand w przypadku Player.Command lub setSessionCommand w przypadku wstępnie zdefiniowanego lub niestandardowego SessionCommand.
  3. Miejsce, które określa, gdzie przycisk powinien być umieszczony w interfejsie kontrolera. To pole jest opcjonalne i jest ustawiane automatycznie na podstawie ikonypolecenia. Na przykład pozwala określić, że przycisk powinien być wyświetlany w obszarze nawigacji po prawej stronie interfejsu, a nie w domyślnym obszarze ukrytych ikon.

Kotlin

val button =
  CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
    .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
    .setSlots(CommandButton.SLOT_FORWARD)
    .build()

Java

CommandButton button =
    new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
        .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
        .setSlots(CommandButton.SLOT_FORWARD)
        .build();

Podczas ustalania preferencji dotyczących przycisków multimedialnych stosowany jest ten algorytm:

  1. Dla każdego CommandButtonustawieniach przycisków multimedialnych umieść przycisk w pierwszym dostępnym i dozwolonym miejscu.
  2. Jeśli którekolwiek z miejsc na środku, po prawej i po lewej nie są wypełnione przyciskiem, dodaj do tego miejsca domyślne przyciski.

Możesz użyć CommandButton.DisplayConstraints, aby wygenerować podgląd sposobu rozwiązywania ustawień przycisków multimedialnych w zależności od ograniczeń wyświetlania interfejsu.

Ustawianie preferencji przycisków multimedialnych

Najprostszym sposobem ustawienia preferencji dotyczących przycisków multimedialnych jest zdefiniowanie listy podczas tworzenia MediaSession. Możesz też zastąpić MediaSession.Callback.onConnect, aby dostosować ustawienia przycisków multimedialnych do 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ń przycisków multimedialnych po interakcji użytkownika

Po obsłużeniu interakcji z odtwarzaczem możesz zaktualizować przyciski wyświetlane w interfejsie kontrolera. Typowym przykładem jest przycisk przełączania, który zmienia ikonę i działanie po wywołaniu działania powiązanego z tym przyciskiem. Aby zaktualizować ustawienia przycisków multimedialnych, możesz użyć 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 działania

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

Deklarowanie i obsługa poleceń niestandardowych

Aplikacje multimedialne mogą definiować polecenia niestandardowe, których można na przykład używać w ustawieniach przycisków multimedialnych. Możesz na przykład wdrożyć przyciski, które pozwolą użytkownikowi zapisać element multimedialny na liście ulubionych. Urządzenie MediaController wysyła niestandardowe polecenia, a urządzenie MediaSession.Callback je odbiera.

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

Kotlin

private class CustomMediaSessionCallback : MediaSession.Callback {

  // Configure commands available to the controller in onConnect()
  override fun onConnectAsync(
    session: MediaSession,
    controller: ControllerInfo,
  ): ListenableFuture<ConnectionResult> {
    val sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS