AndroidX Media3 遷移指南

目前使用獨立 com.google.android.exoplayer2 程式庫和 androidx.media 的應用程式應遷移至 androidx.media3。使用遷移指令碼,將 Gradle 建構檔案、Java 和 Kotlin 來源檔案以及 XML 版面配置檔案從 ExoPlayer 2.19.1 遷移至 AndroidX Media3 1.1.1

總覽

進行遷移前,請詳閱下列各節,進一步瞭解新 API 的優點、要遷移的 API,以及應用程式專案應符合的必要條件。

為何要遷移至 Jetpack Media3

  • 這是 ExoPlayer 的新首頁,而 com.google.android.exoplayer2 已終止。
  • 使用 MediaBrowser/MediaController 存取不同元件/程序的 Player API
  • 使用 MediaSessionMediaController 的擴充功能 API。
  • 透過精細的存取權控管機制通告播放功能。
  • 移除 MediaSessionConnectorPlayerNotificationManager,藉此簡化應用程式
  • 與 media-compat 用戶端 API 回溯相容 (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

應遷移至 AndroidX Media3 的媒體 API

  • ExoPlayer 及其擴充功能
    這包括舊版 ExoPlayer 專案的所有模組,但已停用的媒體工作階段模組。視 com.google.android.exoplayer2 中的套件而定的應用程式或模組可以使用遷移指令碼進行遷移。
  • MediaSessionConnector (視 androidx.media:media:1.4.3+androidx.media.* 套件而定)
    移除 MediaSessionConnector 並改用 androidx.media3.session.MediaSession
  • MediaBrowserServiceCompat (取決於 androidx.media:media:1.4.3+androidx.media.* 套件)
    androidx.media.MediaBrowserServiceCompat 的子類別遷移至 androidx.media3.session.MediaLibraryService,並將使用 MediaBrowserCompat.MediaItem 的程式碼遷移至 androidx.media3.common.MediaItem
  • MediaBrowserCompat (取決於 androidx.media:media:1.4.3+android.support.v4.media.* 套件)
    使用 MediaBrowserCompatMediaControllerCompat 遷移用戶端程式碼,以便將 androidx.media3.session.MediaBrowserandroidx.media3.common.MediaItem 搭配使用。

必要條件

  1. 確認專案受到原始碼控管

    請確認您能輕鬆還原透過指令碼編寫的遷移工具套用的變更。如果您的專案尚未受原始碼控管,現在是開始執行 的絕佳時機。如果您因為某些原因而不想執行這項操作,請在開始遷移之前,先為專案建立備份。

  2. 更新應用程式

    • 建議您更新專案,改用最新版 ExoPlayer 程式庫,並移除所有對已淘汰方法的呼叫。如果要使用指令碼進行遷移,您需要讓更新的版本與指令碼處理的版本相符。

    • 應用程式的 compileSdkVersion 至少提高為 32

    • 將 Gradle 和 Android Studio Gradle 外掛程式升級至支援上述更新依附元件的最新版本。例如:

      • Android Gradle 外掛程式版本:7.1.0
      • Gradle 版本:7.4
    • 取代所有使用光柵 (*) 的萬用字元匯入陳述式,並使用完整匯入陳述式:刪除萬用字元匯入陳述式,並使用 Android Studio 匯入完整的陳述式 (F2 - Alt/Enter、F2 - Alt/Enter、...)。

    • com.google.android.exoplayer2.PlayerView 遷移至 com.google.android.exoplayer2.StyledPlayerView。由於 AndroidX Media3 中沒有等同於 com.google.android.exoplayer2.PlayerView 的功能,因此這是必要步驟。

透過支援指令碼遷移 ExoPlayer

這個指令碼可協助您從 com.google.android.exoplayer2 移至 androidx.media3 下的新套件和模組結構。指令碼會對專案套用部分驗證檢查,並在驗證失敗時顯示警告。否則,系統會在以 Java 或 Kotlin 編寫的 Android Gradle 專案資源中,套用已重新命名類別和套件的對應

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

使用遷移指令碼

  1. 找到 GitHub 上與您已更新應用程式相對應的版本,從 ExoPlayer 專案的標記下載遷移指令碼

    curl -o media3-migration.sh \
      "https://raw.githubusercontent.com/google/ExoPlayer/r2.19.1/media3-migration.sh"
    
  2. 將指令碼設為可執行狀態:

    chmod 744 media3-migration.sh
    
  3. 使用 --help 執行指令碼,瞭解選項。

  4. 使用 -l 執行指令碼,列出選取要遷移的檔案組合 (使用 -f 來強制列出清單而不顯示警告):

    ./media3-migration.sh -l -f /path/to/gradle/project/root
    
  5. 使用 -m 執行指令碼,將套件、類別和模組對應至 Media3。使用 -m 選項執行指令碼時,系統會將變更套用至所選檔案。

    • 發生驗證錯誤但不進行變更
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • 強制執行

    如果指令碼發現違反必要條件,您就能使用 -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

使用 -m 選項執行指令碼後,請完成下列手動步驟:

  1. 檢查指令碼變更程式碼的方式:使用差異工具並修正潛在問題 (如果您認為指令碼有一般問題,但未傳遞 -f 選項就導入,請考慮提交錯誤)。
  2. 建構專案:請使用 ./gradlew clean build,或在 Android Studio 中依序選擇「File」>「Sync Project with Gradle Files」,然後依序選擇「Build」>「Clean project」、「Build」>「Rebuild project」 (透過 Android Studio 的「Build - Build Output」分頁監控建構作業。

建議的後續步驟:

  1. 解決啟用與使用不穩定 API 相關的錯誤
  2. 取代已淘汰的 API 呼叫:使用建議的替代 API。將遊標懸停在 Android Studio 中的警告上,並參閱已淘汰符號的 JavaDoc,瞭解該使用什麼,而非指定的呼叫。
  3. 排序匯入陳述式:在 Android Studio 中開啟專案,然後在專案檢視器中的套件資料夾節點上按一下滑鼠右鍵,然後在包含已變更來源檔案的套件上選擇「Optimize imports」(最佳化匯入)

androidx.media3.session.MediaSession 取代 MediaSessionConnector

在舊版 MediaSessionCompat 中,MediaSessionConnector 負責將玩家狀態與工作階段狀態保持同步,並從需要委派的控制器接收指令至適當的玩家方法。AndroidX Media3 則是由 MediaSession 直接執行,無須使用連接器。

  1. 移除所有 MediaSessionConnector 的參照和用法:如果您使用自動化指令碼遷移 ExoPlayer 類別和套件,則指令碼很可能會將您的程式碼處於無法解析的 MediaSessionConnector 狀態。當您嘗試建構或啟動應用程式時,Android Studio 會顯示損毀的程式碼。

  2. 在維護依附元件的 build.gradle 檔案中,新增實作依附元件至 AndroidX Media3 工作階段模組,並移除舊版依附元件:

    implementation "androidx.media3:media3-session:1.3.1"
    
  3. MediaSessionCompat 替換為 androidx.media3.session.MediaSession

  4. 在建立舊版 MediaSessionCompat 的程式碼網站上,使用 androidx.media3.session.MediaSession.Builder建構 MediaSession傳遞玩家以建構工作階段建構工具。

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player)
        .setSessionCallback(MySessionCallback())
        .build()
    
  5. 請依應用程式要求實作 MySessionCallback。此為選用功能。如要允許控制器在播放器中新增媒體項目,請實作 MediaSession.Callback.onAddMediaItems()。可提供各種目前和舊版 API 方法,以回溯相容的方式將媒體項目新增至播放器以便播放。其中包括 Media3 控制器的 MediaController.set/addMediaItems() 方法,以及舊版 API 的 TransportControls.prepareFrom*/playFrom* 方法。您可以在工作階段示範應用程式的 PlaybackService 中找到 onAddMediaItems 的實作範例。

  6. 在遷移前刪除工作階段的程式碼網站,啟動媒體工作階段:

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

Media3 中的 MediaSessionConnector 功能

下表顯示處理先前在 MediaSessionConnector 中實作的功能的 Media3 API。

MediaSessionConnectorAndroidX Media3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setCustomLayout()
PlaybackPreparer MediaSession.Callback.onAddMediaItems() (在內部呼叫 prepare())
QueueNavigator ForwardingPlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

MediaBrowserService 遷移至 MediaLibraryService

AndroidX Media3 推出了 MediaLibraryService 取代 MediaBrowserServiceCompatMediaLibraryService 的 JavaDoc 及其父類別 MediaSessionService 提供了詳細的 API 和這項服務非同步程式設計模型介紹。

MediaLibraryServiceMediaBrowserService 回溯相容。使用 MediaBrowserCompatMediaControllerCompat 的用戶端應用程式可在連線至 MediaLibraryService 時繼續運作,且不會變更程式碼。對用戶端而言,您的應用程式使用的是 MediaLibraryService 還是舊版 MediaBrowserServiceCompat

應用程式元件圖表,當中包含服務、活動和外部應用程式。
圖 1:媒體應用程式元件總覽
  1. 為了讓回溯相容性正常運作,您必須在 AndroidManifest.xml註冊兩個服務介面。如此一來,用戶端就能透過所需的服務介面找到您的服務:

    <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. 在維護依附元件的 build.gradle 檔案中,將實作依附元件新增至 AndroidX Media3 工作階段模組,並移除舊版依附元件:

    implementation "androidx.media3:media3-session:1.3.1"
    
  3. 將服務變更為沿用 MediaLibraryService (而非 MediaBrowserService)。如先前所述,MediaLibraryService 與舊版 MediaBrowserService 相容。因此,服務為客戶提供的更廣泛 API 依然是相同的。因此,應用程式可能可以保留實作 MediaBrowserService 並配合新的 MediaLibraryService 所需的大部分邏輯。

    與舊版 MediaBrowserServiceCompat 的主要差異如下:

    • 實作服務生命週期方法:需要在服務本身覆寫的方法為 onCreate/onDestroy,也就是應用程式分配/釋出程式庫工作階段、玩家和其他資源。除了標準服務生命週期方法外,應用程式還需要覆寫 onGetSession(MediaSession.ControllerInfo),才能傳回在 onCreate 中建構的 MediaLibrarySession

    • 實作 MediaLibraryService.MediaLibrarySessionCallback:建構工作階段需要實作實際網域 API 方法的 MediaLibraryService.MediaLibrarySessionCallback。因此,您不需要覆寫舊版服務的 API 方法,而是覆寫 MediaLibrarySession.Callback 的方法。

      然後,系統會使用回呼建構 MediaLibrarySession

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

      您可以在 API 說明文件中找到 MediaLibrarySessionCallback 的完整 API

    • 實作 MediaSession.Callback.onAddMediaItems():回呼 onAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>) 提供多種目前和舊版 API 方法,可將媒體項目新增至播放器,以便以回溯相容的方式播放。其中包括 Media3 控制器的 MediaController.set/addMediaItems() 方法,以及舊版 API 的 TransportControls.prepareFrom*/playFrom* 方法。您可以在工作階段示範應用程式的 PlaybackService 中找到回呼的實作範例。

    • AndroidX Media3 使用 androidx.media3.common.MediaItem,而非 MediaBrowserCompat.MediaItemMediaMetadataCompat。與舊版類別綁定的程式碼部分需要視情況變更,或改為對應至 Media3 MediaItem

    • 一般的非同步程式設計模型變更為 Futures,這與 MediaBrowserServiceCompat 的可卸離 Result 方法比較。服務實作可以傳回非同步 ListenableFuture,而不必卸離結果,或是傳回立即 Future 以直接傳回值

移除 PlayerNotificationManager

MediaLibraryService 會自動支援媒體通知,且在使用 MediaLibraryServiceMediaSessionService 時,可以移除 PlayerNotificationManager

應用程式可在 onCreate() 中設定自訂 MediaNotification.Provider,藉此自訂通知來取代 DefaultMediaNotificationProvider。接著,MediaLibraryService 會視需要在前景中啟動服務。

覆寫 MediaLibraryService.updateNotification() 後,應用程式可以進一步取得發布通知的完整擁有權,並視需要在前景中啟動/停止服務。

使用 MediaBrowser 遷移用戶端程式碼

使用 AndroidX Media3 時,MediaBrowser 會實作 MediaController/Player 介面,並可用於控制媒體播放 (除了瀏覽媒體庫之外)。如果您必須在舊版環境中建立 MediaBrowserCompatMediaControllerCompat,只要在 Media3 中使用 MediaBrowser 即可進行相同操作。

可以建構 MediaBrowser 並等待連線至正在建立的服務:

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)
}

請參考在媒體工作階段中控製播放功能一文,瞭解如何建立用於控制背景播放功能的 MediaController

後續步驟和清除所用資源

不穩定的 API 錯誤

遷移至 Media3 後,您可能會看到與 API 用量不穩定相關的 Lint 錯誤。這些 API 可以安全使用,而 Lint 錯誤是新版二進位檔相容性保證的副產品。如果您不需要嚴格的二進位檔相容性,可使用 @OptIn 註解安全地略過這些錯誤。

背景

ExoPlayer v1 或 v2 並未嚴格保證後續版本之間的程式庫二進位檔相容性。ExoPlayer API 介面在設計上非常大,可讓應用程式自訂幾乎所有播放層面。後續版本的 ExoPlayer 偶爾會發生符號重新命名或其他破壞性變更 (例如介面上的新必要方法)。在大多數情況下,藉由導入新符號並淘汰數個版本的舊符號,即可降低這些損壞情形,讓開發人員有時間遷移使用情況,但並非絕對可行。

這些破壞性變更導致 ExoPlayer v1 和 v2 程式庫使用者遇到兩個問題:

  1. 從 ExoPlayer 版本升級可能會導致程式碼停止編譯。
  2. 直接或透過中繼程式庫依附 ExoPlayer 的應用程式必須確保兩個依附元件版本相同,否則二進位檔不相容可能會導致執行階段異常終止。

改善 Media3

Media3 保證部分 API 介面的二進位檔相容性。「無法」保證二進位檔相容性的部分會標示 @UnstableApi。為清楚區分這項差異,使用不穩定的 API 符號時,除非加上 @OptIn 註解,否則會產生 Lint 錯誤。

從 ExoPlayer v2 遷移至 Media3 後,您可能會看到許多不穩定的 API Lint 錯誤。這可能會使 Media3 似乎比 ExoPlayer v2「不穩定」。但事實並非如此。Media3 API 的「不穩定」部分與 ExoPlayer v2 API 介面的整體穩定性等級相同,而 ExoPlayer v2 完全不提供穩定版 Media3 API 介面的保證。差別只在於 Lint 錯誤現在會提醒您有不同層級的穩定性。

處理不穩定的 API Lint 錯誤

您有兩種方法可處理不穩定的 API Lint 錯誤:

  • 改用可達到相同結果的穩定 API。
  • 繼續使用不穩定的 API,並使用 @OptIn 為用量加上註解。

    import androidx.annotation.OptIn
    import androidx.media3.common.util.UnstableApi
    
    @OptIn(UnstableApi::class)
    fun functionUsingUnstableApi() {
      // Do something useful.
    }
    

    請注意,畫面上還有不應使用的 kotlin.OptIn 註解。為此,請務必保留 androidx.annotation.OptIn 註解。

    螢幕截圖:如何新增「選擇不採用」註解
    圖 2:透過 Android Studio 新增 @androidx.annotations.OptIn 註解。

新增 package-info.java 即可選擇加入整個套件:

@OptIn(markerClass = UnstableApi.class)
package name.of.your.package;

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;

只要隱藏 Lint.xml 中的特定 Lint 錯誤,即可選擇加入整個專案。詳情請參閱 UnstableApi 註解的 JavaDoc

已淘汰的 API

您可能會發現,在 Android Studio 中,對已淘汰 API 的呼叫會加上刪除線。建議您以適當的替代做法取代這類呼叫。將滑鼠遊標懸停在符號上,即可查看 JavaDoc。

螢幕截圖:如何顯示以已淘汰方法替代的 JavaDoc
圖 3:Android Studio 中的 JavaDoc 工具提示,可為任何已淘汰的符號提供替代方案。

程式碼範例和試用版應用程式