网络堆栈

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) 源(例如本地文件)的支持。

以下示例展示了如何构建一个将使用 Cronet 网络栈并支持播放非 http(s) 内容的 ExoPlayer

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 直接支持 HttpEngine、Cronet、OkHttp 和 Android 的内置默认网络栈。ExoPlayer 还可以扩展以支持 Android 上的任何其他网络栈。

HttpEngine

HttpEngine 是 API 34(或 S 扩展程序 7)及更高版本 Android 上推荐的默认网络栈。在大多数情况下,它在内部使用 Cronet 网络栈,支持通过 QUIC 协议的 HTTP、HTTP/2 和 HTTP/3。

ExoPlayer 通过其 HttpEngineDataSource.Factory 支持 HttpEngine。您可以按照配置 ExoPlayer 以使用特定的网络栈 中的说明注入此数据源工厂。

Cronet

Cronet 是 Chromium 网络栈,作为库提供给 Android 应用。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 嵌入式,但是 OkHttp 的体积明显更小,向您的应用添加不到 1MB。

Android 的内置网络栈

ExoPlayer 支持使用 Android 的内置网络栈,其 DefaultHttpDataSourceDefaultHttpDataSource.Factory 是核心 ExoPlayer 库的一部分。

确切的网络栈实现取决于底层设备上运行的软件。在大多数设备上,仅支持 HTTP(即,不支持通过 QUIC 的 HTTP/2 和 HTTP/3)。

其他网络栈

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

与纯 Java 网络栈集成时,最好实现一个 DataSourceContractTest 来检查您的 HttpDataSource 实现是否行为正确。OkHttp 库中的 OkHttpDataSourceContractTest 是如何执行此操作的良好示例。

选择网络栈

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

网络栈 协议 APK 大小影响 备注
HttpEngine HTTP
HTTP/2
HTTP/3 通过 QUIC
仅在 API 34 或 S 扩展程序 7 上可用
Cronet(Google Play 服务) HTTP
HTTP/2
HTTP/3 通过 QUIC

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

(~8MB)
Cronet 版本由应用开发者控制
Cronet(回退) HTTP
(取决于设备)

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

(<1MB)
内置网络栈 HTTP
(取决于设备)
实现取决于设备

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

仅考虑媒体流式传输时,我们建议使用 HttpEngine 或 Google Play 服务提供的 Cronet,如果 Google Play 服务不可用,则回退到 DefaultHttpDataSource。此建议在启用大多数设备上的 HTTP/2 和通过 QUIC 的 HTTP/3 与避免显着增加 APK 大小之间取得了良好的平衡。此建议也有例外。对于 Google Play 服务可能在将运行您的应用的大量设备上不可用的情况,使用 Cronet 嵌入式或 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();