Add preload pool to media period MediaPeriodQueue
The pool is created and the queue uses holder instances found to enqueue. No preloading is done yet though. PiperOrigin-RevId: 631053172
This commit is contained in:
parent
4a54db7cc7
commit
0ab6ea5668
@ -437,6 +437,31 @@ public interface ExoPlayer extends Player {
|
||||
default void onOffloadedPlayback(boolean isOffloadedPlayback) {}
|
||||
}
|
||||
|
||||
/** Configuration options for preloading playlist items. */
|
||||
@UnstableApi
|
||||
class PreloadConfiguration {
|
||||
|
||||
/** Default preload configuration that disables playlist preloading. */
|
||||
public static final PreloadConfiguration DEFAULT =
|
||||
new PreloadConfiguration(/* targetPreloadDurationUs= */ C.TIME_UNSET);
|
||||
|
||||
/**
|
||||
* The target duration to buffer when preloading, in microseconds or {@link C#TIME_UNSET} to
|
||||
* disable preloading.
|
||||
*/
|
||||
public final long targetPreloadDurationUs;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param targetPreloadDurationUs The target duration to preload, in microseconds or {@link
|
||||
* C#TIME_UNSET} to disable preloading.
|
||||
*/
|
||||
public PreloadConfiguration(long targetPreloadDurationUs) {
|
||||
this.targetPreloadDurationUs = targetPreloadDurationUs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link ExoPlayer} instances.
|
||||
*
|
||||
@ -1536,6 +1561,19 @@ public interface ExoPlayer extends Player {
|
||||
@UnstableApi
|
||||
void setShuffleOrder(ShuffleOrder shuffleOrder);
|
||||
|
||||
/**
|
||||
* Sets the {@linkplain PreloadConfiguration preload configuration} to configure playlist
|
||||
* preloading.
|
||||
*
|
||||
* @param preloadConfiguration The preload configuration.
|
||||
*/
|
||||
@UnstableApi
|
||||
void setPreloadConfiguration(PreloadConfiguration preloadConfiguration);
|
||||
|
||||
/** Returns the {@linkplain PreloadConfiguration preload configuration}. */
|
||||
@UnstableApi
|
||||
PreloadConfiguration getPreloadConfiguration();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
@ -195,6 +195,7 @@ import java.util.concurrent.TimeoutException;
|
||||
private boolean foregroundMode;
|
||||
private SeekParameters seekParameters;
|
||||
private ShuffleOrder shuffleOrder;
|
||||
private PreloadConfiguration preloadConfiguration;
|
||||
private boolean pauseAtEndOfMediaItems;
|
||||
private Commands availableCommands;
|
||||
private MediaMetadata mediaMetadata;
|
||||
@ -298,6 +299,7 @@ import java.util.concurrent.TimeoutException;
|
||||
audioOffloadListeners = new CopyOnWriteArraySet<>();
|
||||
mediaSourceHolderSnapshots = new ArrayList<>();
|
||||
shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
|
||||
preloadConfiguration = PreloadConfiguration.DEFAULT;
|
||||
emptyTrackSelectorResult =
|
||||
new TrackSelectorResult(
|
||||
new RendererConfiguration[renderers.length],
|
||||
@ -374,7 +376,8 @@ import java.util.concurrent.TimeoutException;
|
||||
clock,
|
||||
playbackInfoUpdateListener,
|
||||
playerId,
|
||||
builder.playbackLooper);
|
||||
builder.playbackLooper,
|
||||
preloadConfiguration);
|
||||
|
||||
volume = 1;
|
||||
repeatMode = Player.REPEAT_MODE_OFF;
|
||||
@ -881,6 +884,21 @@ import java.util.concurrent.TimeoutException;
|
||||
return shuffleModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreloadConfiguration(PreloadConfiguration preloadConfiguration) {
|
||||
verifyApplicationThread();
|
||||
if (this.preloadConfiguration.equals(preloadConfiguration)) {
|
||||
return;
|
||||
}
|
||||
this.preloadConfiguration = preloadConfiguration;
|
||||
internalPlayer.setPreloadConfiguration(preloadConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreloadConfiguration getPreloadConfiguration() {
|
||||
return preloadConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoading() {
|
||||
verifyApplicationThread();
|
||||
|
@ -57,6 +57,7 @@ import androidx.media3.common.util.TraceUtil;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSourceException;
|
||||
import androidx.media3.exoplayer.DefaultMediaClock.PlaybackParametersListener;
|
||||
import androidx.media3.exoplayer.ExoPlayer.PreloadConfiguration;
|
||||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||
import androidx.media3.exoplayer.drm.DrmSession;
|
||||
@ -171,6 +172,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
private static final int MSG_ATTEMPT_RENDERER_ERROR_RECOVERY = 25;
|
||||
private static final int MSG_RENDERER_CAPABILITIES_CHANGED = 26;
|
||||
private static final int MSG_UPDATE_MEDIA_SOURCES_WITH_MEDIA_ITEMS = 27;
|
||||
private static final int MSG_SET_PRELOAD_CONFIGURATION = 28;
|
||||
|
||||
private static final int ACTIVE_INTERVAL_MS = 10;
|
||||
private static final int IDLE_INTERVAL_MS = 1000;
|
||||
@ -237,6 +239,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@Nullable private ExoPlaybackException pendingRecoverableRendererError;
|
||||
private long setForegroundModeTimeoutMs;
|
||||
private long playbackMaybeBecameStuckAtMs;
|
||||
private PreloadConfiguration preloadConfiguration;
|
||||
private Timeline lastPreloadPoolInvalidationTimeline;
|
||||
|
||||
public ExoPlayerImplInternal(
|
||||
Renderer[] renderers,
|
||||
@ -255,7 +259,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
Clock clock,
|
||||
PlaybackInfoUpdateListener playbackInfoUpdateListener,
|
||||
PlayerId playerId,
|
||||
Looper playbackLooper) {
|
||||
Looper playbackLooper,
|
||||
PreloadConfiguration preloadConfiguration) {
|
||||
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
|
||||
this.renderers = renderers;
|
||||
this.trackSelector = trackSelector;
|
||||
@ -271,11 +276,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
this.pauseAtEndOfWindow = pauseAtEndOfWindow;
|
||||
this.clock = clock;
|
||||
this.playerId = playerId;
|
||||
this.preloadConfiguration = preloadConfiguration;
|
||||
|
||||
playbackMaybeBecameStuckAtMs = C.TIME_UNSET;
|
||||
lastRebufferRealtimeMs = C.TIME_UNSET;
|
||||
backBufferDurationUs = loadControl.getBackBufferDurationUs(playerId);
|
||||
retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe(playerId);
|
||||
lastPreloadPoolInvalidationTimeline = Timeline.EMPTY;
|
||||
|
||||
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
|
||||
playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo);
|
||||
@ -300,7 +307,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
deliverPendingMessageAtStartPositionRequired = true;
|
||||
|
||||
HandlerWrapper eventHandler = clock.createHandler(applicationLooper, /* callback= */ null);
|
||||
queue = new MediaPeriodQueue(analyticsCollector, eventHandler, this::createMediaPeriodHolder);
|
||||
queue =
|
||||
new MediaPeriodQueue(
|
||||
analyticsCollector, eventHandler, this::createMediaPeriodHolder, preloadConfiguration);
|
||||
mediaSourceList =
|
||||
new MediaSourceList(/* listener= */ this, analyticsCollector, eventHandler, playerId);
|
||||
|
||||
@ -359,6 +368,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
handler.obtainMessage(MSG_SET_SHUFFLE_ENABLED, shuffleModeEnabled ? 1 : 0, 0).sendToTarget();
|
||||
}
|
||||
|
||||
public void setPreloadConfiguration(PreloadConfiguration preloadConfiguration) {
|
||||
handler.obtainMessage(MSG_SET_PRELOAD_CONFIGURATION, preloadConfiguration).sendToTarget();
|
||||
}
|
||||
|
||||
public void seekTo(Timeline timeline, int windowIndex, long positionUs) {
|
||||
handler
|
||||
.obtainMessage(MSG_SEEK_TO, new SeekPosition(timeline, windowIndex, positionUs))
|
||||
@ -543,6 +556,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
case MSG_SET_SHUFFLE_ENABLED:
|
||||
setShuffleModeEnabledInternal(msg.arg1 != 0);
|
||||
break;
|
||||
case MSG_SET_PRELOAD_CONFIGURATION:
|
||||
setPreloadConfigurationInternal((PreloadConfiguration) msg.obj);
|
||||
break;
|
||||
case MSG_DO_SOME_WORK:
|
||||
doSomeWork();
|
||||
break;
|
||||
@ -930,6 +946,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
||||
}
|
||||
|
||||
private void setPreloadConfigurationInternal(PreloadConfiguration preloadConfiguration) {
|
||||
this.preloadConfiguration = preloadConfiguration;
|
||||
queue.updatePreloadConfiguration(playbackInfo.timeline, preloadConfiguration);
|
||||
}
|
||||
|
||||
private void seekToCurrentPosition(boolean sendDiscontinuity) throws ExoPlaybackException {
|
||||
// Renderers may have read from a period that's been removed. Seek back to the current
|
||||
// position of the playing period to make sure none of the removed period is played.
|
||||
@ -1605,6 +1626,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
/* positionUpdateTimeMs= */ 0,
|
||||
/* sleepingForOffload= */ false);
|
||||
if (releaseMediaSourceList) {
|
||||
queue.releasePreloadPool();
|
||||
mediaSourceList.release();
|
||||
}
|
||||
}
|
||||
@ -2137,13 +2159,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
// No periods available.
|
||||
return;
|
||||
}
|
||||
maybeUpdateLoadingPeriod();
|
||||
boolean loadingPeriodChanged = maybeUpdateLoadingPeriod();
|
||||
maybeUpdateReadingPeriod();
|
||||
maybeUpdateReadingRenderers();
|
||||
maybeUpdatePlayingPeriod();
|
||||
maybeUpdatePreloadPeriods(loadingPeriodChanged);
|
||||
}
|
||||
|
||||
private void maybeUpdateLoadingPeriod() throws ExoPlaybackException {
|
||||
private boolean maybeUpdateLoadingPeriod() throws ExoPlaybackException {
|
||||
boolean loadingPeriodChanged = false;
|
||||
queue.reevaluateBuffer(rendererPositionUs);
|
||||
if (queue.shouldLoadNextMediaPeriod()) {
|
||||
@Nullable
|
||||
@ -2155,6 +2179,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
resetRendererPosition(info.startPositionUs);
|
||||
}
|
||||
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
||||
loadingPeriodChanged = true;
|
||||
}
|
||||
}
|
||||
if (shouldContinueLoading) {
|
||||
@ -2165,6 +2190,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
} else {
|
||||
maybeContinueLoading();
|
||||
}
|
||||
return loadingPeriodChanged;
|
||||
}
|
||||
|
||||
private void maybeUpdateReadingPeriod() throws ExoPlaybackException {
|
||||
@ -2269,6 +2295,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdatePreloadPeriods(boolean loadingPeriodChanged) {
|
||||
if (preloadConfiguration.targetPreloadDurationUs == C.TIME_UNSET
|
||||
|| (!loadingPeriodChanged
|
||||
&& playbackInfo.timeline.equals(lastPreloadPoolInvalidationTimeline))) {
|
||||
// Do nothing if preloading disabled or no change in loading period or timeline has occurred.
|
||||
return;
|
||||
}
|
||||
lastPreloadPoolInvalidationTimeline = playbackInfo.timeline;
|
||||
queue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
}
|
||||
|
||||
private boolean replaceStreamsOrDisableRendererForTransition() throws ExoPlaybackException {
|
||||
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
|
||||
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer;
|
||||
|
||||
import static androidx.media3.exoplayer.MediaPeriodQueue.areDurationsCompatible;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@ -471,6 +472,12 @@ import androidx.media3.exoplayer.upstream.Allocator;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canBeUsedForMediaPeriodInfo(MediaPeriodInfo info) {
|
||||
return areDurationsCompatible(this.info.durationUs, info.durationUs)
|
||||
&& this.info.startPositionUs == info.startPositionUs
|
||||
&& this.info.id.equals(info.id);
|
||||
}
|
||||
|
||||
/* package */ interface Factory {
|
||||
MediaPeriodHolder create(MediaPeriodInfo info, long rendererPositionOffsetUs);
|
||||
}
|
||||
|
@ -28,10 +28,13 @@ import androidx.media3.common.Player.RepeatMode;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.HandlerWrapper;
|
||||
import androidx.media3.exoplayer.ExoPlayer.PreloadConfiguration;
|
||||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds a queue of media periods, from the currently playing media period at the front to the
|
||||
@ -82,6 +85,8 @@ import com.google.common.collect.ImmutableList;
|
||||
private int length;
|
||||
@Nullable private Object oldFrontPeriodUid;
|
||||
private long oldFrontPeriodWindowSequenceNumber;
|
||||
private PreloadConfiguration preloadConfiguration;
|
||||
private List<MediaPeriodHolder> preloadPriorityList;
|
||||
|
||||
/**
|
||||
* Creates a new media period queue.
|
||||
@ -94,12 +99,15 @@ import com.google.common.collect.ImmutableList;
|
||||
public MediaPeriodQueue(
|
||||
AnalyticsCollector analyticsCollector,
|
||||
HandlerWrapper analyticsCollectorHandler,
|
||||
MediaPeriodHolder.Factory mediaPeriodHolderFactory) {
|
||||
MediaPeriodHolder.Factory mediaPeriodHolderFactory,
|
||||
PreloadConfiguration preloadConfiguration) {
|
||||
this.analyticsCollector = analyticsCollector;
|
||||
this.analyticsCollectorHandler = analyticsCollectorHandler;
|
||||
this.mediaPeriodHolderFactory = mediaPeriodHolderFactory;
|
||||
this.preloadConfiguration = preloadConfiguration;
|
||||
period = new Timeline.Period();
|
||||
window = new Timeline.Window();
|
||||
preloadPriorityList = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,6 +136,18 @@ import com.google.common.collect.ImmutableList;
|
||||
return updateForPlaybackModeChange(timeline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the preload configuration.
|
||||
*
|
||||
* @param timeline The current timeline.
|
||||
* @param preloadConfiguration The new preload configuration.
|
||||
*/
|
||||
public void updatePreloadConfiguration(
|
||||
Timeline timeline, PreloadConfiguration preloadConfiguration) {
|
||||
this.preloadConfiguration = preloadConfiguration;
|
||||
invalidatePreloadPool(timeline);
|
||||
}
|
||||
|
||||
/** Returns whether {@code mediaPeriod} is the current loading media period. */
|
||||
public boolean isLoading(MediaPeriod mediaPeriod) {
|
||||
return loading != null && loading.mediaPeriod == mediaPeriod;
|
||||
@ -180,8 +200,13 @@ import com.google.common.collect.ImmutableList;
|
||||
loading == null
|
||||
? INITIAL_RENDERER_POSITION_OFFSET_US
|
||||
: (loading.getRendererOffset() + loading.info.durationUs - info.startPositionUs);
|
||||
MediaPeriodHolder newPeriodHolder =
|
||||
mediaPeriodHolderFactory.create(info, rendererPositionOffsetUs);
|
||||
@Nullable MediaPeriodHolder newPeriodHolder = removePreloadedMediaPeriodHolder(info);
|
||||
if (newPeriodHolder == null) {
|
||||
newPeriodHolder = mediaPeriodHolderFactory.create(info, rendererPositionOffsetUs);
|
||||
} else {
|
||||
newPeriodHolder.info = info;
|
||||
newPeriodHolder.setRendererOffset(rendererPositionOffsetUs);
|
||||
}
|
||||
if (loading != null) {
|
||||
loading.setNext(newPeriodHolder);
|
||||
} else {
|
||||
@ -195,6 +220,110 @@ import com.google.common.collect.ImmutableList;
|
||||
return newPeriodHolder;
|
||||
}
|
||||
|
||||
/** Invalidates the preload pool. */
|
||||
public void invalidatePreloadPool(Timeline timeline) {
|
||||
if (preloadConfiguration.targetPreloadDurationUs == C.TIME_UNSET || loading == null) {
|
||||
releasePreloadPool();
|
||||
return;
|
||||
}
|
||||
MediaPeriodHolder loading = this.loading;
|
||||
List<MediaPeriodHolder> newPreloadPriorityList = new ArrayList<>();
|
||||
Pair<Object, Long> defaultPositionOfNextWindow =
|
||||
getDefaultPeriodPositionOfNextWindow(
|
||||
timeline, loading.info.id.periodUid, /* defaultPositionProjectionUs= */ 0L);
|
||||
if (defaultPositionOfNextWindow != null
|
||||
&& !timeline
|
||||
.getWindow(
|
||||
timeline.getPeriodByUid(defaultPositionOfNextWindow.first, period).windowIndex,
|
||||
window)
|
||||
.isLive()) {
|
||||
long windowSequenceNumber =
|
||||
resolvePeriodUidToWindowSequenceNumberInPreloadPeriods(defaultPositionOfNextWindow.first);
|
||||
if (windowSequenceNumber == C.INDEX_UNSET) {
|
||||
windowSequenceNumber = nextWindowSequenceNumber++;
|
||||
}
|
||||
@Nullable
|
||||
MediaPeriodInfo nextInfo =
|
||||
getMediaPeriodInfoForPeriodPosition(
|
||||
timeline,
|
||||
defaultPositionOfNextWindow.first,
|
||||
defaultPositionOfNextWindow.second,
|
||||
windowSequenceNumber);
|
||||
@Nullable
|
||||
MediaPeriodHolder nextMediaPeriodHolder = removePreloadedMediaPeriodHolder(nextInfo);
|
||||
if (nextMediaPeriodHolder == null) {
|
||||
// The holder's renderer position offset may be different and is reset when enqueuing.
|
||||
long rendererPositionOffsetUs =
|
||||
loading.getRendererOffset() + loading.info.durationUs - nextInfo.startPositionUs;
|
||||
nextMediaPeriodHolder = mediaPeriodHolderFactory.create(nextInfo, rendererPositionOffsetUs);
|
||||
}
|
||||
newPreloadPriorityList.add(nextMediaPeriodHolder);
|
||||
}
|
||||
releaseAndResetPreloadPriorityList(newPreloadPriorityList);
|
||||
}
|
||||
|
||||
/** Removes all periods from the preload pool and releases them. */
|
||||
public void releasePreloadPool() {
|
||||
if (!preloadPriorityList.isEmpty()) {
|
||||
releaseAndResetPreloadPriorityList(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MediaPeriodHolder removePreloadedMediaPeriodHolder(MediaPeriodInfo info) {
|
||||
for (int i = 0; i < preloadPriorityList.size(); i++) {
|
||||
MediaPeriodHolder mediaPeriodHolder = preloadPriorityList.get(i);
|
||||
if (mediaPeriodHolder.canBeUsedForMediaPeriodInfo(info)) {
|
||||
return preloadPriorityList.remove(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void releaseAndResetPreloadPriorityList(List<MediaPeriodHolder> newPriorityList) {
|
||||
for (int i = 0; i < preloadPriorityList.size(); i++) {
|
||||
preloadPriorityList.get(i).release();
|
||||
}
|
||||
preloadPriorityList = newPriorityList;
|
||||
}
|
||||
|
||||
private MediaPeriodInfo getMediaPeriodInfoForPeriodPosition(
|
||||
Timeline timeline, Object periodUid, long positionUs, long windowSequenceNumber) {
|
||||
MediaPeriodId mediaPeriodId =
|
||||
resolveMediaPeriodIdForAds(
|
||||
timeline, periodUid, positionUs, windowSequenceNumber, window, period);
|
||||
return mediaPeriodId.isAd()
|
||||
? getMediaPeriodInfoForAd(
|
||||
timeline,
|
||||
mediaPeriodId.periodUid,
|
||||
mediaPeriodId.adGroupIndex,
|
||||
mediaPeriodId.adIndexInAdGroup,
|
||||
/* contentPositionUs= */ positionUs,
|
||||
mediaPeriodId.windowSequenceNumber)
|
||||
: getMediaPeriodInfoForContent(
|
||||
timeline,
|
||||
mediaPeriodId.periodUid,
|
||||
/* startPositionUs= */ positionUs,
|
||||
/* requestedContentPositionUs= */ C.TIME_UNSET,
|
||||
mediaPeriodId.windowSequenceNumber);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Pair<Object, Long> getDefaultPeriodPositionOfNextWindow(
|
||||
Timeline timeline, Object periodUid, long defaultPositionProjectionUs) {
|
||||
int nextWindowIndex =
|
||||
timeline.getNextWindowIndex(
|
||||
timeline.getPeriodByUid(periodUid, period).windowIndex, repeatMode, shuffleModeEnabled);
|
||||
return nextWindowIndex != C.INDEX_UNSET
|
||||
? timeline.getPeriodPositionUs(
|
||||
window,
|
||||
period,
|
||||
nextWindowIndex,
|
||||
/* windowPositionUs= */ C.TIME_UNSET,
|
||||
defaultPositionProjectionUs)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the loading period holder which is at the end of the queue, or null if the queue is
|
||||
* empty.
|
||||
@ -430,7 +559,7 @@ import com.google.common.collect.ImmutableList;
|
||||
*/
|
||||
public MediaPeriodId resolveMediaPeriodIdForAds(
|
||||
Timeline timeline, Object periodUid, long positionUs) {
|
||||
long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(timeline, periodUid);
|
||||
long windowSequenceNumber = resolvePeriodUidToWindowSequenceNumber(timeline, periodUid);
|
||||
return resolveMediaPeriodIdForAds(
|
||||
timeline, periodUid, positionUs, windowSequenceNumber, window, period);
|
||||
}
|
||||
@ -507,7 +636,7 @@ import com.google.common.collect.ImmutableList;
|
||||
*/
|
||||
public MediaPeriodId resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
Timeline timeline, Object periodUid, long positionUs) {
|
||||
long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(timeline, periodUid);
|
||||
long windowSequenceNumber = resolvePeriodUidToWindowSequenceNumber(timeline, periodUid);
|
||||
// Check for preceding ad periods in multi-period window.
|
||||
timeline.getPeriodByUid(periodUid, period);
|
||||
timeline.getWindow(period.windowIndex, window);
|
||||
@ -553,7 +682,7 @@ import com.google.common.collect.ImmutableList;
|
||||
* @param periodUid The uid of the timeline period.
|
||||
* @return A window sequence number for a media period created for this timeline period.
|
||||
*/
|
||||
private long resolvePeriodIndexToWindowSequenceNumber(Timeline timeline, Object periodUid) {
|
||||
private long resolvePeriodUidToWindowSequenceNumber(Timeline timeline, Object periodUid) {
|
||||
int windowIndex = timeline.getPeriodByUid(periodUid, period).windowIndex;
|
||||
if (oldFrontPeriodUid != null) {
|
||||
int oldFrontPeriodIndex = timeline.getIndexOfPeriod(oldFrontPeriodUid);
|
||||
@ -585,8 +714,14 @@ import com.google.common.collect.ImmutableList;
|
||||
}
|
||||
mediaPeriodHolder = mediaPeriodHolder.getNext();
|
||||
}
|
||||
|
||||
long windowSequenceNumber = resolvePeriodUidToWindowSequenceNumberInPreloadPeriods(periodUid);
|
||||
if (windowSequenceNumber != C.INDEX_UNSET) {
|
||||
return windowSequenceNumber;
|
||||
}
|
||||
|
||||
// If no match is found, create new sequence number.
|
||||
long windowSequenceNumber = nextWindowSequenceNumber++;
|
||||
windowSequenceNumber = nextWindowSequenceNumber++;
|
||||
if (playing == null) {
|
||||
// If the queue is empty, save it as old front uid to allow later reuse.
|
||||
oldFrontPeriodUid = periodUid;
|
||||
@ -595,6 +730,16 @@ import com.google.common.collect.ImmutableList;
|
||||
return windowSequenceNumber;
|
||||
}
|
||||
|
||||
private long resolvePeriodUidToWindowSequenceNumberInPreloadPeriods(Object periodUid) {
|
||||
for (int i = 0; i < preloadPriorityList.size(); i++) {
|
||||
MediaPeriodHolder preloadHolder = preloadPriorityList.get(i);
|
||||
if (preloadHolder.uid.equals(periodUid)) {
|
||||
return preloadHolder.info.id.windowSequenceNumber;
|
||||
}
|
||||
}
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a period described by {@code oldInfo} can be kept for playing the media period
|
||||
* described by {@code newInfo}.
|
||||
@ -606,7 +751,7 @@ import com.google.common.collect.ImmutableList;
|
||||
/**
|
||||
* Returns whether a duration change of a period is compatible with keeping the following periods.
|
||||
*/
|
||||
private boolean areDurationsCompatible(long previousDurationUs, long newDurationUs) {
|
||||
/* package */ static boolean areDurationsCompatible(long previousDurationUs, long newDurationUs) {
|
||||
return previousDurationUs == C.TIME_UNSET || previousDurationUs == newDurationUs;
|
||||
}
|
||||
|
||||
@ -649,7 +794,6 @@ import com.google.common.collect.ImmutableList;
|
||||
|
||||
// Update the period info for the last holder, as it may now be the last period in the timeline.
|
||||
lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(timeline, lastValidPeriodHolder.info);
|
||||
|
||||
// If renderers may have read from a period that's been removed, it is necessary to restart.
|
||||
return !readingPeriodRemoved;
|
||||
}
|
||||
@ -746,7 +890,12 @@ import com.google.common.collect.ImmutableList;
|
||||
if (nextMediaPeriodHolder != null && nextMediaPeriodHolder.uid.equals(nextPeriodUid)) {
|
||||
windowSequenceNumber = nextMediaPeriodHolder.info.id.windowSequenceNumber;
|
||||
} else {
|
||||
windowSequenceNumber = nextWindowSequenceNumber++;
|
||||
long windowSequenceNumberFromPreload =
|
||||
resolvePeriodUidToWindowSequenceNumberInPreloadPeriods(nextPeriodUid);
|
||||
windowSequenceNumber =
|
||||
windowSequenceNumberFromPreload == C.INDEX_UNSET
|
||||
? nextWindowSequenceNumber++
|
||||
: windowSequenceNumberFromPreload;
|
||||
}
|
||||
}
|
||||
|
||||
@ -874,7 +1023,6 @@ import com.google.common.collect.ImmutableList;
|
||||
&& (adGroupCount > 1 || period.getAdGroupTimeUs(firstAdGroupIndex) != C.TIME_END_OF_SOURCE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MediaPeriodInfo getMediaPeriodInfo(
|
||||
Timeline timeline, MediaPeriodId id, long requestedContentPositionUs, long startPositionUs) {
|
||||
timeline.getPeriodByUid(id.periodUid, period);
|
||||
|
@ -992,6 +992,18 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
player.setRepeatMode(repeatMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreloadConfiguration(PreloadConfiguration preloadConfiguration) {
|
||||
blockUntilConstructorFinished();
|
||||
player.setPreloadConfiguration(preloadConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreloadConfiguration getPreloadConfiguration() {
|
||||
blockUntilConstructorFinished();
|
||||
return player.getPreloadConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
|
||||
blockUntilConstructorFinished();
|
||||
|
@ -40,9 +40,11 @@ import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.HandlerWrapper;
|
||||
import androidx.media3.exoplayer.ExoPlayer.PreloadConfiguration;
|
||||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||
import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector;
|
||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaSourceCaller;
|
||||
import androidx.media3.exoplayer.source.SinglePeriodTimeline;
|
||||
@ -61,7 +63,10 @@ import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -95,7 +100,9 @@ public final class MediaPeriodQueueTest {
|
||||
private TrackSelector trackSelector;
|
||||
private Allocator allocator;
|
||||
private MediaSourceList mediaSourceList;
|
||||
private FakeMediaSource fakeMediaSource;
|
||||
private List<FakeMediaSource> fakeMediaSources;
|
||||
private ArrayList<MediaPeriodInfo> mediaPeriodHolderFactoryInfos;
|
||||
private ArrayList<Long> mediaPeriodHolderFactoryRendererPositionOffsets;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@ -105,23 +112,29 @@ public final class MediaPeriodQueueTest {
|
||||
Looper.getMainLooper());
|
||||
HandlerWrapper handler =
|
||||
Clock.DEFAULT.createHandler(Looper.getMainLooper(), /* callback= */ null);
|
||||
mediaPeriodHolderFactoryInfos = new ArrayList<>();
|
||||
mediaPeriodHolderFactoryRendererPositionOffsets = new ArrayList<>();
|
||||
mediaPeriodQueue =
|
||||
new MediaPeriodQueue(
|
||||
analyticsCollector,
|
||||
handler,
|
||||
(info, rendererPositionOffsetUs) ->
|
||||
new MediaPeriodHolder(
|
||||
rendererCapabilities,
|
||||
rendererPositionOffsetUs,
|
||||
trackSelector,
|
||||
allocator,
|
||||
mediaSourceList,
|
||||
info,
|
||||
new TrackSelectorResult(
|
||||
new RendererConfiguration[0],
|
||||
new ExoTrackSelection[0],
|
||||
Tracks.EMPTY,
|
||||
/* info= */ null)));
|
||||
(info, rendererPositionOffsetUs) -> {
|
||||
mediaPeriodHolderFactoryInfos.add(info);
|
||||
mediaPeriodHolderFactoryRendererPositionOffsets.add(rendererPositionOffsetUs);
|
||||
return new MediaPeriodHolder(
|
||||
rendererCapabilities,
|
||||
rendererPositionOffsetUs,
|
||||
trackSelector,
|
||||
allocator,
|
||||
mediaSourceList,
|
||||
info,
|
||||
new TrackSelectorResult(
|
||||
new RendererConfiguration[0],
|
||||
new ExoTrackSelection[0],
|
||||
Tracks.EMPTY,
|
||||
/* info= */ null));
|
||||
},
|
||||
PreloadConfiguration.DEFAULT);
|
||||
mediaSourceList =
|
||||
new MediaSourceList(
|
||||
mock(MediaSourceList.MediaSourceListInfoRefreshListener.class),
|
||||
@ -131,6 +144,7 @@ public final class MediaPeriodQueueTest {
|
||||
rendererCapabilities = new RendererCapabilities[0];
|
||||
trackSelector = mock(TrackSelector.class);
|
||||
allocator = mock(Allocator.class);
|
||||
fakeMediaSources = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -305,7 +319,7 @@ public final class MediaPeriodQueueTest {
|
||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 4000);
|
||||
SinglePeriodAdTimeline adTimeline =
|
||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||
setupTimeline(adTimeline);
|
||||
setupTimelines(adTimeline);
|
||||
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||
assertNextMediaPeriodInfoIsAd(
|
||||
@ -382,7 +396,7 @@ public final class MediaPeriodQueueTest {
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true);
|
||||
SinglePeriodAdTimeline adTimeline =
|
||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||
setupTimeline(adTimeline);
|
||||
setupTimelines(adTimeline);
|
||||
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||
assertNextMediaPeriodInfoIsAd(
|
||||
@ -463,7 +477,7 @@ public final class MediaPeriodQueueTest {
|
||||
/* isContentTimeline= */ true,
|
||||
/* populateAds= */ false,
|
||||
/* playedAds= */ false);
|
||||
setupTimeline(multiPeriodLiveTimeline);
|
||||
setupTimelines(multiPeriodLiveTimeline);
|
||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||
/* periodUid= */ firstPeriodUid,
|
||||
/* startPositionUs= */ 0,
|
||||
@ -533,7 +547,7 @@ public final class MediaPeriodQueueTest {
|
||||
/* isContentTimeline= */ false,
|
||||
/* populateAds= */ false,
|
||||
/* playedAds= */ false);
|
||||
setupTimeline(multiPeriodLiveTimeline);
|
||||
setupTimelines(multiPeriodLiveTimeline);
|
||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||
/* periodUid= */ firstPeriodUid,
|
||||
/* startPositionUs= */ 0,
|
||||
@ -602,7 +616,7 @@ public final class MediaPeriodQueueTest {
|
||||
/* isContentTimeline= */ false,
|
||||
/* populateAds= */ true,
|
||||
/* playedAds= */ false);
|
||||
setupTimeline(multiPeriodLiveTimeline);
|
||||
setupTimelines(multiPeriodLiveTimeline);
|
||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||
/* periodUid= */ firstPeriodUid,
|
||||
/* startPositionUs= */ 0,
|
||||
@ -686,7 +700,7 @@ public final class MediaPeriodQueueTest {
|
||||
/* isContentTimeline= */ false,
|
||||
/* populateAds= */ true,
|
||||
/* playedAds= */ true);
|
||||
setupTimeline(multiPeriodLiveTimeline);
|
||||
setupTimelines(multiPeriodLiveTimeline);
|
||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||
/* periodUid= */ firstPeriodUid,
|
||||
/* startPositionUs= */ 0,
|
||||
@ -798,7 +812,7 @@ public final class MediaPeriodQueueTest {
|
||||
|
||||
@Test
|
||||
public void getNextMediaPeriodInfo_inMultiPeriodWindow_returnsCorrectMediaPeriodInfos() {
|
||||
setupTimeline(
|
||||
setupTimelines(
|
||||
new FakeTimeline(
|
||||
new TimelineWindowDefinition(
|
||||
/* periodCount= */ 2,
|
||||
@ -896,7 +910,7 @@ public final class MediaPeriodQueueTest {
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true);
|
||||
SinglePeriodAdTimeline adTimeline =
|
||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||
setupTimeline(adTimeline);
|
||||
setupTimelines(adTimeline);
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||
enqueueNext(); // Content before ad.
|
||||
enqueueNext(); // Ad.
|
||||
@ -907,7 +921,7 @@ public final class MediaPeriodQueueTest {
|
||||
new AdPlaybackState(
|
||||
/* adsId= */ new Object(), /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US - 2000)
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true);
|
||||
updateTimeline();
|
||||
updateAdTimeline(/* mediaSourceIndex= */ 0);
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||
long maxRendererReadPositionUs =
|
||||
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 1000;
|
||||
@ -1397,23 +1411,406 @@ public final class MediaPeriodQueueTest {
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidatePreloadPool_withThreeWindowsPreloadEnabled_preloadHoldersCreated() {
|
||||
setupTimelines(new FakeTimeline(), new FakeTimeline(), new FakeTimeline());
|
||||
mediaPeriodQueue.updatePreloadConfiguration(
|
||||
playbackInfo.timeline, new PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L));
|
||||
|
||||
// Creates period of first window for enqueuing.
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(1);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets).containsExactly(1_000_000_000_000L);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(0));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.windowSequenceNumber).isEqualTo(0);
|
||||
|
||||
// Creates period of second window for preloading.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(2);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(1_000_000_000_000L, 1_000_010_000_000L)
|
||||
.inOrder();
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(1));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.windowSequenceNumber).isEqualTo(1);
|
||||
|
||||
// Enqueue period of second window from preload pool.
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(2);
|
||||
|
||||
// Creates period of third window for preloading.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(3);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(1_000_000_000_000L, 1_000_010_000_000L, 1_000_020_000_000L)
|
||||
.inOrder();
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(2).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(2));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(2).id.windowSequenceNumber).isEqualTo(2);
|
||||
|
||||
// Enqueue period of third window from preload pool.
|
||||
enqueueNext();
|
||||
// No further next window. Invalidating is a no-op.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidatePreloadPool_withThreeWindowsPreloadDisabled_preloadHoldersNotCreated() {
|
||||
List<MediaPeriod> releasedMediaPeriods = new ArrayList<>();
|
||||
FakeMediaSource fakeMediaSource =
|
||||
new FakeMediaSource() {
|
||||
@Override
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||
releasedMediaPeriods.add(mediaPeriod);
|
||||
super.releasePeriod(mediaPeriod);
|
||||
}
|
||||
};
|
||||
setupMediaSources(fakeMediaSource, fakeMediaSource, fakeMediaSource);
|
||||
mediaPeriodQueue.updatePreloadConfiguration(
|
||||
playbackInfo.timeline, PreloadConfiguration.DEFAULT);
|
||||
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(1);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets).containsExactly(1_000_000_000_000L);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(0));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.windowSequenceNumber).isEqualTo(0);
|
||||
|
||||
// Expect no-op.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(1);
|
||||
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(2);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(1_000_000_000_000L, 1_000_010_000_000L)
|
||||
.inOrder();
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(1));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.windowSequenceNumber).isEqualTo(1);
|
||||
assertThat(releasedMediaPeriods).isEmpty();
|
||||
|
||||
// Expect no-op.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(2);
|
||||
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(3);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(1_000_000_000_000L, 1_000_010_000_000L, 1_000_020_000_000L)
|
||||
.inOrder();
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(2).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(2));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(2).id.windowSequenceNumber).isEqualTo(2);
|
||||
assertThat(releasedMediaPeriods).isEmpty();
|
||||
|
||||
// Expect no-op.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
invalidatePreloadPool_secondWindowIsLivePreloadEnabled_preloadHolderForLiveNotCreated() {
|
||||
TimelineWindowDefinition liveWindow =
|
||||
new TimelineWindowDefinition(
|
||||
/* periodCount= */ 1,
|
||||
/* id= */ 1234,
|
||||
/* isSeekable= */ false,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* isPlaceholder= */ false,
|
||||
/* durationUs= */ DEFAULT_WINDOW_DURATION_US,
|
||||
/* defaultPositionUs= */ 0,
|
||||
/* windowOffsetInFirstPeriodUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||
/* adPlaybackStates= */ ImmutableList.of(AdPlaybackState.NONE),
|
||||
MediaItem.EMPTY);
|
||||
setupTimelines(new FakeTimeline(), new FakeTimeline(liveWindow));
|
||||
mediaPeriodQueue.updatePreloadConfiguration(
|
||||
playbackInfo.timeline, new PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L));
|
||||
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(1);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets).containsExactly(1_000_000_000_000L);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(0));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.windowSequenceNumber).isEqualTo(0);
|
||||
|
||||
// Expected to be a no-op for live.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(1);
|
||||
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(2);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(1_000_000_000_000L, 1_000_010_000_000L)
|
||||
.inOrder();
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(1));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.windowSequenceNumber).isEqualTo(1);
|
||||
|
||||
// Expected to be a no-op for last window.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
invalidatePreloadPool_windowWithTwoPeriodsPreloadEnabled_preloadHolderForThirdPeriodCreated() {
|
||||
TimelineWindowDefinition window1 =
|
||||
new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ 1234);
|
||||
setupTimelines(new FakeTimeline(window1), new FakeTimeline());
|
||||
mediaPeriodQueue.updatePreloadConfiguration(
|
||||
playbackInfo.timeline, new PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L));
|
||||
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(1);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets).containsExactly(1_000_000_000_000L);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 0));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.windowSequenceNumber).isEqualTo(0);
|
||||
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(2);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(1_000_000_000_000L, 1_000_005_000_000L)
|
||||
.inOrder();
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 2));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.windowSequenceNumber).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
invalidatePreloadPool_withThreeWindowsWithAdsInSecondPreloadEnabled_preloadHolderCreatedForPreroll() {
|
||||
AdPlaybackState adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ new Object(), 0L, 5_000_000L)
|
||||
.withAdCount(/* adGroupIndex= */ 0, 1)
|
||||
.withAdCount(/* adGroupIndex= */ 1, 1)
|
||||
.withAdDurationsUs(/* adGroupIndex= */ 0, 2_000L)
|
||||
.withAdDurationsUs(/* adGroupIndex= */ 1, 1_000L)
|
||||
.withContentDurationUs(CONTENT_DURATION_US);
|
||||
SinglePeriodAdTimeline adTimeline =
|
||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||
setupMediaSources(
|
||||
new FakeMediaSource(), new FakeMediaSource(adTimeline), new FakeMediaSource());
|
||||
mediaPeriodQueue.updatePreloadConfiguration(
|
||||
playbackInfo.timeline, new PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L));
|
||||
|
||||
// Creates the first and only period of the first window for enqueuing.
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(1);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets).containsExactly(1_000_000_000_000L);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.windowSequenceNumber).isEqualTo(0);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(0));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(0).id.nextAdGroupIndex).isEqualTo(-1);
|
||||
|
||||
// Creates the pre-roll period of the 2nd window for preload.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(2);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(1_000_000_000_000L, 1_000_133_000_000L)
|
||||
.inOrder();
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.windowSequenceNumber).isEqualTo(1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 1));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.adGroupIndex).isEqualTo(0);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(1).id.nextAdGroupIndex).isEqualTo(-1);
|
||||
|
||||
// Enqueue the pre-roll period from pool.
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(2);
|
||||
|
||||
// Creates the first content period of the 3rd window for preloading.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(3);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(
|
||||
1_000_000_000_000L,
|
||||
1_000_133_000_000L,
|
||||
1_000_133_000_000L + 2_000 - DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US)
|
||||
.inOrder();
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(2).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 2));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(2).id.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(2).id.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(2).id.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(2).id.windowSequenceNumber).isEqualTo(2);
|
||||
|
||||
// Creates the first content period of the 2nd window for enqueueing.
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(4);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(
|
||||
1_000_000_000_000L,
|
||||
1_000_133_000_000L,
|
||||
1_000_133_000_000L + 2_000L - DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||
1_000_133_000_000L + 2_000L)
|
||||
.inOrder();
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(3).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 1));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(3).id.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(3).id.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(3).id.nextAdGroupIndex).isEqualTo(1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(3).id.windowSequenceNumber).isEqualTo(1);
|
||||
|
||||
// Invalidating does keep the same state and does not create further periods.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(4);
|
||||
|
||||
// Creates the mid-roll ad period of the 2nd window for enqueueing.
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(5);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(
|
||||
1_000_000_000_000L,
|
||||
1_000_133_000_000L,
|
||||
1_000_133_002_000L - DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||
1_000_133_002_000L,
|
||||
1_000_138_002_000L);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(4).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 1));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(4).id.adGroupIndex).isEqualTo(1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(4).id.adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(4).id.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(4).id.windowSequenceNumber).isEqualTo(1);
|
||||
|
||||
// Invalidating does keep the same state and does not create further periods.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(5);
|
||||
|
||||
// Creates the last content period of the 2nd window for enqueueing.
|
||||
enqueueNext();
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(6);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(
|
||||
1_000_000_000_000L,
|
||||
1_000_133_000_000L,
|
||||
1_000_133_002_000L - DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||
1_000_133_002_000L,
|
||||
1_000_138_002_000L,
|
||||
1_000_133_003_000L);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(5).id.periodUid)
|
||||
.isEqualTo(playbackInfo.timeline.getUidOfPeriod(/* periodIndex= */ 1));
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(5).id.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(5).id.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(5).id.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodHolderFactoryInfos.get(5).id.windowSequenceNumber).isEqualTo(1);
|
||||
|
||||
// Invalidating does keep the same state and does not create further periods.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
// Enqueue the first and only content period of the 3rd and last window from pool.
|
||||
enqueueNext();
|
||||
// No further next window. Invalidating is a no-op.
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryInfos).hasSize(6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPreloadConfiguration_disablePreloading_releasesPreloadHolders() {
|
||||
AtomicBoolean releaseCalled = new AtomicBoolean();
|
||||
FakeMediaSource preloadedSource =
|
||||
new FakeMediaSource(
|
||||
new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 1, "1234"))) {
|
||||
@Override
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||
releaseCalled.set(true);
|
||||
super.releasePeriod(mediaPeriod);
|
||||
}
|
||||
};
|
||||
setupMediaSources(new FakeMediaSource(), preloadedSource);
|
||||
mediaPeriodQueue.updatePreloadConfiguration(
|
||||
playbackInfo.timeline, new PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L));
|
||||
enqueueNext();
|
||||
mediaPeriodQueue.invalidatePreloadPool(playbackInfo.timeline);
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(1_000_000_000_000L, 1_000_010_000_000L)
|
||||
.inOrder();
|
||||
assertThat(releaseCalled.get()).isFalse();
|
||||
|
||||
mediaPeriodQueue.updatePreloadConfiguration(
|
||||
playbackInfo.timeline, PreloadConfiguration.DEFAULT);
|
||||
|
||||
assertThat(releaseCalled.get()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPreloadConfiguration_enablePreloading_preloadHolderCreated() {
|
||||
setupTimelines(new FakeTimeline(), new FakeTimeline());
|
||||
enqueueNext();
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets).containsExactly(1_000_000_000_000L);
|
||||
|
||||
mediaPeriodQueue.updatePreloadConfiguration(
|
||||
playbackInfo.timeline, new PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L));
|
||||
|
||||
assertThat(mediaPeriodHolderFactoryRendererPositionOffsets)
|
||||
.containsExactly(1_000_000_000_000L, 1_000_010_000_000L)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
private void setupAdTimeline(long... adGroupTimesUs) {
|
||||
adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ new Object(), adGroupTimesUs)
|
||||
.withContentDurationUs(CONTENT_DURATION_US);
|
||||
SinglePeriodAdTimeline adTimeline =
|
||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||
setupTimeline(adTimeline);
|
||||
setupTimelines(adTimeline);
|
||||
}
|
||||
|
||||
private void setupTimeline(Timeline timeline) {
|
||||
fakeMediaSource = new FakeMediaSource(timeline);
|
||||
MediaSourceList.MediaSourceHolder mediaSourceHolder =
|
||||
new MediaSourceList.MediaSourceHolder(fakeMediaSource, /* useLazyPreparation= */ false);
|
||||
mediaSourceList.setMediaSources(
|
||||
ImmutableList.of(mediaSourceHolder), new FakeShuffleOrder(/* length= */ 1));
|
||||
mediaSourceHolder.mediaSource.prepareSource(
|
||||
mock(MediaSourceCaller.class), /* mediaTransferListener= */ null, PlayerId.UNSET);
|
||||
private void setupTimelines(Timeline... timelines) {
|
||||
FakeMediaSource[] sources = new FakeMediaSource[timelines.length];
|
||||
for (int i = 0; i < timelines.length; i++) {
|
||||
sources[i] = new FakeMediaSource(timelines[i]);
|
||||
}
|
||||
setupMediaSources(sources);
|
||||
}
|
||||
|
||||
private void setupMediaSources(FakeMediaSource... mediaSources) {
|
||||
ImmutableList.Builder<MediaSourceList.MediaSourceHolder> mediaSourceHolders =
|
||||
new ImmutableList.Builder<>();
|
||||
for (FakeMediaSource source : mediaSources) {
|
||||
fakeMediaSources.add(source);
|
||||
MediaSourceList.MediaSourceHolder mediaSourceHolder =
|
||||
new MediaSourceList.MediaSourceHolder(source, /* useLazyPreparation= */ false);
|
||||
mediaSourceHolder.mediaSource.prepareSource(
|
||||
mock(MediaSourceCaller.class), /* mediaTransferListener= */ null, PlayerId.UNSET);
|
||||
mediaSourceHolders.add(mediaSourceHolder);
|
||||
}
|
||||
ImmutableList<MediaSourceList.MediaSourceHolder> holders = mediaSourceHolders.build();
|
||||
mediaSourceList.setMediaSources(holders, new FakeShuffleOrder(/* length= */ holders.size()));
|
||||
|
||||
Timeline playlistTimeline = mediaSourceList.createTimeline();
|
||||
firstPeriodUid = playlistTimeline.getUidOfPeriod(/* periodIndex= */ 0);
|
||||
@ -1494,14 +1891,14 @@ public final class MediaPeriodQueueTest {
|
||||
.withAdCount(adGroupIndex, /* adCount= */ 1)
|
||||
.withAvailableAdMediaItem(adGroupIndex, /* adIndexInAdGroup= */ 0, AD_MEDIA_ITEM)
|
||||
.withAdDurationsUs(newDurations);
|
||||
updateTimeline();
|
||||
updateAdTimeline(/* mediaSourceIndex= */ 0);
|
||||
}
|
||||
|
||||
private void setAdGroupPlayed(int adGroupIndex) {
|
||||
for (int i = 0; i < adPlaybackState.getAdGroup(adGroupIndex).count; i++) {
|
||||
adPlaybackState = adPlaybackState.withPlayedAd(adGroupIndex, /* adIndexInAdGroup= */ i);
|
||||
}
|
||||
updateTimeline();
|
||||
updateAdTimeline(/* mediaSourceIndex= */ 0);
|
||||
}
|
||||
|
||||
private void setAdGroupFailedToLoad(int adGroupIndex) {
|
||||
@ -1509,20 +1906,20 @@ public final class MediaPeriodQueueTest {
|
||||
adPlaybackState
|
||||
.withAdCount(adGroupIndex, /* adCount= */ 1)
|
||||
.withAdLoadError(adGroupIndex, /* adIndexInAdGroup= */ 0);
|
||||
updateTimeline();
|
||||
updateAdTimeline(/* mediaSourceIndex= */ 0);
|
||||
}
|
||||
|
||||
private void updateAdPlaybackStateAndTimeline(long... adGroupTimesUs) {
|
||||
adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ new Object(), adGroupTimesUs)
|
||||
.withContentDurationUs(CONTENT_DURATION_US);
|
||||
updateTimeline();
|
||||
updateAdTimeline(/* mediaSourceIndex= */ 0);
|
||||
}
|
||||
|
||||
private void updateTimeline() {
|
||||
private void updateAdTimeline(int mediaSourceIndex) {
|
||||
SinglePeriodAdTimeline adTimeline =
|
||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||
fakeMediaSource.setNewSourceInfo(adTimeline);
|
||||
fakeMediaSources.get(mediaSourceIndex).setNewSourceInfo(adTimeline);
|
||||
// Progress the looper so that the source info events have been executed.
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
playbackInfo = playbackInfo.copyWithTimeline(mediaSourceList.createTimeline());
|
||||
|
@ -154,6 +154,16 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreloadConfiguration(PreloadConfiguration preloadConfiguration) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreloadConfiguration getPreloadConfiguration() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMediaSource(MediaSource mediaSource) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
Loading…
x
Reference in New Issue
Block a user