ExoPlayer 库的核心是 Player
接口。Player
会公开传统的高级媒体播放器功能,例如缓冲媒体、播放、暂停和跳转。默认实现 ExoPlayer
旨在尽量少对播放的媒体类型、存储方式和位置以及呈现方式做出假设(因此对这些方面施加的限制也较少)。ExoPlayer
实现并非直接实现媒体的加载和渲染,而是将此工作委托给在创建播放器或将新媒体源传递给播放器时注入的组件。所有 ExoPlayer
实现都具有以下共同的组件:
- 用于定义要播放的媒体、加载媒体以及从中读取已加载媒体的
MediaSource
实例。播放器中的MediaSource.Factory
会通过MediaItem
创建MediaSource
实例。您还可以使用基于媒体来源的播放列表 API 将其直接传递给播放器。 - 用于将
MediaItem
转换为MediaSource
的MediaSource.Factory
实例。在创建播放器时注入MediaSource.Factory
。 - 用于渲染媒体的各个组件的
Renderer
实例。这些事件会在创建播放器时注入。 - 用于选择
MediaSource
提供的轨道以供每个可用Renderer
使用。TrackSelector
创建播放器时会注入TrackSelector
。 - 用于控制
MediaSource
何时缓冲更多媒体以及缓冲多少媒体的LoadControl
。在创建播放器时会注入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 级别 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
构造函数。
使用 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
将其发送到组件来传递配置更改。发送要在播放线程中传送的消息可确保这些消息与在播放器上执行的任何其他操作一起有序执行。