Den Kern der ExoPlayer-Bibliothek bildet die Player
-Oberfläche. Ein Player
bietet traditionelle allgemeine Mediaplayer-Funktionen wie die Möglichkeit, Medien zu puffern, abzuspielen, anzuhalten und zu suchen. Bei der Standardimplementierung ExoPlayer
wird nur wenige Annahmen über die Art der Medien, die wiedergegeben werden, sowie darauf, wie und wo sie gespeichert und wie sie gerendert werden, vorgenommen (damit sind nur wenige Einschränkungen erforderlich). Anstatt das Laden und Rendern von Medien direkt zu implementieren, delegieren ExoPlayer
-Implementierungen diese Arbeit an Komponenten, die beim Erstellen eines Players oder der Weitergabe neuer Medienquellen an den Player eingeschleust werden.
Die folgenden Komponenten sind in allen ExoPlayer
-Implementierungen gemein:
MediaSource
-Instanzen, die die Medien definieren, die wiedergegeben werden sollen, die Medien laden und von denen die geladenen Medien gelesen werden können. EineMediaSource
-Instanz wird aus einemMediaItem
von einemMediaSource.Factory
im Player erstellt. Sie können auch mit der medienquellenbasierten Playlist API direkt an den Player übergeben werden.- Eine
MediaSource.Factory
-Instanz, die einenMediaItem
in einenMediaSource
konvertiert.MediaSource.Factory
wird beim Erstellen des Players eingeschleust. Renderer
-Instanzen, die einzelne Komponenten der Medien rendern. Diese werden beim Erstellen des Players eingeschleust.- Ein
TrackSelector
, der von derMediaSource
bereitgestellte Tracks auswählt, die von jedem verfügbarenRenderer
genutzt werden sollen. EinTrackSelector
wird beim Erstellen des Players eingefügt. - Ein
LoadControl
, der steuert, wannMediaSource
weitere Medien zwischenspeichert und wie viele Medien zwischengespeichert werden. EinLoadControl
wird beim Erstellen des Players eingeschleust. - Ein
LivePlaybackSpeedControl
, das die Wiedergabegeschwindigkeit bei Live-Wiedergaben steuert, damit der Player nah an einem konfigurierten Live-Offset bleiben kann. EinLivePlaybackSpeedControl
wird beim Erstellen des Players eingeschleust.
Das Konzept des Einfügens von Komponenten, die einzelne Playerfunktionen implementieren, ist überall in der Bibliothek zu finden. Bei den Standardimplementierungen einiger Komponenten wird die Arbeit an weitere eingeschleuste Komponenten delegieren. Dadurch können viele Unterkomponenten einzeln durch individuell konfigurierte Implementierungen ersetzt werden.
Player-Anpassung
Im Folgenden findest du einige allgemeine Beispiele für das Anpassen des Players durch das Einfügen von Komponenten.
Netzwerkstack konfigurieren
Auf unserer Seite erfahren Sie, wie Sie den von ExoPlayer verwendeten Netzwerk-Stack anpassen.
Aus dem Netzwerk geladene Daten im Cache speichern
Weitere Informationen finden Sie in den Anleitungen zum temporären Caching und zum Herunterladen von Medien.
Serverinteraktionen anpassen
Einige Apps möchten möglicherweise HTTP-Anfragen und -Antworten abfangen. Vielleicht möchten Sie benutzerdefinierte Anfrageheader einfügen, die Antwortheader des Servers lesen, die URIs der Anfrage ändern usw. Ihre App kann sich beispielsweise authentifizieren, indem sie beim Anfordern der Mediensegmente ein Token als Header einschleust.
Im folgenden Beispiel wird gezeigt, wie diese Verhaltensweisen implementiert werden, indem ein benutzerdefiniertes DataSource.Factory
in das DefaultMediaSourceFactory
-Element injiziert 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 obigen Code-Snippet enthält die eingefügte HttpDataSource
den Header "Header: Value"
in jeder HTTP-Anfrage. Dieses Verhalten wird bei jeder Interaktion mit einer HTTP-Quelle behoben.
Für einen detaillierteren Ansatz können Sie das Just-in-Time-Verhalten mit einer ResolvingDataSource
einfügen. Das folgende Code-Snippet zeigt, wie Anfrageheader unmittelbar 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 bedarfsorientierte Ä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 möchte, anstatt es mehrfach zu versuchen, oder die Backoff-Logik anpassen, 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 zum fehlgeschlagenen Ladevorgang, um die Logik anhand des Fehlertyps oder der fehlgeschlagenen Anfrage anzupassen.
Extrahierer-Flags anpassen
Mit Extrahierer-Flags können Sie anpassen, wie einzelne Formate aus Progressive Media extrahiert werden. Sie können für das DefaultExtractorsFactory
festgelegt werden, das der DefaultMediaSourceFactory
zur Verfügung gestellt wird. Im folgenden Beispiel wird ein Flag übergeben, das die indexbasierte Suche nach MP3-Streams ermöglicht.
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();
Suche nach konstanter Bitrate aktivieren
Für MP3-, ADTS- und AMR-Streams können Sie die ungefähre Suche unter Verwendung 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 festgelegt werden, wie oben beschrieben. Verwenden Sie DefaultExtractorsFactory.setConstantBitrateSeekingEnabled
, um die Suche mit konstanter Bitrate für alle Extraktoren zu aktivieren, die sie unterstützen.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
Die ExtractorsFactory
kann dann über DefaultMediaSourceFactory
eingeschleust werden, wie oben zum Anpassen der Extrahierer-Flags beschrieben.
Asynchrone Zwischenspeicherung von Warteschlange aktivieren
Die asynchrone Zwischenspeicherwarteschlange ist eine Verbesserung in der Renderingpipeline von ExoPlayer, die MediaCodec
-Instanzen im asynchronen Modus ausführt und zusätzliche Threads verwendet, um die Decodierung und das Rendering von Daten zu planen. Wenn Sie diese Option aktivieren, können ausgelassene Frames und Audiountergänge reduziert werden.
Auf Geräten mit Android 12 (API-Level 31) und höher ist die asynchrone Zwischenspeicherwarteschlange standardmäßig aktiviert. Ab Android 6.0 (API-Ebene 23) kann sie manuell aktiviert werden. Du kannst die Funktion für bestimmte Geräte aktivieren, auf denen du abgebrochene Frames oder Audiountergänge feststellst, insbesondere wenn du DRM-geschützte Inhalte oder Inhalte mit hoher Framerate abspielst.
Im einfachsten Fall müssen Sie DefaultRenderersFactory
so in den Player einschleusen:
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 einen AsynchronousMediaCodecAdapter.Factory
an die Konstruktoren MediaCodecVideoRenderer
und MediaCodecAudioRenderer
.
Methodenaufrufe mit ForwardingPlayer
abfangen
Sie können einen Teil des Verhaltens einer Player
-Instanz anpassen, indem Sie sie in eine abgeleitete Klasse von ForwardingPlayer
einbinden und Methoden überschreiben, um eine der folgenden Aktionen auszuführen:
- Rufen Sie die Parameter auf, bevor Sie sie an den Delegat
Player
übergeben. - Greifen Sie vor der Rückgabe des Delegaten
Player
auf den Rückgabewert zu. - Implementieren Sie die Methode vollständig noch einmal.
Beim Überschreiben von ForwardingPlayer
-Methoden muss dafür gesorgt werden, dass die Implementierung selbstkonsistent und mit der Player
-Schnittstelle konform bleibt. Das gilt insbesondere, wenn es sich um Methoden handelt, die ein identisches oder ähnliches Verhalten haben sollen. Beispiele:
- Wenn Sie jeden "play"-Vorgang überschreiben möchten, müssen Sie sowohl
ForwardingPlayer.play
als auchForwardingPlayer.setPlayWhenReady
überschreiben, da ein Aufrufer erwartet, dass das Verhalten dieser Methoden inplayWhenReady = true
identisch ist. - Wenn Sie das Vorwärts-Inkrement ändern möchten, müssen Sie
ForwardingPlayer.seekForward
überschreiben, um eine Suche mit Ihrem benutzerdefinierten Inkrement durchzuführen, undForwardingPlayer.getSeekForwardIncrement
, um das richtige benutzerdefinierte Inkrement an den Aufrufer zurückzumelden. - Wenn Sie steuern möchten, welche
Player.Commands
von einer Spielerinstanz angeboten werden, müssen Sie sowohlPlayer.getAvailableCommands()
als auchPlayer.isCommandAvailable()
überschreiben und auch 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 zur Verwendung während der Wiedergabe aller MediaItem
-Objekte eingefügt, die an den Player übergeben werden. Wenn eine präzise Anpassung erforderlich ist, ist es auch möglich, benutzerdefinierte Komponenten in einzelne MediaSource
-Instanzen einzufügen, die direkt an den Player übergeben werden können. Das folgende Beispiel zeigt, wie Sie ein ProgressiveMediaSource
anpassen, um einen benutzerdefinierten DataSource.Factory
, ExtractorsFactory
und LoadErrorHandlingPolicy
zu verwenden:
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 enthält Standardimplementierungen der oben auf dieser Seite aufgeführten Komponenten für häufige Anwendungsfälle. Ein ExoPlayer
kann diese Komponenten verwenden, kann aber auch für benutzerdefinierte Implementierungen erstellt werden, wenn ein nicht standardmäßiges Verhalten erforderlich ist. Einige Anwendungsfälle für benutzerdefinierte Implementierungen sind:
Renderer
: Sie können einen benutzerdefiniertenRenderer
für einen Medientyp implementieren, der von den Standardimplementierungen der Bibliothek nicht unterstützt wird.TrackSelector
: Die Implementierung eines benutzerdefiniertenTrackSelector
ermöglicht es einem App-Entwickler, die Art und Weise zu ändern, wie von einemMediaSource
bereitgestellte Tracks für die Nutzung mit jedem der verfügbarenRenderer
s ausgewählt werden.LoadControl
: Die Implementierung eines benutzerdefiniertenLoadControl
ermöglicht einem App-Entwickler, die Pufferrichtlinie des Players zu ä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 auf benutzerdefinierte Weise an Renderer senden oder ein benutzerdefiniertesMediaSource
-Compositing-Verhalten implementieren möchten.MediaSource.Factory
: Durch die Implementierung eines benutzerdefiniertenMediaSource.Factory
kann eine Anwendung anpassen, wie einMediaSource
aus einerMediaItem
erstellt wird.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, einen benutzerdefinierten HTTP-Stack oder einen benutzerdefinierten nichtflüchtigen Cache.
Für das Erstellen benutzerdefinierter Komponenten empfehlen wir Folgendes:
- Wenn eine benutzerdefinierte Komponente Ereignisse an die App zurückmelden muss, empfehlen wir, dafür dasselbe Modell wie bei den vorhandenen ExoPlayer-Komponenten zu verwenden. Sie können 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 eine Neukonfiguration durch die App während der Wiedergabe möglich ist. Dazu sollten benutzerdefinierte Komponenten
PlayerMessage.Target
implementieren und Konfigurationsänderungen in der MethodehandleMessage
erhalten. Der Anwendungscode sollte Konfigurationsänderungen übergeben. Dazu ruft er die MethodecreateMessage
von ExoPlayer auf, konfiguriert die Nachricht und sendet sie mithilfe vonPlayerMessage.send
an die Komponente. Durch das Senden von Nachrichten, die im Wiedergabe-Thread übermittelt werden sollen, wird sichergestellt, dass sie in der richtigen Reihenfolge mit allen anderen Vorgängen ausgeführt werden, die auf dem Player ausgeführt werden.