HLS

ExoPlayer 支援 HLS,並提供多種容器格式。內含的音訊和影片樣本格式也必須受到支援 (詳情請參閱「樣本格式」一節)。我們強烈建議 HLS 內容製作人產生高品質的 HLS 串流,詳情請參閱這篇網誌文章

功能 支援 留言
容器
MPEG-TS
FMP4/CMAF
ADTS (AAC)
MP3
隱藏式輔助字幕 / 字幕
CEA-608
CEA-708
WebVTT
Metadata
ID3
SCTE-35
內容保護
AES-128
AES-128 範例
Widevine YES API 19 以上 (「cenc」架構) 和 25 以上 (「cbcs」架構)
PlayReady SL2000 僅 Android TV
伺服器控制
差異更新
封鎖播放清單重新載入
封鎖預先載入提示的載入作業 YES 長度未定義的位元組範圍除外
廣告插播
伺服器導向的廣告插入 (插頁式廣告) 部分 僅限 VOD,且必須有 X-ASSET-URI。 直播和X-ASSET-LIST將於日後新增。
IMA 伺服器端和用戶端廣告 YES 廣告插播指南
即時播放
一般直播播放
低延遲 HTTP 即時串流 (Apple)
低延遲 HTTP 即時串流 (社群)
通用媒體用戶端資料 CMCD YES CMCD 整合指南

使用 MediaItem

如要播放 HTTP 即時串流,您必須依附於 HTTP 即時串流模組。

Kotlin

implementation("androidx.media3:media3-exoplayer-hls:1.7.1")

Groovy

implementation "androidx.media3:media3-exoplayer-hls:1.7.1"

接著,您可以為 HLS 播放清單 URI 建立 MediaItem,並傳遞至播放器。

Kotlin

// Create a player instance.
val player = ExoPlayer.Builder(context).build()
// Set the media item to be played.
player.setMediaItem(MediaItem.fromUri(hlsUri))
// Prepare the player.
player.prepare()

Java

// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media item to be played.
player.setMediaItem(MediaItem.fromUri(hlsUri));
// Prepare the player.
player.prepare();

如果 URI 並非以 .m3u8 結尾,您可以將 MimeTypes.APPLICATION_M3U8 傳遞至 MediaItem.BuildersetMimeType,明確指出內容類型。

媒體項目的 URI 可能會指向媒體播放清單或多變體播放清單。如果 URI 指向宣告多個 #EXT-X-STREAM-INF 標記的多變體播放清單,ExoPlayer 會自動在變體之間調整,同時考量可用頻寬和裝置功能。

使用 HlsMediaSource

如需更多自訂選項,您可以建立 HlsMediaSource,並直接傳遞至播放器,而非 MediaItem

Kotlin

// Create a data source factory.
val dataSourceFactory: DataSource.Factory = DefaultHttpDataSource.Factory()
// Create a HLS media source pointing to a playlist uri.
val hlsMediaSource =
  HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(hlsUri))
// Create a player instance.
val player = ExoPlayer.Builder(context).build()
// Set the HLS media source as the playlist with a single media item.
player.setMediaSource(hlsMediaSource)
// Prepare the player.
player.prepare()

Java

// Create a data source factory.
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
// Create a HLS media source pointing to a playlist uri.
HlsMediaSource hlsMediaSource =
    new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(hlsUri));
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the HLS media source as the playlist with a single media item.
player.setMediaSource(hlsMediaSource);
// Prepare the player.
player.prepare();

存取資訊清單

您可以呼叫 Player.getCurrentManifest 擷取目前的資訊清單。 如果是 HLS,您應將傳回的物件轉換為 HlsManifest。每當載入資訊清單時,系統也會呼叫 Player.ListeneronTimelineChanged 回呼。隨選內容只會發生一次,但直播內容可能會發生多次。下列程式碼片段說明應用程式如何在資訊清單載入時執行某些動作。

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onTimelineChanged(timeline: Timeline, @TimelineChangeReason reason: Int) {
      val manifest = player.currentManifest
      if (manifest is HlsManifest) {
        // Do something with the manifest.
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onTimelineChanged(
          Timeline timeline, @Player.TimelineChangeReason int reason) {
        Object manifest = player.getCurrentManifest();
        if (manifest != null) {
          HlsManifest hlsManifest = (HlsManifest) manifest;
          // Do something with the manifest.
        }
      }
    });

播放含有插頁式廣告的 HLS 串流

HTTP 即時串流規格定義了 HTTP 即時串流插頁式廣告,可用於在媒體播放清單中加入插頁式廣告資訊。ExoPlayer 預設會忽略這些插頁式廣告。您可以使用 HlsInterstitialsAdsLoader 新增支援。我們不會從一開始就支援規格的所有功能,如果我們未支援你的串流,請在 GitHub 上提出問題,並傳送串流 URI 給我們,我們會新增對你串流的支援。

使用播放清單 API 搭配 MediaItem

如要使用插頁式廣告播放 HLS 串流,最簡單的方法是使用 HlsInterstitialsAdsLoader.AdsMediaSourceFactory 建構 ExoPlayer 例項。這樣就能使用 Player 介面的 MediaItem playlist API 播放 HLS 插頁式廣告。

建構播放器例項時,MediaSource.FactoryExoPlayer 可以注入建構工具:

Kotlin

hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context)
// Create a MediaSource.Factory for HLS streams with interstitials.
var hlsMediaSourceFactory =
  HlsInterstitialsAdsLoader.AdsMediaSourceFactory(
    hlsInterstitialsAdsLoader,
    playerView,
    DefaultMediaSourceFactory(context),
  )

// Build player with interstitials media source factory
player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(hlsMediaSourceFactory)
    .build()

// Set the player on the ads loader.
hlsInterstitialsAdsLoader.setPlayer(player)
playerView.setPlayer(player)

Java

hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context);
// Create a MediaSource.Factory for HLS streams with interstitials.
MediaSource.Factory hlsMediaSourceFactory =
      new HlsInterstitialsAdsLoader.AdsMediaSourceFactory(
          hlsInterstitialsAdsLoader, playerView, new DefaultMediaSourceFactory(context));

// Build player with interstitials media source factory
player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(hlsMediaSourceFactory)
        .build();

// Set the player on the ads loader.
hlsInterstitialsAdsLoader.setPlayer(player);
playerView.setPlayer(player);

完成這類播放器設定後,只要在播放器中設定含有 AdsConfiguration 的媒體項目,即可播放 HLS 插頁式廣告:

Kotlin

player.setMediaItem(
  MediaItem.Builder()
    .setUri("https://www.example.com/media.m3u8")
    .setAdsConfiguration(
      AdsConfiguration.Builder(Uri.parse("hls://interstitials"))
        .setAdsId("ad-tag-0") // must be unique within playlist
        .build())
    .build())

player.prepare();
player.play();

Java

player.setMediaItem(
    new MediaItem.Builder()
        .setUri("https://www.example.com/media.m3u8")
        .setAdsConfiguration(
            new AdsConfiguration.Builder(Uri.parse("hls://interstitials"))
                .setAdsId("ad-tag-0") // must be unique within playlist
                .build())
        .build());
player.prepare();
player.play();

使用以媒體來源為基礎的 API

或者,您也可以建構 ExoPlayer 執行個體,而不覆寫預設的媒體來源 Factory。如要支援插頁式廣告,應用程式隨後可以使用 HlsInterstitialsAdsLoader.AdsMediaSourceFactory 直接建立 MediaSource,並透過以媒體來源為基礎的播放清單 API 提供給 ExoPlayer:

Kotlin

hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context)
// Create a MediaSource.Factory for HLS streams with interstitials.
var hlsMediaSourceFactory =
  HlsInterstitialsAdsLoader.AdsMediaSourceFactory(hlsInterstitialsAdsLoader, playerView, context)

// Build player with default media source factory.
player = new ExoPlayer.Builder(context).build();

// Create an media source from an HLS media item with ads configuration.
val mediaSource =
  hlsMediaSourceFactory.createMediaSource(
    MediaItem.Builder()
      .setUri("https://www.example.com/media.m3u8")
      .setAdsConfiguration(
        MediaItem.AdsConfiguration.Builder(Uri.parse("hls://interstitials"))
          .setAdsId("ad-tag-0")
          .build()
      )
      .build()
  )

// Set the media source on the player.
player.setMediaSource(mediaSource)
player.prepare()
player.play()

Java

HlsInterstitialsAdsLoader hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context);
// Create a MediaSource.Factory for HLS streams with interstitials.
MediaSource.Factory hlsMediaSourceFactory =
    new HlsInterstitialsAdsLoader.AdsMediaSourceFactory(
      hlsInterstitialsAdsLoader, playerView, context);

// Build player with default media source factory.
player = new ExoPlayer.Builder(context).build();

// Create an media source from an HLS media item with ads configuration.
MediaSource mediaSource =
    hlsMediaSourceFactory.createMediaSource(
      new MediaItem.Builder()
        .setUri("https://www.example.com/media.m3u8")
        .setAdsConfiguration(
            new MediaItem.AdsConfiguration.Builder(Uri.parse("hls://interstitials"))
                .setAdsId("ad-tag-0")
                .build())
        .build());

// Set the media source on the player.
exoPlayer.setMediaSource(mediaSource);
exoPlayer.prepare();
exoPlayer.play();

監聽廣告事件

Listener 可新增至 HlsInterstitialsAdsLoader,監控與 HLS 插頁式廣告播放狀態變更相關的事件。應用程式或 SDK 即可追蹤播放的廣告、載入的素材資源清單、準備的廣告媒體來源,或偵測素材資源清單載入和廣告準備錯誤。此外,您還可以接收廣告媒體來源發出的中繼資料,以進行精細的廣告播放驗證,或追蹤廣告播放進度。

Kotlin

class AdsLoaderListener : HlsInterstitialsAdsLoader.Listener {

  override fun onStart(mediaItem: MediaItem, adsId: Any, adViewProvider: AdViewProvider) {
    // Do something when HLS media item with interstitials is started.
  }

  override fun onMetadata(
    mediaItem: MediaItem,
    adsId: Any,
    adGroupIndex: Int,
    adIndexInAdGroup: Int,
    metadata: Metadata,
  ) {
    // Do something with metadata that is emitted by the ad media source of the given ad.
  }

  override fun onAdCompleted(
    mediaItem: MediaItem,
    adsId: Any,
    adGroupIndex: Int,
    adIndexInAdGroup: Int,
  ) {
    // Do something when ad completed playback.
  }

  // ... See JavaDoc for further callbacks of HlsInterstitialsAdsLoader.Listener.

  override fun onStop(mediaItem: MediaItem, adsId: Any, adPlaybackState: AdPlaybackState) {
    // Do something with the resulting ad playback state when stopped.
  }
}

Java

private class AdsLoaderListener
    implements HlsInterstitialsAdsLoader.Listener {

  // implement HlsInterstitialsAdsLoader.Listener

  @Override
  public void onStart(MediaItem mediaItem, Object adsId, AdViewProvider adViewProvider) {
    // Do something when HLS media item with interstitials is started.
  }

  @Override
  public void onMetadata(
      MediaItem mediaItem,
      Object adsId,
      int adGroupIndex,
      int adIndexInAdGroup,
      Metadata metadata) {
    // Do something with metadata that is emitted by the ad media source of the given ad.
  }

  @Override
  public void onAdCompleted(
      MediaItem mediaItem, Object adsId, int adGroupIndex, int adIndexInAdGroup) {
    // Do something when ad completed playback.
  }

  // ... See JavaDoc for further callbacks

  @Override
  public void onStop(MediaItem mediaItem, Object adsId, AdPlaybackState adPlaybackState) {
    // Do something with the resulting ad playback state when stopped.
  }
}

如需所有可用回呼的詳細說明文件,請參閱 HlsInterstitialsAdsLoader.ListenerJavaDoc

接著,即可將監聽器新增至廣告載入器:

Kotlin

var listener  = AdsLoaderListener();
// Add the listener to the ads loader to receive ad loader events.
hlsInterstitialsAdsLoader.addListener(listener);

Java

AdsLoaderListener listener = new AdsLoaderListener();
// Add the listener to the ads loader to receive ad loader events.
hlsInterstitialsAdsLoader.addListener(listener);

HlsInterstitialsAdsLoader 生命週期

HlsInterstitialsAdsLoaderHlsInterstitialsAdsLoader.AdsMediaSourceFactory 的執行個體可重複用於多個播放器執行個體,這些執行個體會建立多個媒體來源,必須載入廣告。

舉例來說,您可以在 ActivityonCreate 方法中建立執行個體,然後重複用於多個播放器執行個體。只要同時只有一個播放器執行個體使用,這項功能就能正常運作。如果應用程式進入背景,播放器例項就會遭到刪除,然後在應用程式再次進入前景時建立新例項,這時這個方法就非常實用。

Kotlin

// Create the ads loader instance (for example onCreate).
hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context);

// Build a player and set it on the ads loader (for example onStart).
player = ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);

// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);

// Build another player and set it on the ads loader (for example onStart).
player = ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);

// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);

// Release the ads loader when not used anymore  (for example onDestroy).
hlsInterstitialsAdsLoader.release();

Java

// Create the ads loader instance (for example onCreate).
hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context);

// Build a player and set it on the ads loader (for example onStart).
player = new ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);

// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);

// Build another player and set it on the ads loader (for example onStart).
player = new ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);

// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);

// Release the ads loader when not used anymore  (for example onDestroy).
hlsInterstitialsAdsLoader.release();

一般來說,請務必先釋出舊的播放器例項,再於廣告載入器上設定下一個播放器例項。廣告載入器本身發布後,就無法再使用廣告載入器。

自訂播放設定

ExoPlayer 提供多種方式,讓您根據應用程式需求調整播放體驗。如需範例,請參閱自訂頁面

停用無區塊準備作業

根據預設,ExoPlayer 會使用不分區塊準備。也就是說,ExoPlayer 只會使用多變體播放清單中的資訊準備串流,如果 #EXT-X-STREAM-INF 標記包含 CODECS 屬性,這項做法就適用。

如果媒體區段包含未在多變體播放清單中以 #EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS 標記宣告的多工處理隱藏式輔助字幕軌,您可能需要停用這項功能。否則系統不會偵測到這些隱藏式輔助字幕軌,也不會播放。您可以在 HlsMediaSource.Factory 中停用無區塊準備作業,如以下程式碼片段所示。請注意,這會增加啟動時間,因為 ExoPlayer 需要下載媒體片段才能探索這些額外軌道,因此最好改為在多變體播放清單中宣告隱藏式輔助字幕軌。

Kotlin

val hlsMediaSource =
  HlsMediaSource.Factory(dataSourceFactory)
    .setAllowChunklessPreparation(false)
    .createMediaSource(MediaItem.fromUri(hlsUri))

Java

HlsMediaSource hlsMediaSource =
    new HlsMediaSource.Factory(dataSourceFactory)
        .setAllowChunklessPreparation(false)
        .createMediaSource(MediaItem.fromUri(hlsUri));

製作高品質 HLS 內容

為充分發揮 ExoPlayer 的效用,建議您遵循特定規範,提升 HLS 內容品質。如需完整說明,請參閱我們在 Medium 上發布的 ExoPlayer 播放 HTTP 即時串流文章。主要內容如下:

  • 使用精確的片段時間長度。
  • 使用連續媒體串流,避免在各個區隔中變更媒體結構。
  • 使用 #EXT-X-INDEPENDENT-SEGMENTS 標記。
  • 請優先使用解多工串流,而非同時包含影片和音訊的檔案。
  • 請在多變數播放清單中加入所有資訊。

直播須符合下列規範:

  • 使用 #EXT-X-PROGRAM-DATE-TIME 標記。
  • 使用 #EXT-X-DISCONTINUITY-SEQUENCE 標記。
  • 提供較長的直播時間範圍。建議至少一分鐘。