Настройка

В основе библиотеки 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, который управляет скоростью воспроизведения во время трансляций, чтобы проигрыватель не отставал от заданного смещения. A 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 содержит дополнительную информацию о сбое загрузки, чтобы вы могли настроить логику в зависимости от типа ошибки или неудачного запроса.

Как настроить флаги экстрактора

Флаги экстрактора можно использовать, чтобы настроить извлечение отдельных форматов из медиаконтента с прогрессивной загрузкой. Их можно задать на 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. Эти флаги можно задать для отдельных экстракторов, используя описанные выше методы individual 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. Отправка сообщений для доставки в потоке воспроизведения гарантирует, что они будут выполнены в порядке очереди с любыми другими операциями, выполняемыми в проигрывателе.