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.4.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 功能

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

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 和其超類別 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.4.1"
    
  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 的呼叫劃掉。建議您將這類呼叫替換為適當的替代方案。將滑鼠遊標懸停在符號上,即可查看已改用哪個 API 的 JavaDoc。

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

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