From fe754f313e8ddba5a77f149ee23f0082c1c2fea6 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 1 Dec 2020 11:02:30 +0000 Subject: [PATCH] Mask ad media periods before the URI is available Previously `MediaPeriodQueue` would return null if an ad media URI hadn't loaded yet, but this meant that the player could be stuck in `STATE_READY` if an `AdsLoader` unexpectedly didn't provide an ad URI. Fix this behavior by masking ad media periods. `MaskingMediaPeriod` no longer requires a `MediaSource` to instantiate it. This also fixes a specific case where playback gets stuck when using the IMA extension with an empty ad where the IMA SDK unexpectedly doesn't notify the ad group fetch error. Issue: #8205 PiperOrigin-RevId: 344984824 --- RELEASENOTES.md | 8 +- .../android/exoplayer2/MediaPeriodQueue.java | 35 +++---- .../google/android/exoplayer2/Timeline.java | 13 --- .../exoplayer2/source/MaskingMediaPeriod.java | 51 +++++----- .../exoplayer2/source/MaskingMediaSource.java | 4 +- .../exoplayer2/source/ads/AdsMediaSource.java | 98 +++++++++++++++---- .../exoplayer2/MediaPeriodQueueTest.java | 23 +++-- 7 files changed, 142 insertions(+), 90 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4db085b9e17..a41e080c2da 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,9 +1,15 @@ # Release notes +### 2.12.3 (???-??-??) ### + +* IMA extension: + * Fix a condition where playback can get stuck before an empty ad + ([#8205](https://github.com/google/ExoPlayer/issues/8205)). + ### 2.12.2 (2020-12-01) ### * Core library: - * Suppress exceptions from registering/unregistering the stream volume + * Suppress exceptions from registering and unregistering the stream volume receiver ([#8087](https://github.com/google/ExoPlayer/issues/8087)), ([#8106](https://github.com/google/ExoPlayer/issues/8106)). * Suppress ProGuard warnings caused by Guava's compile-only dependencies diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index fa6201bf37f..7ba7589a565 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -672,15 +672,13 @@ private MediaPeriodInfo getFollowingMediaPeriodInfo( period.getNextAdIndexToPlay(adGroupIndex, currentPeriodId.adIndexInAdGroup); if (nextAdIndexInAdGroup < adCountInCurrentAdGroup) { // Play the next ad in the ad group if it's available. - return !period.isAdAvailable(adGroupIndex, nextAdIndexInAdGroup) - ? null - : getMediaPeriodInfoForAd( - timeline, - currentPeriodId.periodUid, - adGroupIndex, - nextAdIndexInAdGroup, - mediaPeriodInfo.requestedContentPositionUs, - currentPeriodId.windowSequenceNumber); + return getMediaPeriodInfoForAd( + timeline, + currentPeriodId.periodUid, + adGroupIndex, + nextAdIndexInAdGroup, + mediaPeriodInfo.requestedContentPositionUs, + currentPeriodId.windowSequenceNumber); } else { // Play content from the ad group position. long startPositionUs = mediaPeriodInfo.requestedContentPositionUs; @@ -720,15 +718,13 @@ private MediaPeriodInfo getFollowingMediaPeriodInfo( currentPeriodId.windowSequenceNumber); } int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex); - return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup) - ? null - : getMediaPeriodInfoForAd( - timeline, - currentPeriodId.periodUid, - nextAdGroupIndex, - adIndexInAdGroup, - /* contentPositionUs= */ mediaPeriodInfo.durationUs, - currentPeriodId.windowSequenceNumber); + return getMediaPeriodInfoForAd( + timeline, + currentPeriodId.periodUid, + nextAdGroupIndex, + adIndexInAdGroup, + /* contentPositionUs= */ mediaPeriodInfo.durationUs, + currentPeriodId.windowSequenceNumber); } } @@ -737,9 +733,6 @@ private MediaPeriodInfo getMediaPeriodInfo( Timeline timeline, MediaPeriodId id, long requestedContentPositionUs, long startPositionUs) { timeline.getPeriodByUid(id.periodUid, period); if (id.isAd()) { - if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) { - return null; - } return getMediaPeriodInfoForAd( timeline, id.periodUid, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index e992eb588d9..a7fb1ae20b4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -609,19 +609,6 @@ public int getAdCountInAdGroup(int adGroupIndex) { return adPlaybackState.adGroups[adGroupIndex].count; } - /** - * Returns whether the URL for the specified ad is known. - * - * @param adGroupIndex The ad group index. - * @param adIndexInAdGroup The ad index in the ad group. - * @return Whether the URL for the specified ad is known. - */ - public boolean isAdAvailable(int adGroupIndex, int adIndexInAdGroup) { - AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; - return adGroup.count != C.LENGTH_UNSET - && adGroup.states[adIndexInAdGroup] != AdPlaybackState.AD_STATE_UNAVAILABLE; - } - /** * Returns the duration of the ad at index {@code adIndexInAdGroup} in the ad group at * {@code adGroupIndex}, in microseconds, or {@link C#TIME_UNSET} if not yet known. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaPeriod.java index 9514241035b..a69835532f4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaPeriod.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.source; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Util.castNonNull; import androidx.annotation.Nullable; @@ -25,12 +27,13 @@ import com.google.android.exoplayer2.upstream.Allocator; import java.io.IOException; import org.checkerframework.checker.nullness.compatqual.NullableType; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** - * Media period that wraps a media source and defers calling its {@link - * MediaSource#createPeriod(MediaPeriodId, Allocator, long)} method until {@link - * #createPeriod(MediaPeriodId)} has been called. This is useful if you need to return a media - * period immediately but the media source that should create it is not yet prepared. + * Media period that defers calling {@link MediaSource#createPeriod(MediaPeriodId, Allocator, long)} + * on a given source until {@link #createPeriod(MediaPeriodId)} has been called. This is useful if + * you need to return a media period immediately but the media source that should create it is not + * yet available or prepared. */ public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callback { @@ -46,33 +49,32 @@ public interface PrepareListener { void onPrepareError(MediaPeriodId mediaPeriodId, IOException exception); } - /** The {@link MediaSource} which will create the actual media period. */ - public final MediaSource mediaSource; /** The {@link MediaPeriodId} used to create the masking media period. */ public final MediaPeriodId id; + private final long preparePositionUs; private final Allocator allocator; - @Nullable private MediaPeriod mediaPeriod; + /** The {@link MediaSource} that will create the underlying media period. */ + private @MonotonicNonNull MediaSource mediaSource; + + private @MonotonicNonNull MediaPeriod mediaPeriod; @Nullable private Callback callback; - private long preparePositionUs; @Nullable private PrepareListener listener; private boolean notifiedPrepareError; private long preparePositionOverrideUs; /** - * Creates a new masking media period. + * Creates a new masking media period. The media source must be set via {@link + * #setMediaSource(MediaSource)} before preparation can start. * - * @param mediaSource The media source to wrap. * @param id The identifier used to create the masking media period. * @param allocator The allocator used to create the media period. * @param preparePositionUs The expected start position, in microseconds. */ - public MaskingMediaPeriod( - MediaSource mediaSource, MediaPeriodId id, Allocator allocator, long preparePositionUs) { + public MaskingMediaPeriod(MediaPeriodId id, Allocator allocator, long preparePositionUs) { this.id = id; this.allocator = allocator; - this.mediaSource = mediaSource; this.preparePositionUs = preparePositionUs; preparePositionOverrideUs = C.TIME_UNSET; } @@ -108,6 +110,12 @@ public long getPreparePositionOverrideUs() { return preparePositionOverrideUs; } + /** Sets the {@link MediaSource} that will create the underlying media period. */ + public void setMediaSource(MediaSource mediaSource) { + checkState(this.mediaSource == null); + this.mediaSource = mediaSource; + } + /** * Calls {@link MediaSource#createPeriod(MediaPeriodId, Allocator, long)} on the wrapped source * then prepares it if {@link #prepare(Callback, long)} has been called. Call {@link @@ -117,18 +125,16 @@ public long getPreparePositionOverrideUs() { */ public void createPeriod(MediaPeriodId id) { long preparePositionUs = getPreparePositionWithOverride(this.preparePositionUs); - mediaPeriod = mediaSource.createPeriod(id, allocator, preparePositionUs); + mediaPeriod = checkNotNull(mediaSource).createPeriod(id, allocator, preparePositionUs); if (callback != null) { - mediaPeriod.prepare(this, preparePositionUs); + mediaPeriod.prepare(/* callback= */ this, preparePositionUs); } } - /** - * Releases the period. - */ + /** Releases the period. */ public void releasePeriod() { if (mediaPeriod != null) { - mediaSource.releasePeriod(mediaPeriod); + checkNotNull(mediaSource).releasePeriod(mediaPeriod); } } @@ -136,7 +142,8 @@ public void releasePeriod() { public void prepare(Callback callback, long preparePositionUs) { this.callback = callback; if (mediaPeriod != null) { - mediaPeriod.prepare(this, getPreparePositionWithOverride(this.preparePositionUs)); + mediaPeriod.prepare( + /* callback= */ this, getPreparePositionWithOverride(this.preparePositionUs)); } } @@ -145,10 +152,10 @@ public void maybeThrowPrepareError() throws IOException { try { if (mediaPeriod != null) { mediaPeriod.maybeThrowPrepareError(); - } else { + } else if (mediaSource != null) { mediaSource.maybeThrowSourceInfoRefreshError(); } - } catch (final IOException e) { + } catch (IOException e) { if (listener == null) { throw e; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java index 19f5df2aa56..5d2e1c6fb7b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java @@ -111,8 +111,8 @@ public void maybeThrowSourceInfoRefreshError() { @Override public MaskingMediaPeriod createPeriod( MediaPeriodId id, Allocator allocator, long startPositionUs) { - MaskingMediaPeriod mediaPeriod = - new MaskingMediaPeriod(mediaSource, id, allocator, startPositionUs); + MaskingMediaPeriod mediaPeriod = new MaskingMediaPeriod(id, allocator, startPositionUs); + mediaPeriod.setMediaSource(mediaSource); if (isPrepared) { MediaPeriodId idInSource = id.copyWithPeriodUid(getInternalPeriodUid(id.periodUid)); mediaPeriod.createPeriod(idInSource); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 7320f6f6c57..10e9d8e3bed 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.source.ads; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -116,7 +118,7 @@ private AdLoadException(@Type int type, Exception cause) { */ public RuntimeException getRuntimeExceptionForUnexpected() { Assertions.checkState(type == TYPE_UNEXPECTED); - return (RuntimeException) Assertions.checkNotNull(getCause()); + return (RuntimeException) checkNotNull(getCause()); } } @@ -257,12 +259,10 @@ protected void prepareSourceInternal(@Nullable TransferListener mediaTransferLis @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { - AdPlaybackState adPlaybackState = Assertions.checkNotNull(this.adPlaybackState); + AdPlaybackState adPlaybackState = checkNotNull(this.adPlaybackState); if (adPlaybackState.adGroupCount > 0 && id.isAd()) { int adGroupIndex = id.adGroupIndex; int adIndexInAdGroup = id.adIndexInAdGroup; - Uri adUri = - Assertions.checkNotNull(adPlaybackState.adGroups[adGroupIndex].uris[adIndexInAdGroup]); if (adMediaSourceHolders[adGroupIndex].length <= adIndexInAdGroup) { int adCount = adIndexInAdGroup + 1; adMediaSourceHolders[adGroupIndex] = @@ -272,16 +272,14 @@ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long star AdMediaSourceHolder adMediaSourceHolder = adMediaSourceHolders[adGroupIndex][adIndexInAdGroup]; if (adMediaSourceHolder == null) { - MediaSource adMediaSource = - adMediaSourceFactory.createMediaSource(MediaItem.fromUri(adUri)); - adMediaSourceHolder = new AdMediaSourceHolder(adMediaSource); + adMediaSourceHolder = new AdMediaSourceHolder(id); adMediaSourceHolders[adGroupIndex][adIndexInAdGroup] = adMediaSourceHolder; - prepareChildSource(id, adMediaSource); + maybeUpdateAdMediaSources(); } - return adMediaSourceHolder.createMediaPeriod(adUri, id, allocator, startPositionUs); + return adMediaSourceHolder.createMediaPeriod(id, allocator, startPositionUs); } else { - MaskingMediaPeriod mediaPeriod = - new MaskingMediaPeriod(contentMediaSource, id, allocator, startPositionUs); + MaskingMediaPeriod mediaPeriod = new MaskingMediaPeriod(id, allocator, startPositionUs); + mediaPeriod.setMediaSource(contentMediaSource); mediaPeriod.createPeriod(id); return mediaPeriod; } @@ -293,10 +291,10 @@ public void releasePeriod(MediaPeriod mediaPeriod) { MediaPeriodId id = maskingMediaPeriod.id; if (id.isAd()) { AdMediaSourceHolder adMediaSourceHolder = - Assertions.checkNotNull(adMediaSourceHolders[id.adGroupIndex][id.adIndexInAdGroup]); + checkNotNull(adMediaSourceHolders[id.adGroupIndex][id.adIndexInAdGroup]); adMediaSourceHolder.releaseMediaPeriod(maskingMediaPeriod); if (adMediaSourceHolder.isInactive()) { - releaseChildSource(id); + adMediaSourceHolder.release(); adMediaSourceHolders[id.adGroupIndex][id.adIndexInAdGroup] = null; } } else { @@ -307,7 +305,7 @@ public void releasePeriod(MediaPeriod mediaPeriod) { @Override protected void releaseSourceInternal() { super.releaseSourceInternal(); - Assertions.checkNotNull(componentListener).release(); + checkNotNull(componentListener).release(); componentListener = null; contentTimeline = null; adPlaybackState = null; @@ -321,7 +319,7 @@ protected void onChildSourceInfoRefreshed( if (mediaPeriodId.isAd()) { int adGroupIndex = mediaPeriodId.adGroupIndex; int adIndexInAdGroup = mediaPeriodId.adIndexInAdGroup; - Assertions.checkNotNull(adMediaSourceHolders[adGroupIndex][adIndexInAdGroup]) + checkNotNull(adMediaSourceHolders[adGroupIndex][adIndexInAdGroup]) .handleSourceInfoRefresh(timeline); } else { Assertions.checkArgument(timeline.getPeriodCount() == 1); @@ -346,9 +344,41 @@ private void onAdPlaybackState(AdPlaybackState adPlaybackState) { Arrays.fill(adMediaSourceHolders, new AdMediaSourceHolder[0]); } this.adPlaybackState = adPlaybackState; + maybeUpdateAdMediaSources(); maybeUpdateSourceInfo(); } + /** + * Initializes any {@link AdMediaSourceHolder AdMediaSourceHolders} where the ad media URI is + * newly known. + */ + private void maybeUpdateAdMediaSources() { + @Nullable AdPlaybackState adPlaybackState = this.adPlaybackState; + if (adPlaybackState == null) { + return; + } + for (int adGroupIndex = 0; adGroupIndex < adMediaSourceHolders.length; adGroupIndex++) { + for (int adIndexInAdGroup = 0; + adIndexInAdGroup < this.adMediaSourceHolders[adGroupIndex].length; + adIndexInAdGroup++) { + @Nullable + AdMediaSourceHolder adMediaSourceHolder = + this.adMediaSourceHolders[adGroupIndex][adIndexInAdGroup]; + if (adMediaSourceHolder != null + && !adMediaSourceHolder.hasMediaSource() + && adPlaybackState.adGroups[adGroupIndex] != null + && adIndexInAdGroup < adPlaybackState.adGroups[adGroupIndex].uris.length) { + @Nullable Uri adUri = adPlaybackState.adGroups[adGroupIndex].uris[adIndexInAdGroup]; + if (adUri != null) { + MediaSource adMediaSource = + adMediaSourceFactory.createMediaSource(MediaItem.fromUri(adUri)); + adMediaSourceHolder.initializeWithMediaSource(adMediaSource, adUri); + } + } + } + } + } + private void maybeUpdateSourceInfo() { @Nullable Timeline contentTimeline = this.contentTimeline; if (adPlaybackState != null && contentTimeline != null) { @@ -461,22 +491,38 @@ public void onPrepareError(MediaPeriodId mediaPeriodId, IOException exception) { private final class AdMediaSourceHolder { - private final MediaSource adMediaSource; + private final MediaPeriodId id; private final List activeMediaPeriods; + private @MonotonicNonNull Uri adUri; + private @MonotonicNonNull MediaSource adMediaSource; private @MonotonicNonNull Timeline timeline; - public AdMediaSourceHolder(MediaSource adMediaSource) { - this.adMediaSource = adMediaSource; + public AdMediaSourceHolder(MediaPeriodId id) { + this.id = id; activeMediaPeriods = new ArrayList<>(); } + public void initializeWithMediaSource(MediaSource adMediaSource, Uri adUri) { + this.adMediaSource = adMediaSource; + this.adUri = adUri; + for (int i = 0; i < activeMediaPeriods.size(); i++) { + MaskingMediaPeriod maskingMediaPeriod = activeMediaPeriods.get(i); + maskingMediaPeriod.setMediaSource(adMediaSource); + maskingMediaPeriod.setPrepareListener(new AdPrepareListener(adUri)); + } + prepareChildSource(id, adMediaSource); + } + public MediaPeriod createMediaPeriod( - Uri adUri, MediaPeriodId id, Allocator allocator, long startPositionUs) { + MediaPeriodId id, Allocator allocator, long startPositionUs) { MaskingMediaPeriod maskingMediaPeriod = - new MaskingMediaPeriod(adMediaSource, id, allocator, startPositionUs); - maskingMediaPeriod.setPrepareListener(new AdPrepareListener(adUri)); + new MaskingMediaPeriod(id, allocator, startPositionUs); activeMediaPeriods.add(maskingMediaPeriod); + if (adMediaSource != null) { + maskingMediaPeriod.setMediaSource(adMediaSource); + maskingMediaPeriod.setPrepareListener(new AdPrepareListener(checkNotNull(adUri))); + } if (timeline != null) { Object periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0); MediaPeriodId adSourceMediaPeriodId = new MediaPeriodId(periodUid, id.windowSequenceNumber); @@ -510,6 +556,16 @@ public void releaseMediaPeriod(MaskingMediaPeriod maskingMediaPeriod) { maskingMediaPeriod.releasePeriod(); } + public void release() { + if (hasMediaSource()) { + releaseChildSource(id); + } + } + + public boolean hasMediaSource() { + return adMediaSource != null; + } + public boolean isInactive() { return activeMediaPeriods.isEmpty(); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index 20be8fe12b9..0eaab52ff4c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import static org.robolectric.Shadows.shadowOf; @@ -103,7 +102,8 @@ public void getNextMediaPeriodInfo_withoutAds_returnsLastMediaPeriodInfo() { public void getNextMediaPeriodInfo_withPrerollAd_returnsCorrectMediaPeriodInfos() { setupAdTimeline(/* adGroupTimesUs...= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 0); - assertNextMediaPeriodInfoIsAd(/* adGroupIndex= */ 0, /* contentPositionUs= */ C.TIME_UNSET); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 0, AD_DURATION_US, /* contentPositionUs= */ C.TIME_UNSET); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* periodUid= */ firstPeriodUid, @@ -128,12 +128,14 @@ public void getNextMediaPeriodInfo_withMidrollAds_returnsCorrectMediaPeriodInfos /* isLastInPeriod= */ false, /* isLastInWindow= */ false, /* nextAdGroupIndex= */ 0); - // The next media period info should be null as we haven't loaded the ad yet. advance(); - assertNull(getNextMediaPeriodInfo()); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 0, + /* adDurationUs= */ C.TIME_UNSET, + /* contentPositionUs= */ FIRST_AD_START_TIME_US); setAdGroupLoaded(/* adGroupIndex= */ 0); assertNextMediaPeriodInfoIsAd( - /* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US); + /* adGroupIndex= */ 0, AD_DURATION_US, /* contentPositionUs= */ FIRST_AD_START_TIME_US); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* periodUid= */ firstPeriodUid, @@ -147,7 +149,7 @@ public void getNextMediaPeriodInfo_withMidrollAds_returnsCorrectMediaPeriodInfos advance(); setAdGroupLoaded(/* adGroupIndex= */ 1); assertNextMediaPeriodInfoIsAd( - /* adGroupIndex= */ 1, /* contentPositionUs= */ SECOND_AD_START_TIME_US); + /* adGroupIndex= */ 1, AD_DURATION_US, /* contentPositionUs= */ SECOND_AD_START_TIME_US); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* periodUid= */ firstPeriodUid, @@ -175,7 +177,7 @@ public void getNextMediaPeriodInfo_withMidrollAndPostroll_returnsCorrectMediaPer advance(); setAdGroupLoaded(/* adGroupIndex= */ 0); assertNextMediaPeriodInfoIsAd( - /* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US); + /* adGroupIndex= */ 0, AD_DURATION_US, /* contentPositionUs= */ FIRST_AD_START_TIME_US); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* periodUid= */ firstPeriodUid, @@ -189,7 +191,7 @@ public void getNextMediaPeriodInfo_withMidrollAndPostroll_returnsCorrectMediaPer advance(); setAdGroupLoaded(/* adGroupIndex= */ 1); assertNextMediaPeriodInfoIsAd( - /* adGroupIndex= */ 1, /* contentPositionUs= */ CONTENT_DURATION_US); + /* adGroupIndex= */ 1, AD_DURATION_US, /* contentPositionUs= */ CONTENT_DURATION_US); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* periodUid= */ firstPeriodUid, @@ -531,7 +533,8 @@ private void assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* isFinal= */ isLastInWindow)); } - private void assertNextMediaPeriodInfoIsAd(int adGroupIndex, long contentPositionUs) { + private void assertNextMediaPeriodInfoIsAd( + int adGroupIndex, long adDurationUs, long contentPositionUs) { assertThat(getNextMediaPeriodInfo()) .isEqualTo( new MediaPeriodInfo( @@ -543,7 +546,7 @@ private void assertNextMediaPeriodInfoIsAd(int adGroupIndex, long contentPositio /* startPositionUs= */ 0, contentPositionUs, /* endPositionUs= */ C.TIME_UNSET, - /* durationUs= */ AD_DURATION_US, + adDurationUs, /* isLastInTimelinePeriod= */ false, /* isLastInTimelineWindow= */ false, /* isFinal= */ false));