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)); }