Anpassung

Das Herzstück der ExoPlayer-Bibliothek ist die Player-Schnittstelle. Ein Player die traditionelle High-Level-Mediaplayer-Funktionen Medien zwischenspeichern, wiedergeben, pausieren und suchen. Die Standardimplementierung ExoPlayer ist sind darauf ausgelegt, nur wenige Annahmen zu treffen (und folglich nur wenige Einschränkungen) Art der Medien, die wiedergegeben werden, wie und wo sie gespeichert werden und wie sie gerendert. Anstatt das Laden und Rendern von Medien direkt zu implementieren, ExoPlayer-Implementierungen delegiert diese Arbeit an injizierte Komponenten. Ein Player wird erstellt oder neue Medienquellen an den Player übergeben. Die folgenden Komponenten sind in allen ExoPlayer-Implementierungen gemeinsam:

  • MediaSource-Instanzen, die die wiederzugebenden Medien definieren, die Medien laden und aus dem die geladenen Medien gelesen werden können. Eine MediaSource-Instanz wird erstellt aus einem MediaItem durch einen MediaSource.Factory im Player. Sie können auch Mit der auf Medienquellen basierenden Playlist API können sie direkt an den Player übergeben werden.
  • Eine MediaSource.Factory-Instanz, die eine MediaItem in eine MediaSource konvertiert. Die MediaSource.Factory wird beim Erstellen des Players eingefügt.
  • Renderer-Instanzen, die einzelne Komponenten der Medien rendern. Dies sind bei der Erstellung des Players eingefügt wird.
  • Ein TrackSelector, der von MediaSource bereitgestellte Tracks auswählt, von jedem verfügbaren Renderer verbraucht. TrackSelector wird eingeschleust wenn der Player erstellt wird.
  • Ein LoadControl, das steuert, wann die MediaSource mehr Medien zwischenspeichert, und wie viel Medien zwischengespeichert werden. LoadControl wird eingeschleust, wenn der Player erstellt.
  • Ein LivePlaybackSpeedControl, das die Wiedergabegeschwindigkeit während des Livestreams steuert damit der Player in der Nähe eines konfigurierten Live-Offsets bleibt. A LivePlaybackSpeedControl wird beim Erstellen des Players eingefügt.

Konzept der Injektion von Komponenten, die Teile des Players implementieren Funktionalität in der gesamten Bibliothek vorhanden ist. Die Standardimplementierungen von Einige Komponenten delegieren Arbeit an weitere eingeschleuste Komponenten. Dadurch können viele werden einzeln durch Implementierungen ersetzt, die benutzerdefiniert konfiguriert werden.

Player-Anpassung

Einige gängige Beispiele für die Anpassung des Players durch das Einfügen von Komponenten sind: beschrieben.

Netzwerkstack konfigurieren

Auf dieser Seite erfahren Sie, wie Sie den von ExoPlayer verwendeten Netzwerkstapel anpassen.

Aus dem Netzwerk geladene Daten im Cache speichern

In den Leitfäden finden Sie temporäres spontanes Caching und Medien herunterladen.

Serverinteraktionen anpassen

Einige Apps möchten möglicherweise HTTP-Anfragen und -Antworten abfangen. Vielleicht möchten Sie benutzerdefinierte Anfrage-Header einfügen, Antwort-Header des Servers lesen, Anforderungen URIs enthalten. So kann sich Ihre App beispielsweise durch Einschleusung ein Token als Header, wenn die Mediensegmente angefordert werden.

Das folgende Beispiel zeigt, wie Sie diese Verhaltensweisen durch Einfügen einer benutzerdefinierten DataSource.Factory in 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();

Im obigen Code-Snippet enthält das eingefügte HttpDataSource den Header "Header: Value" in jeder HTTP-Anfrage. Dieses Verhalten ist fest für alle mit einer HTTP-Quelle interagieren.

Für einen detaillierteren Ansatz können Sie Just-in-Time-Verhalten mit einer ResolvingDataSource Das folgende Code-Snippet zeigt, kurz vor der Interaktion mit einer HTTP-Quelle:

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 ein ResolvingDataSource verwenden, um Just-in-Time-Änderungen am URI, 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 die wie ExoPlayer auf Ladefehler reagiert. Wenn z. B. eine App schnell ausfallen möchte, oder die Backoff-Logik anpassen, legt fest, wie lange der Spieler zwischen den Wiederholungen wartet. Das folgende Snippet zeigt, wie Sie eine benutzerdefinierte Backoff-Logik implementieren:

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 Laden in die Logik basierend auf dem Fehlertyp oder der fehlgeschlagenen Anfrage anpassen.

Extrahierer-Flags anpassen

Mit Extrahierer-Flags kann die Extraktion einzelner Formate angepasst werden. von Progressive Media. Sie können auf dem DefaultExtractorsFactory festgelegt werden, DefaultMediaSourceFactory bereitgestellt. Im folgenden Beispiel wird ein Flag übergeben: das eine 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();

Konstante Bitratensuche aktivieren

Bei MP3-, ADTS- und AMR-Streams kannst du die ungefähre Suche mit einem eine konstante Bitratenannahme mit FLAG_ENABLE_CONSTANT_BITRATE_SEEKING-Flags. Diese Flags können für einzelne Extraktoren mithilfe der jeweiligen DefaultExtractorsFactory.setXyzExtractorFlags-Methoden wie oben beschrieben. Bis eine konstante Bitratesuche für alle Extraktoren aktivieren, die diese Funktion unterstützen, verwenden Sie DefaultExtractorsFactory.setConstantBitrateSeekingEnabled

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

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

Das ExtractorsFactory kann dann über DefaultMediaSourceFactory als oben zum Anpassen der Extrahierer-Flags beschrieben.

Asynchrone Pufferwarteschlangen aktivieren

Asynchrone Pufferwarteschlangen sind eine Verbesserung des Renderings von ExoPlayer Pipeline, die MediaCodec-Instanzen im asynchronen Modus ausführt und verwendet zusätzliche Threads, um die Decodierung und Darstellung von Daten zu planen. Aktivieren dass Frames und Audio-Unterläufe reduziert werden können.

Asynchrone Zwischenspeicherwarteschlangen sind auf Geräten mit Android 12 standardmäßig aktiviert (API-Level 31) und höher und können ab Android 6.0 (API-Level 23) manuell aktiviert werden. Aktivieren Sie die Funktion gegebenenfalls für bestimmte Geräte, auf denen die Probleme aufgetreten sind. Frames oder Audio-Unterläufe ab, insbesondere bei der Wiedergabe von DRM-geschützten oder hohen Frame-Rates Inhalte.

Im einfachsten Fall müssen Sie ein DefaultRenderersFactory in den Player folgendermaßen:

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 AsynchronousMediaCodecAdapter.Factory zu MediaCodecVideoRenderer und MediaCodecAudioRenderer-Konstruktoren.

Methodenaufrufe mit ForwardingPlayer abfangen

Sie können einen Teil des Verhaltens einer Player-Instanz anpassen, indem Sie sie einschließen eine abgeleitete Klasse von ForwardingPlayer und Überschreiben von Methoden, Folgendes:

  • Rufen Sie Parameter auf, bevor Sie sie an den Bevollmächtigten Player übergeben.
  • Rufen Sie den Rückgabewert des Delegats Player auf, bevor Sie ihn zurückgeben.
  • Implementieren Sie die Methode vollständig neu.

Beim Überschreiben von ForwardingPlayer-Methoden muss darauf geachtet werden, Implementierung bleibt selbstständig und entspricht den Player insbesondere wenn es um Methoden geht, identisches oder verwandtes Verhalten. Beispiel:

  • Wenn Sie jede Wiedergabe überschreiben möchten, müssen Sie beide Werte überschreiben, ForwardingPlayer.play und ForwardingPlayer.setPlayWhenReady, da ein erwartet, dass das Verhalten dieser Methoden identisch ist, playWhenReady = true
  • Wenn Sie das Inkrement der Vorwärtsspulen ändern möchten, müssen Sie beide ForwardingPlayer.seekForward, um eine Suche mit deinem benutzerdefinierten und ForwardingPlayer.getSeekForwardIncrement, um richtige angepasste Stufe zurück an den Anrufer.
  • Wenn du steuern möchtest, welche Player.Commands von einem Spieler beworben werden müssen Sie sowohl Player.getAvailableCommands() als auch Player.isCommandAvailable() und hören Sie sich auch die Player.Listener.onAvailableCommandsChanged()-Callback, um benachrichtigt zu werden über die vom zugrunde liegenden Player stammen.

MediaSource-Anpassung

In den Beispielen oben werden benutzerdefinierte Komponenten für die Wiedergabe aller Elemente eingefügt, MediaItem-Objekte, die an den Player übergeben werden. Wo ist eine fein abgestufte Anpassung ist es auch möglich, benutzerdefinierte Komponenten in einzelne MediaSource-Instanzen, die direkt an den Spieler übergeben werden können. Das Beispiel unten sehen Sie, wie Sie ein ProgressiveMediaSource anpassen, um ein benutzerdefiniertes DataSource.Factory, ExtractorsFactory und 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));

Benutzerdefinierte Komponenten erstellen

Die Bibliothek bietet Standardimplementierungen der oben aufgeführten Komponenten. auf dieser Seite für häufige Anwendungsfälle. Ein ExoPlayer kann diese Komponenten verwenden, können auch für die Verwendung benutzerdefinierter Implementierungen erstellt werden, wenn nicht standardmäßige Verhaltensweisen erforderlich. Einige Anwendungsfälle für benutzerdefinierte Implementierungen:

  • Renderer: Sie können eine benutzerdefinierte Renderer implementieren, um eine Medientyp wird von den Standardimplementierungen des Bibliothek.
  • TrackSelector: Durch die Implementierung einer benutzerdefinierten TrackSelector wird eine App zugelassen. die Art und Weise zu ändern, in der Tracks, die durch einen MediaSource dargestellt werden, verwendet werden. für die Nutzung durch jeden der verfügbaren Renderers ausgewählt.
  • LoadControl: Durch die Implementierung einer benutzerdefinierten LoadControl wird eine App zugelassen. die Pufferrichtlinie des Players zu ändern.
  • Extractor – wenn ein Containerformat unterstützt werden muss, das derzeit nicht unterstützt wird von der Bibliothek unterstützt wird, können Sie eine benutzerdefinierte Extractor-Klasse implementieren.
  • MediaSource – Die Implementierung einer benutzerdefinierten MediaSource-Klasse kann ist angemessen, wenn Sie Medienbeispiele abrufen möchten, die Sie an Renderer in einem oder wenn Sie eine benutzerdefinierte MediaSource-Zusammensetzung implementieren möchten verhalten.
  • MediaSource.Factory – Benutzerdefinierte MediaSource.Factory implementieren ermöglicht einer Anwendung, die Art und Weise anzupassen, in der ein MediaSource erstellt wird. von einem MediaItem.
  • DataSource: Das Upstream-Paket von ExoPlayer enthält bereits eine Reihe von DataSource-Implementierungen für verschiedene Anwendungsfälle. Vielleicht möchten Sie Implementieren Sie Ihre eigene DataSource-Klasse, um Daten auf andere Weise zu laden, z. B. über Ein benutzerdefiniertes Protokoll, unter Verwendung eines benutzerdefinierten HTTP-Stacks oder von einem benutzerdefinierten persistenten Cache gespeichert werden.

Beim Erstellen benutzerdefinierter Komponenten empfehlen wir Folgendes:

  • Wenn eine benutzerdefinierte Komponente Ereignisse an die App melden muss, empfehlen wir dass Sie dabei dasselbe Modell wie bei den bestehenden ExoPlayer-Komponenten verwenden, Beispiel für die Verwendung von EventDispatcher-Klassen oder die gemeinsame Übergabe von Handler mit Listener für den Konstruktor der Komponente
  • Wir empfehlen, dass benutzerdefinierte Komponenten dasselbe Modell wie der vorhandene ExoPlayer verwenden Komponenten, um eine Neukonfiguration durch die App während der Wiedergabe zu ermöglichen. Gehen Sie dazu wie folgt vor: Benutzerdefinierte Komponenten sollten PlayerMessage.Target implementieren und Konfigurationsänderungen in der Methode handleMessage. Der Anwendungscode sollte Konfigurationsänderungen übergeben, indem Sie die Methode createMessage von ExoPlayer aufrufen konfigurieren und mithilfe von PlayerMessage.send. Nachrichten werden im Wiedergabethread gesendet stellt sicher, dass sie in der richtigen Reihenfolge mit allen anderen Vorgängen ausgeführt werden, im Player ausgeführt wird.