HLS

ExoPlayer 支持采用多种容器格式的 HLS。所含音频和视频样本格式也必须受支持(有关详情,请参阅样本格式部分)。我们强烈建议 HLS 内容制作方生成高质量的 HLS 流,如这篇博文中所述。

功能 支持 评论
容器
MPEG-TS
FMP4/CMAF
ADTS (AAC)
MP3
字幕
CEA-608
CEA-708
WebVTT
元数据
ID3
SCTE-35
内容保护
AES-128
Sample AES-128
Widevine API 19 及更高版本(“cenc”方案)和 25 及更高版本(“cbcs”方案)
PlayReady SL2000 仅分发至 Android TV
服务器控制
增量更新
阻止播放列表重新加载
阻止加载预加载提示 长度未定义的字节范围除外
广告插播
服务器引导的广告插播(插页式广告) 部分 仅限包含 X-ASSET-URI 的 VOD。 直播和X-ASSET-LIST将在稍后添加。
IMA 服务器端广告和客户端广告 广告插播指南
直播
常规直播播放
低延迟 HLS (Apple)
低延迟 HLS(社区)
通用媒体客户端数据 CMCD CMCD 集成指南

使用 MediaItem

如需播放 HLS 直播,您需要依赖于 HLS 模块。

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 流

HLS 规范定义了 HLS 插播广告,可用于在媒体播放列表中添加插播广告信息。ExoPlayer 默认会忽略这些插播广告。您可以使用 HlsInterstitialsAdsLoader 添加支持。我们不会从一开始就支持规范的所有功能。如果您发现自己的直播缺少支持,请在 GitHub 上提交问题并向我们发送您的直播 URI,以便我们为您的直播添加支持。

MediaItem 与 Playlist API 搭配使用

使用插播广告播放 HLS 直播的最便捷方式是使用 HlsInterstitialsAdsLoader.AdsMediaSourceFactory 构建 ExoPlayer 实例。 这样一来,便可以使用 Player 接口基于 MediaItem播放列表 API 来播放 HLS 插播广告。

构建播放器实例时,可以将 ExoPlayerMediaSource.Factory 注入到构建器中:

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);

在这种播放器设置下,播放 HLS 插页式广告只需在播放器上设置一个带有 AdsConfiguration 的媒体项:

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 实例。为了支持插页式广告,应用随后可以直接使用 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 lifecycle

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 内容。如需了解完整说明,请参阅我们关于 ExoPlayer 中 HLS 播放的 Medium 博文。主要内容如下:

  • 使用精确的细分时长。
  • 使用连续的媒体流;避免在各个片段之间更改媒体结构。
  • 使用 #EXT-X-INDEPENDENT-SEGMENTS 标记。
  • 偏好于解复用的流,而不是同时包含视频和音频的文件。
  • 在多变体播放列表中尽可能包含所有信息。

以下准则专门针对直播活动:

  • 使用 #EXT-X-PROGRAM-DATE-TIME 标记。
  • 使用 #EXT-X-DISCONTINUITY-SEQUENCE 标记。
  • 提供较长的直播窗口。一分钟或更长时间效果更佳。