0
# Analytics
1
2
ExoPlayer's analytics system provides comprehensive event tracking, performance monitoring, and debugging capabilities. The analytics framework allows you to monitor playback events, track performance metrics, and gather detailed information about player behavior.
3
4
## AnalyticsListener Interface
5
6
The primary interface for receiving analytics events with detailed event timing and context information.
7
8
```java { .api }
9
public interface AnalyticsListener {
10
/**
11
* Contains timing information for analytics events.
12
*/
13
final class EventTime {
14
/**
15
* The timeline at the time of the event.
16
*/
17
public final Timeline timeline;
18
19
/**
20
* The window index in the timeline.
21
*/
22
public final int windowIndex;
23
24
/**
25
* The media period id, or null if not applicable.
26
*/
27
@Nullable
28
public final MediaPeriodId mediaPeriodId;
29
30
/**
31
* The event timestamp in milliseconds since epoch.
32
*/
33
public final long realtimeMs;
34
35
/**
36
* The current timeline event time in milli
37
*/
38
public final long currentTimelineWindowSequenceNumber;
39
40
public EventTime(long realtimeMs, Timeline timeline, int windowIndex,
41
@Nullable MediaPeriodId mediaPeriodId, long currentPlaybackPositionMs,
42
Timeline currentTimeline, int currentWindowIndex);
43
}
44
45
// Playback state events
46
default void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) {}
47
default void onPlayWhenReadyChanged(EventTime eventTime, boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {}
48
default void onIsPlayingChanged(EventTime eventTime, boolean isPlaying) {}
49
default void onTimelineChanged(EventTime eventTime, @Player.TimelineChangeReason int reason) {}
50
default void onTracksChanged(EventTime eventTime, Tracks tracks) {}
51
52
// Error events
53
default void onPlayerError(EventTime eventTime, PlaybackException error) {}
54
default void onPlayerErrorChanged(EventTime eventTime, @Nullable PlaybackException error) {}
55
56
// Seek events
57
default void onSeekStarted(EventTime eventTime) {}
58
default void onSeekProcessed(EventTime eventTime) {}
59
default void onPositionDiscontinuity(EventTime eventTime, Player.PositionInfo oldPosition, Player.PositionInfo newPosition, @Player.DiscontinuityReason int reason) {}
60
61
// Media events
62
default void onMediaItemTransition(EventTime eventTime, @Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {}
63
default void onPlaylistMetadataChanged(EventTime eventTime, MediaMetadata playlistMetadata) {}
64
default void onMediaMetadataChanged(EventTime eventTime, MediaMetadata mediaMetadata) {}
65
66
// Video events
67
default void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) {}
68
default void onSurfaceSizeChanged(EventTime eventTime, int width, int height) {}
69
default void onRenderedFirstFrame(EventTime eventTime, Object output, long renderTimeMs) {}
70
71
// Audio events
72
default void onAudioSessionIdChanged(EventTime eventTime, int audioSessionId) {}
73
default void onAudioAttributesChanged(EventTime eventTime, AudioAttributes audioAttributes) {}
74
default void onVolumeChanged(EventTime eventTime, float volume) {}
75
default void onSkipSilenceEnabledChanged(EventTime eventTime, boolean skipSilenceEnabled) {}
76
77
// Cue events (subtitles/captions)
78
default void onCues(EventTime eventTime, CueGroup cueGroup) {}
79
80
// Device events
81
default void onDeviceInfoChanged(EventTime eventTime, DeviceInfo deviceInfo) {}
82
default void onDeviceVolumeChanged(EventTime eventTime, int volume, boolean muted) {}
83
84
// Bandwidth and network events
85
default void onBandwidthEstimate(EventTime eventTime, int totalLoadTimeMs, long totalBytesLoaded, long bitrateEstimate) {}
86
87
// Loading events
88
default void onLoadStarted(EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {}
89
default void onLoadCompleted(EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {}
90
default void onLoadCanceled(EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {}
91
default void onLoadError(EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData, IOException error, boolean wasCanceled) {}
92
93
// Downstream format events
94
default void onDownstreamFormatChanged(EventTime eventTime, MediaLoadData mediaLoadData) {}
95
default void onUpstreamDiscarded(EventTime eventTime, MediaLoadData mediaLoadData) {}
96
97
// DRM events
98
default void onDrmSessionAcquired(EventTime eventTime, @C.TrackType int trackType) {}
99
default void onDrmKeysLoaded(EventTime eventTime, @C.TrackType int trackType) {}
100
default void onDrmSessionManagerError(EventTime eventTime, Exception error) {}
101
default void onDrmKeysRestored(EventTime eventTime, @C.TrackType int trackType) {}
102
default void onDrmKeysRemoved(EventTime eventTime, @C.TrackType int trackType) {}
103
default void onDrmSessionReleased(EventTime eventTime, @C.TrackType int trackType) {}
104
105
// Metadata events
106
default void onMetadata(EventTime eventTime, Metadata metadata) {}
107
108
// Decoder events
109
default void onAudioEnabled(EventTime eventTime, DecoderCounters counters) {}
110
default void onAudioDecoderInitialized(EventTime eventTime, String decoderName, long initializationDurationMs) {}
111
default void onAudioInputFormatChanged(EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {}
112
default void onAudioUnderrun(EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {}
113
default void onAudioDecoderReleased(EventTime eventTime, String decoderName) {}
114
default void onAudioDisabled(EventTime eventTime, DecoderCounters counters) {}
115
116
default void onVideoEnabled(EventTime eventTime, DecoderCounters counters) {}
117
default void onVideoDecoderInitialized(EventTime eventTime, String decoderName, long initializationDurationMs) {}
118
default void onVideoInputFormatChanged(EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {}
119
default void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {}
120
default void onVideoFrameProcessingOffset(EventTime eventTime, long totalProcessingOffsetUs, int frameCount) {}
121
default void onVideoDecoderReleased(EventTime eventTime, String decoderName) {}
122
default void onVideoDisabled(EventTime eventTime, DecoderCounters counters) {}
123
}
124
```
125
126
## AnalyticsCollector Interface
127
128
Collects and forwards analytics events from various ExoPlayer components.
129
130
```java { .api }
131
public interface AnalyticsCollector extends Player.Listener, AudioRendererEventListener,
132
VideoRendererEventListener, MediaSourceEventListener,
133
BandwidthMeter.EventListener, DrmSessionEventListener {
134
135
/**
136
* Adds an analytics listener.
137
*
138
* @param listener The listener to add
139
*/
140
void addListener(AnalyticsListener listener);
141
142
/**
143
* Removes an analytics listener.
144
*
145
* @param listener The listener to remove
146
*/
147
void removeListener(AnalyticsListener listener);
148
149
/**
150
* Sets the player instance.
151
*
152
* @param player The player
153
* @param looper The looper
154
*/
155
void setPlayer(Player player, Looper looper);
156
157
/**
158
* Called when the player is released.
159
*/
160
void release();
161
162
/**
163
* Updates the media period queue info.
164
*
165
* @param queue The media period queue
166
* @param readingPeriod The reading period
167
*/
168
void updateMediaPeriodQueueInfo(List<MediaPeriodHolder> queue, @Nullable MediaPeriodHolder readingPeriod);
169
}
170
```
171
172
## DefaultAnalyticsCollector
173
174
The default implementation of AnalyticsCollector.
175
176
```java { .api }
177
public class DefaultAnalyticsCollector implements AnalyticsCollector {
178
/**
179
* Creates a DefaultAnalyticsCollector.
180
*
181
* @param clock The clock for generating event timestamps
182
*/
183
public DefaultAnalyticsCollector(Clock clock);
184
185
/**
186
* Creates a DefaultAnalyticsCollector with the default clock.
187
*/
188
public DefaultAnalyticsCollector();
189
190
@Override
191
public void addListener(AnalyticsListener listener);
192
193
@Override
194
public void removeListener(AnalyticsListener listener);
195
196
@Override
197
public void setPlayer(Player player, Looper looper);
198
199
@Override
200
public void release();
201
}
202
```
203
204
## EventLogger
205
206
A utility class that logs analytics events for debugging purposes.
207
208
```java { .api }
209
public final class EventLogger implements AnalyticsListener {
210
/**
211
* Creates an EventLogger.
212
*/
213
public EventLogger();
214
215
/**
216
* Creates an EventLogger with a custom tag.
217
*
218
* @param tag The log tag
219
*/
220
public EventLogger(@Nullable String tag);
221
222
/**
223
* Starts logging session with a session name.
224
*
225
* @param sessionName The session name
226
*/
227
public void startSession(String sessionName);
228
229
/**
230
* Stops the current logging session.
231
*/
232
public void stopSession();
233
234
// Implements all AnalyticsListener methods with logging
235
}
236
```
237
238
## Media Load Data
239
240
Information about media loading operations.
241
242
```java { .api }
243
public final class MediaLoadData {
244
/**
245
* The data type.
246
*/
247
@C.DataType public final int dataType;
248
249
/**
250
* The track type.
251
*/
252
@C.TrackType public final int trackType;
253
254
/**
255
* The track format, or null if not applicable.
256
*/
257
@Nullable public final Format trackFormat;
258
259
/**
260
* The track selection reason.
261
*/
262
@C.SelectionReason public final int trackSelectionReason;
263
264
/**
265
* The track selection data, or null if not applicable.
266
*/
267
@Nullable public final Object trackSelectionData;
268
269
/**
270
* The media start time in microseconds.
271
*/
272
public final long mediaStartTimeMs;
273
274
/**
275
* The media end time in microseconds.
276
*/
277
public final long mediaEndTimeMs;
278
279
public MediaLoadData(@C.DataType int dataType, @C.TrackType int trackType, @Nullable Format trackFormat,
280
@C.SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData,
281
long mediaStartTimeMs, long mediaEndTimeMs);
282
}
283
```
284
285
## Load Event Info
286
287
Information about loading events.
288
289
```java { .api }
290
public final class LoadEventInfo {
291
/**
292
* The data specification for the load.
293
*/
294
public final DataSpec dataSpec;
295
296
/**
297
* The URI after any redirection.
298
*/
299
public final Uri uri;
300
301
/**
302
* The response headers, or empty if not applicable.
303
*/
304
public final Map<String, List<String>> responseHeaders;
305
306
/**
307
* The number of bytes loaded.
308
*/
309
public final long bytesLoaded;
310
311
/**
312
* The load duration in milliseconds.
313
*/
314
public final long loadDurationMs;
315
316
public LoadEventInfo(DataSpec dataSpec, Uri uri, Map<String, List<String>> responseHeaders,
317
long bytesLoaded, long loadDurationMs);
318
}
319
```
320
321
## Usage Examples
322
323
### Basic Analytics Setup
324
325
```java
326
// Create analytics collector
327
DefaultAnalyticsCollector analyticsCollector = new DefaultAnalyticsCollector();
328
329
// Add event logger for debugging
330
EventLogger eventLogger = new EventLogger("ExoPlayer");
331
analyticsCollector.addListener(eventLogger);
332
333
// Use with ExoPlayer
334
ExoPlayer player = new ExoPlayer.Builder(context)
335
.setAnalyticsCollector(analyticsCollector)
336
.build();
337
```
338
339
### Custom Analytics Listener
340
341
```java
342
public class CustomAnalyticsListener implements AnalyticsListener {
343
private static final String TAG = "CustomAnalytics";
344
345
@Override
346
public void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) {
347
String stateName = getStateName(state);
348
Log.d(TAG, "Playback state changed to: " + stateName);
349
350
// Send analytics to your backend
351
sendAnalyticsEvent("playback_state_changed",
352
Map.of("state", stateName, "timestamp", eventTime.realtimeMs));
353
}
354
355
@Override
356
public void onPlayerError(EventTime eventTime, PlaybackException error) {
357
Log.e(TAG, "Player error occurred", error);
358
359
// Report error to crash reporting service
360
crashReporter.recordException(error);
361
362
// Send error analytics
363
sendAnalyticsEvent("player_error",
364
Map.of("error_code", error.errorCode,
365
"error_message", error.getMessage(),
366
"timestamp", eventTime.realtimeMs));
367
}
368
369
@Override
370
public void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) {
371
Log.d(TAG, String.format("Video size changed: %dx%d",
372
videoSize.width, videoSize.height));
373
374
sendAnalyticsEvent("video_size_changed",
375
Map.of("width", videoSize.width,
376
"height", videoSize.height,
377
"pixel_width_height_ratio", videoSize.pixelWidthHeightRatio));
378
}
379
380
@Override
381
public void onBandwidthEstimate(EventTime eventTime, int totalLoadTimeMs,
382
long totalBytesLoaded, long bitrateEstimate) {
383
Log.d(TAG, String.format("Bandwidth estimate: %d kbps", bitrateEstimate / 1000));
384
385
sendAnalyticsEvent("bandwidth_estimate",
386
Map.of("bitrate_kbps", bitrateEstimate / 1000,
387
"total_load_time_ms", totalLoadTimeMs,
388
"total_bytes_loaded", totalBytesLoaded));
389
}
390
391
@Override
392
public void onLoadError(EventTime eventTime, LoadEventInfo loadEventInfo,
393
MediaLoadData mediaLoadData, IOException error, boolean wasCanceled) {
394
Log.w(TAG, "Load error occurred: " + error.getMessage());
395
396
sendAnalyticsEvent("load_error",
397
Map.of("error_message", error.getMessage(),
398
"was_canceled", wasCanceled,
399
"uri", loadEventInfo.uri.toString(),
400
"bytes_loaded", loadEventInfo.bytesLoaded));
401
}
402
403
private String getStateName(@Player.State int state) {
404
switch (state) {
405
case Player.STATE_IDLE: return "IDLE";
406
case Player.STATE_BUFFERING: return "BUFFERING";
407
case Player.STATE_READY: return "READY";
408
case Player.STATE_ENDED: return "ENDED";
409
default: return "UNKNOWN";
410
}
411
}
412
413
private void sendAnalyticsEvent(String eventName, Map<String, Object> parameters) {
414
// Implementation to send analytics to your backend
415
}
416
}
417
418
// Use the custom listener
419
CustomAnalyticsListener customListener = new CustomAnalyticsListener();
420
player.addAnalyticsListener(customListener);
421
```
422
423
### Performance Monitoring
424
425
```java
426
public class PerformanceAnalyticsListener implements AnalyticsListener {
427
private long sessionStartTime;
428
private int totalDroppedFrames;
429
private long totalVideoFrames;
430
private Map<String, Integer> decoderInitCounts = new HashMap<>();
431
432
@Override
433
public void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) {
434
if (state == Player.STATE_READY && sessionStartTime == 0) {
435
sessionStartTime = eventTime.realtimeMs;
436
} else if (state == Player.STATE_ENDED || state == Player.STATE_IDLE) {
437
reportSessionMetrics(eventTime);
438
}
439
}
440
441
@Override
442
public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {
443
totalDroppedFrames += droppedFrames;
444
445
// Calculate drop rate
446
if (elapsedMs > 0) {
447
double dropRate = (double) droppedFrames / (elapsedMs / 1000.0);
448
Log.d("Performance", String.format("Dropped %d frames in %dms (%.2f fps drop rate)",
449
droppedFrames, elapsedMs, dropRate));
450
}
451
}
452
453
@Override
454
public void onVideoDecoderInitialized(EventTime eventTime, String decoderName, long initializationDurationMs) {
455
decoderInitCounts.put(decoderName, decoderInitCounts.getOrDefault(decoderName, 0) + 1);
456
457
Log.d("Performance", String.format("Video decoder %s initialized in %dms",
458
decoderName, initializationDurationMs));
459
}
460
461
@Override
462
public void onAudioUnderrun(EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
463
Log.w("Performance", String.format("Audio underrun: buffer=%d bytes (%dms), starved for %dms",
464
bufferSize, bufferSizeMs, elapsedSinceLastFeedMs));
465
}
466
467
private void reportSessionMetrics(EventTime eventTime) {
468
if (sessionStartTime == 0) return;
469
470
long sessionDuration = eventTime.realtimeMs - sessionStartTime;
471
double averageDropRate = totalVideoFrames > 0 ?
472
(double) totalDroppedFrames / totalVideoFrames * 100 : 0;
473
474
Log.i("Performance", String.format(
475
"Session metrics - Duration: %dms, Dropped frames: %d, Average drop rate: %.2f%%",
476
sessionDuration, totalDroppedFrames, averageDropRate));
477
478
// Reset for next session
479
sessionStartTime = 0;
480
totalDroppedFrames = 0;
481
totalVideoFrames = 0;
482
decoderInitCounts.clear();
483
}
484
}
485
```
486
487
### Network Analytics
488
489
```java
490
public class NetworkAnalyticsListener implements AnalyticsListener {
491
private long totalBytesLoaded = 0;
492
private long sessionStartTime;
493
private List<Long> bitrateEstimates = new ArrayList<>();
494
495
@Override
496
public void onLoadCompleted(EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
497
totalBytesLoaded += loadEventInfo.bytesLoaded;
498
499
// Calculate effective bitrate
500
if (loadEventInfo.loadDurationMs > 0) {
501
long effectiveBitrate = (loadEventInfo.bytesLoaded * 8 * 1000) / loadEventInfo.loadDurationMs;
502
Log.d("Network", String.format("Load completed: %d bytes in %dms (effective bitrate: %d bps)",
503
loadEventInfo.bytesLoaded, loadEventInfo.loadDurationMs, effectiveBitrate));
504
}
505
}
506
507
@Override
508
public void onBandwidthEstimate(EventTime eventTime, int totalLoadTimeMs, long totalBytesLoaded, long bitrateEstimate) {
509
bitrateEstimates.add(bitrateEstimate);
510
511
// Keep only recent estimates (last 10)
512
if (bitrateEstimates.size() > 10) {
513
bitrateEstimates.remove(0);
514
}
515
516
// Calculate average bitrate
517
long avgBitrate = bitrateEstimates.stream().mapToLong(Long::longValue).sum() / bitrateEstimates.size();
518
519
Log.d("Network", String.format("Bandwidth estimate: %d kbps (avg: %d kbps)",
520
bitrateEstimate / 1000, avgBitrate / 1000));
521
}
522
523
@Override
524
public void onLoadError(EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData,
525
IOException error, boolean wasCanceled) {
526
Log.w("Network", String.format("Load error for %s: %s (canceled: %s)",
527
loadEventInfo.uri, error.getMessage(), wasCanceled));
528
529
// Track error by type
530
String errorType = error.getClass().getSimpleName();
531
// Report to analytics backend
532
}
533
}
534
```
535
536
### Debug Text View Helper
537
538
```java
539
public class DebugTextViewHelper {
540
private final ExoPlayer player;
541
private final TextView textView;
542
private final Runnable updateRunnable = this::updateAndPost;
543
private boolean started;
544
545
public DebugTextViewHelper(ExoPlayer player, TextView textView) {
546
this.player = player;
547
this.textView = textView;
548
}
549
550
/**
551
* Starts updating the text view with debug information.
552
*/
553
public void start() {
554
if (started) return;
555
started = true;
556
updateAndPost();
557
}
558
559
/**
560
* Stops updating the text view.
561
*/
562
public void stop() {
563
started = false;
564
textView.removeCallbacks(updateRunnable);
565
}
566
567
private void updateAndPost() {
568
updateDebugText();
569
if (started) {
570
textView.postDelayed(updateRunnable, 1000); // Update every second
571
}
572
}
573
574
private void updateDebugText() {
575
StringBuilder debug = new StringBuilder();
576
577
// Playback state
578
debug.append("State: ").append(getStateName(player.getPlaybackState())).append("\n");
579
debug.append("Playing: ").append(player.isPlaying()).append("\n");
580
581
// Position info
582
long position = player.getCurrentPosition();
583
long duration = player.getDuration();
584
long buffered = player.getBufferedPosition();
585
586
debug.append("Position: ").append(formatTime(position))
587
.append(" / ").append(formatTime(duration)).append("\n");
588
debug.append("Buffered: ").append(formatTime(buffered)).append("\n");
589
590
// Video info
591
VideoSize videoSize = player.getVideoSize();
592
if (videoSize.width > 0) {
593
debug.append("Video: ").append(videoSize.width).append("x").append(videoSize.height).append("\n");
594
}
595
596
// Audio info
597
Format audioFormat = player.getAudioFormat();
598
if (audioFormat != null) {
599
debug.append("Audio: ").append(audioFormat.sampleMimeType)
600
.append(", ").append(audioFormat.channelCount).append(" ch")
601
.append(", ").append(audioFormat.sampleRate).append(" Hz").append("\n");
602
}
603
604
textView.setText(debug.toString());
605
}
606
607
private String formatTime(long timeMs) {
608
if (timeMs == C.TIME_UNSET) return "--:--";
609
long seconds = timeMs / 1000;
610
return String.format("%d:%02d", seconds / 60, seconds % 60);
611
}
612
613
private String getStateName(@Player.State int state) {
614
switch (state) {
615
case Player.STATE_IDLE: return "IDLE";
616
case Player.STATE_BUFFERING: return "BUFFERING";
617
case Player.STATE_READY: return "READY";
618
case Player.STATE_ENDED: return "ENDED";
619
default: return "UNKNOWN";
620
}
621
}
622
}
623
624
// Usage
625
DebugTextViewHelper debugHelper = new DebugTextViewHelper(player, debugTextView);
626
debugHelper.start();
627
// ... later
628
debugHelper.stop();
629
```