Настройка

В основе библиотеки ExoPlayer лежит интерфейс Player. Player предоставляет традиционные функции медиапроигрывателя, такие как буферизация, воспроизведение, пауза и перемотка. Реализация по умолчанию ExoPlayer разработана так, чтобы не накладывать ограничений на тип воспроизводимого медиаконтента, способ и место его хранения, а также способ его отрисовки. Вместо того чтобы реализовывать загрузку и воспроизведение медиаконтента напрямую, реализации ExoPlayer делегируют эту работу компонентам, которые внедряются при создании проигрывателя или при передаче ему новых источников медиаконтента. Общие компоненты для всех реализаций ExoPlayer:

  • MediaSource, которые определяют, какой медиаконтент нужно воспроизвести, загружают его и позволяют его считывать. Экземпляр MediaSource создается из MediaItem с помощью MediaSource.Factory в проигрывателе. Их также можно передавать непосредственно в проигрыватель с помощью API плейлистов на основе источников медиаконтента.
  • Экземпляр MediaSource.Factory, который преобразует MediaItem в MediaSource. Объект MediaSource.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 включает заголовок "Header: Value" в каждый HTTP-запрос. Это поведение фиксировано для каждого взаимодействия с источником 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: LoadErrorHandlingPolicy.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(LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo) {
        // Implement custom back-off logic here.
        return 0;
      }
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context)
                .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy))
        .build();

Аргумент LoadErrorInfo содержит дополнительную информацию о неудачной загрузке, которая позволяет настроить логику в зависимости от типа ошибки или неудачного запроса.

Настройка флагов экстрактора

Флаги Extractor можно использовать для настройки извлечения отдельных форматов из прогрессивных медиа. Их можно задать в объекте DefaultExtractorsFactory, который передается в DefaultMediaSourceFactory. В примере ниже передается флаг, который включает поиск на основе индекса для потоков 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);

Затем ExtractorsFactory можно внедрить с помощью DefaultMediaSourceFactory, как описано выше в разделе о настройке флагов экстрактора.

Включение асинхронной очереди буфера

Асинхронная очередь буфера – это улучшение в конвейере рендеринга 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();

Если вы создаете экземпляры средств визуализации напрямую, передайте new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous() в конструкторы 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

public static final 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

public static final 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 позволяет приложению настраивать способ создания MediaSource из MediaItem.
  • DataSource – в пакете ExoPlayer уже есть несколько реализаций DataSource для разных сценариев использования. Вы можете реализовать собственный класс DataSource, чтобы загружать данные другим способом, например по специальному протоколу, с помощью специального стека HTTP или из специального постоянного кеша.

При создании пользовательских компонентов рекомендуем следующее:

  • Если специальный компонент должен передавать события в приложение, рекомендуем использовать ту же модель, что и в существующих компонентах ExoPlayer, например классы EventDispatcher или передачу Handler вместе с прослушивателем в конструктор компонента.
  • Мы рекомендуем использовать в пользовательских компонентах ту же модель, что и в существующих компонентах ExoPlayer, чтобы приложение могло менять конфигурацию во время воспроизведения. Для этого специальные компоненты должны реализовывать PlayerMessage.Target и получать изменения конфигурации в методе handleMessage. Код приложения должен передавать изменения конфигурации, вызывая метод createMessage ExoPlayer, настраивая сообщение и отправляя его компоненту с помощью PlayerMessage.send. Отправка сообщений, которые должны быть доставлены в поток воспроизведения, гарантирует, что они будут выполнены по порядку вместе с любыми другими операциями, выполняемыми в проигрывателе.