mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Allow ad groups to specify a resume offset.
Content after ad groups currently always resumes at the ad break position (unless overridden by a seek or similar). In some cases, media inserting ads wants to specify an offset after the ad group at which playback should resume. A common example is a live stream that inserts an ad and then wants to continue streaming at the current live edge. Support this use case by allowing ad groups to specify a content resume offset and making sure that the content start position after the ad group uses this offset. PiperOrigin-RevId: 373393807
This commit is contained in:
parent
a038f875f6
commit
ef5a0b6c4d
6 changed files with 150 additions and 14 deletions
|
|
@ -41,6 +41,7 @@
|
||||||
* Ad playback:
|
* Ad playback:
|
||||||
* Support changing ad break positions in the player logic
|
* Support changing ad break positions in the player logic
|
||||||
([#5067](https://github.com/google/ExoPlayer/issues/5067).
|
([#5067](https://github.com/google/ExoPlayer/issues/5067).
|
||||||
|
* Support resuming content with an offset after an ad group.
|
||||||
* HLS
|
* HLS
|
||||||
* Use the PRECISE attribute in EXT-X-START to select the default start
|
* Use the PRECISE attribute in EXT-X-START to select the default start
|
||||||
position.
|
position.
|
||||||
|
|
|
||||||
|
|
@ -804,6 +804,17 @@ public abstract class Timeline implements Bundleable {
|
||||||
return adPlaybackState.adResumePositionUs;
|
return adPlaybackState.adResumePositionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the offset in microseconds which should be added to the content stream when resuming
|
||||||
|
* playback after the specified ad group.
|
||||||
|
*
|
||||||
|
* @param adGroupIndex The ad group index.
|
||||||
|
* @return The offset that should be added to the content stream, in microseconds.
|
||||||
|
*/
|
||||||
|
public long getContentResumeOffsetUs(int adGroupIndex) {
|
||||||
|
return adPlaybackState.adGroups[adGroupIndex].contentResumeOffsetUs;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object obj) {
|
public boolean equals(@Nullable Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,11 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
@AdState public final int[] states;
|
@AdState public final int[] states;
|
||||||
/** The durations of each ad in the ad group, in microseconds. */
|
/** The durations of each ad in the ad group, in microseconds. */
|
||||||
public final long[] durationsUs;
|
public final long[] durationsUs;
|
||||||
|
/**
|
||||||
|
* The offset in microseconds which should be added to the content stream when resuming playback
|
||||||
|
* after the ad group.
|
||||||
|
*/
|
||||||
|
public final long contentResumeOffsetUs;
|
||||||
|
|
||||||
/** Creates a new ad group with an unspecified number of ads. */
|
/** Creates a new ad group with an unspecified number of ads. */
|
||||||
public AdGroup() {
|
public AdGroup() {
|
||||||
|
|
@ -65,16 +70,22 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
/* count= */ C.LENGTH_UNSET,
|
/* count= */ C.LENGTH_UNSET,
|
||||||
/* states= */ new int[0],
|
/* states= */ new int[0],
|
||||||
/* uris= */ new Uri[0],
|
/* uris= */ new Uri[0],
|
||||||
/* durationsUs= */ new long[0]);
|
/* durationsUs= */ new long[0],
|
||||||
|
/* contentResumeOffsetUs= */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AdGroup(
|
private AdGroup(
|
||||||
int count, @AdState int[] states, @NullableType Uri[] uris, long[] durationsUs) {
|
int count,
|
||||||
|
@AdState int[] states,
|
||||||
|
@NullableType Uri[] uris,
|
||||||
|
long[] durationsUs,
|
||||||
|
long contentResumeOffsetUs) {
|
||||||
checkArgument(states.length == uris.length);
|
checkArgument(states.length == uris.length);
|
||||||
this.count = count;
|
this.count = count;
|
||||||
this.states = states;
|
this.states = states;
|
||||||
this.uris = uris;
|
this.uris = uris;
|
||||||
this.durationsUs = durationsUs;
|
this.durationsUs = durationsUs;
|
||||||
|
this.contentResumeOffsetUs = contentResumeOffsetUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -118,7 +129,8 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
return count == adGroup.count
|
return count == adGroup.count
|
||||||
&& Arrays.equals(uris, adGroup.uris)
|
&& Arrays.equals(uris, adGroup.uris)
|
||||||
&& Arrays.equals(states, adGroup.states)
|
&& Arrays.equals(states, adGroup.states)
|
||||||
&& Arrays.equals(durationsUs, adGroup.durationsUs);
|
&& Arrays.equals(durationsUs, adGroup.durationsUs)
|
||||||
|
&& contentResumeOffsetUs == adGroup.contentResumeOffsetUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -127,6 +139,7 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
result = 31 * result + Arrays.hashCode(uris);
|
result = 31 * result + Arrays.hashCode(uris);
|
||||||
result = 31 * result + Arrays.hashCode(states);
|
result = 31 * result + Arrays.hashCode(states);
|
||||||
result = 31 * result + Arrays.hashCode(durationsUs);
|
result = 31 * result + Arrays.hashCode(durationsUs);
|
||||||
|
result = 31 * result + (int) (contentResumeOffsetUs ^ (contentResumeOffsetUs >>> 32));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +149,7 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, count);
|
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, count);
|
||||||
long[] durationsUs = copyDurationsUsWithSpaceForAdCount(this.durationsUs, count);
|
long[] durationsUs = copyDurationsUsWithSpaceForAdCount(this.durationsUs, count);
|
||||||
@NullableType Uri[] uris = Arrays.copyOf(this.uris, count);
|
@NullableType Uri[] uris = Arrays.copyOf(this.uris, count);
|
||||||
return new AdGroup(count, states, uris, durationsUs);
|
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -153,7 +166,7 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
@NullableType Uri[] uris = Arrays.copyOf(this.uris, states.length);
|
@NullableType Uri[] uris = Arrays.copyOf(this.uris, states.length);
|
||||||
uris[index] = uri;
|
uris[index] = uri;
|
||||||
states[index] = AD_STATE_AVAILABLE;
|
states[index] = AD_STATE_AVAILABLE;
|
||||||
return new AdGroup(count, states, uris, durationsUs);
|
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -180,7 +193,7 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
Uri[] uris =
|
Uri[] uris =
|
||||||
this.uris.length == states.length ? this.uris : Arrays.copyOf(this.uris, states.length);
|
this.uris.length == states.length ? this.uris : Arrays.copyOf(this.uris, states.length);
|
||||||
states[index] = state;
|
states[index] = state;
|
||||||
return new AdGroup(count, states, uris, durationsUs);
|
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a new instance with the specified ad durations, in microseconds. */
|
/** Returns a new instance with the specified ad durations, in microseconds. */
|
||||||
|
|
@ -191,7 +204,13 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
} else if (count != C.LENGTH_UNSET && durationsUs.length > uris.length) {
|
} else if (count != C.LENGTH_UNSET && durationsUs.length > uris.length) {
|
||||||
durationsUs = Arrays.copyOf(durationsUs, uris.length);
|
durationsUs = Arrays.copyOf(durationsUs, uris.length);
|
||||||
}
|
}
|
||||||
return new AdGroup(count, states, uris, durationsUs);
|
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns an instance with the specified {@link #contentResumeOffsetUs}. */
|
||||||
|
@CheckResult
|
||||||
|
public AdGroup withContentResumeOffsetUs(long contentResumeOffsetUs) {
|
||||||
|
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -205,7 +224,8 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
/* count= */ 0,
|
/* count= */ 0,
|
||||||
/* states= */ new int[0],
|
/* states= */ new int[0],
|
||||||
/* uris= */ new Uri[0],
|
/* uris= */ new Uri[0],
|
||||||
/* durationsUs= */ new long[0]);
|
/* durationsUs= */ new long[0],
|
||||||
|
contentResumeOffsetUs);
|
||||||
}
|
}
|
||||||
int count = this.states.length;
|
int count = this.states.length;
|
||||||
@AdState int[] states = Arrays.copyOf(this.states, count);
|
@AdState int[] states = Arrays.copyOf(this.states, count);
|
||||||
|
|
@ -214,7 +234,7 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
states[i] = AD_STATE_SKIPPED;
|
states[i] = AD_STATE_SKIPPED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new AdGroup(count, states, uris, durationsUs);
|
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
|
|
@ -239,13 +259,20 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
|
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({FIELD_COUNT, FIELD_URIS, FIELD_STATES, FIELD_DURATIONS_US})
|
@IntDef({
|
||||||
|
FIELD_COUNT,
|
||||||
|
FIELD_URIS,
|
||||||
|
FIELD_STATES,
|
||||||
|
FIELD_DURATIONS_US,
|
||||||
|
FIELD_CONTENT_RESUME_OFFSET_US,
|
||||||
|
})
|
||||||
private @interface FieldNumber {}
|
private @interface FieldNumber {}
|
||||||
|
|
||||||
private static final int FIELD_COUNT = 0;
|
private static final int FIELD_COUNT = 0;
|
||||||
private static final int FIELD_URIS = 1;
|
private static final int FIELD_URIS = 1;
|
||||||
private static final int FIELD_STATES = 2;
|
private static final int FIELD_STATES = 2;
|
||||||
private static final int FIELD_DURATIONS_US = 3;
|
private static final int FIELD_DURATIONS_US = 3;
|
||||||
|
private static final int FIELD_CONTENT_RESUME_OFFSET_US = 4;
|
||||||
|
|
||||||
// putParcelableArrayList actually supports null elements.
|
// putParcelableArrayList actually supports null elements.
|
||||||
@SuppressWarnings("nullness:argument.type.incompatible")
|
@SuppressWarnings("nullness:argument.type.incompatible")
|
||||||
|
|
@ -257,6 +284,7 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
keyForField(FIELD_URIS), new ArrayList<@NullableType Uri>(Arrays.asList(uris)));
|
keyForField(FIELD_URIS), new ArrayList<@NullableType Uri>(Arrays.asList(uris)));
|
||||||
bundle.putIntArray(keyForField(FIELD_STATES), states);
|
bundle.putIntArray(keyForField(FIELD_STATES), states);
|
||||||
bundle.putLongArray(keyForField(FIELD_DURATIONS_US), durationsUs);
|
bundle.putLongArray(keyForField(FIELD_DURATIONS_US), durationsUs);
|
||||||
|
bundle.putLong(keyForField(FIELD_CONTENT_RESUME_OFFSET_US), contentResumeOffsetUs);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,11 +301,13 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
@AdState
|
@AdState
|
||||||
int[] states = bundle.getIntArray(keyForField(FIELD_STATES));
|
int[] states = bundle.getIntArray(keyForField(FIELD_STATES));
|
||||||
@Nullable long[] durationsUs = bundle.getLongArray(keyForField(FIELD_DURATIONS_US));
|
@Nullable long[] durationsUs = bundle.getLongArray(keyForField(FIELD_DURATIONS_US));
|
||||||
|
long contentResumeOffsetUs = bundle.getLong(keyForField(FIELD_CONTENT_RESUME_OFFSET_US));
|
||||||
return new AdGroup(
|
return new AdGroup(
|
||||||
count,
|
count,
|
||||||
states == null ? new int[0] : states,
|
states == null ? new int[0] : states,
|
||||||
uriList == null ? new Uri[0] : uriList.toArray(new Uri[0]),
|
uriList == null ? new Uri[0] : uriList.toArray(new Uri[0]),
|
||||||
durationsUs == null ? new long[0] : durationsUs);
|
durationsUs == null ? new long[0] : durationsUs,
|
||||||
|
contentResumeOffsetUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@AdGroup.FieldNumber int field) {
|
private static String keyForField(@AdGroup.FieldNumber int field) {
|
||||||
|
|
@ -559,6 +589,22 @@ public final class AdPlaybackState implements Bundleable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance with the specified {@link AdGroup#contentResumeOffsetUs}, in microseconds,
|
||||||
|
* for the specified ad group.
|
||||||
|
*/
|
||||||
|
@CheckResult
|
||||||
|
public AdPlaybackState withContentResumeOffsetUs(int adGroupIndex, long contentResumeOffsetUs) {
|
||||||
|
if (adGroups[adGroupIndex].contentResumeOffsetUs == contentResumeOffsetUs) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length);
|
||||||
|
adGroups[adGroupIndex] =
|
||||||
|
adGroups[adGroupIndex].withContentResumeOffsetUs(contentResumeOffsetUs);
|
||||||
|
return new AdPlaybackState(
|
||||||
|
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object o) {
|
public boolean equals(@Nullable Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,8 @@ public class AdPlaybackStateTest {
|
||||||
.withPlayedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1)
|
.withPlayedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1)
|
||||||
.withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, TEST_URI)
|
.withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, TEST_URI)
|
||||||
.withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1, TEST_URI)
|
.withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1, TEST_URI)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 4444)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 3333)
|
||||||
.withAdDurationsUs(new long[][] {{12}, {34, 56}})
|
.withAdDurationsUs(new long[][] {{12}, {34, 56}})
|
||||||
.withAdResumePositionUs(123)
|
.withAdResumePositionUs(123)
|
||||||
.withContentDurationUs(456);
|
.withContentDurationUs(456);
|
||||||
|
|
@ -216,7 +218,8 @@ public class AdPlaybackStateTest {
|
||||||
.withAdState(AD_STATE_PLAYED, /* index= */ 1)
|
.withAdState(AD_STATE_PLAYED, /* index= */ 1)
|
||||||
.withAdUri(Uri.parse("https://www.google.com"), /* index= */ 0)
|
.withAdUri(Uri.parse("https://www.google.com"), /* index= */ 0)
|
||||||
.withAdUri(Uri.EMPTY, /* index= */ 1)
|
.withAdUri(Uri.EMPTY, /* index= */ 1)
|
||||||
.withAdDurationsUs(new long[] {1234, 5678});
|
.withAdDurationsUs(new long[] {1234, 5678})
|
||||||
|
.withContentResumeOffsetUs(4444);
|
||||||
|
|
||||||
assertThat(AdPlaybackState.AdGroup.CREATOR.fromBundle(adGroup.toBundle())).isEqualTo(adGroup);
|
assertThat(AdPlaybackState.AdGroup.CREATOR.fromBundle(adGroup.toBundle())).isEqualTo(adGroup);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -703,10 +703,13 @@ import com.google.common.collect.ImmutableList;
|
||||||
}
|
}
|
||||||
startPositionUs = defaultPosition.second;
|
startPositionUs = defaultPosition.second;
|
||||||
}
|
}
|
||||||
|
long minStartPositionUs =
|
||||||
|
getMinStartPositionAfterAdGroupUs(
|
||||||
|
timeline, currentPeriodId.periodUid, currentPeriodId.adGroupIndex);
|
||||||
return getMediaPeriodInfoForContent(
|
return getMediaPeriodInfoForContent(
|
||||||
timeline,
|
timeline,
|
||||||
currentPeriodId.periodUid,
|
currentPeriodId.periodUid,
|
||||||
startPositionUs,
|
max(minStartPositionUs, startPositionUs),
|
||||||
mediaPeriodInfo.requestedContentPositionUs,
|
mediaPeriodInfo.requestedContentPositionUs,
|
||||||
currentPeriodId.windowSequenceNumber);
|
currentPeriodId.windowSequenceNumber);
|
||||||
}
|
}
|
||||||
|
|
@ -715,10 +718,13 @@ import com.google.common.collect.ImmutableList;
|
||||||
int adIndexInAdGroup = period.getFirstAdIndexToPlay(currentPeriodId.nextAdGroupIndex);
|
int adIndexInAdGroup = period.getFirstAdIndexToPlay(currentPeriodId.nextAdGroupIndex);
|
||||||
if (adIndexInAdGroup == period.getAdCountInAdGroup(currentPeriodId.nextAdGroupIndex)) {
|
if (adIndexInAdGroup == period.getAdCountInAdGroup(currentPeriodId.nextAdGroupIndex)) {
|
||||||
// The next ad group has no ads left to play. Play content from the end position instead.
|
// The next ad group has no ads left to play. Play content from the end position instead.
|
||||||
|
long startPositionUs =
|
||||||
|
getMinStartPositionAfterAdGroupUs(
|
||||||
|
timeline, currentPeriodId.periodUid, currentPeriodId.nextAdGroupIndex);
|
||||||
return getMediaPeriodInfoForContent(
|
return getMediaPeriodInfoForContent(
|
||||||
timeline,
|
timeline,
|
||||||
currentPeriodId.periodUid,
|
currentPeriodId.periodUid,
|
||||||
/* startPositionUs= */ mediaPeriodInfo.durationUs,
|
startPositionUs,
|
||||||
/* requestedContentPositionUs= */ mediaPeriodInfo.durationUs,
|
/* requestedContentPositionUs= */ mediaPeriodInfo.durationUs,
|
||||||
currentPeriodId.windowSequenceNumber);
|
currentPeriodId.windowSequenceNumber);
|
||||||
}
|
}
|
||||||
|
|
@ -842,4 +848,14 @@ import com.google.common.collect.ImmutableList;
|
||||||
&& timeline.isLastPeriod(periodIndex, period, window, repeatMode, shuffleModeEnabled)
|
&& timeline.isLastPeriod(periodIndex, period, window, repeatMode, shuffleModeEnabled)
|
||||||
&& isLastMediaPeriodInPeriod;
|
&& isLastMediaPeriodInPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long getMinStartPositionAfterAdGroupUs(
|
||||||
|
Timeline timeline, Object periodUid, int adGroupIndex) {
|
||||||
|
timeline.getPeriodByUid(periodUid, period);
|
||||||
|
long startPositionUs = period.getAdGroupTimeUs(adGroupIndex);
|
||||||
|
if (startPositionUs == C.TIME_END_OF_SOURCE) {
|
||||||
|
return period.durationUs;
|
||||||
|
}
|
||||||
|
return startPositionUs + period.getContentResumeOffsetUs(adGroupIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,65 @@ public final class MediaPeriodQueueTest {
|
||||||
/* nextAdGroupIndex= */ C.INDEX_UNSET);
|
/* nextAdGroupIndex= */ C.INDEX_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getNextMediaPeriodInfo_withAdGroupResumeOffsets_returnsCorrectMediaPeriodInfos() {
|
||||||
|
adPlaybackState =
|
||||||
|
new AdPlaybackState(
|
||||||
|
/* adsId= */ new Object(),
|
||||||
|
/* adGroupTimesUs...= */ 0,
|
||||||
|
FIRST_AD_START_TIME_US,
|
||||||
|
C.TIME_END_OF_SOURCE)
|
||||||
|
.withContentDurationUs(CONTENT_DURATION_US)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 2000)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 3000)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 4000);
|
||||||
|
SinglePeriodAdTimeline adTimeline =
|
||||||
|
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||||
|
setupTimeline(adTimeline);
|
||||||
|
|
||||||
|
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||||
|
assertNextMediaPeriodInfoIsAd(
|
||||||
|
/* adGroupIndex= */ 0, AD_DURATION_US, /* contentPositionUs= */ C.TIME_UNSET);
|
||||||
|
advance();
|
||||||
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
|
/* periodUid= */ firstPeriodUid,
|
||||||
|
/* startPositionUs= */ 2000,
|
||||||
|
/* requestedContentPositionUs= */ C.TIME_UNSET,
|
||||||
|
/* endPositionUs= */ FIRST_AD_START_TIME_US,
|
||||||
|
/* durationUs= */ FIRST_AD_START_TIME_US,
|
||||||
|
/* isLastInPeriod= */ false,
|
||||||
|
/* isLastInWindow= */ false,
|
||||||
|
/* nextAdGroupIndex= */ 1);
|
||||||
|
advance();
|
||||||
|
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
||||||
|
assertNextMediaPeriodInfoIsAd(
|
||||||
|
/* adGroupIndex= */ 1, AD_DURATION_US, /* contentPositionUs= */ FIRST_AD_START_TIME_US);
|
||||||
|
advance();
|
||||||
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
|
/* periodUid= */ firstPeriodUid,
|
||||||
|
/* startPositionUs= */ FIRST_AD_START_TIME_US + 3000,
|
||||||
|
/* requestedContentPositionUs= */ FIRST_AD_START_TIME_US,
|
||||||
|
/* endPositionUs= */ C.TIME_END_OF_SOURCE,
|
||||||
|
/* durationUs= */ CONTENT_DURATION_US,
|
||||||
|
/* isLastInPeriod= */ false,
|
||||||
|
/* isLastInWindow= */ false,
|
||||||
|
/* nextAdGroupIndex= */ 2);
|
||||||
|
advance();
|
||||||
|
setAdGroupLoaded(/* adGroupIndex= */ 2);
|
||||||
|
assertNextMediaPeriodInfoIsAd(
|
||||||
|
/* adGroupIndex= */ 2, AD_DURATION_US, /* contentPositionUs= */ CONTENT_DURATION_US);
|
||||||
|
advance();
|
||||||
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
|
/* periodUid= */ firstPeriodUid,
|
||||||
|
/* startPositionUs= */ CONTENT_DURATION_US - 1,
|
||||||
|
/* requestedContentPositionUs= */ CONTENT_DURATION_US,
|
||||||
|
/* endPositionUs= */ C.TIME_UNSET,
|
||||||
|
/* durationUs= */ CONTENT_DURATION_US,
|
||||||
|
/* isLastInPeriod= */ true,
|
||||||
|
/* isLastInWindow= */ true,
|
||||||
|
/* nextAdGroupIndex= */ C.INDEX_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getNextMediaPeriodInfo_withPostrollLoadError_returnsEmptyFinalMediaPeriodInfo() {
|
public void getNextMediaPeriodInfo_withPostrollLoadError_returnsEmptyFinalMediaPeriodInfo() {
|
||||||
setupAdTimeline(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE);
|
setupAdTimeline(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue