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);
ExtractorsFactory
然后可以通过上面描述的自定义提取器标志的方式通过 DefaultMediaSourceFactory
注入。
启用异步缓冲队列
异步缓冲队列是 ExoPlayer 渲染管道的增强功能,它在 异步模式 下操作 MediaCodec
实例,并使用额外的线程来调度数据的解码和渲染。启用它可以减少丢帧和音频欠载。
异步缓冲队列在运行 Android 12(API 级别 31)及更高版本的设备上默认启用,并且从 Android 6.0(API 级别 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
构造函数。
使用 ForwardingPlayer
拦截方法调用
您可以通过将 Player
实例包装在一个 ForwardingPlayer
的子类中并覆盖方法来自定义 Player
实例的一些行为,以便执行以下任一操作
- 在将参数传递给委托
Player
之前访问参数。 - 在返回之前访问委托
Player
的返回值。 - 完全重新实现该方法。
覆盖 ForwardingPlayer
方法时,务必确保实现保持自一致且符合 Player
接口,尤其是在处理旨在具有相同或相关行为的方法时。例如
- 如果您想覆盖每个“播放”操作,您需要覆盖
ForwardingPlayer.play
和ForwardingPlayer.setPlayWhenReady
,因为调用者希望这些方法的行为在playWhenReady = true
时相同。 - 如果您想更改向前寻求增量,您需要覆盖
ForwardingPlayer.seekForward
以使用您的自定义增量执行寻求,并覆盖ForwardingPlayer.getSeekForwardIncrement
以将正确的自定义增量报告回调用者。 - 如果您想控制播放器实例广告的
Player.Commands
,您必须覆盖Player.getAvailableCommands()
和Player.isCommandAvailable()
,并监听Player.Listener.onAvailableCommandsChanged()
回调以获取有关来自底层播放器的更改的通知。
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
来传递配置更改。发送要在线程上交付的消息可确保它们与在播放器上执行的任何其他操作按顺序执行。