Основу библиотеки 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-источником.
Для более детального подхода можно внедрить JIT-поведение с помощью 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();
Если вы создаете экземпляры рендереров напрямую, передайте new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous()
конструкторам 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. }
Настройка MediaSource
В приведённых выше примерах реализовано внедрение настраиваемых компонентов для использования во время воспроизведения всех объектов 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
. Отправка сообщений в поток воспроизведения гарантирует их выполнение в том же порядке, что и любые другие операции, выполняемые в проигрывателе.