xref: /aosp_15_r20/external/exoplayer/tree_15dc86382f17a24a3e881e52e31a810c1ea44b49/docs/analytics.md (revision 30877f796caf59d855b10b687a5d6b3918d765cb)
1*30877f79SAndroid Build Coastguard Worker---
2*30877f79SAndroid Build Coastguard Workertitle: Analytics
3*30877f79SAndroid Build Coastguard Worker---
4*30877f79SAndroid Build Coastguard Worker
5*30877f79SAndroid Build Coastguard WorkerExoPlayer supports a wide range of playback analytics needs. Ultimately,
6*30877f79SAndroid Build Coastguard Workeranalytics is about collecting, interpreting, aggregating and summarizing data
7*30877f79SAndroid Build Coastguard Workerfrom playbacks. This data can be used either on the device, for example for
8*30877f79SAndroid Build Coastguard Workerlogging, debugging, or to inform future playback decisions, or reported to a
9*30877f79SAndroid Build Coastguard Workerserver to monitor playbacks across all devices.
10*30877f79SAndroid Build Coastguard Worker
11*30877f79SAndroid Build Coastguard WorkerAn analytics system usually needs to collect events first, and then process them
12*30877f79SAndroid Build Coastguard Workerfurther to make them meaningful:
13*30877f79SAndroid Build Coastguard Worker
14*30877f79SAndroid Build Coastguard Worker* **Event collection**:
15*30877f79SAndroid Build Coastguard Worker  This can be done by registering an `AnalyticsListener` on an `ExoPlayer`
16*30877f79SAndroid Build Coastguard Worker  instance. Registered analytics listeners receive events as they occur during
17*30877f79SAndroid Build Coastguard Worker  usage of the player. Each event is associated with the corresponding media
18*30877f79SAndroid Build Coastguard Worker  item in the playlist, as well as playback position and timestamp metadata.
19*30877f79SAndroid Build Coastguard Worker* **Event processing**:
20*30877f79SAndroid Build Coastguard Worker  Some analytics systems upload raw events to a server, with all event
21*30877f79SAndroid Build Coastguard Worker  processing performed server-side. It's also possible to process events on the
22*30877f79SAndroid Build Coastguard Worker  device, and doing so may be simpler or reduce the amount of information that
23*30877f79SAndroid Build Coastguard Worker  needs to be uploaded. ExoPlayer provides `PlaybackStatsListener`, which
24*30877f79SAndroid Build Coastguard Worker  allows you to perform the following processing steps:
25*30877f79SAndroid Build Coastguard Worker  1. **Event interpretation**: To be useful for analytics purposes, events need
26*30877f79SAndroid Build Coastguard Worker     to be interpreted in the context of a single playback. For example the raw
27*30877f79SAndroid Build Coastguard Worker     event of a player state change to `STATE_BUFFERING` may correspond to
28*30877f79SAndroid Build Coastguard Worker     initial buffering, a rebuffer, or buffering that happens after a seek.
29*30877f79SAndroid Build Coastguard Worker  1. **State tracking**: This step converts events to counters. For example,
30*30877f79SAndroid Build Coastguard Worker     state change events can be converted to counters tracking how much time is
31*30877f79SAndroid Build Coastguard Worker     spent in each playback state. The result is a basic set of analytics data
32*30877f79SAndroid Build Coastguard Worker     values for a single playback.
33*30877f79SAndroid Build Coastguard Worker  1. **Aggregation**: This step combines the analytics data across multiple
34*30877f79SAndroid Build Coastguard Worker     playbacks, typically by adding up counters.
35*30877f79SAndroid Build Coastguard Worker  1. **Calculation of summary metrics**: Many of the most useful metrics are
36*30877f79SAndroid Build Coastguard Worker     those that compute averages or combine the basic analytics data values in
37*30877f79SAndroid Build Coastguard Worker     other ways. Summary metrics can be calculated for single or multiple
38*30877f79SAndroid Build Coastguard Worker     playbacks.
39*30877f79SAndroid Build Coastguard Worker
40*30877f79SAndroid Build Coastguard Worker## Event collection with AnalyticsListener ##
41*30877f79SAndroid Build Coastguard Worker
42*30877f79SAndroid Build Coastguard WorkerRaw playback events from the player are reported to `AnalyticsListener`
43*30877f79SAndroid Build Coastguard Workerimplementations. You can easily add your own listener and override only the
44*30877f79SAndroid Build Coastguard Workermethods you are interested in:
45*30877f79SAndroid Build Coastguard Worker
46*30877f79SAndroid Build Coastguard Worker~~~
47*30877f79SAndroid Build Coastguard WorkerexoPlayer.addAnalyticsListener(new AnalyticsListener() {
48*30877f79SAndroid Build Coastguard Worker    @Override
49*30877f79SAndroid Build Coastguard Worker    public void onPlaybackStateChanged(
50*30877f79SAndroid Build Coastguard Worker        EventTime eventTime, @Player.State int state) {
51*30877f79SAndroid Build Coastguard Worker    }
52*30877f79SAndroid Build Coastguard Worker
53*30877f79SAndroid Build Coastguard Worker    @Override
54*30877f79SAndroid Build Coastguard Worker    public void onDroppedVideoFrames(
55*30877f79SAndroid Build Coastguard Worker        EventTime eventTime, int droppedFrames, long elapsedMs) {
56*30877f79SAndroid Build Coastguard Worker    }
57*30877f79SAndroid Build Coastguard Worker});
58*30877f79SAndroid Build Coastguard Worker~~~
59*30877f79SAndroid Build Coastguard Worker{: .language-java}
60*30877f79SAndroid Build Coastguard Worker
61*30877f79SAndroid Build Coastguard WorkerThe `EventTime` that's passed to each callback associates the event to a media
62*30877f79SAndroid Build Coastguard Workeritem in the playlist, as well as playback position and timestamp metadata:
63*30877f79SAndroid Build Coastguard Worker
64*30877f79SAndroid Build Coastguard Worker* `realtimeMs`: The wall clock time of the event.
65*30877f79SAndroid Build Coastguard Worker* `timeline`, `windowIndex` and `mediaPeriodId`: Defines the playlist and the
66*30877f79SAndroid Build Coastguard Worker  item within the playlist to which the event belongs. The `mediaPeriodId`
67*30877f79SAndroid Build Coastguard Worker  contains optional additional information, for example indicating whether the
68*30877f79SAndroid Build Coastguard Worker  event belongs to an ad within the item.
69*30877f79SAndroid Build Coastguard Worker* `eventPlaybackPositionMs`: The playback position in the item when the event
70*30877f79SAndroid Build Coastguard Worker  occurred.
71*30877f79SAndroid Build Coastguard Worker* `currentTimeline`, `currentWindowIndex`, `currentMediaPeriodId` and
72*30877f79SAndroid Build Coastguard Worker  `currentPlaybackPositionMs`: As above but for the currently playing item. The
73*30877f79SAndroid Build Coastguard Worker  currently playing item may be different from the item to which the event
74*30877f79SAndroid Build Coastguard Worker  belongs, for example if the event corresponds to pre-buffering of the next
75*30877f79SAndroid Build Coastguard Worker  item to be played.
76*30877f79SAndroid Build Coastguard Worker
77*30877f79SAndroid Build Coastguard Worker## Event processing with PlaybackStatsListener ##
78*30877f79SAndroid Build Coastguard Worker
79*30877f79SAndroid Build Coastguard Worker`PlaybackStatsListener` is an `AnalyticsListener` that implements on device
80*30877f79SAndroid Build Coastguard Workerevent processing. It calculates `PlaybackStats`, with counters and derived
81*30877f79SAndroid Build Coastguard Workermetrics including:
82*30877f79SAndroid Build Coastguard Worker
83*30877f79SAndroid Build Coastguard Worker* Summary metrics, for example the total playback time.
84*30877f79SAndroid Build Coastguard Worker* Adaptive playback quality metrics, for example the average video resolution.
85*30877f79SAndroid Build Coastguard Worker* Rendering quality metrics, for example the rate of dropped frames.
86*30877f79SAndroid Build Coastguard Worker* Resource usage metrics, for example the number of bytes read over the network.
87*30877f79SAndroid Build Coastguard Worker
88*30877f79SAndroid Build Coastguard WorkerYou will find a complete list of the available counts and derived metrics in the
89*30877f79SAndroid Build Coastguard Worker[`PlaybackStats` Javadoc][].
90*30877f79SAndroid Build Coastguard Worker
91*30877f79SAndroid Build Coastguard Worker`PlaybackStatsListener` calculates separate `PlaybackStats` for each media item
92*30877f79SAndroid Build Coastguard Workerin the playlist, and also each client-side ad inserted within these items. You
93*30877f79SAndroid Build Coastguard Workercan provide a callback to `PlaybackStatsListener` to be informed about finished
94*30877f79SAndroid Build Coastguard Workerplaybacks, and use the `EventTime` passed to the callback to identify which
95*30877f79SAndroid Build Coastguard Workerplayback finished. It's possible to [aggregate the analytics data][] for
96*30877f79SAndroid Build Coastguard Workermultiple playbacks. It's also possible to query the `PlaybackStats` for the
97*30877f79SAndroid Build Coastguard Workercurrent playback session at any time using
98*30877f79SAndroid Build Coastguard Worker`PlaybackStatsListener.getPlaybackStats()`.
99*30877f79SAndroid Build Coastguard Worker
100*30877f79SAndroid Build Coastguard Worker~~~
101*30877f79SAndroid Build Coastguard WorkerexoPlayer.addAnalyticsListener(
102*30877f79SAndroid Build Coastguard Worker    new PlaybackStatsListener(
103*30877f79SAndroid Build Coastguard Worker        /* keepHistory= */ true, (eventTime, playbackStats) -> {
104*30877f79SAndroid Build Coastguard Worker          // Analytics data for the session started at `eventTime` is ready.
105*30877f79SAndroid Build Coastguard Worker        }));
106*30877f79SAndroid Build Coastguard Worker~~~
107*30877f79SAndroid Build Coastguard Worker{: .language-java}
108*30877f79SAndroid Build Coastguard Worker
109*30877f79SAndroid Build Coastguard WorkerThe constructor of `PlaybackStatsListener` gives the option to keep the full
110*30877f79SAndroid Build Coastguard Workerhistory of processed events. Note that this may incur an unknown memory overhead
111*30877f79SAndroid Build Coastguard Workerdepending on the length of the playback and the number of events. Therefore you
112*30877f79SAndroid Build Coastguard Workershould only turn it on if you need access to the full history of processed
113*30877f79SAndroid Build Coastguard Workerevents, rather than just to the final analytics data.
114*30877f79SAndroid Build Coastguard Worker
115*30877f79SAndroid Build Coastguard WorkerNote that `PlaybackStats` uses an extended set of states to indicate not only
116*30877f79SAndroid Build Coastguard Workerthe state of the media, but also the user intention to play and more detailed
117*30877f79SAndroid Build Coastguard Workerinformation such as why playback was interrupted or ended:
118*30877f79SAndroid Build Coastguard Worker
119*30877f79SAndroid Build Coastguard Worker| Playback state | User intention to play  | No intention to play |
120*30877f79SAndroid Build Coastguard Worker|:---|:---|:---|
121*30877f79SAndroid Build Coastguard Worker| Before playback | `JOINING_FOREGROUND` | `NOT_STARTED`, `JOINING_BACKGROUND` |
122*30877f79SAndroid Build Coastguard Worker| Active playback | `PLAYING` | |
123*30877f79SAndroid Build Coastguard Worker| Interrupted playback | `BUFFERING`, `SEEKING` | `PAUSED`, `PAUSED_BUFFERING`, `SUPPRESSED`, `SUPPRESSED_BUFFERING`, `INTERRUPTED_BY_AD` |
124*30877f79SAndroid Build Coastguard Worker| End states | | `ENDED`, `STOPPED`, `FAILED`, `ABANDONED` |
125*30877f79SAndroid Build Coastguard Worker
126*30877f79SAndroid Build Coastguard WorkerThe user intention to play is important to distinguish times when the user was
127*30877f79SAndroid Build Coastguard Workeractively waiting for playback to continue from passive wait times. For example,
128*30877f79SAndroid Build Coastguard Worker`PlaybackStats.getTotalWaitTimeMs` returns the total time spent in the
129*30877f79SAndroid Build Coastguard Worker`JOINING_FOREGROUND`, `BUFFERING` and `SEEKING` states, but not the time when
130*30877f79SAndroid Build Coastguard Workerplayback was paused. Similarly, `PlaybackStats.getTotalPlayAndWaitTimeMs` will
131*30877f79SAndroid Build Coastguard Workerreturn the total time with a user intention to play, that is the total active
132*30877f79SAndroid Build Coastguard Workerwait time and the total time spent in the `PLAYING` state.
133*30877f79SAndroid Build Coastguard Worker
134*30877f79SAndroid Build Coastguard Worker### Processed and interpreted events ###
135*30877f79SAndroid Build Coastguard Worker
136*30877f79SAndroid Build Coastguard WorkerYou can record processed and interpreted events by using `PlaybackStatsListener`
137*30877f79SAndroid Build Coastguard Workerwith `keepHistory=true`. The resulting `PlaybackStats` will contain the
138*30877f79SAndroid Build Coastguard Workerfollowing event lists:
139*30877f79SAndroid Build Coastguard Worker
140*30877f79SAndroid Build Coastguard Worker* `playbackStateHistory`: An ordered list of extended playback states with
141*30877f79SAndroid Build Coastguard Worker  the `EventTime` at which they started to apply. You can also use
142*30877f79SAndroid Build Coastguard Worker  `PlaybackStats.getPlaybackStateAtTime` to look up the state at a given wall
143*30877f79SAndroid Build Coastguard Worker  clock time.
144*30877f79SAndroid Build Coastguard Worker* `mediaTimeHistory`: A history of wall clock time and media time pairs allowing
145*30877f79SAndroid Build Coastguard Worker  you to reconstruct which parts of the media were played at which time. You can
146*30877f79SAndroid Build Coastguard Worker  also use `PlaybackStats.getMediaTimeMsAtRealtimeMs` to look up the playback
147*30877f79SAndroid Build Coastguard Worker  position at a given wall clock time.
148*30877f79SAndroid Build Coastguard Worker* `videoFormatHistory` and `audioFormatHistory`: Ordered lists of video and
149*30877f79SAndroid Build Coastguard Worker  audio formats used during playback with the `EventTime` at which they started
150*30877f79SAndroid Build Coastguard Worker  to be used.
151*30877f79SAndroid Build Coastguard Worker* `fatalErrorHistory` and `nonFatalErrorHistory`: Ordered lists of fatal and
152*30877f79SAndroid Build Coastguard Worker  non-fatal errors with the `EventTime` at which they occurred. Fatal errors are
153*30877f79SAndroid Build Coastguard Worker  those that ended playback, whereas non-fatal errors may have been recoverable.
154*30877f79SAndroid Build Coastguard Worker
155*30877f79SAndroid Build Coastguard Worker### Single-playback analytics data ###
156*30877f79SAndroid Build Coastguard Worker
157*30877f79SAndroid Build Coastguard WorkerThis data is automatically collected if you use `PlaybackStatsListener`, even
158*30877f79SAndroid Build Coastguard Workerwith `keepHistory=false`. The final values are the public fields that you can
159*30877f79SAndroid Build Coastguard Workerfind in the [`PlaybackStats` Javadoc][] and the playback state durations
160*30877f79SAndroid Build Coastguard Workerreturned by `getPlaybackStateDurationMs`. For convenience, you'll also find
161*30877f79SAndroid Build Coastguard Workermethods like `getTotalPlayTimeMs` and `getTotalWaitTimeMs` that return the
162*30877f79SAndroid Build Coastguard Workerduration of specific playback state combinations.
163*30877f79SAndroid Build Coastguard Worker
164*30877f79SAndroid Build Coastguard Worker~~~
165*30877f79SAndroid Build Coastguard WorkerLog.d("DEBUG", "Playback summary: "
166*30877f79SAndroid Build Coastguard Worker    + "play time = " + playbackStats.getTotalPlayTimeMs()
167*30877f79SAndroid Build Coastguard Worker    + ", rebuffers = " + playbackStats.totalRebufferCount);
168*30877f79SAndroid Build Coastguard Worker~~~
169*30877f79SAndroid Build Coastguard Worker{: .language-java}
170*30877f79SAndroid Build Coastguard Worker
171*30877f79SAndroid Build Coastguard WorkerSome values like `totalVideoFormatHeightTimeProduct` are only useful when
172*30877f79SAndroid Build Coastguard Workercalculating derived summary metrics like the average video height, but are
173*30877f79SAndroid Build Coastguard Workerrequired to correctly combine multiple `PlaybackStats` together.
174*30877f79SAndroid Build Coastguard Worker{:.info}
175*30877f79SAndroid Build Coastguard Worker
176*30877f79SAndroid Build Coastguard Worker### Aggregate analytics data of multiple playbacks ###
177*30877f79SAndroid Build Coastguard Worker
178*30877f79SAndroid Build Coastguard WorkerYou can combine multiple `PlaybackStats` together by calling
179*30877f79SAndroid Build Coastguard Worker`PlaybackStats.merge`. The resulting `PlaybackStats` will contain the aggregated
180*30877f79SAndroid Build Coastguard Workerdata of all merged playbacks. Note that it won't contain the history of
181*30877f79SAndroid Build Coastguard Workerindividual playback events, since these cannot be aggregated.
182*30877f79SAndroid Build Coastguard Worker
183*30877f79SAndroid Build Coastguard Worker`PlaybackStatsListener.getCombinedPlaybackStats` can be used to get an
184*30877f79SAndroid Build Coastguard Workeraggregated view of all analytics data collected in the lifetime of a
185*30877f79SAndroid Build Coastguard Worker`PlaybackStatsListener`.
186*30877f79SAndroid Build Coastguard Worker
187*30877f79SAndroid Build Coastguard Worker### Calculated summary metrics ###
188*30877f79SAndroid Build Coastguard Worker
189*30877f79SAndroid Build Coastguard WorkerIn addition to the basic analytics data, `PlaybackStats` provides many methods
190*30877f79SAndroid Build Coastguard Workerto calculate summary metrics.
191*30877f79SAndroid Build Coastguard Worker
192*30877f79SAndroid Build Coastguard Worker~~~
193*30877f79SAndroid Build Coastguard WorkerLog.d("DEBUG", "Additional calculated summary metrics: "
194*30877f79SAndroid Build Coastguard Worker    + "average video bitrate = " + playbackStats.getMeanVideoFormatBitrate()
195*30877f79SAndroid Build Coastguard Worker    + ", mean time between rebuffers = "
196*30877f79SAndroid Build Coastguard Worker        + playbackStats.getMeanTimeBetweenRebuffers());
197*30877f79SAndroid Build Coastguard Worker~~~
198*30877f79SAndroid Build Coastguard Worker{: .language-java}
199*30877f79SAndroid Build Coastguard Worker
200*30877f79SAndroid Build Coastguard Worker## Advanced topics ##
201*30877f79SAndroid Build Coastguard Worker
202*30877f79SAndroid Build Coastguard Worker### Associating analytics data with playback metadata ###
203*30877f79SAndroid Build Coastguard Worker
204*30877f79SAndroid Build Coastguard WorkerWhen collecting analytics data for individual playbacks, you may wish to
205*30877f79SAndroid Build Coastguard Workerassociate the playback analytics data with metadata about the media being
206*30877f79SAndroid Build Coastguard Workerplayed.
207*30877f79SAndroid Build Coastguard Worker
208*30877f79SAndroid Build Coastguard WorkerIt's advisable to set media-specific metadata with `MediaItem.Builder.setTag`.
209*30877f79SAndroid Build Coastguard WorkerThe media tag is part of the `EventTime` reported for raw events and when
210*30877f79SAndroid Build Coastguard Worker`PlaybackStats` are finished, so it can be easily retrieved when handling the
211*30877f79SAndroid Build Coastguard Workercorresponding analytics data:
212*30877f79SAndroid Build Coastguard Worker
213*30877f79SAndroid Build Coastguard Worker~~~
214*30877f79SAndroid Build Coastguard Workernew PlaybackStatsListener(
215*30877f79SAndroid Build Coastguard Worker    /* keepHistory= */ false, (eventTime, playbackStats) -> {
216*30877f79SAndroid Build Coastguard Worker      Object mediaTag =
217*30877f79SAndroid Build Coastguard Worker          eventTime.timeline.getWindow(eventTime.windowIndex, new Window())
218*30877f79SAndroid Build Coastguard Worker              .mediaItem.localConfiguration.tag;
219*30877f79SAndroid Build Coastguard Worker      // Report playbackStats with mediaTag metadata.
220*30877f79SAndroid Build Coastguard Worker    });
221*30877f79SAndroid Build Coastguard Worker~~~
222*30877f79SAndroid Build Coastguard Worker{: .language-java}
223*30877f79SAndroid Build Coastguard Worker
224*30877f79SAndroid Build Coastguard Worker### Reporting custom analytics events ###
225*30877f79SAndroid Build Coastguard Worker
226*30877f79SAndroid Build Coastguard WorkerIn case you need to add custom events to the analytics data, you need to save
227*30877f79SAndroid Build Coastguard Workerthese events in your own data structure and combine them with the reported
228*30877f79SAndroid Build Coastguard Worker`PlaybackStats` later. If it helps, you can extend `AnalyticsCollector` to be
229*30877f79SAndroid Build Coastguard Workerable to generate `EventTime` instances for your custom events and send them to
230*30877f79SAndroid Build Coastguard Workerthe already registered listeners as shown in the following example.
231*30877f79SAndroid Build Coastguard Worker
232*30877f79SAndroid Build Coastguard Worker~~~
233*30877f79SAndroid Build Coastguard Workerinterface ExtendedListener extends AnalyticsListener {
234*30877f79SAndroid Build Coastguard Worker  void onCustomEvent(EventTime eventTime);
235*30877f79SAndroid Build Coastguard Worker}
236*30877f79SAndroid Build Coastguard Worker
237*30877f79SAndroid Build Coastguard Workerclass ExtendedCollector extends AnalyticsCollector {
238*30877f79SAndroid Build Coastguard Worker public void customEvent() {
239*30877f79SAndroid Build Coastguard Worker   EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
240*30877f79SAndroid Build Coastguard Worker   sendEvent(eventTime, CUSTOM_EVENT_ID, listener -> {
241*30877f79SAndroid Build Coastguard Worker     if (listener instanceof ExtendedListener) {
242*30877f79SAndroid Build Coastguard Worker       ((ExtendedListener) listener).onCustomEvent(eventTime);
243*30877f79SAndroid Build Coastguard Worker     }
244*30877f79SAndroid Build Coastguard Worker   });
245*30877f79SAndroid Build Coastguard Worker }
246*30877f79SAndroid Build Coastguard Worker}
247*30877f79SAndroid Build Coastguard Worker
248*30877f79SAndroid Build Coastguard Worker// Usage - Setup and listener registration.
249*30877f79SAndroid Build Coastguard WorkerExoPlayer player = new ExoPlayer.Builder(context)
250*30877f79SAndroid Build Coastguard Worker    .setAnalyticsCollector(new ExtendedCollector())
251*30877f79SAndroid Build Coastguard Worker    .build();
252*30877f79SAndroid Build Coastguard Workerplayer.addAnalyticsListener(new ExtendedListener() {
253*30877f79SAndroid Build Coastguard Worker  @Override
254*30877f79SAndroid Build Coastguard Worker  public void onCustomEvent(EventTime eventTime) {
255*30877f79SAndroid Build Coastguard Worker    // Save custom event for analytics data.
256*30877f79SAndroid Build Coastguard Worker  }
257*30877f79SAndroid Build Coastguard Worker});
258*30877f79SAndroid Build Coastguard Worker// Usage - Triggering the custom event.
259*30877f79SAndroid Build Coastguard Worker((ExtendedCollector) player.getAnalyticsCollector()).customEvent();
260*30877f79SAndroid Build Coastguard Worker~~~
261*30877f79SAndroid Build Coastguard Worker{: .language-java}
262*30877f79SAndroid Build Coastguard Worker
263*30877f79SAndroid Build Coastguard Worker[`PlaybackStats` Javadoc]: {{ site.exo_sdk }}/analytics/PlaybackStats.html
264*30877f79SAndroid Build Coastguard Worker[aggregate the analytics data]: {{ site.baseurl }}/analytics.html#aggregate-analytics-data-of-multiple-playbacks
265