自訂

ExoPlayer 程式庫的核心是 Player 介面。Player 會公開傳統的高階媒體播放器功能,例如緩衝媒體、播放、暫停和快轉。預設實作 ExoPlayer 的設計目的是盡量不對播放的媒體類型、儲存方式和位置,以及顯示方式做出任何假設 (因此也不會對這些項目施加任何限制)。ExoPlayer 實作項目並未直接實作媒體的載入和轉譯作業,而是將這項工作委派給在建立播放器或將新媒體來源傳遞至播放器時,會被插入的元件。所有 ExoPlayer 實作項目通用的元件如下:

  • MediaSource 例項會定義要播放的媒體、載入媒體,以及可讀取已載入媒體的來源。播放器中的 MediaSource.Factory 會從 MediaItem 建立 MediaSource 例項。您也可以使用以媒體來源為準的播放清單 API,直接將這些資訊傳遞給播放器。
  • MediaItem 轉換為 MediaSourceMediaSource.Factory 例項。系統會在建立播放器時插入 MediaSource.Factory
  • 用於轉譯媒體個別元件的 Renderer 例項。這些會在建立玩家時插入。
  • TrackSelector 會選取 MediaSource 提供的曲目,供每個可用的 Renderer 使用。建立播放器時會插入 TrackSelector
  • LoadControl,可控制 MediaSource 緩衝更多媒體的時間,以及緩衝處理的媒體數量。建立播放器時會插入 LoadControl
  • LivePlaybackSpeedControl 可控制直播播放期間的播放速度,讓播放器保持在所設定的直播偏移值附近。建立播放器時會插入 LivePlaybackSpeedControl

在整個程式庫中,都會注入可實作部分播放器功能的元件。部分元件的預設實作會將工作委派給進一步插入的元件。這樣一來,您就能個別將許多子元件取代為以自訂方式設定的實作項目。

播放器自訂功能

以下列舉一些常見的例子,說明如何透過插入元件自訂播放器。

設定網路堆疊

歡迎參閱這個頁面,瞭解如何自訂 ExoPlayer 使用的網路堆疊。

快取從網路載入的資料

請參閱暫時動態快取下載媒體的指南。

自訂伺服器互動

部分應用程式可能會攔截 HTTP 要求和回應。您可能需要插入自訂要求標頭、讀取伺服器的回應標頭、修改要求的 URI 等。舉例來說,應用程式可以在要求媒體區隔時,插入權杖做為標頭,藉此進行自身驗證。

以下範例說明如何在 DefaultMediaSourceFactory 中插入自訂 DataSource.Factory,以實作這些行為:

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 引數包含失敗載入作業的詳細資訊,可根據錯誤類型或失敗的請求自訂邏輯。

自訂擷取器標記

您可以使用擷取器標記來自訂從漸進式媒體擷取個別格式的方式。您可以在提供給 DefaultMediaSourceFactoryDefaultExtractorsFactory 上設定這些屬性。以下範例會傳遞旗標,啟用以索引為基礎的 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();

如果您要直接將轉譯器例項化,請將 AsynchronousMediaCodecAdapter.Factory 傳遞至 MediaCodecVideoRendererMediaCodecAudioRenderer 建構函式。

使用 ForwardingPlayer 攔截方法呼叫

您可以將 Player 例項包裝在 ForwardingPlayer 的子類別中,並覆寫方法,以便執行下列任何操作:

  • 請先存取參數,再傳送至委派的 Player
  • 在傳回前,先存取委派函 Player 的傳回值。
  • 徹底重新實作該方法。

覆寫 ForwardingPlayer 方法時,請務必確認實作項目保持自我一致性,且與 Player 介面相容,特別是處理預期具有相同或相關行為的方法時。例如:

  • 如果您想覆寫每個「播放」作業,就必須同時覆寫 ForwardingPlayer.playForwardingPlayer.setPlayWhenReady,因為呼叫端會預期這些方法在 playWhenReady = true 時的行為相同。
  • 如要變更向前搜尋增量,您必須覆寫 ForwardingPlayer.seekForwardForwardingPlayer.getSeekForwardIncrement,以便使用自訂增量執行搜尋,並將正確的自訂增量回報給呼叫端。
  • 如果您想控制播放器例項宣傳的 Player.Commands,必須同時覆寫 Player.getAvailableCommands()Player.isCommandAvailable(),並且監聽 Player.Listener.onAvailableCommandsChanged() 回呼,以便收到來自底層播放器的變更通知。

MediaSource 自訂

上述範例會在播放所有傳遞至播放器的 MediaItem 物件時,插入自訂元件。如果需要精細自訂,您也可以將自訂元件插入個別 MediaSource 例項,並直接傳遞給玩家。以下範例說明如何自訂 ProgressiveMediaSource,以便使用自訂 DataSource.FactoryExtractorsFactoryLoadErrorHandlingPolicy

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 將訊息傳送至元件,藉此傳遞設定變更。傳送要在播放執行緒中傳送的訊息,可確保這些訊息會依序執行,並且在播放器上執行任何其他作業。