Make period durations of FakeMultiPeriodLiveTimeline configurable

PiperOrigin-RevId: 522046876
This commit is contained in:
bachinger 2023-04-05 15:27:31 +01:00 committed by Marc Baechinger
parent 5442c33ac7
commit e4194fc862
5 changed files with 433 additions and 172 deletions

View File

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

View File

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

View File

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

View File

@ -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;
*
* <p>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<PeriodData> 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.
*
* <p>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<PeriodData> 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;

View File

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