Przewodnik po migracji na AndroidaX Media3

Aplikacje, które obecnie korzystają z samodzielnej biblioteki com.google.android.exoplayer2 i androidx.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 ExoPlayer 2.19.1 do AndroidX Media3 1.1.1.

Przegląd

Zanim rozpoczniesz migrację, zapoznaj się z poniższymi sekcjami, aby dowiedzieć się więcej o korzyściach płynących z nowych interfejsów API, interfejsach API do migracji i wymaganiach, które powinien spełniać projekt aplikacji.

Dlaczego warto przejść na Jetpack Media3

  • To nowe miejsce dla ExoPlayera, a com.google.android.exoplayer2 został wycofany.
  • Uzyskaj dostęp do interfejsu Player API w różnych komponentach i procesach za pomocą MediaBrowser/MediaController.
  • Użyj rozszerzonych możliwości interfejsów API MediaSession i MediaController.
  • Rozgłaszaj możliwości odtwarzania za pomocą szczegółowej kontroli dostępu.
  • Uprość aplikację, usuwając MediaSessionConnector i PlayerNotificationManager.
  • Zgodność wsteczna z interfejsami API klienta media-compat (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

Interfejsy Media API do przeniesienia 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 przenieść za pomocą skryptu migracji.
  • MediaSessionConnector (w zależności od pakietów androidx.media.* w 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.* w androidx.media:media:1.4.3+)
    Przenieś podklasy androidx.media.MediaBrowserServiceCompat do androidx.media3.session.MediaLibraryService i kod używający MediaBrowserCompat.MediaItem do androidx.media3.common.MediaItem.
  • MediaBrowserCompat (w zależności od pakietów android.support.v4.media.* w androidx.media:media:1.4.3+)
    Przenieś kod klienta za pomocą MediaBrowserCompat lub MediaControllerCompat, aby używać androidx.media3.session.MediaBrowser z androidx.media3.common.MediaItem.

Wymagania wstępne

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

    Upewnij się, że możesz cofnąć zmiany wprowadzone przez narzędzia do migracji oparte na skryptach. Jeśli nie masz jeszcze projektu w systemie kontroli kodu źródłowego, 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 używał najnowszej wersji biblioteki ExoPlayer, i usunięcie wszystkich wywołań wycofanych metod. Jeśli chcesz użyć skryptu do migracji, musisz dopasować wersję, do której przeprowadzasz aktualizację, do wersji obsługiwanej przez skrypt.

    • Zwiększ wartość compileSdkVersion aplikacji do co najmniej 32.

    • Uaktualnij Gradle i wtyczkę Gradle w Android Studio do najnowszej wersji, która działa ze zaktualizowanymi zależnościami wymienionymi wcześniej. Na przykład:

      • Wersja wtyczki Androida do obsługi Gradle: 7.1.0
      • Wersja Gradle: 7.4
    • Zastąp wszystkie instrukcje importu z symbolem wieloznacznym, które używają gwiazdki (*), i użyj w pełni kwalifikowanych instrukcji importu: usuń instrukcje importu z symbolem wieloznacznym i użyj Android Studio, aby zaimportować w pełni kwalifikowane instrukcje (F2 – Alt/Enter, F2 – Alt/Enter…).

    • Migracja 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 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 weryfikację projektu i wyświetla ostrzeżenia, jeśli się ona nie powiedzie. W przeciwnym razie stosuje mapowania zmienionych nazw klas i pakietów w zasobach projektu Android Gradle napisanego 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. Dodaj do skryptu możliwość wykonywania:

    chmod 744 media3-migration.sh
    
  3. Uruchom skrypt z parametrem --help, aby dowiedzieć się więcej o opcjach.

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

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

    • Zatrzymaj przy błędzie walidacji bez wprowadzania zmian
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • Wymuszone wykonanie

    Jeśli skrypt wykryje naruszenie wymagań 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 czynności ręcznie:

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

Zalecane dalsze kroki:

  1. Rozwiąż 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. Najedź wskaźnikiem na ostrzeżenie w Android Studio i sprawdź w JavaDoc wycofany symbol, aby dowiedzieć się, czego użyć 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 Optimize imports (Zoptymalizuj importy) w pakietach zawierających zmienione pliki źródłowe.

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

W starszej wersji MediaSessionCompat MediaSessionConnector odpowiada za synchronizowanie stanu odtwarzacza ze stanem sesji i odbieranie poleceń z kontrolerów, które trzeba przekazać do odpowiednich metod odtwarzacza. W AndroidX Media3 robi to bezpośrednio MediaSession, bez konieczności używania oprogramowania sprzęgającego.

  1. Usuń wszystkie odwołania do MediaSessionConnector i jego zastosowania: jeśli do migracji klas i pakietów ExoPlayera użyto automatycznego skryptu, prawdopodobnie pozostawił on kod w stanie uniemożliwiającym kompilację z powodu błędu MediaSessionConnector, którego 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 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.11.1"
    
  3. Zastąp MediaSessionCompat tym: androidx.media3.session.MediaSession.

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

    Kotlin

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player).setCallback(MySessionCallback()).build()

    Java

    ExoPlayer player = new ExoPlayer.Builder(context).build();
    mediaSession =
        new MediaSession.Builder(context, player).setCallback(new MySessionCallback()).build();

  5. Zaimplementuj 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(). Obsługuje różne aktualne i starsze metody interfejsu API, które dodają elementy multimedialne do odtwarzacza w celu odtworzenia 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 w PlaybackService aplikacji demonstracyjnej sesji.

  6. Zwolnij sesję multimedialną w miejscu w kodzie, w którym sesja została zniszczona przed migracją:

    Kotlin

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

    Java

    if (mediaSession != null) {
      mediaSession.getPlayer().release();
      mediaSession.release();
      mediaSession = null;
    }

Funkcja MediaSessionConnector w Media3

W tabeli poniżej znajdziesz interfejsy Media3 API, które obsługują funkcje wcześniej implementowane 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 danych z MediaBrowserService do MediaLibraryService

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

MediaLibraryService jest zgodny wstecznie z MediaBrowserService. Aplikacja kliencka, która używa MediaBrowserCompat lub MediaControllerCompat, nadal działa bez zmian w kodzie podczas nawiązywania połączenia z MediaLibraryService. Dla klienta nie ma znaczenia, czy aplikacja używa MediaLibraryService czy starszej wersji MediaBrowserServiceCompat.

Diagram komponentów aplikacji z usługą, aktywnością i aplikacjami zewnętrznymi.
Rysunek 1. Omówienie komponentów aplikacji do multimediów
  1. Aby zapewnić zgodność wsteczną, musisz zarejestrować oba interfejsy usługi w usłudze w AndroidManifest.xml. Dzięki temu 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 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.11.1"
    
  3. Zmień usługę, aby dziedziczyła z MediaLibraryService zamiast z MediaBrowserService. Jak wspomniano wcześniej, MediaLibraryService jest zgodny ze starszą wersją 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:

    • Zaimplementuj metody cyklu życia usługi: metody, które należy zastąpić w samej usłudze, to onCreate i onDestroy, w których aplikacja przydziela lub zwalnia sesję biblioteki, odtwarzacz i inne zasoby. Oprócz standardowych metod cyklu życia usługi aplikacja musi zastąpić metodę onGetSession(MediaSession.ControllerInfo), aby zwracać obiekt MediaLibrarySession utworzony w onCreate.

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

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

      Kotlin

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

      Java

      mediaLibrarySession =
          new MediaLibrarySession.Builder(context, player, new MySessionCallback()).build();

      Pełny interfejs API MediaLibrarySessionCallback znajdziesz w dokumentacji interfejsu API.

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

    • AndroidX Media3 używa androidx.media3.common.MediaItem zamiast MediaBrowserCompat.MediaItem i MediaMetadataCompat. Części kodu powiązane ze starszymi klasami należy odpowiednio zmienić lub zmapować na klasę MediaItem Media3.

    • Ogólny model programowania asynchronicznego zmienił się na Futures, w odróżnieniu od podejścia Result w MediaBrowserServiceCompat. Implementacja usługi może zwracać asynchroniczny obiekt ListenableFuture zamiast odłączać wynik lub zwracać natychmiastowy obiekt Future, aby bezpośrednio zwrócić wartość.

Usuwanie PlayerNotificationManager

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

Aplikacja może dostosować powiadomienie, ustawiając niestandardowy element MediaNotification.Provider w onCreate(), który zastępuje DefaultMediaNotificationProvider. MediaLibraryService zajmuje się wtedy uruchomieniem usługi na pierwszym planie zgodnie z wymaganiami.

Zastępując MediaLibraryService.updateNotification(), aplikacja może przejąć pełną kontrolę nad publikowaniem powiadomień oraz uruchamianiem i zatrzymywaniem usługi na pierwszym planie zgodnie z potrzebami.

Migracja kodu klienta za pomocą MediaBrowser

W AndroidX 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ć MediaBrowserCompat i MediaControllerCompat, możesz zrobić to samo, używając tylko MediaBrowser w Media3.

Można utworzyć MediaBrowser i poczekać na nawiązanie połączenia z usługą:

Kotlin

scope.launch {
  val sessionToken = SessionToken(context, ComponentName(context, "MusicService"))
  browser =
    MediaBrowser.Builder(context, sessionToken)
      .setListener(BrowserListener())
      .buildAsync()
      .await()
}

Java

SessionToken sessionToken =
    new SessionToken(context, new ComponentName(context, "MusicService"));
ListenableFuture<MediaBrowser> browserFuture =
    new MediaBrowser.Builder(context, sessionToken)
        .setListener(new BrowserListener())
        .buildAsync();

Zapoznaj się z artykułem Sterowanie odtwarzaniem w sesji multimedialnej, aby dowiedzieć się, jak utworzyć MediaController do sterowania odtwarzaniem w tle.

Dalsze kroki i czyszczenie

W sekcjach poniżej wyjaśniamy, jak po zakończeniu początkowej migracji postępować z błędami analizy kodu dotyczącymi niestabilnych interfejsów API i wycofanych interfejsów API.

Błędy niestabilnego interfejsu API

Po migracji do Media3 mogą pojawić się błędy analizy statycznej dotyczące użycia niestabilnych interfejsów API. Te interfejsy API są bezpieczne w użyciu, a błędy linta są efektem ubocznym naszej nowej polityki zgodności binarnej. Jeśli nie potrzebujesz ścisłej zgodności binarnej, możesz bezpiecznie pominąć te błędy, używając adnotacji @OptIn.

Tło

Ani ExoPlayer v1, ani ExoPlayer v2 nie zapewniały ścisłej zgodności binarnej biblioteki między kolejnymi wersjami. Interfejs ExoPlayer API jest bardzo rozbudowany, aby umożliwić aplikacjom dostosowywanie niemal każdego aspektu odtwarzania. Kolejne wersje ExoPlayera 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 problemy były rozwiązywane przez wprowadzenie nowego symbolu i wycofanie starego w kilku wersjach, aby dać deweloperom czas na przeniesienie zastosowań, ale nie zawsze było to możliwe.

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

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

Ulepszenia w Media3

Media3 zapewnia zgodność binarną w przypadku części interfejsu API. Części, które nie zapewniają zgodności binarnej, są oznaczone symbolem @UnstableApi. Aby wyraźnie zaznaczyć tę różnicę, użycie niestabilnych symboli interfejsu API generuje błąd analizy kodu, chyba że są one oznaczone adnotacją @OptIn.

Po migracji z ExoPlayera v2 na Media3 możesz zobaczyć wiele błędów linta związanych z niestabilnym interfejsem API. Może to sprawiać wrażenie, że Media3 jest „mniej stabilny” niż ExoPlayer v2. To nieprawda. „Niestabilne” części interfejsu Media3 API mają ten sam poziom stabilności co cały interfejs ExoPlayer v2 API, a zgodność binarna stabilnego interfejsu Media3 API nie jest w ogóle dostępna w ExoPlayer v2. Różnica polega na tym, że błąd analizatora kodu ostrzega teraz o różnych poziomach stabilności.

Obsługa błędów analizy kodu niestabilnego interfejsu API

Szczegółowe informacje o tym, jak dodawać adnotacje @OptIn do użycia niestabilnych interfejsów API w językach Java i Kotlin, znajdziesz w sekcji dotyczącej rozwiązywania problemów z błędami analizy statycznej.

Wycofane interfejsy API

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

Zrzut ekranu: jak wyświetlić JavaDoc z alternatywą dla wycofanej metody
Ilustracja 3. Etykietka JavaDoc w Android Studio sugeruje alternatywę dla wycofanego symbolu.

Przykładowe kody i aplikacje demonstracyjne

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