Add EventTimes to AnalyticsListener.onEvents

The EventTime wasn't part of the onEvents callbacks so far because the
individual events may have different event times (e.g. if they relate to
different media periods). By adding a query method to obtain the
EventTime by event type, we can solve this issue.

#exofixit

PiperOrigin-RevId: 343818819
This commit is contained in:
tonihei 2020-11-23 11:27:12 +00:00 committed by kim-vde
parent 7d3399764e
commit e5e903eb8a
4 changed files with 449 additions and 63 deletions

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.analytics;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.os.Looper; import android.os.Looper;
import android.util.SparseArray;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
@ -80,6 +81,7 @@ public class AnalyticsCollector
private final Period period; private final Period period;
private final Window window; private final Window window;
private final MediaPeriodQueueTracker mediaPeriodQueueTracker; private final MediaPeriodQueueTracker mediaPeriodQueueTracker;
private final SparseArray<EventTime> eventTimes;
private ListenerSet<AnalyticsListener, AnalyticsListener.Events> listeners; private ListenerSet<AnalyticsListener, AnalyticsListener.Events> listeners;
private @MonotonicNonNull Player player; private @MonotonicNonNull Player player;
@ -100,6 +102,7 @@ public class AnalyticsCollector
period = new Period(); period = new Period();
window = new Window(); window = new Window();
mediaPeriodQueueTracker = new MediaPeriodQueueTracker(period); mediaPeriodQueueTracker = new MediaPeriodQueueTracker(period);
eventTimes = new SparseArray<>();
} }
/** /**
@ -133,7 +136,12 @@ public class AnalyticsCollector
this.player == null || mediaPeriodQueueTracker.mediaPeriodQueue.isEmpty()); this.player == null || mediaPeriodQueueTracker.mediaPeriodQueue.isEmpty());
this.player = checkNotNull(player); this.player = checkNotNull(player);
listeners = listeners =
listeners.copy(looper, (listener, eventFlags) -> listener.onEvents(player, eventFlags)); listeners.copy(
looper,
(listener, events) -> {
events.setEventTimes(eventTimes);
listener.onEvents(player, events);
});
} }
/** /**
@ -160,8 +168,8 @@ public class AnalyticsCollector
if (!isSeeking) { if (!isSeeking) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
isSeeking = true; isSeeking = true;
listeners.sendEvent( sendEvent(
/* eventFlag= */ C.INDEX_UNSET, listener -> listener.onSeekStarted(eventTime)); eventTime, /* eventFlag= */ C.INDEX_UNSET, listener -> listener.onSeekStarted(eventTime));
} }
} }
@ -175,8 +183,10 @@ public class AnalyticsCollector
@Override @Override
public final void onMetadata(Metadata metadata) { public final void onMetadata(Metadata metadata) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
AnalyticsListener.EVENT_METADATA, listener -> listener.onMetadata(eventTime, metadata)); eventTime,
AnalyticsListener.EVENT_METADATA,
listener -> listener.onMetadata(eventTime, metadata));
} }
// AudioRendererEventListener implementation. // AudioRendererEventListener implementation.
@ -185,7 +195,8 @@ public class AnalyticsCollector
@Override @Override
public final void onAudioEnabled(DecoderCounters counters) { public final void onAudioEnabled(DecoderCounters counters) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_ENABLED, AnalyticsListener.EVENT_AUDIO_ENABLED,
listener -> { listener -> {
listener.onAudioEnabled(eventTime, counters); listener.onAudioEnabled(eventTime, counters);
@ -198,7 +209,8 @@ public class AnalyticsCollector
public final void onAudioDecoderInitialized( public final void onAudioDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) { String decoderName, long initializedTimestampMs, long initializationDurationMs) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_DECODER_INITIALIZED, AnalyticsListener.EVENT_AUDIO_DECODER_INITIALIZED,
listener -> { listener -> {
listener.onAudioDecoderInitialized(eventTime, decoderName, initializationDurationMs); listener.onAudioDecoderInitialized(eventTime, decoderName, initializationDurationMs);
@ -212,7 +224,8 @@ public class AnalyticsCollector
public final void onAudioInputFormatChanged( public final void onAudioInputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_INPUT_FORMAT_CHANGED, AnalyticsListener.EVENT_AUDIO_INPUT_FORMAT_CHANGED,
listener -> { listener -> {
listener.onAudioInputFormatChanged(eventTime, format, decoderReuseEvaluation); listener.onAudioInputFormatChanged(eventTime, format, decoderReuseEvaluation);
@ -223,7 +236,8 @@ public class AnalyticsCollector
@Override @Override
public final void onAudioPositionAdvancing(long playoutStartSystemTimeMs) { public final void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_POSITION_ADVANCING, AnalyticsListener.EVENT_AUDIO_POSITION_ADVANCING,
listener -> listener.onAudioPositionAdvancing(eventTime, playoutStartSystemTimeMs)); listener -> listener.onAudioPositionAdvancing(eventTime, playoutStartSystemTimeMs));
} }
@ -232,7 +246,8 @@ public class AnalyticsCollector
public final void onAudioUnderrun( public final void onAudioUnderrun(
int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_UNDERRUN, AnalyticsListener.EVENT_AUDIO_UNDERRUN,
listener -> listener ->
listener.onAudioUnderrun(eventTime, bufferSize, bufferSizeMs, elapsedSinceLastFeedMs)); listener.onAudioUnderrun(eventTime, bufferSize, bufferSizeMs, elapsedSinceLastFeedMs));
@ -241,7 +256,8 @@ public class AnalyticsCollector
@Override @Override
public final void onAudioDecoderReleased(String decoderName) { public final void onAudioDecoderReleased(String decoderName) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_DECODER_RELEASED, AnalyticsListener.EVENT_AUDIO_DECODER_RELEASED,
listener -> listener.onAudioDecoderReleased(eventTime, decoderName)); listener -> listener.onAudioDecoderReleased(eventTime, decoderName));
} }
@ -250,7 +266,8 @@ public class AnalyticsCollector
@Override @Override
public final void onAudioDisabled(DecoderCounters counters) { public final void onAudioDisabled(DecoderCounters counters) {
EventTime eventTime = generatePlayingMediaPeriodEventTime(); EventTime eventTime = generatePlayingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_DISABLED, AnalyticsListener.EVENT_AUDIO_DISABLED,
listener -> { listener -> {
listener.onAudioDisabled(eventTime, counters); listener.onAudioDisabled(eventTime, counters);
@ -263,7 +280,8 @@ public class AnalyticsCollector
@Override @Override
public final void onAudioSessionId(int audioSessionId) { public final void onAudioSessionId(int audioSessionId) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_SESSION_ID, AnalyticsListener.EVENT_AUDIO_SESSION_ID,
listener -> listener.onAudioSessionId(eventTime, audioSessionId)); listener -> listener.onAudioSessionId(eventTime, audioSessionId));
} }
@ -271,7 +289,8 @@ public class AnalyticsCollector
@Override @Override
public void onAudioAttributesChanged(AudioAttributes audioAttributes) { public void onAudioAttributesChanged(AudioAttributes audioAttributes) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_ATTRIBUTES_CHANGED, AnalyticsListener.EVENT_AUDIO_ATTRIBUTES_CHANGED,
listener -> listener.onAudioAttributesChanged(eventTime, audioAttributes)); listener -> listener.onAudioAttributesChanged(eventTime, audioAttributes));
} }
@ -279,7 +298,8 @@ public class AnalyticsCollector
@Override @Override
public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) { public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_SKIP_SILENCE_ENABLED_CHANGED, AnalyticsListener.EVENT_SKIP_SILENCE_ENABLED_CHANGED,
listener -> listener.onSkipSilenceEnabledChanged(eventTime, skipSilenceEnabled)); listener -> listener.onSkipSilenceEnabledChanged(eventTime, skipSilenceEnabled));
} }
@ -287,7 +307,8 @@ public class AnalyticsCollector
@Override @Override
public void onAudioSinkError(Exception audioSinkError) { public void onAudioSinkError(Exception audioSinkError) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_AUDIO_SINK_ERROR, AnalyticsListener.EVENT_AUDIO_SINK_ERROR,
listener -> listener.onAudioSinkError(eventTime, audioSinkError)); listener -> listener.onAudioSinkError(eventTime, audioSinkError));
} }
@ -295,7 +316,8 @@ public class AnalyticsCollector
@Override @Override
public void onVolumeChanged(float audioVolume) { public void onVolumeChanged(float audioVolume) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_VOLUME_CHANGED, AnalyticsListener.EVENT_VOLUME_CHANGED,
listener -> listener.onVolumeChanged(eventTime, audioVolume)); listener -> listener.onVolumeChanged(eventTime, audioVolume));
} }
@ -306,7 +328,8 @@ public class AnalyticsCollector
@Override @Override
public final void onVideoEnabled(DecoderCounters counters) { public final void onVideoEnabled(DecoderCounters counters) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_VIDEO_ENABLED, AnalyticsListener.EVENT_VIDEO_ENABLED,
listener -> { listener -> {
listener.onVideoEnabled(eventTime, counters); listener.onVideoEnabled(eventTime, counters);
@ -319,7 +342,8 @@ public class AnalyticsCollector
public final void onVideoDecoderInitialized( public final void onVideoDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) { String decoderName, long initializedTimestampMs, long initializationDurationMs) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_VIDEO_DECODER_INITIALIZED, AnalyticsListener.EVENT_VIDEO_DECODER_INITIALIZED,
listener -> { listener -> {
listener.onVideoDecoderInitialized(eventTime, decoderName, initializationDurationMs); listener.onVideoDecoderInitialized(eventTime, decoderName, initializationDurationMs);
@ -333,7 +357,8 @@ public class AnalyticsCollector
public final void onVideoInputFormatChanged( public final void onVideoInputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_VIDEO_INPUT_FORMAT_CHANGED, AnalyticsListener.EVENT_VIDEO_INPUT_FORMAT_CHANGED,
listener -> { listener -> {
listener.onVideoInputFormatChanged(eventTime, format, decoderReuseEvaluation); listener.onVideoInputFormatChanged(eventTime, format, decoderReuseEvaluation);
@ -344,7 +369,8 @@ public class AnalyticsCollector
@Override @Override
public final void onDroppedFrames(int count, long elapsedMs) { public final void onDroppedFrames(int count, long elapsedMs) {
EventTime eventTime = generatePlayingMediaPeriodEventTime(); EventTime eventTime = generatePlayingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_DROPPED_VIDEO_FRAMES, AnalyticsListener.EVENT_DROPPED_VIDEO_FRAMES,
listener -> listener.onDroppedVideoFrames(eventTime, count, elapsedMs)); listener -> listener.onDroppedVideoFrames(eventTime, count, elapsedMs));
} }
@ -352,7 +378,8 @@ public class AnalyticsCollector
@Override @Override
public final void onVideoDecoderReleased(String decoderName) { public final void onVideoDecoderReleased(String decoderName) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_VIDEO_DECODER_RELEASED, AnalyticsListener.EVENT_VIDEO_DECODER_RELEASED,
listener -> listener.onVideoDecoderReleased(eventTime, decoderName)); listener -> listener.onVideoDecoderReleased(eventTime, decoderName));
} }
@ -361,7 +388,8 @@ public class AnalyticsCollector
@Override @Override
public final void onVideoDisabled(DecoderCounters counters) { public final void onVideoDisabled(DecoderCounters counters) {
EventTime eventTime = generatePlayingMediaPeriodEventTime(); EventTime eventTime = generatePlayingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_VIDEO_DISABLED, AnalyticsListener.EVENT_VIDEO_DISABLED,
listener -> { listener -> {
listener.onVideoDisabled(eventTime, counters); listener.onVideoDisabled(eventTime, counters);
@ -372,7 +400,8 @@ public class AnalyticsCollector
@Override @Override
public final void onRenderedFirstFrame(@Nullable Surface surface) { public final void onRenderedFirstFrame(@Nullable Surface surface) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_RENDERED_FIRST_FRAME, AnalyticsListener.EVENT_RENDERED_FIRST_FRAME,
listener -> listener.onRenderedFirstFrame(eventTime, surface)); listener -> listener.onRenderedFirstFrame(eventTime, surface));
} }
@ -380,7 +409,8 @@ public class AnalyticsCollector
@Override @Override
public final void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) { public final void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) {
EventTime eventTime = generatePlayingMediaPeriodEventTime(); EventTime eventTime = generatePlayingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_VIDEO_FRAME_PROCESSING_OFFSET, AnalyticsListener.EVENT_VIDEO_FRAME_PROCESSING_OFFSET,
listener -> listener ->
listener.onVideoFrameProcessingOffset(eventTime, totalProcessingOffsetUs, frameCount)); listener.onVideoFrameProcessingOffset(eventTime, totalProcessingOffsetUs, frameCount));
@ -397,7 +427,8 @@ public class AnalyticsCollector
public final void onVideoSizeChanged( public final void onVideoSizeChanged(
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_VIDEO_SIZE_CHANGED, AnalyticsListener.EVENT_VIDEO_SIZE_CHANGED,
listener -> listener ->
listener.onVideoSizeChanged( listener.onVideoSizeChanged(
@ -407,7 +438,8 @@ public class AnalyticsCollector
@Override @Override
public void onSurfaceSizeChanged(int width, int height) { public void onSurfaceSizeChanged(int width, int height) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_SURFACE_SIZE_CHANGED, AnalyticsListener.EVENT_SURFACE_SIZE_CHANGED,
listener -> listener.onSurfaceSizeChanged(eventTime, width, height)); listener -> listener.onSurfaceSizeChanged(eventTime, width, height));
} }
@ -421,7 +453,8 @@ public class AnalyticsCollector
LoadEventInfo loadEventInfo, LoadEventInfo loadEventInfo,
MediaLoadData mediaLoadData) { MediaLoadData mediaLoadData) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_LOAD_STARTED, AnalyticsListener.EVENT_LOAD_STARTED,
listener -> listener.onLoadStarted(eventTime, loadEventInfo, mediaLoadData)); listener -> listener.onLoadStarted(eventTime, loadEventInfo, mediaLoadData));
} }
@ -433,7 +466,8 @@ public class AnalyticsCollector
LoadEventInfo loadEventInfo, LoadEventInfo loadEventInfo,
MediaLoadData mediaLoadData) { MediaLoadData mediaLoadData) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_LOAD_COMPLETED, AnalyticsListener.EVENT_LOAD_COMPLETED,
listener -> listener.onLoadCompleted(eventTime, loadEventInfo, mediaLoadData)); listener -> listener.onLoadCompleted(eventTime, loadEventInfo, mediaLoadData));
} }
@ -445,7 +479,8 @@ public class AnalyticsCollector
LoadEventInfo loadEventInfo, LoadEventInfo loadEventInfo,
MediaLoadData mediaLoadData) { MediaLoadData mediaLoadData) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_LOAD_CANCELED, AnalyticsListener.EVENT_LOAD_CANCELED,
listener -> listener.onLoadCanceled(eventTime, loadEventInfo, mediaLoadData)); listener -> listener.onLoadCanceled(eventTime, loadEventInfo, mediaLoadData));
} }
@ -459,7 +494,8 @@ public class AnalyticsCollector
IOException error, IOException error,
boolean wasCanceled) { boolean wasCanceled) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_LOAD_ERROR, AnalyticsListener.EVENT_LOAD_ERROR,
listener -> listener ->
listener.onLoadError(eventTime, loadEventInfo, mediaLoadData, error, wasCanceled)); listener.onLoadError(eventTime, loadEventInfo, mediaLoadData, error, wasCanceled));
@ -469,7 +505,8 @@ public class AnalyticsCollector
public final void onUpstreamDiscarded( public final void onUpstreamDiscarded(
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) { int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_UPSTREAM_DISCARDED, AnalyticsListener.EVENT_UPSTREAM_DISCARDED,
listener -> listener.onUpstreamDiscarded(eventTime, mediaLoadData)); listener -> listener.onUpstreamDiscarded(eventTime, mediaLoadData));
} }
@ -478,7 +515,8 @@ public class AnalyticsCollector
public final void onDownstreamFormatChanged( public final void onDownstreamFormatChanged(
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) { int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_DOWNSTREAM_FORMAT_CHANGED, AnalyticsListener.EVENT_DOWNSTREAM_FORMAT_CHANGED,
listener -> listener.onDownstreamFormatChanged(eventTime, mediaLoadData)); listener -> listener.onDownstreamFormatChanged(eventTime, mediaLoadData));
} }
@ -493,7 +531,8 @@ public class AnalyticsCollector
public final void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) { public final void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) {
mediaPeriodQueueTracker.onTimelineChanged(checkNotNull(player)); mediaPeriodQueueTracker.onTimelineChanged(checkNotNull(player));
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_TIMELINE_CHANGED, AnalyticsListener.EVENT_TIMELINE_CHANGED,
listener -> listener.onTimelineChanged(eventTime, reason)); listener -> listener.onTimelineChanged(eventTime, reason));
} }
@ -502,7 +541,8 @@ public class AnalyticsCollector
public final void onMediaItemTransition( public final void onMediaItemTransition(
@Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) { @Nullable MediaItem mediaItem, @Player.MediaItemTransitionReason int reason) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_MEDIA_ITEM_TRANSITION, AnalyticsListener.EVENT_MEDIA_ITEM_TRANSITION,
listener -> listener.onMediaItemTransition(eventTime, mediaItem, reason)); listener -> listener.onMediaItemTransition(eventTime, mediaItem, reason));
} }
@ -511,7 +551,8 @@ public class AnalyticsCollector
public final void onTracksChanged( public final void onTracksChanged(
TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_TRACKS_CHANGED, AnalyticsListener.EVENT_TRACKS_CHANGED,
listener -> listener.onTracksChanged(eventTime, trackGroups, trackSelections)); listener -> listener.onTracksChanged(eventTime, trackGroups, trackSelections));
} }
@ -519,7 +560,8 @@ public class AnalyticsCollector
@Override @Override
public final void onStaticMetadataChanged(List<Metadata> metadataList) { public final void onStaticMetadataChanged(List<Metadata> metadataList) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_STATIC_METADATA_CHANGED, AnalyticsListener.EVENT_STATIC_METADATA_CHANGED,
listener -> listener.onStaticMetadataChanged(eventTime, metadataList)); listener -> listener.onStaticMetadataChanged(eventTime, metadataList));
} }
@ -527,7 +569,8 @@ public class AnalyticsCollector
@Override @Override
public final void onIsLoadingChanged(boolean isLoading) { public final void onIsLoadingChanged(boolean isLoading) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_IS_LOADING_CHANGED, AnalyticsListener.EVENT_IS_LOADING_CHANGED,
listener -> listener.onIsLoadingChanged(eventTime, isLoading)); listener -> listener.onIsLoadingChanged(eventTime, isLoading));
} }
@ -536,7 +579,8 @@ public class AnalyticsCollector
@Override @Override
public final void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) { public final void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
/* eventFlag= */ C.INDEX_UNSET, /* eventFlag= */ C.INDEX_UNSET,
listener -> listener.onPlayerStateChanged(eventTime, playWhenReady, playbackState)); listener -> listener.onPlayerStateChanged(eventTime, playWhenReady, playbackState));
} }
@ -544,7 +588,8 @@ public class AnalyticsCollector
@Override @Override
public final void onPlaybackStateChanged(@Player.State int state) { public final void onPlaybackStateChanged(@Player.State int state) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_PLAYBACK_STATE_CHANGED, AnalyticsListener.EVENT_PLAYBACK_STATE_CHANGED,
listener -> listener.onPlaybackStateChanged(eventTime, state)); listener -> listener.onPlaybackStateChanged(eventTime, state));
} }
@ -553,7 +598,8 @@ public class AnalyticsCollector
public final void onPlayWhenReadyChanged( public final void onPlayWhenReadyChanged(
boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) { boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_PLAY_WHEN_READY_CHANGED, AnalyticsListener.EVENT_PLAY_WHEN_READY_CHANGED,
listener -> listener.onPlayWhenReadyChanged(eventTime, playWhenReady, reason)); listener -> listener.onPlayWhenReadyChanged(eventTime, playWhenReady, reason));
} }
@ -562,7 +608,8 @@ public class AnalyticsCollector
public void onPlaybackSuppressionReasonChanged( public void onPlaybackSuppressionReasonChanged(
@PlaybackSuppressionReason int playbackSuppressionReason) { @PlaybackSuppressionReason int playbackSuppressionReason) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, AnalyticsListener.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED,
listener -> listener ->
listener.onPlaybackSuppressionReasonChanged(eventTime, playbackSuppressionReason)); listener.onPlaybackSuppressionReasonChanged(eventTime, playbackSuppressionReason));
@ -571,7 +618,8 @@ public class AnalyticsCollector
@Override @Override
public void onIsPlayingChanged(boolean isPlaying) { public void onIsPlayingChanged(boolean isPlaying) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_IS_PLAYING_CHANGED, AnalyticsListener.EVENT_IS_PLAYING_CHANGED,
listener -> listener.onIsPlayingChanged(eventTime, isPlaying)); listener -> listener.onIsPlayingChanged(eventTime, isPlaying));
} }
@ -579,7 +627,8 @@ public class AnalyticsCollector
@Override @Override
public final void onRepeatModeChanged(@Player.RepeatMode int repeatMode) { public final void onRepeatModeChanged(@Player.RepeatMode int repeatMode) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_REPEAT_MODE_CHANGED, AnalyticsListener.EVENT_REPEAT_MODE_CHANGED,
listener -> listener.onRepeatModeChanged(eventTime, repeatMode)); listener -> listener.onRepeatModeChanged(eventTime, repeatMode));
} }
@ -587,7 +636,8 @@ public class AnalyticsCollector
@Override @Override
public final void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { public final void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, AnalyticsListener.EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
listener -> listener.onShuffleModeChanged(eventTime, shuffleModeEnabled)); listener -> listener.onShuffleModeChanged(eventTime, shuffleModeEnabled));
} }
@ -598,8 +648,10 @@ public class AnalyticsCollector
error.mediaPeriodId != null error.mediaPeriodId != null
? generateEventTime(error.mediaPeriodId) ? generateEventTime(error.mediaPeriodId)
: generateCurrentPlayerMediaPeriodEventTime(); : generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
AnalyticsListener.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError(eventTime, error)); eventTime,
AnalyticsListener.EVENT_PLAYER_ERROR,
listener -> listener.onPlayerError(eventTime, error));
} }
@Override @Override
@ -609,7 +661,8 @@ public class AnalyticsCollector
} }
mediaPeriodQueueTracker.onPositionDiscontinuity(checkNotNull(player)); mediaPeriodQueueTracker.onPositionDiscontinuity(checkNotNull(player));
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_POSITION_DISCONTINUITY, AnalyticsListener.EVENT_POSITION_DISCONTINUITY,
listener -> listener.onPositionDiscontinuity(eventTime, reason)); listener -> listener.onPositionDiscontinuity(eventTime, reason));
} }
@ -617,7 +670,8 @@ public class AnalyticsCollector
@Override @Override
public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_PLAYBACK_PARAMETERS_CHANGED, AnalyticsListener.EVENT_PLAYBACK_PARAMETERS_CHANGED,
listener -> listener.onPlaybackParametersChanged(eventTime, playbackParameters)); listener -> listener.onPlaybackParametersChanged(eventTime, playbackParameters));
} }
@ -626,8 +680,8 @@ public class AnalyticsCollector
@Override @Override
public final void onSeekProcessed() { public final void onSeekProcessed() {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
/* eventFlag= */ C.INDEX_UNSET, listener -> listener.onSeekProcessed(eventTime)); eventTime, /* eventFlag= */ C.INDEX_UNSET, listener -> listener.onSeekProcessed(eventTime));
} }
// BandwidthMeter.Listener implementation. // BandwidthMeter.Listener implementation.
@ -635,7 +689,8 @@ public class AnalyticsCollector
@Override @Override
public final void onBandwidthSample(int elapsedMs, long bytes, long bitrate) { public final void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
EventTime eventTime = generateLoadingMediaPeriodEventTime(); EventTime eventTime = generateLoadingMediaPeriodEventTime();
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_BANDWIDTH_ESTIMATE, AnalyticsListener.EVENT_BANDWIDTH_ESTIMATE,
listener -> listener.onBandwidthEstimate(eventTime, elapsedMs, bytes, bitrate)); listener -> listener.onBandwidthEstimate(eventTime, elapsedMs, bytes, bitrate));
} }
@ -645,7 +700,8 @@ public class AnalyticsCollector
@Override @Override
public final void onDrmSessionAcquired(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) { public final void onDrmSessionAcquired(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_DRM_SESSION_ACQUIRED, AnalyticsListener.EVENT_DRM_SESSION_ACQUIRED,
listener -> listener.onDrmSessionAcquired(eventTime)); listener -> listener.onDrmSessionAcquired(eventTime));
} }
@ -653,15 +709,18 @@ public class AnalyticsCollector
@Override @Override
public final void onDrmKeysLoaded(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) { public final void onDrmKeysLoaded(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
AnalyticsListener.EVENT_DRM_KEYS_LOADED, listener -> listener.onDrmKeysLoaded(eventTime)); eventTime,
AnalyticsListener.EVENT_DRM_KEYS_LOADED,
listener -> listener.onDrmKeysLoaded(eventTime));
} }
@Override @Override
public final void onDrmSessionManagerError( public final void onDrmSessionManagerError(
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, Exception error) { int windowIndex, @Nullable MediaPeriodId mediaPeriodId, Exception error) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_DRM_SESSION_MANAGER_ERROR, AnalyticsListener.EVENT_DRM_SESSION_MANAGER_ERROR,
listener -> listener.onDrmSessionManagerError(eventTime, error)); listener -> listener.onDrmSessionManagerError(eventTime, error));
} }
@ -669,7 +728,8 @@ public class AnalyticsCollector
@Override @Override
public final void onDrmKeysRestored(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) { public final void onDrmKeysRestored(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_DRM_KEYS_RESTORED, AnalyticsListener.EVENT_DRM_KEYS_RESTORED,
listener -> listener.onDrmKeysRestored(eventTime)); listener -> listener.onDrmKeysRestored(eventTime));
} }
@ -677,20 +737,31 @@ public class AnalyticsCollector
@Override @Override
public final void onDrmKeysRemoved(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) { public final void onDrmKeysRemoved(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
AnalyticsListener.EVENT_DRM_KEYS_REMOVED, listener -> listener.onDrmKeysRemoved(eventTime)); eventTime,
AnalyticsListener.EVENT_DRM_KEYS_REMOVED,
listener -> listener.onDrmKeysRemoved(eventTime));
} }
@Override @Override
public final void onDrmSessionReleased(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) { public final void onDrmSessionReleased(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
listeners.sendEvent( sendEvent(
eventTime,
AnalyticsListener.EVENT_DRM_SESSION_RELEASED, AnalyticsListener.EVENT_DRM_SESSION_RELEASED,
listener -> listener.onDrmSessionReleased(eventTime)); listener -> listener.onDrmSessionReleased(eventTime));
} }
// Internal methods. // Internal methods.
private void sendEvent(
EventTime eventTime,
@AnalyticsListener.EventFlags int eventFlag,
ListenerSet.Event<AnalyticsListener> eventInvocation) {
eventTimes.put(eventFlag, eventTime);
listeners.sendEvent(eventFlag, eventInvocation);
}
/** Returns a new {@link EventTime} for the specified timeline, window and media period id. */ /** Returns a new {@link EventTime} for the specified timeline, window and media period id. */
@RequiresNonNull("player") @RequiresNonNull("player")
protected EventTime generateEventTime( protected EventTime generateEventTime(

View File

@ -15,7 +15,10 @@
*/ */
package com.google.android.exoplayer2.analytics; package com.google.android.exoplayer2.analytics;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.os.Looper; import android.os.Looper;
import android.util.SparseArray;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -63,6 +66,38 @@ public interface AnalyticsListener {
/** A set of {@link EventFlags}. */ /** A set of {@link EventFlags}. */
final class Events extends MutableFlags { final class Events extends MutableFlags {
private final SparseArray<EventTime> eventTimes;
/** Creates the set of event flags. */
public Events() {
eventTimes = new SparseArray<>(/* initialCapacity= */ 0);
}
/**
* Returns the {@link EventTime} for the specified event.
*
* @param event The {@link EventFlags event}.
* @return The {@link EventTime} of this event.
*/
public EventTime getEventTime(@EventFlags int event) {
return checkNotNull(eventTimes.get(event));
}
/**
* Sets the {@link EventTime} values for events recorded in this set.
*
* @param eventTimes A map from {@link EventFlags} to {@link EventTime}. Must at least contain
* all the events recorded in this set.
*/
public void setEventTimes(SparseArray<EventTime> eventTimes) {
this.eventTimes.clear();
for (int i = 0; i < size(); i++) {
@EventFlags int eventFlag = get(i);
this.eventTimes.append(eventFlag, checkNotNull(eventTimes.get(eventFlag)));
}
}
/** /**
* Returns whether the given event occurred. * Returns whether the given event occurred.
* *

View File

@ -8849,7 +8849,7 @@ public final class ExoPlayerTest {
} }
@Test @Test
public void onStateChangedFlags_correspondToListenerCalls() throws Exception { public void onEvents_correspondToListenerCalls() throws Exception {
ExoPlayer player = new TestExoPlayerBuilder(context).build(); ExoPlayer player = new TestExoPlayerBuilder(context).build();
EventListener listener = mock(EventListener.class); EventListener listener = mock(EventListener.class);
player.addListener(listener); player.addListener(listener);
@ -8897,13 +8897,14 @@ public final class ExoPlayerTest {
assertThat(events.contains(Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED)).isTrue(); assertThat(events.contains(Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED)).isTrue();
// Ensure all other events are called (even though we can't control how exactly they are // Ensure all other events are called (even though we can't control how exactly they are
// combined together in onStateChanged calls). // combined together in onEvents calls).
player.prepare(); player.prepare();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY);
player.play(); player.play();
player.setMediaItem(MediaItem.fromUri("http://this-will-throw-an-exception.mp4")); player.setMediaItem(MediaItem.fromUri("http://this-will-throw-an-exception.mp4"));
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_IDLE); TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_IDLE);
ShadowLooper.runMainLooperToNextTask(); ShadowLooper.runMainLooperToNextTask();
player.release();
// Verify that all callbacks have been called at least once. // Verify that all callbacks have been called at least once.
verify(listener, atLeastOnce()).onTimelineChanged(any(), anyInt()); verify(listener, atLeastOnce()).onTimelineChanged(any(), anyInt());
@ -8920,7 +8921,7 @@ public final class ExoPlayerTest {
verify(listener, atLeastOnce()).onIsPlayingChanged(anyBoolean()); verify(listener, atLeastOnce()).onIsPlayingChanged(anyBoolean());
verify(listener, atLeastOnce()).onPlayerError(any()); verify(listener, atLeastOnce()).onPlayerError(any());
// Verify all the same events have been recorded with onStateChanged. // Verify all the same events have been recorded with onEvents.
verify(listener, atLeastOnce()).onEvents(eq(player), eventCaptor.capture()); verify(listener, atLeastOnce()).onEvents(eq(player), eventCaptor.capture());
List<Player.Events> allEvents = eventCaptor.getAllValues(); List<Player.Events> allEvents = eventCaptor.getAllValues();
assertThat(containsEvent(allEvents, Player.EVENT_TIMELINE_CHANGED)).isTrue(); assertThat(containsEvent(allEvents, Player.EVENT_TIMELINE_CHANGED)).isTrue();

View File

@ -28,11 +28,19 @@ import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DR
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_SESSION_RELEASED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DRM_SESSION_RELEASED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DROPPED_VIDEO_FRAMES; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_DROPPED_VIDEO_FRAMES;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_IS_LOADING_CHANGED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_IS_LOADING_CHANGED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_IS_PLAYING_CHANGED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_CANCELED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_COMPLETED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_COMPLETED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_ERROR;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_STARTED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_LOAD_STARTED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_MEDIA_ITEM_TRANSITION;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAYBACK_PARAMETERS_CHANGED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAYBACK_STATE_CHANGED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAYER_ERROR; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAYER_ERROR;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_PLAY_WHEN_READY_CHANGED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_POSITION_DISCONTINUITY; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_POSITION_DISCONTINUITY;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_RENDERED_FIRST_FRAME; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_RENDERED_FIRST_FRAME;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_STATIC_METADATA_CHANGED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_TIMELINE_CHANGED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_TIMELINE_CHANGED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_TRACKS_CHANGED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_TRACKS_CHANGED;
import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_DECODER_INITIALIZED; import static com.google.android.exoplayer2.analytics.AnalyticsListener.EVENT_VIDEO_DECODER_INITIALIZED;
@ -45,11 +53,18 @@ import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSample
import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.os.Looper; import android.os.Looper;
import android.util.SparseArray;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@ -57,6 +72,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
@ -72,6 +88,7 @@ import com.google.android.exoplayer2.drm.ExoMediaDrm;
import com.google.android.exoplayer2.drm.MediaDrmCallback; import com.google.android.exoplayer2.drm.MediaDrmCallback;
import com.google.android.exoplayer2.drm.MediaDrmCallbackException; import com.google.android.exoplayer2.drm.MediaDrmCallbackException;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaLoadData;
@ -89,9 +106,11 @@ import com.google.android.exoplayer2.testutil.FakeRenderer;
import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.FakeVideoRenderer; import com.google.android.exoplayer2.testutil.FakeVideoRenderer;
import com.google.android.exoplayer2.testutil.TestExoPlayerBuilder;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.IOException; import java.io.IOException;
@ -103,8 +122,10 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder; import org.mockito.InOrder;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.robolectric.shadows.ShadowLooper;
/** Integration test for {@link AnalyticsCollector}. */ /** Integration test for {@link AnalyticsCollector}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@ -1601,6 +1622,264 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period1); assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period1);
} }
@Test
public void onEvents_isReportedWithCorrectEventTimes() throws Exception {
SimpleExoPlayer player =
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext()).build();
AnalyticsListener listener = mock(AnalyticsListener.class);
Format[] formats =
new Format[] {
new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build(),
new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).build()
};
player.addAnalyticsListener(listener);
// Trigger some simultaneous events.
player.setMediaSource(new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), formats));
player.seekTo(2_000);
player.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2.0f));
ShadowLooper.runMainLooperToNextTask();
// Move to another item and fail with a third one to trigger events with different EventTimes.
player.prepare();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY);
player.addMediaSource(new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), formats));
player.play();
TestPlayerRunHelper.runUntilPositionDiscontinuity(
player, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION);
player.setMediaItem(MediaItem.fromUri("http://this-will-throw-an-exception.mp4"));
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_IDLE);
ShadowLooper.runMainLooperToNextTask();
player.release();
// Verify that expected individual callbacks have been called and capture EventTimes.
ArgumentCaptor<AnalyticsListener.EventTime> individualTimelineChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onTimelineChanged(individualTimelineChangedEventTimes.capture(), anyInt());
ArgumentCaptor<AnalyticsListener.EventTime> individualMediaItemTransitionEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onMediaItemTransition(individualMediaItemTransitionEventTimes.capture(), any(), anyInt());
ArgumentCaptor<AnalyticsListener.EventTime> individualPositionDiscontinuityEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onPositionDiscontinuity(individualPositionDiscontinuityEventTimes.capture(), anyInt());
ArgumentCaptor<AnalyticsListener.EventTime> individualPlaybackStateChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onPlaybackStateChanged(individualPlaybackStateChangedEventTimes.capture(), anyInt());
ArgumentCaptor<AnalyticsListener.EventTime> individualIsLoadingChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onIsLoadingChanged(individualIsLoadingChangedEventTimes.capture(), anyBoolean());
ArgumentCaptor<AnalyticsListener.EventTime> individualTracksChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onTracksChanged(individualTracksChangedEventTimes.capture(), any(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualPlayWhenReadyChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onPlayWhenReadyChanged(
individualPlayWhenReadyChangedEventTimes.capture(), anyBoolean(), anyInt());
ArgumentCaptor<AnalyticsListener.EventTime> individualIsPlayingChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onIsPlayingChanged(individualIsPlayingChangedEventTimes.capture(), anyBoolean());
ArgumentCaptor<AnalyticsListener.EventTime> individualPlayerErrorEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce()).onPlayerError(individualPlayerErrorEventTimes.capture(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualPlaybackParametersChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onPlaybackParametersChanged(
individualPlaybackParametersChangedEventTimes.capture(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualLoadStartedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onLoadStarted(individualLoadStartedEventTimes.capture(), any(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualLoadCompletedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onLoadCompleted(individualLoadCompletedEventTimes.capture(), any(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualLoadErrorEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onLoadError(individualLoadErrorEventTimes.capture(), any(), any(), any(), anyBoolean());
ArgumentCaptor<AnalyticsListener.EventTime> individualVideoEnabledEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onVideoEnabled(individualVideoEnabledEventTimes.capture(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualAudioEnabledEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onAudioEnabled(individualAudioEnabledEventTimes.capture(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualStaticMetadataChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onStaticMetadataChanged(individualStaticMetadataChangedEventTimes.capture(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualDownstreamFormatChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onDownstreamFormatChanged(individualDownstreamFormatChangedEventTimes.capture(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualVideoInputFormatChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onVideoInputFormatChanged(
individualVideoInputFormatChangedEventTimes.capture(), any(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualAudioInputFormatChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onAudioInputFormatChanged(
individualAudioInputFormatChangedEventTimes.capture(), any(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualVideoDecoderInitializedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onVideoDecoderInitialized(
individualVideoDecoderInitializedEventTimes.capture(), any(), anyLong());
ArgumentCaptor<AnalyticsListener.EventTime> individualAudioDecoderInitializedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onAudioDecoderInitialized(
individualAudioDecoderInitializedEventTimes.capture(), any(), anyLong());
ArgumentCaptor<AnalyticsListener.EventTime> individualVideoDisabledEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onVideoDisabled(individualVideoDisabledEventTimes.capture(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualAudioDisabledEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onAudioDisabled(individualAudioDisabledEventTimes.capture(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualRenderedFirstFrameEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onRenderedFirstFrame(individualRenderedFirstFrameEventTimes.capture(), any());
ArgumentCaptor<AnalyticsListener.EventTime> individualVideoSizeChangedEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onVideoSizeChanged(
individualVideoSizeChangedEventTimes.capture(),
anyInt(),
anyInt(),
anyInt(),
anyFloat());
ArgumentCaptor<AnalyticsListener.EventTime> individualAudioPositionAdvancingEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onAudioPositionAdvancing(individualAudioPositionAdvancingEventTimes.capture(), anyLong());
ArgumentCaptor<AnalyticsListener.EventTime> individualVideoProcessingOffsetEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onVideoFrameProcessingOffset(
individualVideoProcessingOffsetEventTimes.capture(), anyLong(), anyInt());
ArgumentCaptor<AnalyticsListener.EventTime> individualDroppedFramesEventTimes =
ArgumentCaptor.forClass(AnalyticsListener.EventTime.class);
verify(listener, atLeastOnce())
.onDroppedVideoFrames(individualDroppedFramesEventTimes.capture(), anyInt(), anyLong());
// Verify the EventTimes reported with onEvents are a non-empty subset of the individual
// callback EventTimes. We can only assert they are a non-empty subset because there may be
// multiple events of the same type arriving in the same message queue iteration.
ArgumentCaptor<AnalyticsListener.Events> eventsCaptor =
ArgumentCaptor.forClass(AnalyticsListener.Events.class);
verify(listener, atLeastOnce()).onEvents(eq(player), eventsCaptor.capture());
SparseArray<List<AnalyticsListener.EventTime>> onEventsEventTimes = new SparseArray<>();
for (AnalyticsListener.Events events : eventsCaptor.getAllValues()) {
for (int i = 0; i < events.size(); i++) {
@AnalyticsListener.EventFlags int event = events.get(i);
if (onEventsEventTimes.get(event) == null) {
onEventsEventTimes.put(event, new ArrayList<>());
}
onEventsEventTimes.get(event).add(events.getEventTime(event));
}
}
// SparseArray.get returns null if the key doesn't exist, thus verifying the sets are non-empty.
assertThat(individualTimelineChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_TIMELINE_CHANGED))
.inOrder();
assertThat(individualMediaItemTransitionEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_MEDIA_ITEM_TRANSITION))
.inOrder();
assertThat(individualPositionDiscontinuityEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_POSITION_DISCONTINUITY))
.inOrder();
assertThat(individualPlaybackStateChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_PLAYBACK_STATE_CHANGED))
.inOrder();
assertThat(individualIsLoadingChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_IS_LOADING_CHANGED))
.inOrder();
assertThat(individualTracksChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_TRACKS_CHANGED))
.inOrder();
assertThat(individualPlayWhenReadyChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_PLAY_WHEN_READY_CHANGED))
.inOrder();
assertThat(individualIsPlayingChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_IS_PLAYING_CHANGED))
.inOrder();
assertThat(individualPlayerErrorEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_PLAYER_ERROR))
.inOrder();
assertThat(individualPlaybackParametersChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_PLAYBACK_PARAMETERS_CHANGED))
.inOrder();
assertThat(individualLoadStartedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_LOAD_STARTED))
.inOrder();
assertThat(individualLoadCompletedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_LOAD_COMPLETED))
.inOrder();
assertThat(individualLoadErrorEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_LOAD_ERROR))
.inOrder();
assertThat(individualVideoEnabledEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_VIDEO_ENABLED))
.inOrder();
assertThat(individualAudioEnabledEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_AUDIO_ENABLED))
.inOrder();
assertThat(individualStaticMetadataChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_STATIC_METADATA_CHANGED))
.inOrder();
assertThat(individualDownstreamFormatChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_DOWNSTREAM_FORMAT_CHANGED))
.inOrder();
assertThat(individualVideoInputFormatChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_VIDEO_INPUT_FORMAT_CHANGED))
.inOrder();
assertThat(individualAudioInputFormatChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_AUDIO_INPUT_FORMAT_CHANGED))
.inOrder();
assertThat(individualVideoDecoderInitializedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_VIDEO_DECODER_INITIALIZED))
.inOrder();
assertThat(individualAudioDecoderInitializedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_AUDIO_DECODER_INITIALIZED))
.inOrder();
assertThat(individualVideoDisabledEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_VIDEO_DISABLED))
.inOrder();
assertThat(individualAudioDisabledEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_AUDIO_DISABLED))
.inOrder();
assertThat(individualRenderedFirstFrameEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_RENDERED_FIRST_FRAME))
.inOrder();
assertThat(individualVideoSizeChangedEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_VIDEO_SIZE_CHANGED))
.inOrder();
assertThat(individualAudioPositionAdvancingEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_AUDIO_POSITION_ADVANCING))
.inOrder();
assertThat(individualVideoProcessingOffsetEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
.inOrder();
assertThat(individualDroppedFramesEventTimes.getAllValues())
.containsAtLeastElementsIn(onEventsEventTimes.get(EVENT_DROPPED_VIDEO_FRAMES))
.inOrder();
}
private void populateEventIds(Timeline timeline) { private void populateEventIds(Timeline timeline) {
period0 = period0 =
new EventWindowAndPeriodId( new EventWindowAndPeriodId(