Dostosowywanie

Podstawą biblioteki ExoPlayer jest interfejs Player. Player udostępnia tradycyjne funkcje odtwarzacza wysokiego poziomu, takie jak możliwość buforowania, odtwarzania, wstrzymywania i przewijania. Domyślna implementacja ExoPlayer służy do przyjmowania niewielu założeń dotyczących typu odtwarzanych multimediów, sposobu i miejsca ich przechowywania oraz sposobu renderowania. Zamiast implementować ładowanie i renderowanie multimediów bezpośrednio, implementacje ExoPlayer przekazują tę pracę komponentom, które są wstrzykiwane podczas tworzenia odtwarzacza lub przekazywania do niego nowych źródeł multimediów. Komponenty wspólne dla wszystkich implementacji ExoPlayer:

  • MediaSource instancje, które definiują multimedia do odtworzenia, wczytują multimedia i z których można odczytać wczytane multimedia. Z MediaItem w odtwarzaczu MediaSource.Factory tworzy się wystąpienie MediaSource. Można je też przekazać bezpośrednio do odtwarzacza za pomocą interfejsu media source based playlist API.
  • MediaSource.Factory przekształca MediaItemMediaSource. Wartość MediaSource.Factory jest wstrzykiwana podczas tworzenia odtwarzacza.
  • Renderer instancje, które renderują poszczególne komponenty multimediów. Są one wstrzykiwane podczas tworzenia odtwarzacza.
  • TrackSelector, który wybiera utwory udostępnione przez MediaSource do odtworzenia przez każdą dostępną Renderer. Podczas tworzenia gracza dodawany jest TrackSelector.
  • LoadControl, który określa, kiedy MediaSource ma buforować więcej multimediów oraz ile multimediów ma być buforowanych. Gdy gracz zostanie utworzony, do gry zostanie wstrzyknięty element LoadControl.
  • LivePlaybackSpeedControl, który kontroluje szybkość odtwarzania podczas transmisji na żywo, aby odtwarzacz pozostawał jak najbliżej skonfigurowanego opóźnienia. Identyfikator LivePlaybackSpeedControl jest wstrzykiwany podczas tworzenia odtwarzacza.

Koncepcja wstrzykiwania komponentów, które implementują funkcje dla graczy, jest obecna w całej bibliotece. Domyślne implementacje niektórych komponentów zlecają wykonanie zadania innym komponentom. Dzięki temu wiele komponentów podrzędnych można pojedynczo zastąpić implementacjami, które są skonfigurowane w niestandardowy sposób.

Dostosowywanie odtwarzacza

Poniżej znajdziesz kilka typowych przykładów dostosowywania odtwarzacza przez wstrzyknięcie komponentów.

Konfigurowanie stosu sieciowego

Na stronie Dostosowywanie warstwy sieciowej używanej przez ExoPlayera znajdziesz informacje na ten temat.

Dane z pamięci podręcznej wczytane z sieci

Zapoznaj się z przewodnikami dotyczącymi tymczasowego buforowania na bieżącopobierania multimediów.

Dostosowywanie interakcji z serwerem

Niektóre aplikacje mogą przechwytywać żądania i odpowiedzi HTTP. Możesz wstrzyknąć niestandardowe nagłówki żądań, odczytać nagłówki odpowiedzi serwera, zmodyfikować URI żądań itp. Na przykład aplikacja może uwierzytelnić się, wstrzykując token jako nagłówek podczas żądania segmentów multimediów.

Ten przykład pokazuje, jak zaimplementować te zachowania, wstrzykując niestandardową funkcję DataSource.Factory do funkcji 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();

W powyższym fragmencie kodu wstrzyknięty tag HttpDataSource zawiera nagłówek "Header: Value" w każdym żądaniu HTTP. To zachowanie jest naprawione w przypadku każdej interakcji ze źródłem HTTP.

Aby uzyskać bardziej precyzyjne podejście, możesz wstrzyknąć zachowania w odpowiednim momencie za pomocą funkcji ResolvingDataSource. Ten fragment kodu pokazuje, jak wstrzyknąć nagłówki żądań tuż przed nawiązaniem interakcji ze źródłem 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)));

Możesz też użyć funkcji ResolvingDataSource do wprowadzania modyfikacji identyfikatora URI w czasie kompilacji, jak pokazano w tym fragmencie kodu:

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

Dostosowywanie obsługi błędów

Wdrożenie niestandardowego LoadErrorHandlingPolicy pozwala aplikacjom dostosować sposób, w jaki ExoPlayer reaguje na błędy wczytywania. Aplikacja może na przykład szybko zakończyć działanie zamiast wielokrotnego powtarzania prób lub dostosować logikę wycofywania, która kontroluje czas oczekiwania gracza między kolejnymi próbami. Ten fragment kodu pokazuje, jak wdrożyć niestandardową logikę wycofywania:

Kotlin

val loadErrorHandlingPolicy: LoadErrorHandlingPolicy =
  object : DefaultLoadErrorHandlingPolicy() {
    override fun getRetryDelayMsFor(loadErrorInfo: 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(LoadErrorInfo loadErrorInfo) {
        // Implement custom back-off logic here.
        return 0;
      }
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context)
                .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy))
        .build();

Argument LoadErrorInfo zawiera więcej informacji o nieudanym wczytaniu, aby umożliwić dostosowywanie logiki na podstawie typu błędu lub nieudanej prośby.

Dostosowywanie flag ekstraktora

Flagi ekstraktora można używać do dostosowywania sposobu wyodrębniania poszczególnych formatów z multimediów progresywnych. Można je ustawić w DefaultExtractorsFactory, który jest udostępniany DefaultMediaSourceFactory. W tym przykładzie przekazywany jest parametr, który umożliwia wyszukiwanie w indeksie w przypadku strumieni 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();

Włączanie wyszukiwania stałego bitrate’u

W przypadku strumieni MP3, ADTS i AMR możesz włączyć przybliżone wyszukiwanie za pomocą założenia stałej szybkości transmisji bitów z flagami FLAG_ENABLE_CONSTANT_BITRATE_SEEKING. Te flagi można ustawiać w przypadku poszczególnych ekstraktorów za pomocą metod DefaultExtractorsFactory.setXyzExtractorFlags opisanych powyżej. Aby włączyć wyszukiwanie stałego bitrate’u dla wszystkich ekstraktorów, które obsługują tę funkcję, użyj opcji DefaultExtractorsFactory.setConstantBitrateSeekingEnabled.

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);

Następnie możesz wstrzyknąć ExtractorsFactory za pomocą DefaultMediaSourceFactory w sposób opisany powyżej w sekcji poświęconej dostosowywaniu flag wyodrębniania.

Włączanie asynchronicznego kolejkowania w buforze

Asynchroniczne kolejki buforowe to ulepszenie potoku renderowania w ExoPlayer, które obsługuje instancje MediaCodec w trybie asynchronicznym i wykorzystuje dodatkowe wątki do planowania dekodowania i renderowania danych. Włączenie tej opcji może zmniejszyć liczbę utraconej liczby klatek i niedostatecznego dźwięku.

Asychronowe kolejkowanie buforów jest domyślnie włączone na urządzeniach z Androidem 12 (poziom interfejsu API 31) lub nowszym. Można je włączyć ręcznie, zaczynając od Androida 6.0 (poziom interfejsu API 23). Rozważ włączenie tej funkcji na określonych urządzeniach, na których występują utrata klatek lub niedobór dźwięku, zwłaszcza podczas odtwarzania treści chronionych przez DRM lub o wysokiej liczbie klatek na sekundę.

W najprostszym przypadku musisz wstrzyknąć DefaultRenderersFactory do odtwarzacza w ten sposób:

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

Jeśli instancjujesz bezpośrednio renderowanie, przekaż parametrAsynchronousMediaCodecAdapter.Factory do konstruktorów MediaCodecVideoRendererMediaCodecAudioRenderer.

Przechwytywanie wywołań metody za pomocą ForwardingPlayer

Możesz dostosować niektóre zachowania instancji Player, owijając ją w podklasę ForwardingPlayer i zastępując metody, aby wykonać jedną z tych czynności:

  • Parametry dostępu przed przekazaniem ich do delegata Player.
  • Uzyskaj dostęp do wartości zwracanej przez przedstawiciela Player przed jego zwróceniem.
  • całkowicie ponownie zaimplementować metodę;

Podczas zastępowania metod ForwardingPlayer ważne jest, aby implementacja była spójna i zgodna z interfejsem Player, zwłaszcza w przypadku metod, które mają identyczne lub podobne działanie. Na przykład:

  • Jeśli chcesz zastąpić wszystkie operacje „odtwarzania”, musisz zastąpić zarówno metodę ForwardingPlayer.play, jak i ForwardingPlayer.setPlayWhenReady, ponieważ wywołujący oczekuje, że zachowanie tych metod będzie identyczne w przypadku playWhenReady = true.
  • Jeśli chcesz zmienić przyrost wartości przewijania do przodu, musisz zastąpić zarówno parametr ForwardingPlayer.seekForward, aby wykonać przewijanie z dostosowanym przyrostem, oraz ForwardingPlayer.getSeekForwardIncrement, by zgłosić rozmówcy poprawny, dostosowany przyrost.
  • Jeśli chcesz kontrolować, jakie Player.Commands są reklamowane przez instancję odtwarzacza, musisz zastąpić zarówno Player.getAvailableCommands(), jak i Player.isCommandAvailable(), a także słuchać wywołania zwrotnego Player.Listener.onAvailableCommandsChanged(), aby otrzymywać powiadomienia o zmianach pochodzących z podstawowego odtwarzacza.

Dostosowywanie MediaSource

W powyższych przykładach komponenty niestandardowe są wstrzykiwane do odtwarzania wszystkich obiektówMediaItem przekazywanych do odtwarzacza. W przypadku dokładnej personalizacji możesz też wstrzyknąć niestandardowe komponenty do poszczególnych instancji MediaSource, które mogą być przekazywane bezpośrednio do odtwarzacza. Przykład poniżej pokazuje, jak dostosować ProgressiveMediaSource, aby użyć niestandardowych DataSource.Factory, ExtractorsFactory i 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));

Tworzenie komponentów niestandardowych

Biblioteka udostępnia domyślne implementacje komponentów wymienionych u góry tej strony na potrzeby typowych zastosowań. ExoPlayer może używać tych komponentów, ale może też być zbudowany w taki sposób, aby używać implementacji niestandardowych, jeśli wymagane są niestandardowe zachowania. Oto kilka przykładowych zastosowań niestandardowych implementacji:

  • Renderer – możesz wdrożyć niestandardowy Renderer, aby obsługiwać typ multimediów, który nie jest obsługiwany przez domyślne implementacje udostępniane przez bibliotekę.
  • TrackSelector – wdrożenie niestandardowego TrackSelector pozwala deweloperowi aplikacji zmienić sposób, w jaki utwory udostępnione przez MediaSource są wybierane do odtwarzania przez poszczególne dostępne Renderer.
  • LoadControl – jeśli zaimplementujesz niestandardowy LoadControl, deweloper aplikacji może zmienić zasady buforowania w odtwarzaczu.
  • Extractor – jeśli potrzebujesz obsługi formatu kontenera, którego biblioteka nie obsługuje, zaimplementuj niestandardową klasę Extractor.
  • MediaSource – jeśli chcesz uzyskać próbki multimediów, które będą podawane do renderowania w niestandardowy sposób, lub jeśli chcesz zaimplementować niestandardowe zachowanie MediaSource, możesz użyć niestandardowej klasy MediaSource.
  • MediaSource.Factory – zastosowanie niestandardowego elementu MediaSource.Factory umożliwia aplikacji dostosowanie sposobu tworzenia obiektu MediaSource na podstawie MediaItem.
  • DataSource – pakiet upstream ExoPlayera zawiera już kilka implementacji DataSource na potrzeby różnych zastosowań. Możesz zaimplementować własną klasę DataSource, aby wczytywać dane w inny sposób, na przykład za pomocą niestandardowego protokołu, niestandardowego stosu HTTP lub z niestandardowej stałej pamięci podręcznej.

Podczas tworzenia komponentów niestandardowych zalecamy:

  • Jeśli komponent niestandardowy musi zgłaszać zdarzenia do aplikacji, zalecamy użycie tego samego modelu co istniejące komponenty ExoPlayera, na przykład za pomocą klas EventDispatcher lub przekazania obiektu Handler wraz ze słuchaczem do konstruktora komponentu.
  • Zalecamy, aby komponenty niestandardowe korzystały z tego samego modelu co istniejące komponenty ExoPlayer, aby umożliwić aplikacji zmianę konfiguracji podczas odtwarzania. W tym celu komponenty niestandardowe powinny implementować interfejs PlayerMessage.Target i otrzymywać zmiany konfiguracji w metodzie handleMessage. Kod aplikacji powinien przekazywać zmiany konfiguracji, wywołując metodę createMessage w ExoPlayerze, konfigurując wiadomość i wysyłając ją do komponentu za pomocą PlayerMessage.send. Wysyłanie wiadomości do przekazania w wątku odtwarzania zapewnia, że są one wykonywane w kolejności z innymi operacjami wykonywanymi na odtwarzaczu.