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