Commit 28545c66 by bachinger Committed by tonihei

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
parent f2c06fff
/*
* 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 com.google.android.exoplayer2.testutil;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Util.msToUs;
import static com.google.android.exoplayer2.util.Util.usToMs;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Timeline;
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}.
*/
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;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment