Livestreaming

ExoPlayer spielt die meisten adaptiven Livestreams sofort ohne spezielle Konfiguration ab. Weitere Informationen finden Sie auf der Seite Unterstützte Formate.

Adaptive Livestreams bieten ein Fenster mit verfügbaren Medien, das in regelmäßigen Intervallen aktualisiert wird, um sich mit der aktuellen Echtzeit zu bewegen. Das bedeutet, dass sich die Wiedergabeposition immer irgendwo in diesem Fenster befindet, und zwar in den meisten Fällen nahe bei der aktuellen Echtzeit, in der der Stream produziert wird. Die Differenz zwischen der aktuellen Echtzeit und der Wiedergabeposition wird als Live-Offset bezeichnet.

Live-Wiedergaben erkennen und beobachten

Jedes Mal, wenn ein Live-Fenster aktualisiert wird, erhalten registrierte Player.Listener-Instanzen ein onTimelineChanged-Ereignis. Du kannst Details zur aktuellen Livewiedergabe abrufen, indem du verschiedene Player- und Timeline.Window-Methoden abfragen, wie unten aufgeführt und in der folgenden Abbildung dargestellt.

Live-Fenster

  • Player.isCurrentWindowLive gibt an, ob das aktuell wiedergegebene Medienelement ein Livestream ist. Dieser Wert gilt auch dann, wenn der Livestream bereits beendet ist.
  • Player.isCurrentWindowDynamic gibt an, ob das aktuell wiedergegebene Medienelement noch aktualisiert wird. Dies gilt in der Regel für noch nicht beendete Livestreams. Dieses Flag gilt in manchen Fällen auch für Nicht-Livestreams.
  • Player.getCurrentLiveOffset gibt den Versatz zwischen der aktuellen Echtzeit und der Wiedergabeposition (falls verfügbar) zurück.
  • Player.getDuration gibt die Länge des aktuellen Live-Fensters zurück.
  • Player.getCurrentPosition gibt die Wiedergabeposition relativ zum Beginn des Live-Fensters zurück.
  • Player.getCurrentMediaItem gibt das aktuelle Medienelement zurück, wobei MediaItem.liveConfiguration von der App bereitgestellte Überschreibungen für die Parameter des Ziel-Live-Offsets und der Live-Offset-Anpassung enthält.
  • Player.getCurrentTimeline gibt die aktuelle Medienstruktur in einem Timeline-Element zurück. Die aktuelle Timeline.Window kann mit Player.getCurrentWindowIndex und Timeline.getWindow aus dem Timeline abgerufen werden. In Window:
    • Window.liveConfiguration enthält die Parameter für den Live-Zieloffset und die Live-Offsetanpassung. Diese Werte basieren auf Informationen in den Medien und allen von Apps bereitgestellten Überschreibungen, die in MediaItem.liveConfiguration festgelegt sind.
    • Window.windowStartTimeMs ist die Zeit seit der Unix-Epoche, zu der das Live-Fenster beginnt.
    • Window.getCurrentUnixTimeMs ist die Zeit seit der Unix-Epoche der aktuellen Echtzeit. Dieser Wert kann um eine bekannte Zeitdifferenz zwischen Server und Client korrigiert werden.
    • Window.getDefaultPositionMs ist die Position im Live-Fenster, an der der Player die Wiedergabe standardmäßig startet.

Suche in Livestreams

Mit Player.seekTo können Sie zu einer beliebigen Stelle im Live-Fenster springen. Die übergebene Suchposition bezieht sich auf den Beginn des Live-Fensters. So sucht seekTo(0) beispielsweise zum Anfang des Live-Fensters. Der Player versucht, denselben Live-Offset wie die angesteuerte Position nach einem Suchvorgang beizubehalten.

Das Live-Fenster hat auch eine Standardposition, an der die Wiedergabe starten soll. Diese Position befindet sich normalerweise nahe am Live-Edge. Um zur Standardposition zu wechseln, rufen Sie Player.seekToDefaultPosition auf.

Benutzeroberfläche für die Live-Wiedergabe

Die Standard-UI-Komponenten von ExoPlayer zeigen die Dauer des Live-Fensters und die aktuelle Wiedergabeposition darin. Das bedeutet, dass die Position bei jeder Aktualisierung des Live-Fensters rückwärts springt. Wenn Sie ein anderes Verhalten benötigen, z. B. die Unix-Zeit oder den aktuellen Live-Offset, können Sie PlayerControlView verzweigen und an Ihre Anforderungen anpassen.

Parameter für die Live-Wiedergabe konfigurieren

ExoPlayer verwendet einige Parameter, um den Offset der Wiedergabeposition vom Live-Edge aus zu steuern sowie den Bereich der Wiedergabegeschwindigkeiten, mit dem dieser Offset angepasst werden kann.

ExoPlayer ruft die Werte für diese Parameter von drei Stellen in absteigender Reihenfolge nach Priorität ab (der erste gefundene Wert wird verwendet):

  • Pro MediaItem werden Werte an MediaItem.Builder.setLiveConfiguration übergeben.
  • Globale Standardwerte wurden für DefaultMediaSourceFactory festgelegt.
  • Werte werden direkt aus dem Medium gelesen.

Kotlin

// Global settings.
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(DefaultMediaSourceFactory(context).setLiveTargetOffsetMs(5000))
    .build()

// Per MediaItem settings.
val mediaItem =
  MediaItem.Builder()
    .setUri(mediaUri)
    .setLiveConfiguration(
      MediaItem.LiveConfiguration.Builder().setMaxPlaybackSpeed(1.02f).build()
    )
    .build()
player.setMediaItem(mediaItem)

Java

// Global settings.
ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setLiveTargetOffsetMs(5000))
        .build();

// Per MediaItem settings.
MediaItem mediaItem =
    new MediaItem.Builder()
        .setUri(mediaUri)
        .setLiveConfiguration(
            new MediaItem.LiveConfiguration.Builder().setMaxPlaybackSpeed(1.02f).build())
        .build();
player.setMediaItem(mediaItem);

Verfügbare Konfigurationswerte sind:

  • targetOffsetMs: Der Live-Ziel-Offset. Der Player wird versuchen, während der Wiedergabe möglichst nahe an diesen Live-Offset zu kommen.
  • minOffsetMs: Das minimal zulässige Live-Offset. Auch wenn der Offset an die aktuellen Netzwerkbedingungen angepasst wird, versucht der Player nicht, diesen Offset während der Wiedergabe zu unterschreiten.
  • maxOffsetMs: Der maximal zulässige Live-Offset. Auch wenn der Offset an die aktuellen Netzwerkbedingungen angepasst wird, versucht der Player nicht, diesen Offset während der Wiedergabe zu überschreiten.
  • minPlaybackSpeed: Die minimale Wiedergabegeschwindigkeit, die der Player für einen Fallback verwenden kann, wenn versucht wird, den Ziel-Live-Offset zu erreichen.
  • maxPlaybackSpeed: Die maximale Wiedergabegeschwindigkeit, die der Player verwenden kann, um den Zielwert für den Live-Offset zu erreichen.

Anpassung der Wiedergabegeschwindigkeit

Beim Abspielen eines Livestreams mit niedriger Latenz passt ExoPlayer den Live-Offset durch eine leichte Änderung der Wiedergabegeschwindigkeit an. Der Player versucht, den von den Medien oder der App angegebenen Ziel-Live-Offset zu erreichen, versucht aber auch, auf sich ändernde Netzwerkbedingungen zu reagieren. Wenn es beispielsweise während der Wiedergabe zu einer Neupufferung kommt, verlangsamt der Player die Wiedergabe leicht, um sich weiter weg vom Live-Edge zu bewegen. Wenn das Netzwerk dann stabil genug wird, um wieder näher am Live-Edge zu spielen, beschleunigt der Player die Wiedergabe, um zum Ziel-Live-Offset zurückzukehren.

Wenn die automatische Anpassung der Wiedergabegeschwindigkeit nicht erwünscht ist, kannst du sie deaktivieren, indem du die Eigenschaften minPlaybackSpeed und maxPlaybackSpeed auf 1.0f setzt. Ebenso kann es für Livestreams ohne niedrige Latenz aktiviert werden. Dazu setzen Sie sie explizit auf andere Werte als 1.0f. Weitere Informationen zum Festlegen dieser Attribute finden Sie im Abschnitt zur Konfiguration oben.

Algorithmus zur Anpassung der Wiedergabegeschwindigkeit anpassen

Wenn die Geschwindigkeitsanpassung aktiviert ist, definiert ein LivePlaybackSpeedControl, welche Anpassungen vorgenommen werden. Es ist möglich, eine benutzerdefinierte LivePlaybackSpeedControl zu implementieren oder die Standardimplementierung DefaultLivePlaybackSpeedControl anzupassen. In beiden Fällen kann beim Erstellen des Players eine Instanz festgelegt werden:

Kotlin

val player =
  ExoPlayer.Builder(context)
    .setLivePlaybackSpeedControl(
      DefaultLivePlaybackSpeedControl.Builder().setFallbackMaxPlaybackSpeed(1.04f).build()
    )
    .build()

Java

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setLivePlaybackSpeedControl(
            new DefaultLivePlaybackSpeedControl.Builder()
                .setFallbackMaxPlaybackSpeed(1.04f)
                .build())
        .build();

Für DefaultLivePlaybackSpeedControl sind folgende Anpassungsparameter relevant:

  • fallbackMinPlaybackSpeed und fallbackMaxPlaybackSpeed: Die minimalen und maximalen Wiedergabegeschwindigkeiten, die zur Anpassung verwendet werden können, wenn weder das Medium noch die von der App bereitgestellte MediaItem Limits definieren.
  • proportionalControlFactor: Steuert, wie gleichmäßig die Geschwindigkeitsanpassung erfolgt. Ein hoher Wert führt dazu, dass Anpassungen plötzlicher und reaktiver sind, aber auch mit größerer Wahrscheinlichkeit hörbar sind. Ein kleinerer Wert führt zu einem flüssigeren Übergang zwischen den Geschwindigkeiten, allerdings auf Kosten einer geringeren Geschwindigkeit.
  • targetLiveOffsetIncrementOnRebufferMs: Dieser Wert wird bei jeder Neuzwischenspeicherung zum Ziel-Live-Offset hinzugefügt, um vorsichtiger fortzufahren. Diese Funktion kann deaktiviert werden, indem Sie den Wert auf 0 setzen.
  • minPossibleLiveOffsetSmoothingFactor: Ein exponentieller Glättungsfaktor, mit dem das minimal mögliche Live-Offset auf Basis der aktuell zwischengespeicherten Medien verfolgt wird. Ein Wert sehr nahe 1 bedeutet, dass die Schätzung vorsichtiger ist und länger dauern kann, um sich an verbesserte Netzwerkbedingungen anzupassen. Ein niedrigerer Wert bedeutet hingegen, dass die Schätzung schneller angepasst wird und ein höheres Risiko besteht, dass es zu Puffern kommt.

BehindLiveWindowException und ERROR_CODE_Behind_LIVE_WINDOW

Die Wiedergabeposition kann hinter das Live-Fenster fallen, z. B. wenn der Player pausiert ist oder lange genug gepuffert wird. In diesem Fall schlägt die Wiedergabe fehl und über Player.Listener.onPlayerError wird eine Ausnahme mit dem Fehlercode ERROR_CODE_BEHIND_LIVE_WINDOW gemeldet. Der Anwendungscode möchte diese Fehler möglicherweise beheben, indem die Wiedergabe an der Standardposition fortgesetzt wird. Die PlayerActivity der Demo-App veranschaulicht diesen Ansatz.

Kotlin

override fun onPlayerError(error: PlaybackException) {
  if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) {
    // Re-initialize player at the live edge.
    player.seekToDefaultPosition()
    player.prepare()
  } else {
    // Handle other errors
  }
}

Java

@Override
public void onPlayerError(PlaybackException error) {
  if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) {
    // Re-initialize player at the live edge.
    player.seekToDefaultPosition();
    player.prepare();
  } else {
    // Handle other errors
  }
}