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 模块。
implementation("androidx.media3:media3-exoplayer-hls:1.6.0")
implementation "androidx.media3:media3-exoplayer-hls:1.6.0"
然后,您可以为 HLS 播放列表 URI 创建 MediaItem
,并将其传递给播放器。
// 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.Builder
的 setMimeType
,以明确指明内容类型。
媒体内容的 URI 可以指向媒体播放列表或多变体播放列表。如果 URI 指向声明多个 #EXT-X-STREAM-INF
标记的多变体播放列表,则 ExoPlayer 会自动在变体之间进行自适应,同时考虑可用带宽和设备功能。
使用 HlsMediaSource
如需更多自定义选项,您可以创建 HlsMediaSource
并将其直接传递给播放器,而不是 MediaItem
。
// 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.Listener
的 onTimelineChanged
回调。对于点播内容,系统会执行一次此操作;对于直播内容,系统可能会执行多次此操作。以下代码段展示了应用如何在每次加载清单时执行操作。
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 插页式广告。
在构建播放器实例时,可以将 ExoPlayer
的 MediaSource.Factory
注入到构建器中:
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
设置媒体内容即可:
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:
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 便可以跟踪播放的广告、正在加载的素材资源列表、正在准备的广告媒体来源,或检测素材资源列表加载和广告准备错误。此外,您还可以接收广告媒体来源发出的元数据,以进行精细的广告播放验证或跟踪广告播放进度。
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.
}
}
然后,您可以将监听器添加到广告加载器:
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
一个 HlsInterstitialsAdsLoader
或 HlsInterstitialsAdsLoader.AdsMediaSourceFactory
实例可用于多个播放器实例,这些实例会创建多个媒体来源,并且必须为这些媒体来源加载广告。
例如,您可以在 Activity
的 onCreate
方法中创建实例,然后将其重复用于多个玩家实例。只要同时由单个玩家实例使用,这种方法就有效。对于以下常见用例,此方法非常有用:当应用进入后台时,销毁播放器实例,然后在应用再次进入前台时创建新实例。
// 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 需要下载媒体片段才能发现这些额外的轨道,因此最好改为在多变量播放列表中声明字幕轨道。
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
标记。 - 提供较长的直播时长。一分钟或更长时间就很棒了。