Sercem biblioteki ExoPlayer jest interfejs Player
. Player
eksponuje funkcje tradycyjnego odtwarzacza multimediów wysokiego poziomu, takie jak możliwość buforowania, odtwarzania, wstrzymywania i przeszukiwania. Domyślna implementacja ExoPlayer
wprowadza niewielką wartość w odniesieniu do typu odtwarzanych multimediów, sposobu i miejsca ich przechowywania oraz sposobu ich renderowania (dlatego wprowadza niewielkie ograniczenia). Zamiast bezpośrednio wczytywać i renderować multimedia, implementacje ExoPlayer
przekazują te zadania do komponentów wstrzykiwanych podczas tworzenia odtwarzacza lub przekazywania do niego nowych źródeł multimediów.
Komponenty wspólne dla wszystkich implementacji ExoPlayer
to:
MediaSource
instancji, które określają multimedia do odtwarzania oraz wczytują multimedia i z których można je odczytywać. InstancjaMediaSource
jest tworzona zMediaItem
przezMediaSource.Factory
w odtwarzaczu. Można je też przekazywać bezpośrednio do odtwarzacza za pomocą interfejsu API playlisty opartego na źródle multimediów.- Instancje
MediaSource.Factory
, które konwertują dane zMediaItem
naMediaSource
. Podczas tworzenia odtwarzacza jest wstrzykiwanyMediaSource.Factory
. Renderer
do renderowania poszczególnych komponentów multimediów. Są one wstrzykiwane podczas tworzenia odtwarzacza.- Zasób
TrackSelector
, który wybiera ścieżki udostępnione przez obiektMediaSource
do wykorzystania przez każdy dostępny obiektRenderer
. Podczas tworzenia odtwarzacza jest wstrzykiwanyTrackSelector
. - Element
LoadControl
, który określa, kiedyMediaSource
będzie buforować więcej multimediów i ile multimediów ma być buforowane. Podczas tworzenia odtwarzacza jest wstrzykiwanyLoadControl
. - Element
LivePlaybackSpeedControl
, który steruje szybkością odtwarzania podczas odtwarzania na żywo, aby odtwarzacz pozostawał w pobliżu skonfigurowanego przesunięcia transmisji na żywo. Podczas tworzenia odtwarzacza jest dodawanyLivePlaybackSpeedControl
.
Koncepcja wstrzykiwania komponentów implementujących poszczególne funkcje odtwarzacza jest obecna w całej bibliotece. Domyślne implementacje niektórych komponentów delegują pracę do dalszych wstrzykiwanych komponentów. Dzięki temu wiele komponentów podrzędnych można zastępować pojedynczo implementacjami skonfigurowanymi w niestandardowy sposób.
Personalizacja odtwarzacza
Poniżej znajdziesz kilka typowych przykładów dostosowywania odtwarzacza przez wstawianie komponentów.
Konfigurowanie stosu sieciowego
Zapoznaj się ze stroną poświęconą dostosowywaniu stosu sieciowego używanego przez ExoPlayer.
Buforuję dane wczytane z sieci
Zapoznaj się z przewodnikami na temat tymczasowego buforowania w czasie rzeczywistym i pobierania multimediów.
Dostosowywanie interakcji z serwerem
Niektóre aplikacje mogą chcieć przechwytywać żądania i odpowiedzi HTTP. Może być konieczne wstawianie niestandardowych nagłówków żądań, odczytywanie nagłówków odpowiedzi serwera, modyfikowanie identyfikatorów URI żądań itp. Aplikacja może na przykład uwierzytelnić się, wstawiając token jako nagłówek przy wysyłaniu żądania segmentów multimediów.
Ten przykład pokazuje, jak wdrożyć te zachowania przez wstrzyknięcie niestandardowego elementu DataSource.Factory
w interfejsie 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();
We fragmencie kodu powyżej wstrzyknięty HttpDataSource
zawiera nagłówek "Header: Value"
w każdym żądaniu HTTP. To zachowanie jest stałe w przypadku każdej interakcji ze źródłem HTTP.
Aby uzyskać bardziej szczegółowe informacje, możesz wstrzyknąć zdarzenie „w odpowiednim czasie” za pomocą metody ResolvingDataSource
. Ten fragment kodu pokazuje, jak wstrzyknąć nagłówki żądania tuż przed interakcją 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ć polecenia ResolvingDataSource
, aby na bieżąco modyfikować identyfikator URI, jak pokazano w tym fragmencie:
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 elementu LoadErrorHandlingPolicy
pozwala aplikacjom dostosowywać sposób, w jaki ExoPlayer reaguje na błędy wczytywania. Na przykład aplikacja może chcieć szybko ponosić błędy, zamiast ponawiać wielokrotne próby, albo zdecydować o dostosowaniu mechanizmu wycofywania, który określa czas oczekiwania 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 dostosować logikę na podstawie typu błędu lub nieudanego żądania.
Dostosowywanie flag modułu wyodrębniania
Flagi wyodrębniania można używać do dostosowywania sposobu wyodrębniania poszczególnych formatów z multimediów progresywnych. Można je ustawić w elemencie DefaultExtractorsFactory
udostępnianym przez interfejs DefaultMediaSourceFactory
. Poniższy przykład przekazuje flagę, która umożliwia wyszukiwanie na podstawie indeksu 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();
Umożliwianie wyszukiwania ze stałą szybkością transmisji
W przypadku strumieni MP3, ADTS i AMR możesz włączyć przybliżone przewijanie, korzystając z założenia o stałej szybkości transmisji bitów z flagami FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
.
Te flagi można ustawić dla poszczególnych modułów wyodrębniania za pomocą poszczególnych metod DefaultExtractorsFactory.setXyzExtractorFlags
, jak opisano powyżej. Aby włączyć wyszukiwanie ze stałą szybkością transmisji bitów we wszystkich modułach wyodrębniania, które ją obsługują, użyj DefaultExtractorsFactory.setConstantBitrateSeekingEnabled
.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
Następnie można wstrzykiwać ExtractorsFactory
za pomocą DefaultMediaSourceFactory
w sposób opisany powyżej w celu dostosowania flag modułu wyodrębniania.
Włączanie asynchronicznego kolejkowania bufora
Asynchroniczne kolejkowanie bufora to udoskonalenie potoku renderowania ExoPlayera, które obsługuje instancje MediaCodec
w trybie asynchronicznym oraz wykorzystuje dodatkowe wątki do planowania dekodowania i renderowania danych. Włączenie go może zmniejszyć liczbę pomijanych klatek i zaniżenia w dźwięku.
Asynchroniczne kolejki buforowania są domyślnie włączone na urządzeniach z Androidem 12 (poziom interfejsu API 31) i nowszym. Można je włączyć ręcznie na urządzeniach z Androidem 6.0 (poziom API 23). Rozważ włączenie tej funkcji na określonych urządzeniach, na których zauważysz pominięte klatki lub zacinanie się dźwięku, zwłaszcza w przypadku treści z zabezpieczeniami DRM lub dużą liczbą klatek.
W najprostszym przypadku musisz wstawić do odtwarzacza element DefaultRenderersFactory
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 tworzysz bezpośrednio instancje mechanizmu renderowania, przekaż instruktorowi AsynchronousMediaCodecAdapter.Factory
do konstruktorów MediaCodecVideoRenderer
i MediaCodecAudioRenderer
.
Przechwytywanie wywołań metody za pomocą funkcji ForwardingPlayer
Możesz dostosować niektóre działanie instancji Player
, pakując ją w podklasę ForwardingPlayer
i zastępując metody. Dzięki temu da się wykonać dowolną z tych czynności:
- Parametry dostępu przed przekazaniem ich do przedstawiciela
Player
. - Uzyskaj dostęp do wartości zwracanej przez przedstawiciela
Player
, zanim ją zwrócisz. - Całkowicie zaimplementuj metodę ponownie.
W przypadku zastępowania metod ForwardingPlayer
ważne jest, aby zapewnić ich samospójność i zgodność z interfejsem Player
, zwłaszcza w przypadku metod, które mają działać w sposób identyczny lub powiązany. Na przykład:
- Jeśli chcesz zastąpić każdą operację „odtwarzanie”, musisz zastąpić zarówno metodę
ForwardingPlayer.play
, jak iForwardingPlayer.setPlayWhenReady
, ponieważ wywołujący będzie oczekiwać działania tych metod, gdy metodaplayWhenReady = true
będzie taka sama. - Jeśli chcesz zmienić przyrost wartości przewijania, musisz zastąpić oba:
ForwardingPlayer.seekForward
, aby wykonać wyszukiwanie z niestandardowym przyrostem, orazForwardingPlayer.getSeekForwardIncrement
, by zgłosić poprawny i niestandardowy przyrost z powrotem do elementu wywołującego. - Jeśli chcesz kontrolować, jakie elementy
Player.Commands
są rozgłaszane przez wystąpienie odtwarzacza, musisz zastąpić wartościPlayer.getAvailableCommands()
iPlayer.isCommandAvailable()
oraz nasłuchiwać wywołania zwrotnegoPlayer.Listener.onAvailableCommandsChanged()
, aby otrzymywać powiadomienia o zmianach wprowadzanych w odtwarzaczu.
Dostosowanie MediaSource
W powyższych przykładach wstrzyknięte są niestandardowe komponenty, które będą używane podczas odtwarzania wszystkich obiektów MediaItem
przekazywanych do odtwarzacza. Jeśli wymagane jest szczegółowe dostosowanie, można też wstrzykiwać niestandardowe komponenty w poszczególnych instancjach MediaSource
, które można przekazywać bezpośrednio do odtwarzacza. Przykład poniżej pokazuje, jak dostosować ProgressiveMediaSource
, aby użyć niestandardowych elementów 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
W bibliotece znajdziesz domyślne implementacje komponentów wymienionych u góry strony przeznaczone do typowych zastosowań. Element ExoPlayer
może korzystać z tych komponentów, ale można też w niej korzystać z implementacji niestandardowych, jeśli wymagane są zachowania niestandardowe. Przykłady implementacji niestandardowych:
Renderer
– możesz zaimplementować niestandardowyRenderer
do obsługi typu multimediów, który nie jest obsługiwany przez domyślne implementacje udostępnione przez bibliotekę.TrackSelector
– wdrożenie niestandardowego elementuTrackSelector
umożliwia deweloperowi aplikacji zmianę sposobu, w jaki ścieżki wyświetlane przez elementMediaSource
są wybierane do wykorzystania przez poszczególne dostępne właściwościRenderer
.LoadControl
– zaimplementowanie niestandardowego elementuLoadControl
umożliwia deweloperowi aplikacji zmianę zasady buforowania odtwarzacza.Extractor
– jeśli chcesz obsługiwać format kontenera, który nie jest obecnie obsługiwany przez bibliotekę, rozważ wdrożenie niestandardowej klasyExtractor
.MediaSource
– implementacja niestandardowej klasyMediaSource
może być odpowiednia, jeśli chcesz, aby próbki multimediów były przesyłane do mechanizmów renderowania w niestandardowy sposób lub chcesz wdrożyć niestandardowe działanie komponowaniaMediaSource
.MediaSource.Factory
– wdrożenie niestandardowego elementuMediaSource.Factory
umożliwia aplikacji dostosowanie sposobu tworzeniaMediaSource
na podstawieMediaItem
.DataSource
– pakiet nadrzędny platformy ExoPlayer zawiera już wiele implementacjiDataSource
do różnych zastosowań. Możesz zaimplementować własną klasęDataSource
, aby wczytywać dane w inny sposób, np. za pomocą niestandardowego protokołu, niestandardowego stosu HTTP lub z niestandardowej stałej pamięci podręcznej.
Podczas tworzenia komponentów niestandardowych zalecamy te działania:
- Jeśli komponent niestandardowy musi zgłaszać zdarzenia z powrotem do aplikacji, zalecamy użycie tego samego modelu co w przypadku dotychczasowych komponentów ExoPlayer, np. za pomocą klas
EventDispatcher
lub przekazania obiektuHandler
razem z odbiornikiem do konstruktora komponentu. - Zalecaliśmy, aby komponenty niestandardowe korzystały z tego samego modelu co istniejące komponenty ExoPlayer, co umożliwi ponowną konfigurację przez aplikację podczas odtwarzania. W tym celu komponenty niestandardowe powinny implementować atrybut
PlayerMessage.Target
i odbierać zmiany konfiguracji w metodziehandleMessage
. Kod aplikacji powinien przekazywać zmiany w konfiguracji przez wywołanie metodycreateMessage
platformy ExoPlayer, skonfigurowanie wiadomości i wysłanie jej do komponentu za pomocąPlayerMessage.send
. Wysyłanie wiadomości do dostarczenia w wątku odtwarzania gwarantuje, że będą one wykonywane w tej samej kolejności, co inne operacje wykonywane w odtwarzaczu.