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

Jetpack Media3 定義了 Player 介面,其中概略說明瞭播放影片和音訊檔案的基本功能。ExoPlayer 是 Media3 中此介面的預設實作方式。我們建議您使用 ExoPlayer,因為它提供完整的功能組合,涵蓋大多數的播放用途,而且可自訂處理您可能遇到的任何其他用途。ExoPlayer 也會抽離裝置和作業系統的分散現象,讓程式碼在整個 Android 生態系統中一致運作。ExoPlayer 包含:

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

開始使用

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

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

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

請務必將 1.4.1 替換為您偏好的程式庫版本。您可以參閱版本資訊,查看最新版本。

建立媒體播放器

使用 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() 中準備。

控制播放器

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

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

釋放播放器

播放作業可能需要資源 (例如影片解碼器),而這些資源的供應量有限,因此請務必在不再需要播放器時,在播放器上呼叫 release(),釋出資源。

如果播放器位於 ActivityFragment 中,請在 API 級別 24 以上版本的 onStop() 生命週期方法中釋放播放器,或是在 API 級別 23 以下版本的 onPause() 方法中釋放播放器。如果玩家位於 Service,您可以將其釋出至 onDestroy()

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

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

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

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

建立媒體工作階段

您可以在初始化播放器後建立 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 時設定回呼物件。

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

連線後,控制器就能將播放指令傳送至工作階段。然後,工作階段會將這些指令委派給播放器。在 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;
}

連線至使用者介面

由於媒體工作階段位於 Service 中,而非播放器 UI 所在的 ActivityFragment,因此您可以使用 MediaController 將這兩者連結在一起。在 ActivityFragmentonStart() 方法中,為 MediaSession 建立 SessionToken,然後使用 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() 生命週期方法。

發布通知

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

宣傳內容庫

MediaLibraryService 可在 MediaSessionService 上建構,讓用戶端應用程式瀏覽應用程式提供的媒體內容。用戶端應用程式會實作 MediaBrowser,與 MediaLibraryService 互動。

實作 MediaLibraryService 與實作 MediaSessionService 類似,差別只在於在 onGetSession() 中,您應傳回 MediaLibrarySession,而非 MediaSession。與 MediaSession.Callback 相比,MediaLibrarySession.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 回呼會新增下列方法:

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