ExoPlayer unterstützt eine Vielzahl von Anforderungen an die Wiedergabeanalyse. Letztendlich geht es bei der Analyse darum, Daten aus Wiedergaben zu erfassen, zu interpretieren, zu aggregieren und zusammenzufassen. Diese Daten können entweder auf dem Gerät verwendet werden, z. B. für Logging, Debugging oder zur Information über zukünftige Wiedergabeentscheidungen, oder an einen Server gemeldet werden, um die Wiedergabe auf allen Geräten zu überwachen.
Ein Analysesystem muss in der Regel zuerst Ereignisse erfassen und sie dann weiterverarbeiten, um sie aussagekräftig zu machen:
- Ereigniserfassung: Dazu können Sie einen
AnalyticsListenerfür eineExoPlayer-Instanz registrieren. Registrierte Analyse-Listener empfangen Ereignisse, sobald sie während der Nutzung des Players auftreten. Jedes Ereignis ist mit der entsprechenden Mediendatei in der Playlist sowie mit Metadaten zur Wiedergabeposition und zum Zeitstempel verknüpft. - Ereignisverarbeitung: Einige Analysesysteme laden Rohergebnisse auf einen Server hoch, wobei die gesamte Ereignisverarbeitung serverseitig erfolgt. Es ist auch möglich, Ereignisse auf dem Gerät zu verarbeiten. Das kann einfacher sein oder die Menge der hochzuladenden Informationen reduzieren. ExoPlayer bietet
PlaybackStatsListener, mit dem Sie die folgenden Verarbeitungsschritte ausführen können:- Ereignisinterpretation: Damit Ereignisse für Analysezwecke nützlich sind, müssen sie im Kontext einer einzelnen Wiedergabe interpretiert werden. Das Rohereignis einer Änderung des Player-Status zu
STATE_BUFFERINGkann beispielsweise der anfänglichen Zwischenspeicherung, einer erneuten Zwischenspeicherung oder einer Zwischenspeicherung nach einem Suchvorgang entsprechen. - Status-Tracking: In diesem Schritt werden Ereignisse in Zähler umgewandelt. Beispielsweise können Ereignisse für Statusänderungen in Zähler umgewandelt werden, die erfassen, wie viel Zeit in jedem Wiedergabestatus verbracht wird. Das Ergebnis ist ein grundlegender Satz von Analysedatenwerten für eine einzelne Wiedergabe.
- Aggregation: In diesem Schritt werden die Analysedaten aus mehreren Wiedergaben kombiniert, in der Regel durch Addieren von Zählern.
- Berechnung von zusammengefassten Messwerten: Viele der nützlichsten Messwerte sind solche, die Durchschnittswerte berechnen oder die grundlegenden Analysedatenwerte auf andere Weise kombinieren. Zusammenfassende Messwerte können für einzelne oder mehrere Wiedergaben berechnet werden.
- Ereignisinterpretation: Damit Ereignisse für Analysezwecke nützlich sind, müssen sie im Kontext einer einzelnen Wiedergabe interpretiert werden. Das Rohereignis einer Änderung des Player-Status zu
Ereigniserfassung mit AnalyticsListener
Unformatierte Wiedergabeereignisse vom Player werden an AnalyticsListener-Implementierungen gemeldet. Sie können einen eigenen Listener hinzufügen und nur die Methoden überschreiben, die Sie interessieren:
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) {} });
Das EventTime, das an jeden Callback übergeben wird, verknüpft das Ereignis mit einer Mediendatei in der Playlist sowie mit Metadaten zur Wiedergabeposition und zum Zeitstempel:
realtimeMs: Die Uhrzeit des Ereignisses.timeline,windowIndexundmediaPeriodId: Definiert die Playlist und das Element in der Playlist, zu dem das Ereignis gehört.mediaPeriodIdenthält optionale zusätzliche Informationen, z. B. ob das Ereignis zu einer Anzeige im Element gehört.eventPlaybackPositionMs: Die Wiedergabeposition im Element, als das Ereignis aufgetreten ist.currentTimeline,currentWindowIndex,currentMediaPeriodIdundcurrentPlaybackPositionMs: Wie oben beschrieben, 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 der Vorpufferung des nächsten wiederzugebenden Elements entspricht.
Ereignisverarbeitung mit PlaybackStatsListener
PlaybackStatsListener ist ein AnalyticsListener, der die Ereignisverarbeitung auf dem Gerät implementiert. Es berechnet PlaybackStats mit Zählern und abgeleiteten Messwerten, darunter:
- Zusammenfassende Messwerte, z. B. die Gesamtwiedergabezeit.
- Messwerte zur adaptiven Wiedergabequalität, z. B. die durchschnittliche Videoauflösung.
- Messwerte zur Renderingqualitä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 im PlaybackStats-Javadoc.
PlaybackStatsListener berechnet separate PlaybackStats für jede Mediendatei in der Playlist und für jede clientseitige Anzeige, die in diese Elemente eingefügt wird. Sie können einen Callback für PlaybackStatsListener bereitstellen, um über abgeschlossene Wiedergaben informiert zu werden, und die an den Callback übergebene EventTime verwenden, um zu ermitteln, welche Wiedergabe abgeschlossen wurde. Es ist möglich, die Analysedaten für mehrere Wiedergaben zu aggregieren. Es ist auch möglich, die PlaybackStats für die aktuelle Wiedergabesitzung jederzeit 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. Beachten Sie, dass dies je nach Länge der Wiedergabe und Anzahl der Ereignisse zu einem unbekannten Arbeitsspeicher-Overhead führen kann. Sie sollten sie daher nur aktivieren, wenn Sie Zugriff auf den vollständigen Verlauf der verarbeiteten Ereignisse und nicht nur auf die endgültigen Analysedaten benötigen.
Beachte, dass PlaybackStats einen erweiterten Satz von Zuständen verwendet, um nicht nur den Zustand der Medien, sondern auch die Nutzerabsicht, die Medien abzuspielen, und detailliertere Informationen anzugeben, z. B. warum die Wiedergabe unterbrochen oder beendet wurde:
| Wiedergabestatus | Nutzerabsicht, 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 |
| Endzustände | ENDED, STOPPED, FAILED, ABANDONED |
Die Nutzerabsicht, etwas abzuspielen, 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 in den Status JOINING_FOREGROUND, BUFFERING und SEEKING verbracht wurde, aber nicht die Zeit, in der die Wiedergabe pausiert war. Ebenso 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
Sie können verarbeitete und interpretierte Ereignisse mit PlaybackStatsListener und keepHistory=true aufzeichnen. Die resultierende PlaybackStats enthält die folgenden Ereignislisten:
playbackStateHistory: Eine sortierte Liste der erweiterten Wiedergabestatus mit derEventTime, ab der sie gelten. Sie können auchPlaybackStats.getPlaybackStateAtTimeverwenden, um den Status zu einer bestimmten Uhrzeit abzurufen.mediaTimeHistory: Ein Verlauf von Paaren aus Uhrzeit und Medienzeit, mit dem Sie rekonstruieren können, welche Teile der Medien zu welcher Zeit abgespielt wurden. Sie können auchPlaybackStats.getMediaTimeMsAtRealtimeMsverwenden, um die Wiedergabeposition zu einer bestimmten Uhrzeit abzurufen.videoFormatHistoryundaudioFormatHistory: Sortierte Listen der Video- und Audioformate, die während der Wiedergabe verwendet wurden, mit derEventTime, bei der sie verwendet wurden.fatalErrorHistoryundnonFatalErrorHistory: Sortierte Listen mit schwerwiegenden und nicht schwerwiegenden Fehlern mit demEventTime, bei dem sie aufgetreten sind. Schwerwiegende Fehler sind solche, die die Wiedergabe beendet haben, während nicht schwerwiegende Fehler möglicherweise behoben werden konnten.
Analysedaten für einzelne Wiedergaben
Diese Daten werden automatisch erhoben, wenn Sie PlaybackStatsListener verwenden, auch mit keepHistory=false. Die endgültigen Werte sind die öffentlichen Felder, die Sie im PlaybackStats-Javadoc finden, und die von getPlaybackStateDurationMs zurückgegebenen Dauern des Wiedergabestatus. Außerdem gibt es Methoden wie getTotalPlayTimeMs und getTotalWaitTimeMs, die die Dauer bestimmter Wiedergabestatuskombinationen 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);
Aggregierte Analysedaten mehrerer Wiedergaben
Sie können mehrere PlaybackStats-Objekte 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.
PlaybackStatsListener.getCombinedPlaybackStats kann verwendet werden, um eine aggregierte Ansicht aller Analysedaten zu erhalten, die während der Lebensdauer einer PlaybackStatsListener erfasst wurden.
Berechnete zusammenfassende Messwerte
Zusätzlich zu den grundlegenden Analysedaten bietet PlaybackStats viele Methoden zur Berechnung 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());
Themen für Fortgeschrittene
Analysedaten mit Wiedergabe-Metadaten verknüpfen
Wenn du Analysedaten für einzelne Wiedergaben erhebst, kannst du die Wiedergabeanalysedaten mit Metadaten zu den wiedergegebenen Medien verknüpfen.
Es empfiehlt sich, medienspezifische Metadaten mit MediaItem.Builder.setTag festzulegen.
Das Medien-Tag ist Teil der EventTime, die für Rohereignisse und nach Abschluss von PlaybackStats gemeldet werden. Es kann also beim Verarbeiten der entsprechenden Analysedaten 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. Falls es hilfreich ist, können Sie DefaultAnalyticsCollector erweitern, um EventTime-Instanzen für Ihre benutzerdefinierten Ereignisse zu generieren und sie an die bereits registrierten Listener zu senden, wie im folgenden Beispiel gezeigt.
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(); }