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簡化應用程式
  • 與媒體相容性用戶端 API 向下相容 (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

遷移至 AndroidX Media3 的媒體 API

  • ExoPlayer 及其擴充功能
    這包括舊版 ExoPlayer 專案的所有模組,但不包括已停用的 mediasession 模組。應用程式或模組如果依賴 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.common.MediaItem 使用 androidx.media3.session.MediaBrowser

必要條件

  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.5.0"
    
  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 功能

下表列出 Media3 API,可處理先前在 MediaSessionConnector 中實作的功能。

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

MediaBrowserService 遷移至 MediaLibraryService

AndroidX Media3 引進了 MediaLibraryService,用來取代 MediaBrowserServiceCompatMediaLibraryService 和其超類別 MediaSessionService 的 JavaDoc 提供了 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.5.0"
    
  3. 變更服務,使其繼承自 MediaLibraryService 而非 MediaBrowserService。如先前所述,MediaLibraryService 與舊版 MediaBrowserService 相容。因此,服務提供給用戶端的更廣泛 API 仍維持不變。因此,應用程式很可能可以保留實作 MediaBrowserService 所需的大部分邏輯,並將其調整為新 MediaLibraryService

    與舊版 MediaBrowserServiceCompat 相比,主要差異如下:

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

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

      然後使用回呼建構 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 第 1 版和第 2 版都沒有嚴格保證,說明後續版本之間的程式庫二進位檔相容性。設計上,ExoPlayer API 介面非常龐大,可讓應用程式自訂播放的幾乎所有面向。ExoPlayer 的後續版本偶爾會引進符號重新命名或其他破壞性變更 (例如介面上的新必要方法)。在大多數情況下,我們會透過推出新符號並在幾個版本中淘汰舊符號,藉此緩解這些中斷情形,讓開發人員有時間遷移其用途,但這不一定可行。

這些重大變更導致 ExoPlayer v1 和 v2 程式庫的使用者發生兩個問題:

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

Media3 的改善項目

Media3 可保證 API 介面子集的二進位檔相容性。不保證二進位檔相容性的部分會標示為 @UnstableApi。為了清楚區分這兩者,如果使用不穩定的 API 符號,系統會產生 Lint 錯誤,除非這些符號已加上 @OptIn 註解。

從 ExoPlayer 2.0 遷移至 Media3 後,您可能會看到許多不穩定的 API Lint 錯誤。這可能會讓 Media3 看起來比 ExoPlayer v2 更不穩定。但事實並非如此。Media3 API 的「不穩定」部分與 ExoPlayer v2 API 途徑的「整體」具有相同的穩定性,而 ExoPlayer v2 完全無法提供穩定的 Media3 API 途徑保證。差異在於,現在 lint 錯誤會提醒您穩定性的不同層級。

處理不穩定的 API Lint 錯誤

如要進一步瞭解如何使用 @OptIn 為不穩定 API 的 Java 和 Kotlin 用法加上註解,請參閱這些 Lint 錯誤的疑難排解章節

已淘汰的 API

您可能會發現,Android Studio 會將已淘汰 API 的呼叫劃掉。建議您將這類呼叫替換為適當的替代方案。將滑鼠游標懸停在符號上,即可查看 JavaDoc,瞭解應改用哪個 API。

螢幕截圖:如何使用已淘汰方法的替代方法顯示 JavaDoc
圖 3:Android Studio 中的 JavaDoc 工具提示會建議任何已淘汰符號的替代方案。

程式碼範例和示範應用程式