Przewodnik po migracji na AndroidaX Media3

Aplikacje, które obecnie korzystają z samodzielnej biblioteki com.google.android.exoplayer2androidx.media, powinny przejść na androidx.media3. Użyj skryptu migracji, aby przenieść pliki kompilacji Gradle, pliki źródłowe Java i Kotlin oraz pliki układu XML z ExoPlayera 2.19.1 do AndroidaX Media3 1.1.1.

Omówienie

Zanim przeprowadzisz migrację, zapoznaj się z sekcjami poniżej, aby dowiedzieć się więcej o zaletach nowych interfejsów API, interfejsach API, które należy przenieść, oraz wymaganiach wstępnych, które powinien spełniać projekt aplikacji.

Dlaczego warto przejść na Jetpack Media3

  • Jest to nowe miejsce dla ExoPlayera, a com.google.android.exoplayer2 zostało wycofane.
  • Dostęp do interfejsu Player API w różnych komponentach i procesach za pomocą MediaBrowser/MediaController.
  • Korzystaj z rozszerzonych możliwości interfejsu API MediaSessionMediaController.
  • Reklamuj możliwości odtwarzania za pomocą szczegółowej kontroli dostępu.
  • Uprość aplikację, usuwając MediaSessionConnectorPlayerNotificationManager.
  • Wsteczna zgodność z interfejsami API klienta media-compat (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

Interfejsy Media API, które należy przenieść do AndroidX Media3

  • ExoPlayer i jego rozszerzenia
    Obejmuje to wszystkie moduły starszego projektu ExoPlayer z wyjątkiem modułu mediasession, który został wycofany. Aplikacje lub moduły zależne od pakietów w com.google.android.exoplayer2 można migrować za pomocą skryptu migracji.
  • MediaSessionConnector (w zależności od pakietów androidx.media.* androidx.media:media:1.4.3+)
    Usuń MediaSessionConnector i użyj androidx.media3.session.MediaSession.
  • MediaBrowserServiceCompat (w zależności od pakietów androidx.media.* androidx.media:media:1.4.3+)
    Przenieś podklasy androidx.media.MediaBrowserServiceCompat do androidx.media3.session.MediaLibraryService, a kod korzystający z MediaBrowserCompat.MediaItem do androidx.media3.common.MediaItem.
  • MediaBrowserCompat (w zależności od pakietów androidx.media:media:1.4.3+)
    Przenieś kod klienta za pomocą MediaBrowserCompat lub MediaControllerCompat, aby używać androidx.media3.session.MediaBrowserandroidx.media3.common.MediaItem.android.support.v4.media.*

Wymagania wstępne

  1. Sprawdź, czy projekt jest objęty kontrolą źródła

    Upewnij się, że możesz łatwo cofnąć zmiany wprowadzone przez narzędzia do migracji oparte na skryptach. Jeśli Twój projekt nie jest jeszcze objęty kontrolą źródła, teraz jest dobry moment, aby zacząć. Jeśli z jakiegoś powodu nie chcesz tego robić, przed rozpoczęciem migracji utwórz kopię zapasową projektu.

  2. Aktualizowanie aplikacji

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

    • Zwiększ wersję compileSdkVersion aplikacji do co najmniej 32.

    • Uaktualnij Gradle i wtyczkę Androida do obsługi Gradle w Androidzie Studio do najnowszej wersji, która działa ze zaktualizowanymi zależnościami wymienionymi powyżej. Na przykład:

      • Wersja wtyczki Androida do obsługi Gradle: 7.1.0
      • Wersja Gradle: 7.4
    • Zastąp wszystkie instrukcje importu z wieloznacznymi symbolami, które używają gwiazdki (*), instrukcjami importu z pełnymi kwalifikatorami: usuń instrukcje importu z wieloznacznymi symbolami i użyj Android Studio, aby zaimportować instrukcje z pełnymi kwalifikatorami (F2 – Alt/Enter, F2 – Alt/Enter, ...).

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

Migracja ExoPlayera z obsługą skryptów

Skrypt ułatwia przejście z com.google.android.exoplayer2 na nową strukturę pakietów i modułów w androidx.media3. Skrypt przeprowadza w projekcie kilka kontroli weryfikacyjnych i wyświetla ostrzeżenia, jeśli weryfikacja się nie powiedzie. W przeciwnym razie zastosuje mapowania zmienionych nazw klas i pakietów w zasobach projektu Gradle na Androida napisanego w Javie lub Kotlinie.

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 zaktualizowano aplikację:

    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. Uruchom skrypt za pomocą polecenia --help, aby dowiedzieć się więcej o opcjach.

  4. Uruchom skrypt za pomocą polecenia -l, aby wyświetlić listę plików wybranych do migracji (użyj polecenia -f, aby wymusić wyświetlenie listy bez ostrzeżeń):

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

    • Zatrzymaj się w przypadku błędu weryfikacji bez wprowadzania zmian
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • Wymuszone wykonanie

    Jeśli skrypt wykryje naruszenie wymagań wstępnych, można wymusić migrację 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 czynności ręczne:

  1. Sprawdź, jak skrypt zmienił Twój kod: użyj narzędzia do porównywania i rozwiąż potencjalne problemy (rozważ zgłoszenie błędu, jeśli uważasz, że skrypt ma ogólny problem, który został wprowadzony bez przekazania opcji -f).
  2. Skompiluj projekt: użyj ./gradlew clean build lub w Androidzie Studio wybierz Plik > Synchronizuj projekt z plikami Gradle, a następnie Skompiluj > Wyczyść projektSkompiluj > Przebuduj projekt (śledź kompilację na karcie „Kompilacja – dane wyjściowe kompilacji” w Androidzie Studio).

Zalecane dalsze kroki:

  1. Rozwiąż problem z wyrażeniem zgody na otrzymywanie informacji o błędach związanych z używaniem niestabilnych interfejsów API.
  2. Zastąp wycofane wywołania interfejsu API: użyj sugerowanego interfejsu API zastępującego wycofany interfejs. Umieść wskaźnik myszy nad ostrzeżeniem w Android Studio i zapoznaj się z dokumentacją JavaDoc wycofanego symbolu, aby dowiedzieć się, czego używać zamiast danego wywołania.
  3. Posortuj instrukcje importu: otwórz projekt w Android Studio, a następnie kliknij prawym przyciskiem myszy węzeł folderu pakietu w przeglądarce projektu i wybierz Optymalizuj importy w przypadku pakietów zawierających zmienione pliki źródłowe.

Zastąp MediaSessionConnector tekstem androidx.media3.session.MediaSession

W starszym modelu MediaSessionCompat element MediaSessionConnector odpowiadał za synchronizację stanu odtwarzacza ze stanem sesji i odbieranie poleceń z kontrolerów, które wymagały przekazania do odpowiednich metod odtwarzacza. W przypadku AndroidX Media3 odbywa się to bezpośrednio za pomocą MediaSession bez konieczności używania łącznika.

  1. Usuń wszystkie odwołania do klasy MediaSessionConnector i jej użycie: jeśli do migracji klas i pakietów ExoPlayera używasz automatycznego skryptu, prawdopodobnie pozostawi on w kodzie niekompilujący się stan w odniesieniu do klasyMediaSessionConnector, której nie można rozwiązać. Android Studio wyświetli uszkodzony kod, gdy spróbujesz skompilować lub uruchomić aplikację.

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

    implementation "androidx.media3:media3-session:1.7.1"
    
  3. Zastąp symbol MediaSessionCompat ciągiem znaków androidx.media3.session.MediaSession.

  4. W witrynie z kodem, w której utworzono starszy typ MediaSessionCompat, użyj androidx.media3.session.MediaSession.Builder, aby utworzyć MediaSession. Przekaż odtwarzacz, aby utworzyć kreator sesji.

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player)
        .setSessionCallback(MySessionCallback())
        .build()
    
  5. Wdróż MySessionCallback zgodnie z wymaganiami aplikacji. To pole jest opcjonalne. Jeśli chcesz zezwolić kontrolerom na dodawanie elementów multimedialnych do odtwarzacza, zaimplementuj MediaSession.Callback.onAddMediaItems(). Zawiera różne aktualne i starsze metody interfejsu API, które dodają elementy multimedialne do odtwarzacza 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ę onAddMediaItems znajdziesz PlaybackService przykładowej aplikacji demonstracyjnej.

  6. Zwolnij sesję multimediów w witrynie z kodem, w której została ona zakończona przed migracją:

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

MediaSessionConnector w Media3

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

MediaSessionConnectorAndroidX Media3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setMediaButtonPreferences()
PlaybackPreparer MediaSession.Callback.onAddMediaItems() (prepare() jest wywoływana wewnętrznie)
QueueNavigator ForwardingSimpleBasePlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

Przenoszenie MediaBrowserService do MediaLibraryService

W AndroidzieX Media3 wprowadzono MediaLibraryService, który zastępuje MediaBrowserServiceCompat. Dokumentacja JavaDoc klasy MediaLibraryService i jej klasy nadrzędnej MediaSessionService to dobre wprowadzenie do interfejsu API i asynchronicznego modelu programowania usługi.

MediaLibraryService jest zgodny wstecznie z MediaBrowserService. Aplikacja kliencka, która używa MediaBrowserCompat lub MediaControllerCompat, nadal działa bez zmian w kodzie po połączeniu z MediaLibraryService. Dla klienta jest jasne, czy Twoja aplikacja korzysta z MediaLibraryService, czy ze starszej wersji MediaBrowserServiceCompat.

Diagram komponentów aplikacji z usługą, aktywnością i aplikacjami zewnętrznymi.
Rysunek 1. Omówienie komponentów aplikacji do obsługi multimediów
  1. Aby zapewnić zgodność wsteczną, musisz zarejestrować oba interfejsy usługi w usłudze w AndroidManifest.xml. W ten sposób klient znajdzie Twoją usługę za pomocą wymaganego interfejsu 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 zarządzasz zależnościami, dodaj zależność implementacji do modułu sesji AndroidX Media3 i usuń starszą zależność:

    implementation "androidx.media3:media3-session:1.7.1"
    
  3. Zmień usługę, aby dziedziczyła po MediaLibraryService zamiast po MediaBrowserService.MediaBrowserServiceJak wspomnieliśmy wcześniej, MediaLibraryService jest zgodny ze starszym MediaBrowserService. W związku z tym szerszy interfejs API, który usługa oferuje klientom, pozostaje taki sam. Dlatego aplikacja może zachować większość logiki wymaganej do wdrożenia MediaBrowserService i dostosować ją do nowego 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 należy zastąpić w samej usłudze, to onCreate/onDestroy, gdzie aplikacja przydziela/zwalnia 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ć obiekt MediaLibrarySession utworzony w metodzie onCreate.

    • Zaimplementuj MediaLibraryService.MediaLibrarySessionCallback: utworzenie sesji wymaga MediaLibraryService.MediaLibrarySessionCallback, który implementuje rzeczywiste metody interfejsu API domeny. Zamiast zastępować metody interfejsu API starszej usługi, zastąpisz metody MediaLibrarySession.Callback.

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

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

      Pełną dokumentację interfejsu API MediaLibrarySessionCallback znajdziesz w dokumentacji interfejsu API.

    • Wdróż MediaSession.Callback.onAddMediaItems(): wywołanie zwrotneonAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>) obsługuje różne bieżące i starsze metody interfejsu API, które dodają elementy multimedialne do odtwarzacza w celu odtwarzania w sposób wstecznie zgodny. Obejmuje to metody MediaController.set/addMediaItems() kontrolera Media3, a także metody TransportControls.prepareFrom*/playFrom* starszego interfejsu API. Przykładową implementację wywołania zwrotnego znajdziesz PlaybackService aplikacji demonstracyjnej sesji.

    • AndroidX Media3 używa androidx.media3.common.MediaItem zamiast MediaBrowserCompat.MediaItem i MediaMetadataCompat. Części kodu powiązane z klasami starszego typu należy odpowiednio zmienić lub przypisać do interfejsu Media3 MediaItem.

    • Ogólny asynchroniczny model programowania został zmieniony na Futures w przeciwieństwie do odłączanego podejścia ResultMediaBrowserServiceCompat. Implementacja usługi może zwracać asynchroniczny obiekt ListenableFuture zamiast odłączać wynik lub zwracać natychmiastowy obiekt Future, aby bezpośrednio zwracać wartość.

Usuwanie klasy PlayerNotificationManager

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

Aplikacja może dostosować powiadomienie, ustawiając niestandardowy element MediaNotification.ProvideronCreate(), który zastępuje element DefaultMediaNotificationProvider. MediaLibraryService następnie zajmuje się uruchomieniem usługi na pierwszym planie w razie potrzeby.

Przez zastąpienie MediaLibraryService.updateNotification() aplikacja może w pełni przejąć kontrolę nad publikowaniem powiadomień oraz uruchamianiem i zatrzymywaniem usługi na pierwszym planie w razie potrzeby.

Migracja kodu klienta za pomocą klasy MediaBrowser

W AndroidzieX Media3 MediaBrowser implementuje interfejsy MediaController/Player i może służyć do sterowania odtwarzaniem multimediów oraz przeglądania biblioteki multimediów. Jeśli w starszej wersji trzeba było utworzyć MediaBrowserCompatMediaControllerCompat, w Media3 możesz to zrobić, używając tylko MediaBrowser.

MediaBrowser można utworzyć i poczekać na nawiązanie połączenia 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)
}

Aby dowiedzieć się, jak utworzyć MediaController do sterowania odtwarzaniem w tle, przeczytaj artykuł Sterowanie odtwarzaniem w sesji multimedialnej.

Dalsze kroki i czyszczenie

Niestabilne błędy interfejsu API

Po migracji do Media3 mogą się pojawić błędy lint dotyczące niestabilnych zastosowań interfejsu API. Korzystanie z tych interfejsów API jest bezpieczne, a błędy lint są efektem ubocznym naszych nowych gwarancji zgodności binarnej. Jeśli nie potrzebujesz ścisłej zgodności binarnej, możesz bezpiecznie pominąć te błędy za pomocą adnotacji @OptIn.

Tło

Ani ExoPlayer w wersji 1, ani w wersji 2 nie gwarantował ścisłej zgodności binarnej biblioteki między kolejnymi wersjami. Interfejs API ExoPlayera jest celowo bardzo rozbudowany, aby umożliwić aplikacjom dostosowywanie niemal każdego aspektu odtwarzania. W kolejnych wersjach ExoPlayera czasami wprowadzano zmiany nazw symboli lub inne zmiany powodujące niezgodność (np. nowe wymagane metody w interfejsach). W większości przypadków udało się temu zapobiec, wprowadzając nowy symbol i wycofując stary symbol w kilku wersjach, aby deweloperzy mieli czas na przeniesienie swoich zastosowań, ale nie zawsze było to możliwe.

Te zmiany powodujące niezgodność wsteczną spowodowały 2 problemy dla użytkowników bibliotek ExoPlayer w wersjach 1 i 2:

  1. Uaktualnienie do wersji ExoPlayera może spowodować, że kod przestanie się kompilować.
  2. Aplikacja, która była zależna od ExoPlayera bezpośrednio i za pomocą biblioteki pośredniej, musiała mieć obie zależności w tej samej wersji. W przeciwnym razie niezgodności binarne mogły powodować awarie w czasie działania.

Ulepszenia w Media3

Media3 gwarantuje zgodność binarną w przypadku części interfejsu API. Elementy, które nie gwarantują zgodności binarnej, są oznaczone symbolem @UnstableApi. Aby wyraźnie zaznaczyć to rozróżnienie, użycie niestabilnych symboli interfejsu API generuje błąd lint, chyba że są one oznaczone adnotacją @OptIn.

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

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

Więcej informacji o tym, jak oznaczać w kodzie w językach Java i Kotlin użycie niestabilnych interfejsów API za pomocą adnotacji @OptIn, znajdziesz w sekcji rozwiązywania problemów z tymi błędami narzędzia lint.

Wycofane interfejsy API

Możesz zauważyć, że wywołania wycofanych interfejsów API są przekreślone w Androidzie Studio. Zalecamy zastąpienie takich wywołań odpowiednią alternatywą. Najedź kursorem na symbol, aby wyświetlić dokumentację JavaDoc, która informuje, którego interfejsu API należy użyć zamiast tego.

Zrzut ekranu: jak wyświetlić dokumentację JavaDoc z alternatywą dla wycofanej metody
Rysunek 3. Wskazówka JavaDoc w Android Studio sugeruje alternatywę dla każdego wycofanego symbolu.

Przykładowe fragmenty kodu i aplikacje demonstracyjne

  • Aplikacja demonstracyjna sesji AndroidX Media3 (na urządzenia mobilne i WearOS)
    • Działania niestandardowe
    • Powiadomienie interfejsu systemu, MediaButton/BT
    • Sterowanie odtwarzaniem za pomocą Asystenta Google
  • UAMP: Android Media Player (branch media3) (mobile, AutomotiveOS)
    • Powiadomienie interfejsu systemu, MediaButton/BT, wznowienie odtwarzania
    • Sterowanie odtwarzaniem za pomocą Asystenta Google lub Wear OS
    • AutomotiveOS: niestandardowe polecenie i logowanie