Analytics

ExoPlayer unterstützt eine Vielzahl von Anforderungen an die Wiedergabeanalyse. Letztendlich geht es bei der Analyse darum, Daten aus Wiedergaben zu erheben, zu interpretieren, zusammenzufassen und zusammenzustellen. Diese Daten können entweder auf dem Gerät verwendet werden, z. B. für die Protokollierung, das Debugging oder um zukünftige Wiedergabeentscheidungen zu treffen, oder an einen Server gesendet werden, um die Wiedergabe auf allen Geräten zu überwachen.

In einem Analysesystem müssen in der Regel zuerst Ereignisse erfasst und dann weiterverarbeitet werden, damit sie aussagekräftig sind:

  • Ereigniserfassung: Dies kann durch Registrieren eines AnalyticsListener in einer ExoPlayer-Instanz erfolgen. Registrierte Analysen-Listener empfangen Ereignisse, sobald sie während der Verwendung des Players auftreten. Jedes Ereignis ist dem entsprechenden Media-Element in der Playlist sowie Metadaten zu Wiedergabeposition und Zeitstempel zugeordnet.
  • Ereignisverarbeitung: Bei einigen Analysesystemen werden Rohereignisse auf einen Server hochgeladen und alle Ereignisse serverseitig verarbeitet. Es ist auch möglich, Ereignisse auf dem Gerät zu verarbeiten. Das kann einfacher sein oder die Menge der Informationen reduzieren, die hochgeladen werden müssen. ExoPlayer bietet PlaybackStatsListener, mit dem Sie die folgenden Verarbeitungsschritte ausführen können:
    1. Ereignisinterpretation: Damit Ereignisse für Analysezwecke nützlich sind, müssen sie im Kontext einer einzelnen Wiedergabe interpretiert werden. Das Rohereignis einer Änderung des Playerstatus zu STATE_BUFFERING kann beispielsweise dem anfänglichen Puffern, dem erneuten Puffern oder dem Puffern nach einer Suche entsprechen.
    2. Status-Tracking: In diesem Schritt werden Ereignisse in Zähler umgewandelt. Beispielsweise können Ereignisse für Zustandsänderungen in Zähler umgewandelt werden, mit denen die Zeit in den einzelnen Wiedergabezuständen erfasst wird. Das Ergebnis ist ein grundlegender Satz von Analysedatenwerten für eine einzelne Wiedergabe.
    3. Aggregation: In diesem Schritt werden die Analysedaten aus mehreren Wiedergaben kombiniert, in der Regel durch Addieren von Zählern.
    4. Berechnung von zusammengefassten Messwerten: Viele der nützlichsten Messwerte sind solche, bei denen Durchschnittswerte berechnet oder die grundlegenden Analysedatenwerte auf andere Weise kombiniert werden. Zusammenfassende Messwerte können für einzelne oder mehrere Wiedergaben berechnet werden.

Ereigniserfassung mit AnalyticsListener

Rohdaten zu Wiedergabeereignissen vom Player werden an AnalyticsListener-Implementierungen gemeldet. Sie können ganz einfach einen eigenen Listener hinzufügen und nur die Methoden überschreiben, die Sie benötigen:

Kotlin

exoPlayer.addAnalyticsListener(
  object : AnalyticsListener {
    override fun onPlaybackStateChanged(eventTime: EventTime, @Player.State state: Int) {}

    override fun onDroppedVideoFrames(
      eventTime: EventTime,
      droppedFrames: Int,
      elapsedMs: Long,
    ) {}
  }
)

Java

exoPlayer.addAnalyticsListener(
    new AnalyticsListener() {
      @Override
      public void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) {}

      @Override
      public void onDroppedVideoFrames(
          EventTime eventTime, int droppedFrames, long elapsedMs) {}
    });

Die EventTime, die an jeden Callback übergeben wird, verknüpft das Ereignis mit einem Media-Element in der Playlist sowie mit Metadaten zur Wiedergabeposition und zum Zeitstempel:

  • realtimeMs: Die Uhrzeit des Ereignisses.
  • timeline, windowIndex und mediaPeriodId: Definiert die Playlist und das Element in der Playlist, zu dem das Ereignis gehört. mediaPeriodId enthält optionale zusätzliche Informationen, z. B. ob das Ereignis zu einer Anzeige im Artikel gehört.
  • eventPlaybackPositionMs: Die Wiedergabeposition im Element, als das Ereignis aufgetreten ist.
  • currentTimeline, currentWindowIndex, currentMediaPeriodId und currentPlaybackPositionMs: Wie oben, aber für das aktuell wiedergegebene Element. Das aktuell wiedergegebene Element kann sich von dem Element unterscheiden, zu dem das Ereignis gehört, z. B. wenn das Ereignis dem Vorab-Buffering des nächsten wiederzugebenden Elements entspricht.

Ereignisverarbeitung mit PlaybackStatsListener

PlaybackStatsListener ist ein AnalyticsListener, das die Verarbeitung von Ereignissen auf dem Gerät implementiert. Dabei werden PlaybackStats sowie Zähler und abgeleitete Messwerte berechnet, darunter:

  • Zusammenfassende Messwerte, z. B. die Gesamtwiedergabezeit.
  • Messwerte für die adaptive Wiedergabequalität, z. B. die durchschnittliche Videoauflösung.
  • Messwerte zur Rendering-Qualität, z. B. die Rate der verworfenen Frames.
  • Messwerte zur Ressourcennutzung, z. B. die Anzahl der über das Netzwerk gelesenen Byte.

Eine vollständige Liste der verfügbaren Zählungen und abgeleiteten Messwerte finden Sie in der PlaybackStatsJavadoc.

In PlaybackStatsListener werden separate PlaybackStats für jedes Media-Element in der Playlist und für jede clientseitig eingefügte Anzeige in diesen Elementen berechnet. Sie können einen Callback für PlaybackStatsListener angeben, um über abgeschlossene Wiedergaben informiert zu werden. Mit dem an den Callback übergebenen EventTime können Sie ermitteln, welche Wiedergabe abgeschlossen wurde. Es ist möglich, die Analysedaten für mehrere Wiedergaben zu aggregieren. Außerdem ist es jederzeit möglich, den PlaybackStats für die aktuelle Wiedergabesitzung mit PlaybackStatsListener.getPlaybackStats() abzufragen.

Kotlin

exoPlayer.addAnalyticsListener(
  PlaybackStatsListener(/* keepHistory= */ true) {
    eventTime: EventTime?,
    playbackStats: PlaybackStats?
    -> // Analytics data for the session started at `eventTime` is ready.
  }
)

Java

exoPlayer.addAnalyticsListener(
    new PlaybackStatsListener(
        /* keepHistory= */ true,
        (eventTime, playbackStats) -> {
          // Analytics data for the session started at `eventTime` is ready.
        }));

Der Konstruktor von PlaybackStatsListener bietet die Möglichkeit, den vollständigen Verlauf der verarbeiteten Ereignisse beizubehalten. Je nach Länge der Wiedergabe und Anzahl der Ereignisse kann dies zu einem unbekannten Speicher-Overhead führen. Sie sollten sie daher nur aktivieren, wenn Sie Zugriff auf den gesamten Verlauf der verarbeiteten Ereignisse und nicht nur auf die endgültigen Analysedaten benötigen.

PlaybackStats verwendet eine erweiterte Gruppe von Status, um nicht nur den Status der Medien, sondern auch die Wiedergabeabsicht des Nutzers und detailliertere Informationen wie den Grund für die Unterbrechung oder Beendigung der Wiedergabe anzugeben:

Wiedergabestatus Nutzerabsicht, das Spiel zu spielen Keine Absicht zu spielen
Vor der Wiedergabe JOINING_FOREGROUND NOT_STARTED, JOINING_BACKGROUND
Aktive Wiedergabe PLAYING
Unterbrochene Wiedergabe BUFFERING, SEEKING PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING, INTERRUPTED_BY_AD
Endgültige Status ENDED, STOPPED, FAILED, ABANDONED

Die Intention des Nutzers, das Spiel zu spielen, ist wichtig, um Zeiten, in denen der Nutzer aktiv auf die Fortsetzung der Wiedergabe gewartet hat, von passiven Wartezeiten zu unterscheiden. Beispiel: PlaybackStats.getTotalWaitTimeMs gibt die Gesamtzeit zurück, die im JOINING_FOREGROUND-, BUFFERING- und SEEKING-Status verbracht wurde, aber nicht die Zeit, in der die Wiedergabe pausiert wurde. Entsprechend gibt PlaybackStats.getTotalPlayAndWaitTimeMs die Gesamtzeit mit der Nutzerabsicht zurück, zu spielen, d. h. die gesamte aktive Wartezeit und die Gesamtzeit im Status PLAYING.

Verarbeitete und interpretierte Ereignisse

Mit PlaybackStatsListener und keepHistory=true können Sie verarbeitete und interpretierte Ereignisse aufzeichnen. Die resultierende PlaybackStats enthält die folgenden Ereignislisten:

  • playbackStateHistory: Eine geordnete Liste der erweiterten Wiedergabestatus mit dem EventTime, zu dem sie angewendet wurden. Sie können auch PlaybackStats.getPlaybackStateAtTime verwenden, um den Status zu einer bestimmten Uhrzeit abzurufen.
  • mediaTimeHistory: Ein Verlauf von Paaren aus Wanduhrzeit und Media-Zeit, mit denen Sie rekonstruieren können, welche Teile der Media zu welcher Zeit abgespielt wurden. Mit PlaybackStats.getMediaTimeMsAtRealtimeMs können Sie auch die Wiedergabeposition zu einer bestimmten Uhrzeit abrufen.
  • videoFormatHistory und audioFormatHistory: Geordnete Listen der Video- und Audioformate, die während der Wiedergabe verwendet wurden, mit dem EventTime, zu dem sie verwendet wurden.
  • fatalErrorHistory und nonFatalErrorHistory: Geordnete Listen schwerwiegender und nicht schwerwiegender Fehler mit dem EventTime, zu dem sie aufgetreten sind. Schwerwiegende Fehler sind Fehler, die die Wiedergabe beendet haben. Nicht schwerwiegende Fehler konnten möglicherweise behoben werden.

Analysedaten zur einmaligen Wiedergabe

Diese Daten werden automatisch erhoben, wenn Sie PlaybackStatsListener verwenden, auch wenn keepHistory=false aktiviert ist. Die endgültigen Werte sind die öffentlichen Felder, die Sie in der PlaybackStats-Javadoc finden, sowie die Wiedergabestatusdauern, die von getPlaybackStateDurationMs zurückgegeben werden. Außerdem gibt es Methoden wie getTotalPlayTimeMs und getTotalWaitTimeMs, die die Dauer bestimmter Kombinationen von Wiedergabestatus zurückgeben.

Kotlin

Log.d(
  "DEBUG",
  "Playback summary: " +
    "play time = " +
    playbackStats.totalPlayTimeMs +
    ", rebuffers = " +
    playbackStats.totalRebufferCount,
)

Java

Log.d(
    "DEBUG",
    "Playback summary: "
        + "play time = "
        + playbackStats.getTotalPlayTimeMs()
        + ", rebuffers = "
        + playbackStats.totalRebufferCount);

Analysedaten mehrerer Wiedergaben aggregieren

Sie können mehrere PlaybackStats kombinieren, indem Sie PlaybackStats.merge aufrufen. Die resultierende PlaybackStats enthält die aggregierten Daten aller zusammengeführten Wiedergaben. Der Verlauf einzelner Wiedergabeereignisse ist nicht enthalten, da diese nicht zusammengefasst werden können.

Mit PlaybackStatsListener.getCombinedPlaybackStats erhalten Sie eine aggregierte Ansicht aller Analysedaten, die im Laufe der Lebensdauer eines PlaybackStatsListener erfasst wurden.

Berechnete zusammenfassende Messwerte

Neben den grundlegenden Analysedaten bietet PlaybackStats viele Methoden zum Berechnen von zusammenfassenden Messwerten.

Kotlin

Log.d(
  "DEBUG",
  "Additional calculated summary metrics: " +
    "average video bitrate = " +
    playbackStats.meanVideoFormatBitrate +
    ", mean time between rebuffers = " +
    playbackStats.meanTimeBetweenRebuffers,
)

Java

Log.d(
    "DEBUG",
    "Additional calculated summary metrics: "
        + "average video bitrate = "
        + playbackStats.getMeanVideoFormatBitrate()
        + ", mean time between rebuffers = "
        + playbackStats.getMeanTimeBetweenRebuffers());

Weiterführende Informationen

Analysedaten mit Wiedergabemetadaten verknüpfen

Wenn Sie Analysedaten für einzelne Wiedergaben erfassen, möchten Sie die Wiedergabeanalysedaten möglicherweise mit Metadaten zu den wiedergegebenen Medien verknüpfen.

Es empfiehlt sich, mediaspezifische Metadaten mit MediaItem.Builder.setTag festzulegen. Das Media-Tag ist Teil des EventTime, das für Rohereignisse und nach Abschluss von PlaybackStats gemeldet wird. Es kann also beim Verarbeiten der entsprechenden Analysedaten einfach abgerufen werden:

Kotlin

PlaybackStatsListener(/* keepHistory= */ false) {
  eventTime: EventTime,
  playbackStats: PlaybackStats ->
  val mediaTag =
    eventTime.timeline
      .getWindow(eventTime.windowIndex, Timeline.Window())
      .mediaItem
      .localConfiguration
      ?.tag
  // Report playbackStats with mediaTag metadata.
}

Java

new PlaybackStatsListener(
    /* keepHistory= */ false,
    (eventTime, playbackStats) -> {
      Object mediaTag =
          eventTime.timeline.getWindow(eventTime.windowIndex, new Timeline.Window())
              .mediaItem
              .localConfiguration
              .tag;
      // Report playbackStats with mediaTag metadata.
    });

Benutzerdefinierte Analyseereignisse melden

Wenn Sie den Analysedaten benutzerdefinierte Ereignisse hinzufügen möchten, müssen Sie diese Ereignisse in Ihrer eigenen Datenstruktur speichern und später mit den gemeldeten PlaybackStats kombinieren. Bei Bedarf können Sie DefaultAnalyticsCollector erweitern, um EventTime-Instanzen für Ihre benutzerdefinierten Ereignisse zu generieren und sie wie im folgenden Beispiel an die bereits registrierten Listener zu senden.

Kotlin

@OptIn(UnstableApi::class)
private interface ExtendedListener : AnalyticsListener {
  fun onCustomEvent(eventTime: EventTime)
}

@OptIn(UnstableApi::class)
private class ExtendedCollector : DefaultAnalyticsCollector(Clock.DEFAULT) {

  fun customEvent() {
    val eventTime = super.generateCurrentPlayerMediaPeriodEventTime()
    super.sendEvent(eventTime, CUSTOM_EVENT_ID) { listener: AnalyticsListener ->
      if (listener is ExtendedListener) {
        listener.onCustomEvent(eventTime)
      }
    }
  }
}

@OptIn(UnstableApi::class)
fun useExtendedAnalyticsCollector(context: Context) {
  // Usage - Setup and listener registration.
  val player = ExoPlayer.Builder(context).setAnalyticsCollector(ExtendedCollector()).build()
  player.addAnalyticsListener(
    object : ExtendedListener {
      override fun onCustomEvent(eventTime: EventTime) {
        // Save custom event for analytics data.
      }
    }
  )
  // Usage - Triggering the custom event.
  (player.analyticsCollector as ExtendedCollector).customEvent()
}

Java

@OptIn(markerClass = UnstableApi.class)
private interface ExtendedListener extends AnalyticsListener {
  void onCustomEvent(EventTime eventTime);
}

@OptIn(markerClass = UnstableApi.class)
private static class ExtendedCollector extends DefaultAnalyticsCollector {
  public ExtendedCollector() {
    super(Clock.DEFAULT);
  }

  public void customEvent() {
    AnalyticsListener.EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
    sendEvent(
        eventTime,
        CUSTOM_EVENT_ID,
        listener -> {
          if (listener instanceof ExtendedListener) {
            ((ExtendedListener) listener).onCustomEvent(eventTime);
          }
        });
  }
}

@OptIn(markerClass = UnstableApi.class)
public static void useExtendedAnalyticsCollector(Context context) {
  // Usage - Setup and listener registration.
  ExoPlayer player =
      new ExoPlayer.Builder(context).setAnalyticsCollector(new ExtendedCollector()).build();
  player.addAnalyticsListener(
      (ExtendedListener)
          eventTime -> {
            // Save custom event for analytics data.
          });
  // Usage - Triggering the custom event.
  ((ExtendedCollector) player.getAnalyticsCollector()).customEvent();
}