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

  • com.google.android.exoplayer2 已停用,ExoPlayer 則改用 Media3。
  • 使用 MediaBrowser/MediaController 跨元件/程序存取 Player API。
  • 使用 MediaSession 和 MediaController API 的擴充功能。
  • 使用精細的存取控管功能,宣傳播放功能。
  • 移除 MediaSessionConnector 和 PlayerNotificationManager,簡化應用程式。
  • 回溯相容 media-compat 用戶端 API (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

要遷移至 AndroidX Media3 的 Media 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.* 套件)
    使用 MediaBrowserCompat 或 MediaControllerCompat 遷移用戶端程式碼,以便搭配 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.11.1"
    
  3. 將 MediaSessionCompat 替換為 androidx.media3.session.MediaSession。

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

    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. 請根據應用程式需求實作 MySessionCallback。這項操作為選用。如要允許控制器將媒體項目新增至播放器,請實作 MediaSession.Callback.onAddMediaItems()。它提供各種現行和舊版 API 方法,可將媒體項目新增至播放器,並以回溯相容的方式播放。包括 Media3 控制器的 MediaController.set/addMediaItems() 方法,以及舊版 API 的 TransportControls.prepareFrom*/playFrom* 方法。如需 onAddMediaItems 的實作範例,請參閱工作階段示範應用程式的 PlaybackService。

  6. 在遷移前刪除工作階段的程式碼位置,釋出媒體工作階段:

    Kotlin

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

    Java

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

Media3 中的 MediaSessionConnector 功能

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

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

將 MediaBrowserService 遷移至 MediaLibraryService

AndroidX Media3 推出 MediaLibraryService,取代 MediaBrowserServiceCompat。MediaLibraryService 的 JavaDoc 及其超級類別 MediaSessionService 提供了 API 和服務非同步程式設計模型的良好簡介。

MediaLibraryService 與 MediaBrowserService 回溯相容。使用 MediaBrowserCompat 或 MediaControllerCompat 的用戶端應用程式在連線至 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.11.1"
    
  3. 將服務變更為繼承 MediaLibraryService,而非 MediaBrowserService 如前所述,MediaLibraryService 與舊版 MediaBrowserService 相容。因此,服務提供給用戶端的廣泛 API 仍維持不變。因此,應用程式很可能可以保留實作 MediaBrowserService 所需的大部分邏輯,並針對新的 MediaLibraryService 進行調整。

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

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

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

      接著,回呼會用於建構 MediaLibrarySession:

      Kotlin

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

      Java

      mediaLibrarySession =
          new MediaLibrarySession.Builder(context, player, new 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.MediaItem 和 MediaMetadataCompat。與舊版類別相關聯的程式碼部分需要相應變更,或改為對應至 Media3 MediaItem。

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

移除 PlayerNotificationManager

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

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

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

使用 MediaBrowser 遷移用戶端程式碼

在 AndroidX Media3 中,MediaBrowser 會實作 MediaController/Player 介面,除了瀏覽媒體庫外,還可用於控制媒體播放。如果您必須在舊版中建立 MediaBrowserCompat 和 MediaControllerCompat,則只要在 Media3 中使用 MediaBrowser 即可。

您可以建構 MediaBrowser,並等待與服務建立連線:

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();

請參閱「在媒體工作階段中控制播放」,瞭解如何建立 MediaController,以便在背景控制播放。

後續步驟和清除作業

API 錯誤不穩定

遷移至 Media3 後,您可能會看到有關不穩定 API 使用情形的 Lint 錯誤。這些 API 可安全使用,Lint 錯誤是我們新的二進位相容性保證的副產品。如果您不需要嚴格的二進位相容性,可以使用 @OptIn 註解安全地抑制這些錯誤。

背景

ExoPlayer 第 1 版和第 2 版皆未嚴格保證後續版本程式庫的二進位相容性。ExoPlayer API 介面經過精心設計,可讓應用程式自訂幾乎所有播放設定。ExoPlayer 的後續版本有時會重新命名符號,或進行其他破壞性變更 (例如在介面上新增必要方法)。在大多數情況下,我們會引入新符號,同時淘汰舊符號幾個版本,讓開發人員有時間遷移其用途,以緩解這些中斷,但這並非總是可行。

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

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

Media3 的改善項目

Media3 保證部分 API 介面的二進位檔相容性。「不」保證二進位檔相容性的部分會標示 @UnstableApi。為明確區分,使用不穩定的 API 符號會產生 Lint 錯誤,除非加上 @OptIn 註解。

從 ExoPlayer v2 遷移至 Media3 後,您可能會看到許多不穩定的 API Lint 錯誤。這可能會讓人覺得 Media3 比 ExoPlayer v2「不穩定」。但此聲明與事實不符。Media3 API 的「不穩定」部分與 ExoPlayer 第 2 版 API 介面的「整體」穩定性相同,而 ExoPlayer 第 2 版完全不提供穩定 Media3 API 介面的保證。差別在於,現在 Lint 錯誤會提醒您不同的穩定性層級。

處理不穩定的 API lint 錯誤

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

已淘汰的 API

您可能會發現,在 Android Studio 中,對已淘汰 API 的呼叫會加上刪除線。建議您改用適當的替代方案。將滑鼠游標懸停在符號上,即可查看 JavaDoc,瞭解應改用哪個 API。

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

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