使用 Media3 ExoPlayer 建立基本媒體播放器應用程式

Jetpack Media3 定義了 Player 介面,其中列出播放影片和音訊檔案的基本功能。ExoPlayer 是 Media3 中這個介面的預設實作。建議使用 ExoPlayer,因為它提供完整的功能組合,涵蓋大多數的播放用途,而且可自訂,以處理您可能有的任何其他用途。此外,ExoPlayer 也會將裝置和作業系統的片段抽離,讓您的程式碼在整個 Android 生態系統中都能一致運作。ExoPlayer 包含:

本頁將逐步說明建構播放應用程式的一些重要步驟,如需更多詳細資料,請參閱「Media3 ExoPlayer」完整指南。

開始使用

如要開始使用,請新增 Jetpack Media3 的 ExoPlayer、UI 和 Common 模組依附元件:

implementation "androidx.media3:media3-exoplayer:1.9.2"
implementation "androidx.media3:media3-ui:1.9.2"
implementation "androidx.media3:media3-common:1.9.2"

視用途而定,您可能也需要 Media3 的其他模組,例如 exoplayer-dash,才能播放 DASH 格式的串流。

請務必將 1.9.2 替換為偏好的程式庫版本。如要查看最新版本,請參閱「版本資訊」。

建立媒體播放器

使用 Media3 時,您可以選擇使用隨附的 Player 介面實作項目 ExoPlayer,也可以自行建構自訂實作項目。

建立 ExoPlayer

建立 ExoPlayer 執行個體最簡單的方法如下:

Kotlin

val player = ExoPlayer.Builder(context).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

您可以在 ActivityFragmentServiceonCreate() 生命週期方法中建立媒體播放器。

Builder 包含一系列自訂選項,您可能會感興趣,例如:

Media3 提供 PlayerView UI 元件,可加入應用程式的版面配置檔案。這個元件會封裝 PlayerControlView (用於播放控制項)、SubtitleView (用於顯示字幕) 和 Surface (用於算繪影片)。

準備播放器

使用 setMediaItem()addMediaItem() 等方法,將媒體項目新增至播放清單,以便播放。然後呼叫 prepare(),開始載入媒體並取得必要資源。

應用程式處於前景時,您才應執行這些步驟。如果播放器處於 ActivityFragment 狀態,表示您在 API 級別 24 以上版本中,於 onStart() 生命週期方法準備播放器,或在 API 級別 23 以下版本中,於 onResume() 生命週期方法準備播放器。如要為 Service 中的球員準備,請前往 onCreate()。如要瞭解如何實作生命週期方法,請參閱 ExoPlayer 程式碼研究室

控制播放器

準備好播放器後,您可以在播放器上呼叫方法來控制播放作業,例如:

繫結至播放器時,PlayerViewPlayerControlView 等 UI 元件會相應更新。

釋放播放器

播放影片可能需要資源 (例如影片解碼器),因此不再需要播放器時,請務必呼叫 release(),釋出資源。

如果播放器位於 ActivityFragment 中,請在 API 級別 24 以上版本的 onStop() 生命週期方法中,或在 API 級別 23 以下版本的 onPause() 方法中,釋放播放器。如要釋放 Service 中的球員,請前往 onDestroy()。如要瞭解如何實作生命週期方法,請參閱 ExoPlayer 程式碼研究室

使用媒體工作階段管理播放

在 Android 上,媒體工作階段提供標準化方式,可跨程序界線與媒體播放器互動。將媒體工作階段連結至播放器,即可在外部宣傳媒體播放作業,並接收來自外部來源的播放指令,例如整合行動裝置和大螢幕裝置上的系統媒體控制項

如要使用媒體工作階段,請新增 Media3 Session 模組的依附元件:

implementation "androidx.media3:media3-session:1.9.2"

建立媒體工作階段

初始化播放器後,您可以按照下列方式建立 MediaSession

Kotlin

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

Java

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

Media3 會自動將 Player 的狀態與 MediaSession 的狀態同步。這項功能適用於任何 Player 實作項目,包括 ExoPlayerCastPlayer 或自訂實作項目。

將控制權授予其他用戶端

用戶端應用程式可以實作媒體控制器,控制媒體工作階段的播放作業。如要接收這些要求,請在建構 MediaSession 時設定 callback 物件。

當控制器即將連線至媒體工作階段時,系統會呼叫 onConnect() 方法。您可以根據提供的 ControllerInfo 決定是否要接受拒絕要求。如需相關範例,請參閱 Media3 Session 示範應用程式

連線後,遙控器就能將播放指令傳送至工作階段。然後,工作階段會將這些指令委派給播放器。工作階段會自動處理 Player 介面中定義的播放和播放清單指令。

其他回呼方法可讓您處理要求,例如自訂播放指令修改播放清單。這些回呼同樣包含 ControllerInfo 物件,因此您可以根據要求逐一判斷存取控管機制。

在背景播放媒體

如要在應用程式不在前景時繼續播放媒體 (例如在使用者未開啟應用程式時播放音樂、有聲書或 Podcast),請將 PlayerMediaSession 封裝在前景服務中。為此,Media3 提供 MediaSessionService 介面。

實作 MediaSessionService

建立擴充 MediaSessionService 的類別,並在 onCreate() 生命週期方法中例項化 MediaSession

Kotlin

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    // Create your Player and MediaSession in the onCreate lifecycle event
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    // Remember to release the player and media session in onDestroy
    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }
}

Java

public class PlaybackService extends MediaSessionService {
    private MediaSession mediaSession = null;

    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        mediaSession = new MediaSession.Builder(this, player).build();
    }

    @Override
    public void onDestroy() {
        mediaSession.getPlayer().release();
        mediaSession.release();
        mediaSession = null;
        super.onDestroy();
    }
}

在資訊清單中,新增 Service 類別和 MediaSessionService 意圖篩選器,並要求 FOREGROUND_SERVICE 權限來執行前景服務:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

最後,在您建立的類別中,覆寫 onGetSession() 方法,控管用戶端對媒體工作階段的存取權。傳回 MediaSession 即可接受連線要求,傳回 null 則可拒絕要求。

Kotlin

// This example always accepts the connection request
override fun onGetSession(
    controllerInfo: MediaSession.ControllerInfo
): MediaSession? = mediaSession

Java

@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
  // This example always accepts the connection request
  return mediaSession;
}

連結至使用者介面

現在媒體工作階段與播放器 UI 所在的 ActivityFragment 分開,因此您可以使用 MediaController 將兩者連結在一起。ServiceActivityFragmentonStart() 方法中,使用您的 UI 建立 MediaSessionSessionToken,然後使用 SessionToken 建構 MediaController。建構 MediaController 是非同步作業。

Kotlin

override fun onStart() {
  val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
  val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
  controllerFuture.addListener(
    {
        // Call controllerFuture.get() to retrieve the MediaController.
        // MediaController implements the Player interface, so it can be
        // attached to the PlayerView UI component.
        playerView.setPlayer(controllerFuture.get())
      },
    MoreExecutors.directExecutor()
  )
}

Java

@Override
public void onStart() {
  SessionToken sessionToken =
    new SessionToken(this, new ComponentName(this, PlaybackService.class));
  ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(this, sessionToken).buildAsync();
  controllerFuture.addListener(() -> {
    // Call controllerFuture.get() to retrieve the MediaController.
    // MediaController implements the Player interface, so it can be
    // attached to the PlayerView UI component.
    playerView.setPlayer(controllerFuture.get());
  }, MoreExecutors.directExecutor())
}

MediaController 會實作 Player 介面,因此您可以使用 play()pause() 等相同方法來控制播放作業。與其他元件類似,請記得在不再需要 MediaController 時釋出,例如呼叫 MediaController.releaseFuture(),釋出 ActivityonStop() 生命週期方法。

發布通知

前景服務必須在啟動時發布通知。A MediaSessionService 會自動為您建立 MediaStyle 通知,並以 MediaNotification 形式顯示。 如要提供自訂通知,請建立 MediaNotification.Provider,並使用 DefaultMediaNotificationProvider.Builder,或是建立提供者介面的自訂實作項目。使用 setMediaNotificationProvider 將提供者新增至 MediaSession

宣傳內容庫

MediaLibraryService 是以 MediaSessionService 為基礎,可讓用戶端應用程式瀏覽應用程式提供的媒體內容。用戶端應用程式會實作 MediaBrowser,與 MediaLibraryService 互動。

實作 MediaLibraryService 的方式與實作 MediaSessionService 類似,但您應在 onGetSession() 中傳回 MediaLibrarySession,而非 MediaSession。相較於 MediaSession.CallbackMediaLibrarySession.Callback 包含其他方法,可讓瀏覽器用戶端瀏覽程式庫服務提供的內容。

MediaSessionService 類似,在資訊清單中宣告 MediaLibraryService,並要求 FOREGROUND_SERVICE 權限來執行前景服務:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

上述範例包含 MediaLibraryService 的意圖篩選器,以及為了回溯相容性而加入的舊版 MediaBrowserService。額外意圖篩選器可讓使用 MediaBrowserCompat API 的用戶端應用程式辨識 Service

MediaLibrarySession 可讓您以樹狀結構提供內容資料庫,並使用單一根 MediaItem。樹狀結構中的每個 MediaItem 都可以有任意數量的子項 MediaItem 節點。您可以根據用戶端應用程式的要求,提供不同的根或樹狀結構。舉例來說,您傳回給尋找建議媒體項目清單的用戶端的樹狀結構,可能只包含根 MediaItem 和單一層級的子項 MediaItem 節點,而您傳回給其他用戶端應用程式的樹狀結構,則可能代表更完整的內容庫。

建立 MediaLibrarySession

MediaLibrarySession 會擴充 MediaSession API,加入內容瀏覽 API。相較於 MediaSession 回呼MediaLibrarySession 回呼新增了下列方法:

  • onGetLibraryRoot() 當用戶端要求內容樹狀結構的根 MediaItem
  • onGetChildren() for when a client requests the children of a MediaItem in the content tree
  • onGetSearchResult() 當用戶端要求從內容樹狀結構中取得特定查詢的搜尋結果時

相關回呼方法會包含 LibraryParams 物件,其中含有用戶端應用程式感興趣的內容樹狀結構類型相關額外信號。