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