ExoPlayer поддерживает HLS с несколькими форматами контейнеров. Необходимо также поддерживать содержащиеся в контейнере аудио- и видеоформаты (подробнее см. раздел « Форматы образцов »). Мы настоятельно рекомендуем производителям HLS-контента создавать высококачественные HLS-потоки, как описано в этой статье блога .
| Особенность | Поддерживается | Комментарии |
|---|---|---|
| Контейнеры | ||
| MPEG-TS | ДА | |
| FMP4/CMAF | ДА | |
| ADTS (AAC) | ДА | |
| MP3 | ДА | |
| Субтитры / скрытые субтитры | ||
| CEA-608 | ДА | |
| CEA-708 | ДА | |
| WebVTT | ДА | |
| Метаданные | ||
| ID3 | ДА | |
| СКТЕ-35 | НЕТ | |
| Защита контента | ||
| АЭС-128 | ДА | |
| Образец AES-128 | НЕТ | |
| Уайдвин | ДА | API 19+ (схема "cenc") и 25+ (схема "cbcs") |
| PlayReady SL2000 | ДА | Только для Android TV |
| Управление сервером | ||
| Обновления Delta | ДА | |
| Блокировка перезагрузки плейлиста | ДА | |
| Блокировка загрузки подсказок предварительной загрузки | ДА | За исключением диапазонов байтов с неопределенной длиной. |
| Вставка рекламы | ||
| Вставка рекламы с управлением со стороны сервера (межстраничная реклама) | Частично | Только видео по запросу (VOD) с X-ASSET-URI . Прямые трансляции и X-ASSET-LIST будут добавлены позже. |
| Реклама на стороне сервера и на стороне клиента IMA | ДА | руководство по размещению рекламы |
| Воспроизведение в реальном времени | ||
| Обычное воспроизведение в прямом эфире | ДА | |
| Низкозадержечная HLS (Apple) | ДА | |
| Низкозадержечная HLS (Community) | НЕТ | |
| Данные клиента Common Media CMCD | ДА | руководство по интеграции CMCD |
Использование MediaItem
Для воспроизведения HLS-потока необходимо использовать модуль HLS.
Котлин
implementation("androidx.media3:media3-exoplayer-hls:1.9.2")
Классный
implementation "androidx.media3:media3-exoplayer-hls:1.9.2"
Затем вы можете создать MediaItem для URI плейлиста HLS и передать его плееру.
Котлин
// Create a player instance. val player = ExoPlayer.Builder(context).build() // Set the media item to be played. player.setMediaItem(MediaItem.fromUri(hlsUri)) // Prepare the player. player.prepare()
Java
// Create a player instance. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Set the media item to be played. player.setMediaItem(MediaItem.fromUri(hlsUri)); // Prepare the player. player.prepare();
Если ваш URI не заканчивается на .m3u8 , вы можете передать MimeTypes.APPLICATION_M3U8 в setMimeType объекта MediaItem.Builder , чтобы явно указать тип контента.
URI медиафайла может указывать либо на список воспроизведения, либо на многовариантный список воспроизведения. Если URI указывает на многовариантный список воспроизведения, содержащий несколько тегов #EXT-X-STREAM-INF , то ExoPlayer автоматически адаптируется между вариантами, учитывая как доступную пропускную способность, так и возможности устройства.
Использование HlsMediaSource
Для расширения возможностей настройки вы можете создать объект HlsMediaSource и передать его непосредственно плееру вместо MediaItem .
Котлин
// Create a data source factory. val dataSourceFactory: DataSource.Factory = DefaultHttpDataSource.Factory() // Create a HLS media source pointing to a playlist uri. val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(hlsUri)) // Create a player instance. val player = ExoPlayer.Builder(context).build() // Set the HLS media source as the playlist with a single media item. player.setMediaSource(hlsMediaSource) // Prepare the player. player.prepare()
Java
// Create a data source factory. DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory(); // Create a HLS media source pointing to a playlist uri. HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(hlsUri)); // Create a player instance. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Set the HLS media source as the playlist with a single media item. player.setMediaSource(hlsMediaSource); // Prepare the player. player.prepare();
Доступ к манифесту
Текущий манифест можно получить, вызвав метод Player.getCurrentManifest . Для HLS следует привести возвращаемый объект к HlsManifest . Коллбэк onTimelineChanged метода Player.Listener также вызывается при каждой загрузке манифеста. Это произойдет один раз для контента по запросу и, возможно, много раз для контента в реальном времени. Следующий фрагмент кода показывает, как приложение может выполнить определенные действия при каждой загрузке манифеста.
Котлин
player.addListener( object : Player.Listener { override fun onTimelineChanged( timeline: Timeline, @Player.TimelineChangeReason reason: Int, ) { val manifest = player.currentManifest if (manifest is HlsManifest) { // Do something with the manifest. } } } )
Java
player.addListener( new Player.Listener() { @Override public void onTimelineChanged( Timeline timeline, @Player.TimelineChangeReason int reason) { Object manifest = player.getCurrentManifest(); if (manifest != null) { HlsManifest hlsManifest = (HlsManifest) manifest; // Do something with the manifest. } } });
Воспроизводите HLS-трансляции с вставками.
Спецификация HLS определяет HLS-интерстициалы, которые можно использовать для включения промежуточной информации в плейлист медиафайлов. ExoPlayer по умолчанию игнорирует эти интерстициалы. Поддержку можно добавить с помощью HlsInterstitialsAdsLoader . Мы не поддерживаем все функции спецификации с самого начала. Если вам не хватает поддержки для вашего потока, сообщите нам об этом, создав проблему на GitHub и отправив нам URI вашего потока, чтобы мы могли добавить поддержку для вашего потока.
Используйте MediaItem с API плейлистов.
Наиболее удобный способ воспроизведения HLS-потоков с межстраничной рекламой — это создание экземпляра ExoPlayer с классом HlsInterstitialsAdsLoader.AdsMediaSourceFactory . Это позволяет использовать API плейлистов на основе MediaItem из интерфейса Player для воспроизведения HLS-межстраничной рекламы.
Компонент MediaSource.Factory объекта ExoPlayer можно внедрить в построитель при создании экземпляра проигрывателя:
Котлин
val hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context) // Create a MediaSource.Factory for HLS streams with interstitials. val hlsMediaSourceFactory = HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, DefaultMediaSourceFactory(context), ) // Build player with interstitials media source factory val player = ExoPlayer.Builder(context).setMediaSourceFactory(hlsMediaSourceFactory).build() // Set the player on the ads loader. hlsInterstitialsAdsLoader.setPlayer(player) playerView.setPlayer(player)
Java
HlsInterstitialsAdsLoader hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context); // Create a MediaSource.Factory for HLS streams with interstitials. MediaSource.Factory hlsMediaSourceFactory = new HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, new DefaultMediaSourceFactory(context)); // Build player with interstitials media source factory ExoPlayer player = new ExoPlayer.Builder(context).setMediaSourceFactory(hlsMediaSourceFactory).build(); // Set the player on the ads loader. hlsInterstitialsAdsLoader.setPlayer(player); playerView.setPlayer(player);
При такой настройке плеера воспроизведение HLS-интерстициальной рекламы сводится к простой настройке медиафайла с параметром AdsConfiguration в плеере:
Котлин
// Build an HLS media item with ads configuration to be played. player.setMediaItem( MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( MediaItem.AdsConfiguration.Builder("hls://interstitials".toUri()) .setAdsId("ad-tag-0") // must be unique within playlist .build() ) .build() ) player.prepare() player.play()
Java
// Build an HLS media item with ads configuration to be played. player.setMediaItem( new MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( new AdsConfiguration.Builder(Uri.parse("hls://interstitials")) .setAdsId("ad-tag-0") // must be unique within playlist .build()) .build()); player.prepare(); player.play();
Используйте API на основе источника мультимедиа.
В качестве альтернативы, экземпляр ExoPlayer можно создать без переопределения фабрики источников мультимедиа по умолчанию. Для поддержки межстраничной рекламы приложение может использовать HlsInterstitialsAdsLoader.AdsMediaSourceFactory напрямую для создания MediaSource и предоставления его ExoPlayer с помощью API плейлистов на основе источников мультимедиа:
Котлин
val hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context) // Create a MediaSource.Factory for HLS streams with interstitials. val hlsMediaSourceFactory = HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, context, ) // Build player with default media source factory. val player = ExoPlayer.Builder(context).build() // Create an media source from an HLS media item with ads configuration. val mediaSource = hlsMediaSourceFactory.createMediaSource( MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( MediaItem.AdsConfiguration.Builder("hls://interstitials".toUri()) .setAdsId("ad-tag-0") .build() ) .build() ) // Set the media source on the player. player.setMediaSource(mediaSource) player.prepare() player.play()
Java
HlsInterstitialsAdsLoader hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context); // Create a MediaSource.Factory for HLS streams with interstitials. MediaSource.Factory hlsMediaSourceFactory = new HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, context); // Build player with default media source factory. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Create an media source from an HLS media item with ads configuration. MediaSource mediaSource = hlsMediaSourceFactory.createMediaSource( new MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( new MediaItem.AdsConfiguration.Builder(Uri.parse("hls://interstitials")) .setAdsId("ad-tag-0") .build()) .build()); // Set the media source on the player. player.setMediaSource(mediaSource); player.prepare(); player.play();
Слушайте рекламные события
К компоненту HlsInterstitialsAdsLoader можно добавить Listener для отслеживания событий, связанных с изменением статуса воспроизведения HLS-интерстициальной рекламы. Это позволяет приложению или SDK отслеживать воспроизведение рекламы, загрузку списков ресурсов, подготовку источников рекламного контента, а также выявлять ошибки загрузки списков ресурсов и подготовки рекламы. Кроме того, можно получать метаданные, передаваемые источниками рекламного контента, для более точной проверки воспроизведения рекламы или отслеживания прогресса воспроизведения.
Котлин
class AdsLoaderListener : HlsInterstitialsAdsLoader.Listener { override fun onStart(mediaItem: MediaItem, adsId: Any, adViewProvider: AdViewProvider) { // Do something when HLS media item with interstitials is started. } override fun onMetadata( mediaItem: MediaItem, adsId: Any, adGroupIndex: Int, adIndexInAdGroup: Int, metadata: Metadata, ) { // Do something with metadata that is emitted by the ad media source of the given ad. } override fun onAdCompleted( mediaItem: MediaItem, adsId: Any, adGroupIndex: Int, adIndexInAdGroup: Int, ) { // Do something when ad completed playback. } // ... See JavaDoc for further callbacks of HlsInterstitialsAdsLoader.Listener. override fun onStop(mediaItem: MediaItem, adsId: Any, adPlaybackState: AdPlaybackState) { // Do something with the resulting ad playback state when stopped. } }
Java
@OptIn(markerClass = UnstableApi.class) private static class AdsLoaderListener implements HlsInterstitialsAdsLoader.Listener { // implement HlsInterstitialsAdsLoader.Listener @Override public void onStart(MediaItem mediaItem, Object adsId, AdViewProvider adViewProvider) { // Do something when HLS media item with interstitials is started. } @Override public void onMetadata( MediaItem mediaItem, Object adsId, int adGroupIndex, int adIndexInAdGroup, Metadata metadata) { // Do something with metadata that is emitted by the ad media source of the given ad. } @Override public void onAdCompleted( MediaItem mediaItem, Object adsId, int adGroupIndex, int adIndexInAdGroup) { // Do something when ad completed playback. } // ... See JavaDoc for further callbacks @Override public void onStop(MediaItem mediaItem, Object adsId, AdPlaybackState adPlaybackState) { // Do something with the resulting ad playback state when stopped. } }
Подробное описание всех доступных функций обратного вызова см. в документации JavaDoc для HlsInterstitialsAdsLoader.Listener .
Затем обработчик событий можно добавить в загрузчик рекламы:
Котлин
val listener = AdsLoaderListener() // Add the listener to the ads loader to receive ad loader events. hlsInterstitialsAdsLoader.addListener(listener)
Java
AdsLoaderListener listener = new AdsLoaderListener(); // Add the listener to the ads loader to receive ad loader events. hlsInterstitialsAdsLoader.addListener(listener);
Жизненный цикл HlsInterstitialsAdsLoader
Экземпляр класса HlsInterstitialsAdsLoader или HlsInterstitialsAdsLoader.AdsMediaSourceFactory можно повторно использовать для нескольких экземпляров проигрывателя, которые создают несколько источников медиаконтента, для которых необходимо загрузить рекламу.
Экземпляр можно создать, например, в методе onCreate Activity , а затем повторно использовать для нескольких экземпляров плеера. Это работает до тех пор, пока он используется одним экземпляром плеера одновременно. Это полезно в распространенном случае, когда приложение переводится в фоновый режим, экземпляр плеера уничтожается, а затем новый экземпляр создается при повторном переводе приложения в активный режим.
Возобновление воспроизведения при наличии состояния воспроизведения рекламы.
Чтобы пользователям не приходилось пересматривать рекламу, состояние воспроизведения рекламы можно сохранить и восстановить при повторном создании плеера. Это делается путем вызова getAdsResumptionStates() перед завершением воспроизведения и сохранения возвращенных объектов AdsResumptionState . При повторном создании плеера состояние можно восстановить, вызвав addAdResumptionState() для экземпляра загрузчика рекламы. AdsResumptionState является пакетируемым, поэтому его можно сохранить в пакете onSaveInstanceState объекта Activity . Обратите внимание, что возобновление воспроизведения рекламы поддерживается только для видеопотоков по запросу (VOD).
Котлин
class HlsInterstitialsActivity : Activity() { companion object { const val ADS_RESUMPTION_STATE_KEY = "ads_resumption_state" } private var hlsInterstitialsAdsLoader: HlsInterstitialsAdsLoader? = null private var playerView: PlayerView? = null private var player: ExoPlayer? = null private var adsResumptionStates: List<HlsInterstitialsAdsLoader.AdsResumptionState>? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Create the ads loader instance. hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(this) // Restore ad resumption states. savedInstanceState?.getParcelableArrayList<Bundle>(ADS_RESUMPTION_STATE_KEY)?.let { bundles -> adsResumptionStates = bundles.map { HlsInterstitialsAdsLoader.AdsResumptionState.fromBundle(it) } } } override fun onStart() { super.onStart() // Build a player and set it on the ads loader. initializePlayer() hlsInterstitialsAdsLoader?.setPlayer(player) // Add any stored ad resumption states to the ads loader. adsResumptionStates?.forEach { hlsInterstitialsAdsLoader?.addAdResumptionState(it) } adsResumptionStates = null // Consume the states } override fun onStop() { super.onStop() // Get ad resumption states. adsResumptionStates = hlsInterstitialsAdsLoader?.adsResumptionStates releasePlayer() } override fun onDestroy() { // Release the ads loader when not used anymore. hlsInterstitialsAdsLoader?.release() hlsInterstitialsAdsLoader = null super.onDestroy() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) // Store the ad resumption states. adsResumptionStates?.let { outState.putParcelableArrayList( ADS_RESUMPTION_STATE_KEY, ArrayList(it.map(HlsInterstitialsAdsLoader.AdsResumptionState::toBundle)), ) } } fun initializePlayer() { if (player == null) { // Create a media source factory for HLS streams. val hlsMediaSourceFactory = HlsInterstitialsAdsLoader.AdsMediaSourceFactory( checkNotNull(hlsInterstitialsAdsLoader), playerView!!, /* context= */ this, ) // Build player with interstitials media source player = ExoPlayer.Builder(/* context= */ this) .setMediaSourceFactory(hlsMediaSourceFactory) .build() // Set the player on the ads loader. hlsInterstitialsAdsLoader?.setPlayer(player) playerView?.player = player } // Use a media item with an HLS stream URI, an ad tag URI and ads ID. player?.setMediaItem( MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setMimeType(MimeTypes.APPLICATION_M3U8) .setAdsConfiguration( MediaItem.AdsConfiguration.Builder("hls://interstitials".toUri()) .setAdsId("ad-tag-0") // must be unique within ExoPlayer playlist .build() ) .build() ) player?.prepare() player?.play() } fun releasePlayer() { player?.release() player = null hlsInterstitialsAdsLoader?.setPlayer(null) playerView?.player = null } }
Java
@OptIn(markerClass = UnstableApi.class) public static class HlsInterstitialsActivity extends Activity { public static final String ADS_RESUMPTION_STATE_KEY = "ads_resumption_state"; @Nullable private HlsInterstitialsAdsLoader hlsInterstitialsAdsLoader; @Nullable private PlayerView playerView; @Nullable private ExoPlayer player; private List<HlsInterstitialsAdsLoader.AdsResumptionState> adsResumptionStates; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create the ads loader instance. hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(this); // Restore ad resumption states. if (savedInstanceState != null) { ArrayList<Bundle> bundles = savedInstanceState.getParcelableArrayList(ADS_RESUMPTION_STATE_KEY); if (bundles != null) { adsResumptionStates = new ArrayList<>(); for (Bundle bundle : bundles) { adsResumptionStates.add( HlsInterstitialsAdsLoader.AdsResumptionState.fromBundle(bundle)); } } } } @Override protected void onStart() { super.onStart(); // Build a player and set it on the ads loader. initializePlayer(); // Add any stored ad resumption states to the ads loader. if (adsResumptionStates != null) { for (HlsInterstitialsAdsLoader.AdsResumptionState state : adsResumptionStates) { hlsInterstitialsAdsLoader.addAdResumptionState(state); } adsResumptionStates = null; // Consume the states } } @Override protected void onStop() { super.onStop(); // Get ad resumption states before releasing the player. if (hlsInterstitialsAdsLoader != null) { adsResumptionStates = hlsInterstitialsAdsLoader.getAdsResumptionStates(); } releasePlayer(); } @Override protected void onDestroy() { // Release the ads loader when not used anymore. if (hlsInterstitialsAdsLoader != null) { hlsInterstitialsAdsLoader.release(); hlsInterstitialsAdsLoader = null; } super.onDestroy(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Store the ad resumption states. if (adsResumptionStates != null) { ArrayList<Bundle> bundles = new ArrayList<>(); for (HlsInterstitialsAdsLoader.AdsResumptionState state : adsResumptionStates) { bundles.add(state.toBundle()); } outState.putParcelableArrayList(ADS_RESUMPTION_STATE_KEY, bundles); } } private void initializePlayer() { if (player == null) { // Create a media source factory for HLS streams. MediaSource.Factory hlsMediaSourceFactory = new HlsInterstitialsAdsLoader.AdsMediaSourceFactory( checkNotNull(hlsInterstitialsAdsLoader), playerView, /* context= */ this); // Build player with interstitials media source player = new ExoPlayer.Builder(/* context= */ this) .setMediaSourceFactory(hlsMediaSourceFactory) .build(); // Set the player on the ads loader. hlsInterstitialsAdsLoader.setPlayer(player); playerView.setPlayer(player); } // Use a media item with an HLS stream URI, an ad tag URI and ads ID. player.setMediaItem( new MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setMimeType(MimeTypes.APPLICATION_M3U8) .setAdsConfiguration( new MediaItem.AdsConfiguration.Builder(Uri.parse("hls://interstitials")) .setAdsId("ad-tag-0") // must be unique within ExoPlayer playlist .build()) .build()); player.prepare(); player.play(); } private void releasePlayer() { if (player != null) { player.release(); player = null; } if (hlsInterstitialsAdsLoader != null) { hlsInterstitialsAdsLoader.setPlayer(null); } if (playerView != null) { playerView.setPlayer(null); } } }
Настройка воспроизведения
ExoPlayer предоставляет множество способов настроить воспроизведение в соответствии с потребностями вашего приложения. Примеры см. на странице «Настройка» .
Отключение подготовки без блоков
По умолчанию ExoPlayer использует подготовку без использования фрагментов. Это означает, что ExoPlayer будет использовать для подготовки потока только информацию из многовариантного плейлиста, что работает, если теги #EXT-X-STREAM-INF содержат атрибут CODECS .
Возможно, вам потребуется отключить эту функцию, если ваши медиасегменты содержат мультиплексированные дорожки субтитров, которые не указаны в многовариантном плейлисте с тегом #EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS . В противном случае эти дорожки субтитров не будут обнаружены и воспроизведены. Вы можете отключить подготовку без использования фрагментов в HlsMediaSource.Factory , как показано в следующем фрагменте кода. Обратите внимание, что это увеличит время запуска, поскольку ExoPlayer потребуется загрузить медиасегмент для обнаружения этих дополнительных дорожек, и предпочтительнее указывать дорожки субтитров в многовариантном плейлисте.
Котлин
val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(false) .createMediaSource(MediaItem.fromUri(hlsUri))
Java
HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(false) .createMediaSource(MediaItem.fromUri(hlsUri));
Создание высококачественного HLS-контента
Чтобы максимально эффективно использовать ExoPlayer, можно следовать определенным рекомендациям для улучшения качества HLS-контента. Подробное объяснение вы найдете в нашей статье на Medium о воспроизведении HLS в ExoPlayer . Основные моменты:
- Используйте точную продолжительность сегментов.
- Используйте непрерывный медиапоток; избегайте изменений в структуре медиаконтента между сегментами.
- Используйте тег
#EXT-X-INDEPENDENT-SEGMENTS. - Предпочтительнее использовать демультиплексированные потоки, а не файлы, содержащие как видео, так и аудио.
- Включите в многовариантный плейлист всю доступную информацию.
Следующие правила применяются исключительно к прямым трансляциям:
- Используйте тег
#EXT-X-PROGRAM-DATE-TIME. - Используйте тег
#EXT-X-DISCONTINUITY-SEQUENCE. - Предоставьте достаточно длительный временной интервал для трансляции. Одна минута или больше — отлично.