Настройка

В основе библиотеки 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 :

Котлин

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()

Ява

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:

Котлин

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time request headers.
    dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri))
  }

Ява

    DataSource.Factory dataSourceFactory =
        new ResolvingDataSource.Factory(
            httpDataSourceFactory,
            // Provide just-in-time request headers.
            dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));

Вы также можете использовать ResolvingDataSource для своевременного изменения URI, как показано в следующем фрагменте:

Котлин

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time URI resolution logic.
    dataSpec.withUri(resolveUri(dataSpec.uri))
  }

Ява

DataSource.Factory dataSourceFactory =
    new ResolvingDataSource.Factory(
        httpDataSourceFactory,
        // Provide just-in-time URI resolution logic.
        dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));

Настройка обработки ошибок

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

Котлин

val loadErrorHandlingPolicy: LoadErrorHandlingPolicy =
  object : DefaultLoadErrorHandlingPolicy() {
    override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo): Long {
      // Implement custom back-off logic here.
      return 0
    }
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
    )
    .build()

Ява

LoadErrorHandlingPolicy loadErrorHandlingPolicy =
    new DefaultLoadErrorHandlingPolicy() {
      @Override
      public long getRetryDelayMsFor(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 по индексу.

Котлин

val extractorsFactory =
  DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING)
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory))
    .build()

Ява

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 .

Котлин

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Ява

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);

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

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

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

Асинхронная буферная очередь включена по умолчанию на устройствах под управлением Android 12 (уровень API 31) и более поздних версий и может быть включена вручную, начиная с Android 6.0 (уровень API 23). Рассмотрите возможность включения этой функции для определенных устройств, на которых вы наблюдаете пропадание кадров или прерывание звука, особенно при воспроизведении контента с защитой DRM или контента с высокой частотой кадров.

В простейшем случае вам нужно внедрить плееру DefaultRenderersFactory следующим образом:

Котлин

val renderersFactory = 
  DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing()
val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()

Ява

DefaultRenderersFactory renderersFactory =
    new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing();
ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();

Если вы создаете экземпляры средств визуализации напрямую, передайте AsynchronousMediaCodecAdapter.Factory конструкторам MediaCodecVideoRenderer и MediaCodecAudioRenderer .

Настройка операций с помощью ForwardingSimpleBasePlayer

Вы можете настроить некоторые параметры поведения экземпляра Player , обернув его подклассом ForwardingSimpleBasePlayer . Этот класс позволяет вам перехватывать определенные «операции» вместо того, чтобы напрямую реализовывать методы Player . Это обеспечивает согласованное поведение, например, play() , pause() и setPlayWhenReady(boolean) . Это также гарантирует правильное распространение всех изменений состояния на зарегистрированные экземпляры Player.Listener . В большинстве случаев использования ForwardingSimpleBasePlayer следует отдавать предпочтение перед более подверженным ошибкам ForwardingPlayer из-за этих гарантий согласованности.

Например, чтобы добавить некоторую пользовательскую логику при запуске или остановке воспроизведения:

Котлин

class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady)
  }
}

Ява

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 неактивен):

Котлин

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.
}

Ява

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.
}

Настройка медиаисточника

В приведенных выше примерах внедряются настроенные компоненты для использования во время воспроизведения всех объектов MediaItem , передаваемых проигрывателю. Там, где требуется детальная настройка, также можно внедрить настроенные компоненты в отдельные экземпляры MediaSource , которые можно передать непосредственно проигрывателю. В приведенном ниже примере показано, как настроить ProgressiveMediaSource для использования пользовательских DataSource.Factory , ExtractorsFactory и LoadErrorHandlingPolicy :

Котлин

val mediaSource =
  ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
    .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
    .createMediaSource(MediaItem.fromUri(streamUri))

Ява

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