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) {}
|
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.
|
* A builder for {@link ExoPlayer} instances.
|
||||||
*
|
*
|
||||||
@ -1536,6 +1561,19 @@ public interface ExoPlayer extends Player {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
void setShuffleOrder(ShuffleOrder shuffleOrder);
|
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}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
|
@ -195,6 +195,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
private boolean foregroundMode;
|
private boolean foregroundMode;
|
||||||
private SeekParameters seekParameters;
|
private SeekParameters seekParameters;
|
||||||
private ShuffleOrder shuffleOrder;
|
private ShuffleOrder shuffleOrder;
|
||||||
|
private PreloadConfiguration preloadConfiguration;
|
||||||
private boolean pauseAtEndOfMediaItems;
|
private boolean pauseAtEndOfMediaItems;
|
||||||
private Commands availableCommands;
|
private Commands availableCommands;
|
||||||
private MediaMetadata mediaMetadata;
|
private MediaMetadata mediaMetadata;
|
||||||
@ -298,6 +299,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
audioOffloadListeners = new CopyOnWriteArraySet<>();
|
audioOffloadListeners = new CopyOnWriteArraySet<>();
|
||||||
mediaSourceHolderSnapshots = new ArrayList<>();
|
mediaSourceHolderSnapshots = new ArrayList<>();
|
||||||
shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
|
shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
|
||||||
|
preloadConfiguration = PreloadConfiguration.DEFAULT;
|
||||||
emptyTrackSelectorResult =
|
emptyTrackSelectorResult =
|
||||||
new TrackSelectorResult(
|
new TrackSelectorResult(
|
||||||
new RendererConfiguration[renderers.length],
|
new RendererConfiguration[renderers.length],
|
||||||
@ -374,7 +376,8 @@ import java.util.concurrent.TimeoutException;
|
|||||||
clock,
|
clock,
|
||||||
playbackInfoUpdateListener,
|
playbackInfoUpdateListener,
|
||||||
playerId,
|
playerId,
|
||||||
builder.playbackLooper);
|
builder.playbackLooper,
|
||||||
|
preloadConfiguration);
|
||||||
|
|
||||||
volume = 1;
|
volume = 1;
|
||||||
repeatMode = Player.REPEAT_MODE_OFF;
|
repeatMode = Player.REPEAT_MODE_OFF;
|
||||||
@ -881,6 +884,21 @@ import java.util.concurrent.TimeoutException;
|
|||||||
return shuffleModeEnabled;
|
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
|
@Override
|
||||||
public boolean isLoading() {
|
public boolean isLoading() {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
|
@ -57,6 +57,7 @@ import androidx.media3.common.util.TraceUtil;
|
|||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.DataSourceException;
|
import androidx.media3.datasource.DataSourceException;
|
||||||
import androidx.media3.exoplayer.DefaultMediaClock.PlaybackParametersListener;
|
import androidx.media3.exoplayer.DefaultMediaClock.PlaybackParametersListener;
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer.PreloadConfiguration;
|
||||||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||||
import androidx.media3.exoplayer.drm.DrmSession;
|
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_ATTEMPT_RENDERER_ERROR_RECOVERY = 25;
|
||||||
private static final int MSG_RENDERER_CAPABILITIES_CHANGED = 26;
|
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_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 ACTIVE_INTERVAL_MS = 10;
|
||||||
private static final int IDLE_INTERVAL_MS = 1000;
|
private static final int IDLE_INTERVAL_MS = 1000;
|
||||||
@ -237,6 +239,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
@Nullable private ExoPlaybackException pendingRecoverableRendererError;
|
@Nullable private ExoPlaybackException pendingRecoverableRendererError;
|
||||||
private long setForegroundModeTimeoutMs;
|
private long setForegroundModeTimeoutMs;
|
||||||
private long playbackMaybeBecameStuckAtMs;
|
private long playbackMaybeBecameStuckAtMs;
|
||||||
|
private PreloadConfiguration preloadConfiguration;
|
||||||
|
private Timeline lastPreloadPoolInvalidationTimeline;
|
||||||
|
|
||||||
public ExoPlayerImplInternal(
|
public ExoPlayerImplInternal(
|
||||||
Renderer[] renderers,
|
Renderer[] renderers,
|
||||||
@ -255,7 +259,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
Clock clock,
|
Clock clock,
|
||||||
PlaybackInfoUpdateListener playbackInfoUpdateListener,
|
PlaybackInfoUpdateListener playbackInfoUpdateListener,
|
||||||
PlayerId playerId,
|
PlayerId playerId,
|
||||||
Looper playbackLooper) {
|
Looper playbackLooper,
|
||||||
|
PreloadConfiguration preloadConfiguration) {
|
||||||
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
|
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
|
||||||
this.renderers = renderers;
|
this.renderers = renderers;
|
||||||
this.trackSelector = trackSelector;
|
this.trackSelector = trackSelector;
|
||||||
@ -271,11 +276,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
this.pauseAtEndOfWindow = pauseAtEndOfWindow;
|
this.pauseAtEndOfWindow = pauseAtEndOfWindow;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.playerId = playerId;
|
this.playerId = playerId;
|
||||||
|
this.preloadConfiguration = preloadConfiguration;
|
||||||
|
|
||||||
playbackMaybeBecameStuckAtMs = C.TIME_UNSET;
|
playbackMaybeBecameStuckAtMs = C.TIME_UNSET;
|
||||||
lastRebufferRealtimeMs = C.TIME_UNSET;
|
lastRebufferRealtimeMs = C.TIME_UNSET;
|
||||||
backBufferDurationUs = loadControl.getBackBufferDurationUs(playerId);
|
backBufferDurationUs = loadControl.getBackBufferDurationUs(playerId);
|
||||||
retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe(playerId);
|
retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe(playerId);
|
||||||
|
lastPreloadPoolInvalidationTimeline = Timeline.EMPTY;
|
||||||
|
|
||||||
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
|
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
|
||||||
playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo);
|
playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo);
|
||||||
@ -300,7 +307,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
deliverPendingMessageAtStartPositionRequired = true;
|
deliverPendingMessageAtStartPositionRequired = true;
|
||||||
|
|
||||||
HandlerWrapper eventHandler = clock.createHandler(applicationLooper, /* callback= */ null);
|
HandlerWrapper eventHandler = clock.createHandler(applicationLooper, /* callback= */ null);
|
||||||
queue = new MediaPeriodQueue(analyticsCollector, eventHandler, this::createMediaPeriodHolder);
|
queue =
|
||||||
|
new MediaPeriodQueue(
|
||||||
|
analyticsCollector, eventHandler, this::createMediaPeriodHolder, preloadConfiguration);
|
||||||
mediaSourceList =
|
mediaSourceList =
|
||||||
new MediaSourceList(/* listener= */ this, analyticsCollector, eventHandler, playerId);
|
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();
|
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) {
|
public void seekTo(Timeline timeline, int windowIndex, long positionUs) {
|
||||||
handler
|
handler
|
||||||
.obtainMessage(MSG_SEEK_TO, new SeekPosition(timeline, windowIndex, positionUs))
|
.obtainMessage(MSG_SEEK_TO, new SeekPosition(timeline, windowIndex, positionUs))
|
||||||
@ -543,6 +556,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
case MSG_SET_SHUFFLE_ENABLED:
|
case MSG_SET_SHUFFLE_ENABLED:
|
||||||
setShuffleModeEnabledInternal(msg.arg1 != 0);
|
setShuffleModeEnabledInternal(msg.arg1 != 0);
|
||||||
break;
|
break;
|
||||||
|
case MSG_SET_PRELOAD_CONFIGURATION:
|
||||||
|
setPreloadConfigurationInternal((PreloadConfiguration) msg.obj);
|
||||||
|
break;
|
||||||
case MSG_DO_SOME_WORK:
|
case MSG_DO_SOME_WORK:
|
||||||
doSomeWork();
|
doSomeWork();
|
||||||
break;
|
break;
|
||||||
@ -930,6 +946,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setPreloadConfigurationInternal(PreloadConfiguration preloadConfiguration) {
|
||||||
|
this.preloadConfiguration = preloadConfiguration;
|
||||||
|
queue.updatePreloadConfiguration(playbackInfo.timeline, preloadConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
private void seekToCurrentPosition(boolean sendDiscontinuity) throws ExoPlaybackException {
|
private void seekToCurrentPosition(boolean sendDiscontinuity) throws ExoPlaybackException {
|
||||||
// Renderers may have read from a period that's been removed. Seek back to the current
|
// 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.
|
// 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,
|
/* positionUpdateTimeMs= */ 0,
|
||||||
/* sleepingForOffload= */ false);
|
/* sleepingForOffload= */ false);
|
||||||
if (releaseMediaSourceList) {
|
if (releaseMediaSourceList) {
|
||||||
|
queue.releasePreloadPool();
|
||||||
mediaSourceList.release();
|
mediaSourceList.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2137,13 +2159,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
// No periods available.
|
// No periods available.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
maybeUpdateLoadingPeriod();
|
boolean loadingPeriodChanged = maybeUpdateLoadingPeriod();
|
||||||
maybeUpdateReadingPeriod();
|
maybeUpdateReadingPeriod();
|
||||||
maybeUpdateReadingRenderers();
|
maybeUpdateReadingRenderers();
|
||||||
maybeUpdatePlayingPeriod();
|
maybeUpdatePlayingPeriod();
|
||||||
|
maybeUpdatePreloadPeriods(loadingPeriodChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeUpdateLoadingPeriod() throws ExoPlaybackException {
|
private boolean maybeUpdateLoadingPeriod() throws ExoPlaybackException {
|
||||||
|
boolean loadingPeriodChanged = false;
|
||||||
queue.reevaluateBuffer(rendererPositionUs);
|
queue.reevaluateBuffer(rendererPositionUs);
|
||||||
if (queue.shouldLoadNextMediaPeriod()) {
|
if (queue.shouldLoadNextMediaPeriod()) {
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -2155,6 +2179,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
resetRendererPosition(info.startPositionUs);
|
resetRendererPosition(info.startPositionUs);
|
||||||
}
|
}
|
||||||
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
||||||
|
loadingPeriodChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shouldContinueLoading) {
|
if (shouldContinueLoading) {
|
||||||
@ -2165,6 +2190,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
} else {
|
} else {
|
||||||
maybeContinueLoading();
|
maybeContinueLoading();
|
||||||
}
|
}
|
||||||
|
return loadingPeriodChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeUpdateReadingPeriod() throws ExoPlaybackException {
|
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 {
|
private boolean replaceStreamsOrDisableRendererForTransition() throws ExoPlaybackException {
|
||||||
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
|
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
|
||||||
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
|
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer;
|
package androidx.media3.exoplayer;
|
||||||
|
|
||||||
|
import static androidx.media3.exoplayer.MediaPeriodQueue.areDurationsCompatible;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
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 {
|
/* package */ interface Factory {
|
||||||
MediaPeriodHolder create(MediaPeriodInfo info, long rendererPositionOffsetUs);
|
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.Timeline;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.HandlerWrapper;
|
import androidx.media3.common.util.HandlerWrapper;
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer.PreloadConfiguration;
|
||||||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||||
import com.google.common.collect.ImmutableList;
|
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
|
* 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;
|
private int length;
|
||||||
@Nullable private Object oldFrontPeriodUid;
|
@Nullable private Object oldFrontPeriodUid;
|
||||||
private long oldFrontPeriodWindowSequenceNumber;
|
private long oldFrontPeriodWindowSequenceNumber;
|
||||||
|
private PreloadConfiguration preloadConfiguration;
|
||||||
|
private List<MediaPeriodHolder> preloadPriorityList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new media period queue.
|
* Creates a new media period queue.
|
||||||
@ -94,12 +99,15 @@ import com.google.common.collect.ImmutableList;
|
|||||||
public MediaPeriodQueue(
|
public MediaPeriodQueue(
|
||||||
AnalyticsCollector analyticsCollector,
|
AnalyticsCollector analyticsCollector,
|
||||||
HandlerWrapper analyticsCollectorHandler,
|
HandlerWrapper analyticsCollectorHandler,
|
||||||
MediaPeriodHolder.Factory mediaPeriodHolderFactory) {
|
MediaPeriodHolder.Factory mediaPeriodHolderFactory,
|
||||||
|
PreloadConfiguration preloadConfiguration) {
|
||||||
this.analyticsCollector = analyticsCollector;
|
this.analyticsCollector = analyticsCollector;
|
||||||
this.analyticsCollectorHandler = analyticsCollectorHandler;
|
this.analyticsCollectorHandler = analyticsCollectorHandler;
|
||||||
this.mediaPeriodHolderFactory = mediaPeriodHolderFactory;
|
this.mediaPeriodHolderFactory = mediaPeriodHolderFactory;
|
||||||
|
this.preloadConfiguration = preloadConfiguration;
|
||||||
period = new Timeline.Period();
|
period = new Timeline.Period();
|
||||||
window = new Timeline.Window();
|
window = new Timeline.Window();
|
||||||
|
preloadPriorityList = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,6 +136,18 @@ import com.google.common.collect.ImmutableList;
|
|||||||
return updateForPlaybackModeChange(timeline);
|
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. */
|
/** Returns whether {@code mediaPeriod} is the current loading media period. */
|
||||||
public boolean isLoading(MediaPeriod mediaPeriod) {
|
public boolean isLoading(MediaPeriod mediaPeriod) {
|
||||||
return loading != null && loading.mediaPeriod == mediaPeriod;
|
return loading != null && loading.mediaPeriod == mediaPeriod;
|
||||||
@ -180,8 +200,13 @@ import com.google.common.collect.ImmutableList;
|
|||||||
loading == null
|
loading == null
|
||||||
? INITIAL_RENDERER_POSITION_OFFSET_US
|
? INITIAL_RENDERER_POSITION_OFFSET_US
|
||||||
: (loading.getRendererOffset() + loading.info.durationUs - info.startPositionUs);
|
: (loading.getRendererOffset() + loading.info.durationUs - info.startPositionUs);
|
||||||
MediaPeriodHolder newPeriodHolder =
|
@Nullable MediaPeriodHolder newPeriodHolder = removePreloadedMediaPeriodHolder(info);
|
||||||
mediaPeriodHolderFactory.create(info, rendererPositionOffsetUs);
|
if (newPeriodHolder == null) {
|
||||||
|
newPeriodHolder = mediaPeriodHolderFactory.create(info, rendererPositionOffsetUs);
|
||||||
|
} else {
|
||||||
|
newPeriodHolder.info = info;
|
||||||
|
newPeriodHolder.setRendererOffset(rendererPositionOffsetUs);
|
||||||
|
}
|
||||||
if (loading != null) {
|
if (loading != null) {
|
||||||
loading.setNext(newPeriodHolder);
|
loading.setNext(newPeriodHolder);
|
||||||
} else {
|
} else {
|
||||||
@ -195,6 +220,110 @@ import com.google.common.collect.ImmutableList;
|
|||||||
return newPeriodHolder;
|
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
|
* Returns the loading period holder which is at the end of the queue, or null if the queue is
|
||||||
* empty.
|
* empty.
|
||||||
@ -430,7 +559,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
*/
|
*/
|
||||||
public MediaPeriodId resolveMediaPeriodIdForAds(
|
public MediaPeriodId resolveMediaPeriodIdForAds(
|
||||||
Timeline timeline, Object periodUid, long positionUs) {
|
Timeline timeline, Object periodUid, long positionUs) {
|
||||||
long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(timeline, periodUid);
|
long windowSequenceNumber = resolvePeriodUidToWindowSequenceNumber(timeline, periodUid);
|
||||||
return resolveMediaPeriodIdForAds(
|
return resolveMediaPeriodIdForAds(
|
||||||
timeline, periodUid, positionUs, windowSequenceNumber, window, period);
|
timeline, periodUid, positionUs, windowSequenceNumber, window, period);
|
||||||
}
|
}
|
||||||
@ -507,7 +636,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
*/
|
*/
|
||||||
public MediaPeriodId resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
public MediaPeriodId resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||||
Timeline timeline, Object periodUid, long positionUs) {
|
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.
|
// Check for preceding ad periods in multi-period window.
|
||||||
timeline.getPeriodByUid(periodUid, period);
|
timeline.getPeriodByUid(periodUid, period);
|
||||||
timeline.getWindow(period.windowIndex, window);
|
timeline.getWindow(period.windowIndex, window);
|
||||||
@ -553,7 +682,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
* @param periodUid The uid of the timeline period.
|
* @param periodUid The uid of the timeline period.
|
||||||
* @return A window sequence number for a media period created for this 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;
|
int windowIndex = timeline.getPeriodByUid(periodUid, period).windowIndex;
|
||||||
if (oldFrontPeriodUid != null) {
|
if (oldFrontPeriodUid != null) {
|
||||||
int oldFrontPeriodIndex = timeline.getIndexOfPeriod(oldFrontPeriodUid);
|
int oldFrontPeriodIndex = timeline.getIndexOfPeriod(oldFrontPeriodUid);
|
||||||
@ -585,8 +714,14 @@ import com.google.common.collect.ImmutableList;
|
|||||||
}
|
}
|
||||||
mediaPeriodHolder = mediaPeriodHolder.getNext();
|
mediaPeriodHolder = mediaPeriodHolder.getNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long windowSequenceNumber = resolvePeriodUidToWindowSequenceNumberInPreloadPeriods(periodUid);
|
||||||
|
if (windowSequenceNumber != C.INDEX_UNSET) {
|
||||||
|
return windowSequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
// If no match is found, create new sequence number.
|
// If no match is found, create new sequence number.
|
||||||
long windowSequenceNumber = nextWindowSequenceNumber++;
|
windowSequenceNumber = nextWindowSequenceNumber++;
|
||||||
if (playing == null) {
|
if (playing == null) {
|
||||||
// If the queue is empty, save it as old front uid to allow later reuse.
|
// If the queue is empty, save it as old front uid to allow later reuse.
|
||||||
oldFrontPeriodUid = periodUid;
|
oldFrontPeriodUid = periodUid;
|
||||||
@ -595,6 +730,16 @@ import com.google.common.collect.ImmutableList;
|
|||||||
return windowSequenceNumber;
|
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
|
* Returns whether a period described by {@code oldInfo} can be kept for playing the media period
|
||||||
* described by {@code newInfo}.
|
* 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.
|
* 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;
|
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.
|
// 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);
|
lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(timeline, lastValidPeriodHolder.info);
|
||||||
|
|
||||||
// If renderers may have read from a period that's been removed, it is necessary to restart.
|
// If renderers may have read from a period that's been removed, it is necessary to restart.
|
||||||
return !readingPeriodRemoved;
|
return !readingPeriodRemoved;
|
||||||
}
|
}
|
||||||
@ -746,7 +890,12 @@ import com.google.common.collect.ImmutableList;
|
|||||||
if (nextMediaPeriodHolder != null && nextMediaPeriodHolder.uid.equals(nextPeriodUid)) {
|
if (nextMediaPeriodHolder != null && nextMediaPeriodHolder.uid.equals(nextPeriodUid)) {
|
||||||
windowSequenceNumber = nextMediaPeriodHolder.info.id.windowSequenceNumber;
|
windowSequenceNumber = nextMediaPeriodHolder.info.id.windowSequenceNumber;
|
||||||
} else {
|
} 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);
|
&& (adGroupCount > 1 || period.getAdGroupTimeUs(firstAdGroupIndex) != C.TIME_END_OF_SOURCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private MediaPeriodInfo getMediaPeriodInfo(
|
private MediaPeriodInfo getMediaPeriodInfo(
|
||||||
Timeline timeline, MediaPeriodId id, long requestedContentPositionUs, long startPositionUs) {
|
Timeline timeline, MediaPeriodId id, long requestedContentPositionUs, long startPositionUs) {
|
||||||
timeline.getPeriodByUid(id.periodUid, period);
|
timeline.getPeriodByUid(id.periodUid, period);
|
||||||
|
@ -992,6 +992,18 @@ public class SimpleExoPlayer extends BasePlayer
|
|||||||
player.setRepeatMode(repeatMode);
|
player.setRepeatMode(repeatMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPreloadConfiguration(PreloadConfiguration preloadConfiguration) {
|
||||||
|
blockUntilConstructorFinished();
|
||||||
|
player.setPreloadConfiguration(preloadConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreloadConfiguration getPreloadConfiguration() {
|
||||||
|
blockUntilConstructorFinished();
|
||||||
|
return player.getPreloadConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
|
public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
|
||||||
blockUntilConstructorFinished();
|
blockUntilConstructorFinished();
|
||||||
|
@ -40,9 +40,11 @@ import androidx.media3.common.Timeline;
|
|||||||
import androidx.media3.common.Tracks;
|
import androidx.media3.common.Tracks;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.common.util.HandlerWrapper;
|
import androidx.media3.common.util.HandlerWrapper;
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer.PreloadConfiguration;
|
||||||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||||
import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector;
|
import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector;
|
||||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
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.MediaPeriodId;
|
||||||
import androidx.media3.exoplayer.source.MediaSource.MediaSourceCaller;
|
import androidx.media3.exoplayer.source.MediaSource.MediaSourceCaller;
|
||||||
import androidx.media3.exoplayer.source.SinglePeriodTimeline;
|
import androidx.media3.exoplayer.source.SinglePeriodTimeline;
|
||||||
@ -61,7 +63,10 @@ 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.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -95,7 +100,9 @@ public final class MediaPeriodQueueTest {
|
|||||||
private TrackSelector trackSelector;
|
private TrackSelector trackSelector;
|
||||||
private Allocator allocator;
|
private Allocator allocator;
|
||||||
private MediaSourceList mediaSourceList;
|
private MediaSourceList mediaSourceList;
|
||||||
private FakeMediaSource fakeMediaSource;
|
private List<FakeMediaSource> fakeMediaSources;
|
||||||
|
private ArrayList<MediaPeriodInfo> mediaPeriodHolderFactoryInfos;
|
||||||
|
private ArrayList<Long> mediaPeriodHolderFactoryRendererPositionOffsets;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@ -105,23 +112,29 @@ public final class MediaPeriodQueueTest {
|
|||||||
Looper.getMainLooper());
|
Looper.getMainLooper());
|
||||||
HandlerWrapper handler =
|
HandlerWrapper handler =
|
||||||
Clock.DEFAULT.createHandler(Looper.getMainLooper(), /* callback= */ null);
|
Clock.DEFAULT.createHandler(Looper.getMainLooper(), /* callback= */ null);
|
||||||
|
mediaPeriodHolderFactoryInfos = new ArrayList<>();
|
||||||
|
mediaPeriodHolderFactoryRendererPositionOffsets = new ArrayList<>();
|
||||||
mediaPeriodQueue =
|
mediaPeriodQueue =
|
||||||
new MediaPeriodQueue(
|
new MediaPeriodQueue(
|
||||||
analyticsCollector,
|
analyticsCollector,
|
||||||
handler,
|
handler,
|
||||||
(info, rendererPositionOffsetUs) ->
|
(info, rendererPositionOffsetUs) -> {
|
||||||
new MediaPeriodHolder(
|
mediaPeriodHolderFactoryInfos.add(info);
|
||||||
rendererCapabilities,
|
mediaPeriodHolderFactoryRendererPositionOffsets.add(rendererPositionOffsetUs);
|
||||||
rendererPositionOffsetUs,
|
return new MediaPeriodHolder(
|
||||||
trackSelector,
|
rendererCapabilities,
|
||||||
allocator,
|
rendererPositionOffsetUs,
|
||||||
mediaSourceList,
|
trackSelector,
|
||||||
info,
|
allocator,
|
||||||
new TrackSelectorResult(
|
mediaSourceList,
|
||||||
new RendererConfiguration[0],
|
info,
|
||||||
new ExoTrackSelection[0],
|
new TrackSelectorResult(
|
||||||
Tracks.EMPTY,
|
new RendererConfiguration[0],
|
||||||
/* info= */ null)));
|
new ExoTrackSelection[0],
|
||||||
|
Tracks.EMPTY,
|
||||||
|
/* info= */ null));
|
||||||
|
},
|
||||||
|
PreloadConfiguration.DEFAULT);
|
||||||
mediaSourceList =
|
mediaSourceList =
|
||||||
new MediaSourceList(
|
new MediaSourceList(
|
||||||
mock(MediaSourceList.MediaSourceListInfoRefreshListener.class),
|
mock(MediaSourceList.MediaSourceListInfoRefreshListener.class),
|
||||||
@ -131,6 +144,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
rendererCapabilities = new RendererCapabilities[0];
|
rendererCapabilities = new RendererCapabilities[0];
|
||||||
trackSelector = mock(TrackSelector.class);
|
trackSelector = mock(TrackSelector.class);
|
||||||
allocator = mock(Allocator.class);
|
allocator = mock(Allocator.class);
|
||||||
|
fakeMediaSources = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -305,7 +319,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 4000);
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 4000);
|
||||||
SinglePeriodAdTimeline adTimeline =
|
SinglePeriodAdTimeline adTimeline =
|
||||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||||
setupTimeline(adTimeline);
|
setupTimelines(adTimeline);
|
||||||
|
|
||||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||||
assertNextMediaPeriodInfoIsAd(
|
assertNextMediaPeriodInfoIsAd(
|
||||||
@ -382,7 +396,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
.withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true);
|
.withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true);
|
||||||
SinglePeriodAdTimeline adTimeline =
|
SinglePeriodAdTimeline adTimeline =
|
||||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||||
setupTimeline(adTimeline);
|
setupTimelines(adTimeline);
|
||||||
|
|
||||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||||
assertNextMediaPeriodInfoIsAd(
|
assertNextMediaPeriodInfoIsAd(
|
||||||
@ -463,7 +477,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
/* isContentTimeline= */ true,
|
/* isContentTimeline= */ true,
|
||||||
/* populateAds= */ false,
|
/* populateAds= */ false,
|
||||||
/* playedAds= */ false);
|
/* playedAds= */ false);
|
||||||
setupTimeline(multiPeriodLiveTimeline);
|
setupTimelines(multiPeriodLiveTimeline);
|
||||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
/* periodUid= */ firstPeriodUid,
|
/* periodUid= */ firstPeriodUid,
|
||||||
/* startPositionUs= */ 0,
|
/* startPositionUs= */ 0,
|
||||||
@ -533,7 +547,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
/* isContentTimeline= */ false,
|
/* isContentTimeline= */ false,
|
||||||
/* populateAds= */ false,
|
/* populateAds= */ false,
|
||||||
/* playedAds= */ false);
|
/* playedAds= */ false);
|
||||||
setupTimeline(multiPeriodLiveTimeline);
|
setupTimelines(multiPeriodLiveTimeline);
|
||||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
/* periodUid= */ firstPeriodUid,
|
/* periodUid= */ firstPeriodUid,
|
||||||
/* startPositionUs= */ 0,
|
/* startPositionUs= */ 0,
|
||||||
@ -602,7 +616,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
/* isContentTimeline= */ false,
|
/* isContentTimeline= */ false,
|
||||||
/* populateAds= */ true,
|
/* populateAds= */ true,
|
||||||
/* playedAds= */ false);
|
/* playedAds= */ false);
|
||||||
setupTimeline(multiPeriodLiveTimeline);
|
setupTimelines(multiPeriodLiveTimeline);
|
||||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
/* periodUid= */ firstPeriodUid,
|
/* periodUid= */ firstPeriodUid,
|
||||||
/* startPositionUs= */ 0,
|
/* startPositionUs= */ 0,
|
||||||
@ -686,7 +700,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
/* isContentTimeline= */ false,
|
/* isContentTimeline= */ false,
|
||||||
/* populateAds= */ true,
|
/* populateAds= */ true,
|
||||||
/* playedAds= */ true);
|
/* playedAds= */ true);
|
||||||
setupTimeline(multiPeriodLiveTimeline);
|
setupTimelines(multiPeriodLiveTimeline);
|
||||||
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
|
||||||
/* periodUid= */ firstPeriodUid,
|
/* periodUid= */ firstPeriodUid,
|
||||||
/* startPositionUs= */ 0,
|
/* startPositionUs= */ 0,
|
||||||
@ -798,7 +812,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getNextMediaPeriodInfo_inMultiPeriodWindow_returnsCorrectMediaPeriodInfos() {
|
public void getNextMediaPeriodInfo_inMultiPeriodWindow_returnsCorrectMediaPeriodInfos() {
|
||||||
setupTimeline(
|
setupTimelines(
|
||||||
new FakeTimeline(
|
new FakeTimeline(
|
||||||
new TimelineWindowDefinition(
|
new TimelineWindowDefinition(
|
||||||
/* periodCount= */ 2,
|
/* periodCount= */ 2,
|
||||||
@ -896,7 +910,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true);
|
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true);
|
||||||
SinglePeriodAdTimeline adTimeline =
|
SinglePeriodAdTimeline adTimeline =
|
||||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||||
setupTimeline(adTimeline);
|
setupTimelines(adTimeline);
|
||||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||||
enqueueNext(); // Content before ad.
|
enqueueNext(); // Content before ad.
|
||||||
enqueueNext(); // Ad.
|
enqueueNext(); // Ad.
|
||||||
@ -907,7 +921,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
new AdPlaybackState(
|
new AdPlaybackState(
|
||||||
/* adsId= */ new Object(), /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US - 2000)
|
/* adsId= */ new Object(), /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US - 2000)
|
||||||
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true);
|
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true);
|
||||||
updateTimeline();
|
updateAdTimeline(/* mediaSourceIndex= */ 0);
|
||||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||||
long maxRendererReadPositionUs =
|
long maxRendererReadPositionUs =
|
||||||
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 1000;
|
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);
|
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) {
|
private void setupAdTimeline(long... adGroupTimesUs) {
|
||||||
adPlaybackState =
|
adPlaybackState =
|
||||||
new AdPlaybackState(/* adsId= */ new Object(), adGroupTimesUs)
|
new AdPlaybackState(/* adsId= */ new Object(), adGroupTimesUs)
|
||||||
.withContentDurationUs(CONTENT_DURATION_US);
|
.withContentDurationUs(CONTENT_DURATION_US);
|
||||||
SinglePeriodAdTimeline adTimeline =
|
SinglePeriodAdTimeline adTimeline =
|
||||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
||||||
setupTimeline(adTimeline);
|
setupTimelines(adTimeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupTimeline(Timeline timeline) {
|
private void setupTimelines(Timeline... timelines) {
|
||||||
fakeMediaSource = new FakeMediaSource(timeline);
|
FakeMediaSource[] sources = new FakeMediaSource[timelines.length];
|
||||||
MediaSourceList.MediaSourceHolder mediaSourceHolder =
|
for (int i = 0; i < timelines.length; i++) {
|
||||||
new MediaSourceList.MediaSourceHolder(fakeMediaSource, /* useLazyPreparation= */ false);
|
sources[i] = new FakeMediaSource(timelines[i]);
|
||||||
mediaSourceList.setMediaSources(
|
}
|
||||||
ImmutableList.of(mediaSourceHolder), new FakeShuffleOrder(/* length= */ 1));
|
setupMediaSources(sources);
|
||||||
mediaSourceHolder.mediaSource.prepareSource(
|
}
|
||||||
mock(MediaSourceCaller.class), /* mediaTransferListener= */ null, PlayerId.UNSET);
|
|
||||||
|
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();
|
Timeline playlistTimeline = mediaSourceList.createTimeline();
|
||||||
firstPeriodUid = playlistTimeline.getUidOfPeriod(/* periodIndex= */ 0);
|
firstPeriodUid = playlistTimeline.getUidOfPeriod(/* periodIndex= */ 0);
|
||||||
@ -1494,14 +1891,14 @@ public final class MediaPeriodQueueTest {
|
|||||||
.withAdCount(adGroupIndex, /* adCount= */ 1)
|
.withAdCount(adGroupIndex, /* adCount= */ 1)
|
||||||
.withAvailableAdMediaItem(adGroupIndex, /* adIndexInAdGroup= */ 0, AD_MEDIA_ITEM)
|
.withAvailableAdMediaItem(adGroupIndex, /* adIndexInAdGroup= */ 0, AD_MEDIA_ITEM)
|
||||||
.withAdDurationsUs(newDurations);
|
.withAdDurationsUs(newDurations);
|
||||||
updateTimeline();
|
updateAdTimeline(/* mediaSourceIndex= */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAdGroupPlayed(int adGroupIndex) {
|
private void setAdGroupPlayed(int adGroupIndex) {
|
||||||
for (int i = 0; i < adPlaybackState.getAdGroup(adGroupIndex).count; i++) {
|
for (int i = 0; i < adPlaybackState.getAdGroup(adGroupIndex).count; i++) {
|
||||||
adPlaybackState = adPlaybackState.withPlayedAd(adGroupIndex, /* adIndexInAdGroup= */ i);
|
adPlaybackState = adPlaybackState.withPlayedAd(adGroupIndex, /* adIndexInAdGroup= */ i);
|
||||||
}
|
}
|
||||||
updateTimeline();
|
updateAdTimeline(/* mediaSourceIndex= */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAdGroupFailedToLoad(int adGroupIndex) {
|
private void setAdGroupFailedToLoad(int adGroupIndex) {
|
||||||
@ -1509,20 +1906,20 @@ public final class MediaPeriodQueueTest {
|
|||||||
adPlaybackState
|
adPlaybackState
|
||||||
.withAdCount(adGroupIndex, /* adCount= */ 1)
|
.withAdCount(adGroupIndex, /* adCount= */ 1)
|
||||||
.withAdLoadError(adGroupIndex, /* adIndexInAdGroup= */ 0);
|
.withAdLoadError(adGroupIndex, /* adIndexInAdGroup= */ 0);
|
||||||
updateTimeline();
|
updateAdTimeline(/* mediaSourceIndex= */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAdPlaybackStateAndTimeline(long... adGroupTimesUs) {
|
private void updateAdPlaybackStateAndTimeline(long... adGroupTimesUs) {
|
||||||
adPlaybackState =
|
adPlaybackState =
|
||||||
new AdPlaybackState(/* adsId= */ new Object(), adGroupTimesUs)
|
new AdPlaybackState(/* adsId= */ new Object(), adGroupTimesUs)
|
||||||
.withContentDurationUs(CONTENT_DURATION_US);
|
.withContentDurationUs(CONTENT_DURATION_US);
|
||||||
updateTimeline();
|
updateAdTimeline(/* mediaSourceIndex= */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTimeline() {
|
private void updateAdTimeline(int mediaSourceIndex) {
|
||||||
SinglePeriodAdTimeline adTimeline =
|
SinglePeriodAdTimeline adTimeline =
|
||||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState);
|
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.
|
// Progress the looper so that the source info events have been executed.
|
||||||
shadowOf(Looper.getMainLooper()).idle();
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
playbackInfo = playbackInfo.copyWithTimeline(mediaSourceList.createTimeline());
|
playbackInfo = playbackInfo.copyWithTimeline(mediaSourceList.createTimeline());
|
||||||
|
@ -154,6 +154,16 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPreloadConfiguration(PreloadConfiguration preloadConfiguration) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreloadConfiguration getPreloadConfiguration() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMediaSource(MediaSource mediaSource) {
|
public void setMediaSource(MediaSource mediaSource) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user