网络堆栈

ExoPlayer 通常用于通过互联网流式传输媒体内容。它支持使用多个网络堆栈发出其底层网络请求。您选择的网络堆栈会对流式传输性能产生重大影响。

本页概述了如何配置 ExoPlayer 以使用您选择的网络堆栈,列出了可用的选项,提供了一些有关如何为应用选择网络堆栈的指导,并说明了如何为流式媒体启用缓存。

配置 ExoPlayer 以使用特定的网络堆栈

ExoPlayer 通过 DataSource 组件加载数据,该组件会从从应用代码注入的 DataSource.Factory 实例中获取。

如果您的应用只需要播放 http(s) 内容,那么选择网络堆栈非常简单,只需将应用注入的所有 DataSource.Factory 实例更新为与您要使用的网络堆栈对应的 HttpDataSource.Factory 实例即可。如果您的应用还需要播放非 http(s) 内容(如本地文件),请使用 DefaultDataSource.Factory

Kotlin

DefaultDataSource.Factory(
  ...
  /* baseDataSourceFactory= */ PreferredHttpDataSource.Factory(...))

Java

new DefaultDataSource.Factory(
    ...
    /* baseDataSourceFactory= */ new PreferredHttpDataSource.Factory(...));

在此示例中,PreferredHttpDataSource.Factory 是与您的首选网络堆栈对应的出厂设置。DefaultDataSource.Factory 层增加了对非 http(s) 来源(例如本地文件)的支持。

以下示例展示了如何构建一个 ExoPlayer,它不仅使用 Cronet 网络堆栈,还支持播放非 http(s) 内容。

Kotlin

// Given a CronetEngine and Executor, build a CronetDataSource.Factory.
val cronetDataSourceFactory = CronetDataSource.Factory(cronetEngine, executor)

// Wrap the CronetDataSource.Factory in a DefaultDataSource.Factory, which adds
// in support for requesting data from other sources (such as files, resources,
// etc).
val dataSourceFactory =
  DefaultDataSource.Factory(context, /* baseDataSourceFactory= */ cronetDataSourceFactory)

// Inject the DefaultDataSource.Factory when creating the player.
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
    )
    .build()

Java

// Given a CronetEngine and Executor, build a CronetDataSource.Factory.
CronetDataSource.Factory cronetDataSourceFactory =
    new CronetDataSource.Factory(cronetEngine, executor);

// Wrap the CronetDataSource.Factory in a DefaultDataSource.Factory, which adds
// in support for requesting data from other sources (such as files, resources,
// etc).
DefaultDataSource.Factory dataSourceFactory =
    new DefaultDataSource.Factory(
        context, /* baseDataSourceFactory= */ cronetDataSourceFactory);

// Inject the DefaultDataSource.Factory when creating the player.
ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory))
        .build();

支持的网络堆栈

ExoPlayer 直接支持 Cronet、OkHttp 和 Android 的内置网络堆栈。您还可扩展 ExoPlayer 以支持适用于 Android 的任何其他网络堆栈。

Cronet

Cronet 是以库的形式提供给 Android 应用使用的 Chromium 网络堆栈。Cronet 利用多种技术来减少延迟并提高应用正常运行所需的网络请求吞吐量,包括 ExoPlayer 发出的请求。它原生支持基于 QUIC 协议的 HTTP、HTTP/2 和 HTTP/3。全球一些大型的在线影音应用都在使用 Cronet,包括 YouTube。

ExoPlayer 通过其 Cronet 库支持 Cronet。如需详细了解相关用法,请参阅库的 README.md。请注意,Cronet 库能够使用三种底层 Cronet 实现:

  1. Google Play 服务:我们建议在大多数情况下使用此实现;如果 Google Play 服务不可用,建议回退到 Android 的内置网络堆栈 (DefaultHttpDataSource)。
  2. 嵌入式 Cronet:如果很大一部分用户所在的市场尚未广泛提供 Google Play 服务,或者您想要控制所使用的 Cronet 实现的确切版本,这可能是不错的选择。嵌入式 Cronet 的主要缺点是它会为应用增加大约 8MB 的大小。
  3. Cronet 回退:Cronet 的回退实现会实现 Cronet 的 API 作为 Android 内置网络堆栈的封装容器。它不应与 ExoPlayer 一起使用,因为直接使用 Android 的内置网络堆栈(通过使用 DefaultHttpDataSource)更高效。

OkHttp

OkHttp 是另一种现代网络堆栈,被许多热门 Android 应用广泛使用。它支持 HTTP 和 HTTP/2,但尚不支持基于 QUIC 的 HTTP/3。

ExoPlayer 通过其 OkHttp 库支持 OkHttp。如需详细了解相关用法,请参阅库的 README.md。使用 OkHttp 库时,网络堆栈会嵌入到应用内。这与 Cronet Embedded 类似,但 OkHttp 要小得多,为应用增加不到 1 MB。

Android 的内置网络堆栈

ExoPlayer 支持将 Android 的内置网络堆栈与 DefaultHttpDataSourceDefaultHttpDataSource.Factory 搭配使用,它们是核心 ExoPlayer 库的一部分。

确切的网络堆栈实现取决于底层设备上运行的软件。大多数设备(从 2021 年开始)仅支持 HTTP(即不支持基于 QUIC 的 HTTP/2 和 HTTP/3)。

其他网络堆栈

应用还可以将其他网络堆栈与 ExoPlayer 集成。为此,请实现封装网络堆栈的 HttpDataSource 以及相应的 HttpDataSource.Factory。ExoPlayer 的 Cronet 和 OkHttp 库就是关于如何执行此操作的很好示例。

与纯 Java 网络堆栈集成时,最好实现 DataSourceContractTest 以检查 HttpDataSource 实现是否正常运行。OkHttp 库中的 OkHttpDataSourceContractTest 就是一个很好的示例。

选择网络堆栈

下表概述了 ExoPlayer 支持的网络堆栈的优缺点。

网络堆栈 协议 对 APK 大小的影响 备注
Cronet(Google Play 服务) HTTP
HTTP/2
基于 QUIC 的 HTTP/3

(<100KB)
需要 Google Play 服务。Cronet 版本自动更新
Cronet(嵌入式) HTTP
HTTP/2
基于 QUIC 的 HTTP/3

(约 8MB)
由应用开发者控制的 Cronet 版本
Cronet(后备) HTTP
(因设备而异)

(<100KB)
不推荐用于 ExoPlayer
OkHttp HTTP
HTTP/2

(<1MB)
需要 Kotlin 运行时
内置网络堆栈 HTTP
(因设备而异)
实现因设备而异

基于 QUIC 协议的 HTTP/2 和 HTTP/3 可以显著提高媒体流式传输的性能。特别是,在流式传输使用内容分发网络 (CDN) 分发的自适应媒体时,在某些情况下,使用这些协议可让 CDN 更高效地运行。因此,与使用 Android 内置网络堆栈相比,Cronet 同时支持通过 QUIC 使用 HTTP/2 和 HTTP/3(以及 OkHttp 对 HTTP/2 的支持)是一个主要优势,前提是托管内容的服务器也支持这些协议。

在单独考虑媒体流式传输时,我们建议使用 Google Play 服务提供的 Cronet;如果 Google Play 服务不可用,则回退到 DefaultHttpDataSource。此建议可在大多数设备上通过 QUIC 启用 HTTP/2 和 HTTP/3,同时避免 APK 大小显著增加,从而实现很好的平衡。此项建议也有例外情况。如果大部分将运行应用的设备都可能无法使用 Google Play 服务,那么使用 Cronet Embedded 或 OkHttp 可能更合适。如果 APK 大小对您来说很重要,或者媒体流式传输只是应用功能的一小部分,那么使用内置网络堆栈可能是可以接受的。

除了媒体之外,通常还建议您为应用执行的所有网络选择单个网络堆栈。这样可以在 ExoPlayer 与其他应用组件之间高效地池化和共享资源(例如套接字)。

由于您的应用很可能需要执行与媒体播放无关的网络,因此您选择的网络堆栈最终应考虑上述单独媒体流式传输的建议、执行网络的任何其他组件的要求以及它们对应用的相对重要性。

缓存媒体

ExoPlayer 支持将已加载的字节缓存到磁盘,以防止从网络重复加载相同的字节。在当前媒体中快退或重复同一项时,这非常有用。

缓存需要指向专用缓存目录的 SimpleCache 实例和 CacheDataSource.Factory

Kotlin

// Note: This should be a singleton in your app.
val databaseProvider = StandaloneDatabaseProvider(context)

// An on-the-fly cache should evict media when reaching a maximum disk space limit.
val cache =
    SimpleCache(
        downloadDirectory, LeastRecentlyUsedCacheEvictor(maxBytes), databaseProvider)

// Configure the DataSource.Factory with the cache and factory for the desired HTTP stack.
val cacheDataSourceFactory =
    CacheDataSource.Factory()
        .setCache(cache)
        .setUpstreamDataSourceFactory(httpDataSourceFactory)

// Inject the DefaultDataSource.Factory when creating the player.
val player =
    ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory))
        .build()

Java

// Note: This should be a singleton in your app.
DatabaseProvider databaseProvider = new StandaloneDatabaseProvider(context);

// An on-the-fly cache should evict media when reaching a maximum disk space limit.
Cache cache =
    new SimpleCache(
        downloadDirectory, new LeastRecentlyUsedCacheEvictor(maxBytes), databaseProvider);

// Configure the DataSource.Factory with the cache and factory for the desired HTTP stack.
DataSource.Factory cacheDataSourceFactory =
    new CacheDataSource.Factory()
        .setCache(cache)
        .setUpstreamDataSourceFactory(httpDataSourceFactory);

// Inject the DefaultDataSource.Factory when creating the player.
ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory))
        .build();