From 714fa477d0b285ac4b6bb2dc1ab466bdfaaa7cd0 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 13 Feb 2020 11:28:28 +0000 Subject: [PATCH] Add isLastInTimelineWindow to MediaPeriodInfo. This information isn't easily available to the player at the moment (or it would need to revaluate this every time), so add it to MediaPeriodInfo similar to the existing isLastInTimelinePeriod. The player needs to know whether a MediaPeriod is the last in its Timeline window if we want to add an option to pause at the end of a window. PiperOrigin-RevId: 294877628 --- .../android/exoplayer2/MediaPeriodInfo.java | 8 ++ .../android/exoplayer2/MediaPeriodQueue.java | 14 ++ .../exoplayer2/MediaPeriodQueueTest.java | 127 +++++++++++++----- 3 files changed, 116 insertions(+), 33 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java index c686371ad8..b14af5e1de 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java @@ -53,6 +53,8 @@ import com.google.android.exoplayer2.util.Util; * period corresponding to a timeline period without ads). */ public final boolean isLastInTimelinePeriod; + /** Whether this is the last media period in its timeline window. */ + public final boolean isLastInTimelineWindow; /** * Whether this is the last media period in the entire timeline. If true, {@link * #isLastInTimelinePeriod} will also be true. @@ -66,6 +68,7 @@ import com.google.android.exoplayer2.util.Util; long endPositionUs, long durationUs, boolean isLastInTimelinePeriod, + boolean isLastInTimelineWindow, boolean isFinal) { this.id = id; this.startPositionUs = startPositionUs; @@ -73,6 +76,7 @@ import com.google.android.exoplayer2.util.Util; this.endPositionUs = endPositionUs; this.durationUs = durationUs; this.isLastInTimelinePeriod = isLastInTimelinePeriod; + this.isLastInTimelineWindow = isLastInTimelineWindow; this.isFinal = isFinal; } @@ -90,6 +94,7 @@ import com.google.android.exoplayer2.util.Util; endPositionUs, durationUs, isLastInTimelinePeriod, + isLastInTimelineWindow, isFinal); } @@ -107,6 +112,7 @@ import com.google.android.exoplayer2.util.Util; endPositionUs, durationUs, isLastInTimelinePeriod, + isLastInTimelineWindow, isFinal); } @@ -124,6 +130,7 @@ import com.google.android.exoplayer2.util.Util; && endPositionUs == that.endPositionUs && durationUs == that.durationUs && isLastInTimelinePeriod == that.isLastInTimelinePeriod + && isLastInTimelineWindow == that.isLastInTimelineWindow && isFinal == that.isFinal && Util.areEqual(id, that.id); } @@ -137,6 +144,7 @@ import com.google.android.exoplayer2.util.Util; result = 31 * result + (int) endPositionUs; result = 31 * result + (int) durationUs; result = 31 * result + (isLastInTimelinePeriod ? 1 : 0); + result = 31 * result + (isLastInTimelineWindow ? 1 : 0); result = 31 * result + (isFinal ? 1 : 0); return result; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index e97c3ee489..fe5863cbfd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -353,6 +353,7 @@ import com.google.android.exoplayer2.util.Assertions; public MediaPeriodInfo getUpdatedMediaPeriodInfo(Timeline timeline, MediaPeriodInfo info) { MediaPeriodId id = info.id; boolean isLastInPeriod = isLastInPeriod(id); + boolean isLastInWindow = isLastInWindow(timeline, id); boolean isLastInTimeline = isLastInTimeline(timeline, id, isLastInPeriod); timeline.getPeriodByUid(info.id.periodUid, period); long durationUs = @@ -368,6 +369,7 @@ import com.google.android.exoplayer2.util.Assertions; info.endPositionUs, durationUs, isLastInPeriod, + isLastInWindow, isLastInTimeline); } @@ -735,6 +737,7 @@ import com.google.android.exoplayer2.util.Assertions; /* endPositionUs= */ C.TIME_UNSET, durationUs, /* isLastInTimelinePeriod= */ false, + /* isLastInTimelineWindow= */ false, /* isFinal= */ false); } @@ -748,6 +751,7 @@ import com.google.android.exoplayer2.util.Assertions; int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, nextAdGroupIndex); boolean isLastInPeriod = isLastInPeriod(id); + boolean isLastInWindow = isLastInWindow(timeline, id); boolean isLastInTimeline = isLastInTimeline(timeline, id, isLastInPeriod); long endPositionUs = nextAdGroupIndex != C.INDEX_UNSET @@ -764,6 +768,7 @@ import com.google.android.exoplayer2.util.Assertions; endPositionUs, durationUs, isLastInPeriod, + isLastInWindow, isLastInTimeline); } @@ -771,6 +776,15 @@ import com.google.android.exoplayer2.util.Assertions; return !id.isAd() && id.nextAdGroupIndex == C.INDEX_UNSET; } + private boolean isLastInWindow(Timeline timeline, MediaPeriodId id) { + if (!isLastInPeriod(id)) { + return false; + } + int windowIndex = timeline.getPeriodByUid(id.periodUid, period).windowIndex; + int periodIndex = timeline.getIndexOfPeriod(id.periodUid); + return timeline.getWindow(windowIndex, window).lastPeriodIndex == periodIndex; + } + private boolean isLastInTimeline( Timeline timeline, MediaPeriodId id, boolean isLastMediaPeriodInPeriod) { int periodIndex = timeline.getIndexOfPeriod(id.periodUid); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index 6c97f7e1e4..d45bc801fa 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -27,6 +27,8 @@ import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.SinglePeriodAdTimeline; import com.google.android.exoplayer2.testutil.FakeMediaSource; +import com.google.android.exoplayer2.testutil.FakeTimeline; +import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; @@ -52,7 +54,7 @@ public final class MediaPeriodQueueTest { private MediaPeriodQueue mediaPeriodQueue; private AdPlaybackState adPlaybackState; - private Object periodUid; + private Object firstPeriodUid; private PlaybackInfo playbackInfo; private RendererCapabilities[] rendererCapabilities; @@ -73,40 +75,46 @@ public final class MediaPeriodQueueTest { @Test public void getNextMediaPeriodInfo_withoutAds_returnsLastMediaPeriodInfo() { - setupTimeline(); + setupAdTimeline(/* no ad groups */ ); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, /* startPositionUs= */ 0, /* requestedContentPositionUs= */ C.TIME_UNSET, /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ CONTENT_DURATION_US, - /* isLast= */ true, + /* isLastInPeriod= */ true, + /* isLastInWindow= */ true, /* nextAdGroupIndex= */ C.INDEX_UNSET); } @Test public void getNextMediaPeriodInfo_withPrerollAd_returnsCorrectMediaPeriodInfos() { - setupTimeline(/* adGroupTimesUs...= */ 0); + setupAdTimeline(/* adGroupTimesUs...= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 0); assertNextMediaPeriodInfoIsAd(/* adGroupIndex= */ 0, /* contentPositionUs= */ C.TIME_UNSET); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, /* startPositionUs= */ 0, /* requestedContentPositionUs= */ C.TIME_UNSET, /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ CONTENT_DURATION_US, - /* isLast= */ true, + /* isLastInPeriod= */ true, + /* isLastInWindow= */ true, /* nextAdGroupIndex= */ C.INDEX_UNSET); } @Test public void getNextMediaPeriodInfo_withMidrollAds_returnsCorrectMediaPeriodInfos() { - setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); + setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, /* startPositionUs= */ 0, /* requestedContentPositionUs= */ C.TIME_UNSET, /* endPositionUs= */ FIRST_AD_START_TIME_US, /* durationUs= */ FIRST_AD_START_TIME_US, - /* isLast= */ false, + /* isLastInPeriod= */ false, + /* isLastInWindow= */ false, /* nextAdGroupIndex= */ 0); // The next media period info should be null as we haven't loaded the ad yet. advance(); @@ -116,11 +124,13 @@ public final class MediaPeriodQueueTest { /* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, /* startPositionUs= */ FIRST_AD_START_TIME_US, /* requestedContentPositionUs= */ FIRST_AD_START_TIME_US, /* endPositionUs= */ SECOND_AD_START_TIME_US, /* durationUs= */ SECOND_AD_START_TIME_US, - /* isLast= */ false, + /* isLastInPeriod= */ false, + /* isLastInWindow= */ false, /* nextAdGroupIndex= */ 1); advance(); setAdGroupLoaded(/* adGroupIndex= */ 1); @@ -128,23 +138,27 @@ public final class MediaPeriodQueueTest { /* adGroupIndex= */ 1, /* contentPositionUs= */ SECOND_AD_START_TIME_US); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, /* startPositionUs= */ SECOND_AD_START_TIME_US, /* requestedContentPositionUs= */ SECOND_AD_START_TIME_US, /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ CONTENT_DURATION_US, - /* isLast= */ true, + /* isLastInPeriod= */ true, + /* isLastInWindow= */ true, /* nextAdGroupIndex= */ C.INDEX_UNSET); } @Test public void getNextMediaPeriodInfo_withMidrollAndPostroll_returnsCorrectMediaPeriodInfos() { - setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, C.TIME_END_OF_SOURCE); + setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, C.TIME_END_OF_SOURCE); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, /* startPositionUs= */ 0, /* requestedContentPositionUs= */ C.TIME_UNSET, /* endPositionUs= */ FIRST_AD_START_TIME_US, /* durationUs= */ FIRST_AD_START_TIME_US, - /* isLast= */ false, + /* isLastInPeriod= */ false, + /* isLastInWindow= */ false, /* nextAdGroupIndex= */ 0); advance(); setAdGroupLoaded(/* adGroupIndex= */ 0); @@ -152,11 +166,13 @@ public final class MediaPeriodQueueTest { /* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, /* startPositionUs= */ FIRST_AD_START_TIME_US, /* requestedContentPositionUs= */ FIRST_AD_START_TIME_US, /* endPositionUs= */ C.TIME_END_OF_SOURCE, /* durationUs= */ CONTENT_DURATION_US, - /* isLast= */ false, + /* isLastInPeriod= */ false, + /* isLastInWindow= */ false, /* nextAdGroupIndex= */ 1); advance(); setAdGroupLoaded(/* adGroupIndex= */ 1); @@ -164,39 +180,77 @@ public final class MediaPeriodQueueTest { /* adGroupIndex= */ 1, /* contentPositionUs= */ CONTENT_DURATION_US); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, /* startPositionUs= */ CONTENT_DURATION_US, /* requestedContentPositionUs= */ CONTENT_DURATION_US, /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ CONTENT_DURATION_US, - /* isLast= */ true, + /* isLastInPeriod= */ true, + /* isLastInWindow= */ true, /* nextAdGroupIndex= */ C.INDEX_UNSET); } @Test public void getNextMediaPeriodInfo_withPostrollLoadError_returnsEmptyFinalMediaPeriodInfo() { - setupTimeline(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE); + setupAdTimeline(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, /* startPositionUs= */ 0, /* requestedContentPositionUs= */ C.TIME_UNSET, /* endPositionUs= */ C.TIME_END_OF_SOURCE, /* durationUs= */ CONTENT_DURATION_US, - /* isLast= */ false, + /* isLastInPeriod= */ false, + /* isLastInWindow= */ false, /* nextAdGroupIndex= */ 0); advance(); setAdGroupFailedToLoad(/* adGroupIndex= */ 0); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ firstPeriodUid, /* startPositionUs= */ CONTENT_DURATION_US, /* requestedContentPositionUs= */ CONTENT_DURATION_US, /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ CONTENT_DURATION_US, - /* isLast= */ true, + /* isLastInPeriod= */ true, + /* isLastInWindow= */ true, + /* nextAdGroupIndex= */ C.INDEX_UNSET); + } + + @Test + public void getNextMediaPeriodInfo_inMultiPeriodWindow_returnsCorrectMediaPeriodInfos() { + setupTimeline( + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 2, + /* id= */ new Object(), + /* isSeekable= */ false, + /* isDynamic= */ false, + /* durationUs= */ 2 * CONTENT_DURATION_US))); + + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 0), + /* startPositionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLastInPeriod= */ true, + /* isLastInWindow= */ false, + /* nextAdGroupIndex= */ C.INDEX_UNSET); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* periodUid= */ playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 1), + /* startPositionUs= */ 0, + /* requestedContentPositionUs= */ 0, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLastInPeriod= */ true, + /* isLastInWindow= */ true, /* nextAdGroupIndex= */ C.INDEX_UNSET); } @Test public void updateQueuedPeriods_withDurationChangeAfterReadingPeriod_handlesChangeAndRemovesPeriodsAfterChangedPeriod() { - setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); + setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); enqueueNext(); // Content before first ad. @@ -221,7 +275,7 @@ public final class MediaPeriodQueueTest { @Test public void updateQueuedPeriods_withDurationChangeBeforeReadingPeriod_doesntHandleChangeAndRemovesPeriodsAfterChangedPeriod() { - setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); + setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); enqueueNext(); // Content before first ad. @@ -249,7 +303,7 @@ public final class MediaPeriodQueueTest { @Test public void updateQueuedPeriods_withDurationChangeInReadingPeriodAfterReadingPosition_handlesChangeAndRemovesPeriodsAfterChangedPeriod() { - setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); + setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); enqueueNext(); // Content before first ad. @@ -279,7 +333,7 @@ public final class MediaPeriodQueueTest { @Test public void updateQueuedPeriods_withDurationChangeInReadingPeriodBeforeReadingPosition_doesntHandleChangeAndRemovesPeriodsAfterChangedPeriod() { - setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); + setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); enqueueNext(); // Content before first ad. @@ -309,7 +363,7 @@ public final class MediaPeriodQueueTest { @Test public void updateQueuedPeriods_withDurationChangeInReadingPeriodReadToEnd_doesntHandleChangeAndRemovesPeriodsAfterChangedPeriod() { - setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); + setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 1); enqueueNext(); // Content before first ad. @@ -335,24 +389,27 @@ public final class MediaPeriodQueueTest { assertThat(getQueueLength()).isEqualTo(3); } - private void setupTimeline(long... adGroupTimesUs) { + private void setupAdTimeline(long... adGroupTimesUs) { adPlaybackState = new AdPlaybackState(adGroupTimesUs).withContentDurationUs(CONTENT_DURATION_US); - - // Create a media source holder. SinglePeriodAdTimeline adTimeline = new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState); - fakeMediaSource = new FakeMediaSource(adTimeline); + setupTimeline(adTimeline); + } + + private void setupTimeline(Timeline timeline) { + fakeMediaSource = new FakeMediaSource(timeline); mediaSourceHolder = new Playlist.MediaSourceHolder(fakeMediaSource, false); mediaSourceHolder.mediaSource.prepareSourceInternal(/* mediaTransferListener */ null); - Timeline timeline = createPlaylistTimeline(); - periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0); + Timeline playlistTimeline = createPlaylistTimeline(); + firstPeriodUid = playlistTimeline.getUidOfPeriod(/* periodIndex= */ 0); playbackInfo = new PlaybackInfo( - timeline, - mediaPeriodQueue.resolveMediaPeriodIdForAds(timeline, periodUid, /* positionUs= */ 0), + playlistTimeline, + mediaPeriodQueue.resolveMediaPeriodIdForAds( + playlistTimeline, firstPeriodUid, /* positionUs= */ 0), /* requestedContentPositionUs= */ C.TIME_UNSET, Player.STATE_READY, /* playbackError= */ null, @@ -437,11 +494,13 @@ public final class MediaPeriodQueueTest { } private void assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + Object periodUid, long startPositionUs, long requestedContentPositionUs, long endPositionUs, long durationUs, - boolean isLast, + boolean isLastInPeriod, + boolean isLastInWindow, int nextAdGroupIndex) { assertThat(getNextMediaPeriodInfo()) .isEqualTo( @@ -451,8 +510,9 @@ public final class MediaPeriodQueueTest { requestedContentPositionUs, endPositionUs, durationUs, - /* isLastInTimelinePeriod= */ isLast, - /* isFinal= */ isLast)); + isLastInPeriod, + isLastInWindow, + /* isFinal= */ isLastInWindow)); } private void assertNextMediaPeriodInfoIsAd(int adGroupIndex, long contentPositionUs) { @@ -460,7 +520,7 @@ public final class MediaPeriodQueueTest { .isEqualTo( new MediaPeriodInfo( new MediaPeriodId( - periodUid, + firstPeriodUid, adGroupIndex, /* adIndexInAdGroup= */ 0, /* windowSequenceNumber= */ 0), @@ -469,6 +529,7 @@ public final class MediaPeriodQueueTest { /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ AD_DURATION_US, /* isLastInTimelinePeriod= */ false, + /* isLastInTimelineWindow= */ false, /* isFinal= */ false)); }