В основе библиотеки 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()
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-источником:
Котлин
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 непосредственно в процессе работы, как показано в следующем фрагменте кода:
Котлин
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 на ошибки загрузки. Например, приложение может захотеть быстро завершить процесс с ошибкой, вместо того чтобы повторять попытки много раз, или может захотеть настроить логику задержки, которая определяет, как долго плеер ждет между каждой попыткой. Следующий фрагмент кода показывает, как реализовать пользовательскую логику задержки:
Котлин
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.
Котлин
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 .
Котлин
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 в плеер следующим образом:
Котлин
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();
Если вы создаете экземпляры рендереров напрямую, передайте конструкторам MediaCodecVideoRenderer и MediaCodecAudioRenderer new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous() .
Настройка операций с помощью 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) } }
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 ничего не делает):
Котлин
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 :
Котлин
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. Код приложения должен передавать изменения конфигурации, вызывая методcreateMessageкомпонента ExoPlayer, настраивая сообщение и отправляя его компоненту с помощьюPlayerMessage.send. Отправка сообщений для доставки в поток воспроизведения гарантирует, что они будут выполняться в порядке, соответствующем любым другим операциям, выполняемым над проигрывателем.