mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Store adPlaybackStates and shared periods by period UID
To support multi-period content we need to store AdPlaybackStates and SharedMediaPeriod by the periodUid as a key. While after this no-op CL, we still only support single-period content, storing these resources by periodUid is the ground work for multi-period support being added in an follow-up CL. PiperOrigin-RevId: 416836445
This commit is contained in:
parent
2fad15a815
commit
f352836bde
@ -17,6 +17,7 @@ package androidx.media3.exoplayer.source.ads;
|
|||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Util.castNonNull;
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.getAdCountInGroup;
|
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.getAdCountInGroup;
|
||||||
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.getMediaPeriodPositionUs;
|
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.getMediaPeriodPositionUs;
|
||||||
@ -36,7 +37,6 @@ import androidx.media3.common.StreamKey;
|
|||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.TrackGroup;
|
import androidx.media3.common.TrackGroup;
|
||||||
import androidx.media3.common.TrackGroupArray;
|
import androidx.media3.common.TrackGroupArray;
|
||||||
import androidx.media3.common.util.Assertions;
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.TransferListener;
|
import androidx.media3.datasource.TransferListener;
|
||||||
@ -57,6 +57,7 @@ import androidx.media3.exoplayer.source.SampleStream;
|
|||||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||||
import androidx.media3.exoplayer.upstream.Allocator;
|
import androidx.media3.exoplayer.upstream.Allocator;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -75,8 +76,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* server-side inserted ad breaks and ensures that playback continues seamlessly with the wrapped
|
* server-side inserted ad breaks and ensures that playback continues seamlessly with the wrapped
|
||||||
* media across all transitions.
|
* media across all transitions.
|
||||||
*
|
*
|
||||||
* <p>The ad breaks need to be specified using {@link #setAdPlaybackState} and can be updated during
|
* <p>The ad breaks need to be specified using {@link #setAdPlaybackStates} and can be updated
|
||||||
* playback.
|
* during playback.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
||||||
@ -91,7 +92,7 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
* Called when the content source has refreshed the timeline.
|
* Called when the content source has refreshed the timeline.
|
||||||
*
|
*
|
||||||
* <p>If true is returned the source refresh publication is deferred, to wait for an {@link
|
* <p>If true is returned the source refresh publication is deferred, to wait for an {@link
|
||||||
* #setAdPlaybackState(AdPlaybackState) ad playback state update}. If false is returned, the
|
* #setAdPlaybackStates(ImmutableMap)} ad playback state update}. If false is returned, the
|
||||||
* source refresh is immediately published.
|
* source refresh is immediately published.
|
||||||
*
|
*
|
||||||
* <p>Called on the playback thread.
|
* <p>Called on the playback thread.
|
||||||
@ -104,7 +105,7 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final MediaSource mediaSource;
|
private final MediaSource mediaSource;
|
||||||
private final ListMultimap<Long, SharedMediaPeriod> mediaPeriods;
|
private final ListMultimap<Pair<Long, Object>, SharedMediaPeriod> mediaPeriods;
|
||||||
private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcherWithoutId;
|
private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcherWithoutId;
|
||||||
private final DrmSessionEventListener.EventDispatcher drmEventDispatcherWithoutId;
|
private final DrmSessionEventListener.EventDispatcher drmEventDispatcherWithoutId;
|
||||||
@Nullable private final AdPlaybackStateUpdater adPlaybackStateUpdater;
|
@Nullable private final AdPlaybackStateUpdater adPlaybackStateUpdater;
|
||||||
@ -115,7 +116,7 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
|
|
||||||
@Nullable private SharedMediaPeriod lastUsedMediaPeriod;
|
@Nullable private SharedMediaPeriod lastUsedMediaPeriod;
|
||||||
@Nullable private Timeline contentTimeline;
|
@Nullable private Timeline contentTimeline;
|
||||||
private AdPlaybackState adPlaybackState;
|
private ImmutableMap<Object, AdPlaybackState> adPlaybackStates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the media source.
|
* Creates the media source.
|
||||||
@ -131,53 +132,74 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
this.mediaSource = mediaSource;
|
this.mediaSource = mediaSource;
|
||||||
this.adPlaybackStateUpdater = adPlaybackStateUpdater;
|
this.adPlaybackStateUpdater = adPlaybackStateUpdater;
|
||||||
mediaPeriods = ArrayListMultimap.create();
|
mediaPeriods = ArrayListMultimap.create();
|
||||||
adPlaybackState = AdPlaybackState.NONE;
|
adPlaybackStates = ImmutableMap.of();
|
||||||
mediaSourceEventDispatcherWithoutId = createEventDispatcher(/* mediaPeriodId= */ null);
|
mediaSourceEventDispatcherWithoutId = createEventDispatcher(/* mediaPeriodId= */ null);
|
||||||
drmEventDispatcherWithoutId = createDrmEventDispatcher(/* mediaPeriodId= */ null);
|
drmEventDispatcherWithoutId = createDrmEventDispatcher(/* mediaPeriodId= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link AdPlaybackState} published by this source.
|
* Sets the map of {@link AdPlaybackState ad playback states} published by this source. The key is
|
||||||
|
* the period UID of a period in the {@link
|
||||||
|
* AdPlaybackStateUpdater#onAdPlaybackStateUpdateRequested(Timeline)} content timeline}.
|
||||||
|
*
|
||||||
|
* <p>Each period has an {@link AdPlaybackState} that tells where in the period the ad groups
|
||||||
|
* start and end. Must only contain server-side inserted ad groups. The number of ad groups and
|
||||||
|
* the number of ads within an ad group may only increase. The durations of ads may change and the
|
||||||
|
* positions of future ad groups may change. Post-roll ad groups with {@link C#TIME_END_OF_SOURCE}
|
||||||
|
* must be empty and can be used as a placeholder for a future ad group.
|
||||||
*
|
*
|
||||||
* <p>May be called from any thread.
|
* <p>May be called from any thread.
|
||||||
*
|
*
|
||||||
* <p>Must only contain server-side inserted ad groups. The number of ad groups and the number of
|
* @param adPlaybackStates The map of {@link AdPlaybackState} keyed by their period UID.
|
||||||
* ads within an ad group may only increase. The durations of ads may change and the positions of
|
|
||||||
* future ad groups may change. Post-roll ad groups with {@link C#TIME_END_OF_SOURCE} must be
|
|
||||||
* empty and can be used as a placeholder for a future ad group.
|
|
||||||
*
|
|
||||||
* @param adPlaybackState The new {@link AdPlaybackState}.
|
|
||||||
*/
|
*/
|
||||||
public void setAdPlaybackState(AdPlaybackState adPlaybackState) {
|
public void setAdPlaybackStates(ImmutableMap<Object, AdPlaybackState> adPlaybackStates) {
|
||||||
checkArgument(adPlaybackState.adGroupCount >= this.adPlaybackState.adGroupCount);
|
checkArgument(!adPlaybackStates.isEmpty());
|
||||||
|
Object adsId = checkNotNull(adPlaybackStates.values().asList().get(0).adsId);
|
||||||
|
for (Map.Entry<Object, AdPlaybackState> entry : adPlaybackStates.entrySet()) {
|
||||||
|
Object periodUid = entry.getKey();
|
||||||
|
AdPlaybackState adPlaybackState = entry.getValue();
|
||||||
|
checkArgument(Util.areEqual(adsId, adPlaybackState.adsId));
|
||||||
|
@Nullable AdPlaybackState oldAdPlaybackState = this.adPlaybackStates.get(periodUid);
|
||||||
|
if (oldAdPlaybackState != null) {
|
||||||
for (int i = adPlaybackState.removedAdGroupCount; i < adPlaybackState.adGroupCount; i++) {
|
for (int i = adPlaybackState.removedAdGroupCount; i < adPlaybackState.adGroupCount; i++) {
|
||||||
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(i);
|
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(i);
|
||||||
checkArgument(adGroup.isServerSideInserted);
|
checkArgument(adGroup.isServerSideInserted);
|
||||||
if (i < this.adPlaybackState.adGroupCount) {
|
if (i < oldAdPlaybackState.adGroupCount) {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i)
|
getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i)
|
||||||
>= getAdCountInGroup(this.adPlaybackState, /* adGroupIndex= */ i));
|
>= getAdCountInGroup(oldAdPlaybackState, /* adGroupIndex= */ i));
|
||||||
}
|
}
|
||||||
if (adGroup.timeUs == C.TIME_END_OF_SOURCE) {
|
if (adGroup.timeUs == C.TIME_END_OF_SOURCE) {
|
||||||
checkArgument(getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i) == 0);
|
checkArgument(getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i) == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (playbackHandler == null) {
|
if (playbackHandler == null) {
|
||||||
this.adPlaybackState = adPlaybackState;
|
this.adPlaybackStates = adPlaybackStates;
|
||||||
} else {
|
} else {
|
||||||
playbackHandler.post(
|
playbackHandler.post(
|
||||||
() -> {
|
() -> {
|
||||||
for (SharedMediaPeriod mediaPeriod : mediaPeriods.values()) {
|
for (SharedMediaPeriod mediaPeriod : mediaPeriods.values()) {
|
||||||
|
@Nullable
|
||||||
|
AdPlaybackState adPlaybackState = adPlaybackStates.get(mediaPeriod.periodUid);
|
||||||
|
if (adPlaybackState != null) {
|
||||||
mediaPeriod.updateAdPlaybackState(adPlaybackState);
|
mediaPeriod.updateAdPlaybackState(adPlaybackState);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (lastUsedMediaPeriod != null) {
|
if (lastUsedMediaPeriod != null) {
|
||||||
|
@Nullable
|
||||||
|
AdPlaybackState adPlaybackState =
|
||||||
|
adPlaybackStates.get(lastUsedMediaPeriod.periodUid);
|
||||||
|
if (adPlaybackState != null) {
|
||||||
lastUsedMediaPeriod.updateAdPlaybackState(adPlaybackState);
|
lastUsedMediaPeriod.updateAdPlaybackState(adPlaybackState);
|
||||||
}
|
}
|
||||||
this.adPlaybackState = adPlaybackState;
|
}
|
||||||
|
this.adPlaybackStates = adPlaybackStates;
|
||||||
if (contentTimeline != null) {
|
if (contentTimeline != null) {
|
||||||
refreshSourceInfo(
|
refreshSourceInfo(
|
||||||
new ServerSideAdInsertionTimeline(contentTimeline, adPlaybackState));
|
new ServerSideAdInsertionTimeline(contentTimeline, adPlaybackStates));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -221,8 +243,8 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
this.contentTimeline = timeline;
|
this.contentTimeline = timeline;
|
||||||
if ((adPlaybackStateUpdater == null
|
if ((adPlaybackStateUpdater == null
|
||||||
|| !adPlaybackStateUpdater.onAdPlaybackStateUpdateRequested(timeline))
|
|| !adPlaybackStateUpdater.onAdPlaybackStateUpdateRequested(timeline))
|
||||||
&& !AdPlaybackState.NONE.equals(adPlaybackState)) {
|
&& !adPlaybackStates.isEmpty()) {
|
||||||
refreshSourceInfo(new ServerSideAdInsertionTimeline(timeline, adPlaybackState));
|
refreshSourceInfo(new ServerSideAdInsertionTimeline(timeline, adPlaybackStates));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,19 +262,26 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||||
SharedMediaPeriod sharedPeriod;
|
@Nullable SharedMediaPeriod sharedPeriod = null;
|
||||||
|
Pair<Long, Object> sharedMediaPeriodKey = new Pair<>(id.windowSequenceNumber, id.periodUid);
|
||||||
if (lastUsedMediaPeriod != null) {
|
if (lastUsedMediaPeriod != null) {
|
||||||
|
if (lastUsedMediaPeriod.periodUid.equals(id.periodUid)) {
|
||||||
sharedPeriod = lastUsedMediaPeriod;
|
sharedPeriod = lastUsedMediaPeriod;
|
||||||
lastUsedMediaPeriod = null;
|
mediaPeriods.put(sharedMediaPeriodKey, sharedPeriod);
|
||||||
mediaPeriods.put(id.windowSequenceNumber, sharedPeriod);
|
|
||||||
} else {
|
} else {
|
||||||
|
lastUsedMediaPeriod.release(mediaSource);
|
||||||
|
}
|
||||||
|
lastUsedMediaPeriod = null;
|
||||||
|
}
|
||||||
|
if (sharedPeriod == null) {
|
||||||
@Nullable
|
@Nullable
|
||||||
SharedMediaPeriod lastExistingPeriod =
|
SharedMediaPeriod lastExistingPeriod =
|
||||||
Iterables.getLast(mediaPeriods.get(id.windowSequenceNumber), /* defaultValue= */ null);
|
Iterables.getLast(mediaPeriods.get(sharedMediaPeriodKey), /* defaultValue= */ null);
|
||||||
if (lastExistingPeriod != null
|
if (lastExistingPeriod != null
|
||||||
&& lastExistingPeriod.canReuseMediaPeriod(id, startPositionUs)) {
|
&& lastExistingPeriod.canReuseMediaPeriod(id, startPositionUs)) {
|
||||||
sharedPeriod = lastExistingPeriod;
|
sharedPeriod = lastExistingPeriod;
|
||||||
} else {
|
} else {
|
||||||
|
AdPlaybackState adPlaybackState = checkNotNull(adPlaybackStates.get(id.periodUid));
|
||||||
long streamPositionUs = getStreamPositionUs(startPositionUs, id, adPlaybackState);
|
long streamPositionUs = getStreamPositionUs(startPositionUs, id, adPlaybackState);
|
||||||
sharedPeriod =
|
sharedPeriod =
|
||||||
new SharedMediaPeriod(
|
new SharedMediaPeriod(
|
||||||
@ -260,8 +289,9 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
new MediaPeriodId(id.periodUid, id.windowSequenceNumber),
|
new MediaPeriodId(id.periodUid, id.windowSequenceNumber),
|
||||||
allocator,
|
allocator,
|
||||||
streamPositionUs),
|
streamPositionUs),
|
||||||
|
id.periodUid,
|
||||||
adPlaybackState);
|
adPlaybackState);
|
||||||
mediaPeriods.put(id.windowSequenceNumber, sharedPeriod);
|
mediaPeriods.put(sharedMediaPeriodKey, sharedPeriod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MediaPeriodImpl mediaPeriod =
|
MediaPeriodImpl mediaPeriod =
|
||||||
@ -277,7 +307,10 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
mediaPeriodImpl.sharedPeriod.remove(mediaPeriodImpl);
|
mediaPeriodImpl.sharedPeriod.remove(mediaPeriodImpl);
|
||||||
if (mediaPeriodImpl.sharedPeriod.isUnused()) {
|
if (mediaPeriodImpl.sharedPeriod.isUnused()) {
|
||||||
mediaPeriods.remove(
|
mediaPeriods.remove(
|
||||||
mediaPeriodImpl.mediaPeriodId.windowSequenceNumber, mediaPeriodImpl.sharedPeriod);
|
new Pair<>(
|
||||||
|
mediaPeriodImpl.mediaPeriodId.windowSequenceNumber,
|
||||||
|
mediaPeriodImpl.mediaPeriodId.periodUid),
|
||||||
|
mediaPeriodImpl.sharedPeriod);
|
||||||
if (mediaPeriods.isEmpty()) {
|
if (mediaPeriods.isEmpty()) {
|
||||||
// Keep until disabled.
|
// Keep until disabled.
|
||||||
lastUsedMediaPeriod = mediaPeriodImpl.sharedPeriod;
|
lastUsedMediaPeriod = mediaPeriodImpl.sharedPeriod;
|
||||||
@ -381,7 +414,11 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
} else {
|
} else {
|
||||||
mediaPeriod.sharedPeriod.onLoadStarted(loadEventInfo, mediaLoadData);
|
mediaPeriod.sharedPeriod.onLoadStarted(loadEventInfo, mediaLoadData);
|
||||||
mediaPeriod.mediaSourceEventDispatcher.loadStarted(
|
mediaPeriod.mediaSourceEventDispatcher.loadStarted(
|
||||||
loadEventInfo, correctMediaLoadData(mediaPeriod, mediaLoadData, adPlaybackState));
|
loadEventInfo,
|
||||||
|
correctMediaLoadData(
|
||||||
|
mediaPeriod,
|
||||||
|
mediaLoadData,
|
||||||
|
checkNotNull(adPlaybackStates.get(mediaPeriod.mediaPeriodId.periodUid))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +436,11 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
} else {
|
} else {
|
||||||
mediaPeriod.sharedPeriod.onLoadFinished(loadEventInfo);
|
mediaPeriod.sharedPeriod.onLoadFinished(loadEventInfo);
|
||||||
mediaPeriod.mediaSourceEventDispatcher.loadCompleted(
|
mediaPeriod.mediaSourceEventDispatcher.loadCompleted(
|
||||||
loadEventInfo, correctMediaLoadData(mediaPeriod, mediaLoadData, adPlaybackState));
|
loadEventInfo,
|
||||||
|
correctMediaLoadData(
|
||||||
|
mediaPeriod,
|
||||||
|
mediaLoadData,
|
||||||
|
checkNotNull(adPlaybackStates.get(mediaPeriod.mediaPeriodId.periodUid))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +458,11 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
} else {
|
} else {
|
||||||
mediaPeriod.sharedPeriod.onLoadFinished(loadEventInfo);
|
mediaPeriod.sharedPeriod.onLoadFinished(loadEventInfo);
|
||||||
mediaPeriod.mediaSourceEventDispatcher.loadCanceled(
|
mediaPeriod.mediaSourceEventDispatcher.loadCanceled(
|
||||||
loadEventInfo, correctMediaLoadData(mediaPeriod, mediaLoadData, adPlaybackState));
|
loadEventInfo,
|
||||||
|
correctMediaLoadData(
|
||||||
|
mediaPeriod,
|
||||||
|
mediaLoadData,
|
||||||
|
checkNotNull(adPlaybackStates.get(mediaPeriod.mediaPeriodId.periodUid))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,7 +486,10 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
}
|
}
|
||||||
mediaPeriod.mediaSourceEventDispatcher.loadError(
|
mediaPeriod.mediaSourceEventDispatcher.loadError(
|
||||||
loadEventInfo,
|
loadEventInfo,
|
||||||
correctMediaLoadData(mediaPeriod, mediaLoadData, adPlaybackState),
|
correctMediaLoadData(
|
||||||
|
mediaPeriod,
|
||||||
|
mediaLoadData,
|
||||||
|
checkNotNull(adPlaybackStates.get(mediaPeriod.mediaPeriodId.periodUid))),
|
||||||
error,
|
error,
|
||||||
wasCanceled);
|
wasCanceled);
|
||||||
}
|
}
|
||||||
@ -457,7 +505,10 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
mediaSourceEventDispatcherWithoutId.upstreamDiscarded(mediaLoadData);
|
mediaSourceEventDispatcherWithoutId.upstreamDiscarded(mediaLoadData);
|
||||||
} else {
|
} else {
|
||||||
mediaPeriod.mediaSourceEventDispatcher.upstreamDiscarded(
|
mediaPeriod.mediaSourceEventDispatcher.upstreamDiscarded(
|
||||||
correctMediaLoadData(mediaPeriod, mediaLoadData, adPlaybackState));
|
correctMediaLoadData(
|
||||||
|
mediaPeriod,
|
||||||
|
mediaLoadData,
|
||||||
|
checkNotNull(adPlaybackStates.get(mediaPeriod.mediaPeriodId.periodUid))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -472,7 +523,10 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
} else {
|
} else {
|
||||||
mediaPeriod.sharedPeriod.onDownstreamFormatChanged(mediaPeriod, mediaLoadData);
|
mediaPeriod.sharedPeriod.onDownstreamFormatChanged(mediaPeriod, mediaLoadData);
|
||||||
mediaPeriod.mediaSourceEventDispatcher.downstreamFormatChanged(
|
mediaPeriod.mediaSourceEventDispatcher.downstreamFormatChanged(
|
||||||
correctMediaLoadData(mediaPeriod, mediaLoadData, adPlaybackState));
|
correctMediaLoadData(
|
||||||
|
mediaPeriod,
|
||||||
|
mediaLoadData,
|
||||||
|
checkNotNull(adPlaybackStates.get(mediaPeriod.mediaPeriodId.periodUid))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,7 +545,8 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
if (mediaPeriodId == null) {
|
if (mediaPeriodId == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
List<SharedMediaPeriod> periods = mediaPeriods.get(mediaPeriodId.windowSequenceNumber);
|
List<SharedMediaPeriod> periods =
|
||||||
|
mediaPeriods.get(new Pair<>(mediaPeriodId.windowSequenceNumber, mediaPeriodId.periodUid));
|
||||||
if (periods.isEmpty()) {
|
if (periods.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -560,6 +615,7 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
private final MediaPeriod actualMediaPeriod;
|
private final MediaPeriod actualMediaPeriod;
|
||||||
private final List<MediaPeriodImpl> mediaPeriods;
|
private final List<MediaPeriodImpl> mediaPeriods;
|
||||||
private final Map<Long, Pair<LoadEventInfo, MediaLoadData>> activeLoads;
|
private final Map<Long, Pair<LoadEventInfo, MediaLoadData>> activeLoads;
|
||||||
|
private final Object periodUid;
|
||||||
|
|
||||||
private AdPlaybackState adPlaybackState;
|
private AdPlaybackState adPlaybackState;
|
||||||
@Nullable private MediaPeriodImpl loadingPeriod;
|
@Nullable private MediaPeriodImpl loadingPeriod;
|
||||||
@ -569,8 +625,10 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
public @NullableType SampleStream[] sampleStreams;
|
public @NullableType SampleStream[] sampleStreams;
|
||||||
public @NullableType MediaLoadData[] lastDownstreamFormatChangeData;
|
public @NullableType MediaLoadData[] lastDownstreamFormatChangeData;
|
||||||
|
|
||||||
public SharedMediaPeriod(MediaPeriod actualMediaPeriod, AdPlaybackState adPlaybackState) {
|
public SharedMediaPeriod(
|
||||||
|
MediaPeriod actualMediaPeriod, Object periodUid, AdPlaybackState adPlaybackState) {
|
||||||
this.actualMediaPeriod = actualMediaPeriod;
|
this.actualMediaPeriod = actualMediaPeriod;
|
||||||
|
this.periodUid = periodUid;
|
||||||
this.adPlaybackState = adPlaybackState;
|
this.adPlaybackState = adPlaybackState;
|
||||||
mediaPeriods = new ArrayList<>();
|
mediaPeriods = new ArrayList<>();
|
||||||
activeLoads = new HashMap<>();
|
activeLoads = new HashMap<>();
|
||||||
@ -931,19 +989,27 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
|
|
||||||
private static final class ServerSideAdInsertionTimeline extends ForwardingTimeline {
|
private static final class ServerSideAdInsertionTimeline extends ForwardingTimeline {
|
||||||
|
|
||||||
private final AdPlaybackState adPlaybackState;
|
private final ImmutableMap<Object, AdPlaybackState> adPlaybackStates;
|
||||||
|
|
||||||
public ServerSideAdInsertionTimeline(
|
public ServerSideAdInsertionTimeline(
|
||||||
Timeline contentTimeline, AdPlaybackState adPlaybackState) {
|
Timeline contentTimeline, ImmutableMap<Object, AdPlaybackState> adPlaybackStates) {
|
||||||
super(contentTimeline);
|
super(contentTimeline);
|
||||||
Assertions.checkState(contentTimeline.getPeriodCount() == 1);
|
checkState(contentTimeline.getPeriodCount() == 1);
|
||||||
Assertions.checkState(contentTimeline.getWindowCount() == 1);
|
checkState(contentTimeline.getWindowCount() == 1);
|
||||||
this.adPlaybackState = adPlaybackState;
|
Period period = new Period();
|
||||||
|
for (int i = 0; i < contentTimeline.getPeriodCount(); i++) {
|
||||||
|
contentTimeline.getPeriod(/* periodIndex= */ i, period, /* setIds= */ true);
|
||||||
|
checkState(adPlaybackStates.containsKey(checkNotNull(period.uid)));
|
||||||
|
}
|
||||||
|
this.adPlaybackStates = adPlaybackStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
|
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
|
||||||
super.getWindow(windowIndex, window, defaultPositionProjectionUs);
|
super.getWindow(windowIndex, window, defaultPositionProjectionUs);
|
||||||
|
Object firstPeriodUid =
|
||||||
|
checkNotNull(getPeriod(/* periodIndex= */ 0, new Period(), /* setIds= */ true).uid);
|
||||||
|
AdPlaybackState adPlaybackState = checkNotNull(adPlaybackStates.get(firstPeriodUid));
|
||||||
long positionInPeriodUs =
|
long positionInPeriodUs =
|
||||||
getMediaPeriodPositionUsForContent(
|
getMediaPeriodPositionUsForContent(
|
||||||
window.positionInFirstPeriodUs,
|
window.positionInFirstPeriodUs,
|
||||||
@ -968,7 +1034,8 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||||
super.getPeriod(periodIndex, period, setIds);
|
super.getPeriod(periodIndex, period, /* setIds= */ true);
|
||||||
|
AdPlaybackState adPlaybackState = checkNotNull(adPlaybackStates.get(period.uid));
|
||||||
long durationUs = period.durationUs;
|
long durationUs = period.durationUs;
|
||||||
if (durationUs == C.TIME_UNSET) {
|
if (durationUs == C.TIME_UNSET) {
|
||||||
durationUs = adPlaybackState.contentDurationUs;
|
durationUs = adPlaybackState.contentDurationUs;
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.source.ads;
|
package androidx.media3.exoplayer.source.ads;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState;
|
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState;
|
||||||
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil;
|
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil;
|
||||||
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.playUntilPosition;
|
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.playUntilPosition;
|
||||||
@ -32,6 +33,7 @@ import static org.mockito.Mockito.verify;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.media3.common.AdPlaybackState;
|
import androidx.media3.common.AdPlaybackState;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
@ -50,7 +52,9 @@ import androidx.media3.test.utils.robolectric.PlaybackOutput;
|
|||||||
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@ -104,8 +108,8 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 400_000)
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 400_000)
|
||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 200_000);
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 200_000);
|
||||||
AtomicReference<Timeline> timelineReference = new AtomicReference<>();
|
AtomicReference<Timeline> timelineReference = new AtomicReference<>();
|
||||||
|
mediaSource.setAdPlaybackStates(ImmutableMap.of(new Pair<>(0, 0), adPlaybackState));
|
||||||
|
|
||||||
mediaSource.setAdPlaybackState(adPlaybackState);
|
|
||||||
mediaSource.prepareSource(
|
mediaSource.prepareSource(
|
||||||
(source, timeline) -> timelineReference.set(timeline),
|
(source, timeline) -> timelineReference.set(timeline),
|
||||||
/* mediaTransferListener= */ null,
|
/* mediaTransferListener= */ null,
|
||||||
@ -143,6 +147,26 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||||||
assertThat(window.durationUs).isEqualTo(9_800_000);
|
assertThat(window.durationUs).isEqualTo(9_800_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void timeline_missingAdPlaybackStateByPeriodUid_isAssertedAndThrows() {
|
||||||
|
ServerSideAdInsertionMediaSource mediaSource =
|
||||||
|
new ServerSideAdInsertionMediaSource(
|
||||||
|
new FakeMediaSource(), /* adPlaybackStateUpdater= */ null);
|
||||||
|
// The map of adPlaybackStates does not contain a valid period UID as key.
|
||||||
|
mediaSource.setAdPlaybackStates(
|
||||||
|
ImmutableMap.of(new Object(), new AdPlaybackState(/* adsId= */ new Object())));
|
||||||
|
|
||||||
|
Assert.assertThrows(
|
||||||
|
IllegalStateException.class,
|
||||||
|
() ->
|
||||||
|
mediaSource.prepareSource(
|
||||||
|
(source, timeline) -> {
|
||||||
|
/* Do nothing. */
|
||||||
|
},
|
||||||
|
/* mediaTransferListener= */ null,
|
||||||
|
PlayerId.UNSET));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void playbackWithPredefinedAds_playsSuccessfulWithoutRendererResets() throws Exception {
|
public void playbackWithPredefinedAds_playsSuccessfulWithoutRendererResets() throws Exception {
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
@ -154,10 +178,6 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||||
PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory);
|
PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory);
|
||||||
|
|
||||||
ServerSideAdInsertionMediaSource mediaSource =
|
|
||||||
new ServerSideAdInsertionMediaSource(
|
|
||||||
new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
|
|
||||||
/* adPlaybackStateUpdater= */ null);
|
|
||||||
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object());
|
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object());
|
||||||
adPlaybackState =
|
adPlaybackState =
|
||||||
addAdGroupToAdPlaybackState(
|
addAdGroupToAdPlaybackState(
|
||||||
@ -171,17 +191,32 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||||||
/* fromPositionUs= */ 400_000,
|
/* fromPositionUs= */ 400_000,
|
||||||
/* toPositionUs= */ 700_000,
|
/* toPositionUs= */ 700_000,
|
||||||
/* contentResumeOffsetUs= */ 1_000_000);
|
/* contentResumeOffsetUs= */ 1_000_000);
|
||||||
adPlaybackState =
|
AdPlaybackState firstAdPlaybackState =
|
||||||
addAdGroupToAdPlaybackState(
|
addAdGroupToAdPlaybackState(
|
||||||
adPlaybackState,
|
adPlaybackState,
|
||||||
/* fromPositionUs= */ 900_000,
|
/* fromPositionUs= */ 900_000,
|
||||||
/* toPositionUs= */ 1_000_000,
|
/* toPositionUs= */ 1_000_000,
|
||||||
/* contentResumeOffsetUs= */ 0);
|
/* contentResumeOffsetUs= */ 0);
|
||||||
mediaSource.setAdPlaybackState(adPlaybackState);
|
|
||||||
|
AtomicReference<ServerSideAdInsertionMediaSource> mediaSourceRef = new AtomicReference<>();
|
||||||
|
mediaSourceRef.set(
|
||||||
|
new ServerSideAdInsertionMediaSource(
|
||||||
|
new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
|
||||||
|
contentTimeline -> {
|
||||||
|
Object periodUid =
|
||||||
|
checkNotNull(
|
||||||
|
contentTimeline.getPeriod(
|
||||||
|
/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true)
|
||||||
|
.uid);
|
||||||
|
mediaSourceRef
|
||||||
|
.get()
|
||||||
|
.setAdPlaybackStates(ImmutableMap.of(periodUid, firstAdPlaybackState));
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
|
||||||
AnalyticsListener listener = mock(AnalyticsListener.class);
|
AnalyticsListener listener = mock(AnalyticsListener.class);
|
||||||
player.addAnalyticsListener(listener);
|
player.addAnalyticsListener(listener);
|
||||||
player.setMediaSource(mediaSource);
|
player.setMediaSource(mediaSourceRef.get());
|
||||||
player.prepare();
|
player.prepare();
|
||||||
player.play();
|
player.play();
|
||||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||||
@ -205,6 +240,7 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void playbackWithNewlyInsertedAds_playsSuccessfulWithoutRendererResets() throws Exception {
|
public void playbackWithNewlyInsertedAds_playsSuccessfulWithoutRendererResets() throws Exception {
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
AtomicReference<Object> periodUid = new AtomicReference<>();
|
||||||
CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(context);
|
CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(context);
|
||||||
ExoPlayer player =
|
ExoPlayer player =
|
||||||
new ExoPlayer.Builder(context, renderersFactory)
|
new ExoPlayer.Builder(context, renderersFactory)
|
||||||
@ -213,33 +249,43 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||||
PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory);
|
PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory);
|
||||||
|
|
||||||
ServerSideAdInsertionMediaSource mediaSource =
|
AdPlaybackState firstAdPlaybackState =
|
||||||
new ServerSideAdInsertionMediaSource(
|
|
||||||
new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
|
|
||||||
/* adPlaybackStateUpdater= */ null);
|
|
||||||
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object());
|
|
||||||
adPlaybackState =
|
|
||||||
addAdGroupToAdPlaybackState(
|
addAdGroupToAdPlaybackState(
|
||||||
adPlaybackState,
|
new AdPlaybackState(/* adsId= */ new Object()),
|
||||||
/* fromPositionUs= */ 900_000,
|
/* fromPositionUs= */ 900_000,
|
||||||
/* toPositionUs= */ 1_000_000,
|
/* toPositionUs= */ 1_000_000,
|
||||||
/* contentResumeOffsetUs= */ 0);
|
/* contentResumeOffsetUs= */ 0);
|
||||||
mediaSource.setAdPlaybackState(adPlaybackState);
|
AtomicReference<ServerSideAdInsertionMediaSource> mediaSourceRef = new AtomicReference<>();
|
||||||
|
mediaSourceRef.set(
|
||||||
|
new ServerSideAdInsertionMediaSource(
|
||||||
|
new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
|
||||||
|
/* adPlaybackStateUpdater= */ contentTimeline -> {
|
||||||
|
periodUid.set(
|
||||||
|
checkNotNull(
|
||||||
|
contentTimeline.getPeriod(
|
||||||
|
/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true)
|
||||||
|
.uid));
|
||||||
|
mediaSourceRef
|
||||||
|
.get()
|
||||||
|
.setAdPlaybackStates(ImmutableMap.of(periodUid.get(), firstAdPlaybackState));
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
AnalyticsListener listener = mock(AnalyticsListener.class);
|
AnalyticsListener listener = mock(AnalyticsListener.class);
|
||||||
player.addAnalyticsListener(listener);
|
player.addAnalyticsListener(listener);
|
||||||
player.setMediaSource(mediaSource);
|
player.setMediaSource(mediaSourceRef.get());
|
||||||
player.prepare();
|
player.prepare();
|
||||||
|
|
||||||
// Add ad at the current playback position during playback.
|
// Add ad at the current playback position during playback.
|
||||||
runUntilPlaybackState(player, Player.STATE_READY);
|
runUntilPlaybackState(player, Player.STATE_READY);
|
||||||
adPlaybackState =
|
AdPlaybackState secondAdPlaybackState =
|
||||||
addAdGroupToAdPlaybackState(
|
addAdGroupToAdPlaybackState(
|
||||||
adPlaybackState,
|
firstAdPlaybackState,
|
||||||
/* fromPositionUs= */ 0,
|
/* fromPositionUs= */ 0,
|
||||||
/* toPositionUs= */ 500_000,
|
/* toPositionUs= */ 500_000,
|
||||||
/* contentResumeOffsetUs= */ 0);
|
/* contentResumeOffsetUs= */ 0);
|
||||||
mediaSource.setAdPlaybackState(adPlaybackState);
|
mediaSourceRef
|
||||||
|
.get()
|
||||||
|
.setAdPlaybackStates(ImmutableMap.of(periodUid.get(), secondAdPlaybackState));
|
||||||
runUntilPendingCommandsAreFullyHandled(player);
|
runUntilPendingCommandsAreFullyHandled(player);
|
||||||
|
|
||||||
player.play();
|
player.play();
|
||||||
@ -265,6 +311,7 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||||||
public void playbackWithAdditionalAdsInAdGroup_playsSuccessfulWithoutRendererResets()
|
public void playbackWithAdditionalAdsInAdGroup_playsSuccessfulWithoutRendererResets()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
AtomicReference<Object> periodUid = new AtomicReference<>();
|
||||||
CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(context);
|
CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(context);
|
||||||
ExoPlayer player =
|
ExoPlayer player =
|
||||||
new ExoPlayer.Builder(context, renderersFactory)
|
new ExoPlayer.Builder(context, renderersFactory)
|
||||||
@ -273,32 +320,45 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||||
PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory);
|
PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory);
|
||||||
|
|
||||||
ServerSideAdInsertionMediaSource mediaSource =
|
AdPlaybackState firstAdPlaybackState =
|
||||||
new ServerSideAdInsertionMediaSource(
|
|
||||||
new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
|
|
||||||
/* adPlaybackStateUpdater= */ null);
|
|
||||||
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object());
|
|
||||||
adPlaybackState =
|
|
||||||
addAdGroupToAdPlaybackState(
|
addAdGroupToAdPlaybackState(
|
||||||
adPlaybackState,
|
new AdPlaybackState(/* adsId= */ new Object()),
|
||||||
/* fromPositionUs= */ 0,
|
/* fromPositionUs= */ 0,
|
||||||
/* toPositionUs= */ 500_000,
|
/* toPositionUs= */ 500_000,
|
||||||
/* contentResumeOffsetUs= */ 0);
|
/* contentResumeOffsetUs= */ 0);
|
||||||
mediaSource.setAdPlaybackState(adPlaybackState);
|
AtomicReference<ServerSideAdInsertionMediaSource> mediaSourceRef = new AtomicReference<>();
|
||||||
|
mediaSourceRef.set(
|
||||||
|
new ServerSideAdInsertionMediaSource(
|
||||||
|
new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
|
||||||
|
/* adPlaybackStateUpdater= */ contentTimeline -> {
|
||||||
|
if (periodUid.get() == null) {
|
||||||
|
periodUid.set(
|
||||||
|
checkNotNull(
|
||||||
|
contentTimeline.getPeriod(
|
||||||
|
/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true)
|
||||||
|
.uid));
|
||||||
|
mediaSourceRef
|
||||||
|
.get()
|
||||||
|
.setAdPlaybackStates(ImmutableMap.of(periodUid.get(), firstAdPlaybackState));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
|
||||||
AnalyticsListener listener = mock(AnalyticsListener.class);
|
AnalyticsListener listener = mock(AnalyticsListener.class);
|
||||||
player.addAnalyticsListener(listener);
|
player.addAnalyticsListener(listener);
|
||||||
player.setMediaSource(mediaSource);
|
player.setMediaSource(mediaSourceRef.get());
|
||||||
player.prepare();
|
player.prepare();
|
||||||
|
|
||||||
// Wait until playback is ready with first ad and then replace by 3 ads.
|
// Wait until playback is ready with first ad and then replace by 3 ads.
|
||||||
runUntilPlaybackState(player, Player.STATE_READY);
|
runUntilPlaybackState(player, Player.STATE_READY);
|
||||||
adPlaybackState =
|
AdPlaybackState secondAdPlaybackState =
|
||||||
adPlaybackState
|
firstAdPlaybackState
|
||||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3)
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3)
|
||||||
.withAdDurationsUs(
|
.withAdDurationsUs(
|
||||||
/* adGroupIndex= */ 0, /* adDurationsUs...= */ 50_000, 250_000, 200_000);
|
/* adGroupIndex= */ 0, /* adDurationsUs...= */ 50_000, 250_000, 200_000);
|
||||||
mediaSource.setAdPlaybackState(adPlaybackState);
|
mediaSourceRef
|
||||||
|
.get()
|
||||||
|
.setAdPlaybackStates(ImmutableMap.of(periodUid.get(), secondAdPlaybackState));
|
||||||
runUntilPendingCommandsAreFullyHandled(player);
|
runUntilPendingCommandsAreFullyHandled(player);
|
||||||
|
|
||||||
player.play();
|
player.play();
|
||||||
@ -327,10 +387,6 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||||||
new ExoPlayer.Builder(context).setClock(new FakeClock(/* isAutoAdvancing= */ true)).build();
|
new ExoPlayer.Builder(context).setClock(new FakeClock(/* isAutoAdvancing= */ true)).build();
|
||||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||||
|
|
||||||
ServerSideAdInsertionMediaSource mediaSource =
|
|
||||||
new ServerSideAdInsertionMediaSource(
|
|
||||||
new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
|
|
||||||
/* adPlaybackStateUpdater= */ null);
|
|
||||||
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object());
|
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object());
|
||||||
adPlaybackState =
|
adPlaybackState =
|
||||||
addAdGroupToAdPlaybackState(
|
addAdGroupToAdPlaybackState(
|
||||||
@ -344,17 +400,32 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||||||
/* fromPositionUs= */ 600_000,
|
/* fromPositionUs= */ 600_000,
|
||||||
/* toPositionUs= */ 700_000,
|
/* toPositionUs= */ 700_000,
|
||||||
/* contentResumeOffsetUs= */ 1_000_000);
|
/* contentResumeOffsetUs= */ 1_000_000);
|
||||||
adPlaybackState =
|
AdPlaybackState firstAdPlaybackState =
|
||||||
addAdGroupToAdPlaybackState(
|
addAdGroupToAdPlaybackState(
|
||||||
adPlaybackState,
|
adPlaybackState,
|
||||||
/* fromPositionUs= */ 900_000,
|
/* fromPositionUs= */ 900_000,
|
||||||
/* toPositionUs= */ 1_000_000,
|
/* toPositionUs= */ 1_000_000,
|
||||||
/* contentResumeOffsetUs= */ 0);
|
/* contentResumeOffsetUs= */ 0);
|
||||||
mediaSource.setAdPlaybackState(adPlaybackState);
|
|
||||||
|
AtomicReference<ServerSideAdInsertionMediaSource> mediaSourceRef = new AtomicReference<>();
|
||||||
|
mediaSourceRef.set(
|
||||||
|
new ServerSideAdInsertionMediaSource(
|
||||||
|
new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
|
||||||
|
/* adPlaybackStateUpdater= */ contentTimeline -> {
|
||||||
|
Object periodUid =
|
||||||
|
checkNotNull(
|
||||||
|
contentTimeline.getPeriod(
|
||||||
|
/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true)
|
||||||
|
.uid);
|
||||||
|
mediaSourceRef
|
||||||
|
.get()
|
||||||
|
.setAdPlaybackStates(ImmutableMap.of(periodUid, firstAdPlaybackState));
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
|
||||||
AnalyticsListener listener = mock(AnalyticsListener.class);
|
AnalyticsListener listener = mock(AnalyticsListener.class);
|
||||||
player.addAnalyticsListener(listener);
|
player.addAnalyticsListener(listener);
|
||||||
player.setMediaSource(mediaSource);
|
player.setMediaSource(mediaSourceRef.get());
|
||||||
player.prepare();
|
player.prepare();
|
||||||
// Play to the first content part, then seek past the midroll.
|
// Play to the first content part, then seek past the midroll.
|
||||||
playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 100);
|
playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 100);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user