ExoPlayer 库的核心是 Player
接口。Player
公开了传统的、高级别的媒体播放器功能,例如缓冲媒体、播放、暂停和查找的能力。默认实现 ExoPlayer
旨在对所播放媒体的类型、存储和渲染方式以及存储位置做出很少假设(因此施加很少限制)。ExoPlayer
实现不直接实现媒体的加载和渲染,而是将这项工作委托给在创建播放器或向播放器传递新媒体源时注入的组件。所有 ExoPlayer
实现的通用组件是:
MediaSource
实例,它们定义要播放的媒体、加载媒体,并从中读取加载的媒体。MediaSource
实例由播放器内的MediaSource.Factory
从MediaItem
创建。它们也可以使用基于媒体源的播放列表 API 直接传递给播放器。MediaSource.Factory
实例,它将MediaItem
转换为MediaSource
。MediaSource.Factory
在创建播放器时注入。Renderer
实例,它们渲染媒体的各个组件。这些组件在创建播放器时注入。TrackSelector
,它选择MediaSource
提供的轨道,供每个可用Renderer
消费。TrackSelector
在创建播放器时注入。LoadControl
,它控制MediaSource
何时缓冲更多媒体以及缓冲多少媒体。LoadControl
在创建播放器时注入。LivePlaybackSpeedControl
,它在直播播放期间控制播放速度,以使播放器保持接近配置的直播偏移。在创建播放器时注入LivePlaybackSpeedControl
。
注入实现播放器功能部件的组件的概念贯穿整个库。某些组件的默认实现将工作委托给进一步注入的组件。这允许许多子组件被单独替换为以自定义方式配置的实现。
播放器自定义
下面描述了一些通过注入组件自定义播放器的常见示例。
配置网络堆栈
我们有一个关于自定义 ExoPlayer 使用的网络堆栈的页面。
缓存从网络加载的数据
自定义服务器交互
某些应用可能希望拦截 HTTP 请求和响应。您可能希望注入自定义请求头、读取服务器的响应头、修改请求的 URI 等。例如,您的应用可以通过在请求媒体段时将令牌作为头注入来验证自身。
以下示例演示了如何通过将自定义 DataSource.Factory
注入 DefaultMediaSourceFactory
来实现这些行为
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
参数包含有关失败加载的更多信息,以便根据错误类型或失败的请求自定义逻辑。
自定义提取器标志
提取器标志可用于自定义如何从渐进式媒体中提取单个格式。它们可以在提供给 DefaultMediaSourceFactory
的 DefaultExtractorsFactory
上设置。以下示例传递了一个标志,该标志为 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 level 31) 及更高版本的设备上默认启用,并且可以从 Android 6.0 (API level 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
传递给 MediaCodecVideoRenderer
和 MediaCodecAudioRenderer
构造函数。
使用 ForwardingSimpleBasePlayer
自定义操作
您可以通过将 Player
实例包装在 ForwardingSimpleBasePlayer
的子类中来自定义其某些行为。此类允许您拦截特定的“操作”,而无需直接实现 Player
方法。这确保了例如 play()
、pause()
和 setPlayWhenReady(boolean)
的一致行为。它还确保所有状态更改都正确传播到已注册的 Player.Listener
实例。对于大多数自定义用例,由于这些一致性保证,ForwardingSimpleBasePlayer
应该优于更容易出错的 ForwardingPlayer
。
例如,在播放开始或停止时添加一些自定义逻辑
Kotlin
class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady) } }
Java
class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer { public PlayerWithCustomPlay(Player player) { super(player); } @Override protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady); } }
或者禁止 SEEK_TO_NEXT
命令(并确保 Player.seekToNext
是空操作)
Kotlin
class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) { override fun getState(): State { val state = super.getState() return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build() ) .build() } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
Java
class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer { public PlayerWithoutSeekToNext(Player player) { super(player); } @Override protected State getState() { State state = super.getState(); return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()) .build(); } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
MediaSource 自定义
上面的示例注入了自定义组件,用于播放传递给播放器的所有 MediaItem
对象。如果需要细粒度自定义,也可以将自定义组件注入到单个 MediaSource
实例中,这些实例可以直接传递给播放器。下面的示例展示了如何自定义 ProgressiveMediaSource
以使用自定义的 DataSource.Factory
、ExtractorsFactory
和 LoadErrorHandlingPolicy
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
将其发送到组件来传递配置更改。发送要在播放线程上传递的消息可确保它们与播放器上执行的任何其他操作按顺序执行。