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:
parent
198d73425e
commit
1760d63fc4
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user