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. ZMediaItem
w odtwarzaczuMediaSource.Factory
tworzy się wystąpienieMediaSource
. Można je też przekazać bezpośrednio do odtwarzacza za pomocą interfejsu media source based playlist API.MediaSource.Factory
przekształcaMediaItem
wMediaSource
. 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 przezMediaSource
do odtworzenia przez każdą dostępnąRenderer
. Podczas tworzenia gracza dodawany jestTrackSelector
.LoadControl
, który określa, kiedyMediaSource
ma buforować więcej multimediów oraz ile multimediów ma być buforowanych. Gdy gracz zostanie utworzony, do gry zostanie wstrzyknięty elementLoadControl
.LivePlaybackSpeedControl
, który kontroluje szybkość odtwarzania podczas transmisji na żywo, aby odtwarzacz pozostawał jak najbliżej skonfigurowanego opóźnienia. IdentyfikatorLivePlaybackSpeedControl
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żąco i pobierania 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 MediaCodecVideoRenderer
i MediaCodecAudioRenderer
.
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 iForwardingPlayer.setPlayWhenReady
, ponieważ wywołujący oczekuje, że zachowanie tych metod będzie identyczne w przypadkuplayWhenReady = 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, orazForwardingPlayer.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ównoPlayer.getAvailableCommands()
, jak iPlayer.isCommandAvailable()
, a także słuchać wywołania zwrotnegoPlayer.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ć niestandardowyRenderer
, aby obsługiwać typ multimediów, który nie jest obsługiwany przez domyślne implementacje udostępniane przez bibliotekę.TrackSelector
– wdrożenie niestandardowegoTrackSelector
pozwala deweloperowi aplikacji zmienić sposób, w jaki utwory udostępnione przezMediaSource
są wybierane do odtwarzania przez poszczególne dostępneRenderer
.LoadControl
– jeśli zaimplementujesz niestandardowyLoadControl
, 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 zachowanieMediaSource
, możesz użyć niestandardowej klasyMediaSource
.MediaSource.Factory
– zastosowanie niestandardowego elementuMediaSource.Factory
umożliwia aplikacji dostosowanie sposobu tworzenia obiektuMediaSource
na podstawieMediaItem
.DataSource
– pakiet upstream ExoPlayera zawiera już kilka implementacjiDataSource
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 obiektuHandler
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 metodziehandleMessage
. 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.