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
This commit is contained in:
parent
d8df5411b8
commit
fe754f313e
@ -1,9 +1,15 @@
|
|||||||
# Release notes
|
# 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) ###
|
### 2.12.2 (2020-12-01) ###
|
||||||
|
|
||||||
* Core library:
|
* 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)),
|
receiver ([#8087](https://github.com/google/ExoPlayer/issues/8087)),
|
||||||
([#8106](https://github.com/google/ExoPlayer/issues/8106)).
|
([#8106](https://github.com/google/ExoPlayer/issues/8106)).
|
||||||
* Suppress ProGuard warnings caused by Guava's compile-only dependencies
|
* Suppress ProGuard warnings caused by Guava's compile-only dependencies
|
||||||
|
@ -672,15 +672,13 @@ import com.google.common.collect.ImmutableList;
|
|||||||
period.getNextAdIndexToPlay(adGroupIndex, currentPeriodId.adIndexInAdGroup);
|
period.getNextAdIndexToPlay(adGroupIndex, currentPeriodId.adIndexInAdGroup);
|
||||||
if (nextAdIndexInAdGroup < adCountInCurrentAdGroup) {
|
if (nextAdIndexInAdGroup < adCountInCurrentAdGroup) {
|
||||||
// Play the next ad in the ad group if it's available.
|
// Play the next ad in the ad group if it's available.
|
||||||
return !period.isAdAvailable(adGroupIndex, nextAdIndexInAdGroup)
|
return getMediaPeriodInfoForAd(
|
||||||
? null
|
timeline,
|
||||||
: getMediaPeriodInfoForAd(
|
currentPeriodId.periodUid,
|
||||||
timeline,
|
adGroupIndex,
|
||||||
currentPeriodId.periodUid,
|
nextAdIndexInAdGroup,
|
||||||
adGroupIndex,
|
mediaPeriodInfo.requestedContentPositionUs,
|
||||||
nextAdIndexInAdGroup,
|
currentPeriodId.windowSequenceNumber);
|
||||||
mediaPeriodInfo.requestedContentPositionUs,
|
|
||||||
currentPeriodId.windowSequenceNumber);
|
|
||||||
} else {
|
} else {
|
||||||
// Play content from the ad group position.
|
// Play content from the ad group position.
|
||||||
long startPositionUs = mediaPeriodInfo.requestedContentPositionUs;
|
long startPositionUs = mediaPeriodInfo.requestedContentPositionUs;
|
||||||
@ -720,15 +718,13 @@ import com.google.common.collect.ImmutableList;
|
|||||||
currentPeriodId.windowSequenceNumber);
|
currentPeriodId.windowSequenceNumber);
|
||||||
}
|
}
|
||||||
int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex);
|
int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex);
|
||||||
return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup)
|
return getMediaPeriodInfoForAd(
|
||||||
? null
|
timeline,
|
||||||
: getMediaPeriodInfoForAd(
|
currentPeriodId.periodUid,
|
||||||
timeline,
|
nextAdGroupIndex,
|
||||||
currentPeriodId.periodUid,
|
adIndexInAdGroup,
|
||||||
nextAdGroupIndex,
|
/* contentPositionUs= */ mediaPeriodInfo.durationUs,
|
||||||
adIndexInAdGroup,
|
currentPeriodId.windowSequenceNumber);
|
||||||
/* contentPositionUs= */ mediaPeriodInfo.durationUs,
|
|
||||||
currentPeriodId.windowSequenceNumber);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -737,9 +733,6 @@ import com.google.common.collect.ImmutableList;
|
|||||||
Timeline timeline, MediaPeriodId id, long requestedContentPositionUs, long startPositionUs) {
|
Timeline timeline, MediaPeriodId id, long requestedContentPositionUs, long startPositionUs) {
|
||||||
timeline.getPeriodByUid(id.periodUid, period);
|
timeline.getPeriodByUid(id.periodUid, period);
|
||||||
if (id.isAd()) {
|
if (id.isAd()) {
|
||||||
if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return getMediaPeriodInfoForAd(
|
return getMediaPeriodInfoForAd(
|
||||||
timeline,
|
timeline,
|
||||||
id.periodUid,
|
id.periodUid,
|
||||||
|
@ -609,19 +609,6 @@ public abstract class Timeline {
|
|||||||
return adPlaybackState.adGroups[adGroupIndex].count;
|
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
|
* 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.
|
* {@code adGroupIndex}, in microseconds, or {@link C#TIME_UNSET} if not yet known.
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source;
|
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 static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -25,12 +27,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
|
|||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
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
|
* Media period that defers calling {@link MediaSource#createPeriod(MediaPeriodId, Allocator, long)}
|
||||||
* MediaSource#createPeriod(MediaPeriodId, Allocator, long)} method until {@link
|
* on a given source until {@link #createPeriod(MediaPeriodId)} has been called. This is useful if
|
||||||
* #createPeriod(MediaPeriodId)} has been called. This is useful if you need to return a media
|
* you need to return a media period immediately but the media source that should create it is not
|
||||||
* period immediately but the media source that should create it is not yet prepared.
|
* yet available or prepared.
|
||||||
*/
|
*/
|
||||||
public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
|
public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
|
||||||
|
|
||||||
@ -46,33 +49,32 @@ public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
|
|||||||
void onPrepareError(MediaPeriodId mediaPeriodId, IOException exception);
|
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. */
|
/** The {@link MediaPeriodId} used to create the masking media period. */
|
||||||
public final MediaPeriodId id;
|
public final MediaPeriodId id;
|
||||||
|
|
||||||
|
private final long preparePositionUs;
|
||||||
private final Allocator allocator;
|
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;
|
@Nullable private Callback callback;
|
||||||
private long preparePositionUs;
|
|
||||||
@Nullable private PrepareListener listener;
|
@Nullable private PrepareListener listener;
|
||||||
private boolean notifiedPrepareError;
|
private boolean notifiedPrepareError;
|
||||||
private long preparePositionOverrideUs;
|
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 id The identifier used to create the masking media period.
|
||||||
* @param allocator The allocator used to create the media period.
|
* @param allocator The allocator used to create the media period.
|
||||||
* @param preparePositionUs The expected start position, in microseconds.
|
* @param preparePositionUs The expected start position, in microseconds.
|
||||||
*/
|
*/
|
||||||
public MaskingMediaPeriod(
|
public MaskingMediaPeriod(MediaPeriodId id, Allocator allocator, long preparePositionUs) {
|
||||||
MediaSource mediaSource, MediaPeriodId id, Allocator allocator, long preparePositionUs) {
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.allocator = allocator;
|
this.allocator = allocator;
|
||||||
this.mediaSource = mediaSource;
|
|
||||||
this.preparePositionUs = preparePositionUs;
|
this.preparePositionUs = preparePositionUs;
|
||||||
preparePositionOverrideUs = C.TIME_UNSET;
|
preparePositionOverrideUs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
@ -108,6 +110,12 @@ public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
|
|||||||
return preparePositionOverrideUs;
|
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
|
* Calls {@link MediaSource#createPeriod(MediaPeriodId, Allocator, long)} on the wrapped source
|
||||||
* then prepares it if {@link #prepare(Callback, long)} has been called. Call {@link
|
* then prepares it if {@link #prepare(Callback, long)} has been called. Call {@link
|
||||||
@ -117,18 +125,16 @@ public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
|
|||||||
*/
|
*/
|
||||||
public void createPeriod(MediaPeriodId id) {
|
public void createPeriod(MediaPeriodId id) {
|
||||||
long preparePositionUs = getPreparePositionWithOverride(this.preparePositionUs);
|
long preparePositionUs = getPreparePositionWithOverride(this.preparePositionUs);
|
||||||
mediaPeriod = mediaSource.createPeriod(id, allocator, preparePositionUs);
|
mediaPeriod = checkNotNull(mediaSource).createPeriod(id, allocator, preparePositionUs);
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
mediaPeriod.prepare(this, preparePositionUs);
|
mediaPeriod.prepare(/* callback= */ this, preparePositionUs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Releases the period. */
|
||||||
* Releases the period.
|
|
||||||
*/
|
|
||||||
public void releasePeriod() {
|
public void releasePeriod() {
|
||||||
if (mediaPeriod != null) {
|
if (mediaPeriod != null) {
|
||||||
mediaSource.releasePeriod(mediaPeriod);
|
checkNotNull(mediaSource).releasePeriod(mediaPeriod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +142,8 @@ public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
|
|||||||
public void prepare(Callback callback, long preparePositionUs) {
|
public void prepare(Callback callback, long preparePositionUs) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
if (mediaPeriod != null) {
|
if (mediaPeriod != null) {
|
||||||
mediaPeriod.prepare(this, getPreparePositionWithOverride(this.preparePositionUs));
|
mediaPeriod.prepare(
|
||||||
|
/* callback= */ this, getPreparePositionWithOverride(this.preparePositionUs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,10 +152,10 @@ public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
|
|||||||
try {
|
try {
|
||||||
if (mediaPeriod != null) {
|
if (mediaPeriod != null) {
|
||||||
mediaPeriod.maybeThrowPrepareError();
|
mediaPeriod.maybeThrowPrepareError();
|
||||||
} else {
|
} else if (mediaSource != null) {
|
||||||
mediaSource.maybeThrowSourceInfoRefreshError();
|
mediaSource.maybeThrowSourceInfoRefreshError();
|
||||||
}
|
}
|
||||||
} catch (final IOException e) {
|
} catch (IOException e) {
|
||||||
if (listener == null) {
|
if (listener == null) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -111,8 +111,8 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> {
|
|||||||
@Override
|
@Override
|
||||||
public MaskingMediaPeriod createPeriod(
|
public MaskingMediaPeriod createPeriod(
|
||||||
MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||||
MaskingMediaPeriod mediaPeriod =
|
MaskingMediaPeriod mediaPeriod = new MaskingMediaPeriod(id, allocator, startPositionUs);
|
||||||
new MaskingMediaPeriod(mediaSource, id, allocator, startPositionUs);
|
mediaPeriod.setMediaSource(mediaSource);
|
||||||
if (isPrepared) {
|
if (isPrepared) {
|
||||||
MediaPeriodId idInSource = id.copyWithPeriodUid(getInternalPeriodUid(id.periodUid));
|
MediaPeriodId idInSource = id.copyWithPeriodUid(getInternalPeriodUid(id.periodUid));
|
||||||
mediaPeriod.createPeriod(idInSource);
|
mediaPeriod.createPeriod(idInSource);
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.ads;
|
package com.google.android.exoplayer2.source.ads;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@ -116,7 +118,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
*/
|
*/
|
||||||
public RuntimeException getRuntimeExceptionForUnexpected() {
|
public RuntimeException getRuntimeExceptionForUnexpected() {
|
||||||
Assertions.checkState(type == TYPE_UNEXPECTED);
|
Assertions.checkState(type == TYPE_UNEXPECTED);
|
||||||
return (RuntimeException) Assertions.checkNotNull(getCause());
|
return (RuntimeException) checkNotNull(getCause());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,12 +259,10 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
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()) {
|
if (adPlaybackState.adGroupCount > 0 && id.isAd()) {
|
||||||
int adGroupIndex = id.adGroupIndex;
|
int adGroupIndex = id.adGroupIndex;
|
||||||
int adIndexInAdGroup = id.adIndexInAdGroup;
|
int adIndexInAdGroup = id.adIndexInAdGroup;
|
||||||
Uri adUri =
|
|
||||||
Assertions.checkNotNull(adPlaybackState.adGroups[adGroupIndex].uris[adIndexInAdGroup]);
|
|
||||||
if (adMediaSourceHolders[adGroupIndex].length <= adIndexInAdGroup) {
|
if (adMediaSourceHolders[adGroupIndex].length <= adIndexInAdGroup) {
|
||||||
int adCount = adIndexInAdGroup + 1;
|
int adCount = adIndexInAdGroup + 1;
|
||||||
adMediaSourceHolders[adGroupIndex] =
|
adMediaSourceHolders[adGroupIndex] =
|
||||||
@ -272,16 +272,14 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
AdMediaSourceHolder adMediaSourceHolder =
|
AdMediaSourceHolder adMediaSourceHolder =
|
||||||
adMediaSourceHolders[adGroupIndex][adIndexInAdGroup];
|
adMediaSourceHolders[adGroupIndex][adIndexInAdGroup];
|
||||||
if (adMediaSourceHolder == null) {
|
if (adMediaSourceHolder == null) {
|
||||||
MediaSource adMediaSource =
|
adMediaSourceHolder = new AdMediaSourceHolder(id);
|
||||||
adMediaSourceFactory.createMediaSource(MediaItem.fromUri(adUri));
|
|
||||||
adMediaSourceHolder = new AdMediaSourceHolder(adMediaSource);
|
|
||||||
adMediaSourceHolders[adGroupIndex][adIndexInAdGroup] = adMediaSourceHolder;
|
adMediaSourceHolders[adGroupIndex][adIndexInAdGroup] = adMediaSourceHolder;
|
||||||
prepareChildSource(id, adMediaSource);
|
maybeUpdateAdMediaSources();
|
||||||
}
|
}
|
||||||
return adMediaSourceHolder.createMediaPeriod(adUri, id, allocator, startPositionUs);
|
return adMediaSourceHolder.createMediaPeriod(id, allocator, startPositionUs);
|
||||||
} else {
|
} else {
|
||||||
MaskingMediaPeriod mediaPeriod =
|
MaskingMediaPeriod mediaPeriod = new MaskingMediaPeriod(id, allocator, startPositionUs);
|
||||||
new MaskingMediaPeriod(contentMediaSource, id, allocator, startPositionUs);
|
mediaPeriod.setMediaSource(contentMediaSource);
|
||||||
mediaPeriod.createPeriod(id);
|
mediaPeriod.createPeriod(id);
|
||||||
return mediaPeriod;
|
return mediaPeriod;
|
||||||
}
|
}
|
||||||
@ -293,10 +291,10 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
MediaPeriodId id = maskingMediaPeriod.id;
|
MediaPeriodId id = maskingMediaPeriod.id;
|
||||||
if (id.isAd()) {
|
if (id.isAd()) {
|
||||||
AdMediaSourceHolder adMediaSourceHolder =
|
AdMediaSourceHolder adMediaSourceHolder =
|
||||||
Assertions.checkNotNull(adMediaSourceHolders[id.adGroupIndex][id.adIndexInAdGroup]);
|
checkNotNull(adMediaSourceHolders[id.adGroupIndex][id.adIndexInAdGroup]);
|
||||||
adMediaSourceHolder.releaseMediaPeriod(maskingMediaPeriod);
|
adMediaSourceHolder.releaseMediaPeriod(maskingMediaPeriod);
|
||||||
if (adMediaSourceHolder.isInactive()) {
|
if (adMediaSourceHolder.isInactive()) {
|
||||||
releaseChildSource(id);
|
adMediaSourceHolder.release();
|
||||||
adMediaSourceHolders[id.adGroupIndex][id.adIndexInAdGroup] = null;
|
adMediaSourceHolders[id.adGroupIndex][id.adIndexInAdGroup] = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -307,7 +305,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
@Override
|
@Override
|
||||||
protected void releaseSourceInternal() {
|
protected void releaseSourceInternal() {
|
||||||
super.releaseSourceInternal();
|
super.releaseSourceInternal();
|
||||||
Assertions.checkNotNull(componentListener).release();
|
checkNotNull(componentListener).release();
|
||||||
componentListener = null;
|
componentListener = null;
|
||||||
contentTimeline = null;
|
contentTimeline = null;
|
||||||
adPlaybackState = null;
|
adPlaybackState = null;
|
||||||
@ -321,7 +319,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
if (mediaPeriodId.isAd()) {
|
if (mediaPeriodId.isAd()) {
|
||||||
int adGroupIndex = mediaPeriodId.adGroupIndex;
|
int adGroupIndex = mediaPeriodId.adGroupIndex;
|
||||||
int adIndexInAdGroup = mediaPeriodId.adIndexInAdGroup;
|
int adIndexInAdGroup = mediaPeriodId.adIndexInAdGroup;
|
||||||
Assertions.checkNotNull(adMediaSourceHolders[adGroupIndex][adIndexInAdGroup])
|
checkNotNull(adMediaSourceHolders[adGroupIndex][adIndexInAdGroup])
|
||||||
.handleSourceInfoRefresh(timeline);
|
.handleSourceInfoRefresh(timeline);
|
||||||
} else {
|
} else {
|
||||||
Assertions.checkArgument(timeline.getPeriodCount() == 1);
|
Assertions.checkArgument(timeline.getPeriodCount() == 1);
|
||||||
@ -346,9 +344,41 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
Arrays.fill(adMediaSourceHolders, new AdMediaSourceHolder[0]);
|
Arrays.fill(adMediaSourceHolders, new AdMediaSourceHolder[0]);
|
||||||
}
|
}
|
||||||
this.adPlaybackState = adPlaybackState;
|
this.adPlaybackState = adPlaybackState;
|
||||||
|
maybeUpdateAdMediaSources();
|
||||||
maybeUpdateSourceInfo();
|
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() {
|
private void maybeUpdateSourceInfo() {
|
||||||
@Nullable Timeline contentTimeline = this.contentTimeline;
|
@Nullable Timeline contentTimeline = this.contentTimeline;
|
||||||
if (adPlaybackState != null && contentTimeline != null) {
|
if (adPlaybackState != null && contentTimeline != null) {
|
||||||
@ -461,22 +491,38 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
|
|
||||||
private final class AdMediaSourceHolder {
|
private final class AdMediaSourceHolder {
|
||||||
|
|
||||||
private final MediaSource adMediaSource;
|
private final MediaPeriodId id;
|
||||||
private final List<MaskingMediaPeriod> activeMediaPeriods;
|
private final List<MaskingMediaPeriod> activeMediaPeriods;
|
||||||
|
|
||||||
|
private @MonotonicNonNull Uri adUri;
|
||||||
|
private @MonotonicNonNull MediaSource adMediaSource;
|
||||||
private @MonotonicNonNull Timeline timeline;
|
private @MonotonicNonNull Timeline timeline;
|
||||||
|
|
||||||
public AdMediaSourceHolder(MediaSource adMediaSource) {
|
public AdMediaSourceHolder(MediaPeriodId id) {
|
||||||
this.adMediaSource = adMediaSource;
|
this.id = id;
|
||||||
activeMediaPeriods = new ArrayList<>();
|
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(
|
public MediaPeriod createMediaPeriod(
|
||||||
Uri adUri, MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||||
MaskingMediaPeriod maskingMediaPeriod =
|
MaskingMediaPeriod maskingMediaPeriod =
|
||||||
new MaskingMediaPeriod(adMediaSource, id, allocator, startPositionUs);
|
new MaskingMediaPeriod(id, allocator, startPositionUs);
|
||||||
maskingMediaPeriod.setPrepareListener(new AdPrepareListener(adUri));
|
|
||||||
activeMediaPeriods.add(maskingMediaPeriod);
|
activeMediaPeriods.add(maskingMediaPeriod);
|
||||||
|
if (adMediaSource != null) {
|
||||||
|
maskingMediaPeriod.setMediaSource(adMediaSource);
|
||||||
|
maskingMediaPeriod.setPrepareListener(new AdPrepareListener(checkNotNull(adUri)));
|
||||||
|
}
|
||||||
if (timeline != null) {
|
if (timeline != null) {
|
||||||
Object periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0);
|
Object periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0);
|
||||||
MediaPeriodId adSourceMediaPeriodId = new MediaPeriodId(periodUid, id.windowSequenceNumber);
|
MediaPeriodId adSourceMediaPeriodId = new MediaPeriodId(periodUid, id.windowSequenceNumber);
|
||||||
@ -510,6 +556,16 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
maskingMediaPeriod.releasePeriod();
|
maskingMediaPeriod.releasePeriod();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
if (hasMediaSource()) {
|
||||||
|
releaseChildSource(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMediaSource() {
|
||||||
|
return adMediaSource != null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInactive() {
|
public boolean isInactive() {
|
||||||
return activeMediaPeriods.isEmpty();
|
return activeMediaPeriods.isEmpty();
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
package com.google.android.exoplayer2;
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.robolectric.Shadows.shadowOf;
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
@ -103,7 +102,8 @@ public final class MediaPeriodQueueTest {
|
|||||||
public void getNextMediaPeriodInfo_withPrerollAd_returnsCorrectMediaPeriodInfos() {
|
public void getNextMediaPeriodInfo_withPrerollAd_returnsCorrectMediaPeriodInfos() {
|
||||||
setupAdTimeline(/* adGroupTimesUs...= */ 0);
|
setupAdTimeline(/* adGroupTimesUs...= */ 0);
|
||||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||||
assertNextMediaPeriodInfoIsAd(/* adGroupIndex= */ 0, /* contentPositionUs= */ C.TIME_UNSET);
|
assertNextMediaPeriodInfoIsAd(
|
||||||
|
/* adGroupIndex= */ 0, AD_DURATION_US, /* contentPositionUs= */ C.TIME_UNSET);
|
||||||
advance();
|
advance();
|
||||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
/* periodUid= */ firstPeriodUid,
|
/* periodUid= */ firstPeriodUid,
|
||||||
@ -128,12 +128,14 @@ public final class MediaPeriodQueueTest {
|
|||||||
/* isLastInPeriod= */ false,
|
/* isLastInPeriod= */ false,
|
||||||
/* isLastInWindow= */ false,
|
/* isLastInWindow= */ false,
|
||||||
/* nextAdGroupIndex= */ 0);
|
/* nextAdGroupIndex= */ 0);
|
||||||
// The next media period info should be null as we haven't loaded the ad yet.
|
|
||||||
advance();
|
advance();
|
||||||
assertNull(getNextMediaPeriodInfo());
|
assertNextMediaPeriodInfoIsAd(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adDurationUs= */ C.TIME_UNSET,
|
||||||
|
/* contentPositionUs= */ FIRST_AD_START_TIME_US);
|
||||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||||
assertNextMediaPeriodInfoIsAd(
|
assertNextMediaPeriodInfoIsAd(
|
||||||
/* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US);
|
/* adGroupIndex= */ 0, AD_DURATION_US, /* contentPositionUs= */ FIRST_AD_START_TIME_US);
|
||||||
advance();
|
advance();
|
||||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
/* periodUid= */ firstPeriodUid,
|
/* periodUid= */ firstPeriodUid,
|
||||||
@ -147,7 +149,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
advance();
|
advance();
|
||||||
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
||||||
assertNextMediaPeriodInfoIsAd(
|
assertNextMediaPeriodInfoIsAd(
|
||||||
/* adGroupIndex= */ 1, /* contentPositionUs= */ SECOND_AD_START_TIME_US);
|
/* adGroupIndex= */ 1, AD_DURATION_US, /* contentPositionUs= */ SECOND_AD_START_TIME_US);
|
||||||
advance();
|
advance();
|
||||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
/* periodUid= */ firstPeriodUid,
|
/* periodUid= */ firstPeriodUid,
|
||||||
@ -175,7 +177,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
advance();
|
advance();
|
||||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||||
assertNextMediaPeriodInfoIsAd(
|
assertNextMediaPeriodInfoIsAd(
|
||||||
/* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US);
|
/* adGroupIndex= */ 0, AD_DURATION_US, /* contentPositionUs= */ FIRST_AD_START_TIME_US);
|
||||||
advance();
|
advance();
|
||||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
/* periodUid= */ firstPeriodUid,
|
/* periodUid= */ firstPeriodUid,
|
||||||
@ -189,7 +191,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
advance();
|
advance();
|
||||||
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
||||||
assertNextMediaPeriodInfoIsAd(
|
assertNextMediaPeriodInfoIsAd(
|
||||||
/* adGroupIndex= */ 1, /* contentPositionUs= */ CONTENT_DURATION_US);
|
/* adGroupIndex= */ 1, AD_DURATION_US, /* contentPositionUs= */ CONTENT_DURATION_US);
|
||||||
advance();
|
advance();
|
||||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
/* periodUid= */ firstPeriodUid,
|
/* periodUid= */ firstPeriodUid,
|
||||||
@ -531,7 +533,8 @@ public final class MediaPeriodQueueTest {
|
|||||||
/* isFinal= */ isLastInWindow));
|
/* isFinal= */ isLastInWindow));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertNextMediaPeriodInfoIsAd(int adGroupIndex, long contentPositionUs) {
|
private void assertNextMediaPeriodInfoIsAd(
|
||||||
|
int adGroupIndex, long adDurationUs, long contentPositionUs) {
|
||||||
assertThat(getNextMediaPeriodInfo())
|
assertThat(getNextMediaPeriodInfo())
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
new MediaPeriodInfo(
|
new MediaPeriodInfo(
|
||||||
@ -543,7 +546,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
/* startPositionUs= */ 0,
|
/* startPositionUs= */ 0,
|
||||||
contentPositionUs,
|
contentPositionUs,
|
||||||
/* endPositionUs= */ C.TIME_UNSET,
|
/* endPositionUs= */ C.TIME_UNSET,
|
||||||
/* durationUs= */ AD_DURATION_US,
|
adDurationUs,
|
||||||
/* isLastInTimelinePeriod= */ false,
|
/* isLastInTimelinePeriod= */ false,
|
||||||
/* isLastInTimelineWindow= */ false,
|
/* isLastInTimelineWindow= */ false,
|
||||||
/* isFinal= */ false));
|
/* isFinal= */ false));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user