blob: 19d172d0be77f19d5c0572a9d0e7b9bec7217095 [file] [log] [blame]
/*
* Copyright (C) 2017 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 android.net.Uri;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
/** Fake {@link Timeline} which can be setup to return custom {@link TimelineWindowDefinition}s. */
public final class FakeTimeline extends Timeline {
/**
* Definition used to define a {@link FakeTimeline}.
*/
public static final class TimelineWindowDefinition {
/** Default window duration in microseconds. */
public static final long DEFAULT_WINDOW_DURATION_US = 10 * C.MICROS_PER_SECOND;
/** Default offset of a window in its first period in microseconds. */
public static final long DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US =
10_000 * C.MICROS_PER_SECOND;
public final int periodCount;
public final Object id;
public final boolean isSeekable;
public final boolean isDynamic;
public final boolean isLive;
public final boolean isPlaceholder;
public final long durationUs;
public final long defaultPositionUs;
public final long windowOffsetInFirstPeriodUs;
public final AdPlaybackState adPlaybackState;
/**
* Creates a window definition that corresponds to a dummy placeholder timeline using the given
* tag.
*
* @param tag The tag to use in the timeline.
*/
public static TimelineWindowDefinition createDummy(Object tag) {
return new TimelineWindowDefinition(
/* periodCount= */ 1,
/* id= */ tag,
/* isSeekable= */ false,
/* isDynamic= */ true,
/* isLive= */ false,
/* isPlaceholder= */ true,
/* durationUs= */ C.TIME_UNSET,
/* defaultPositionUs= */ 0,
/* windowOffsetInFirstPeriodUs= */ 0,
AdPlaybackState.NONE);
}
/**
* Creates a seekable, non-dynamic window definition with a duration of {@link
* #DEFAULT_WINDOW_DURATION_US}.
*
* @param periodCount The number of periods in the window. Each period get an equal slice of the
* total window duration.
* @param id The UID of the window.
*/
public TimelineWindowDefinition(int periodCount, Object id) {
this(periodCount, id, true, false, DEFAULT_WINDOW_DURATION_US);
}
/**
* Creates a window definition with one period.
*
* @param isSeekable Whether the window is seekable.
* @param isDynamic Whether the window is dynamic.
* @param durationUs The duration of the window in microseconds.
*/
public TimelineWindowDefinition(boolean isSeekable, boolean isDynamic, long durationUs) {
this(1, 0, isSeekable, isDynamic, durationUs);
}
/**
* Creates a window definition.
*
* @param periodCount The number of periods in the window. Each period get an equal slice of the
* total window duration.
* @param id The UID of the window.
* @param isSeekable Whether the window is seekable.
* @param isDynamic Whether the window is dynamic.
* @param durationUs The duration of the window in microseconds.
*/
public TimelineWindowDefinition(int periodCount, Object id, boolean isSeekable,
boolean isDynamic, long durationUs) {
this(periodCount, id, isSeekable, isDynamic, durationUs, AdPlaybackState.NONE);
}
/**
* Creates a window definition with ad groups.
*
* @param periodCount The number of periods in the window. Each period get an equal slice of the
* total window duration.
* @param id The UID of the window.
* @param isSeekable Whether the window is seekable.
* @param isDynamic Whether the window is dynamic.
* @param durationUs The duration of the window in microseconds.
* @param adPlaybackState The ad playback state.
*/
public TimelineWindowDefinition(
int periodCount,
Object id,
boolean isSeekable,
boolean isDynamic,
long durationUs,
AdPlaybackState adPlaybackState) {
this(
periodCount,
id,
isSeekable,
isDynamic,
/* isLive= */ isDynamic,
/* isPlaceholder= */ false,
durationUs,
/* defaultPositionUs= */ 0,
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
adPlaybackState);
}
/**
* Creates a window definition with ad groups.
*
* @param periodCount The number of periods in the window. Each period get an equal slice of the
* total window duration.
* @param id The UID of the window.
* @param isSeekable Whether the window is seekable.
* @param isDynamic Whether the window is dynamic.
* @param isLive Whether the window is live.
* @param isPlaceholder Whether the window is a placeholder.
* @param durationUs The duration of the window in microseconds.
* @param defaultPositionUs The default position of the window in microseconds.
* @param windowOffsetInFirstPeriodUs The offset of the window in the first period, in
* microseconds.
* @param adPlaybackState The ad playback state.
*/
public TimelineWindowDefinition(
int periodCount,
Object id,
boolean isSeekable,
boolean isDynamic,
boolean isLive,
boolean isPlaceholder,
long durationUs,
long defaultPositionUs,
long windowOffsetInFirstPeriodUs,
AdPlaybackState adPlaybackState) {
Assertions.checkArgument(durationUs != C.TIME_UNSET || periodCount == 1);
this.periodCount = periodCount;
this.id = id;
this.isSeekable = isSeekable;
this.isDynamic = isDynamic;
this.isLive = isLive;
this.isPlaceholder = isPlaceholder;
this.durationUs = durationUs;
this.defaultPositionUs = defaultPositionUs;
this.windowOffsetInFirstPeriodUs = windowOffsetInFirstPeriodUs;
this.adPlaybackState = adPlaybackState;
}
}
private static final long AD_DURATION_US = 10 * C.MICROS_PER_SECOND;
private final TimelineWindowDefinition[] windowDefinitions;
private final Object[] manifests;
private final int[] periodOffsets;
/**
* Returns an ad playback state with the specified number of ads in each of the specified ad
* groups, each ten seconds long.
*
* @param adsPerAdGroup The number of ads per ad group.
* @param adGroupTimesUs The times of ad groups, in microseconds.
* @return The ad playback state.
*/
public static AdPlaybackState createAdPlaybackState(int adsPerAdGroup, long... adGroupTimesUs) {
int adGroupCount = adGroupTimesUs.length;
AdPlaybackState adPlaybackState = new AdPlaybackState(adGroupTimesUs);
long[][] adDurationsUs = new long[adGroupCount][];
for (int i = 0; i < adGroupCount; i++) {
adPlaybackState = adPlaybackState.withAdCount(/* adGroupIndex= */ i, adsPerAdGroup);
for (int j = 0; j < adsPerAdGroup; j++) {
adPlaybackState =
adPlaybackState.withAdUri(
/* adGroupIndex= */ i,
/* adIndexInAdGroup= */ j,
Uri.parse("https://ad/" + i + "/" + j));
}
adDurationsUs[i] = new long[adsPerAdGroup];
Arrays.fill(adDurationsUs[i], AD_DURATION_US);
}
adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs);
return adPlaybackState;
}
/**
* Creates a fake timeline with the given number of seekable, non-dynamic windows with one period
* with a duration of {@link TimelineWindowDefinition#DEFAULT_WINDOW_DURATION_US} each.
*
* @param windowCount The number of windows.
* @param manifests The manifests of the windows.
*/
public FakeTimeline(int windowCount, Object... manifests) {
this(manifests, createDefaultWindowDefinitions(windowCount));
}
/**
* Creates a fake timeline with the given window definitions.
*
* @param windowDefinitions A list of {@link TimelineWindowDefinition}s.
*/
public FakeTimeline(TimelineWindowDefinition... windowDefinitions) {
this(new Object[0], windowDefinitions);
}
/**
* Creates a fake timeline with the given window definitions.
*
* @param windowDefinitions A list of {@link TimelineWindowDefinition}s.
*/
public FakeTimeline(Object[] manifests, TimelineWindowDefinition... windowDefinitions) {
this.manifests = new Object[windowDefinitions.length];
System.arraycopy(
manifests, 0, this.manifests, 0, Math.min(this.manifests.length, manifests.length));
this.windowDefinitions = windowDefinitions;
periodOffsets = new int[windowDefinitions.length + 1];
periodOffsets[0] = 0;
for (int i = 0; i < windowDefinitions.length; i++) {
periodOffsets[i + 1] = periodOffsets[i] + windowDefinitions[i].periodCount;
}
}
@Override
public int getWindowCount() {
return windowDefinitions.length;
}
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
window.set(
/* uid= */ windowDefinition.id,
/* tag= */ windowDefinition.id,
manifests[windowIndex],
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
windowDefinition.isSeekable,
windowDefinition.isDynamic,
windowDefinition.isLive,
windowDefinition.defaultPositionUs,
windowDefinition.durationUs,
periodOffsets[windowIndex],
periodOffsets[windowIndex + 1] - 1,
windowDefinition.windowOffsetInFirstPeriodUs);
window.isPlaceholder = windowDefinition.isPlaceholder;
return window;
}
@Override
public int getPeriodCount() {
return periodOffsets[periodOffsets.length - 1];
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
int windowIndex = Util.binarySearchFloor(periodOffsets, periodIndex, true, false);
int windowPeriodIndex = periodIndex - periodOffsets[windowIndex];
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
Object id = setIds ? windowPeriodIndex : null;
Object uid = setIds ? Pair.create(windowDefinition.id, windowPeriodIndex) : null;
// Arbitrarily set period duration by distributing window duration equally among all periods.
long periodDurationUs =
windowDefinition.durationUs == C.TIME_UNSET
? C.TIME_UNSET
: windowDefinition.durationUs / windowDefinition.periodCount;
long positionInWindowUs;
if (windowPeriodIndex == 0) {
if (windowDefinition.durationUs != C.TIME_UNSET) {
periodDurationUs += windowDefinition.windowOffsetInFirstPeriodUs;
}
positionInWindowUs = -windowDefinition.windowOffsetInFirstPeriodUs;
} else {
positionInWindowUs = periodDurationUs * windowPeriodIndex;
}
return period.set(
id,
uid,
windowIndex,
periodDurationUs,
positionInWindowUs,
windowDefinition.adPlaybackState);
}
@Override
public int getIndexOfPeriod(Object uid) {
for (int i = 0; i < getPeriodCount(); i++) {
if (getUidOfPeriod(i).equals(uid)) {
return i;
}
}
return C.INDEX_UNSET;
}
@Override
public Object getUidOfPeriod(int periodIndex) {
Assertions.checkIndex(periodIndex, 0, getPeriodCount());
int windowIndex =
Util.binarySearchFloor(
periodOffsets, periodIndex, /* inclusive= */ true, /* stayInBounds= */ false);
int windowPeriodIndex = periodIndex - periodOffsets[windowIndex];
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
return Pair.create(windowDefinition.id, windowPeriodIndex);
}
private static TimelineWindowDefinition[] createDefaultWindowDefinitions(int windowCount) {
TimelineWindowDefinition[] windowDefinitions = new TimelineWindowDefinition[windowCount];
for (int i = 0; i < windowCount; i++) {
windowDefinitions[i] = new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ i);
}
return windowDefinitions;
}
}