Support changing ad break positions.
The player already supports changing durations of periods and ads. The only thing not yet supported is a change in ad break positions which changes the duration of clipped content ending in an ad break. Adding support for this requires updating the end position in MediaPeriodInfo and changing the clip end position of the respective ClippingMediaPeriod. Issue: #5067 PiperOrigin-RevId: 373139724
This commit is contained in:
parent
0a93cc23f0
commit
08fb7bd65f
@ -33,6 +33,9 @@
|
|||||||
* Remove `CastPlayer` specific playlist manipulation methods. Use
|
* Remove `CastPlayer` specific playlist manipulation methods. Use
|
||||||
`setMediaItems`, `addMediaItems`, `removeMediaItem` and `moveMediaItem`
|
`setMediaItems`, `addMediaItems`, `removeMediaItem` and `moveMediaItem`
|
||||||
instead.
|
instead.
|
||||||
|
* Ad playback:
|
||||||
|
* Support changing ad break positions in the player logic
|
||||||
|
([#5067](https://github.com/google/ExoPlayer/issues/5067).
|
||||||
|
|
||||||
### 2.14.0 (2021-05-13)
|
### 2.14.0 (2021-05-13)
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.source.ads;
|
package com.google.android.exoplayer2.source.ads;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
||||||
|
import static com.google.common.collect.ObjectArrays.concat;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -315,7 +316,7 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
new AdPlaybackState(
|
new AdPlaybackState(
|
||||||
/* adsId= */ null,
|
/* adsId= */ null,
|
||||||
/* adGroupTimesUs= */ new long[0],
|
/* adGroupTimesUs= */ new long[0],
|
||||||
/* adGroups= */ null,
|
/* adGroups= */ new AdGroup[0],
|
||||||
/* adResumePositionUs= */ 0L,
|
/* adResumePositionUs= */ 0L,
|
||||||
/* contentDurationUs= */ C.TIME_UNSET);
|
/* contentDurationUs= */ C.TIME_UNSET);
|
||||||
|
|
||||||
@ -353,7 +354,7 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
this(
|
this(
|
||||||
adsId,
|
adsId,
|
||||||
adGroupTimesUs,
|
adGroupTimesUs,
|
||||||
/* adGroups= */ null,
|
createEmptyAdGroups(adGroupTimesUs.length),
|
||||||
/* adResumePositionUs= */ 0,
|
/* adResumePositionUs= */ 0,
|
||||||
/* contentDurationUs= */ C.TIME_UNSET);
|
/* contentDurationUs= */ C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
@ -361,21 +362,15 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
private AdPlaybackState(
|
private AdPlaybackState(
|
||||||
@Nullable Object adsId,
|
@Nullable Object adsId,
|
||||||
long[] adGroupTimesUs,
|
long[] adGroupTimesUs,
|
||||||
@Nullable AdGroup[] adGroups,
|
AdGroup[] adGroups,
|
||||||
long adResumePositionUs,
|
long adResumePositionUs,
|
||||||
long contentDurationUs) {
|
long contentDurationUs) {
|
||||||
checkArgument(adGroups == null || adGroups.length == adGroupTimesUs.length);
|
checkArgument(adGroups.length == adGroupTimesUs.length);
|
||||||
this.adsId = adsId;
|
this.adsId = adsId;
|
||||||
this.adGroupTimesUs = adGroupTimesUs;
|
this.adGroupTimesUs = adGroupTimesUs;
|
||||||
this.adResumePositionUs = adResumePositionUs;
|
this.adResumePositionUs = adResumePositionUs;
|
||||||
this.contentDurationUs = contentDurationUs;
|
this.contentDurationUs = contentDurationUs;
|
||||||
adGroupCount = adGroupTimesUs.length;
|
adGroupCount = adGroupTimesUs.length;
|
||||||
if (adGroups == null) {
|
|
||||||
adGroups = new AdGroup[adGroupCount];
|
|
||||||
for (int i = 0; i < adGroupCount; i++) {
|
|
||||||
adGroups[i] = new AdGroup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.adGroups = adGroups;
|
this.adGroups = adGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,6 +435,30 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
return adGroup.states[adIndexInAdGroup] == AdPlaybackState.AD_STATE_ERROR;
|
return adGroup.states[adIndexInAdGroup] == AdPlaybackState.AD_STATE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance with the specified ad group times.
|
||||||
|
*
|
||||||
|
* <p>If the number of ad groups differs, ad groups are either removed or empty ad groups are
|
||||||
|
* added.
|
||||||
|
*
|
||||||
|
* @param adGroupTimesUs The new ad group times, in microseconds.
|
||||||
|
* @return The updated ad playback state.
|
||||||
|
*/
|
||||||
|
@CheckResult
|
||||||
|
public AdPlaybackState withAdGroupTimesUs(long[] adGroupTimesUs) {
|
||||||
|
AdGroup[] adGroups =
|
||||||
|
adGroupTimesUs.length < adGroupCount
|
||||||
|
? Util.nullSafeArrayCopy(this.adGroups, adGroupTimesUs.length)
|
||||||
|
: adGroupTimesUs.length == adGroupCount
|
||||||
|
? this.adGroups
|
||||||
|
: concat(
|
||||||
|
this.adGroups,
|
||||||
|
createEmptyAdGroups(adGroupTimesUs.length - adGroupCount),
|
||||||
|
AdGroup.class);
|
||||||
|
return new AdPlaybackState(
|
||||||
|
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an instance with the number of ads in {@code adGroupIndex} resolved to {@code adCount}.
|
* Returns an instance with the number of ads in {@code adGroupIndex} resolved to {@code adCount}.
|
||||||
* The ad count must be greater than zero.
|
* The ad count must be greater than zero.
|
||||||
@ -679,12 +698,15 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
|
|
||||||
private static AdPlaybackState fromBundle(Bundle bundle) {
|
private static AdPlaybackState fromBundle(Bundle bundle) {
|
||||||
@Nullable long[] adGroupTimesUs = bundle.getLongArray(keyForField(FIELD_AD_GROUP_TIMES_US));
|
@Nullable long[] adGroupTimesUs = bundle.getLongArray(keyForField(FIELD_AD_GROUP_TIMES_US));
|
||||||
|
if (adGroupTimesUs == null) {
|
||||||
|
adGroupTimesUs = new long[0];
|
||||||
|
}
|
||||||
@Nullable
|
@Nullable
|
||||||
ArrayList<Bundle> adGroupBundleList =
|
ArrayList<Bundle> adGroupBundleList =
|
||||||
bundle.getParcelableArrayList(keyForField(FIELD_AD_GROUPS));
|
bundle.getParcelableArrayList(keyForField(FIELD_AD_GROUPS));
|
||||||
@Nullable AdGroup[] adGroups;
|
@Nullable AdGroup[] adGroups;
|
||||||
if (adGroupBundleList == null) {
|
if (adGroupBundleList == null) {
|
||||||
adGroups = null;
|
adGroups = createEmptyAdGroups(adGroupTimesUs.length);
|
||||||
} else {
|
} else {
|
||||||
adGroups = new AdGroup[adGroupBundleList.size()];
|
adGroups = new AdGroup[adGroupBundleList.size()];
|
||||||
for (int i = 0; i < adGroupBundleList.size(); i++) {
|
for (int i = 0; i < adGroupBundleList.size(); i++) {
|
||||||
@ -696,14 +718,18 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
long contentDurationUs =
|
long contentDurationUs =
|
||||||
bundle.getLong(keyForField(FIELD_CONTENT_DURATION_US), /* defaultValue= */ C.TIME_UNSET);
|
bundle.getLong(keyForField(FIELD_CONTENT_DURATION_US), /* defaultValue= */ C.TIME_UNSET);
|
||||||
return new AdPlaybackState(
|
return new AdPlaybackState(
|
||||||
/* adsId= */ null,
|
/* adsId= */ null, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
|
||||||
adGroupTimesUs == null ? new long[0] : adGroupTimesUs,
|
|
||||||
adGroups,
|
|
||||||
adResumePositionUs,
|
|
||||||
contentDurationUs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
private static String keyForField(@FieldNumber int field) {
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
return Integer.toString(field, Character.MAX_RADIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AdGroup[] createEmptyAdGroups(int count) {
|
||||||
|
AdGroup[] adGroups = new AdGroup[count];
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
adGroups[i] = new AdGroup();
|
||||||
|
}
|
||||||
|
return adGroups;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,38 @@ public class AdPlaybackStateTest {
|
|||||||
assertThat(state.isAdInErrorState(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1)).isFalse();
|
assertThat(state.isAdInErrorState(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withAdGroupTimesUs_removingGroups_keepsRemainingGroups() {
|
||||||
|
AdPlaybackState state =
|
||||||
|
new AdPlaybackState(TEST_ADS_ID, new long[] {0, C.msToUs(10_000)})
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
|
||||||
|
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI);
|
||||||
|
|
||||||
|
state = state.withAdGroupTimesUs(new long[] {C.msToUs(3_000)});
|
||||||
|
|
||||||
|
assertThat(state.adGroupCount).isEqualTo(1);
|
||||||
|
assertThat(state.adGroups[0].count).isEqualTo(2);
|
||||||
|
assertThat(state.adGroups[0].uris[1]).isSameInstanceAs(TEST_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withAdGroupTimesUs_addingGroups_keepsExistingGroups() {
|
||||||
|
AdPlaybackState state =
|
||||||
|
new AdPlaybackState(TEST_ADS_ID, new long[] {0, C.msToUs(10_000)})
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
|
||||||
|
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI)
|
||||||
|
.withSkippedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0);
|
||||||
|
|
||||||
|
state = state.withAdGroupTimesUs(new long[] {0, C.msToUs(3_000), C.msToUs(20_000)});
|
||||||
|
|
||||||
|
assertThat(state.adGroupCount).isEqualTo(3);
|
||||||
|
assertThat(state.adGroups[0].count).isEqualTo(2);
|
||||||
|
assertThat(state.adGroups[0].uris[1]).isSameInstanceAs(TEST_URI);
|
||||||
|
assertThat(state.adGroups[1].states[0]).isEqualTo(AdPlaybackState.AD_STATE_SKIPPED);
|
||||||
|
assertThat(state.adGroups[2].count).isEqualTo(C.INDEX_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getFirstAdIndexToPlayIsZero() {
|
public void getFirstAdIndexToPlayIsZero() {
|
||||||
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3);
|
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3);
|
||||||
|
@ -1799,6 +1799,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
// Update the new playing media period info if it already exists.
|
// Update the new playing media period info if it already exists.
|
||||||
if (periodHolder.info.id.equals(newPeriodId)) {
|
if (periodHolder.info.id.equals(newPeriodId)) {
|
||||||
periodHolder.info = queue.getUpdatedMediaPeriodInfo(timeline, periodHolder.info);
|
periodHolder.info = queue.getUpdatedMediaPeriodInfo(timeline, periodHolder.info);
|
||||||
|
periodHolder.updateClipping();
|
||||||
}
|
}
|
||||||
periodHolder = periodHolder.getNext();
|
periodHolder = periodHolder.getNext();
|
||||||
}
|
}
|
||||||
@ -2127,7 +2128,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
Renderer renderer = renderers[i];
|
Renderer renderer = renderers[i];
|
||||||
SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
|
SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
|
||||||
if (renderer.getStream() != sampleStream
|
if (renderer.getStream() != sampleStream
|
||||||
|| (sampleStream != null && !renderer.hasReadStreamToEnd())) {
|
|| (sampleStream != null
|
||||||
|
&& !renderer.hasReadStreamToEnd()
|
||||||
|
&& !hasFinishedReadingClippedContent(renderer, readingPeriodHolder))) {
|
||||||
// The current reading period is still being read by at least one renderer.
|
// The current reading period is still being read by at least one renderer.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -2135,6 +2138,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasFinishedReadingClippedContent(Renderer renderer, MediaPeriodHolder reading) {
|
||||||
|
MediaPeriodHolder nextPeriod = reading.getNext();
|
||||||
|
// We can advance the reading period early once the clipped content has been read beyond its
|
||||||
|
// clipped end time because we know there won't be any further samples. This shortcut is helpful
|
||||||
|
// in case the clipped end time was reduced and renderers already read beyond the new end time.
|
||||||
|
// But wait until the next period is actually prepared to allow a seamless transition.
|
||||||
|
return reading.info.id.nextAdGroupIndex != C.INDEX_UNSET
|
||||||
|
&& nextPeriod.prepared
|
||||||
|
&& renderer.getReadingPositionUs() >= nextPeriod.getStartPositionRendererTime();
|
||||||
|
}
|
||||||
|
|
||||||
private void setAllRendererStreamsFinal(long streamEndPositionUs) {
|
private void setAllRendererStreamsFinal(long streamEndPositionUs) {
|
||||||
for (Renderer renderer : renderers) {
|
for (Renderer renderer : renderers) {
|
||||||
if (renderer.getStream() != null) {
|
if (renderer.getStream() != null) {
|
||||||
@ -2587,12 +2601,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
// Drop update if we keep playing the same content (MediaPeriod.periodUid are identical) and
|
// Drop update if we keep playing the same content (MediaPeriod.periodUid are identical) and
|
||||||
// the only change is that MediaPeriodId.nextAdGroupIndex increased. This postpones a potential
|
// the only change is that MediaPeriodId.nextAdGroupIndex increased. This postpones a potential
|
||||||
// discontinuity until we reach the former next ad group position.
|
// discontinuity until we reach the former next ad group position.
|
||||||
boolean oldAndNewPeriodIdAreSame =
|
boolean onlyNextAdGroupIndexIncreased =
|
||||||
oldPeriodId.periodUid.equals(newPeriodUid)
|
oldPeriodId.periodUid.equals(newPeriodUid)
|
||||||
&& !oldPeriodId.isAd()
|
&& !oldPeriodId.isAd()
|
||||||
&& !periodIdWithAds.isAd()
|
&& !periodIdWithAds.isAd()
|
||||||
&& earliestCuePointIsUnchangedOrLater;
|
&& earliestCuePointIsUnchangedOrLater;
|
||||||
MediaPeriodId newPeriodId = oldAndNewPeriodIdAreSame ? oldPeriodId : periodIdWithAds;
|
MediaPeriodId newPeriodId = onlyNextAdGroupIndexIncreased ? oldPeriodId : periodIdWithAds;
|
||||||
|
|
||||||
long periodPositionUs = contentPositionForAdResolutionUs;
|
long periodPositionUs = contentPositionForAdResolutionUs;
|
||||||
if (newPeriodId.isAd()) {
|
if (newPeriodId.isAd()) {
|
||||||
|
@ -320,7 +320,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
/** Releases the media period. No other method should be called after the release. */
|
/** Releases the media period. No other method should be called after the release. */
|
||||||
public void release() {
|
public void release() {
|
||||||
disableTrackSelectionsInResult();
|
disableTrackSelectionsInResult();
|
||||||
releaseMediaPeriod(info.endPositionUs, mediaSourceList, mediaPeriod);
|
releaseMediaPeriod(mediaSourceList, mediaPeriod);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -357,6 +357,15 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
return trackSelectorResult;
|
return trackSelectorResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Updates the clipping to {@link MediaPeriodInfo#endPositionUs} if required. */
|
||||||
|
public void updateClipping() {
|
||||||
|
if (mediaPeriod instanceof ClippingMediaPeriod) {
|
||||||
|
long endPositionUs =
|
||||||
|
info.endPositionUs == C.TIME_UNSET ? C.TIME_END_OF_SOURCE : info.endPositionUs;
|
||||||
|
((ClippingMediaPeriod) mediaPeriod).updateClipping(/* startUs= */ 0, endPositionUs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void enableTrackSelectionsInResult() {
|
private void enableTrackSelectionsInResult() {
|
||||||
if (!isLoadingMediaPeriod()) {
|
if (!isLoadingMediaPeriod()) {
|
||||||
return;
|
return;
|
||||||
@ -422,7 +431,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
long startPositionUs,
|
long startPositionUs,
|
||||||
long endPositionUs) {
|
long endPositionUs) {
|
||||||
MediaPeriod mediaPeriod = mediaSourceList.createPeriod(id, allocator, startPositionUs);
|
MediaPeriod mediaPeriod = mediaSourceList.createPeriod(id, allocator, startPositionUs);
|
||||||
if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
|
if (endPositionUs != C.TIME_UNSET) {
|
||||||
mediaPeriod =
|
mediaPeriod =
|
||||||
new ClippingMediaPeriod(
|
new ClippingMediaPeriod(
|
||||||
mediaPeriod, /* enableInitialDiscontinuity= */ true, /* startUs= */ 0, endPositionUs);
|
mediaPeriod, /* enableInitialDiscontinuity= */ true, /* startUs= */ 0, endPositionUs);
|
||||||
@ -431,10 +440,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */
|
/** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */
|
||||||
private static void releaseMediaPeriod(
|
private static void releaseMediaPeriod(MediaSourceList mediaSourceList, MediaPeriod mediaPeriod) {
|
||||||
long endPositionUs, MediaSourceList mediaSourceList, MediaPeriod mediaPeriod) {
|
|
||||||
try {
|
try {
|
||||||
if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
|
if (mediaPeriod instanceof ClippingMediaPeriod) {
|
||||||
mediaSourceList.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
|
mediaSourceList.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
|
||||||
} else {
|
} else {
|
||||||
mediaSourceList.releasePeriod(mediaPeriod);
|
mediaSourceList.releasePeriod(mediaPeriod);
|
||||||
|
@ -351,6 +351,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
if (!areDurationsCompatible(oldPeriodInfo.durationUs, newPeriodInfo.durationUs)) {
|
if (!areDurationsCompatible(oldPeriodInfo.durationUs, newPeriodInfo.durationUs)) {
|
||||||
// The period duration changed. Remove all subsequent periods and check whether we read
|
// The period duration changed. Remove all subsequent periods and check whether we read
|
||||||
// beyond the new duration.
|
// beyond the new duration.
|
||||||
|
periodHolder.updateClipping();
|
||||||
long newDurationInRendererTime =
|
long newDurationInRendererTime =
|
||||||
newPeriodInfo.durationUs == C.TIME_UNSET
|
newPeriodInfo.durationUs == C.TIME_UNSET
|
||||||
? Long.MAX_VALUE
|
? Long.MAX_VALUE
|
||||||
@ -384,17 +385,21 @@ import com.google.common.collect.ImmutableList;
|
|||||||
boolean isLastInWindow = isLastInWindow(timeline, id);
|
boolean isLastInWindow = isLastInWindow(timeline, id);
|
||||||
boolean isLastInTimeline = isLastInTimeline(timeline, id, isLastInPeriod);
|
boolean isLastInTimeline = isLastInTimeline(timeline, id, isLastInPeriod);
|
||||||
timeline.getPeriodByUid(info.id.periodUid, period);
|
timeline.getPeriodByUid(info.id.periodUid, period);
|
||||||
|
long endPositionUs =
|
||||||
|
id.isAd() || id.nextAdGroupIndex == C.INDEX_UNSET
|
||||||
|
? C.TIME_UNSET
|
||||||
|
: period.getAdGroupTimeUs(id.nextAdGroupIndex);
|
||||||
long durationUs =
|
long durationUs =
|
||||||
id.isAd()
|
id.isAd()
|
||||||
? period.getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup)
|
? period.getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup)
|
||||||
: (info.endPositionUs == C.TIME_UNSET || info.endPositionUs == C.TIME_END_OF_SOURCE
|
: (endPositionUs == C.TIME_UNSET || endPositionUs == C.TIME_END_OF_SOURCE
|
||||||
? period.getDurationUs()
|
? period.getDurationUs()
|
||||||
: info.endPositionUs);
|
: endPositionUs);
|
||||||
return new MediaPeriodInfo(
|
return new MediaPeriodInfo(
|
||||||
id,
|
id,
|
||||||
info.startPositionUs,
|
info.startPositionUs,
|
||||||
info.requestedContentPositionUs,
|
info.requestedContentPositionUs,
|
||||||
info.endPositionUs,
|
endPositionUs,
|
||||||
durationUs,
|
durationUs,
|
||||||
isLastInPeriod,
|
isLastInPeriod,
|
||||||
isLastInWindow,
|
isLastInWindow,
|
||||||
|
@ -305,6 +305,31 @@ public final class MediaPeriodQueueTest {
|
|||||||
/* nextAdGroupIndex= */ C.INDEX_UNSET);
|
/* nextAdGroupIndex= */ C.INDEX_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
updateQueuedPeriods_withDurationChangeInPlayingContent_handlesChangeAndRemovesPeriodsAfterChangedPeriod() {
|
||||||
|
setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US);
|
||||||
|
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||||
|
enqueueNext(); // Content before first ad.
|
||||||
|
enqueueNext(); // First ad.
|
||||||
|
enqueueNext(); // Content between ads.
|
||||||
|
|
||||||
|
// Change position of first ad (= change duration of playing content before first ad).
|
||||||
|
updateAdPlaybackStateAndTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US - 2000);
|
||||||
|
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||||
|
long maxRendererReadPositionUs = FIRST_AD_START_TIME_US - 3000;
|
||||||
|
boolean changeHandled =
|
||||||
|
mediaPeriodQueue.updateQueuedPeriods(
|
||||||
|
playbackInfo.timeline, /* rendererPositionUs= */ 0, maxRendererReadPositionUs);
|
||||||
|
|
||||||
|
assertThat(changeHandled).isTrue();
|
||||||
|
assertThat(getQueueLength()).isEqualTo(1);
|
||||||
|
assertThat(mediaPeriodQueue.getPlayingPeriod().info.endPositionUs)
|
||||||
|
.isEqualTo(FIRST_AD_START_TIME_US - 2000);
|
||||||
|
assertThat(mediaPeriodQueue.getPlayingPeriod().info.durationUs)
|
||||||
|
.isEqualTo(FIRST_AD_START_TIME_US - 2000);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void
|
public void
|
||||||
updateQueuedPeriods_withDurationChangeAfterReadingPeriod_handlesChangeAndRemovesPeriodsAfterChangedPeriod() {
|
updateQueuedPeriods_withDurationChangeAfterReadingPeriod_handlesChangeAndRemovesPeriodsAfterChangedPeriod() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user