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

Jetpack Media3 定義了 Player 介面,概述影片和音訊檔案的基本播放功能。ExoPlayer 是此介面在 Media3 中的預設實作方式。建議您使用 ExoPlayer,因為這項工具可提供一系列涵蓋大部分播放用途的功能,並能自訂來處理您可能有的任何其他用途。ExoPlayer 也會抽離裝置和 OS 零散,因此您的程式碼在整個 Android 生態系統中穩定運作。ExoPlayer 包含下列項目:

本頁將逐步說明建構播放應用程式的一些重要步驟,詳情請參閱 Media3 ExoPlayer 的完整指南。

開始使用

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

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

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

請務必將 1.3.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 Session 模組中新增依附元件:

implementation "androidx.media3:media3-session:1.3.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 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();
    }
}

在您的資訊清單中,具有 MediaSessionService 意圖篩選器的 Service 類別,並要求 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 所在的 ServiceActivityFragment 是分開的,您可以使用 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 時 (例如 ActivityonStop() 生命週期方法),方法是呼叫 MediaController.releaseFuture()

發布通知

需要使用前景服務才能發布通知。MediaSessionService 會以 MediaNotification 的形式自動為您建立 MediaStyle 通知。如要提供自訂通知,請使用 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 回呼會新增以下方法:

  • onGetLibraryRoot() 用於用戶端要求內容樹狀結構的根層級 MediaItem
  • onGetChildren() 用於用戶端要求內容樹狀結構中 MediaItem 的子項時
  • onGetSearchResult() 適用於用戶端針對特定查詢的內容樹狀結構要求搜尋結果時

相關的回呼方法將包含 LibraryParams 物件,並提供有關用戶端應用程式感興趣的內容樹狀結構類型的其他信號。