Das Herzstück der ExoPlayer-Bibliothek ist die Player
-Schnittstelle. Ein Player
bietet traditionelle Funktionen eines Mediaplayers auf oberster Ebene, z. B. die Möglichkeit, Medien zu puffern, abzuspielen, anzuhalten und zu suchen. Bei der Standardimplementierung ExoPlayer
werden nur wenige Annahmen über die Art der wiedergegebenen Medien, die Art und Weise, wie sie gespeichert und gerendert werden, getroffen. Daher gelten auch nur wenige Einschränkungen. Anstatt das Laden und Rendern von Medien direkt zu implementieren, delegieren ExoPlayer
-Implementierungen diese Arbeit an Komponenten, die eingeschleust werden, wenn ein Player erstellt oder neue Medienquellen an den Player übergeben werden.
Komponenten, die allen ExoPlayer
-Implementierungen gemeinsam sind:
MediaSource
-Instanzen, die Medien für die Wiedergabe definieren, die Medien laden und von denen die geladenen Medien gelesen werden können. EineMediaSource
-Instanz wird von einemMediaSource.Factory
im Player aus einerMediaItem
erstellt. Sie können auch über die Media Source Based Playlist API direkt an den Player übergeben werden.- Eine
MediaSource.Factory
-Instanz, die eineMediaItem
in eineMediaSource
konvertiert.MediaSource.Factory
wird beim Erstellen des Players eingefügt. Renderer
-Instanzen, die einzelne Komponenten der Medien rendern. Sie werden beim Erstellen des Players eingefügt.- Eine
TrackSelector
, die von derMediaSource
bereitgestellte Titel auswählt, die von allen verfügbarenRenderer
verwendet werden sollen. Beim Erstellen des Players wirdTrackSelector
eingefügt. - Ein
LoadControl
, das steuert, wann und wie viel Medien vomMediaSource
im Puffer gespeichert werden. Beim Erstellen des Players wirdLoadControl
eingefügt. - Ein
LivePlaybackSpeedControl
, das die Wiedergabegeschwindigkeit bei der Livewiedergabe steuert, damit der Player nah an einem konfigurierten Live-Offset bleibt. EinLivePlaybackSpeedControl
wird beim Erstellen des Players eingefügt.
Das Konzept, Komponenten einzufügen, die Teile der Playerfunktion implementieren, ist in der gesamten Bibliothek vorhanden. Die Standardimplementierungen einiger Komponenten delegieren Aufgaben an weitere injizierte Komponenten. So können viele Unterkomponenten einzeln durch benutzerdefinierte Implementierungen ersetzt werden.
Spieleranpassung
Im Folgenden sind einige gängige Beispiele für die Anpassung des Players durch das Einfügen von Komponenten beschrieben.
Netzwerkstack konfigurieren
Auf dieser Seite erfährst du, wie du den von ExoPlayer verwendeten Netzwerkstack anpassen kannst.
Aus dem Netzwerk geladene Daten im Cache speichern
Weitere Informationen finden Sie in den Anleitungen zum vorübergehenden On-the-fly-Caching und zum Herunterladen von Medien.
Serverinteraktionen anpassen
Einige Apps möchten möglicherweise HTTP-Anfragen und ‑Antworten abfangen. Du kannst beispielsweise benutzerdefinierte Anfrageheader einfügen, die Antwortheader des Servers lesen oder die URIs der Anfragen ändern. Deine App kann sich beispielsweise authentifizieren, indem sie beim Anfordern der Mediensegmente ein Token als Header einfügt.
Das folgende Beispiel zeigt, wie diese Verhaltensweisen implementiert werden, indem ein benutzerdefinierter DataSource.Factory
in den DefaultMediaSourceFactory
eingefügt wird:
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();
Im Code-Snippet oben enthält das eingefügte HttpDataSource
den Header "Header: Value"
in jeder HTTP-Anfrage. Dieses Verhalten ist für jede Interaktion mit einer HTTP-Quelle fest.
Für einen detaillierteren Ansatz können Sie mithilfe einer ResolvingDataSource
ein Just-in-Time-Verhalten einschleusen. Das folgende Code-Snippet zeigt, wie Anfrageheader kurz vor der Interaktion mit einer HTTP-Quelle eingefügt werden:
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)));
Sie können auch einen ResolvingDataSource
verwenden, um Just-in-Time-Änderungen am URI vorzunehmen, wie im folgenden Snippet gezeigt:
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)));
Fehlerbehandlung anpassen
Durch die Implementierung eines benutzerdefinierten LoadErrorHandlingPolicy
können Apps anpassen, wie ExoPlayer auf Ladefehler reagiert. Beispielsweise kann es sein, dass eine Anwendung schnell ausfallen soll, anstatt viele Versuche zu wiederholen, oder die Backoff-Logik anpassen möchte, die steuert, wie lange der Spieler zwischen den einzelnen Wiederholungen wartet. Das folgende Snippet zeigt, wie eine benutzerdefinierte Backoff-Logik implementiert wird:
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();
Das Argument LoadErrorInfo
enthält weitere Informationen zur fehlgeschlagenen Ladeaktion, damit die Logik je nach Fehlertyp oder fehlgeschlagener Anfrage angepasst werden kann.
Extrahierungs-Flags anpassen
Mit Extractor-Flags kannst du anpassen, wie einzelne Formate aus progressiven Medien extrahiert werden. Sie können auf dem DefaultExtractorsFactory
festgelegt werden, das für die DefaultMediaSourceFactory
bereitgestellt wird. Im folgenden Beispiel wird ein Flag übergeben, das die indexbasierte Suche für MP3-Streams aktiviert.
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();
Konstante Bitratensuche aktivieren
Bei MP3-, ADTS- und AMR-Streams kannst du die ungefähre Suche mit einer Annahme einer konstanten Bitrate mit FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
-Flags aktivieren.
Diese Flags können für einzelne Extraktoren mit den einzelnen DefaultExtractorsFactory.setXyzExtractorFlags
-Methoden wie oben beschrieben festgelegt werden. Wenn du die Suche nach konstanter Bitrate für alle unterstützten Extractor aktivieren möchtest, verwende DefaultExtractorsFactory.setConstantBitrateSeekingEnabled
.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
Das ExtractorsFactory
kann dann über DefaultMediaSourceFactory
eingeschleust werden, wie oben zum Anpassen der Extrahierer-Flags beschrieben.
Asynchrone Pufferwarteschlange aktivieren
Die asynchrone Pufferwarteschlange ist eine Verbesserung in der Rendering-Pipeline von ExoPlayer. Dabei werden MediaCodec
-Instanzen im asynchronen Modus ausgeführt und zusätzliche Threads werden verwendet, um die Dekodierung und das Rendering von Daten zu planen. Wenn du sie aktivierst, können weniger Frames verloren gehen und es kommt seltener zu Audioaussetzern.
Die asynchrone Pufferwarteschlange ist auf Geräten mit Android 12 (API-Level 31) und höher standardmäßig aktiviert und kann ab Android 6.0 (API-Level 23) manuell aktiviert werden. Erwägen Sie, die Funktion für bestimmte Geräte zu aktivieren, auf denen Sie verworfene Frames oder Audiounterläufe beobachten, insbesondere bei der Wiedergabe von DRM-geschützten Inhalten oder Inhalten mit hoher Framerate.
Im einfachsten Fall musst du dem Player einen DefaultRenderersFactory
einfügen. Gehe dazu so vor:
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();
Wenn Sie Renderer direkt instanziieren, übergeben Sie den Konstruktoren von MediaCodecVideoRenderer
und MediaCodecAudioRenderer
einen AsynchronousMediaCodecAdapter.Factory
.
Methodenaufrufe mit ForwardingPlayer
abfangen
Sie können einen Teil des Verhaltens einer Player
-Instanz anpassen, indem Sie sie in eine abgeleitete Klasse von ForwardingPlayer
einschließen und Methoden überschreiben, um Folgendes zu tun:
- Rufe die Parameter auf, bevor du sie an den Delegaten
Player
weitergibst. - Rufen Sie den Rückgabewert aus dem Delegaten
Player
ab, bevor Sie ihn zurückgeben. - Implementieren Sie die Methode vollständig neu.
Wenn Sie ForwardingPlayer
-Methoden überschreiben, ist es wichtig, dass die Implementierung einheitlich bleibt und der Player
-Benutzeroberfläche entspricht. Das gilt insbesondere für Methoden, die dasselbe oder ein ähnliches Verhalten haben sollen. Beispiel:
- Wenn Sie jeden „play“-Vorgang überschreiben möchten, müssen Sie sowohl
ForwardingPlayer.play
als auchForwardingPlayer.setPlayWhenReady
überschreiben, da ein Aufrufer davon ausgeht, dass das Verhalten dieser Methoden beiplayWhenReady = true
identisch ist. - Wenn du das Increment für die Vorwärtssuche ändern möchtest, musst du sowohl
ForwardingPlayer.seekForward
überschreiben, um eine Suche mit dem benutzerdefinierten Increment durchzuführen, als auchForwardingPlayer.getSeekForwardIncrement
, um den richtigen benutzerdefinierten Wert an den Aufrufer zurückzugeben. - Wenn du festlegen möchtest, welche
Player.Commands
von einer Playerinstanz beworben werden, musst du sowohlPlayer.getAvailableCommands()
als auchPlayer.isCommandAvailable()
überschreiben und denPlayer.Listener.onAvailableCommandsChanged()
-Callback überwachen, um über Änderungen informiert zu werden, die vom zugrunde liegenden Player stammen.
MediaSource-Anpassung
In den Beispielen oben werden benutzerdefinierte Komponenten für die Wiedergabe aller MediaItem
-Objekte eingefügt, die an den Player übergeben werden. Wenn eine detaillierte Anpassung erforderlich ist, können benutzerdefinierte Komponenten auch in einzelne MediaSource
-Instanzen eingefügt werden, die direkt an den Player übergeben werden können. Im folgenden Beispiel wird gezeigt, wie eine ProgressiveMediaSource
so angepasst wird, dass benutzerdefinierte DataSource.Factory
-, ExtractorsFactory
- und LoadErrorHandlingPolicy
-Elemente verwendet werden:
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));
Benutzerdefinierte Komponenten erstellen
Die Bibliothek bietet Standardimplementierungen der oben auf dieser Seite aufgeführten Komponenten für gängige Anwendungsfälle. Ein ExoPlayer
kann diese Komponenten verwenden, aber auch für benutzerdefinierte Implementierungen entwickelt werden, wenn nicht standardmäßiges Verhalten erforderlich ist. Einige Anwendungsfälle für benutzerdefinierte Implementierungen:
Renderer
: Du kannst eine benutzerdefinierteRenderer
implementieren, um einen Medientyp zu verarbeiten, der von den Standardimplementierungen der Bibliothek nicht unterstützt wird.TrackSelector
: Mit einer benutzerdefiniertenTrackSelector
können App-Entwickler ändern, wie die von einemMediaSource
freigegebenen Tracks für die Nutzung durch die einzelnen verfügbarenRenderer
s ausgewählt werden.LoadControl
: Mit einer benutzerdefiniertenLoadControl
können App-Entwickler die Pufferrichtlinie des Players ändern.Extractor
: Wenn Sie ein Containerformat unterstützen müssen, das derzeit nicht von der Bibliothek unterstützt wird, sollten Sie eine benutzerdefinierteExtractor
-Klasse implementieren.MediaSource
: Die Implementierung einer benutzerdefiniertenMediaSource
-Klasse kann sinnvoll sein, wenn Sie Medienbeispiele erhalten möchten, die auf benutzerdefinierte Weise an Renderer gesendet werden, oder wenn Sie benutzerdefiniertesMediaSource
-Compositing-Verhalten implementieren möchten.MediaSource.Factory
: Mit einer benutzerdefiniertenMediaSource.Factory
kann in einer Anwendung die Erstellung einerMediaSource
aus einerMediaItem
angepasst werden.DataSource
: Das Upstream-Paket von ExoPlayer enthält bereits eine Reihe vonDataSource
-Implementierungen für verschiedene Anwendungsfälle. Sie können eine eigeneDataSource
-Klasse implementieren, um Daten auf andere Weise zu laden, z. B. über ein benutzerdefiniertes Protokoll, mit einem benutzerdefinierten HTTP-Stack oder aus einem benutzerdefinierten persistenten Cache.
Beim Erstellen benutzerdefinierter Komponenten empfehlen wir Folgendes:
- Wenn eine benutzerdefinierte Komponente Ereignisse an die App zurückgeben muss, empfehlen wir, dasselbe Modell wie bei vorhandenen ExoPlayer-Komponenten zu verwenden. Du kannst beispielsweise
EventDispatcher
-Klassen verwenden oder einenHandler
zusammen mit einem Listener an den Konstruktor der Komponente übergeben. - Wir empfehlen, für benutzerdefinierte Komponenten dasselbe Modell wie für vorhandene ExoPlayer-Komponenten zu verwenden, damit die App sie während der Wiedergabe neu konfigurieren kann. Dazu sollten benutzerdefinierte Komponenten
PlayerMessage.Target
implementieren und Konfigurationsänderungen in der MethodehandleMessage
erhalten. Der Anwendungscode sollte Konfigurationsänderungen übergeben, indem diecreateMessage
-Methode von ExoPlayer aufgerufen, die Nachricht konfiguriert und mitPlayerMessage.send
an die Komponente gesendet wird. Das Senden von Nachrichten, die im Wiedergabe-Thread zugestellt werden sollen, stellt sicher, dass sie in der richtigen Reihenfolge mit allen anderen Vorgängen auf dem Player ausgeführt werden.