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