HLS

ExoPlayer 支持采用多种容器格式的 HLS。还必须支持其中包含的音频和视频选段格式(如需了解详情,请参阅选段格式部分)。我们强烈建议 HLS 内容生产者生成高质量的 HLS 串流,如这篇博文中所述。

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

使用 MediaItem

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

KotlinGroovy
implementation("androidx.media3:media3-exoplayer-hls:1.6.0")
implementation "androidx.media3:media3-exoplayer-hls:1.6.0"

然后,您可以为 HLS 播放列表 URI 创建 MediaItem,并将其传递给播放器。

KotlinJava
// 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()
// 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

KotlinJava
// 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()
// 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 回调。对于点播内容,系统会执行一次此操作;对于直播内容,系统可能会执行多次此操作。以下代码段展示了应用如何在每次加载清单时执行操作。

KotlinJava
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.
      }
    }
  }
)
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 与播放列表 API 搭配使用

如需播放包含插页式广告的 HLS 直播,最方便的方法是使用 HlsInterstitialsAdsLoader.AdsMediaSourceFactory 构建 ExoPlayer 实例。这样,您就可以使用 Player 接口基于 MediaItem播放列表 API 来播放 HLS 插页式广告。

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

KotlinJava
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)
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 设置媒体内容即可:

KotlinJava
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();
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:

KotlinJava
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()
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 便可以跟踪播放的广告、正在加载的素材资源列表、正在准备的广告媒体来源,或检测素材资源列表加载和广告准备错误。此外,您还可以接收广告媒体来源发出的元数据,以进行精细的广告播放验证或跟踪广告播放进度。

KotlinJava
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.
  }
}
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 of HlsInterstitialsAdsLoader.Listener.

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

然后,您可以将监听器添加到广告加载器:

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

HlsInterstitialsAdsLoader lifecycle

一个 HlsInterstitialsAdsLoaderHlsInterstitialsAdsLoader.AdsMediaSourceFactory 实例可用于多个播放器实例,这些实例会创建多个媒体来源,并且必须为这些媒体来源加载广告。

例如,您可以在 ActivityonCreate 方法中创建实例,然后将其重复用于多个玩家实例。只要同时由单个玩家实例使用,这种方法就有效。对于以下常见用例,此方法非常有用:当应用进入后台时,销毁播放器实例,然后在应用再次进入前台时创建新实例。

KotlinJava
// 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();
// 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 需要下载媒体片段才能发现这些额外的轨道,因此最好改为在多变量播放列表中声明字幕轨道。

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

制作高品质 HLS 内容

为了充分利用 ExoPlayer,您可以遵循一些准则来改进 HLS 内容。如需了解完整说明,请参阅我们的 Medium 博文“ExoPlayer 中的 HLS 播放”。主要要点如下:

  • 使用精确的片段时长。
  • 使用连续的媒体流;避免在各个片段中更改媒体结构。
  • 使用 #EXT-X-INDEPENDENT-SEGMENTS 标记。
  • 首选解复 mux 的串流,而不是同时包含视频和音频的文件。
  • 在多变体商品目录中添加尽可能多的信息。

以下指南专门适用于直播:

  • 使用 #EXT-X-PROGRAM-DATE-TIME 标记。
  • 使用 #EXT-X-DISCONTINUITY-SEQUENCE 标记。
  • 提供较长的直播时长。一分钟或更长时间就很棒了。