Spielerereignisse

Änderungen am Status des Players (z. B. Start der Wiedergabe, Pufferung oder Fehler) lösen Ereignisse aus, die an registrierte Player.Listener Instanzen gesendet werden. Diese Ereignisse werden durch ganzzahlige Konstanten dargestellt und durch Player.Event und Player.Eventsdefiniert.

Player.Listener registrieren

Player-Ereignisse werden an registrierte Player.Listener-Instanzen gemeldet. So registrieren Sie einen Listener, der solche Ereignisse empfangen soll:

Kotlin

// Add a listener to receive events from the player.
player.addListener(listener)

Java

// Add a listener to receive events from the player.
player.addListener(listener);

Wenn Sie Kotlin verwenden, können Sie auch die suspendierenden Erweiterungsfunktionen des Moduls media3-common-ktx verwenden, um Ereignisse mit Coroutinen zu beobachten. In diesem Fall müssen Sie keinen Player.Listener explizit registrieren oder die Registrierung aufheben.

Wiedergabeereignisse mit Player.Listener beobachten

Player.Listener hat leere Standardmethoden. Sie müssen also nur die Methoden implementieren, die Sie interessieren. Eine vollständige Beschreibung der Methoden und der Zeitpunkte, zu denen sie aufgerufen werden, finden Sie in der Javadoc. Einige der wichtigsten Methoden werden unten ausführlicher beschrieben.

Listener können entweder einzelne Ereignis-Callbacks oder einen generischen onEvents-Callback implementieren, der aufgerufen wird, nachdem ein oder mehrere Ereignisse gleichzeitig aufgetreten sind. Siehe Individual callbacks vs onEvents für eine Erläuterung, welche für verschiedene Anwendungsfälle bevorzugt werden sollte.

Änderungen des Wiedergabestatus

Änderungen des Player-Status können empfangen werden, indem onPlaybackStateChanged(@State int state) in einem registrierten Player.Listener implementiert wird. Der Player kann sich in einem von vier Wiedergabestatus befinden:

  • Player.STATE_IDLE: Dies ist der Anfangsstatus, der Status, wenn der Player angehalten wird, und wenn die Wiedergabe fehlgeschlagen ist. In diesem Status hält der Player nur begrenzte Ressourcen.
  • Player.STATE_BUFFERING: Der Player kann nicht sofort von seiner aktuellen Position aus wiedergeben. Das liegt meist daran, dass weitere Daten geladen werden müssen.
  • Player.STATE_READY: Der Player kann sofort von seiner aktuellen Position aus wiedergeben.
  • Player.STATE_ENDED: Der Player hat alle Medien wiedergegeben.

Zusätzlich zu diesen Status hat der Player ein playWhenReady-Flag, um die Wiedergabeabsicht des Nutzers anzugeben. Änderungen an diesem Flag können empfangen werden, indem onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason) implementiert wird.

Ein Player gibt wieder (d. h., seine Position wird erhöht und Medien werden dem Nutzer präsentiert), wenn alle drei folgenden Bedingungen erfüllt sind:

  • Der Player befindet sich im Status Player.STATE_READY.
  • playWhenReady ist true.
  • Die Wiedergabe wird nicht aus einem Grund unterdrückt, der von Player.getPlaybackSuppressionReason zurückgegeben wird.

Anstatt diese Eigenschaften einzeln zu prüfen, kann Player.isPlaying aufgerufen werden. Änderungen an diesem Status können empfangen werden, indem onIsPlayingChanged(boolean isPlaying) implementiert wird:

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onIsPlayingChanged(isPlaying: Boolean) {
      if (isPlaying) {
        // Active playback.
      } else {
        // Not playing because playback is paused, ended, suppressed, or the player
        // is buffering, stopped or failed. Check player.playWhenReady,
        // player.playbackState, player.playbackSuppressionReason and
        // player.playerError for details.
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onIsPlayingChanged(boolean isPlaying) {
        if (isPlaying) {
          // Active playback.
        } else {
          // Not playing because playback is paused, ended, suppressed, or the player
          // is buffering, stopped or failed. Check player.getPlayWhenReady,
          // player.getPlaybackState, player.getPlaybackSuppressionReason and
          // player.getPlaybackError for details.
        }
      }
    });

Wiedergabefehler

Fehler, die dazu führen, dass die Wiedergabe fehlschlägt, können empfangen werden, indem onPlayerError(PlaybackException error) in einem registrierten Player.Listener implementiert wird. Wenn ein Fehler auftritt, wird diese Methode unmittelbar vor dem Übergang des Wiedergabestatus zu Player.STATE_IDLE aufgerufen. Fehlgeschlagene oder angehaltene Wiedergaben können mit ExoPlayer.prepare wiederholt werden.

Einige Player-Implementierungen übergeben Instanzen von Unterklassen von PlaybackException, um zusätzliche Informationen zum Fehler zu liefern. For example, ExoPlayer übergibt ExoPlaybackException mit type, rendererIndex und anderen ExoPlayer-spezifischen Feldern.

Das folgende Beispiel zeigt, wie Sie erkennen können, wenn eine Wiedergabe aufgrund eines HTTP-Netzwerkproblems fehlgeschlagen ist:

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onPlayerError(error: PlaybackException) {
      val cause = error.cause
      if (cause is HttpDataSourceException) {
        // An HTTP error occurred.
        val httpError = cause
        // It's possible to find out more about the error both by casting and by querying
        // the cause.
        if (httpError is InvalidResponseCodeException) {
          // Cast to InvalidResponseCodeException and retrieve the response code, message
          // and headers.
        } else {
          // Try calling httpError.getCause() to retrieve the underlying cause, although
          // note that it may be null.
        }
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onPlayerError(PlaybackException error) {
        @Nullable Throwable cause = error.getCause();
        if (cause instanceof HttpDataSourceException) {
          // An HTTP error occurred.
          HttpDataSourceException httpError = (HttpDataSourceException) cause;
          // It's possible to find out more about the error both by casting and by querying
          // the cause.
          if (httpError instanceof HttpDataSource.InvalidResponseCodeException) {
            // Cast to InvalidResponseCodeException and retrieve the response code, message
            // and headers.
          } else {
            // Try calling httpError.getCause() to retrieve the underlying cause, although
            // note that it may be null.
          }
        }
      }
    });

Playlist-Übergänge

Immer wenn der Player zu einem neuen Medienelement in der Playlist wechselt onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int reason) wird für registrierte Player.Listener Objekte aufgerufen. Der Grund gibt an, ob es sich um einen automatischen Übergang, eine Suche (z. B. nach dem Aufrufen von player.next()), eine Wiederholung desselben Elements oder eine Änderung der Playlist handelt (z. B. wenn das aktuell wiedergegebene Element entfernt wird).

Metadaten

Die von player.getCurrentMediaMetadata() zurückgegebenen Metadaten können aus vielen Gründen geändert werden: Playlist-Übergänge, In‑Stream-Metadaten-Updates oder Aktualisieren des aktuellen MediaItem während der Wiedergabe.

Wenn Sie an Metadatenänderungen interessiert sind, z. B. um eine Benutzeroberfläche zu aktualisieren, auf der der aktuelle Titel angezeigt wird, können Sie onMediaMetadataChanged beobachten.

Springen zu Videopositionen aktiviert

Das Aufrufen von Player.seekTo-Methoden führt zu einer Reihe von Callbacks für registrierte Player.Listener-Instanzen:

  1. onPositionDiscontinuity mit reason=DISCONTINUITY_REASON_SEEK. Dies ist das direkte Ergebnis des Aufrufs von Player.seekTo. Der Callback hat PositionInfo-Felder für die Position vor und nach der Suche.
  2. onPlaybackStateChanged mit allen sofortigen Statusänderungen im Zusammenhang mit der Suche. Eine solche Änderung ist nicht immer vorhanden.

Einzelne Callbacks im Vergleich zu onEvents

Listener können zwischen der Implementierung einzelner Callbacks wie onIsPlayingChanged(boolean isPlaying), und dem generischen onEvents(Player player, Events events) Callback wählen. Der generische Callback bietet Zugriff auf das Player-Objekt und gibt die Menge der events an, die gleichzeitig aufgetreten sind. Dieser Callback wird immer nach den Callbacks aufgerufen, die den einzelnen Ereignissen entsprechen.

Kotlin

override fun onEvents(player: Player, events: Player.Events) {
  if (
    events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) ||
      events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)
  ) {
    uiModule.updateUi(player)
  }
}

Java

@Override
public void onEvents(Player player, Events events) {
  if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)
      || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) {
    uiModule.updateUi(player);
  }
}

Einzelne Ereignisse sollten in den folgenden Fällen bevorzugt werden:

  • Der Listener ist an den Gründen für Änderungen interessiert. Beispiele sind die Gründe für onPlayWhenReadyChanged oder onMediaItemTransition.
  • Der Listener reagiert nur auf die neuen Werte, die über Callback-Parameter bereitgestellt werden, oder löst etwas anderes aus, das nicht von den Callback-Parametern abhängt.
  • Die Listener-Implementierung bevorzugt eine klare, lesbare Angabe dessen, was das Ereignis ausgelöst hat, im Methodennamen.
  • Der Listener meldet sich bei einem Analysesystem, das über alle einzelnen Ereignisse und Statusänderungen informiert werden muss.

Der generische onEvents(Player player, Events events) sollte in den folgenden Fällen bevorzugt werden:

  • Der Listener möchte für mehrere Ereignisse dieselbe Logik auslösen. Beispiel: Aktualisieren einer Benutzeroberfläche für onPlaybackStateChanged und onPlayWhenReadyChanged.
  • Der Listener benötigt Zugriff auf das Player-Objekt, um weitere Ereignisse auszulösen, z. B. die Suche nach einem Übergang des Medienelements.
  • Der Listener beabsichtigt, mehrere Statuswerte zu verwenden, die über separate Callbacks gemeldet werden, oder in Kombination mit Player-Getter-Methoden. Beispiel: Verwendung von Player.getCurrentWindowIndex() mit dem in onTimelineChanged bereitgestellten Timeline ist nur innerhalb des onEvents Callbacks sicher.
  • Der Listener ist daran interessiert, ob Ereignisse logisch zusammen aufgetreten sind. Beispiel: onPlaybackStateChanged zu STATE_BUFFERING aufgrund eines Übergangs des Medienelements.

In einigen Fällen müssen Listener die einzelnen Callbacks mit dem generischen onEvents-Callback kombinieren, z. B. um Gründe für Änderungen an Medienelementen mit onMediaItemTransition aufzuzeichnen, aber erst zu reagieren, wenn alle Statusänderungen in onEvents zusammen verwendet werden können.

Wiedergabeereignisse mit Coroutinen beobachten

Alternativ können Sie mit Player.listenTo eine Kotlin-Coroutine starten und das relevante Player.Event angeben:

Beachten Sie, dass Player.listen und Player.listenTo von jedem Thread aus aufgerufen werden können, während das Callback-Lambda immer im Thread aufgerufen wird, der mit Player.getApplicationLooper verknüpft ist. Daher können Sie auch dann sicher auf Player-Methoden und Statuseigenschaften innerhalb des Callback-Lambdas zugreifen, wenn die Coroutine in einem anderen Thread gestartet wurde.

Änderungen des Wiedergabestatus

coroutineScope.launch {
  player.listenTo(Player.EVENT_IS_PLAYING_CHANGED) {
    // `Player` is a receiver scope for this trailing lambda
    if (isPlaying) {
      // Active playback.
    } else {
      // Not playing.
    }
  }
}

Wiedergabefehler

coroutineScope.launch {
  player.listenTo(Player.EVENT_PLAYER_ERROR) {
    val error = playerError ?: return@listenTo
    val cause = error.cause
    if (cause is HttpDataSourceException) {
      // An HTTP error occurred.
      if (cause is InvalidResponseCodeException) {
        // Retrieve the response code, message and headers
      } else {
        // Try calling cause.cause to retrieve the underlying cause
      }
    }
  }
}

Einzelne Callbacks im Vergleich zu onEvents

Wenn Sie Player-Ereignisse in einer Coroutine beobachten, stellen Sie immer die Implementierung für den onEvents-Callback bereit, im Gegensatz zu einem einzelnen Callback. Sie können zwischen Player.listen und Player.listenTo wählen, je nachdem, welche Ereignisse einen Aufruf Ihres Lambdas auslösen sollen. Die Funktionen sind ansonsten gleichwertig:

anhören

coroutineScope.launch {
  player.listen { events ->
    // `Player` is a receiver scope for this trailing lambda
    if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
      // Access the player state directly from the receiver
      updateUi(playbackState)
    }

    if (events.contains(Player.EVENT_PLAYER_ERROR)) {
      // Access the error directly from the player
      handleError(playerError)
    }
  }
}

anhören

coroutineScope.launch {
  player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYER_ERROR) { events ->
    // `Player` is a receiver scope for this trailing lambda
    if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
      // Access the player state directly from the receiver
      updateUi(playbackState)
    }

    if (events.contains(Player.EVENT_PLAYER_ERROR)) {
      // Access the error directly from the player
      handleError(playerError)
    }
  }
}

Wenn Sie an mehreren Ereignistypen interessiert sind, können Sie eine Liste von Ereignissen an Player.listenTo übergeben. Ihr Lambda wird immer aufgerufen, wenn eines dieser Ereignisse eintritt. Sie können den Parameter Events prüfen, um zu sehen, welche Ereignisse tatsächlich ausgelöst wurden:

coroutineScope.launch {
  player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYER_ERROR) { events ->
    // Unclear which event got triggered without querying `events` parameter
    // The following function will fire whenever either one is caught
    updateUiAndHandleError(playbackState, playerError)
  }
}

Da diese Funktionen mit onEvents arbeiten, bieten sie keinen Zugriff auf die transienten Argumente, die an einzelne Callbacks übergeben werden, z. B. den Grund in onMediaItemTransition(..., int reason) oder die oldPosition in onPositionDiscontinuity(...). Wenn Ihre Logik von diesen spezifischen Argumenten abhängt (und sie nicht als Statuseigenschaften für den Player verfügbar sind), sollten Sie stattdessen die Standardschnittstelle Player.Listener verwenden.

AnalyticsListener verwenden

Wenn Sie ExoPlayer verwenden, kann ein AnalyticsListener mit dem Player registriert werden, indem Sie addAnalyticsListener aufrufen. AnalyticsListener -Implementierungen können detaillierte Ereignisse beobachten, die für Analyse- und Loggingzwecke nützlich sein können. Weitere Informationen finden Sie auf der Analyseseite.

EventLogger verwenden

EventLogger ist ein AnalyticsListener, der direkt von der Bibliothek für Loggingzwecke bereitgestellt wird. Fügen Sie EventLogger einem ExoPlayer hinzu, um mit einer einzigen Zeile nützliches zusätzliches Logging zu aktivieren:

Kotlin

player.addAnalyticsListener(EventLogger())

Java

player.addAnalyticsListener(new EventLogger());

Weitere Informationen finden Sie auf der Seite zum Debug-Logging.

Ereignisse an bestimmten Wiedergabepositionen auslösen

In einigen Anwendungsfällen müssen Ereignisse an bestimmten Wiedergabepositionen ausgelöst werden. Dies wird mit PlayerMessage unterstützt. Eine PlayerMessage kann mit ExoPlayer.createMessage erstellt werden. Die Wiedergabeposition, an der sie ausgeführt werden soll, kann mit PlayerMessage.setPosition festgelegt werden. Nachrichten werden standardmäßig im Wiedergabethread ausgeführt. Dies kann jedoch mit PlayerMessage.setLooper angepasst werden. Mit PlayerMessage.setDeleteAfterDelivery können Sie festlegen, ob die Nachricht jedes Mal ausgeführt werden soll, wenn die angegebene Wiedergabeposition erreicht wird (dies kann aufgrund von Such- und Wiederholungsmodi mehrmals vorkommen), oder nur beim ersten Mal. Nachdem die PlayerMessage konfiguriert wurde, kann sie mit PlayerMessage.send geplant werden.

Kotlin

player
  .createMessage { messageType: Int, payload: Any? -> }
  .setLooper(Looper.getMainLooper())
  .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120000)
  .setPayload(customPayloadData)
  .setDeleteAfterDelivery(false)
  .send()

Java

player
    .createMessage(
        (messageType, payload) -> {
          // Do something at the specified playback position.
        })
    .setLooper(Looper.getMainLooper())
    .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120_000)
    .setPayload(customPayloadData)
    .setDeleteAfterDelivery(false)
    .send();