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