Przewodnik po migracji na AndroidaX Media3

Aplikacje, które obecnie używają samodzielnej biblioteki com.google.android.exoplayer2 i biblioteki androidx.media, należy przenieść do androidx.media3. Aby przenieść pliki kompilacji Gradle, pliki źródłowe Java i Kotlin oraz pliki układu XML z ExoPlayer 2.19.1 na platformę AndroidX Media3 1.1.1, użyj skryptu migracji.

Omówienie

Przed migracją zapoznaj się z poniższymi sekcjami, aby dowiedzieć się więcej o zaletach nowych interfejsów API, ich migracji oraz wymaganiach wstępnych, jakie musi spełnić projekt aplikacji.

Dlaczego warto przejść na Jetpack Media3

  • To nowa strona ExoPlayer, natomiast com.google.android.exoplayer2 została wycofana.
  • Uzyskaj dostęp do Player API w różnych komponentach/procesach za pomocą MediaBrowser/MediaController.
  • korzystać z rozszerzonych możliwości interfejsów API MediaSession i MediaController.
  • Reklamuj możliwości odtwarzania za pomocą szczegółowej kontroli dostępu.
  • Uprość swoją aplikację, usuwając właściwości MediaSessionConnector i PlayerNotificationManager.
  • Zgodność wsteczna z interfejsami API klienta kompatybilnymi z mediami (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

Interfejsy Media API zostaną przeniesione do AndroidX Media3

  • ExoPlayer i jego rozszerzenia
    Obejmuje to wszystkie moduły starszego projektu ExoPlayer z wyjątkiem wycofanego modułu mediasession. Aplikacje lub moduły w zależności od pakietów w com.google.android.exoplayer2 można przenieść za pomocą skryptu migracji.
  • MediaSessionConnector (w zależności od pakietów androidx.media.* pakietu androidx.media:media:1.4.3+)
    Usuń MediaSessionConnector i użyj androidx.media3.session.MediaSession.
  • Media BrowserServiceCompat (w zależności od pakietów androidx.media.* androidx.media:media:1.4.3+)
    Przenieś podklasy klasy androidx.media.MediaBrowserServiceCompat do androidx.media3.session.MediaLibraryService i kod za pomocą kodu MediaBrowserCompat.MediaItem do androidx.media3.common.MediaItem.
  • Media BrowserCompat (w zależności od pakietów android.support.v4.media.* systemu androidx.media:media:1.4.3+)
    Przeprowadź migrację kodu klienta za pomocą MediaBrowserCompat lubMediaControllerCompat, aby używać androidx.media3.session.MediaBrowser z androidx.media3.common.MediaItem.

Wymagania wstępne

  1. Sprawdzanie, czy projekt znajduje się pod kontrolą źródła

    Zadbaj o to, aby zmiany wprowadzone przez narzędzia do migracji oparte na skryptach można było łatwo cofnąć. Jeśli jeszcze nie masz projektu pod kontrolą źródła, to dobry moment, by zacząć z niego korzystać. Jeśli z jakiegoś powodu nie chcesz tego robić, przed rozpoczęciem migracji utwórz kopię zapasową projektu.

  2. Aktualizowanie aplikacji

    • Zalecamy zaktualizowanie projektu tak, by korzystał z najnowszej wersji biblioteki ExoPlayer, i usunięcie wszystkich wywołań wycofanych metod. Jeśli zamierzasz użyć skryptu podczas migracji, musisz dopasować wersję, do której aktualizujesz usługę, do wersji obsługiwanej przez skrypt.

    • Zwiększ parametr buildSdkVersion aplikacji do co najmniej 32.

    • Uaktualnij Gradle i wtyczkę Android Studio Gradle do najnowszej wersji, która współpracuje ze zaktualizowanymi zależnościami podanymi powyżej. Na przykład:

      • Wersja wtyczki Androida do obsługi Gradle: 7.1.0
      • Wersja Gradle: 7.4
    • Zastąp wszystkie instrukcje importowania symboli zastępczych, w których znajduje się asterix (*), i użyj w pełni kwalifikowanych instrukcji importu: usuń instrukcje importu z symbolami wieloznacznymi i użyj Android Studio, aby zaimportować w pełni kwalifikowane instrukcje (F2 – Alt/Enter, F2 – Alt/Enter itp.).

    • Przenieś z com.google.android.exoplayer2.PlayerView do com.google.android.exoplayer2.StyledPlayerView. Jest to konieczne, ponieważ w AndroidX Media3 nie ma odpowiednika com.google.android.exoplayer2.PlayerView.

Migracja ExoPlayer z obsługą skryptów

Skrypt ułatwia przejście z com.google.android.exoplayer2 do nowego pakietu i struktury modułów w androidx.media3. Skrypt stosuje w projekcie kilka testów weryfikacyjnych i wyświetla ostrzeżenia, jeśli weryfikacja się nie powiedzie. W przeciwnym razie stosowane są mapowania zmienionych klas i pakietów w zasobach projektu Gradle na Androida zapisanego w języku Java lub Kotlin.

usage: ./media3-migration.sh [-p|-c|-d|-v]|[-m|-l [-x <path>] [-f] PROJECT_ROOT]
 PROJECT_ROOT: path to your project root (location of 'gradlew')
 -p: list package mappings and then exit
 -c: list class mappings (precedence over package mappings) and then exit
 -d: list dependency mappings and then exit
 -l: list files that will be considered for rewrite and then exit
 -x: exclude the path from the list of file to be changed: 'app/src/test'
 -m: migrate packages, classes and dependencies to AndroidX Media3
 -f: force the action even when validation fails
 -v: print the exoplayer2/media3 version strings of this script
 -h, --help: show this help text

Korzystanie ze skryptu migracji

  1. Pobierz skrypt migracji z tagu projektu ExoPlayer na GitHubie odpowiadającego wersji, do której została zaktualizowana aplikacja:

    curl -o media3-migration.sh \
      "https://raw.githubusercontent.com/google/ExoPlayer/r2.19.1/media3-migration.sh"
    
  2. Ustaw skrypt jako wykonywalny:

    chmod 744 media3-migration.sh
    
  3. Aby poznać opcje, uruchom skrypt z użyciem parametru --help.

  4. Uruchom skrypt z użyciem parametru -l, aby wyświetlić listę plików wybranych do migracji (użyj polecenia -f, aby wymusić wyświetlanie strony bez ostrzeżeń):

    ./media3-migration.sh -l -f /path/to/gradle/project/root
    
  5. Uruchom skrypt z użyciem parametru -m, aby zmapować pakiety, klasy i moduły na Media3. Uruchomienie skryptu z opcją -m spowoduje zastosowanie zmian do wybranych plików.

    • Zatrzymaj po wystąpieniu błędu weryfikacji bez wprowadzania zmian
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • Wymuszone wykonanie

    Jeśli skrypt wykryje naruszenie warunków wstępnych, migrację można wymusić za pomocą flagi -f:

    ./media3-migration.sh -m -f /path/to/gradle/project/root
    
 # list files selected for migration when excluding paths
 ./media3-migration.sh -l -x "app/src/test/" -x "service/" /path/to/project/root
 # migrate the selected files
 ./media3-migration.sh -m -x "app/src/test/" -x "service/" /path/to/project/root

Po uruchomieniu skryptu z opcją -m wykonaj te ręczne czynności:

  1. Sprawdź, jak skrypt zmienił Twój kod: użyj narzędzia różnic i rozwiąż potencjalne problemy (rozważ zgłoszenie błędu, jeśli uważasz, że w skrypcie występuje ogólny problem, który został wprowadzony bez podania opcji -f).
  2. Utwórz projekt: użyj ./gradlew clean build lub w Android Studio wybierz Plik > Synchronizuj projekt z plikami Gradle, a następnie Kompiluj > Oczyść projekt i Kompiluj > Przebuduj projekt (możesz sprawdzać kompilację na karcie „Kompilacja – dane wyjściowe kompilacji” w Android Studio.

Zalecane dodatkowe czynności:

  1. rozwiązać problem z akceptacją błędów dotyczących używania niestabilnych interfejsów API.
  2. Zastąp wycofane wywołania interfejsu API: użyj sugerowanego zastępczego interfejsu API. Aby dowiedzieć się, czego użyć zamiast danego wywołania, najedź kursorem na ostrzeżenie w Android Studio i zapoznaj się z dokumentem JavaDoc dotyczącym wycofanego symbolu.
  3. Posortuj instrukcje importu: otwórz projekt w Android Studio, a następnie kliknij prawym przyciskiem myszy węzeł folderu pakietów w widoku projektu i wybierz Optymalizuj importowanie przy pakietach, które zawierają zmienione pliki źródłowe.

Zamień MediaSessionConnector na androidx.media3.session.MediaSession

W starszym świecie MediaSessionCompat element MediaSessionConnector odpowiadał za synchronizację stanu odtwarzacza ze stanem sesji oraz odbieranie poleceń od kontrolerów, które wymagały przekazania dostępu do odpowiednich metod odtwarzacza. W AndroidX Media3 MediaSession robi to bezpośrednio bez konieczności stosowania oprogramowania sprzęgającego.

  1. Usuń wszystkie odniesienia i użycie MediaSessionConnector: jeśli do przeniesienia klas i pakietów ExoPlayer użyto automatycznego skryptu, prawdopodobnie skrypt pozostawił kod w stanie nieskompilowania w związku z elementem MediaSessionConnector, którego nie można rozwiązać. Android Studio wyświetli uszkodzony kod przy próbie skompilowania lub uruchomienia aplikacji.

  2. W pliku build.gradle, w którym przechowujesz zależności, dodaj zależność implementacji do modułu sesji AndroidX Media3 i usuń starszą zależność:

    implementation "androidx.media3:media3-session:1.3.1"
    
  3. Zastąp MediaSessionCompat elementem androidx.media3.session.MediaSession.

  4. W witrynie kodu, w której została utworzona starsza wersja MediaSessionCompat, użyj androidx.media3.session.MediaSession.Builder, aby utworzyć MediaSession. Przekaż odtwarzacz, by utworzyć kreator sesji.

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player)
        .setSessionCallback(MySessionCallback())
        .build()
    
  5. Zaimplementuj MySessionCallback zgodnie z wymaganiami aplikacji. Jest to opcjonalne. Jeśli chcesz zezwolić kontrolerom na dodawanie elementów multimedialnych do odtwarzacza, zaimplementuj MediaSession.Callback.onAddMediaItems(). Obsługuje różne obecne i starsze metody interfejsu API, które dodają elementy multimedialne do odtwarzacza w celu ich odtwarzania w sposób zgodny wstecznie. Obejmuje to metody MediaController.set/addMediaItems() kontrolera Media3, a także metody TransportControls.prepareFrom*/playFrom* starszego interfejsu API. Przykładową implementację interfejsu onAddMediaItems znajdziesz w PlaybackService aplikacji demonstracyjnej sesji.

  6. Zwolnij sesję multimediów w witrynie kodu, w której sesja została zniszczona przed migracją:

    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }
    

Funkcje MediaSessionConnector w Media3

W tabeli poniżej znajdziesz interfejsy API Media3, które obsługują funkcje wcześniej zaimplementowane w MediaSessionConnector.

Łącznik sesji MediaSessionAndroidX Media3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setCustomLayout()
PlaybackPreparer MediaSession.Callback.onAddMediaItems() (prepare() jest wywoływany wewnętrznie)
QueueNavigator ForwardingPlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

Przenieś MediaBrowserService do MediaLibraryService

AndroidX Media3 wprowadza interfejs MediaLibraryService, który zastępuje MediaBrowserServiceCompat. Dokument JavaDoc klasy MediaLibraryService i jego nadrzędna klasa MediaSessionService stanowią dobre wprowadzenie do interfejsu API i asynchronicznego modelu programowania usługi.

Element MediaLibraryService jest zgodny wstecz z MediaBrowserService. Aplikacja kliencka, która używa MediaBrowserCompat lub MediaControllerCompat, może w dalszym ciągu działać bez zmian w kodzie po połączeniu się z MediaLibraryService. Dla klienta jasne jest to, czy aplikacja używa MediaLibraryService czy starszej wersji MediaBrowserServiceCompat.

Diagram komponentu aplikacji z usługą, aktywnością i aplikacjami zewnętrznymi.
Rysunek 1. Omówienie komponentu aplikacji do multimediów
  1. Aby zgodność wsteczna działała, musisz zarejestrować oba interfejsy usług w usłudze w AndroidManifest.xml. W ten sposób klient znajdzie Twoją usługę przez wymagany interfejs usługi:

    <service android:name=".MusicService" android:exported="true">
        <intent-filter>
            <action android:name="androidx.media3.session.MediaLibraryService"/>
            <action android:name="android.media.browse.MediaBrowserService" />
        </intent-filter>
    </service>
    
  2. W pliku build.gradle, w którym przechowujesz zależności, dodaj zależność implementacji do modułu sesji AndroidX Media3 i usuń starszą zależność:

    implementation "androidx.media3:media3-session:1.3.1"
    
  3. Zmień swoją usługę na dziedziczenie z MediaLibraryService zamiast MediaBrowserService Jak wspomnieliśmy wcześniej, usługa MediaLibraryService jest zgodna ze starszą wersją MediaBrowserService. W związku z tym szerszy interfejs API dostępny w usłudze jest nadal taki sam. Możliwe więc, że aplikacja zachowa większość logiki niezbędną do zaimplementowania elementu MediaBrowserService i dostosowania go do nowej wersji MediaLibraryService.

    Główne różnice w porównaniu ze starszą wersją MediaBrowserServiceCompat są następujące:

    • Zaimplementuj metody cyklu życia usługi: metody, które trzeba zastąpić w samej usłudze, to onCreate/onDestroy, gdzie aplikacja alokuje/uwalnia sesję biblioteki, odtwarzacz i inne zasoby. Oprócz standardowych metod cyklu życia usługi aplikacja musi zastąpić metodę onGetSession(MediaSession.ControllerInfo), aby zwrócić metodę MediaLibrarySession utworzoną w onCreate.

    • Zaimplementuj MediaLibraryService.MediaLibrarySessionCallback: do utworzenia sesji wymagane jest narzędzie MediaLibraryService.MediaLibrarySessionCallback, które implementuje rzeczywiste metody interfejsu API domeny. Dlatego zamiast metody interfejsu API starszej usługi zastąpisz metody z MediaLibrarySession.Callback.

      Wywołanie zwrotne jest następnie używane do utworzenia MediaLibrarySession:

      mediaLibrarySession =
            MediaLibrarySession.Builder(this, player, MySessionCallback())
               .build()
      

      Pełny interfejs API MediaLibrarySessionCallback znajdziesz w jego dokumentacji.

    • Wdróż MediaSession.Callback.onAddMediaItems(): wywołanie zwrotne onAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>) obsługuje różne obecne i starsze metody interfejsu API, które dodają elementy multimedialne do odtwarzacza, aby umożliwić odtwarzanie w sposób zgodny wstecznie. Obejmuje to metody MediaController.set/addMediaItems() kontrolera Media3 oraz metody TransportControls.prepareFrom*/playFrom* starszej wersji interfejsu API. Przykładową implementację wywołania zwrotnego znajdziesz w PlaybackService aplikacji demonstracyjnej sesji.

    • AndroidX Media3 korzysta z androidx.media3.common.MediaItem, a nie z Media BrowserCompat.MediaItem i MediaMetadataCompat. Fragmenty kodu powiązane ze starszymi klasami muszą zostać odpowiednio zmienione lub zmapować je na obiekt MediaItem Media3.

    • Ogólny model programowania asynchronicznego został zmieniony na Futures, a nie w przeciwieństwie do metody Result stosowanej w modelu MediaBrowserServiceCompat. Implementacja usługi może zwrócić asynchroniczny obiekt ListenableFuture zamiast odłączać wynik lub zwrócić natychmiastową przyszłość, aby bezpośrednio zwrócić wartość.

Usunięcie PlayerPowiadomienieManagera

MediaLibraryService automatycznie obsługuje powiadomienia o multimediach, a element PlayerNotificationManager można usunąć, gdy używasz elementu MediaLibraryService lub MediaSessionService.

Aplikacja może dostosować powiadomienie, ustawiając w onCreate() wartość niestandardową MediaNotification.Provider, która zastępuje DefaultMediaNotificationProvider. W razie potrzeby MediaLibraryService uruchamia usługę na pierwszym planie.

Po zastąpieniu ustawienia MediaLibraryService.updateNotification() aplikacja uzyska pełne prawo własności do publikowania powiadomień i uruchamiania/zatrzymywania usługi na pierwszym planie zgodnie z potrzebami.

Migracja kodu klienta przy użyciu przeglądarki Media Browser

W AndroidX Media3 interfejs MediaBrowser zawiera interfejsy MediaController/Player, za pomocą których można sterować odtwarzaniem multimediów poza przeglądaniem biblioteki multimediów. Jeśli w starszym świecie trzeba było utworzyć MediaBrowserCompat i MediaControllerCompat, możesz to zrobić, używając tylko elementu MediaBrowser w Media3.

Można skompilować MediaBrowser i poczekać na połączenie z usługą:

scope.launch {
    val sessionToken =
        SessionToken(context, ComponentName(context, MusicService::class.java)
    browser =
        MediaBrowser.Builder(context, sessionToken))
            .setListener(BrowserListener())
            .buildAsync()
            .await()
    // Get the library root to start browsing the library.
    root = browser.getLibraryRoot(/* params= */ null).await();
    // Add a MediaController.Listener to listen to player state events.
    browser.addListener(playerListener)
    playerView.setPlayer(browser)
}

Przeczytaj artykuł o sterowaniu odtwarzaniem w trakcie sesji multimediów, aby dowiedzieć się, jak utworzyć element MediaController do sterowania odtwarzaniem w tle.

Dalsze kroki i czyszczenie

Błędy niestabilnego interfejsu API

Po migracji do Media3 mogą pojawić się błędy lint dotyczące niestabilnych zastosowań interfejsu API. Te interfejsy API są bezpieczne w użyciu, a błędy lintowania są efektem naszych nowych gwarancji zgodności plików binarnych. Jeśli nie wymagasz ścisłej zgodności plików binarnych, te błędy można bezpiecznie pominąć za pomocą adnotacji @OptIn.

Tło

Ani ExoPlayer w wersji 1, ani 2 nie gwarantuje rygorystycznej zgodności plików binarnych biblioteki między kolejnymi wersjami. Interfejs API ExoPlayer jest z założenia bardzo duży, umożliwiając aplikacjom dostosowywanie niemal każdego aspektu odtwarzania. Kolejne wersje ExoPlayer czasami wprowadzały zmiany nazw symboli lub inne zmiany powodujące niezgodność (np. nowe wymagane metody w interfejsach). W większości przypadków te awarie można było ograniczyć przez wprowadzenie nowego symbolu i wycofanie starego symbolu w kilku wersjach, aby dać deweloperom czas na migrację wykorzystania, ale nie zawsze było to możliwe.

Ta zmiana sprawiła, że użytkownicy bibliotek ExoPlayer w wersjach 1 i 2 mieli 2 problemy:

  1. Przejście z wersji ExoPlayer na wersję ExoPlayer może spowodować zatrzymanie kompilacji kodu.
  2. Aplikacja, która bazowała na ExoPlayer zarówno bezpośrednio, jak i za pomocą biblioteki pośredniej, musiała zadbać o tę samą wersję obu zależności. W przeciwnym razie niezgodności binarne mogłyby powodować awarie w czasie działania.

Ulepszenia w Media3

Media3 gwarantuje zgodność plików binarnych dla podzbioru powierzchni interfejsu API. Elementy, które nie gwarantują zgodności plików binarnych, są oznaczone symbolem @UnstableApi. Aby rozróżnić to rozróżnienie, użycie niestabilnych symboli interfejsu API powoduje błąd lintowania, chyba że ma on adnotację @OptIn.

Po migracji z ExoPlayer w wersji 2 do Media3 możesz napotkać wiele błędów lintowania interfejsu API niestabilnych. Może to sprawiać wrażenie, że Media3 jest „mniej stabilne” niż ExoPlayer v2. To nieprawda. „Niestabilne” części interfejsu Media3 API mają taki sam poziom stabilności jak cała powierzchnia interfejsu ExoPlayer v2 API, a gwarancje dotyczące stabilnej powierzchni interfejsu Media3 API nie są dostępne w ExoPlayer v2. Różnica polega na tym, że błąd lintowania informuje teraz o różnych poziomach stabilności.

Obsługa niestabilnych błędów lintowania interfejsu API

Zapoznaj się z sekcją rozwiązywania problemów z tymi błędami lintowania, aby dowiedzieć się, jak w @OptIn dodawać adnotacje do niestabilnych interfejsów API w Javie i Kotlinie.

Wycofane interfejsy API

Możesz zauważyć, że wywołania wycofanych interfejsów API w Android Studio są przekreślone. Zalecamy zastąpienie takich połączeń odpowiednią alternatywą. Najedź kursorem na ten symbol, aby zobaczyć dokument JavaDoc informujący o tym, którego interfejsu API użyć.

Zrzut ekranu: jak wyświetlić dokument JavaDoc z alternatywną, wycofaną metodą
Rysunek 3. Etykietka JavaDoc w Android Studio sugeruje alternatywę dla dowolnego wycofanego symbolu.

Przykładowe fragmenty kodu i aplikacje demonstracyjne

  • Aplikacja demonstracyjna sesji na AndroidaX Media3 (na urządzenia mobilne i Wear OS)
    • Działania niestandardowe
    • Powiadomienie w interfejsie systemowym, MediaButton/BT
    • Sterowanie odtwarzaniem w Asystencie Google
  • UAMP: Android Media Player (branch media3) (urządzenia mobilne, system AutomotiveOS)
    • Powiadomienie w interfejsie systemu, MediaButton/BT, wznowienie odtwarzania
    • Sterowanie odtwarzaniem w Asystencie Google/Wear OS
    • AutomotiveOS: niestandardowe polecenie i logowanie