Tworzenie podstawowej aplikacji odtwarzacza multimediów za pomocą Media3 ExoPlayer

Jetpack Media3 definiuje interfejs Player, który określa podstawowe funkcje odtwarzania plików wideo i audio. ExoPlayer to domyślna implementacja tego interfejsu w Media3. Zalecamy używanie ExoPlayer, ponieważ zapewnia on kompleksowy zestaw funkcji, które obejmują większość przypadków użycia odtwarzania, i można go dostosować do obsługi dodatkowych przypadków użycia. ExoPlayer abstrahuje też od fragmentacji urządzeń i systemów operacyjnych, dzięki czemu Twój kod działa spójnie w całym ekosystemie Androida. ExoPlayer obejmuje:

  • obsługę playlist;
  • obsługę różnych formatów progresywnego i adaptacyjnego przesyłania strumieniowego formatów
  • obsługę wstawiania reklam po stronie klienta i po stronie serwera; wstawiania reklam
  • obsługę odtwarzania treści chronionych DRM.

Na tej stronie znajdziesz opis najważniejszych kroków związanych z tworzeniem aplikacji do odtwarzania, a więcej informacji znajdziesz w pełnych przewodnikach dotyczących Media3 ExoPlayer.

Pierwsze kroki

Aby rozpocząć, dodaj zależność od modułów ExoPlayer, UI i Common w Jetpack Media3:

Kotlin

implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")
implementation("androidx.media3:media3-common:1.11.0")

Dynamiczny

implementation "androidx.media3:media3-exoplayer:1.11.0"
implementation "androidx.media3:media3-ui:1.11.0"
implementation "androidx.media3:media3-common:1.11.0"

W zależności od przypadku użycia możesz też potrzebować dodatkowych modułów z Media3, np. exoplayer-dash do odtwarzania strumieni w formacie DASH.

Pamiętaj, aby zastąpić 1.11.0 preferowaną wersją biblioteki. Najnowszą wersję znajdziesz w informacjach o wersji.

Tworzenie odtwarzacza multimediów

W Media3 możesz użyć dołączonej implementacji Player interfejsu, ExoPlayer, lub utworzyć własną implementację niestandardową.

Tworzenie ExoPlayer

Najprostszy sposób utworzenia instancji ExoPlayer jest taki:

Kotlin

val player = ExoPlayer.Builder(context).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

Odtwarzacz multimediów możesz utworzyć w metodzie cyklu życia onCreate() w Activity, Fragment lub Service, w której się znajduje.

Builder zawiera szereg opcji dostosowywania, które mogą Cię zainteresować , np.:

Media3 udostępnia komponent interfejsu PlayerView, który możesz umieścić w pliku układu aplikacji. Ten komponent zawiera PlayerControlView do sterowania odtwarzaniem, SubtitleView do wyświetlania napisów i Surface do renderowania wideo.

Przygotowywanie odtwarzacza

Dodaj elementy multimedialne do playlisty, aby je odtworzyć, za pomocą metod takich jak setMediaItem() i addMediaItem(). Następnie wywołaj prepare(), aby rozpocząć wczytywanie multimediów i uzyskać niezbędne zasoby.

Nie należy wykonywać tych czynności, zanim aplikacja nie znajdzie się na pierwszym planie. Jeśli odtwarzacz znajduje się w Activity lub Fragment, oznacza to, że należy go przygotować w metodzie cyklu życia onStart() na poziomie interfejsu API 24 i wyższym lub w metodzie cyklu życia onResume() na poziomie interfejsu API 23 i niższym. W przypadku odtwarzacza, który znajduje się w Service, możesz go przygotować w onCreate(). Przykład implementacji metod cyklu życia znajdziesz w ćwiczeniach z programowania dotyczących ExoPlayer (zobacz ExoPlayer codelab).

Sterowanie odtwarzaczem

Po przygotowaniu odtwarzacza możesz sterować odtwarzaniem, wywołując metody odtwarzacza, takie jak:

Komponenty interfejsu, takie jak PlayerView czy PlayerControlView, zostaną odpowiednio zaktualizowane po powiązaniu z odtwarzaczem.

Zwalnianie odtwarzacza

Odtwarzanie może wymagać zasobów, które są ograniczone, np. dekoderów wideo . Dlatego ważne jest, aby wywołać release() w odtwarzaczu, aby zwolnić zasoby, gdy odtwarzacz nie jest już potrzebny.

Jeśli odtwarzacz znajduje się w Activity lub Fragment, zwolnij go w metodzie cyklu życia onStop() na poziomie interfejsu API 24 i wyższym lub w metodzie onPause() na poziomie interfejsu API 23 i niższym. W przypadku odtwarzacza, który znajduje się w Service, możesz go zwolnić w onDestroy(). Przykład implementacji metod cyklu życia znajdziesz w ćwiczeniach z programowania dotyczących ExoPlayer.

Zarządzanie odtwarzaniem za pomocą sesji multimedialnej

W Androidzie sesje multimedialne zapewniają standardowy sposób interakcji z odtwarzaczem multimediów w różnych procesach. Połączenie sesji multimedialnej z odtwarzaczem umożliwia przekazywanie informacji o odtwarzaniu multimediów źródłom zewnętrznym i otrzymywanie poleceń odtwarzania ze źródeł zewnętrznych, np. w celu integracji z systemowymi elementami sterującymi multimediami na urządzeniach mobilnych i urządzeniach z dużym ekranem.

Aby korzystać z sesji multimedialnych, dodaj zależność od modułu Media3 Session:

Kotlin

implementation("androidx.media3:media3-session:1.11.0")

Dynamiczny

implementation "androidx.media3:media3-session:1.11.0"

Tworzenie sesji multimedialnej

MediaSession możesz utworzyć po zainicjowaniu odtwarzacza 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();

Media3 automatycznie synchronizuje stan Player ze stanem MediaSession. Działa to z każdą implementacją Player, w tym ExoPlayer, CastPlayer i implementacją niestandardową.

Przyznawanie kontroli innym klientom

Aplikacje klienckie mogą implementować kontroler multimediów, aby sterować odtwarzaniem sesji multimedialnej. Aby otrzymywać te żądania, ustaw obiekt wywołania zwrotnego podczas tworzenia MediaSession.

Gdy kontroler ma się połączyć z sesją multimedialną, wywoływana jest onConnect() metoda. Możesz użyć podanego ControllerInfo, aby zdecydować, czy to zaakceptować czy odrzucić żądanie. Przykład znajdziesz w aplikacji w wersji demonstracyjnej sesji Media3 Session.

Po nawiązaniu połączenia 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. żądań niestandardowych poleceń odtwarzania i modyfikowania playlisty. Te wywołania zwrotne również zawierają obiekt ControllerInfo, dzięki czemu możesz określać kontrolę dostępu na podstawie poszczególnych żądań.

Odtwarzanie multimediów w tle

Aby kontynuować odtwarzanie multimediów, gdy aplikacja nie jest na pierwszym planie, np. odtwarzać muzykę, audiobooki lub podcasty, nawet gdy użytkownik nie ma otwartej aplikacji, Player i MediaSession powinny być zamknięte w usłudze na pierwszym planie. Media3 udostępnia do tego interfejs MediaSessionService.

Implementowanie MediaSessionService

Utwórz klasę, która rozszerza MediaSessionService, i utwórz instancję MediaSession w metodzie cyklu życia onCreate().

Kotlin

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    // Create your Player and MediaSession in the onCreate lifecycle event
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    // Remember to release the player and media session in onDestroy
    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }
}

Java

public class PlaybackService extends MediaSessionService {
    private MediaSession mediaSession = null;

    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        mediaSession = new MediaSession.Builder(this, player).build();
    }

    @Override
    public void onDestroy() {
        mediaSession.getPlayer().release();
        mediaSession.release();
        mediaSession = null;
        super.onDestroy();
    }
}

W pliku manifestu zadeklaruj klasę Service z filtrem intencji MediaSessionService i poproś o uprawnienie FOREGROUND_SERVICE, aby uruchomić usługę na pierwszym planie:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Na koniec w utworzonej klasie zastąp metodę onGetSession(), aby kontrolować dostęp klientów do sesji multimedialnej. Aby zaakceptować prośbę o połączenie, zwróć MediaSession, a aby ją odrzucić, zwróć null.

Kotlin

// This example always accepts the connection request
override fun onGetSession(
    controllerInfo: MediaSession.ControllerInfo
): MediaSession? = mediaSession

Java

@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
  // This example always accepts the connection request
  return mediaSession;
}

Łączenie z interfejsem

Teraz, gdy sesja multimedialna znajduje się w Service oddzielnej od Activity lub Fragment, w której znajduje się interfejs odtwarzacza, możesz użyć MediaController, aby je połączyć. W metodzie onStart() w Activity lub Fragment z interfejsem utwórz SessionToken dla MediaSession, a następnie użyj SessionToken, aby utworzyć MediaController. Tworzenie MediaController odbywa się asynchronicznie.

Kotlin

override fun onStart() {
  val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
  val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
  controllerFuture.addListener(
    {
        // Call controllerFuture.get() to retrieve the MediaController.
        // MediaController implements the Player interface, so it can be
        // attached to the PlayerView UI component.
        playerView.setPlayer(controllerFuture.get())
      },
    MoreExecutors.directExecutor()
  )
}

Java

@Override
public void onStart() {
  SessionToken sessionToken =
    new SessionToken(this, new ComponentName(this, PlaybackService.class));
  ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(this, sessionToken).buildAsync();
  controllerFuture.addListener(() -> {
    // Call controllerFuture.get() to retrieve the MediaController.
    // MediaController implements the Player interface, so it can be
    // attached to the PlayerView UI component.
    playerView.setPlayer(controllerFuture.get());
  }, MoreExecutors.directExecutor())
}

MediaController implementuje interfejs Player, więc możesz używać tych samych metod, takich jak play() i pause(), do sterowania odtwarzaniem. Podobnie jak w przypadku innych komponentów, pamiętaj, aby zwolnić MediaController, gdy nie jest już potrzebny, w metodzie cyklu życia onStop() w Activity, wywołując MediaController.releaseFuture().

Publikowanie powiadomienia

Usługi na pierwszym planie muszą publikować powiadomienie, gdy są aktywne. A MediaSessionService automatycznie utworzy dla Ciebie powiadomienie MediaStyle w postaci MediaNotification. Aby udostępnić niestandardowe powiadomienie, utwórz MediaNotification.Provider za pomocą DefaultMediaNotificationProvider.Builder lub tworząc niestandardową implementację interfejsu dostawcy. Dodaj dostawcę do swojego MediaSession za pomocą setMediaNotificationProvider.

Reklamowanie biblioteki treści

MediaLibraryService rozszerza MediaSessionService, umożliwiając aplikacjom klienckim przeglądanie treści multimedialnych udostępnianych przez Twoją aplikację. Aplikacje klienckie implementują MediaBrowser, aby wchodzić w interakcje z MediaLibraryService.

Implementowanie MediaLibraryService jest podobne do implementowania MediaSessionService, z tym wyjątkiem, że w onGetSession() należy zwrócić MediaLibrarySession zamiast MediaSession. W porównaniu z MediaSession.Callback, MediaLibrarySession.Callback zawiera dodatkowe metody, które umożliwiają klientowi przeglądarki poruszanie się po treściach oferowanych przez usługę biblioteki.

Podobnie jak w przypadku MediaSessionService, zadeklaruj MediaLibraryService w pliku manifestu i poproś o uprawnienie FOREGROUND_SERVICE, aby uruchomić usługę na pierwszym planie:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Powyższy przykład zawiera filtr intencji zarówno dla MediaLibraryService, jak i dla zgodności wstecznej starszej wersji MediaBrowserService. Dodatkowy filtr intencji umożliwia aplikacjom klienckim korzystającym z interfejsu API MediaBrowserCompat rozpoznawanie Twojej Service.

MediaLibrarySession umożliwia udostępnianie biblioteki treści w strukturze drzewa z jednym głównym MediaItem. Każdy MediaItem w drzewie może mieć dowolną liczbę węzłów podrzędnych MediaItem. Możesz udostępniać inny katalog główny lub inne drzewo w zależności od żądania aplikacji klienckiej. Na przykład drzewo, które zwracasz klientowi szukającemu listy polecanych elementów multimedialnych, może zawierać tylko główny MediaItem i jeden poziom węzłów podrzędnych MediaItem, natomiast drzewo, które zwracasz innej aplikacji klienckiej, może reprezentować pełniejszą bibliotekę treści.

Tworzenie MediaLibrarySession

MediaLibrarySession rozszerza interfejs API MediaSession, dodając interfejsy API do przeglądania treści. W porównaniu z wywołaniem zwrotnym MediaSession, wywołanie zwrotne MediaLibrarySession dodaje metody takie jak:

  • onGetLibraryRoot() w przypadku, gdy klient prosi o główny MediaItem drzewa treści;
  • onGetChildren() w przypadku, gdy klient prosi o elementy podrzędne MediaItem w drzewie treści;
  • onGetSearchResult() w przypadku, gdy klient prosi o wyniki wyszukiwania w drzewie treści dla danego zapytania.

Odpowiednie metody wywołania zwrotnego będą zawierać obiekt LibraryParams z dodatkowymi sygnałami dotyczącymi typu drzewa treści, które interesuje aplikację kliencką.