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