diff --git a/libraries/common/src/test/java/androidx/media3/common/TimelineTest.java b/libraries/common/src/test/java/androidx/media3/common/TimelineTest.java index bc7686d0ff..9f6da671a9 100644 --- a/libraries/common/src/test/java/androidx/media3/common/TimelineTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/TimelineTest.java @@ -15,6 +15,8 @@ */ package androidx.media3.common; +import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_MS; +import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.PERIOD_DURATION_MS; import static com.google.common.truth.Truth.assertThat; import android.os.Bundle; @@ -436,10 +438,13 @@ public class TimelineTest { public void periodIsLivePostrollPlaceholder_recognizesLivePostrollPlaceholder() { FakeMultiPeriodLiveTimeline timeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 60_000_000, /* nowUs= */ 60_000_000, /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ false, /* populateAds= */ true, /* playedAds= */ false); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/MediaPeriodQueueTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/MediaPeriodQueueTest.java index d6c014a3d2..ae35ac47b6 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/MediaPeriodQueueTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/MediaPeriodQueueTest.java @@ -15,8 +15,11 @@ */ package androidx.media3.exoplayer; +import static androidx.media3.common.util.Util.msToUs; import static androidx.media3.test.utils.ExoPlayerTestRunner.AUDIO_FORMAT; import static androidx.media3.test.utils.ExoPlayerTestRunner.VIDEO_FORMAT; +import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_MS; +import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.PERIOD_DURATION_MS; import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US; import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US; import static com.google.common.truth.Truth.assertThat; @@ -430,15 +433,18 @@ public final class MediaPeriodQueueTest { @Test @SuppressWarnings("unchecked") public void getNextMediaPeriodInfo_multiPeriodTimelineWithNoAdsAndNoPostrollPlaceholder() { - long contentPeriodDurationUs = FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US; - long adPeriodDurationUs = FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_US; + long contentPeriodDurationUs = msToUs(PERIOD_DURATION_MS); + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); // Multi period timeline without ad playback state. FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 60_000_000, /* nowUs= */ 110_000_000, - new boolean[] {false, true, true}, + /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -497,15 +503,18 @@ public final class MediaPeriodQueueTest { @Test @SuppressWarnings("unchecked") public void getNextMediaPeriodInfo_multiPeriodTimelineWithPostrollPlaceHolder() { - long contentPeriodDurationUs = FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US; - long adPeriodDurationUs = FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_US; + long contentPeriodDurationUs = msToUs(PERIOD_DURATION_MS); + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); // Multi period timeline without ad playback state. FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 60_000_000, /* nowUs= */ 110_000_000, - new boolean[] {false, true, true}, + /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ false, /* populateAds= */ false, /* playedAds= */ false); @@ -564,14 +573,17 @@ public final class MediaPeriodQueueTest { @Test @SuppressWarnings("unchecked") public void getNextMediaPeriodInfo_multiPeriodTimelineWithAdsAndWithPostRollPlaceHolder() { - long contentPeriodDurationUs = FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US; - long adPeriodDurationUs = FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_US; + long contentPeriodDurationUs = msToUs(PERIOD_DURATION_MS); + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 60_000_000, /* nowUs= */ 110_000_000, - new boolean[] {false, true, true}, + /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ false, /* populateAds= */ true, /* playedAds= */ false); @@ -646,13 +658,16 @@ public final class MediaPeriodQueueTest { @Test @SuppressWarnings("unchecked") public void getNextMediaPeriodInfo_multiPeriodTimelineWithPlayedAdsAndWithPostRollPlaceHolder() { - long contentPeriodDurationUs = FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US; + long contentPeriodDurationUs = msToUs(PERIOD_DURATION_MS); FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 60_000_000, /* nowUs= */ 110_000_000, - new boolean[] {false, true, true}, + /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ false, /* populateAds= */ true, /* playedAds= */ true); diff --git a/libraries/exoplayer_ima/src/test/java/androidx/media3/exoplayer/ima/ImaUtilTest.java b/libraries/exoplayer_ima/src/test/java/androidx/media3/exoplayer/ima/ImaUtilTest.java index 40bb2d4210..43a3cc532e 100644 --- a/libraries/exoplayer_ima/src/test/java/androidx/media3/exoplayer/ima/ImaUtilTest.java +++ b/libraries/exoplayer_ima/src/test/java/androidx/media3/exoplayer/ima/ImaUtilTest.java @@ -20,6 +20,7 @@ import static androidx.media3.common.AdPlaybackState.AD_STATE_ERROR; import static androidx.media3.common.AdPlaybackState.AD_STATE_PLAYED; import static androidx.media3.common.AdPlaybackState.AD_STATE_SKIPPED; import static androidx.media3.common.AdPlaybackState.AD_STATE_UNAVAILABLE; +import static androidx.media3.common.util.Util.msToUs; import static androidx.media3.exoplayer.ima.ImaUtil.addLiveAdBreak; import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInLiveMultiPeriodTimeline; import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInVodMultiPeriodTimeline; @@ -28,7 +29,8 @@ import static androidx.media3.exoplayer.ima.ImaUtil.maybeCorrectPreviouslyUnknow import static androidx.media3.exoplayer.ima.ImaUtil.secToUsRounded; import static androidx.media3.exoplayer.ima.ImaUtil.splitAdGroup; import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState; -import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_US; +import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_MS; +import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.PERIOD_DURATION_MS; import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US; import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US; import static com.google.common.truth.Truth.assertThat; @@ -560,7 +562,7 @@ public class ImaUtilTest { @Test public void splitAdPlaybackStateForPeriods_liveAdGroupStartedAndMovedOutOfWindow_splitCorrectly() { - long adPeriodDurationUs = AD_PERIOD_DURATION_US; + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ "adsId", C.TIME_END_OF_SOURCE) .withIsServerSideInserted(/* adGroupIndex= */ 0, true); @@ -568,10 +570,13 @@ public class ImaUtilTest { // Period durations: content=30_000_000, ad=10_000_000 FakeMultiPeriodLiveTimeline liveTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 100_000_000, /* nowUs= */ 150_000_000, /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -851,6 +856,7 @@ public class ImaUtilTest { @Test public void splitAdPlaybackStateForPeriods_fullAdGroupAtBeginOfWindow_adPeriodsCorrectlyDetected() { + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ "adsId", C.TIME_END_OF_SOURCE) .withIsServerSideInserted(/* adGroupIndex= */ 0, true); @@ -858,10 +864,13 @@ public class ImaUtilTest { // Period durations: content=30_000_000, ad=10_000_000 FakeMultiPeriodLiveTimeline liveTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 30_000_000, /* nowUs= */ 59_999_999, /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -869,9 +878,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 30_000_000, - AD_PERIOD_DURATION_US, + adPeriodDurationUs, /* adPositionInAdPod= */ 1, - /* totalAdDurationUs= */ 2 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 2 * adPeriodDurationUs, /* totalAdsInAdPod= */ 2, adPlaybackState); @@ -968,9 +977,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 80_000_000, - AD_PERIOD_DURATION_US - 1000L, // SDK fallback duration. + adPeriodDurationUs - 1000L, // SDK fallback duration. /* adPositionInAdPod= */ 1, - /* totalAdDurationUs= */ 2 * AD_PERIOD_DURATION_US - 1001L, + /* totalAdDurationUs= */ 2 * adPeriodDurationUs - 1001L, /* totalAdsInAdPod= */ 2, adPlaybackState); splitAdPlaybackStates = ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, liveTimeline); @@ -984,7 +993,7 @@ public class ImaUtilTest { AdPlaybackState.AdGroup actualAdGroup = splitAdPlaybackStates.get("uid-4[a]").getAdGroup(/* adGroupIndex= */ 0); assertThat(actualAdGroup.count).isEqualTo(1); - assertThat(actualAdGroup.durationsUs[0]).isEqualTo(AD_PERIOD_DURATION_US - 1000L); + assertThat(actualAdGroup.durationsUs[0]).isEqualTo(adPeriodDurationUs - 1000L); // Advance to make the window overlap 1 microsecond into the second ad period. Assert whether // both ad periods, including the last with unknown duration, are correctly marked as ad. @@ -999,11 +1008,11 @@ public class ImaUtilTest { assertThat(splitAdPlaybackStates.get("uid-4[a]").adGroupCount).isEqualTo(2); actualAdGroup = splitAdPlaybackStates.get("uid-4[a]").getAdGroup(/* adGroupIndex= */ 0); assertThat(actualAdGroup.count).isEqualTo(1); - assertThat(actualAdGroup.durationsUs[0]).isEqualTo(AD_PERIOD_DURATION_US); + assertThat(actualAdGroup.durationsUs[0]).isEqualTo(adPeriodDurationUs); assertThat(splitAdPlaybackStates.get("uid-5[a]").adGroupCount).isEqualTo(2); actualAdGroup = splitAdPlaybackStates.get("uid-5[a]").getAdGroup(/* adGroupIndex= */ 0); assertThat(actualAdGroup.count).isEqualTo(1); - assertThat(actualAdGroup.durationsUs[0]).isEqualTo(AD_PERIOD_DURATION_US - 1L); // SDK fallback. + assertThat(actualAdGroup.durationsUs[0]).isEqualTo(adPeriodDurationUs - 1L); // SDK fallback. } @Test @@ -1033,6 +1042,7 @@ public class ImaUtilTest { @Test public void maybeCorrectPreviouslyUnknownAdDuration_singleAdInAdGroup_adDurationCorrected() { + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); long liveWindowDurationUs = 60_000_000L; long nowUs = 110_234_567L; AdPlaybackState adPlaybackState = @@ -1053,10 +1063,13 @@ public class ImaUtilTest { /* adDurationsUs...= */ 123); FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, liveWindowDurationUs, nowUs, /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -1070,13 +1083,14 @@ public class ImaUtilTest { assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 1).timeUs).isEqualTo(90_000_000L); assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 1).durationsUs) .asList() - .containsExactly(AD_PERIOD_DURATION_US); + .containsExactly(adPeriodDurationUs); assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 1).contentResumeOffsetUs) - .isEqualTo(AD_PERIOD_DURATION_US); + .isEqualTo(adPeriodDurationUs); } @Test public void maybeCorrectPreviouslyUnknownAdDuration_multipleAdsInAdGroup_adDurationCorrected() { + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); long liveWindowDurationUs = 60_000_000L; long nowUs = 110_234_567L; AdPlaybackState adPlaybackState = @@ -1086,15 +1100,18 @@ public class ImaUtilTest { adPlaybackState, /* fromPositionUs= */ 80_000_000L, /* contentResumeOffsetUs= */ 10_000_123L, - /* adDurationsUs...= */ AD_PERIOD_DURATION_US, + /* adDurationsUs...= */ adPeriodDurationUs, 123L); // Content timeline: [content, ad, ad, content] FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, liveWindowDurationUs, nowUs, /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -1104,9 +1121,9 @@ public class ImaUtilTest { assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).timeUs).isEqualTo(80_000_000L); assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs) .asList() - .containsExactly(AD_PERIOD_DURATION_US, AD_PERIOD_DURATION_US); + .containsExactly(adPeriodDurationUs, adPeriodDurationUs); assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).contentResumeOffsetUs) - .isEqualTo(2 * AD_PERIOD_DURATION_US); + .isEqualTo(2 * adPeriodDurationUs); } @Test @@ -1128,10 +1145,13 @@ public class ImaUtilTest { /* adDurationsUs...= */ 123); FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 60_000_000L, /* nowUs= */ 160_000_000L, /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -1150,10 +1170,17 @@ public class ImaUtilTest { // Content and ad period in window at the beginning: [c, a, a] FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 50_000_000L, /* nowUs= */ 50_000_000L, /* adSequencePattern= */ new boolean[] {false, true, true, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -1271,14 +1298,16 @@ public class ImaUtilTest { } @Test - public void - maybeCorrectPreviouslyUnknownAdDuration_singleContentPeriodTimeline_adPlaybackStateNotChanged() { + public void maybeCorrectPreviouslyUnknownAdDuration_singleContentPeriodTimeline_doNothing() { FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 30_000_000L, /* nowUs= */ 80_000_000L, /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -1308,10 +1337,13 @@ public class ImaUtilTest { maybeCorrectPreviouslyUnknownAdDuration_singleAdPeriodTimeline_doesNotOverrideWithTimeUnset() { FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 10_000_000L, /* nowUs= */ 90_000_000L, /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -1608,13 +1640,20 @@ public class ImaUtilTest { @Test public void getAdGroupAndIndexInLiveMultiPeriodTimeline_calledForPeriodsAfterUnplayedAdGroup_correctAdGroupIndexAndAdIndexInAdGroup() { + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); // Content live window with content and ad periods: c, [a, c, a, a, a, c, a], a, a FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 100_000_000, /* nowUs= */ 159_000_123, /* adSequencePattern= */ new boolean[] {false, true, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -1623,9 +1662,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 50_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 1, - /* totalAdDurationUs= */ AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ adPeriodDurationUs, /* totalAdsInAdPod= */ 1, adPlaybackState); @@ -1642,25 +1681,25 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 90_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 1, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 100_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 2, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 110_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 3, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); @@ -1699,9 +1738,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 150_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 2, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); @@ -1717,13 +1756,20 @@ public class ImaUtilTest { @Test public void getAdGroupAndIndexInLiveMultiPeriodTimeline_calledForPeriodsBeforeUnplayedAdGroup_throwsWhenCalledForNonAdPeriods() { + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); // Content live window with content and ad periods: c, [a, c, a, a, a, c, a], a, a FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 100_000_000, /* nowUs= */ 159_000_123, /* adSequencePattern= */ new boolean[] {false, true, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -1732,9 +1778,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 50_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 1, - /* totalAdDurationUs= */ AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ adPeriodDurationUs, /* totalAdsInAdPod= */ 1, adPlaybackState); adPlaybackState = @@ -1742,25 +1788,25 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 90_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 1, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 100_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 2, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 110_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 3, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); AdPlaybackState finalAdPlaybackState = adPlaybackState; @@ -1783,9 +1829,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 150_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 2, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); AdPlaybackState anotherFinalAdPlaybackState = adPlaybackState; @@ -1803,13 +1849,20 @@ public class ImaUtilTest { @Test public void getAdGroupAndIndexInLiveMultiPeriodTimeline_partialAdGroupAtTimelineStart_correctAdGroupIndexAndAdIndexInAdGroup() { + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); // Content timeline with content and ad periods: c, a, a, [a, c, a, a, a, c] FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 100_000_000, /* nowUs= */ 151_000_123, /* adSequencePattern= */ new boolean[] {false, true, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -1818,25 +1871,25 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 30_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 1, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 40_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 2, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 50_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 3, - /* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 3 * adPeriodDurationUs, /* totalAdsInAdPod= */ 3, adPlaybackState); adPlaybackState = @@ -1856,14 +1909,24 @@ public class ImaUtilTest { @Test public void getAdGroupAndIndexInLiveMultiPeriodTimeline_onlyPartialAdGroupInWindow_correctAdGroupIndexAndAdIndexInAdGroup() { + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); // Content timeline with content and ad periods: c, a, [a, a, a, a], a, c // First three ad periods of the ad group already outside of the live window. FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 30_000_000, /* nowUs= */ 71_000_123, /* adSequencePattern= */ new boolean[] {false, true, true, true, true, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -1873,9 +1936,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 30_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 1, - /* totalAdDurationUs= */ 6 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 6 * adPeriodDurationUs, /* totalAdsInAdPod= */ 6, adPlaybackState); adPlaybackState = @@ -1883,9 +1946,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 40_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 2, - /* totalAdDurationUs= */ 6 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 6 * adPeriodDurationUs, /* totalAdsInAdPod= */ 6, adPlaybackState); @@ -1903,9 +1966,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 50_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 3, - /* totalAdDurationUs= */ 6 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 6 * adPeriodDurationUs, /* totalAdsInAdPod= */ 6, adPlaybackState); @@ -1941,13 +2004,23 @@ public class ImaUtilTest { @Test public void handleAdPeriodRemovedFromTimeline_removalCorrectlyHandled() { + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); // Content timeline with content and ad periods: a,[c, a, a, a, a, a, a, c], a FakeMultiPeriodLiveTimeline contentTimeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 70_000_123, /* nowUs= */ 189_453_123, /* adSequencePattern= */ new boolean[] {false, true, true, true, true, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ false, /* populateAds= */ true, /* playedAds= */ false); @@ -1957,9 +2030,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 120_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 1, - /* totalAdDurationUs= */ 6 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 6 * adPeriodDurationUs, /* totalAdsInAdPod= */ 6, adPlaybackState); adPlaybackState = @@ -1967,9 +2040,9 @@ public class ImaUtilTest { adPlaybackState = addLiveAdBreak( /* currentContentPeriodPositionUs= */ 130_000_000, - /* adDurationUs= */ AD_PERIOD_DURATION_US, + /* adDurationUs= */ adPeriodDurationUs, /* adPositionInAdPod= */ 2, - /* totalAdDurationUs= */ 6 * AD_PERIOD_DURATION_US, + /* totalAdDurationUs= */ 6 * adPeriodDurationUs, /* totalAdsInAdPod= */ 6, adPlaybackState); @@ -2099,13 +2172,17 @@ public class ImaUtilTest { @Test public void getAdGroupDurationUsForLiveAdPeriodIndex_allAdsInTimeline_correctAdGroupDuration() { int adPodTotalAdCount = 2; + long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS); // Content and ad periods in timeline: [c, a, a, c, a, a]. FakeMultiPeriodLiveTimeline timeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 75_007_123, /* nowUs= */ 99_321_457, /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ false, /* populateAds= */ true, /* playedAds= */ false); @@ -2121,11 +2198,11 @@ public class ImaUtilTest { assertThat( ImaUtil.getAdGroupDurationUsForLiveAdPeriodIndex( timeline, firstAdPodInfo, /* adPeriodIndex= */ 1, new Window(), new Period())) - .isEqualTo(2 * AD_PERIOD_DURATION_US); + .isEqualTo(2 * adPeriodDurationUs); assertThat( ImaUtil.getAdGroupDurationUsForLiveAdPeriodIndex( timeline, secondAdPodInfo, /* adPeriodIndex= */ 2, new Window(), new Period())) - .isEqualTo(2 * AD_PERIOD_DURATION_US); + .isEqualTo(2 * adPeriodDurationUs); // The second ad group has the last ad with an unknown duration. assertThat( ImaUtil.getAdGroupDurationUsForLiveAdPeriodIndex( @@ -2144,10 +2221,13 @@ public class ImaUtilTest { // Content and ad periods in timeline: [a, a, c]. First two ads not in window. FakeMultiPeriodLiveTimeline timeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0, + /* availabilityStartTimeMs= */ 0, /* liveWindowDurationUs= */ 49_321_753, /* nowUs= */ 85_007_123, /* adSequencePattern= */ new boolean[] {false, true, true}, + /* periodDurationMsPattern= */ new long[] { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ false, /* populateAds= */ true, /* playedAds= */ false); diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeMultiPeriodLiveTimeline.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeMultiPeriodLiveTimeline.java index fc63e5d15d..1bfba42203 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeMultiPeriodLiveTimeline.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeMultiPeriodLiveTimeline.java @@ -17,6 +17,7 @@ package androidx.media3.test.utils; import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Util.msToUs; +import static androidx.media3.common.util.Util.sum; import static androidx.media3.common.util.Util.usToMs; import androidx.media3.common.AdPlaybackState; @@ -42,14 +43,14 @@ import java.util.Arrays; * *

Periods are either of type content or ad as defined by the ad sequence pattern. A period is an * ad if {@code adSequencePattern[id % adSequencePattern.length]} evaluates to true. Ad periods have - * a duration of {@link #AD_PERIOD_DURATION_US} and content periods have a duration of {@link - * #PERIOD_DURATION_US}. + * a duration of {@link #AD_PERIOD_DURATION_MS} and content periods have a duration of {@link + * #PERIOD_DURATION_MS}. */ @UnstableApi public class FakeMultiPeriodLiveTimeline extends Timeline { - public static final long AD_PERIOD_DURATION_US = 10_000_000L; - public static final long PERIOD_DURATION_US = 30_000_000L; + public static final long AD_PERIOD_DURATION_MS = 10_000L; + public static final long PERIOD_DURATION_MS = 30_000L; private final boolean[] adSequencePattern; private final MediaItem mediaItem; @@ -58,6 +59,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline { private final boolean isContentTimeline; private final boolean populateAds; private final boolean playedAds; + private final long[] periodDurationUsPattern; private long nowUs; private ImmutableList periods; @@ -65,53 +67,55 @@ public class FakeMultiPeriodLiveTimeline extends Timeline { /** * Creates an instance. * - * @param availabilityStartTimeUs The start time of the available time range, in UNIX epoch. + * @param availabilityStartTimeMs The start time of the available time range, UNIX epoch in + * milliseconds. * @param liveWindowDurationUs The duration of the live window. * @param nowUs The current time that determines the end of the live window. * @param adSequencePattern The repeating pattern of periods starting at {@code - * availabilityStartTimeUs}. True is an ad period, and false a content period. + * availabilityStartTimeMs}. True is an ad period, and false a content period. + * @param periodDurationMsPattern The repeating pattern of periods durations starting at {@code + * availabilityStartTimeMs}, in milliseconds. Must have the same length as {@code + * adSequencePattern}. * @param isContentTimeline Whether the timeline is a content timeline without {@link * AdPlaybackState}s. * @param populateAds Whether to populate ads in the same way if an ad event has been received. * @param playedAds Whether ads should be marked as played if populated. */ public FakeMultiPeriodLiveTimeline( - long availabilityStartTimeUs, + long availabilityStartTimeMs, long liveWindowDurationUs, long nowUs, boolean[] adSequencePattern, + long[] periodDurationMsPattern, boolean isContentTimeline, boolean populateAds, boolean playedAds) { - checkArgument(nowUs - liveWindowDurationUs >= availabilityStartTimeUs); - this.availabilityStartTimeUs = availabilityStartTimeUs; + checkArgument(nowUs - liveWindowDurationUs >= msToUs(availabilityStartTimeMs)); + checkArgument(adSequencePattern.length == periodDurationMsPattern.length); + this.availabilityStartTimeUs = msToUs(availabilityStartTimeMs); this.liveWindowDurationUs = liveWindowDurationUs; this.nowUs = nowUs; this.adSequencePattern = Arrays.copyOf(adSequencePattern, adSequencePattern.length); + periodDurationUsPattern = new long[periodDurationMsPattern.length]; + for (int i = 0; i < periodDurationMsPattern.length; i++) { + periodDurationUsPattern[i] = msToUs(periodDurationMsPattern[i]); + } this.isContentTimeline = isContentTimeline; this.populateAds = populateAds; this.playedAds = playedAds; mediaItem = new MediaItem.Builder().build(); periods = invalidate( - availabilityStartTimeUs, + msToUs(availabilityStartTimeMs), liveWindowDurationUs, nowUs, adSequencePattern, + periodDurationUsPattern, isContentTimeline, populateAds, playedAds); } - /** Calculates the total duration of the given ad period sequence. */ - public static long calculateAdSequencePatternDurationUs(boolean[] adSequencePattern) { - long durationUs = 0; - for (boolean isAd : adSequencePattern) { - durationUs += (isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US); - } - return durationUs; - } - /** Advances the live window by the given duration, in microseconds. */ public void advanceNowUs(long durationUs) { nowUs += durationUs; @@ -121,11 +125,35 @@ public class FakeMultiPeriodLiveTimeline extends Timeline { liveWindowDurationUs, nowUs, adSequencePattern, + periodDurationUsPattern, isContentTimeline, populateAds, playedAds); } + /** + * The window's start time in microseconds since the Unix epoch, or {@link C#TIME_UNSET} if + * unknown or not applicable. + */ + public long getWindowStartTimeUs() { + Window window = getWindow(/* windowIndex= */ 0, new Window()); + // Revert us/ms truncation introduced in `getWindow()`. This is identical to the truncation + // applied in the Media3 `DashMediaSource.DashTimeline` and can be reverted in the same way. + return window.windowStartTimeMs != C.TIME_UNSET + ? msToUs(window.windowStartTimeMs) + (window.positionInFirstPeriodUs % 1000) + : C.TIME_UNSET; + } + + /** + * Returns the period start time since Unix epoch, in microseconds. + * + *

Note: The returned value has millisecond precision only, so the trailing 3 digits are always + * zeros. + */ + public long getPeriodStartTimeUs(int periodIndex) { + return msToUs(periods.get(periodIndex).periodStartTimeMs); + } + @Override public int getWindowCount() { return 1; @@ -136,12 +164,13 @@ public class FakeMultiPeriodLiveTimeline extends Timeline { checkArgument(windowIndex == 0); MediaItem.LiveConfiguration liveConfiguration = new MediaItem.LiveConfiguration.Builder().build(); + long positionInFirstPeriodUs = -periods.get(0).positionInWindowUs; window.set( /* uid= */ "live-window", mediaItem, /* manifest= */ null, /* presentationStartTimeMs= */ C.TIME_UNSET, - /* windowStartTimeMs= */ usToMs(nowUs - liveWindowDurationUs), + /* windowStartTimeMs= */ periods.get(0).periodStartTimeMs + usToMs(positionInFirstPeriodUs), /* elapsedRealtimeEpochOffsetMs= */ 0, /* isSeekable= */ true, /* isDynamic= */ true, @@ -150,7 +179,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline { /* durationUs= */ liveWindowDurationUs, /* firstPeriodIndex= */ 0, /* lastPeriodIndex= */ getPeriodCount() - 1, - /* positionInFirstPeriodUs= */ -periods.get(0).positionInWindowUs); + positionInFirstPeriodUs); return window; } @@ -193,24 +222,23 @@ public class FakeMultiPeriodLiveTimeline extends Timeline { long liveWindowDurationUs, long now, boolean[] adSequencePattern, + long[] periodDurationUsPattern, boolean isContentTimeline, boolean populateAds, boolean playedAds) { long windowStartTimeUs = now - liveWindowDurationUs; int sequencePeriodCount = adSequencePattern.length; - long sequenceDurationUs = calculateAdSequencePatternDurationUs(adSequencePattern); + long sequenceDurationUs = sum(periodDurationUsPattern); long skippedSequenceCount = (windowStartTimeUs - availabilityStartTimeUs) / sequenceDurationUs; // Search the first period of the live window. int firstPeriodIndex = (int) (skippedSequenceCount * sequencePeriodCount); - boolean isAd = adSequencePattern[firstPeriodIndex % sequencePeriodCount]; - long firstPeriodDurationUs = isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US; + long firstPeriodDurationUs = periodDurationUsPattern[firstPeriodIndex % sequencePeriodCount]; long firstPeriodEndTimeUs = availabilityStartTimeUs + (sequenceDurationUs * skippedSequenceCount) + firstPeriodDurationUs; while (firstPeriodEndTimeUs <= windowStartTimeUs) { - isAd = adSequencePattern[++firstPeriodIndex % sequencePeriodCount]; - firstPeriodDurationUs = isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US; + firstPeriodDurationUs = periodDurationUsPattern[++firstPeriodIndex % sequencePeriodCount]; firstPeriodEndTimeUs += firstPeriodDurationUs; } ImmutableList.Builder liveWindow = new ImmutableList.Builder<>(); @@ -218,8 +246,8 @@ public class FakeMultiPeriodLiveTimeline extends Timeline { int lastPeriodIndex = firstPeriodIndex; // Add periods to the window from the first period until we find a period start after `now`. while (lastPeriodStartTimeUs < now) { - isAd = adSequencePattern[lastPeriodIndex % sequencePeriodCount]; - long periodDurationUs = isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US; + long periodDurationUs = periodDurationUsPattern[lastPeriodIndex % sequencePeriodCount]; + boolean isAd = adSequencePattern[lastPeriodIndex % sequencePeriodCount]; AdPlaybackState adPlaybackState = AdPlaybackState.NONE; if (!isContentTimeline) { adPlaybackState = new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended(); @@ -244,6 +272,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline { /* id= */ lastPeriodIndex++, periodDurationUs, /* positionInWindowUs= */ lastPeriodStartTimeUs - windowStartTimeUs, + /* periodStartTimeMs= */ usToMs(lastPeriodStartTimeUs), isAd, adPlaybackState)); lastPeriodStartTimeUs += periodDurationUs; @@ -257,6 +286,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline { private final Object uid; private final long durationUs; private final long positionInWindowUs; + private final long periodStartTimeMs; private final AdPlaybackState adPlaybackState; /** Creates an instance. */ @@ -264,9 +294,11 @@ public class FakeMultiPeriodLiveTimeline extends Timeline { int id, long durationUs, long positionInWindowUs, + long periodStartTimeMs, boolean isAd, AdPlaybackState adPlaybackState) { this.id = id; + this.periodStartTimeMs = periodStartTimeMs; this.uid = "uid-" + id + "[" + (isAd ? "a" : "c") + "]"; this.durationUs = durationUs; this.positionInWindowUs = positionInWindowUs; diff --git a/libraries/test_utils/src/test/java/androidx/media3/test/utils/FakeMultiPeriodLiveTimelineTest.java b/libraries/test_utils/src/test/java/androidx/media3/test/utils/FakeMultiPeriodLiveTimelineTest.java index 05cc89787a..df17a2fad2 100644 --- a/libraries/test_utils/src/test/java/androidx/media3/test/utils/FakeMultiPeriodLiveTimelineTest.java +++ b/libraries/test_utils/src/test/java/androidx/media3/test/utils/FakeMultiPeriodLiveTimelineTest.java @@ -15,14 +15,15 @@ */ package androidx.media3.test.utils; -import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_US; -import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US; -import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.calculateAdSequencePatternDurationUs; +import static androidx.media3.common.util.Util.msToUs; +import static androidx.media3.common.util.Util.sum; +import static androidx.media3.common.util.Util.usToMs; +import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_MS; +import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.PERIOD_DURATION_MS; import static com.google.common.truth.Truth.assertThat; import androidx.media3.common.C; import androidx.media3.common.Timeline; -import androidx.media3.common.util.Util; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,12 +37,16 @@ public class FakeMultiPeriodLiveTimelineTest { @Test public void newInstance_availabilitySinceStartOfUnixEpoch_correctLiveWindow() { boolean[] adSequencePattern = {false, true, true}; + long[] periodDurationMsPattern = { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }; FakeMultiPeriodLiveTimeline timeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 60_000_000L, /* nowUs= */ 60_000_000L, adSequencePattern, + periodDurationMsPattern, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -63,22 +68,28 @@ public class FakeMultiPeriodLiveTimelineTest { assertExpectedWindow( timeline, calculateExpectedWindow( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 60_000_000L, /* nowUs= */ 60_000_000L, - adSequencePattern), - adSequencePattern); + adSequencePattern, + periodDurationMsPattern), + adSequencePattern, + periodDurationMsPattern); } @Test public void newInstance_timelineWithAdsPopulated_correctPlaybackStates() { boolean[] adSequencePattern = {false, true, true}; + long[] periodDurationMsPattern = { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }; FakeMultiPeriodLiveTimeline timeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 50_000_000L, /* nowUs= */ 100_000_000L, adSequencePattern, + periodDurationMsPattern, /* isContentTimeline= */ false, /* populateAds= */ true, /* playedAds= */ false); @@ -123,22 +134,28 @@ public class FakeMultiPeriodLiveTimelineTest { assertExpectedWindow( timeline, calculateExpectedWindow( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 50_000_000L, /* nowUs= */ 100_000_000L, - adSequencePattern), - adSequencePattern); + adSequencePattern, + periodDurationMsPattern), + adSequencePattern, + periodDurationMsPattern); } @Test public void newInstance_timelineWithAdsNotPopulated_correctPlaybackStates() { boolean[] adSequencePattern = {false, true, true}; + long[] periodDurationMsPattern = { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }; FakeMultiPeriodLiveTimeline timeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 50_000_000L, /* nowUs= */ 100_000_000L, adSequencePattern, + periodDurationMsPattern, /* isContentTimeline= */ false, /* populateAds= */ false, /* playedAds= */ false); @@ -175,22 +192,28 @@ public class FakeMultiPeriodLiveTimelineTest { assertExpectedWindow( timeline, calculateExpectedWindow( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 50_000_000L, /* nowUs= */ 100_000_000L, - adSequencePattern), - adSequencePattern); + adSequencePattern, + periodDurationMsPattern), + adSequencePattern, + periodDurationMsPattern); } @Test public void advanceTimeUs_availabilitySinceStartOfUnixEpoch_correctPeriodsInLiveWindow() { boolean[] adSequencePattern = {false, true, true}; + long[] periodDurationMsPattern = { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }; FakeMultiPeriodLiveTimeline timeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 60_000_000L, /* nowUs= */ 60_000_123L, adSequencePattern, + periodDurationMsPattern, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -209,11 +232,13 @@ public class FakeMultiPeriodLiveTimelineTest { assertExpectedWindow( timeline, calculateExpectedWindow( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 60_000_000L, /* nowUs= */ 60_000_123L, - adSequencePattern), - adSequencePattern); + adSequencePattern, + periodDurationMsPattern), + adSequencePattern, + periodDurationMsPattern); // Advance nowUs so that the window ends just 1us before the next period moves into the window. timeline.advanceNowUs(19999877L); @@ -231,11 +256,13 @@ public class FakeMultiPeriodLiveTimelineTest { assertExpectedWindow( timeline, calculateExpectedWindow( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 60_000_000L, /* nowUs= */ 60_000_123L + 19999877L, - adSequencePattern), - adSequencePattern); + adSequencePattern, + periodDurationMsPattern), + adSequencePattern, + periodDurationMsPattern); // Advance the window by 1us to add the next period at the end of the window. timeline.advanceNowUs(1L); @@ -254,110 +281,159 @@ public class FakeMultiPeriodLiveTimelineTest { assertExpectedWindow( timeline, calculateExpectedWindow( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 60_000_000L, /* nowUs= */ 60_000_123L + 19999878L, - adSequencePattern), - adSequencePattern); + adSequencePattern, + periodDurationMsPattern), + adSequencePattern, + periodDurationMsPattern); } @Test public void newInstance_advancedAvailabilityStartTime_correctlyInterpolatedPeriodIds() { Timeline.Period period = new Timeline.Period(); - long availabilityStartTimeUs = 0; + long availabilityStartTimeMs = 0; long nowUs = 120_000_123; long liveWindowDurationUs = 60_000_987L; boolean[] adSequencePattern = {false, true, true}; - long sequenceDurationUs = calculateAdSequencePatternDurationUs(adSequencePattern); + long[] periodDurationMsPattern = { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }; + long sequenceDurationUs = 50_000_000L; FakeMultiPeriodLiveTimeline timeline = new FakeMultiPeriodLiveTimeline( - availabilityStartTimeUs, + availabilityStartTimeMs, liveWindowDurationUs, nowUs, adSequencePattern, + periodDurationMsPattern, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ true); - assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs) - .isEqualTo(Util.usToMs(nowUs - liveWindowDurationUs)); assertThat(timeline.getPeriodCount()).isEqualTo(4); + long windowStartTimeUs = timeline.getWindowStartTimeUs(); + assertThat( + windowStartTimeUs + timeline.getPeriod(/* periodIndex= */ 0, period).positionInWindowUs) + .isEqualTo(timeline.getPeriodStartTimeUs(/* periodIndex= */ 0)); assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).id).isEqualTo(3); assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).uid).isEqualTo("uid-3[c]"); + assertThat( + windowStartTimeUs + timeline.getPeriod(/* periodIndex= */ 1, period).positionInWindowUs) + .isEqualTo(timeline.getPeriodStartTimeUs(/* periodIndex= */ 1)); assertThat(timeline.getPeriod(/* periodIndex= */ 1, period).id).isEqualTo(4); assertThat(timeline.getPeriod(/* periodIndex= */ 1, period).uid).isEqualTo("uid-4[a]"); + assertThat( + windowStartTimeUs + timeline.getPeriod(/* periodIndex= */ 2, period).positionInWindowUs) + .isEqualTo(timeline.getPeriodStartTimeUs(/* periodIndex= */ 2)); assertThat(timeline.getPeriod(/* periodIndex= */ 2, period).id).isEqualTo(5); assertThat(timeline.getPeriod(/* periodIndex= */ 2, period).uid).isEqualTo("uid-5[a]"); + assertThat( + windowStartTimeUs + timeline.getPeriod(/* periodIndex= */ 3, period).positionInWindowUs) + .isEqualTo(timeline.getPeriodStartTimeUs(/* periodIndex= */ 3)); assertThat(timeline.getPeriod(/* periodIndex= */ 3, period).id).isEqualTo(6); assertThat(timeline.getPeriod(/* periodIndex= */ 3, period).uid).isEqualTo("uid-6[c]"); assertExpectedWindow( timeline, calculateExpectedWindow( - availabilityStartTimeUs, liveWindowDurationUs, nowUs, adSequencePattern), - adSequencePattern); + availabilityStartTimeMs, + liveWindowDurationUs, + nowUs, + adSequencePattern, + periodDurationMsPattern), + adSequencePattern, + periodDurationMsPattern); timeline.advanceNowUs(sequenceDurationUs * 13); - - assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs) - .isEqualTo(Util.usToMs((nowUs + sequenceDurationUs * 13) - liveWindowDurationUs)); + windowStartTimeUs = timeline.getWindowStartTimeUs(); + assertThat( + windowStartTimeUs + timeline.getPeriod(/* periodIndex= */ 0, period).positionInWindowUs) + .isEqualTo(timeline.getPeriodStartTimeUs(/* periodIndex= */ 0)); assertThat(timeline.getPeriodCount()).isEqualTo(4); assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).id).isEqualTo((13 * 3) + 3); assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).uid) .isEqualTo("uid-" + ((13 * 3) + 3) + "[c]"); + assertThat( + windowStartTimeUs + timeline.getPeriod(/* periodIndex= */ 1, period).positionInWindowUs) + .isEqualTo(timeline.getPeriodStartTimeUs(/* periodIndex= */ 1)); assertThat(timeline.getPeriod(/* periodIndex= */ 1, period).id).isEqualTo((13 * 3) + 4); assertThat(timeline.getPeriod(/* periodIndex= */ 1, period).uid) .isEqualTo("uid-" + ((13 * 3) + 4) + "[a]"); + assertThat( + windowStartTimeUs + timeline.getPeriod(/* periodIndex= */ 2, period).positionInWindowUs) + .isEqualTo(timeline.getPeriodStartTimeUs(/* periodIndex= */ 2)); assertThat(timeline.getPeriod(/* periodIndex= */ 2, period).id).isEqualTo((13 * 3) + 5); assertThat(timeline.getPeriod(/* periodIndex= */ 2, period).uid) .isEqualTo("uid-" + ((13 * 3) + 5) + "[a]"); + assertThat( + windowStartTimeUs + timeline.getPeriod(/* periodIndex= */ 3, period).positionInWindowUs) + .isEqualTo(timeline.getPeriodStartTimeUs(/* periodIndex= */ 3)); assertThat(timeline.getPeriod(/* periodIndex= */ 3, period).id).isEqualTo((13 * 3) + 6); assertThat(timeline.getPeriod(/* periodIndex= */ 3, period).uid) .isEqualTo("uid-" + ((13 * 3) + 6) + "[c]"); assertExpectedWindow( timeline, calculateExpectedWindow( - availabilityStartTimeUs, + availabilityStartTimeMs, liveWindowDurationUs, (nowUs + sequenceDurationUs * 13), - adSequencePattern), - adSequencePattern); + adSequencePattern, + periodDurationMsPattern), + adSequencePattern, + periodDurationMsPattern); } @Test public void newInstance_availabilitySinceAWeekAfterStartOfUnixEpoch_correctLiveWindow() { - long availabilityStartTimeUs = 7 * A_DAY_US; + long availabilityStartTimeMs = usToMs(7 * A_DAY_US); long nowUs = 18 * A_DAY_US + 135_000_000; long liveWindowDurationUs = 60_000_000L; boolean[] adSequencePattern = {false, true, true}; + long[] periodDurationMsPattern = { + PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS + }; FakeMultiPeriodLiveTimeline timeline = new FakeMultiPeriodLiveTimeline( - availabilityStartTimeUs, + availabilityStartTimeMs, liveWindowDurationUs, nowUs, adSequencePattern, + periodDurationMsPattern, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs) - .isEqualTo(Util.usToMs(nowUs - liveWindowDurationUs)); + .isEqualTo(usToMs(nowUs - liveWindowDurationUs)); assertExpectedWindow( timeline, calculateExpectedWindow( - availabilityStartTimeUs, liveWindowDurationUs, nowUs, adSequencePattern), - adSequencePattern); + availabilityStartTimeMs, + liveWindowDurationUs, + nowUs, + adSequencePattern, + periodDurationMsPattern), + adSequencePattern, + periodDurationMsPattern); } @Test public void newInstance_adSequencePattern_correctPeriodTypesFromStartOfAvailability() { FakeMultiPeriodLiveTimeline timeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 120_000_000L, /* nowUs= */ 120_000_000L, new boolean[] {false, true, true, true}, + new long[] { + PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -392,10 +468,17 @@ public class FakeMultiPeriodLiveTimelineTest { timeline = new FakeMultiPeriodLiveTimeline( - /* availabilityStartTimeUs= */ 0L, + /* availabilityStartTimeMs= */ 0L, /* liveWindowDurationUs= */ 220_000_000L, /* nowUs= */ 250_000_000L, new boolean[] {false, true, false, true, false}, + new long[] { + PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + PERIOD_DURATION_MS, + AD_PERIOD_DURATION_MS, + PERIOD_DURATION_MS + }, /* isContentTimeline= */ true, /* populateAds= */ false, /* playedAds= */ false); @@ -415,22 +498,62 @@ public class FakeMultiPeriodLiveTimelineTest { assertThat(timeline.getPeriod(9, period).uid).isEqualTo("uid-10[c]"); } + @Test + public void advanceNowUs_calculatePeriodStartTimeUsFromWindowStartMs_correctPeriodStartTimeUs() { + long[] periodDurationMsPattern = {1, 7, 5, 3}; + boolean[] adSequencePattern = {false, true, true, true}; + long liveWindowDurationUs = 15_243L; + long nowUs = 29_000_123L; + long availabilityStartTimeMs = 1L; + FakeMultiPeriodLiveTimeline timeline = + new FakeMultiPeriodLiveTimeline( + availabilityStartTimeMs, + liveWindowDurationUs, + nowUs, + adSequencePattern, + periodDurationMsPattern, + /* isContentTimeline= */ true, + /* populateAds= */ false, + /* playedAds= */ false); + Timeline.Window window = new Timeline.Window(); + Timeline.Period period = new Timeline.Period(); + + for (long i = 0; i < 50_000L; i++) { + timeline.getWindow(/* windowIndex= */ 0, window); + // Assert the DashMediaSource specific truncation can be reverted to calculate the period + // start time (See `FakeMultiPeriodLiveTimeline.getWindowStartImeUs()` also). + long windowStartTimeUs = + msToUs(window.windowStartTimeMs) + (window.positionInFirstPeriodUs % 1000); + for (int j = window.firstPeriodIndex; j <= window.lastPeriodIndex; j++) { + timeline.getPeriod(/* periodIndex= */ j, period); + assertThat(windowStartTimeUs + period.positionInWindowUs) + .isEqualTo(timeline.getPeriodStartTimeUs(/* periodIndex= */ j)); + } + timeline.advanceNowUs(1); + } + } + private ExpectedWindow calculateExpectedWindow( - long availabilityStartTimeUs, + long availabilityStartTimeMs, long liveWindowDurationUs, long nowUs, - boolean[] adSequencePattern) { + boolean[] adSequencePattern, + long[] periodDurationMsPattern) { + long[] periodDurationUsPattern = new long[periodDurationMsPattern.length]; + for (int i = 0; i < periodDurationMsPattern.length; i++) { + periodDurationUsPattern[i] = msToUs(periodDurationMsPattern[i]); + } long windowStartTimeUs = nowUs - liveWindowDurationUs; - long sequenceDurationUs = calculateAdSequencePatternDurationUs(adSequencePattern); - long durationBeforeWindowStartUs = windowStartTimeUs - availabilityStartTimeUs; + long sequenceDurationUs = sum(periodDurationUsPattern); + long durationBeforeWindowStartUs = windowStartTimeUs - msToUs(availabilityStartTimeMs); long skippedSequenceCount = durationBeforeWindowStartUs / sequenceDurationUs; long remainingDurationBeforeWindowUs = durationBeforeWindowStartUs % sequenceDurationUs; int idOfFirstPeriodInWindow = (int) (skippedSequenceCount * adSequencePattern.length); long lastSkippedPeriodDurationUs = 0L; // Skip period by period until we reach the window start. while (remainingDurationBeforeWindowUs > 0) { - boolean isAd = adSequencePattern[idOfFirstPeriodInWindow++ % adSequencePattern.length]; - lastSkippedPeriodDurationUs = isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US; + lastSkippedPeriodDurationUs = + periodDurationUsPattern[idOfFirstPeriodInWindow++ % adSequencePattern.length]; remainingDurationBeforeWindowUs -= lastSkippedPeriodDurationUs; } long positionOfFirstPeriodInWindowUs = 0; @@ -442,24 +565,29 @@ public class FakeMultiPeriodLiveTimelineTest { -(lastSkippedPeriodDurationUs + remainingDurationBeforeWindowUs); } long durationOfFirstPeriodInWindowUs = - adSequencePattern[idOfFirstPeriodInWindow % adSequencePattern.length] - ? AD_PERIOD_DURATION_US - : PERIOD_DURATION_US; + periodDurationUsPattern[idOfFirstPeriodInWindow % adSequencePattern.length]; long durationInWindowUs = remainingDurationBeforeWindowUs == 0 ? durationOfFirstPeriodInWindowUs : -remainingDurationBeforeWindowUs; int idOfLastPeriodInWindow = idOfFirstPeriodInWindow; while (durationInWindowUs < liveWindowDurationUs) { - boolean isAd = adSequencePattern[++idOfLastPeriodInWindow % adSequencePattern.length]; - durationInWindowUs += isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US; + durationInWindowUs += + periodDurationUsPattern[++idOfLastPeriodInWindow % adSequencePattern.length]; } return new ExpectedWindow( idOfFirstPeriodInWindow, idOfLastPeriodInWindow, positionOfFirstPeriodInWindowUs); } private void assertExpectedWindow( - Timeline timeline, ExpectedWindow expectedWindow, boolean[] adSequencePattern) { + Timeline timeline, + ExpectedWindow expectedWindow, + boolean[] adSequencePattern, + long[] periodDurationMsPattern) { + long[] periodDurationUsPattern = new long[periodDurationMsPattern.length]; + for (int i = 0; i < periodDurationMsPattern.length; i++) { + periodDurationUsPattern[i] = msToUs(periodDurationMsPattern[i]); + } Timeline.Period period = new Timeline.Period(); assertThat(timeline.getPeriodCount()) .isEqualTo(expectedWindow.idOfLastPeriod - expectedWindow.idOfFirstPeriod + 1); @@ -467,11 +595,12 @@ public class FakeMultiPeriodLiveTimelineTest { for (int i = 0; i < timeline.getPeriodCount(); i++) { int id = expectedWindow.idOfFirstPeriod + i; boolean isAd = adSequencePattern[id % adSequencePattern.length]; + long durationUs = periodDurationUsPattern[id % periodDurationUsPattern.length]; assertThat(timeline.getPeriod(i, period).id).isEqualTo(id); assertThat(timeline.getPeriod(i, period).uid) .isEqualTo("uid-" + id + "[" + (isAd ? "a" : "c") + "]"); assertThat(timeline.getPeriod(i, period).positionInWindowUs).isEqualTo(positionInWindowUs); - positionInWindowUs += isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US; + positionInWindowUs += durationUs; } }