Add FakeMultiPeriodLiveTimeline and test case

This timeline will be used in unit test cases of follow-up
CLs. It basically can be used to emulate the timeline created by a
multi-period live media source when the real time advances.

PiperOrigin-RevId: 512665552
This commit is contained in:
bachinger 2023-02-27 18:28:04 +00:00 committed by tonihei
parent 198d73425e
commit 1760d63fc4
2 changed files with 569 additions and 0 deletions

View File

@ -0,0 +1,213 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.usToMs;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
/**
* A fake {@link Timeline} that produces a live window with periods according to the available time
* range.
*
* <p>The parameters passed to the {@linkplain #FakeMultiPeriodLiveTimeline constructor} define the
* availability start time, the window size and {@code now}. Use {@link #advanceNowUs(long)} to
* advance the live window of the timeline accordingly.
*
* <p>The first available period with {@link Period#id ID} 0 (zero) starts at {@code
* availabilityStartTimeUs}. The {@link Window live window} starts at {@code now -
* liveWindowDurationUs} with the first period of the window having its ID relative to the first
* available period.
*
* <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}.
*/
@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;
private final boolean[] adSequencePattern;
private final MediaItem mediaItem;
private final long availabilityStartTimeUs;
private final long liveWindowDurationUs;
private long nowUs;
private ImmutableList<PeriodData> periods;
/**
* Creates an instance.
*
* @param availabilityStartTimeUs The start time of the available time range, in UNIX epoch.
* @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.
*/
public FakeMultiPeriodLiveTimeline(
long availabilityStartTimeUs,
long liveWindowDurationUs,
long nowUs,
boolean[] adSequencePattern) {
checkArgument(nowUs - liveWindowDurationUs >= availabilityStartTimeUs);
this.availabilityStartTimeUs = availabilityStartTimeUs;
this.liveWindowDurationUs = liveWindowDurationUs;
this.nowUs = nowUs;
this.adSequencePattern = Arrays.copyOf(adSequencePattern, adSequencePattern.length);
mediaItem = new MediaItem.Builder().build();
periods = invalidate(availabilityStartTimeUs, liveWindowDurationUs, nowUs, adSequencePattern);
}
/** 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;
periods = invalidate(availabilityStartTimeUs, liveWindowDurationUs, nowUs, adSequencePattern);
}
@Override
public int getWindowCount() {
return 1;
}
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
MediaItem.LiveConfiguration liveConfiguration =
new MediaItem.LiveConfiguration.Builder().build();
window.set(
/* uid= */ "live-window",
mediaItem,
/* manifest= */ null,
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ usToMs(nowUs - liveWindowDurationUs),
/* elapsedRealtimeEpochOffsetMs= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ true,
liveConfiguration,
/* defaultPositionUs= */ liveWindowDurationUs - msToUs(liveConfiguration.targetOffsetMs),
/* durationUs= */ liveWindowDurationUs,
/* firstPeriodIndex= */ 0,
/* lastPeriodIndex= */ getPeriodCount() - 1,
/* positionInFirstPeriodUs= */ -periods.get(0).positionInWindowUs);
return window;
}
@Override
public int getPeriodCount() {
return periods.size();
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
PeriodData periodData = periods.get(periodIndex);
period.set(
periodData.id,
periodData.uid,
/* windowIndex= */ 0,
/* durationUs= */ periodIndex < getPeriodCount() - 1 ? periodData.durationUs : C.TIME_UNSET,
periodData.positionInWindowUs);
return period;
}
@Override
public int getIndexOfPeriod(Object uid) {
for (int i = 0; i < periods.size(); i++) {
if (uid.equals(periods.get(i).uid)) {
return i;
}
}
return C.INDEX_UNSET;
}
@Override
public Object getUidOfPeriod(int periodIndex) {
return periods.get(periodIndex).uid;
}
private static ImmutableList<PeriodData> invalidate(
long availabilityStartTimeUs,
long liveWindowDurationUs,
long now,
boolean[] adSequencePattern) {
long windowStartTimeUs = now - liveWindowDurationUs;
int sequencePeriodCount = adSequencePattern.length;
long sequenceDurationUs = calculateAdSequencePatternDurationUs(adSequencePattern);
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 firstPeriodEndTimeUs =
availabilityStartTimeUs
+ (sequenceDurationUs * skippedSequenceCount)
+ firstPeriodDurationUs;
while (firstPeriodEndTimeUs <= windowStartTimeUs) {
isAd = adSequencePattern[++firstPeriodIndex % sequencePeriodCount];
firstPeriodDurationUs = isAd ? AD_PERIOD_DURATION_US : PERIOD_DURATION_US;
firstPeriodEndTimeUs += firstPeriodDurationUs;
}
ImmutableList.Builder<PeriodData> liveWindow = new ImmutableList.Builder<>();
long lastPeriodStartTimeUs = firstPeriodEndTimeUs - firstPeriodDurationUs;
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;
liveWindow.add(
new PeriodData(
/* id= */ lastPeriodIndex++,
isAd,
periodDurationUs,
/* positionInWindowUs= */ lastPeriodStartTimeUs - windowStartTimeUs));
lastPeriodStartTimeUs += periodDurationUs;
}
return liveWindow.build();
}
private static class PeriodData {
private final int id;
private final Object uid;
private final long durationUs;
private final long positionInWindowUs;
/** Creates an instance. */
public PeriodData(int id, boolean isAd, long durationUs, long positionInWindowUs) {
this.id = id;
this.uid = "uid-" + id + "[" + (isAd ? "a" : "c") + "]";
this.durationUs = durationUs;
this.positionInWindowUs = positionInWindowUs;
}
}
}

View File

@ -0,0 +1,356 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 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;
/** Tests for {@link FakeMultiPeriodLiveTimeline}. */
@RunWith(AndroidJUnit4.class)
public class FakeMultiPeriodLiveTimelineTest {
private static final long A_DAY_US = 24L * 60L * 60L * 1_000_000L;
@Test
public void newInstance_availabilitySinceStartOfUnixEpoch_correctLiveWindow() {
boolean[] adSequencePattern = {false, true, true};
FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_000L,
adSequencePattern);
Timeline.Period period = new Timeline.Period();
Timeline.Window window = new Timeline.Window();
assertThat(timeline.getPeriodCount()).isEqualTo(4);
assertThat(timeline.getWindow(0, window).windowStartTimeMs).isEqualTo(0L);
assertThat(timeline.getPeriod(0, period).id).isEqualTo(0);
assertThat(timeline.getPeriod(0, period).uid).isEqualTo("uid-0[c]");
assertThat(timeline.getPeriod(0, period).positionInWindowUs).isEqualTo(0L);
assertThat(timeline.getPeriod(0, period).durationUs).isEqualTo(30_000_000L);
assertThat(timeline.getPeriod(1, period).positionInWindowUs).isEqualTo(30_000_000L);
assertThat(timeline.getPeriod(1, period).durationUs).isEqualTo(10_000_000L);
assertThat(timeline.getPeriod(2, period).positionInWindowUs).isEqualTo(40_000_000L);
assertThat(timeline.getPeriod(2, period).durationUs).isEqualTo(10_000_000L);
assertThat(timeline.getPeriod(3, period).positionInWindowUs).isEqualTo(50_000_000L);
assertThat(timeline.getPeriod(3, period).durationUs).isEqualTo(C.TIME_UNSET);
assertExpectedWindow(
timeline,
calculateExpectedWindow(
/* availabilityStartTimeUs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_000L,
adSequencePattern),
adSequencePattern);
}
@Test
public void advanceTimeUs_availabilitySinceStartOfUnixEpoch_correctPeriodsInLiveWindow() {
boolean[] adSequencePattern = {false, true, true};
FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_123L,
adSequencePattern);
Timeline.Period period = new Timeline.Period();
Timeline.Window window = new Timeline.Window();
assertThat(timeline.getPeriodCount()).isEqualTo(4);
assertThat(timeline.getWindow(0, window).windowStartTimeMs).isEqualTo(0L);
assertThat(timeline.getPeriod(0, period).id).isEqualTo(0);
assertThat(timeline.getPeriod(0, period).uid).isEqualTo("uid-0[c]");
assertThat(timeline.getPeriod(0, period).positionInWindowUs).isEqualTo(-123L);
assertThat(timeline.getPeriod(1, period).positionInWindowUs).isEqualTo(29_999_877L);
assertThat(timeline.getPeriod(2, period).positionInWindowUs).isEqualTo(39_999_877L);
assertThat(timeline.getPeriod(3, period).positionInWindowUs).isEqualTo(49_999_877L);
assertThat(timeline.getPeriod(3, period).durationUs).isEqualTo(C.TIME_UNSET);
assertExpectedWindow(
timeline,
calculateExpectedWindow(
/* availabilityStartTimeUs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_123L,
adSequencePattern),
adSequencePattern);
// Advance nowUs so that the window ends just 1us before the next period moves into the window.
timeline.advanceNowUs(19999877L);
// Assert that an additional period has not been included in the live window.
assertThat(timeline.getPeriodCount()).isEqualTo(4);
assertThat(timeline.getWindow(0, window).windowStartTimeMs).isEqualTo(20_000L);
assertThat(timeline.getPeriod(0, period).id).isEqualTo(0);
assertThat(timeline.getPeriod(0, period).uid).isEqualTo("uid-0[c]");
assertThat(timeline.getPeriod(0, period).positionInWindowUs).isEqualTo(-20_000_000L);
assertThat(timeline.getPeriod(1, period).positionInWindowUs).isEqualTo(10_000_000L);
assertThat(timeline.getPeriod(2, period).positionInWindowUs).isEqualTo(20_000_000L);
assertThat(timeline.getPeriod(3, period).positionInWindowUs).isEqualTo(30_000_000L);
assertThat(timeline.getPeriod(3, period).durationUs).isEqualTo(C.TIME_UNSET);
assertExpectedWindow(
timeline,
calculateExpectedWindow(
/* availabilityStartTimeUs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_123L + 19999877L,
adSequencePattern),
adSequencePattern);
// Advance the window by 1us to add the next period at the end of the window.
timeline.advanceNowUs(1L);
// Assert that the previously first period has been moved out of the live window.
assertThat(timeline.getPeriodCount()).isEqualTo(5);
assertThat(timeline.getWindow(0, window).windowStartTimeMs).isEqualTo(20_000L);
assertThat(timeline.getPeriod(0, period).id).isEqualTo(0);
assertThat(timeline.getPeriod(0, period).uid).isEqualTo("uid-0[c]");
assertThat(timeline.getPeriod(0, period).positionInWindowUs).isEqualTo(-20_000_001L);
assertThat(timeline.getPeriod(1, period).positionInWindowUs).isEqualTo(9_999_999L);
assertThat(timeline.getPeriod(2, period).positionInWindowUs).isEqualTo(19_999_999L);
assertThat(timeline.getPeriod(3, period).positionInWindowUs).isEqualTo(29_999_999L);
assertThat(timeline.getPeriod(4, period).positionInWindowUs).isEqualTo(59_999_999L);
assertThat(timeline.getPeriod(4, period).durationUs).isEqualTo(C.TIME_UNSET);
assertExpectedWindow(
timeline,
calculateExpectedWindow(
/* availabilityStartTimeUs= */ 0L,
/* liveWindowDurationUs= */ 60_000_000L,
/* nowUs= */ 60_000_123L + 19999878L,
adSequencePattern),
adSequencePattern);
}
@Test
public void newInstance_advancedAvailabilityStartTime_correctlyInterpolatedPeriodIds() {
Timeline.Period period = new Timeline.Period();
long availabilityStartTimeUs = 0;
long nowUs = 120_000_123;
long liveWindowDurationUs = 60_000_987L;
boolean[] adSequencePattern = {false, true, true};
long sequenceDurationUs = calculateAdSequencePatternDurationUs(adSequencePattern);
FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline(
availabilityStartTimeUs, liveWindowDurationUs, nowUs, adSequencePattern);
assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs)
.isEqualTo(Util.usToMs(nowUs - liveWindowDurationUs));
assertThat(timeline.getPeriodCount()).isEqualTo(4);
assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).id).isEqualTo(3);
assertThat(timeline.getPeriod(/* periodIndex= */ 0, period).uid).isEqualTo("uid-3[c]");
assertThat(timeline.getPeriod(/* periodIndex= */ 1, period).id).isEqualTo(4);
assertThat(timeline.getPeriod(/* periodIndex= */ 1, period).uid).isEqualTo("uid-4[a]");
assertThat(timeline.getPeriod(/* periodIndex= */ 2, period).id).isEqualTo(5);
assertThat(timeline.getPeriod(/* periodIndex= */ 2, period).uid).isEqualTo("uid-5[a]");
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);
timeline.advanceNowUs(sequenceDurationUs * 13);
assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs)
.isEqualTo(Util.usToMs((nowUs + sequenceDurationUs * 13) - liveWindowDurationUs));
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(timeline.getPeriod(/* periodIndex= */ 1, period).id).isEqualTo((13 * 3) + 4);
assertThat(timeline.getPeriod(/* periodIndex= */ 1, period).uid)
.isEqualTo("uid-" + ((13 * 3) + 4) + "[a]");
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(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,
liveWindowDurationUs,
(nowUs + sequenceDurationUs * 13),
adSequencePattern),
adSequencePattern);
}
@Test
public void newInstance_availabilitySinceAWeekAfterStartOfUnixEpoch_correctLiveWindow() {
long availabilityStartTimeUs = 7 * A_DAY_US;
long nowUs = 18 * A_DAY_US + 135_000_000;
long liveWindowDurationUs = 60_000_000L;
boolean[] adSequencePattern = {false, true, true};
FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline(
availabilityStartTimeUs, liveWindowDurationUs, nowUs, adSequencePattern);
assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs)
.isEqualTo(Util.usToMs(nowUs - liveWindowDurationUs));
assertExpectedWindow(
timeline,
calculateExpectedWindow(
availabilityStartTimeUs, liveWindowDurationUs, nowUs, adSequencePattern),
adSequencePattern);
}
@Test
public void newInstance_adSequencePattern_correctPeriodTypesFromStartOfAvailability() {
FakeMultiPeriodLiveTimeline timeline =
new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0L,
/* liveWindowDurationUs= */ 120_000_000L,
/* nowUs= */ 120_000_000L,
new boolean[] {false, true, true, true});
Timeline.Period period = new Timeline.Period();
Timeline.Window window = new Timeline.Window();
assertThat(timeline.getPeriodCount()).isEqualTo(8);
assertThat(timeline.getWindow(0, window).windowStartTimeMs).isEqualTo(0L);
assertThat(timeline.getPeriod(0, period).positionInWindowUs).isEqualTo(0L);
assertThat(timeline.getPeriod(0, period).uid).isEqualTo("uid-0[c]");
assertThat(timeline.getPeriod(1, period).uid).isEqualTo("uid-1[a]");
assertThat(timeline.getPeriod(2, period).uid).isEqualTo("uid-2[a]");
assertThat(timeline.getPeriod(3, period).uid).isEqualTo("uid-3[a]");
assertThat(timeline.getPeriod(4, period).uid).isEqualTo("uid-4[c]");
assertThat(timeline.getPeriod(5, period).uid).isEqualTo("uid-5[a]");
assertThat(timeline.getPeriod(6, period).uid).isEqualTo("uid-6[a]");
assertThat(timeline.getPeriod(7, period).uid).isEqualTo("uid-7[a]");
timeline.advanceNowUs(40_000_000L);
assertThat(timeline.getPeriodCount()).isEqualTo(8);
assertThat(timeline.getWindow(0, window).windowStartTimeMs).isEqualTo(40_000L);
assertThat(timeline.getPeriod(0, period).positionInWindowUs).isEqualTo(0L);
assertThat(timeline.getPeriod(0, period).uid).isEqualTo("uid-2[a]");
assertThat(timeline.getPeriod(1, period).uid).isEqualTo("uid-3[a]");
assertThat(timeline.getPeriod(2, period).uid).isEqualTo("uid-4[c]");
assertThat(timeline.getPeriod(3, period).uid).isEqualTo("uid-5[a]");
assertThat(timeline.getPeriod(4, period).uid).isEqualTo("uid-6[a]");
assertThat(timeline.getPeriod(5, period).uid).isEqualTo("uid-7[a]");
assertThat(timeline.getPeriod(6, period).uid).isEqualTo("uid-8[c]");
assertThat(timeline.getPeriod(7, period).uid).isEqualTo("uid-9[a]");
timeline =
new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0L,
/* liveWindowDurationUs= */ 220_000_000L,
/* nowUs= */ 250_000_000L,
new boolean[] {false, true, false, true, false});
assertThat(timeline.getPeriodCount()).isEqualTo(10);
assertThat(timeline.getWindow(0, window).windowStartTimeMs).isEqualTo(30_000L);
assertThat(timeline.getPeriod(0, period).positionInWindowUs).isEqualTo(0L);
assertThat(timeline.getPeriod(0, period).uid).isEqualTo("uid-1[a]");
assertThat(timeline.getPeriod(1, period).uid).isEqualTo("uid-2[c]");
assertThat(timeline.getPeriod(2, period).uid).isEqualTo("uid-3[a]");
assertThat(timeline.getPeriod(3, period).uid).isEqualTo("uid-4[c]");
assertThat(timeline.getPeriod(4, period).uid).isEqualTo("uid-5[c]");
assertThat(timeline.getPeriod(5, period).uid).isEqualTo("uid-6[a]");
assertThat(timeline.getPeriod(6, period).uid).isEqualTo("uid-7[c]");
assertThat(timeline.getPeriod(7, period).uid).isEqualTo("uid-8[a]");
assertThat(timeline.getPeriod(8, period).uid).isEqualTo("uid-9[c]");
assertThat(timeline.getPeriod(9, period).uid).isEqualTo("uid-10[c]");
}
private ExpectedWindow calculateExpectedWindow(
long availabilityStartTimeUs,
long liveWindowDurationUs,
long nowUs,
boolean[] adSequencePattern) {
long windowStartTimeUs = nowUs - liveWindowDurationUs;
long sequenceDurationUs = calculateAdSequencePatternDurationUs(adSequencePattern);
long durationBeforeWindowStartUs = windowStartTimeUs - availabilityStartTimeUs;
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;
remainingDurationBeforeWindowUs -= lastSkippedPeriodDurationUs;
}
long positionOfFirstPeriodInWindowUs = 0;
if (remainingDurationBeforeWindowUs < 0) {
// The previous period overlaps into the window, so the window starts in the previous period.
idOfFirstPeriodInWindow--;
// The negative duration of the part of the period that is not overlapping the window.
positionOfFirstPeriodInWindowUs =
-(lastSkippedPeriodDurationUs + remainingDurationBeforeWindowUs);
}
long durationOfFirstPeriodInWindowUs =
adSequencePattern[idOfFirstPeriodInWindow % adSequencePattern.length]
? AD_PERIOD_DURATION_US
: PERIOD_DURATION_US;
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;
}
return new ExpectedWindow(
idOfFirstPeriodInWindow, idOfLastPeriodInWindow, positionOfFirstPeriodInWindowUs);
}
private void assertExpectedWindow(
Timeline timeline, ExpectedWindow expectedWindow, boolean[] adSequencePattern) {
Timeline.Period period = new Timeline.Period();
assertThat(timeline.getPeriodCount())
.isEqualTo(expectedWindow.idOfLastPeriod - expectedWindow.idOfFirstPeriod + 1);
long positionInWindowUs = expectedWindow.positionOfFirstPeriodInWindowUs;
for (int i = 0; i < timeline.getPeriodCount(); i++) {
int id = expectedWindow.idOfFirstPeriod + i;
boolean isAd = adSequencePattern[id % adSequencePattern.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;
}
}
private static class ExpectedWindow {
private final int idOfFirstPeriod;
private final int idOfLastPeriod;
private final long positionOfFirstPeriodInWindowUs;
/** Creates an instance. */
public ExpectedWindow(
int idOfFirstPeriod, int idOfLastPeriod, long positionOfFirstPeriodInWindowUs) {
this.idOfFirstPeriod = idOfFirstPeriod;
this.idOfLastPeriod = idOfLastPeriod;
this.positionOfFirstPeriodInWindowUs = positionOfFirstPeriodInWindowUs;
}
}
}