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