or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

analytics.mdaudio-rendering.mdcore-player.mddrm-support.mdindex.mdmedia-sources.mdoffline-support.mdtrack-selection.mdvideo-rendering.md

analytics.mddocs/

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

```