Стеки протоколов

ExoPlayer часто используется для потоковой передачи медиаконтента через интернет. Он поддерживает несколько сетевых стеков для выполнения базовых сетевых запросов. Выбор сетевого стека может существенно повлиять на производительность потоковой передачи.

На этой странице рассказывается, как настроить ExoPlayer для использования выбранного вами сетевого стека, перечислены доступные варианты, приведены рекомендации по выбору сетевого стека для вашего приложения и объясняется, как включить кеширование для потокового мультимедиа.

Как настроить ExoPlayer для использования определенного сетевого стека

ExoPlayer загружает данные с помощью компонентов DataSource, которые получает от экземпляров DataSource.Factory, внедренных из кода приложения.

Если приложению нужно только воспроизводить контент с протоколом HTTP/S, выбрать сетевой стек можно, просто обновив все экземпляры DataSource.Factory, которые приложение внедряет, до экземпляров HttpDataSource.Factory, соответствующих нужному сетевому стеку. Если приложению также нужно воспроизводить контент, не относящийся к протоколам HTTP(S), например локальные файлы, используйте DefaultDataSource.Factory:

Kotlin

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

Java

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

В этом примере 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 поддерживает HttpEngine, Cronet, OkHttp и встроенный в Android сетевой стек по умолчанию. Кроме того, ExoPlayer можно расширить, чтобы он поддерживал любой другой сетевой стек, работающий на Android.

HttpEngine

HttpEngine – рекомендуемый сетевой стек по умолчанию в Android начиная с API 34 (или расширений S 7). В большинстве случаев он использует внутренний сетевой стек Cronet, поддерживающий протоколы HTTP, HTTP/2 и HTTP/3 поверх QUIC.

ExoPlayer поддерживает HttpEngine с помощью HttpEngineDataSource.Factory. Вы можете внедрить фабрику источников данных, как описано в разделе Настройка ExoPlayer для использования определенного сетевого стека.

Cronet

Cronet – это сетевой стек Chromium, доступный для приложений Android в виде библиотеки. Cronet использует несколько технологий, которые позволяют снизить задержку и повысить пропускную способность сетевых запросов, необходимых для работы вашего приложения, в том числе запросов, созданных ExoPlayer. Он изначально поддерживает протоколы HTTP, HTTP/2 и HTTP/3 over QUIC. Cronet используется в некоторых крупнейших стриминговых приложениях, в том числе YouTube.

ExoPlayer поддерживает Cronet через библиотеку Cronet. Подробные инструкции по использованию библиотеки приведены в ее README.md. Библиотека Cronet может использовать три базовые реализации Cronet:

  1. Сервисы Google Play. Мы рекомендуем использовать эту реализацию в большинстве случаев и переходить на встроенный сетевой стек Android (DefaultHttpDataSource), если сервисы Google Play недоступны.
  2. Встроенная библиотека Cronet. Этот вариант может подойти, если значительная часть ваших пользователей находится в регионах, где сервисы Google Play не распространены, или если вы хотите контролировать версию используемой реализации Cronet. Основной недостаток встроенной версии Cronet заключается в том, что она добавляет к приложению около 8 МБ.
  3. Резервная реализация Cronet. В резервной реализации API Cronet используется в качестве оболочки для встроенного сетевого стека Android. Его не следует использовать с ExoPlayer, поскольку прямое использование встроенного сетевого стека Android (с помощью DefaultHttpDataSource) более эффективно.

OkHttp

OkHttp – ещё один современный сетевой стек, который широко используется во многих популярных приложениях для Android. Он поддерживает HTTP и HTTP/2, но пока не поддерживает HTTP/3 через QUIC.

ExoPlayer поддерживает OkHttp через библиотеку OkHttp. Подробные инструкции по использованию библиотеки приведены в ее README.md. При использовании библиотеки OkHttp сетевой стек встроен в приложение. Это похоже на Cronet Embedded, но OkHttp значительно меньше и добавляет в приложение менее 1 МБ.

Встроенный сетевой стек Android

ExoPlayer поддерживает использование встроенного в Android сетевого стека с помощью классов DefaultHttpDataSource и DefaultHttpDataSource.Factory, которые входят в основную библиотеку ExoPlayer.

Реализация стека зависит от программного обеспечения, установленного на устройстве. На большинстве устройств поддерживается только HTTP (то есть HTTP/2 и HTTP/3 через QUIC не поддерживаются).

Другие сетевые стеки

Приложения также могут интегрировать с ExoPlayer другие сетевые стеки. Для этого реализуйте HttpDataSource, который будет обертывать сетевой стек, а также соответствующий HttpDataSource.Factory. Примеры того, как это можно сделать, приведены в библиотеках Cronet и OkHttp для ExoPlayer.

При интеграции с сетевым стеком на основе Java рекомендуется реализовать DataSourceContractTest, чтобы проверить, правильно ли работает HttpDataSource. OkHttpDataSourceContractTest в библиотеке OkHttp – хороший пример того, как это можно сделать.

Как выбрать сетевой стек

В таблице ниже перечислены преимущества и недостатки сетевых стеков, поддерживаемых ExoPlayer.

Сетевой стек Протоколы Влияние на размер APK-файла Примечания
HttpEngine HTTP
HTTP/2
HTTP/3 через QUIC
Нет Доступно только в API 34 или S Extensions 7
Cronet (сервисы Google Play) HTTP
HTTP/2
HTTP/3 через QUIC
Маленький
(<100 КБ)
Требуются сервисы Google Play. Версия Cronet обновляется автоматически
Cronet (встроенный) HTTP
HTTP/2
HTTP/3 через QUIC
Большой
(~8 МБ)
Версия Cronet, контролируемая разработчиком приложения
Cronet (резервный) HTTP
(зависит от устройства)
Маленький
(<100 КБ)
Не рекомендуется для ExoPlayer
OkHttp HTTP
HTTP/2
Маленький
(<1 МБ)
Встроенный сетевой стек HTTP
(зависит от устройства)
Нет Реализация зависит от устройства

Протоколы HTTP/2 и HTTP/3 поверх QUIC могут значительно повысить эффективность потоковой передачи медиаконтента. В частности, при потоковой передаче адаптивного медиаконтента, распространяемого с помощью сети доставки контента (CDN), использование этих протоколов может значительно повысить эффективность работы CDN. Поэтому поддержка HTTP/2 и HTTP/3 через QUIC в HttpEngine и Cronet (а также поддержка HTTP/2 в OkHttp) является большим преимуществом по сравнению со встроенным сетевым стеком Android, если серверы, на которых размещен контент, также поддерживают эти протоколы.

Если вы рассматриваете только потоковую передачу медиаконтента, мы рекомендуем использовать HttpEngine или Cronet, предоставляемые сервисами Google Play, с переходом на DefaultHttpDataSource, если сервисы Google Play недоступны. Это позволит использовать HTTP/2 и HTTP/3 через QUIC на большинстве устройств и при этом избежать значительного увеличения размера 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();