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

View File

@ -15,7 +15,10 @@
*/
package com.google.android.exoplayer2.analytics;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.os.Looper;
import android.util.SparseArray;
import android.view.Surface;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@ -63,6 +66,38 @@ public interface AnalyticsListener {
/** A set of {@link EventFlags}. */
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.
*

View File

@ -8849,7 +8849,7 @@ public final class ExoPlayerTest {
}
@Test
public void onStateChangedFlags_correspondToListenerCalls() throws Exception {
public void onEvents_correspondToListenerCalls() throws Exception {
ExoPlayer player = new TestExoPlayerBuilder(context).build();
EventListener listener = mock(EventListener.class);
player.addListener(listener);
@ -8897,13 +8897,14 @@ public final class ExoPlayerTest {
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
// combined together in onStateChanged calls).
// combined together in onEvents calls).
player.prepare();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY);
player.play();
player.setMediaItem(MediaItem.fromUri("http://this-will-throw-an-exception.mp4"));
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_IDLE);
ShadowLooper.runMainLooperToNextTask();
player.release();
// Verify that all callbacks have been called at least once.
verify(listener, atLeastOnce()).onTimelineChanged(any(), anyInt());
@ -8920,7 +8921,7 @@ public final class ExoPlayerTest {
verify(listener, atLeastOnce()).onIsPlayingChanged(anyBoolean());
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());
List<Player.Events> allEvents = eventCaptor.getAllValues();
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_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_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_ERROR;
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_PLAY_WHEN_READY_CHANGED;
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_STATIC_METADATA_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_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.common.truth.Truth.assertThat;
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.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.os.Looper;
import android.util.SparseArray;
import android.view.Surface;
import androidx.annotation.Nullable;
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.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
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.MediaDrmCallbackException;
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.LoadEventInfo;
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.TimelineWindowDefinition;
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.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@ -103,8 +122,10 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.robolectric.shadows.ShadowLooper;
/** Integration test for {@link AnalyticsCollector}. */
@RunWith(AndroidJUnit4.class)
@ -1601,6 +1622,264 @@ public final class AnalyticsCollectorTest {
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) {
period0 =
new EventWindowAndPeriodId(