自定义

ExoPlayer 库的核心是 Player 接口。Player 公开了传统的、高级别的媒体播放器功能,例如缓冲媒体、播放、暂停和查找的能力。默认实现 ExoPlayer 旨在对所播放媒体的类型、存储和渲染方式以及存储位置做出很少假设(因此施加很少限制)。ExoPlayer 实现不直接实现媒体的加载和渲染,而是将这项工作委托给在创建播放器或向播放器传递新媒体源时注入的组件。所有 ExoPlayer 实现的通用组件是:

  • MediaSource 实例,它们定义要播放的媒体、加载媒体,并从中读取加载的媒体。MediaSource 实例由播放器内的 MediaSource.FactoryMediaItem 创建。它们也可以使用基于媒体源的播放列表 API 直接传递给播放器。
  • MediaSource.Factory 实例,它将 MediaItem 转换为 MediaSourceMediaSource.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 参数包含有关失败加载的更多信息,以便根据错误类型或失败的请求自定义逻辑。

自定义提取器标志

提取器标志可用于自定义如何从渐进式媒体中提取单个格式。它们可以在提供给 DefaultMediaSourceFactoryDefaultExtractorsFactory 上设置。以下示例传递了一个标志,该标志为 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 传递给 MediaCodecVideoRendererMediaCodecAudioRenderer 构造函数。

使用 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.FactoryExtractorsFactoryLoadErrorHandlingPolicy

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 将其发送到组件来传递配置更改。发送要在播放线程上传递的消息可确保它们与播放器上执行的任何其他操作按顺序执行。