HLS

ExoPlayer obsługuje HLS w wielu formatach kontenerów. Obsługiwane muszą być też formaty próbek audio i wideo (szczegółowe informacje znajdziesz w sekcji formaty próbek). Gorąco zachęcamy producentów treści HLS do generowania wysokiej jakości transmisji HLS, zgodnie z opisem w tym poście na blogu.

Funkcja Obsługiwane Komentarze
Kontenery
MPEG-TS TAK
FMP4/CMAF TAK
ADTS (AAC) TAK
MP3 TAK
Napisy
CEA-608 TAK
CEA-708 TAK
WebVTT TAK
Metadane
ID3 TAK
SCTE-35 NIE
Ochrona treści
AES-128 TAK
Przykładowy AES-128 NIE
Widevine TAK API w wersji 19 lub nowszej (schemat „cenc”) i 25 lub nowszej (schemat „cbcs”)
PlayReady SL2000 TAK Tylko Android TV
Kontrola serwera
Aktualizacje delta TAK
Blokowanie ponownego wczytywania playlisty TAK
Blokowanie wczytywania wskazówek wstępnego wczytywania TAK Z wyjątkiem zakresów bajtów o nieokreślonej długości
Wstawianie reklam
Wstawianie reklam sterowane przez serwer (reklamy pełnoekranowe) Częściowo Tylko VOD z X-ASSET-URI. Transmisje na żywo i X-ASSET-LIST zostaną dodane później.
Reklamy po stronie serwera i klienta w pakiecie IMA SDK TAK Przewodnik po wstawianiu reklam
Odtwarzanie na żywo
Odtwarzanie na żywo TAK
HLS o małym opóźnieniu (Apple) TAK
HLS o niskim opóźnieniu (społeczność) NIE
Common Media Client Data CMCD TAK Przewodnik po integracji CMCD

Używanie elementu MediaItem

Aby odtwarzać strumień HLS, musisz korzystać z modułu HLS.

Kotlin

implementation("androidx.media3:media3-exoplayer-hls:1.7.1")

Groovy

implementation "androidx.media3:media3-exoplayer-hls:1.7.1"

Następnie możesz utworzyć MediaItem dla adresu URI playlisty HLS i przekazać go do odtwarzacza.

Kotlin

// 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();

Jeśli Twój identyfikator URI nie kończy się znakiem .m3u8, możesz przekazać wartość MimeTypes.APPLICATION_M3U8 do parametru setMimeType w metodzie MediaItem.Builder, aby wyraźnie wskazać typ treści.

Identyfikator URI elementu multimedialnego może wskazywać playlistę multimediów lub playlistę z wieloma wariantami. Jeśli identyfikator URI wskazuje listę odtwarzania z wieloma wariantami, która deklaruje wiele tagów #EXT-X-STREAM-INF, ExoPlayer automatycznie dostosuje się do wariantów, biorąc pod uwagę zarówno dostępną przepustowość, jak i możliwości urządzenia.

Korzystanie z HlsMediaSource

Aby uzyskać więcej opcji dostosowywania, możesz utworzyć HlsMediaSource i przekazać go bezpośrednio do odtwarzacza zamiast MediaItem.

Kotlin

// 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();

Dostęp do pliku manifestu

Aby pobrać bieżący plik manifestu, wywołaj funkcję Player.getCurrentManifest. W przypadku HLS zwrócony obiekt należy przekształcić w HlsManifest. Wywołanie zwrotne onTimelineChanged funkcji Player.Listener jest też wywoływane za każdym razem, gdy wczytywany jest plik manifestu. W przypadku treści na żądanie zdarzy się to raz, a w przypadku treści na żywo może się to zdarzyć wiele razy. Ten fragment kodu pokazuje, jak aplikacja może wykonać działanie po wczytaniu pliku manifestu.

Kotlin

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.
      }
    }
  }
)

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.
        }
      }
    });

Odtwarzanie strumieni HLS z reklamami pełnoekranowymi

Specyfikacja HLS definiuje reklamy pełnoekranowe HLS, których można używać do umieszczania informacji o reklamach pełnoekranowych na playliście multimedialnej. ExoPlayer domyślnie ignoruje te reklamy pełnoekranowe. Pomoc można dodać za pomocą HlsInterstitialsAdsLoader. Nie obsługujemy wszystkich funkcji specyfikacji od samego początku. Jeśli brakuje obsługi Twojego strumienia, daj nam znać, zgłaszając problem w serwisie GitHub i przesyłając nam identyfikator URI strumienia, abyśmy mogli dodać jego obsługę.

Używanie MediaItem z interfejsem Playlist API

Najwygodniejszym sposobem odtwarzania strumieni HLS z reklamami pełnoekranowymi jest utworzenie instancji ExoPlayera za pomocą HlsInterstitialsAdsLoader.AdsMediaSourceFactory. Umożliwia to korzystanie z MediaItem opartego na interfejsie API playlisty Playerdo odtwarzania reklam pełnoekranowych HLS.

Wartość MediaSource.FactoryExoPlayer można wstawić do konstruktora podczas tworzenia instancji odtwarzacza:

Kotlin

hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context)
// Create a MediaSource.Factory for HLS streams with interstitials.
var 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)

Java

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);

W takiej konfiguracji odtwarzanie reklam pełnoekranowych HLS polega na ustawieniu w odtwarzaczu elementu multimedialnego z parametrem AdsConfiguration:

Kotlin

player.setMediaItem(
  MediaItem.Builder()
    .setUri("https://www.example.com/media.m3u8")
    .setAdsConfiguration(
      AdsConfiguration.Builder(Uri.parse("hls://interstitials"))
        .setAdsId("ad-tag-0") // must be unique within playlist
        .build())
    .build())

player.prepare();
player.play();

Java

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();

Korzystanie z interfejsu API opartego na źródle danych

Możesz też utworzyć instancję ExoPlayera bez zastępowania domyślnej fabryki źródeł multimediów. Aby obsługiwać reklamy pełnoekranowe, aplikacja może używać bezpośrednio HlsInterstitialsAdsLoader.AdsMediaSourceFactory do tworzenia MediaSource i przekazywania go do ExoPlayera za pomocą interfejsu API listy odtwarzania opartej na źródle multimediów:

Kotlin

hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context)
// Create a MediaSource.Factory for HLS streams with interstitials.
var hlsMediaSourceFactory =
  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.
val mediaSource =
  hlsMediaSourceFactory.createMediaSource(
    MediaItem.Builder()
      .setUri("https://www.example.com/media.m3u8")
      .setAdsConfiguration(
        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()

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.
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();

Nasłuchiwanie zdarzeń reklamowych

Do HlsInterstitialsAdsLoader można dodać Listener, aby monitorować zdarzenia dotyczące zmian stanu odtwarzania reklam pełnoekranowych HLS. Umożliwia to aplikacji lub pakietowi SDK śledzenie wyświetlanych reklam, wczytywanych list komponentów, przygotowywanych źródeł multimediów reklamowych oraz wykrywanie błędów wczytywania list komponentów i przygotowywania reklam. Ponadto metadane emitowane przez źródła multimediów reklamowych mogą być odbierane w celu dokładnej weryfikacji odtwarzania reklam lub śledzenia postępów odtwarzania reklam.

Kotlin

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

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.
  }
}

Szczegółową dokumentację wszystkich dostępnych wywołań zwrotnych znajdziesz w JavaDoc HlsInterstitialsAdsLoader.Listener.

Detektor można następnie dodać do narzędzia do wczytywania reklam:

Kotlin

var 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 cykl życia

Instancję HlsInterstitialsAdsLoader lub HlsInterstitialsAdsLoader.AdsMediaSourceFactory można ponownie wykorzystać w przypadku wielu instancji odtwarzacza, które tworzą wiele źródeł multimediów, do których trzeba wczytać reklamy.

Instancję można utworzyć na przykład w metodzie onCreate elementu Activity, a następnie użyć jej ponownie w przypadku wielu instancji odtwarzacza. Działa to, o ile jest używane przez jedną instancję odtwarzacza w tym samym czasie. Jest to przydatne w typowym przypadku, gdy aplikacja jest przenoszona w tle, instancja odtwarzacza jest niszczona, a następnie tworzona jest nowa instancja, gdy aplikacja ponownie przechodzi na pierwszy plan.

Kotlin

// Create the ads loader instance (for example onCreate).
hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context);

// Build a player and set it on the ads loader (for example onStart).
player = ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);

// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);

// Build another player and set it on the ads loader (for example onStart).
player = ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);

// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);

// Release the ads loader when not used anymore  (for example onDestroy).
hlsInterstitialsAdsLoader.release();

Java

// Create the ads loader instance (for example onCreate).
hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context);

// Build a player and set it on the ads loader (for example onStart).
player = new ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);

// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);

// Build another player and set it on the ads loader (for example onStart).
player = new ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);

// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);

// Release the ads loader when not used anymore  (for example onDestroy).
hlsInterstitialsAdsLoader.release();

Zwykle przed ustawieniem następnej instancji odtwarzacza w programie wczytującym reklamy należy zwolnić starą instancję odtwarzacza. Po udostępnieniu samego narzędzia do wczytywania reklam nie będzie można go już używać.

Dostosowywanie odtwarzania

ExoPlayer udostępnia wiele sposobów dostosowywania odtwarzania do potrzeb aplikacji. Przykłady znajdziesz na stronie Dostosowywanie.

Wyłączanie przygotowywania bez podziału na części

Domyślnie ExoPlayer używa przygotowania bez podziału na fragmenty. Oznacza to, że ExoPlayer będzie używać tylko informacji z listy odtwarzania z wieloma wariantami do przygotowania strumienia, co działa, jeśli tagi #EXT-X-STREAM-INF zawierają atrybut CODECS.

Może być konieczne wyłączenie tej funkcji, jeśli segmenty multimediów zawierają zmultipleksowane ścieżki napisów, które nie są zadeklarowane na liście odtwarzania z wieloma wariantami za pomocą tagu #EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS. W przeciwnym razie ścieżki napisów nie zostaną wykryte ani odtworzone. Przygotowywanie bez podziału na części możesz wyłączyć w HlsMediaSource.Factory, jak pokazano w tym fragmencie kodu. Pamiętaj, że wydłuży to czas uruchamiania, ponieważ ExoPlayer musi pobrać segment multimediów, aby wykryć te dodatkowe ścieżki. Zamiast tego lepiej jest zadeklarować ścieżki napisów w wielowariantowej playliście.

Kotlin

val hlsMediaSource =
  HlsMediaSource.Factory(dataSourceFactory)
    .setAllowChunklessPreparation(false)
    .createMediaSource(MediaItem.fromUri(hlsUri))

Java

HlsMediaSource hlsMediaSource =
    new HlsMediaSource.Factory(dataSourceFactory)
        .setAllowChunklessPreparation(false)
        .createMediaSource(MediaItem.fromUri(hlsUri));

Tworzenie wysokiej jakości treści HLS

Aby w pełni wykorzystać możliwości ExoPlayera, możesz postępować zgodnie z określonymi wytycznymi, które pomogą Ci ulepszyć treści HLS. Pełne wyjaśnienie znajdziesz w naszym poście na Medium o odtwarzaniu HLS w ExoPlayerze. Najważniejsze kwestie:

  • Używaj dokładnych czasów trwania segmentów.
  • Używaj ciągłego strumienia multimediów; unikaj zmian w strukturze multimediów w poszczególnych segmentach.
  • Używaj tagu #EXT-X-INDEPENDENT-SEGMENTS.
  • Preferuj strumienie zdemuksowane zamiast plików zawierających zarówno wideo, jak i audio.
  • W playlistach z wieloma wariantami podawaj jak najwięcej informacji.

W przypadku transmisji na żywo obowiązują te wytyczne:

  • Używaj tagu #EXT-X-PROGRAM-DATE-TIME.
  • Używaj tagu #EXT-X-DISCONTINUITY-SEQUENCE.
  • Zapewnij długie okno transmisji na żywo. Minuta lub więcej to świetny wynik.