ExoPlayer 程式庫的核心是 Player
介面。Player
會公開傳統的高階媒體播放器功能,例如緩衝處理媒體、播放、暫停和搜尋。預設實作 ExoPlayer
的設計宗旨,是盡量減少對播放媒體類型、儲存方式和位置,以及轉譯方式的假設 (因此也盡量減少限制)。實作會將這項工作委派給元件,而不是直接實作媒體的載入和算繪作業,
ExoPlayer
元件會在建立播放器或將新媒體來源傳遞至播放器時插入。
所有 ExoPlayer
實作方式的常見元件包括:
MediaSource
執行個體,用於定義要播放的媒體、載入媒體,以及從中讀取載入的媒體。系統會從播放器中的MediaItem
,透過MediaSource.Factory
建立MediaSource
執行個體。也可以使用以媒體來源為準的播放清單 API,直接傳遞至播放器。- 可將
MediaItem
轉換為MediaSource
的MediaSource.Factory
執行個體。建立播放器時,系統會注入MediaSource.Factory
。 Renderer
執行個體,可算繪媒體的個別元件。這些項目會在建立播放器時插入。TrackSelector
:選取MediaSource
提供的音軌,供每個可用Renderer
使用。建立播放器時,系統會注入TrackSelector
。LoadControl
,可控制MediaSource
緩衝處理更多媒體的時機和緩衝處理的媒體量。建立播放器時,系統會插入LoadControl
。LivePlaybackSpeedControl
,可控制直播回放期間的播放速度,讓播放器保持在設定的直播偏移量附近。建立播放器時,系統會插入LivePlaybackSpeedControl
。
程式庫中隨處可見注入元件的概念,這些元件會實作播放器功能片段。部分元件的預設實作會將工作委派給進一步注入的元件。因此許多子元件都能個別替換為以自訂方式設定的實作項目。
自訂播放器
以下列舉一些常見的範例,說明如何透過插入元件自訂播放器。
設定網路堆疊
我們提供自訂 ExoPlayer 所用網路堆疊的頁面。
快取從網路載入的資料
自訂伺服器互動
部分應用程式可能會想攔截 HTTP 要求和回應。您可能需要插入自訂要求標頭、讀取伺服器的回應標頭、修改要求的 URI 等。舉例來說,應用程式可能會在要求媒體片段時,插入權杖做為標頭,藉此驗證自身。
以下範例說明如何將自訂 DataSource.Factory
插入 DefaultMediaSourceFactory
,藉此實作這些行為:
Kotlin
val dataSourceFactory = DataSource.Factory { val dataSource = httpDataSourceFactory.createDataSource() // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value") dataSource } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory) ) .build()
Java
DataSource.Factory dataSourceFactory = () -> { HttpDataSource dataSource = httpDataSourceFactory.createDataSource(); // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value"); return dataSource; }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)) .build();
在上述程式碼片段中,插入的 HttpDataSource
會在每個 HTTP 要求中加入標頭 "Header: Value"
。每次與 HTTP 來源互動時,系統都會修正這項行為。
如要採取更精細的做法,可以使用 ResolvingDataSource
注入即時行為。下列程式碼片段說明如何在與 HTTP 來源互動前,注入要求標頭:
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time request headers. dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time request headers. dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));
您也可以使用 ResolvingDataSource
即時修改 URI,如下列程式碼片段所示:
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time URI resolution logic. dataSpec.withUri(resolveUri(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time URI resolution logic. dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));
自訂錯誤處理
實作自訂 LoadErrorHandlingPolicy
可讓應用程式自訂 ExoPlayer 對載入錯誤的反應方式。舉例來說,應用程式可能會想快速失敗,而不是多次重試,或是想自訂輪詢邏輯,控制播放器在每次重試之間的等待時間。下列程式碼片段說明如何實作自訂退避邏輯:
Kotlin
val loadErrorHandlingPolicy: LoadErrorHandlingPolicy = object : DefaultLoadErrorHandlingPolicy() { override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo): Long { // Implement custom back-off logic here. return 0 } } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy) ) .build()
Java
LoadErrorHandlingPolicy loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy() { @Override public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) { // Implement custom back-off logic here. return 0; } }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context) .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)) .build();
LoadErrorInfo
引數包含載入失敗的詳細資訊,可根據錯誤類型或失敗要求自訂邏輯。
自訂擷取器標記
擷取器標記可用於自訂從漸進式媒體擷取個別格式的方式。這些值可在提供給 DefaultMediaSourceFactory
的 DefaultExtractorsFactory
中設定。以下範例會傳遞可為 MP3 串流啟用索引式搜尋的旗標。
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING) val player = ExoPlayer.Builder(context) .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory)) .build()
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING); ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory)) .build();
啟用固定位元率搜尋
對於 MP3、ADTS 和 AMR 串流,您可以使用 FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
標記,透過固定位元率假設啟用概略搜尋。如要為個別擷取器設定這些標記,請使用上述的個別 DefaultExtractorsFactory.setXyzExtractorFlags
方法。如要為所有支援的擷取器啟用固定位元率搜尋,請使用 DefaultExtractorsFactory.setConstantBitrateSeekingEnabled
。
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
接著,您可以透過 DefaultMediaSourceFactory
注入 ExtractorsFactory
,如上所述自訂擷取器標記。
啟用非同步緩衝區佇列
非同步緩衝區佇列是 ExoPlayer 顯示管道的強化功能,可在非同步模式中運作 MediaCodec
執行個體,並使用額外執行緒排定資料的解碼和顯示作業。啟用這項功能可減少影格遺失和音訊欠載的情況。
在搭載 Android 12 (API 級別 31) 以上版本的裝置上,非同步緩衝區佇列預設為啟用狀態。在搭載 Android 6.0 (API 級別 23) 以上版本的裝置上,則可手動啟用。如果發現特定裝置有掉格或音訊欠載情形,請考慮啟用這項功能,特別是播放受 DRM 保護或高影格率的內容時。
在最簡單的情況下,您需要將 DefaultRenderersFactory
插入播放器,如下所示:
Kotlin
val renderersFactory = DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing() val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()
Java
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing(); ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();
如果直接例項化算繪器,請將 new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous()
傳遞至 MediaCodecVideoRenderer
和 MediaCodecAudioRenderer
建構函式。
使用 ForwardingSimpleBasePlayer
自訂作業
如要自訂 Player
執行個體的某些行為,請將其包裝在 ForwardingSimpleBasePlayer
的子類別中。這個類別可讓您攔截特定「作業」,不必直接實作 Player
方法。這可確保 play()
、pause()
和 setPlayWhenReady(boolean)
等項目行為一致。這項機制也能確保所有狀態變更都正確傳播至已註冊的 Player.Listener
例項。在大多數自訂用途中,由於有這些一致性保證,因此建議使用 ForwardingSimpleBasePlayer
,而非較容易出錯的 ForwardingPlayer
。
舉例來說,如要在播放開始或停止時新增一些自訂邏輯,請按照下列步驟操作:
Kotlin
class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady) } }
Java
class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer { public PlayerWithCustomPlay(Player player) { super(player); } @Override protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady); } }
或者,如要禁止 SEEK_TO_NEXT
指令 (並確保 Player.seekToNext
是無運算):
Kotlin
class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) { override fun getState(): State { val state = super.getState() return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build() ) .build() } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
Java
class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer { public PlayerWithoutSeekToNext(Player player) { super(player); } @Override protected State getState() { State state = super.getState(); return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()) .build(); } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
自訂 MediaSource
上述範例會插入自訂元件,供傳遞至播放器的所有 MediaItem
物件在播放期間使用。如果需要精細的自訂作業,也可以將自訂元件插入個別 MediaSource
執行個體,並直接傳遞至播放器。以下範例說明如何自訂 ProgressiveMediaSource
,以使用自訂的 DataSource.Factory
、ExtractorsFactory
和 LoadErrorHandlingPolicy
:
Kotlin
val mediaSource = ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri))
Java
ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri));
建立自訂元件
程式庫會針對常見用途,提供本頁頂端所列元件的預設實作方式。ExoPlayer
可以使用這些元件,但如果需要非標準行為,也可以建構為使用自訂實作項目。自訂實作的用途包括:
Renderer
- 您可能想實作自訂Renderer
,處理程式庫預設實作項目不支援的媒體類型。TrackSelector
:實作自訂TrackSelector
可讓應用程式開發人員變更MediaSource
顯示的曲目選取方式,供各個可用Renderer
使用。LoadControl
:應用程式開發人員可以實作自訂LoadControl
,變更播放器的緩衝區政策。Extractor
:如果需要支援程式庫目前不支援的容器格式,請考慮實作自訂Extractor
類別。MediaSource
:如要以自訂方式取得媒體樣本,並提供給算繪器,或是要實作自訂MediaSource
合成行為,可能適合實作自訂MediaSource
類別。MediaSource.Factory
- 實作自訂MediaSource.Factory
可讓應用程式自訂從MediaItem
建立MediaSource
的方式。DataSource
- ExoPlayer 的上游套件已包含多種用途的DataSource
實作項目。您可能想實作自己的DataSource
類別,以其他方式載入資料,例如透過自訂通訊協定、使用自訂 HTTP 堆疊,或從自訂持續性快取載入資料。
建構自訂元件時,建議您採取下列做法:
- 如果自訂元件需要將事件回報給應用程式,建議您使用與現有 ExoPlayer 元件相同的模型,例如使用
EventDispatcher
類別,或將Handler
連同接聽程式傳遞至元件的建構函式。 - 我們建議自訂元件使用與現有 ExoPlayer 元件相同的模型,以便應用程式在播放期間重新設定。如要這麼做,自訂元件應實作
PlayerMessage.Target
,並在handleMessage
方法中接收設定變更。應用程式程式碼應呼叫 ExoPlayer 的createMessage
方法,設定訊息並使用PlayerMessage.send
將訊息傳送至元件,藉此傳遞設定變更。將訊息傳送至播放執行緒,確保訊息會與播放器執行的任何其他作業依序執行。