diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 9fa9257459..29b8967587 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -76,7 +76,6 @@ public class AnalyticsCollector private final MediaPeriodQueueTracker mediaPeriodQueueTracker; private @MonotonicNonNull Player player; - private boolean isSeeking; /** * Creates an analytics collector. @@ -127,9 +126,9 @@ public class AnalyticsCollector * adjusts its state and position to the seek. */ public final void notifySeekStarted() { - if (!isSeeking) { + if (!mediaPeriodQueueTracker.isSeeking()) { EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); - isSeeking = true; + mediaPeriodQueueTracker.onSeekStarted(); for (AnalyticsListener listener : listeners) { listener.onSeekStarted(eventTime); } @@ -310,8 +309,7 @@ public class AnalyticsCollector @Override public final void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) { - mediaPeriodQueueTracker.onMediaPeriodCreated( - windowIndex, mediaPeriodId, Assertions.checkNotNull(player)); + mediaPeriodQueueTracker.onMediaPeriodCreated(windowIndex, mediaPeriodId); EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); for (AnalyticsListener listener : listeners) { listener.onMediaPeriodCreated(eventTime); @@ -321,8 +319,7 @@ public class AnalyticsCollector @Override public final void onMediaPeriodReleased(int windowIndex, MediaPeriodId mediaPeriodId) { EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId); - if (mediaPeriodQueueTracker.onMediaPeriodReleased( - mediaPeriodId, Assertions.checkNotNull(player))) { + if (mediaPeriodQueueTracker.onMediaPeriodReleased(mediaPeriodId)) { for (AnalyticsListener listener : listeners) { listener.onMediaPeriodReleased(eventTime); } @@ -414,7 +411,7 @@ public class AnalyticsCollector @Override public final void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) { - mediaPeriodQueueTracker.onTimelineChanged(timeline, Assertions.checkNotNull(player)); + mediaPeriodQueueTracker.onTimelineChanged(timeline); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onTimelineChanged(eventTime, reason); @@ -481,7 +478,7 @@ public class AnalyticsCollector @Override public final void onPlayerError(ExoPlaybackException error) { - EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); + EventTime eventTime = generatePlayingMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onPlayerError(eventTime, error); } @@ -489,7 +486,6 @@ public class AnalyticsCollector @Override public final void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { - mediaPeriodQueueTracker.onPositionDiscontinuity(Assertions.checkNotNull(player)); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onPositionDiscontinuity(eventTime, reason); @@ -506,8 +502,8 @@ public class AnalyticsCollector @Override public final void onSeekProcessed() { - if (isSeeking) { - isSeeking = false; + if (mediaPeriodQueueTracker.isSeeking()) { + mediaPeriodQueueTracker.onSeekProcessed(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onSeekProcessed(eventTime); @@ -624,10 +620,13 @@ public class AnalyticsCollector Assertions.checkNotNull(player); if (mediaPeriodInfo == null) { int windowIndex = player.getCurrentWindowIndex(); - Timeline timeline = player.getCurrentTimeline(); - boolean windowIsInTimeline = windowIndex < timeline.getWindowCount(); - return generateEventTime( - windowIsInTimeline ? timeline : Timeline.EMPTY, windowIndex, /* mediaPeriodId= */ null); + mediaPeriodInfo = mediaPeriodQueueTracker.tryResolveWindowIndex(windowIndex); + if (mediaPeriodInfo == null) { + Timeline timeline = player.getCurrentTimeline(); + boolean windowIsInTimeline = windowIndex < timeline.getWindowCount(); + return generateEventTime( + windowIsInTimeline ? timeline : Timeline.EMPTY, windowIndex, /* mediaPeriodId= */ null); + } } return generateEventTime( mediaPeriodInfo.timeline, mediaPeriodInfo.windowIndex, mediaPeriodInfo.mediaPeriodId); @@ -674,10 +673,10 @@ public class AnalyticsCollector private final HashMap mediaPeriodIdToInfo; private final Period period; - @Nullable private MediaPeriodInfo currentPlayerMediaPeriod; - private @MonotonicNonNull MediaPeriodInfo playingMediaPeriod; + @Nullable private MediaPeriodInfo playingMediaPeriod; @Nullable private MediaPeriodInfo readingMediaPeriod; private Timeline timeline; + private boolean isSeeking; public MediaPeriodQueueTracker() { mediaPeriodInfoQueue = new ArrayList<>(); @@ -690,11 +689,14 @@ public class AnalyticsCollector * Returns the {@link MediaPeriodInfo} of the media period corresponding the current position of * the player. * - *

May be null if no matching media period has been created yet. + *

May be null if no matching media period has been created yet or the player is currently + * masking its state. */ @Nullable public MediaPeriodInfo getCurrentPlayerMediaPeriod() { - return currentPlayerMediaPeriod; + return mediaPeriodInfoQueue.isEmpty() || timeline.isEmpty() || isSeeking + ? null + : mediaPeriodInfoQueue.get(0); } /** @@ -737,13 +739,35 @@ public class AnalyticsCollector return mediaPeriodIdToInfo.get(mediaPeriodId); } - /** Updates the queue with a reported position discontinuity. */ - public void onPositionDiscontinuity(Player player) { - currentPlayerMediaPeriod = findMatchingMediaPeriodInQueue(player); + /** Returns whether the player is currently seeking. */ + public boolean isSeeking() { + return isSeeking; + } + + /** + * Tries to find an existing media period info from the specified window index. Only returns a + * non-null media period info if there is a unique, unambiguous match. + */ + @Nullable + public MediaPeriodInfo tryResolveWindowIndex(int windowIndex) { + MediaPeriodInfo match = null; + for (int i = 0; i < mediaPeriodInfoQueue.size(); i++) { + MediaPeriodInfo info = mediaPeriodInfoQueue.get(i); + int periodIndex = timeline.getIndexOfPeriod(info.mediaPeriodId.periodUid); + if (periodIndex != C.INDEX_UNSET + && timeline.getPeriod(periodIndex, period).windowIndex == windowIndex) { + if (match != null) { + // Ambiguous match. + return null; + } + match = info; + } + } + return match; } /** Updates the queue with a reported timeline change. */ - public void onTimelineChanged(Timeline timeline, Player player) { + public void onTimelineChanged(Timeline timeline) { for (int i = 0; i < mediaPeriodInfoQueue.size(); i++) { MediaPeriodInfo newMediaPeriodInfo = updateMediaPeriodInfoToNewTimeline(mediaPeriodInfoQueue.get(i), timeline); @@ -757,11 +781,20 @@ public class AnalyticsCollector playingMediaPeriod = mediaPeriodInfoQueue.get(0); } this.timeline = timeline; - currentPlayerMediaPeriod = findMatchingMediaPeriodInQueue(player); + } + + /** Updates the queue with a reported start of seek. */ + public void onSeekStarted() { + isSeeking = true; + } + + /** Updates the queue with a reported processed seek. */ + public void onSeekProcessed() { + isSeeking = false; } /** Updates the queue with a newly created media period. */ - public void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId, Player player) { + public void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) { int periodIndex = timeline.getIndexOfPeriod(mediaPeriodId.periodUid); boolean isInTimeline = periodIndex != C.INDEX_UNSET; MediaPeriodInfo mediaPeriodInfo = @@ -772,17 +805,14 @@ public class AnalyticsCollector mediaPeriodInfoQueue.add(mediaPeriodInfo); mediaPeriodIdToInfo.put(mediaPeriodId, mediaPeriodInfo); playingMediaPeriod = mediaPeriodInfoQueue.get(0); - if (currentPlayerMediaPeriod == null && isMatchingPlayingMediaPeriod(player)) { - currentPlayerMediaPeriod = playingMediaPeriod; - } } /** * Updates the queue with a released media period. Returns whether the media period was still in * the queue. */ - public boolean onMediaPeriodReleased(MediaPeriodId mediaPeriodId, Player player) { - @Nullable MediaPeriodInfo mediaPeriodInfo = mediaPeriodIdToInfo.remove(mediaPeriodId); + public boolean onMediaPeriodReleased(MediaPeriodId mediaPeriodId) { + MediaPeriodInfo mediaPeriodInfo = mediaPeriodIdToInfo.remove(mediaPeriodId); if (mediaPeriodInfo == null) { // The media period has already been removed from the queue in resetForNewPlaylist(). return false; @@ -794,9 +824,6 @@ public class AnalyticsCollector if (!mediaPeriodInfoQueue.isEmpty()) { playingMediaPeriod = mediaPeriodInfoQueue.get(0); } - if (currentPlayerMediaPeriod == null && isMatchingPlayingMediaPeriod(player)) { - currentPlayerMediaPeriod = playingMediaPeriod; - } return true; } @@ -805,99 +832,6 @@ public class AnalyticsCollector readingMediaPeriod = mediaPeriodIdToInfo.get(mediaPeriodId); } - @Nullable - private MediaPeriodInfo findMatchingMediaPeriodInQueue(Player player) { - Timeline playerTimeline = player.getCurrentTimeline(); - int playerPeriodIndex = player.getCurrentPeriodIndex(); - @Nullable - Object playerPeriodUid = - playerTimeline.isEmpty() ? null : playerTimeline.getUidOfPeriod(playerPeriodIndex); - int playerNextAdGroupIndex = - player.isPlayingAd() || playerTimeline.isEmpty() - ? C.INDEX_UNSET - : playerTimeline - .getPeriod(playerPeriodIndex, period) - .getAdGroupIndexAfterPositionUs(C.msToUs(player.getCurrentPosition())); - for (int i = 0; i < mediaPeriodInfoQueue.size(); i++) { - MediaPeriodInfo mediaPeriodInfo = mediaPeriodInfoQueue.get(i); - if (isMatchingMediaPeriod( - mediaPeriodInfo, - playerTimeline, - player.getCurrentWindowIndex(), - playerPeriodUid, - player.isPlayingAd(), - player.getCurrentAdGroupIndex(), - player.getCurrentAdIndexInAdGroup(), - playerNextAdGroupIndex)) { - return mediaPeriodInfo; - } - } - if (mediaPeriodInfoQueue.isEmpty() && playingMediaPeriod != null) { - if (isMatchingMediaPeriod( - playingMediaPeriod, - playerTimeline, - player.getCurrentWindowIndex(), - playerPeriodUid, - player.isPlayingAd(), - player.getCurrentAdGroupIndex(), - player.getCurrentAdIndexInAdGroup(), - playerNextAdGroupIndex)) { - return playingMediaPeriod; - } - } - return null; - } - - private boolean isMatchingPlayingMediaPeriod(Player player) { - if (playingMediaPeriod == null) { - return false; - } - Timeline playerTimeline = player.getCurrentTimeline(); - int playerPeriodIndex = player.getCurrentPeriodIndex(); - @Nullable - Object playerPeriodUid = - playerTimeline.isEmpty() ? null : playerTimeline.getUidOfPeriod(playerPeriodIndex); - int playerNextAdGroupIndex = - player.isPlayingAd() || playerTimeline.isEmpty() - ? C.INDEX_UNSET - : playerTimeline - .getPeriod(playerPeriodIndex, period) - .getAdGroupIndexAfterPositionUs(C.msToUs(player.getCurrentPosition())); - return isMatchingMediaPeriod( - playingMediaPeriod, - playerTimeline, - player.getCurrentWindowIndex(), - playerPeriodUid, - player.isPlayingAd(), - player.getCurrentAdGroupIndex(), - player.getCurrentAdIndexInAdGroup(), - playerNextAdGroupIndex); - } - - private static boolean isMatchingMediaPeriod( - MediaPeriodInfo mediaPeriodInfo, - Timeline playerTimeline, - int playerWindowIndex, - @Nullable Object playerPeriodUid, - boolean isPlayingAd, - int playerAdGroupIndex, - int playerAdIndexInAdGroup, - int playerNextAdGroupIndex) { - if (mediaPeriodInfo.timeline.isEmpty() - || !mediaPeriodInfo.timeline.equals(playerTimeline) - || mediaPeriodInfo.windowIndex != playerWindowIndex - || !mediaPeriodInfo.mediaPeriodId.periodUid.equals(playerPeriodUid)) { - return false; - } - // Timeline period matches. Still need to check ad information. - return (isPlayingAd - && mediaPeriodInfo.mediaPeriodId.adGroupIndex == playerAdGroupIndex - && mediaPeriodInfo.mediaPeriodId.adIndexInAdGroup == playerAdIndexInAdGroup) - || (!isPlayingAd - && mediaPeriodInfo.mediaPeriodId.adGroupIndex == C.INDEX_UNSET - && mediaPeriodInfo.mediaPeriodId.nextAdGroupIndex == playerNextAdGroupIndex); - } - private MediaPeriodInfo updateMediaPeriodInfoToNewTimeline( MediaPeriodInfo info, Timeline newTimeline) { int newPeriodIndex = newTimeline.getIndexOfPeriod(info.mediaPeriodId.periodUid); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 36809c4ee7..8149e63f2c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.SurfaceTexture; import android.media.AudioManager; +import android.net.Uri; import android.os.Looper; import android.view.Surface; import androidx.annotation.Nullable; @@ -2968,7 +2969,10 @@ public final class ExoPlayerTest { @Test public void contentWithInitialSeekPositionAfterPrerollAdStartsAtSeekPosition() throws Exception { AdPlaybackState adPlaybackState = - FakeTimeline.createAdPlaybackState(/* adsPerAdGroup= */ 3, /* adGroupTimesUs...= */ 0); + FakeTimeline.createAdPlaybackState(/* adsPerAdGroup= */ 3, /* adGroupTimesUs= */ 0) + .withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, Uri.parse("https://ad1")) + .withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, Uri.parse("https://ad2")) + .withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, Uri.parse("https://ad3")); Timeline fakeTimeline = new FakeTimeline( new TimelineWindowDefinition( @@ -4056,14 +4060,15 @@ public final class ExoPlayerTest { } }) .build(); - new ExoPlayerTestRunner.Builder() - .setMediaSources(concatenatingMediaSource) - .initialSeek(seekToWindowIndex, 5000) - .setActionSchedule(actionSchedule) - .build(context) - .start() - .blockUntilActionScheduleFinished(TIMEOUT_MS) - .blockUntilEnded(TIMEOUT_MS); + ExoPlayerTestRunner testRunner = + new ExoPlayerTestRunner.Builder() + .setMediaSources(concatenatingMediaSource) + .initialSeek(seekToWindowIndex, 5000) + .setActionSchedule(actionSchedule) + .build(context) + .start() + .blockUntilActionScheduleFinished(TIMEOUT_MS) + .blockUntilEnded(TIMEOUT_MS); assertArrayEquals(new long[] {5_000}, currentPlaybackPositions); assertArrayEquals(new int[] {seekToWindowIndex}, currentWindowIndices); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index b9ad0a14c5..547a0be459 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -23,7 +23,6 @@ import android.view.Surface; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; 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.PlaybackParameters; @@ -42,14 +41,12 @@ import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable; import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner; import com.google.android.exoplayer2.testutil.FakeMediaSource; 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.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener; @@ -57,8 +54,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.LooperMode; @@ -138,7 +133,7 @@ public final class AnalyticsCollectorTest { .containsExactly( WINDOW_0 /* setPlayWhenReady */, WINDOW_0 /* BUFFERING */, WINDOW_0 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, WINDOW_0 /* SOURCE_UPDATE */); + .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, WINDOW_0 /* DYNAMIC */); listener.assertNoMoreEvents(); } @@ -159,7 +154,7 @@ public final class AnalyticsCollectorTest { period0 /* READY */, period0 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, WINDOW_0 /* SOURCE_UPDATE */); + .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, WINDOW_0 /* DYNAMIC */); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) .containsExactly(period0 /* started */, period0 /* stopped */); assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(period0); @@ -206,7 +201,7 @@ public final class AnalyticsCollectorTest { period0 /* READY */, period1 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */); + .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* DYNAMIC */); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) .containsExactly(period0, period0, period0, period0); @@ -262,7 +257,7 @@ public final class AnalyticsCollectorTest { period1 /* READY */, period1 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */); + .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* DYNAMIC */); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) .containsExactly(period0, period0, period0, period0); @@ -325,7 +320,7 @@ public final class AnalyticsCollectorTest { period1 /* setPlayWhenReady=true */, period1 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */); + .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* DYNAMIC */); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period1); assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period1); @@ -400,7 +395,7 @@ public final class AnalyticsCollectorTest { period1Seq2 /* READY */, period1Seq2 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */); + .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* DYNAMIC */); assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)) .containsExactly(period0, period1Seq2); assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0); @@ -482,9 +477,9 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly( WINDOW_0 /* PLAYLIST_CHANGE */, - WINDOW_0 /* SOURCE_UPDATE */, + WINDOW_0 /* DYNAMIC */, WINDOW_0 /* PLAYLIST_CHANGE */, - WINDOW_0 /* SOURCE_UPDATE */); + WINDOW_0 /* DYNAMIC */); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) .containsExactly(period0Seq0, period0Seq0, period0Seq1, period0Seq1); assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) @@ -527,13 +522,11 @@ public final class AnalyticsCollectorTest { new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT); ActionSchedule actionSchedule = new ActionSchedule.Builder("AnalyticsCollectorTest") - .pause() .waitForPlaybackState(Player.STATE_READY) .throwPlaybackException(ExoPlaybackException.createForSource(new IOException())) .waitForPlaybackState(Player.STATE_IDLE) .seek(/* positionMs= */ 0) .prepare() - .play() .waitForPlaybackState(Player.STATE_ENDED) .build(); TestAnalyticsListener listener = runAnalyticsTest(mediaSource, actionSchedule); @@ -542,19 +535,17 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) .containsExactly( WINDOW_0 /* setPlayWhenReady=true */, - WINDOW_0 /* setPlayWhenReady=false */, WINDOW_0 /* BUFFERING */, period0Seq0 /* READY */, - period0Seq0 /* IDLE */, - period0Seq0 /* BUFFERING */, - period0Seq0 /* setPlayWhenReady=true */, + WINDOW_0 /* IDLE */, + WINDOW_0 /* BUFFERING */, period0Seq0 /* READY */, period0Seq0 /* ENDED */); assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly(WINDOW_0 /* prepared */, WINDOW_0 /* prepared */); - assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period0Seq0); - assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0Seq0); - assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(period0Seq0); + assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(WINDOW_0); + assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(WINDOW_0); + assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(WINDOW_0); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) .containsExactly(period0Seq0, period0Seq0, period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period0Seq0); @@ -582,7 +573,8 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) .containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0Seq0); - assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq0); + assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)) + .containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) .containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) @@ -628,8 +620,8 @@ public final class AnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) .containsExactly( WINDOW_0 /* PLAYLIST_CHANGED */, - window0Period1Seq0 /* SOURCE_UPDATE (concatenated timeline replaces dummy) */, - period1Seq0 /* SOURCE_UPDATE (child sources in concatenating source moved) */); + window0Period1Seq0 /* DYNAMIC (concatenated timeline replaces dummy) */, + period1Seq0 /* DYNAMIC (child sources in concatenating source moved) */); assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) .containsExactly( window0Period1Seq0, window0Period1Seq0, window0Period1Seq0, window0Period1Seq0); @@ -664,401 +656,6 @@ public final class AnalyticsCollectorTest { listener.assertNoMoreEvents(); } - @Test - public void testPlaylistOperations() throws Exception { - MediaSource fakeMediaSource = - new FakeMediaSource(SINGLE_PERIOD_TIMELINE, ExoPlayerTestRunner.Builder.VIDEO_FORMAT); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("AnalyticsCollectorTest") - .pause() - .waitForPlaybackState(Player.STATE_READY) - .addMediaSources(fakeMediaSource) - // Wait until second period has fully loaded to assert loading events without flakiness. - .waitForIsLoading(true) - .waitForIsLoading(false) - .removeMediaItem(/* index= */ 0) - .play() - .build(); - TestAnalyticsListener listener = runAnalyticsTest(fakeMediaSource, actionSchedule); - - // Populate event ids with second to last timeline that still contained both periods. - populateEventIds(listener.reportedTimelines.get(listener.reportedTimelines.size() - 2)); - // Expect the second period with window index 0 and increased window sequence after the removal - // moved the period to another window index. - period0Seq1 = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId( - listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0), - /* windowSequenceNumber= */ 1)); - assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) - .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, - WINDOW_0 /* setPlayWhenReady=false */, - WINDOW_0 /* BUFFERING */, - period0Seq0 /* READY */, - period0Seq1 /* BUFFERING */, - period0Seq1 /* setPlayWhenReady=true */, - period0Seq1 /* READY */, - period0Seq1 /* ENDED */); - assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly( - WINDOW_0 /* PLAYLIST_CHANGED */, - WINDOW_0 /* SOURCE_UPDATE (first item) */, - period0Seq0 /* PLAYLIST_CHANGED (add) */, - period0Seq0 /* SOURCE_UPDATE (second item) */, - period0Seq1 /* PLAYLIST_CHANGED (remove) */); - assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) - .containsExactly(period0Seq0, period0Seq0, period0Seq0, period0Seq0); - assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)).containsExactly(period0Seq0, period0Seq1); - assertThat(listener.getEvents(EVENT_LOAD_STARTED)) - .containsExactly(WINDOW_0 /* manifest */, period0Seq0 /* media */, period1Seq1 /* media */); - assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) - .containsExactly(WINDOW_0 /* manifest */, period0Seq0 /* media */, period1Seq1 /* media */); - assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly(period0Seq0, period0Seq1); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)) - .containsExactly(period0Seq0, period1Seq1); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0Seq0); - assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0Seq0, period0Seq1); - assertThat(listener.getEvents(EVENT_DECODER_ENABLED)).containsExactly(period0Seq0, period0Seq1); - assertThat(listener.getEvents(EVENT_DECODER_INIT)).containsExactly(period0Seq0, period0Seq1); - assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly(period0Seq0, period0Seq1); - assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0Seq0); - assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(period0Seq0, period0Seq1); - assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) - .containsExactly(period0Seq0, period0Seq1); - listener.assertNoMoreEvents(); - } - - @Test - public void testAdPlayback() throws Exception { - long contentDurationsUs = 10 * C.MICROS_PER_SECOND; - AtomicReference adPlaybackState = - new AtomicReference<>( - FakeTimeline.createAdPlaybackState( - /* adsPerAdGroup= */ 1, /* adGroupTimesUs...= */ - 0, - 5 * C.MICROS_PER_SECOND, - C.TIME_END_OF_SOURCE) - .withContentDurationUs(contentDurationsUs)); - AtomicInteger playedAdCount = new AtomicInteger(0); - Timeline adTimeline = - new FakeTimeline( - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 0, - /* isSeekable= */ true, - /* isDynamic= */ false, - contentDurationsUs, - adPlaybackState.get())); - FakeMediaSource fakeMediaSource = - new FakeMediaSource(adTimeline, ExoPlayerTestRunner.Builder.VIDEO_FORMAT); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("AnalyticsCollectorTest") - .executeRunnable( - new PlayerRunnable() { - @Override - public void run(SimpleExoPlayer player) { - player.addListener( - new Player.EventListener() { - @Override - public void onPositionDiscontinuity( - @Player.DiscontinuityReason int reason) { - if (!player.isPlayingAd() - && reason == Player.DISCONTINUITY_REASON_AD_INSERTION) { - // Finished playing ad. Marked as played. - adPlaybackState.set( - adPlaybackState - .get() - .withPlayedAd( - playedAdCount.getAndIncrement(), - /* adIndexInAdGroup= */ 0)); - fakeMediaSource.setNewSourceInfo( - new FakeTimeline( - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 0, - /* isSeekable= */ true, - /* isDynamic= */ false, - /* durationUs =*/ 10 * C.MICROS_PER_SECOND, - adPlaybackState.get())), - /* newManifest= */ null); - } - } - }); - } - }) - .pause() - .waitForPlaybackState(Player.STATE_READY) - .play() - .waitForPlaybackState(Player.STATE_ENDED) - .build(); - TestAnalyticsListener listener = runAnalyticsTest(fakeMediaSource, actionSchedule); - - Object periodUid = listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0); - EventWindowAndPeriodId prerollAd = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId( - periodUid, - /* adGroupIndex= */ 0, - /* adIndexInAdGroup= */ 0, - /* windowSequenceNumber= */ 0)); - EventWindowAndPeriodId midrollAd = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId( - periodUid, - /* adGroupIndex= */ 1, - /* adIndexInAdGroup= */ 0, - /* windowSequenceNumber= */ 0)); - EventWindowAndPeriodId postrollAd = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId( - periodUid, - /* adGroupIndex= */ 2, - /* adIndexInAdGroup= */ 0, - /* windowSequenceNumber= */ 0)); - EventWindowAndPeriodId contentAfterPreroll = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId(periodUid, /* windowSequenceNumber= */ 0, /* nextAdGroupIndex= */ 1)); - EventWindowAndPeriodId contentAfterMidroll = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId(periodUid, /* windowSequenceNumber= */ 0, /* nextAdGroupIndex= */ 2)); - EventWindowAndPeriodId contentAfterPostroll = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId( - periodUid, /* windowSequenceNumber= */ 0, /* nextAdGroupIndex= */ C.INDEX_UNSET)); - assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) - .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, - WINDOW_0 /* setPlayWhenReady=false */, - WINDOW_0 /* BUFFERING */, - prerollAd /* READY */, - prerollAd /* setPlayWhenReady=true */, - contentAfterPostroll /* ENDED */); - assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly( - WINDOW_0 /* PLAYLIST_CHANGED */, - WINDOW_0 /* SOURCE_UPDATE (initial) */, - contentAfterPreroll /* SOURCE_UPDATE (played preroll) */, - contentAfterMidroll /* SOURCE_UPDATE (played midroll) */, - contentAfterPostroll /* SOURCE_UPDATE (played postroll) */); - assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)) - .containsExactly( - contentAfterPreroll, midrollAd, contentAfterMidroll, postrollAd, contentAfterPostroll); - assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) - .containsExactly( - prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, prerollAd, - prerollAd, prerollAd, prerollAd, prerollAd); - assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) - .containsExactly( - prerollAd, - contentAfterPreroll, - midrollAd, - contentAfterMidroll, - postrollAd, - contentAfterPostroll); - assertThat(listener.getEvents(EVENT_LOAD_STARTED)) - .containsExactly( - WINDOW_0 /* content manifest */, - WINDOW_0 /* preroll manifest */, - prerollAd, - contentAfterPreroll, - WINDOW_0 /* midroll manifest */, - midrollAd, - contentAfterMidroll, - WINDOW_0 /* postroll manifest */, - postrollAd, - contentAfterPostroll); - assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) - .containsExactly( - WINDOW_0 /* content manifest */, - WINDOW_0 /* preroll manifest */, - prerollAd, - contentAfterPreroll, - WINDOW_0 /* midroll manifest */, - midrollAd, - contentAfterMidroll, - WINDOW_0 /* postroll manifest */, - postrollAd, - contentAfterPostroll); - assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly( - prerollAd, - contentAfterPreroll, - midrollAd, - contentAfterMidroll, - postrollAd, - contentAfterPostroll); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)) - .containsExactly( - prerollAd, - contentAfterPreroll, - midrollAd, - contentAfterMidroll, - postrollAd, - contentAfterPostroll); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)) - .containsExactly( - prerollAd, contentAfterPreroll, midrollAd, contentAfterMidroll, postrollAd); - assertThat(listener.getEvents(EVENT_READING_STARTED)) - .containsExactly( - prerollAd, - contentAfterPreroll, - midrollAd, - contentAfterMidroll, - postrollAd, - contentAfterPostroll); - assertThat(listener.getEvents(EVENT_DECODER_ENABLED)).containsExactly(prerollAd); - assertThat(listener.getEvents(EVENT_DECODER_INIT)) - .containsExactly( - prerollAd, - contentAfterPreroll, - midrollAd, - contentAfterMidroll, - postrollAd, - contentAfterPostroll); - assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly( - prerollAd, - contentAfterPreroll, - midrollAd, - contentAfterMidroll, - postrollAd, - contentAfterPostroll); - assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)) - .containsExactly(contentAfterPostroll); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(prerollAd); - assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(prerollAd); - listener.assertNoMoreEvents(); - } - - @Test - public void testSeekAfterMidroll() throws Exception { - Timeline adTimeline = - new FakeTimeline( - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 0, - /* isSeekable= */ true, - /* isDynamic= */ false, - 10 * C.MICROS_PER_SECOND, - FakeTimeline.createAdPlaybackState( - /* adsPerAdGroup= */ 1, /* adGroupTimesUs...= */ 5 * C.MICROS_PER_SECOND))); - FakeMediaSource fakeMediaSource = - new FakeMediaSource(adTimeline, ExoPlayerTestRunner.Builder.VIDEO_FORMAT); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("AnalyticsCollectorTest") - .pause() - // Ensure everything is preloaded. - .waitForIsLoading(true) - .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) - .waitForIsLoading(true) - .waitForIsLoading(false) - // Seek behind the midroll. - .seek(6 * C.MICROS_PER_SECOND) - .play() - .waitForPlaybackState(Player.STATE_ENDED) - .build(); - TestAnalyticsListener listener = runAnalyticsTest(fakeMediaSource, actionSchedule); - - Object periodUid = listener.lastReportedTimeline.getUidOfPeriod(/* periodIndex= */ 0); - EventWindowAndPeriodId midrollAd = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId( - periodUid, - /* adGroupIndex= */ 0, - /* adIndexInAdGroup= */ 0, - /* windowSequenceNumber= */ 0)); - EventWindowAndPeriodId contentBeforeMidroll = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId(periodUid, /* windowSequenceNumber= */ 0, /* nextAdGroupIndex= */ 0)); - EventWindowAndPeriodId contentAfterMidroll = - new EventWindowAndPeriodId( - /* windowIndex= */ 0, - new MediaPeriodId( - periodUid, /* windowSequenceNumber= */ 0, /* nextAdGroupIndex= */ C.INDEX_UNSET)); - assertThat(listener.getEvents(EVENT_PLAYER_STATE_CHANGED)) - .containsExactly( - WINDOW_0 /* setPlayWhenReady=true */, - WINDOW_0 /* setPlayWhenReady=false */, - WINDOW_0 /* BUFFERING */, - contentBeforeMidroll /* READY */, - contentAfterMidroll /* setPlayWhenReady=true */, - midrollAd /* BUFFERING */, - midrollAd /* READY */, - contentAfterMidroll /* ENDED */); - assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED)) - .containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, WINDOW_0 /* SOURCE_UPDATE */); - assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)) - .containsExactly( - contentAfterMidroll /* seek */, - midrollAd /* seek adjustment */, - contentAfterMidroll /* ad transition */); - assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(contentBeforeMidroll); - assertThat(listener.getEvents(EVENT_SEEK_PROCESSED)).containsExactly(midrollAd); - assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) - .containsExactly( - contentBeforeMidroll, - contentBeforeMidroll, - contentBeforeMidroll, - contentBeforeMidroll, - contentBeforeMidroll, - contentBeforeMidroll, - midrollAd, - midrollAd); - assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) - .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll); - assertThat(listener.getEvents(EVENT_LOAD_STARTED)) - .containsExactly( - WINDOW_0 /* content manifest */, - contentBeforeMidroll, - midrollAd, - contentAfterMidroll, - contentAfterMidroll); - assertThat(listener.getEvents(EVENT_LOAD_COMPLETED)) - .containsExactly( - WINDOW_0 /* content manifest */, - contentBeforeMidroll, - midrollAd, - contentAfterMidroll, - contentAfterMidroll); - assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED)) - .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED)) - .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll, contentAfterMidroll); - assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)) - .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll); - assertThat(listener.getEvents(EVENT_READING_STARTED)) - .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll); - assertThat(listener.getEvents(EVENT_DECODER_ENABLED)) - .containsExactly(contentBeforeMidroll, midrollAd); - assertThat(listener.getEvents(EVENT_DECODER_INIT)) - .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll); - assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED)) - .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll); - assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(contentBeforeMidroll); - assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(contentAfterMidroll); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(contentBeforeMidroll, midrollAd); - assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) - .containsExactly(contentBeforeMidroll, midrollAd); - listener.assertNoMoreEvents(); - } - @Test public void testNotifyExternalEvents() throws Exception { MediaSource mediaSource = new FakeMediaSource(SINGLE_PERIOD_TIMELINE); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java index 6b738ec075..8160dc3147 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.testutil; -import android.net.Uri; import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Timeline; @@ -161,19 +160,11 @@ public final class FakeTimeline extends Timeline { AdPlaybackState adPlaybackState = new AdPlaybackState(adGroupTimesUs); long[][] adDurationsUs = new long[adGroupCount][]; for (int i = 0; i < adGroupCount; i++) { - adPlaybackState = adPlaybackState.withAdCount(/* adGroupIndex= */ i, adsPerAdGroup); - for (int j = 0; j < adsPerAdGroup; j++) { - adPlaybackState = - adPlaybackState.withAdUri( - /* adGroupIndex= */ i, - /* adIndexInAdGroup= */ j, - Uri.parse("https://ad/" + i + "/" + j)); - } + adPlaybackState = adPlaybackState.withAdCount(i, adsPerAdGroup); adDurationsUs[i] = new long[adsPerAdGroup]; Arrays.fill(adDurationsUs[i], AD_DURATION_US); } adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs); - return adPlaybackState; }