ExoPlayer поддерживает HLS с несколькими форматами контейнеров. Также должны поддерживаться содержащиеся в нём форматы аудио- и видеоматериалов (подробнее см. в разделе «Форматы материалов »). Мы настоятельно рекомендуем производителям HLS-контента создавать высококачественные потоки HLS, как описано в этой записи блога .
| Особенность | Поддерживается | Комментарии | 
|---|---|---|
| Контейнеры | ||
| MPEG-TS | ДА | |
| FMP4/CMAF | ДА | |
| ADTS (AAC) | ДА | |
| МП3 | ДА | |
| Скрытые субтитры | ||
| CEA-608 | ДА | |
| CEA-708 | ДА | |
| ВебВТТ | ДА | |
| Метаданные | ||
| ID3 | ДА | |
| СКТЭ-35 | НЕТ | |
| Защита контента | ||
| АЕС-128 | ДА | |
| Образец АЕС-128 | НЕТ | |
| Уайдвайн | ДА | API 19+ (схема «cenc») и 25+ (схема «cbcs») | 
| PlayReady SL2000 | ДА | только Android TV | 
| Управление сервером | ||
| Дельта-обновления | ДА | |
| Блокировка перезагрузки плейлиста | ДА | |
| Блокировка загрузки подсказок предварительной загрузки | ДА | За исключением байтовых диапазонов с неопределенной длиной. | 
| Вставка рекламы | ||
| Вставка рекламы под управлением сервера (межстраничная реклама) | Частично |  Только VOD с X-ASSET-URI . Прямые трансляции и X-ASSET-LIST будут добавлены позже. | 
| Реклама IMA на стороне сервера и клиента | ДА | Руководство по вставке рекламы | 
| Живое воспроизведение | ||
| Регулярное живое воспроизведение | ДА | |
| HLS с низкой задержкой (Apple) | ДА | |
| HLS с низкой задержкой (Сообщество) | НЕТ | |
| Общие данные клиента медиа CMCD | ДА | Руководство по интеграции CMCD | 
Использование MediaItem
Для воспроизведения потока HLS вам необходим модуль HLS.
Котлин
implementation("androidx.media3:media3-exoplayer-hls:1.8.0")
Круто
implementation "androidx.media3:media3-exoplayer-hls:1.8.0"
 Затем вы можете создать 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()
Ява
// 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()
Ява
// 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, @TimelineChangeReason reason: Int) {
      val manifest = player.currentManifest
      if (manifest is HlsManifest) {
        // Do something with the manifest.
      }
    }
  }
)
Ява
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 . Мы не поддерживаем все функции спецификации изначально. Если вам не хватает поддержки для вашего потока, сообщите нам об этом, опубликовав issue на GitHub и отправив URI вашего потока, чтобы мы могли добавить поддержку для вашего потока.
 Используйте MediaItem с API плейлиста
 Самый удобный способ воспроизведения HLS-потоков с полноэкранной вставкой — создание экземпляра ExoPlayer с HlsInterstitialsAdsLoader.AdsMediaSourceFactory . Это позволяет использовать API плейлистов на основе MediaItem из интерфейса Player для воспроизведения полноэкранной вставки HLS.
 MediaSource.Factory ExoPlayer можно внедрить в конструктор при создании экземпляра проигрывателя: 
Котлин
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
player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(hlsMediaSourceFactory)
    .build()
// Set the player on the ads loader.
hlsInterstitialsAdsLoader.setPlayer(player)
playerView.setPlayer(player)
Ява
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
player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(hlsMediaSourceFactory)
        .build();
// Set the player on the ads loader.
hlsInterstitialsAdsLoader.setPlayer(player);
playerView.setPlayer(player);
 При такой настройке проигрывателя воспроизведение полноэкранной рекламы HLS сводится к настройке медиа-элемента с помощью AdsConfiguration на проигрывателе: 
Котлин
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()
Ява
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 плейлистов на основе источников медиа: 
Котлин
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.
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()
Ява
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.
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.
exoPlayer.setMediaSource(mediaSource);
exoPlayer.prepare();
exoPlayer.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.
  }
}
Ява
private 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)
Ява
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. 
Котлин
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?.setPlayer(null)
}
Ява
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.
  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);
  }
}
 Вы также можете удалить отдельные состояния возобновления с помощью removeAdResumptionState(Object adsId) или очистить все с помощью clearAllAdResumptionStates() .
Как правило, перед установкой нового экземпляра проигрывателя в загрузчике рекламы необходимо освободить старый экземпляр проигрывателя. После освобождения загрузчика рекламы его использование становится невозможным.
Настройка воспроизведения
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))
Ява
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. - Обеспечьте себе продолжительное окно. Одна минута или больше — это отлично.