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; 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 static com.google.common.truth.Truth.assertThat;
import android.os.Bundle; import android.os.Bundle;
@ -436,10 +438,13 @@ public class TimelineTest {
public void periodIsLivePostrollPlaceholder_recognizesLivePostrollPlaceholder() { public void periodIsLivePostrollPlaceholder_recognizesLivePostrollPlaceholder() {
FakeMultiPeriodLiveTimeline timeline = FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 60_000_000, /* liveWindowDurationUs= */ 60_000_000,
/* nowUs= */ 60_000_000, /* nowUs= */ 60_000_000,
/* adSequencePattern= */ 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, /* isContentTimeline= */ false,
/* populateAds= */ true, /* populateAds= */ true,
/* playedAds= */ false); /* playedAds= */ false);

View File

@ -15,8 +15,11 @@
*/ */
package androidx.media3.exoplayer; 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.AUDIO_FORMAT;
import static androidx.media3.test.utils.ExoPlayerTestRunner.VIDEO_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_DURATION_US;
import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US; import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@ -430,15 +433,18 @@ public final class MediaPeriodQueueTest {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void getNextMediaPeriodInfo_multiPeriodTimelineWithNoAdsAndNoPostrollPlaceholder() { public void getNextMediaPeriodInfo_multiPeriodTimelineWithNoAdsAndNoPostrollPlaceholder() {
long contentPeriodDurationUs = FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US; long contentPeriodDurationUs = msToUs(PERIOD_DURATION_MS);
long adPeriodDurationUs = FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_US; long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS);
// Multi period timeline without ad playback state. // Multi period timeline without ad playback state.
FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline = FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 60_000_000, /* liveWindowDurationUs= */ 60_000_000,
/* nowUs= */ 110_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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -497,15 +503,18 @@ public final class MediaPeriodQueueTest {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void getNextMediaPeriodInfo_multiPeriodTimelineWithPostrollPlaceHolder() { public void getNextMediaPeriodInfo_multiPeriodTimelineWithPostrollPlaceHolder() {
long contentPeriodDurationUs = FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US; long contentPeriodDurationUs = msToUs(PERIOD_DURATION_MS);
long adPeriodDurationUs = FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_US; long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS);
// Multi period timeline without ad playback state. // Multi period timeline without ad playback state.
FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline = FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 60_000_000, /* liveWindowDurationUs= */ 60_000_000,
/* nowUs= */ 110_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, /* isContentTimeline= */ false,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -564,14 +573,17 @@ public final class MediaPeriodQueueTest {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void getNextMediaPeriodInfo_multiPeriodTimelineWithAdsAndWithPostRollPlaceHolder() { public void getNextMediaPeriodInfo_multiPeriodTimelineWithAdsAndWithPostRollPlaceHolder() {
long contentPeriodDurationUs = FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US; long contentPeriodDurationUs = msToUs(PERIOD_DURATION_MS);
long adPeriodDurationUs = FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_US; long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS);
FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline = FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 60_000_000, /* liveWindowDurationUs= */ 60_000_000,
/* nowUs= */ 110_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, /* isContentTimeline= */ false,
/* populateAds= */ true, /* populateAds= */ true,
/* playedAds= */ false); /* playedAds= */ false);
@ -646,13 +658,16 @@ public final class MediaPeriodQueueTest {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void getNextMediaPeriodInfo_multiPeriodTimelineWithPlayedAdsAndWithPostRollPlaceHolder() { public void getNextMediaPeriodInfo_multiPeriodTimelineWithPlayedAdsAndWithPostRollPlaceHolder() {
long contentPeriodDurationUs = FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US; long contentPeriodDurationUs = msToUs(PERIOD_DURATION_MS);
FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline = FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 60_000_000, /* liveWindowDurationUs= */ 60_000_000,
/* nowUs= */ 110_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, /* isContentTimeline= */ false,
/* populateAds= */ true, /* populateAds= */ true,
/* playedAds= */ 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_PLAYED;
import static androidx.media3.common.AdPlaybackState.AD_STATE_SKIPPED; import static androidx.media3.common.AdPlaybackState.AD_STATE_SKIPPED;
import static androidx.media3.common.AdPlaybackState.AD_STATE_UNAVAILABLE; 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.addLiveAdBreak;
import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInLiveMultiPeriodTimeline; import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInLiveMultiPeriodTimeline;
import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInVodMultiPeriodTimeline; 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.secToUsRounded;
import static androidx.media3.exoplayer.ima.ImaUtil.splitAdGroup; import static androidx.media3.exoplayer.ima.ImaUtil.splitAdGroup;
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState; 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_DURATION_US;
import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US; import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@ -560,7 +562,7 @@ public class ImaUtilTest {
@Test @Test
public void public void
splitAdPlaybackStateForPeriods_liveAdGroupStartedAndMovedOutOfWindow_splitCorrectly() { splitAdPlaybackStateForPeriods_liveAdGroupStartedAndMovedOutOfWindow_splitCorrectly() {
long adPeriodDurationUs = AD_PERIOD_DURATION_US; long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS);
AdPlaybackState adPlaybackState = AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId", C.TIME_END_OF_SOURCE) new AdPlaybackState(/* adsId= */ "adsId", C.TIME_END_OF_SOURCE)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true); .withIsServerSideInserted(/* adGroupIndex= */ 0, true);
@ -568,10 +570,13 @@ public class ImaUtilTest {
// Period durations: content=30_000_000, ad=10_000_000 // Period durations: content=30_000_000, ad=10_000_000
FakeMultiPeriodLiveTimeline liveTimeline = FakeMultiPeriodLiveTimeline liveTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 100_000_000, /* liveWindowDurationUs= */ 100_000_000,
/* nowUs= */ 150_000_000, /* nowUs= */ 150_000_000,
/* adSequencePattern= */ 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -851,6 +856,7 @@ public class ImaUtilTest {
@Test @Test
public void public void
splitAdPlaybackStateForPeriods_fullAdGroupAtBeginOfWindow_adPeriodsCorrectlyDetected() { splitAdPlaybackStateForPeriods_fullAdGroupAtBeginOfWindow_adPeriodsCorrectlyDetected() {
long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS);
AdPlaybackState adPlaybackState = AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId", C.TIME_END_OF_SOURCE) new AdPlaybackState(/* adsId= */ "adsId", C.TIME_END_OF_SOURCE)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true); .withIsServerSideInserted(/* adGroupIndex= */ 0, true);
@ -858,10 +864,13 @@ public class ImaUtilTest {
// Period durations: content=30_000_000, ad=10_000_000 // Period durations: content=30_000_000, ad=10_000_000
FakeMultiPeriodLiveTimeline liveTimeline = FakeMultiPeriodLiveTimeline liveTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 30_000_000, /* liveWindowDurationUs= */ 30_000_000,
/* nowUs= */ 59_999_999, /* nowUs= */ 59_999_999,
/* adSequencePattern= */ 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -869,9 +878,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 30_000_000, /* currentContentPeriodPositionUs= */ 30_000_000,
AD_PERIOD_DURATION_US, adPeriodDurationUs,
/* adPositionInAdPod= */ 1, /* adPositionInAdPod= */ 1,
/* totalAdDurationUs= */ 2 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 2 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 2, /* totalAdsInAdPod= */ 2,
adPlaybackState); adPlaybackState);
@ -968,9 +977,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 80_000_000, /* currentContentPeriodPositionUs= */ 80_000_000,
AD_PERIOD_DURATION_US - 1000L, // SDK fallback duration. adPeriodDurationUs - 1000L, // SDK fallback duration.
/* adPositionInAdPod= */ 1, /* adPositionInAdPod= */ 1,
/* totalAdDurationUs= */ 2 * AD_PERIOD_DURATION_US - 1001L, /* totalAdDurationUs= */ 2 * adPeriodDurationUs - 1001L,
/* totalAdsInAdPod= */ 2, /* totalAdsInAdPod= */ 2,
adPlaybackState); adPlaybackState);
splitAdPlaybackStates = ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, liveTimeline); splitAdPlaybackStates = ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, liveTimeline);
@ -984,7 +993,7 @@ public class ImaUtilTest {
AdPlaybackState.AdGroup actualAdGroup = AdPlaybackState.AdGroup actualAdGroup =
splitAdPlaybackStates.get("uid-4[a]").getAdGroup(/* adGroupIndex= */ 0); splitAdPlaybackStates.get("uid-4[a]").getAdGroup(/* adGroupIndex= */ 0);
assertThat(actualAdGroup.count).isEqualTo(1); 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 // 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. // 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); assertThat(splitAdPlaybackStates.get("uid-4[a]").adGroupCount).isEqualTo(2);
actualAdGroup = splitAdPlaybackStates.get("uid-4[a]").getAdGroup(/* adGroupIndex= */ 0); actualAdGroup = splitAdPlaybackStates.get("uid-4[a]").getAdGroup(/* adGroupIndex= */ 0);
assertThat(actualAdGroup.count).isEqualTo(1); 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); assertThat(splitAdPlaybackStates.get("uid-5[a]").adGroupCount).isEqualTo(2);
actualAdGroup = splitAdPlaybackStates.get("uid-5[a]").getAdGroup(/* adGroupIndex= */ 0); actualAdGroup = splitAdPlaybackStates.get("uid-5[a]").getAdGroup(/* adGroupIndex= */ 0);
assertThat(actualAdGroup.count).isEqualTo(1); 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 @Test
@ -1033,6 +1042,7 @@ public class ImaUtilTest {
@Test @Test
public void maybeCorrectPreviouslyUnknownAdDuration_singleAdInAdGroup_adDurationCorrected() { public void maybeCorrectPreviouslyUnknownAdDuration_singleAdInAdGroup_adDurationCorrected() {
long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS);
long liveWindowDurationUs = 60_000_000L; long liveWindowDurationUs = 60_000_000L;
long nowUs = 110_234_567L; long nowUs = 110_234_567L;
AdPlaybackState adPlaybackState = AdPlaybackState adPlaybackState =
@ -1053,10 +1063,13 @@ public class ImaUtilTest {
/* adDurationsUs...= */ 123); /* adDurationsUs...= */ 123);
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
liveWindowDurationUs, liveWindowDurationUs,
nowUs, nowUs,
/* adSequencePattern= */ 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ 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).timeUs).isEqualTo(90_000_000L);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 1).durationsUs) assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 1).durationsUs)
.asList() .asList()
.containsExactly(AD_PERIOD_DURATION_US); .containsExactly(adPeriodDurationUs);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 1).contentResumeOffsetUs) assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 1).contentResumeOffsetUs)
.isEqualTo(AD_PERIOD_DURATION_US); .isEqualTo(adPeriodDurationUs);
} }
@Test @Test
public void maybeCorrectPreviouslyUnknownAdDuration_multipleAdsInAdGroup_adDurationCorrected() { public void maybeCorrectPreviouslyUnknownAdDuration_multipleAdsInAdGroup_adDurationCorrected() {
long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS);
long liveWindowDurationUs = 60_000_000L; long liveWindowDurationUs = 60_000_000L;
long nowUs = 110_234_567L; long nowUs = 110_234_567L;
AdPlaybackState adPlaybackState = AdPlaybackState adPlaybackState =
@ -1086,15 +1100,18 @@ public class ImaUtilTest {
adPlaybackState, adPlaybackState,
/* fromPositionUs= */ 80_000_000L, /* fromPositionUs= */ 80_000_000L,
/* contentResumeOffsetUs= */ 10_000_123L, /* contentResumeOffsetUs= */ 10_000_123L,
/* adDurationsUs...= */ AD_PERIOD_DURATION_US, /* adDurationsUs...= */ adPeriodDurationUs,
123L); 123L);
// Content timeline: [content, ad, ad, content] // Content timeline: [content, ad, ad, content]
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
liveWindowDurationUs, liveWindowDurationUs,
nowUs, nowUs,
/* adSequencePattern= */ 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ 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).timeUs).isEqualTo(80_000_000L);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs) assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
.asList() .asList()
.containsExactly(AD_PERIOD_DURATION_US, AD_PERIOD_DURATION_US); .containsExactly(adPeriodDurationUs, adPeriodDurationUs);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).contentResumeOffsetUs) assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).contentResumeOffsetUs)
.isEqualTo(2 * AD_PERIOD_DURATION_US); .isEqualTo(2 * adPeriodDurationUs);
} }
@Test @Test
@ -1128,10 +1145,13 @@ public class ImaUtilTest {
/* adDurationsUs...= */ 123); /* adDurationsUs...= */ 123);
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 60_000_000L, /* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 160_000_000L, /* nowUs= */ 160_000_000L,
/* adSequencePattern= */ 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -1150,10 +1170,17 @@ public class ImaUtilTest {
// Content and ad period in window at the beginning: [c, a, a] // Content and ad period in window at the beginning: [c, a, a]
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 50_000_000L, /* liveWindowDurationUs= */ 50_000_000L,
/* nowUs= */ 50_000_000L, /* nowUs= */ 50_000_000L,
/* adSequencePattern= */ new boolean[] {false, true, true, true, true}, /* 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -1271,14 +1298,16 @@ public class ImaUtilTest {
} }
@Test @Test
public void public void maybeCorrectPreviouslyUnknownAdDuration_singleContentPeriodTimeline_doNothing() {
maybeCorrectPreviouslyUnknownAdDuration_singleContentPeriodTimeline_adPlaybackStateNotChanged() {
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 30_000_000L, /* liveWindowDurationUs= */ 30_000_000L,
/* nowUs= */ 80_000_000L, /* nowUs= */ 80_000_000L,
/* adSequencePattern= */ 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -1308,10 +1337,13 @@ public class ImaUtilTest {
maybeCorrectPreviouslyUnknownAdDuration_singleAdPeriodTimeline_doesNotOverrideWithTimeUnset() { maybeCorrectPreviouslyUnknownAdDuration_singleAdPeriodTimeline_doesNotOverrideWithTimeUnset() {
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 10_000_000L, /* liveWindowDurationUs= */ 10_000_000L,
/* nowUs= */ 90_000_000L, /* nowUs= */ 90_000_000L,
/* adSequencePattern= */ 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -1608,13 +1640,20 @@ public class ImaUtilTest {
@Test @Test
public void public void
getAdGroupAndIndexInLiveMultiPeriodTimeline_calledForPeriodsAfterUnplayedAdGroup_correctAdGroupIndexAndAdIndexInAdGroup() { 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 // Content live window with content and ad periods: c, [a, c, a, a, a, c, a], a, a
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 100_000_000, /* liveWindowDurationUs= */ 100_000_000,
/* nowUs= */ 159_000_123, /* nowUs= */ 159_000_123,
/* adSequencePattern= */ new boolean[] {false, true, true, true}, /* 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -1623,9 +1662,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 50_000_000, /* currentContentPeriodPositionUs= */ 50_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 1, /* adPositionInAdPod= */ 1,
/* totalAdDurationUs= */ AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ adPeriodDurationUs,
/* totalAdsInAdPod= */ 1, /* totalAdsInAdPod= */ 1,
adPlaybackState); adPlaybackState);
@ -1642,25 +1681,25 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 90_000_000, /* currentContentPeriodPositionUs= */ 90_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 1, /* adPositionInAdPod= */ 1,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 100_000_000, /* currentContentPeriodPositionUs= */ 100_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 2, /* adPositionInAdPod= */ 2,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 110_000_000, /* currentContentPeriodPositionUs= */ 110_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 3, /* adPositionInAdPod= */ 3,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
@ -1699,9 +1738,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 150_000_000, /* currentContentPeriodPositionUs= */ 150_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 2, /* adPositionInAdPod= */ 2,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
@ -1717,13 +1756,20 @@ public class ImaUtilTest {
@Test @Test
public void public void
getAdGroupAndIndexInLiveMultiPeriodTimeline_calledForPeriodsBeforeUnplayedAdGroup_throwsWhenCalledForNonAdPeriods() { 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 // Content live window with content and ad periods: c, [a, c, a, a, a, c, a], a, a
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 100_000_000, /* liveWindowDurationUs= */ 100_000_000,
/* nowUs= */ 159_000_123, /* nowUs= */ 159_000_123,
/* adSequencePattern= */ new boolean[] {false, true, true, true}, /* 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -1732,9 +1778,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 50_000_000, /* currentContentPeriodPositionUs= */ 50_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 1, /* adPositionInAdPod= */ 1,
/* totalAdDurationUs= */ AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ adPeriodDurationUs,
/* totalAdsInAdPod= */ 1, /* totalAdsInAdPod= */ 1,
adPlaybackState); adPlaybackState);
adPlaybackState = adPlaybackState =
@ -1742,25 +1788,25 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 90_000_000, /* currentContentPeriodPositionUs= */ 90_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 1, /* adPositionInAdPod= */ 1,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 100_000_000, /* currentContentPeriodPositionUs= */ 100_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 2, /* adPositionInAdPod= */ 2,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 110_000_000, /* currentContentPeriodPositionUs= */ 110_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 3, /* adPositionInAdPod= */ 3,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
AdPlaybackState finalAdPlaybackState = adPlaybackState; AdPlaybackState finalAdPlaybackState = adPlaybackState;
@ -1783,9 +1829,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 150_000_000, /* currentContentPeriodPositionUs= */ 150_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 2, /* adPositionInAdPod= */ 2,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
AdPlaybackState anotherFinalAdPlaybackState = adPlaybackState; AdPlaybackState anotherFinalAdPlaybackState = adPlaybackState;
@ -1803,13 +1849,20 @@ public class ImaUtilTest {
@Test @Test
public void public void
getAdGroupAndIndexInLiveMultiPeriodTimeline_partialAdGroupAtTimelineStart_correctAdGroupIndexAndAdIndexInAdGroup() { 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] // Content timeline with content and ad periods: c, a, a, [a, c, a, a, a, c]
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 100_000_000, /* liveWindowDurationUs= */ 100_000_000,
/* nowUs= */ 151_000_123, /* nowUs= */ 151_000_123,
/* adSequencePattern= */ new boolean[] {false, true, true, true}, /* 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -1818,25 +1871,25 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 30_000_000, /* currentContentPeriodPositionUs= */ 30_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 1, /* adPositionInAdPod= */ 1,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 40_000_000, /* currentContentPeriodPositionUs= */ 40_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 2, /* adPositionInAdPod= */ 2,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 50_000_000, /* currentContentPeriodPositionUs= */ 50_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 3, /* adPositionInAdPod= */ 3,
/* totalAdDurationUs= */ 3 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 3 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 3, /* totalAdsInAdPod= */ 3,
adPlaybackState); adPlaybackState);
adPlaybackState = adPlaybackState =
@ -1856,14 +1909,24 @@ public class ImaUtilTest {
@Test @Test
public void public void
getAdGroupAndIndexInLiveMultiPeriodTimeline_onlyPartialAdGroupInWindow_correctAdGroupIndexAndAdIndexInAdGroup() { 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 // 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. // First three ad periods of the ad group already outside of the live window.
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 30_000_000, /* liveWindowDurationUs= */ 30_000_000,
/* nowUs= */ 71_000_123, /* nowUs= */ 71_000_123,
/* adSequencePattern= */ new boolean[] {false, true, true, true, true, true, true}, /* 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -1873,9 +1936,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 30_000_000, /* currentContentPeriodPositionUs= */ 30_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 1, /* adPositionInAdPod= */ 1,
/* totalAdDurationUs= */ 6 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 6 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 6, /* totalAdsInAdPod= */ 6,
adPlaybackState); adPlaybackState);
adPlaybackState = adPlaybackState =
@ -1883,9 +1946,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 40_000_000, /* currentContentPeriodPositionUs= */ 40_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 2, /* adPositionInAdPod= */ 2,
/* totalAdDurationUs= */ 6 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 6 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 6, /* totalAdsInAdPod= */ 6,
adPlaybackState); adPlaybackState);
@ -1903,9 +1966,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 50_000_000, /* currentContentPeriodPositionUs= */ 50_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 3, /* adPositionInAdPod= */ 3,
/* totalAdDurationUs= */ 6 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 6 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 6, /* totalAdsInAdPod= */ 6,
adPlaybackState); adPlaybackState);
@ -1941,13 +2004,23 @@ public class ImaUtilTest {
@Test @Test
public void handleAdPeriodRemovedFromTimeline_removalCorrectlyHandled() { 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 // Content timeline with content and ad periods: a,[c, a, a, a, a, a, a, c], a
FakeMultiPeriodLiveTimeline contentTimeline = FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 70_000_123, /* liveWindowDurationUs= */ 70_000_123,
/* nowUs= */ 189_453_123, /* nowUs= */ 189_453_123,
/* adSequencePattern= */ new boolean[] {false, true, true, true, true, true, true}, /* 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, /* isContentTimeline= */ false,
/* populateAds= */ true, /* populateAds= */ true,
/* playedAds= */ false); /* playedAds= */ false);
@ -1957,9 +2030,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 120_000_000, /* currentContentPeriodPositionUs= */ 120_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 1, /* adPositionInAdPod= */ 1,
/* totalAdDurationUs= */ 6 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 6 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 6, /* totalAdsInAdPod= */ 6,
adPlaybackState); adPlaybackState);
adPlaybackState = adPlaybackState =
@ -1967,9 +2040,9 @@ public class ImaUtilTest {
adPlaybackState = adPlaybackState =
addLiveAdBreak( addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 130_000_000, /* currentContentPeriodPositionUs= */ 130_000_000,
/* adDurationUs= */ AD_PERIOD_DURATION_US, /* adDurationUs= */ adPeriodDurationUs,
/* adPositionInAdPod= */ 2, /* adPositionInAdPod= */ 2,
/* totalAdDurationUs= */ 6 * AD_PERIOD_DURATION_US, /* totalAdDurationUs= */ 6 * adPeriodDurationUs,
/* totalAdsInAdPod= */ 6, /* totalAdsInAdPod= */ 6,
adPlaybackState); adPlaybackState);
@ -2099,13 +2172,17 @@ public class ImaUtilTest {
@Test @Test
public void getAdGroupDurationUsForLiveAdPeriodIndex_allAdsInTimeline_correctAdGroupDuration() { public void getAdGroupDurationUsForLiveAdPeriodIndex_allAdsInTimeline_correctAdGroupDuration() {
int adPodTotalAdCount = 2; int adPodTotalAdCount = 2;
long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS);
// Content and ad periods in timeline: [c, a, a, c, a, a]. // Content and ad periods in timeline: [c, a, a, c, a, a].
FakeMultiPeriodLiveTimeline timeline = FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 75_007_123, /* liveWindowDurationUs= */ 75_007_123,
/* nowUs= */ 99_321_457, /* nowUs= */ 99_321_457,
/* adSequencePattern= */ 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, /* isContentTimeline= */ false,
/* populateAds= */ true, /* populateAds= */ true,
/* playedAds= */ false); /* playedAds= */ false);
@ -2121,11 +2198,11 @@ public class ImaUtilTest {
assertThat( assertThat(
ImaUtil.getAdGroupDurationUsForLiveAdPeriodIndex( ImaUtil.getAdGroupDurationUsForLiveAdPeriodIndex(
timeline, firstAdPodInfo, /* adPeriodIndex= */ 1, new Window(), new Period())) timeline, firstAdPodInfo, /* adPeriodIndex= */ 1, new Window(), new Period()))
.isEqualTo(2 * AD_PERIOD_DURATION_US); .isEqualTo(2 * adPeriodDurationUs);
assertThat( assertThat(
ImaUtil.getAdGroupDurationUsForLiveAdPeriodIndex( ImaUtil.getAdGroupDurationUsForLiveAdPeriodIndex(
timeline, secondAdPodInfo, /* adPeriodIndex= */ 2, new Window(), new Period())) 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. // The second ad group has the last ad with an unknown duration.
assertThat( assertThat(
ImaUtil.getAdGroupDurationUsForLiveAdPeriodIndex( ImaUtil.getAdGroupDurationUsForLiveAdPeriodIndex(
@ -2144,10 +2221,13 @@ public class ImaUtilTest {
// Content and ad periods in timeline: [a, a, c]. First two ads not in window. // Content and ad periods in timeline: [a, a, c]. First two ads not in window.
FakeMultiPeriodLiveTimeline timeline = FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0, /* availabilityStartTimeMs= */ 0,
/* liveWindowDurationUs= */ 49_321_753, /* liveWindowDurationUs= */ 49_321_753,
/* nowUs= */ 85_007_123, /* nowUs= */ 85_007_123,
/* adSequencePattern= */ 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, /* isContentTimeline= */ false,
/* populateAds= */ true, /* populateAds= */ true,
/* playedAds= */ false); /* 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.Assertions.checkArgument;
import static androidx.media3.common.util.Util.msToUs; 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.common.util.Util.usToMs;
import androidx.media3.common.AdPlaybackState; 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 * <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 * 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 * a duration of {@link #AD_PERIOD_DURATION_MS} and content periods have a duration of {@link
* #PERIOD_DURATION_US}. * #PERIOD_DURATION_MS}.
*/ */
@UnstableApi @UnstableApi
public class FakeMultiPeriodLiveTimeline extends Timeline { public class FakeMultiPeriodLiveTimeline extends Timeline {
public static final long AD_PERIOD_DURATION_US = 10_000_000L; public static final long AD_PERIOD_DURATION_MS = 10_000L;
public static final long PERIOD_DURATION_US = 30_000_000L; public static final long PERIOD_DURATION_MS = 30_000L;
private final boolean[] adSequencePattern; private final boolean[] adSequencePattern;
private final MediaItem mediaItem; private final MediaItem mediaItem;
@ -58,6 +59,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
private final boolean isContentTimeline; private final boolean isContentTimeline;
private final boolean populateAds; private final boolean populateAds;
private final boolean playedAds; private final boolean playedAds;
private final long[] periodDurationUsPattern;
private long nowUs; private long nowUs;
private ImmutableList<PeriodData> periods; private ImmutableList<PeriodData> periods;
@ -65,53 +67,55 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
/** /**
* Creates an instance. * 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 liveWindowDurationUs The duration of the live window.
* @param nowUs The current time that determines the end 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 * @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 * @param isContentTimeline Whether the timeline is a content timeline without {@link
* AdPlaybackState}s. * AdPlaybackState}s.
* @param populateAds Whether to populate ads in the same way if an ad event has been received. * @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. * @param playedAds Whether ads should be marked as played if populated.
*/ */
public FakeMultiPeriodLiveTimeline( public FakeMultiPeriodLiveTimeline(
long availabilityStartTimeUs, long availabilityStartTimeMs,
long liveWindowDurationUs, long liveWindowDurationUs,
long nowUs, long nowUs,
boolean[] adSequencePattern, boolean[] adSequencePattern,
long[] periodDurationMsPattern,
boolean isContentTimeline, boolean isContentTimeline,
boolean populateAds, boolean populateAds,
boolean playedAds) { boolean playedAds) {
checkArgument(nowUs - liveWindowDurationUs >= availabilityStartTimeUs); checkArgument(nowUs - liveWindowDurationUs >= msToUs(availabilityStartTimeMs));
this.availabilityStartTimeUs = availabilityStartTimeUs; checkArgument(adSequencePattern.length == periodDurationMsPattern.length);
this.availabilityStartTimeUs = msToUs(availabilityStartTimeMs);
this.liveWindowDurationUs = liveWindowDurationUs; this.liveWindowDurationUs = liveWindowDurationUs;
this.nowUs = nowUs; this.nowUs = nowUs;
this.adSequencePattern = Arrays.copyOf(adSequencePattern, adSequencePattern.length); 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.isContentTimeline = isContentTimeline;
this.populateAds = populateAds; this.populateAds = populateAds;
this.playedAds = playedAds; this.playedAds = playedAds;
mediaItem = new MediaItem.Builder().build(); mediaItem = new MediaItem.Builder().build();
periods = periods =
invalidate( invalidate(
availabilityStartTimeUs, msToUs(availabilityStartTimeMs),
liveWindowDurationUs, liveWindowDurationUs,
nowUs, nowUs,
adSequencePattern, adSequencePattern,
periodDurationUsPattern,
isContentTimeline, isContentTimeline,
populateAds, populateAds,
playedAds); 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. */ /** Advances the live window by the given duration, in microseconds. */
public void advanceNowUs(long durationUs) { public void advanceNowUs(long durationUs) {
nowUs += durationUs; nowUs += durationUs;
@ -121,11 +125,35 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
liveWindowDurationUs, liveWindowDurationUs,
nowUs, nowUs,
adSequencePattern, adSequencePattern,
periodDurationUsPattern,
isContentTimeline, isContentTimeline,
populateAds, populateAds,
playedAds); 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 @Override
public int getWindowCount() { public int getWindowCount() {
return 1; return 1;
@ -136,12 +164,13 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
checkArgument(windowIndex == 0); checkArgument(windowIndex == 0);
MediaItem.LiveConfiguration liveConfiguration = MediaItem.LiveConfiguration liveConfiguration =
new MediaItem.LiveConfiguration.Builder().build(); new MediaItem.LiveConfiguration.Builder().build();
long positionInFirstPeriodUs = -periods.get(0).positionInWindowUs;
window.set( window.set(
/* uid= */ "live-window", /* uid= */ "live-window",
mediaItem, mediaItem,
/* manifest= */ null, /* manifest= */ null,
/* presentationStartTimeMs= */ C.TIME_UNSET, /* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ usToMs(nowUs - liveWindowDurationUs), /* windowStartTimeMs= */ periods.get(0).periodStartTimeMs + usToMs(positionInFirstPeriodUs),
/* elapsedRealtimeEpochOffsetMs= */ 0, /* elapsedRealtimeEpochOffsetMs= */ 0,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
@ -150,7 +179,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
/* durationUs= */ liveWindowDurationUs, /* durationUs= */ liveWindowDurationUs,
/* firstPeriodIndex= */ 0, /* firstPeriodIndex= */ 0,
/* lastPeriodIndex= */ getPeriodCount() - 1, /* lastPeriodIndex= */ getPeriodCount() - 1,
/* positionInFirstPeriodUs= */ -periods.get(0).positionInWindowUs); positionInFirstPeriodUs);
return window; return window;
} }
@ -193,24 +222,23 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
long liveWindowDurationUs, long liveWindowDurationUs,
long now, long now,
boolean[] adSequencePattern, boolean[] adSequencePattern,
long[] periodDurationUsPattern,
boolean isContentTimeline, boolean isContentTimeline,
boolean populateAds, boolean populateAds,
boolean playedAds) { boolean playedAds) {
long windowStartTimeUs = now - liveWindowDurationUs; long windowStartTimeUs = now - liveWindowDurationUs;
int sequencePeriodCount = adSequencePattern.length; int sequencePeriodCount = adSequencePattern.length;
long sequenceDurationUs = calculateAdSequencePatternDurationUs(adSequencePattern); long sequenceDurationUs = sum(periodDurationUsPattern);
long skippedSequenceCount = (windowStartTimeUs - availabilityStartTimeUs) / sequenceDurationUs; long skippedSequenceCount = (windowStartTimeUs - availabilityStartTimeUs) / sequenceDurationUs;
// Search the first period of the live window. // Search the first period of the live window.
int firstPeriodIndex = (int) (skippedSequenceCount * sequencePeriodCount); int firstPeriodIndex = (int) (skippedSequenceCount * sequencePeriodCount);
boolean isAd = adSequencePattern[firstPeriodIndex % sequencePeriodCount]; long firstPeriodDurationUs = periodDurationUsPattern[firstPeriodIndex % sequencePeriodCount];
long firstPeriodDurationUs = isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US;
long firstPeriodEndTimeUs = long firstPeriodEndTimeUs =
availabilityStartTimeUs availabilityStartTimeUs
+ (sequenceDurationUs * skippedSequenceCount) + (sequenceDurationUs * skippedSequenceCount)
+ firstPeriodDurationUs; + firstPeriodDurationUs;
while (firstPeriodEndTimeUs <= windowStartTimeUs) { while (firstPeriodEndTimeUs <= windowStartTimeUs) {
isAd = adSequencePattern[++firstPeriodIndex % sequencePeriodCount]; firstPeriodDurationUs = periodDurationUsPattern[++firstPeriodIndex % sequencePeriodCount];
firstPeriodDurationUs = isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US;
firstPeriodEndTimeUs += firstPeriodDurationUs; firstPeriodEndTimeUs += firstPeriodDurationUs;
} }
ImmutableList.Builder<PeriodData> liveWindow = new ImmutableList.Builder<>(); ImmutableList.Builder<PeriodData> liveWindow = new ImmutableList.Builder<>();
@ -218,8 +246,8 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
int lastPeriodIndex = firstPeriodIndex; int lastPeriodIndex = firstPeriodIndex;
// Add periods to the window from the first period until we find a period start after `now`. // Add periods to the window from the first period until we find a period start after `now`.
while (lastPeriodStartTimeUs < now) { while (lastPeriodStartTimeUs < now) {
isAd = adSequencePattern[lastPeriodIndex % sequencePeriodCount]; long periodDurationUs = periodDurationUsPattern[lastPeriodIndex % sequencePeriodCount];
long periodDurationUs = isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US; boolean isAd = adSequencePattern[lastPeriodIndex % sequencePeriodCount];
AdPlaybackState adPlaybackState = AdPlaybackState.NONE; AdPlaybackState adPlaybackState = AdPlaybackState.NONE;
if (!isContentTimeline) { if (!isContentTimeline) {
adPlaybackState = new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended(); adPlaybackState = new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
@ -244,6 +272,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
/* id= */ lastPeriodIndex++, /* id= */ lastPeriodIndex++,
periodDurationUs, periodDurationUs,
/* positionInWindowUs= */ lastPeriodStartTimeUs - windowStartTimeUs, /* positionInWindowUs= */ lastPeriodStartTimeUs - windowStartTimeUs,
/* periodStartTimeMs= */ usToMs(lastPeriodStartTimeUs),
isAd, isAd,
adPlaybackState)); adPlaybackState));
lastPeriodStartTimeUs += periodDurationUs; lastPeriodStartTimeUs += periodDurationUs;
@ -257,6 +286,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
private final Object uid; private final Object uid;
private final long durationUs; private final long durationUs;
private final long positionInWindowUs; private final long positionInWindowUs;
private final long periodStartTimeMs;
private final AdPlaybackState adPlaybackState; private final AdPlaybackState adPlaybackState;
/** Creates an instance. */ /** Creates an instance. */
@ -264,9 +294,11 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
int id, int id,
long durationUs, long durationUs,
long positionInWindowUs, long positionInWindowUs,
long periodStartTimeMs,
boolean isAd, boolean isAd,
AdPlaybackState adPlaybackState) { AdPlaybackState adPlaybackState) {
this.id = id; this.id = id;
this.periodStartTimeMs = periodStartTimeMs;
this.uid = "uid-" + id + "[" + (isAd ? "a" : "c") + "]"; this.uid = "uid-" + id + "[" + (isAd ? "a" : "c") + "]";
this.durationUs = durationUs; this.durationUs = durationUs;
this.positionInWindowUs = positionInWindowUs; this.positionInWindowUs = positionInWindowUs;

View File

@ -15,14 +15,15 @@
*/ */
package androidx.media3.test.utils; package androidx.media3.test.utils;
import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_US; import static androidx.media3.common.util.Util.msToUs;
import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US; import static androidx.media3.common.util.Util.sum;
import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.calculateAdSequencePatternDurationUs; 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 static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.util.Util;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -36,12 +37,16 @@ public class FakeMultiPeriodLiveTimelineTest {
@Test @Test
public void newInstance_availabilitySinceStartOfUnixEpoch_correctLiveWindow() { public void newInstance_availabilitySinceStartOfUnixEpoch_correctLiveWindow() {
boolean[] adSequencePattern = {false, true, true}; boolean[] adSequencePattern = {false, true, true};
long[] periodDurationMsPattern = {
PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS
};
FakeMultiPeriodLiveTimeline timeline = FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L, /* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_000L, /* nowUs= */ 60_000_000L,
adSequencePattern, adSequencePattern,
periodDurationMsPattern,
/* isContentTimeline= */ true, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -63,22 +68,28 @@ public class FakeMultiPeriodLiveTimelineTest {
assertExpectedWindow( assertExpectedWindow(
timeline, timeline,
calculateExpectedWindow( calculateExpectedWindow(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L, /* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_000L, /* nowUs= */ 60_000_000L,
adSequencePattern), adSequencePattern,
adSequencePattern); periodDurationMsPattern),
adSequencePattern,
periodDurationMsPattern);
} }
@Test @Test
public void newInstance_timelineWithAdsPopulated_correctPlaybackStates() { public void newInstance_timelineWithAdsPopulated_correctPlaybackStates() {
boolean[] adSequencePattern = {false, true, true}; boolean[] adSequencePattern = {false, true, true};
long[] periodDurationMsPattern = {
PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS
};
FakeMultiPeriodLiveTimeline timeline = FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 50_000_000L, /* liveWindowDurationUs= */ 50_000_000L,
/* nowUs= */ 100_000_000L, /* nowUs= */ 100_000_000L,
adSequencePattern, adSequencePattern,
periodDurationMsPattern,
/* isContentTimeline= */ false, /* isContentTimeline= */ false,
/* populateAds= */ true, /* populateAds= */ true,
/* playedAds= */ false); /* playedAds= */ false);
@ -123,22 +134,28 @@ public class FakeMultiPeriodLiveTimelineTest {
assertExpectedWindow( assertExpectedWindow(
timeline, timeline,
calculateExpectedWindow( calculateExpectedWindow(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 50_000_000L, /* liveWindowDurationUs= */ 50_000_000L,
/* nowUs= */ 100_000_000L, /* nowUs= */ 100_000_000L,
adSequencePattern), adSequencePattern,
adSequencePattern); periodDurationMsPattern),
adSequencePattern,
periodDurationMsPattern);
} }
@Test @Test
public void newInstance_timelineWithAdsNotPopulated_correctPlaybackStates() { public void newInstance_timelineWithAdsNotPopulated_correctPlaybackStates() {
boolean[] adSequencePattern = {false, true, true}; boolean[] adSequencePattern = {false, true, true};
long[] periodDurationMsPattern = {
PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS
};
FakeMultiPeriodLiveTimeline timeline = FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 50_000_000L, /* liveWindowDurationUs= */ 50_000_000L,
/* nowUs= */ 100_000_000L, /* nowUs= */ 100_000_000L,
adSequencePattern, adSequencePattern,
periodDurationMsPattern,
/* isContentTimeline= */ false, /* isContentTimeline= */ false,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -175,22 +192,28 @@ public class FakeMultiPeriodLiveTimelineTest {
assertExpectedWindow( assertExpectedWindow(
timeline, timeline,
calculateExpectedWindow( calculateExpectedWindow(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 50_000_000L, /* liveWindowDurationUs= */ 50_000_000L,
/* nowUs= */ 100_000_000L, /* nowUs= */ 100_000_000L,
adSequencePattern), adSequencePattern,
adSequencePattern); periodDurationMsPattern),
adSequencePattern,
periodDurationMsPattern);
} }
@Test @Test
public void advanceTimeUs_availabilitySinceStartOfUnixEpoch_correctPeriodsInLiveWindow() { public void advanceTimeUs_availabilitySinceStartOfUnixEpoch_correctPeriodsInLiveWindow() {
boolean[] adSequencePattern = {false, true, true}; boolean[] adSequencePattern = {false, true, true};
long[] periodDurationMsPattern = {
PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS
};
FakeMultiPeriodLiveTimeline timeline = FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L, /* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_123L, /* nowUs= */ 60_000_123L,
adSequencePattern, adSequencePattern,
periodDurationMsPattern,
/* isContentTimeline= */ true, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -209,11 +232,13 @@ public class FakeMultiPeriodLiveTimelineTest {
assertExpectedWindow( assertExpectedWindow(
timeline, timeline,
calculateExpectedWindow( calculateExpectedWindow(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L, /* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_123L, /* 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. // Advance nowUs so that the window ends just 1us before the next period moves into the window.
timeline.advanceNowUs(19999877L); timeline.advanceNowUs(19999877L);
@ -231,11 +256,13 @@ public class FakeMultiPeriodLiveTimelineTest {
assertExpectedWindow( assertExpectedWindow(
timeline, timeline,
calculateExpectedWindow( calculateExpectedWindow(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L, /* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_123L + 19999877L, /* 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. // Advance the window by 1us to add the next period at the end of the window.
timeline.advanceNowUs(1L); timeline.advanceNowUs(1L);
@ -254,110 +281,159 @@ public class FakeMultiPeriodLiveTimelineTest {
assertExpectedWindow( assertExpectedWindow(
timeline, timeline,
calculateExpectedWindow( calculateExpectedWindow(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L, /* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_123L + 19999878L, /* nowUs= */ 60_000_123L + 19999878L,
adSequencePattern), adSequencePattern,
adSequencePattern); periodDurationMsPattern),
adSequencePattern,
periodDurationMsPattern);
} }
@Test @Test
public void newInstance_advancedAvailabilityStartTime_correctlyInterpolatedPeriodIds() { public void newInstance_advancedAvailabilityStartTime_correctlyInterpolatedPeriodIds() {
Timeline.Period period = new Timeline.Period(); Timeline.Period period = new Timeline.Period();
long availabilityStartTimeUs = 0; long availabilityStartTimeMs = 0;
long nowUs = 120_000_123; long nowUs = 120_000_123;
long liveWindowDurationUs = 60_000_987L; long liveWindowDurationUs = 60_000_987L;
boolean[] adSequencePattern = {false, true, true}; 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 = FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
availabilityStartTimeUs, availabilityStartTimeMs,
liveWindowDurationUs, liveWindowDurationUs,
nowUs, nowUs,
adSequencePattern, adSequencePattern,
periodDurationMsPattern,
/* isContentTimeline= */ true, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ true); /* playedAds= */ true);
assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs)
.isEqualTo(Util.usToMs(nowUs - liveWindowDurationUs));
assertThat(timeline.getPeriodCount()).isEqualTo(4); 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).id).isEqualTo(3);
assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).uid).isEqualTo("uid-3[c]"); 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).id).isEqualTo(4);
assertThat(timeline.getPeriod(/* periodIndex= */ 1, period).uid).isEqualTo("uid-4[a]"); 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).id).isEqualTo(5);
assertThat(timeline.getPeriod(/* periodIndex= */ 2, period).uid).isEqualTo("uid-5[a]"); 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).id).isEqualTo(6);
assertThat(timeline.getPeriod(/* periodIndex= */ 3, period).uid).isEqualTo("uid-6[c]"); assertThat(timeline.getPeriod(/* periodIndex= */ 3, period).uid).isEqualTo("uid-6[c]");
assertExpectedWindow( assertExpectedWindow(
timeline, timeline,
calculateExpectedWindow( calculateExpectedWindow(
availabilityStartTimeUs, liveWindowDurationUs, nowUs, adSequencePattern), availabilityStartTimeMs,
adSequencePattern); liveWindowDurationUs,
nowUs,
adSequencePattern,
periodDurationMsPattern),
adSequencePattern,
periodDurationMsPattern);
timeline.advanceNowUs(sequenceDurationUs * 13); timeline.advanceNowUs(sequenceDurationUs * 13);
windowStartTimeUs = timeline.getWindowStartTimeUs();
assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs) assertThat(
.isEqualTo(Util.usToMs((nowUs + sequenceDurationUs * 13) - liveWindowDurationUs)); windowStartTimeUs + timeline.getPeriod(/* periodIndex= */ 0, period).positionInWindowUs)
.isEqualTo(timeline.getPeriodStartTimeUs(/* periodIndex= */ 0));
assertThat(timeline.getPeriodCount()).isEqualTo(4); assertThat(timeline.getPeriodCount()).isEqualTo(4);
assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).id).isEqualTo((13 * 3) + 3); assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).id).isEqualTo((13 * 3) + 3);
assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).uid) assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).uid)
.isEqualTo("uid-" + ((13 * 3) + 3) + "[c]"); .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).id).isEqualTo((13 * 3) + 4);
assertThat(timeline.getPeriod(/* periodIndex= */ 1, period).uid) assertThat(timeline.getPeriod(/* periodIndex= */ 1, period).uid)
.isEqualTo("uid-" + ((13 * 3) + 4) + "[a]"); .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).id).isEqualTo((13 * 3) + 5);
assertThat(timeline.getPeriod(/* periodIndex= */ 2, period).uid) assertThat(timeline.getPeriod(/* periodIndex= */ 2, period).uid)
.isEqualTo("uid-" + ((13 * 3) + 5) + "[a]"); .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).id).isEqualTo((13 * 3) + 6);
assertThat(timeline.getPeriod(/* periodIndex= */ 3, period).uid) assertThat(timeline.getPeriod(/* periodIndex= */ 3, period).uid)
.isEqualTo("uid-" + ((13 * 3) + 6) + "[c]"); .isEqualTo("uid-" + ((13 * 3) + 6) + "[c]");
assertExpectedWindow( assertExpectedWindow(
timeline, timeline,
calculateExpectedWindow( calculateExpectedWindow(
availabilityStartTimeUs, availabilityStartTimeMs,
liveWindowDurationUs, liveWindowDurationUs,
(nowUs + sequenceDurationUs * 13), (nowUs + sequenceDurationUs * 13),
adSequencePattern), adSequencePattern,
adSequencePattern); periodDurationMsPattern),
adSequencePattern,
periodDurationMsPattern);
} }
@Test @Test
public void newInstance_availabilitySinceAWeekAfterStartOfUnixEpoch_correctLiveWindow() { 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 nowUs = 18 * A_DAY_US + 135_000_000;
long liveWindowDurationUs = 60_000_000L; long liveWindowDurationUs = 60_000_000L;
boolean[] adSequencePattern = {false, true, true}; boolean[] adSequencePattern = {false, true, true};
long[] periodDurationMsPattern = {
PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS
};
FakeMultiPeriodLiveTimeline timeline = FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
availabilityStartTimeUs, availabilityStartTimeMs,
liveWindowDurationUs, liveWindowDurationUs,
nowUs, nowUs,
adSequencePattern, adSequencePattern,
periodDurationMsPattern,
/* isContentTimeline= */ true, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs) assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs)
.isEqualTo(Util.usToMs(nowUs - liveWindowDurationUs)); .isEqualTo(usToMs(nowUs - liveWindowDurationUs));
assertExpectedWindow( assertExpectedWindow(
timeline, timeline,
calculateExpectedWindow( calculateExpectedWindow(
availabilityStartTimeUs, liveWindowDurationUs, nowUs, adSequencePattern), availabilityStartTimeMs,
adSequencePattern); liveWindowDurationUs,
nowUs,
adSequencePattern,
periodDurationMsPattern),
adSequencePattern,
periodDurationMsPattern);
} }
@Test @Test
public void newInstance_adSequencePattern_correctPeriodTypesFromStartOfAvailability() { public void newInstance_adSequencePattern_correctPeriodTypesFromStartOfAvailability() {
FakeMultiPeriodLiveTimeline timeline = FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 120_000_000L, /* liveWindowDurationUs= */ 120_000_000L,
/* nowUs= */ 120_000_000L, /* nowUs= */ 120_000_000L,
new boolean[] {false, true, true, true}, 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -392,10 +468,17 @@ public class FakeMultiPeriodLiveTimelineTest {
timeline = timeline =
new FakeMultiPeriodLiveTimeline( new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0L, /* availabilityStartTimeMs= */ 0L,
/* liveWindowDurationUs= */ 220_000_000L, /* liveWindowDurationUs= */ 220_000_000L,
/* nowUs= */ 250_000_000L, /* nowUs= */ 250_000_000L,
new boolean[] {false, true, false, true, false}, 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, /* isContentTimeline= */ true,
/* populateAds= */ false, /* populateAds= */ false,
/* playedAds= */ false); /* playedAds= */ false);
@ -415,22 +498,62 @@ public class FakeMultiPeriodLiveTimelineTest {
assertThat(timeline.getPeriod(9, period).uid).isEqualTo("uid-10[c]"); 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( private ExpectedWindow calculateExpectedWindow(
long availabilityStartTimeUs, long availabilityStartTimeMs,
long liveWindowDurationUs, long liveWindowDurationUs,
long nowUs, 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 windowStartTimeUs = nowUs - liveWindowDurationUs;
long sequenceDurationUs = calculateAdSequencePatternDurationUs(adSequencePattern); long sequenceDurationUs = sum(periodDurationUsPattern);
long durationBeforeWindowStartUs = windowStartTimeUs - availabilityStartTimeUs; long durationBeforeWindowStartUs = windowStartTimeUs - msToUs(availabilityStartTimeMs);
long skippedSequenceCount = durationBeforeWindowStartUs / sequenceDurationUs; long skippedSequenceCount = durationBeforeWindowStartUs / sequenceDurationUs;
long remainingDurationBeforeWindowUs = durationBeforeWindowStartUs % sequenceDurationUs; long remainingDurationBeforeWindowUs = durationBeforeWindowStartUs % sequenceDurationUs;
int idOfFirstPeriodInWindow = (int) (skippedSequenceCount * adSequencePattern.length); int idOfFirstPeriodInWindow = (int) (skippedSequenceCount * adSequencePattern.length);
long lastSkippedPeriodDurationUs = 0L; long lastSkippedPeriodDurationUs = 0L;
// Skip period by period until we reach the window start. // Skip period by period until we reach the window start.
while (remainingDurationBeforeWindowUs > 0) { while (remainingDurationBeforeWindowUs > 0) {
boolean isAd = adSequencePattern[idOfFirstPeriodInWindow++ % adSequencePattern.length]; lastSkippedPeriodDurationUs =
lastSkippedPeriodDurationUs = isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US; periodDurationUsPattern[idOfFirstPeriodInWindow++ % adSequencePattern.length];
remainingDurationBeforeWindowUs -= lastSkippedPeriodDurationUs; remainingDurationBeforeWindowUs -= lastSkippedPeriodDurationUs;
} }
long positionOfFirstPeriodInWindowUs = 0; long positionOfFirstPeriodInWindowUs = 0;
@ -442,24 +565,29 @@ public class FakeMultiPeriodLiveTimelineTest {
-(lastSkippedPeriodDurationUs + remainingDurationBeforeWindowUs); -(lastSkippedPeriodDurationUs + remainingDurationBeforeWindowUs);
} }
long durationOfFirstPeriodInWindowUs = long durationOfFirstPeriodInWindowUs =
adSequencePattern[idOfFirstPeriodInWindow % adSequencePattern.length] periodDurationUsPattern[idOfFirstPeriodInWindow % adSequencePattern.length];
? AD_PERIOD_DURATION_US
: PERIOD_DURATION_US;
long durationInWindowUs = long durationInWindowUs =
remainingDurationBeforeWindowUs == 0 remainingDurationBeforeWindowUs == 0
? durationOfFirstPeriodInWindowUs ? durationOfFirstPeriodInWindowUs
: -remainingDurationBeforeWindowUs; : -remainingDurationBeforeWindowUs;
int idOfLastPeriodInWindow = idOfFirstPeriodInWindow; int idOfLastPeriodInWindow = idOfFirstPeriodInWindow;
while (durationInWindowUs < liveWindowDurationUs) { while (durationInWindowUs < liveWindowDurationUs) {
boolean isAd = adSequencePattern[++idOfLastPeriodInWindow % adSequencePattern.length]; durationInWindowUs +=
durationInWindowUs += isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US; periodDurationUsPattern[++idOfLastPeriodInWindow % adSequencePattern.length];
} }
return new ExpectedWindow( return new ExpectedWindow(
idOfFirstPeriodInWindow, idOfLastPeriodInWindow, positionOfFirstPeriodInWindowUs); idOfFirstPeriodInWindow, idOfLastPeriodInWindow, positionOfFirstPeriodInWindowUs);
} }
private void assertExpectedWindow( 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(); Timeline.Period period = new Timeline.Period();
assertThat(timeline.getPeriodCount()) assertThat(timeline.getPeriodCount())
.isEqualTo(expectedWindow.idOfLastPeriod - expectedWindow.idOfFirstPeriod + 1); .isEqualTo(expectedWindow.idOfLastPeriod - expectedWindow.idOfFirstPeriod + 1);
@ -467,11 +595,12 @@ public class FakeMultiPeriodLiveTimelineTest {
for (int i = 0; i < timeline.getPeriodCount(); i++) { for (int i = 0; i < timeline.getPeriodCount(); i++) {
int id = expectedWindow.idOfFirstPeriod + i; int id = expectedWindow.idOfFirstPeriod + i;
boolean isAd = adSequencePattern[id % adSequencePattern.length]; 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).id).isEqualTo(id);
assertThat(timeline.getPeriod(i, period).uid) assertThat(timeline.getPeriod(i, period).uid)
.isEqualTo("uid-" + id + "[" + (isAd ? "a" : "c") + "]"); .isEqualTo("uid-" + id + "[" + (isAd ? "a" : "c") + "]");
assertThat(timeline.getPeriod(i, period).positionInWindowUs).isEqualTo(positionInWindowUs); assertThat(timeline.getPeriod(i, period).positionInWindowUs).isEqualTo(positionInWindowUs);
positionInWindowUs += isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US; positionInWindowUs += durationUs;
} }
} }