Implement mediasource list update support for pre-warming renderers

PiperOrigin-RevId: 704381778
This commit is contained in:
michaelkatz 2024-12-09 12:39:24 -08:00 committed by Copybara-Service
parent bb62278627
commit be63e156bb
4 changed files with 514 additions and 84 deletions

View File

@ -19,7 +19,8 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.common.util.Util.castNonNull;
import static androidx.media3.common.util.Util.msToUs; import static androidx.media3.common.util.Util.msToUs;
import static androidx.media3.exoplayer.MediaPeriodQueue.REMOVE_AFTER_REMOVED_READING_PERIOD; import static androidx.media3.exoplayer.MediaPeriodQueue.UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD;
import static androidx.media3.exoplayer.MediaPeriodQueue.UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD;
import static androidx.media3.exoplayer.RendererHolder.REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED; import static androidx.media3.exoplayer.RendererHolder.REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED;
import static androidx.media3.exoplayer.RendererHolder.REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING; import static androidx.media3.exoplayer.RendererHolder.REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING;
import static androidx.media3.exoplayer.audio.AudioSink.OFFLOAD_MODE_DISABLED; import static androidx.media3.exoplayer.audio.AudioSink.OFFLOAD_MODE_DISABLED;
@ -971,8 +972,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void setRepeatModeInternal(@Player.RepeatMode int repeatMode) private void setRepeatModeInternal(@Player.RepeatMode int repeatMode)
throws ExoPlaybackException { throws ExoPlaybackException {
this.repeatMode = repeatMode; this.repeatMode = repeatMode;
if (!queue.updateRepeatMode(playbackInfo.timeline, repeatMode)) { @MediaPeriodQueue.UpdatePeriodQueueResult
int result = queue.updateRepeatMode(playbackInfo.timeline, repeatMode);
if ((result & UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD) != 0) {
seekToCurrentPosition(/* sendDiscontinuity= */ true); seekToCurrentPosition(/* sendDiscontinuity= */ true);
} else if ((result & UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD) != 0) {
disableAndResetPrewarmingRenderers();
} }
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
} }
@ -980,8 +985,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void setShuffleModeEnabledInternal(boolean shuffleModeEnabled) private void setShuffleModeEnabledInternal(boolean shuffleModeEnabled)
throws ExoPlaybackException { throws ExoPlaybackException {
this.shuffleModeEnabled = shuffleModeEnabled; this.shuffleModeEnabled = shuffleModeEnabled;
if (!queue.updateShuffleModeEnabled(playbackInfo.timeline, shuffleModeEnabled)) { @MediaPeriodQueue.UpdatePeriodQueueResult
int result = queue.updateShuffleModeEnabled(playbackInfo.timeline, shuffleModeEnabled);
if ((result & UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD) != 0) {
seekToCurrentPosition(/* sendDiscontinuity= */ true); seekToCurrentPosition(/* sendDiscontinuity= */ true);
} else if ((result & UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD) != 0) {
disableAndResetPrewarmingRenderers();
} }
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
} }
@ -1950,8 +1959,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (selectionsChangedForReadPeriod) { if (selectionsChangedForReadPeriod) {
// Update streams and rebuffer for the new selection, recreating all streams if reading ahead. // Update streams and rebuffer for the new selection, recreating all streams if reading ahead.
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
@MediaPeriodQueue.UpdatePeriodQueueResult
int removeAfterResult = queue.removeAfter(playingPeriodHolder); int removeAfterResult = queue.removeAfter(playingPeriodHolder);
boolean recreateStreams = (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) != 0; boolean recreateStreams =
(removeAfterResult & UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD) != 0;
boolean[] streamResetFlags = new boolean[renderers.length]; boolean[] streamResetFlags = new boolean[renderers.length];
long periodPositionUs = long periodPositionUs =
@ -2135,9 +2146,26 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
if (!periodPositionChanged) { if (!periodPositionChanged) {
// We can keep the current playing period. Update the rest of the queued periods. // We can keep the current playing period. Update the rest of the queued periods.
if (!queue.updateQueuedPeriods( long maxRendererReadPositionUs =
timeline, rendererPositionUs, getMaxRendererReadPositionUs())) { queue.getReadingPeriod() == null
? 0
: getMaxRendererReadPositionUs(queue.getReadingPeriod());
long maxRendererPrewarmingPositionUs =
!areRenderersPrewarming() || queue.getPrewarmingPeriod() == null
? 0
: getMaxRendererReadPositionUs(queue.getPrewarmingPeriod());
@MediaPeriodQueue.UpdatePeriodQueueResult
int updateQueuedPeriodsResult =
queue.updateQueuedPeriods(
timeline,
rendererPositionUs,
maxRendererReadPositionUs,
maxRendererPrewarmingPositionUs);
if ((updateQueuedPeriodsResult & UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD) != 0) {
seekToCurrentPosition(/* sendDiscontinuity= */ false); seekToCurrentPosition(/* sendDiscontinuity= */ false);
} else if ((updateQueuedPeriodsResult & UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD)
!= 0) {
disableAndResetPrewarmingRenderers();
} }
} else if (!timeline.isEmpty()) { } else if (!timeline.isEmpty()) {
// Something changed. Seek to new start position. // Something changed. Seek to new start position.
@ -2239,21 +2267,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
} }
private long getMaxRendererReadPositionUs() { private long getMaxRendererReadPositionUs(MediaPeriodHolder periodHolder) {
MediaPeriodHolder readingHolder = queue.getReadingPeriod(); if (periodHolder == null) {
if (readingHolder == null) {
return 0; return 0;
} }
long maxReadPositionUs = readingHolder.getRendererOffset(); long maxReadPositionUs = periodHolder.getRendererOffset();
if (!readingHolder.prepared) { if (!periodHolder.prepared) {
return maxReadPositionUs; return maxReadPositionUs;
} }
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
if (!renderers[i].isReadingFromPeriod(readingHolder)) { if (!renderers[i].isReadingFromPeriod(periodHolder)) {
// Ignore disabled renderers and renderers with sample streams from previous periods. // Ignore disabled renderers and renderers with sample streams from previous periods.
continue; continue;
} }
long readingPositionUs = renderers[i].getReadingPositionUs(readingHolder); long readingPositionUs = renderers[i].getReadingPositionUs(periodHolder);
if (readingPositionUs == C.TIME_END_OF_SOURCE) { if (readingPositionUs == C.TIME_END_OF_SOURCE) {
return C.TIME_END_OF_SOURCE; return C.TIME_END_OF_SOURCE;
} else { } else {

View File

@ -119,27 +119,36 @@ import java.util.List;
} }
/** /**
* Sets the {@link RepeatMode} and returns whether the repeat mode change has been fully handled. * Sets the {@link RepeatMode} and returns whether the repeat mode change change has modified the
* If not, it is necessary to seek to the current playback position. * reading or pre-warming media periods. If it has modified the reading period then it is
* necessary to seek to the current playback position. If it has modified the pre-warming period
* then it is necessary to reset any pre-warming renderers. A value of {@code 0} is returned if it
* has neither modified the reading period nor the pre-warming period.
* *
* @param timeline The current timeline. * @param timeline The current timeline.
* @param repeatMode The new repeat mode. * @param repeatMode The new repeat mode.
* @return Whether the repeat mode change has been fully handled. * @return {@link UpdatePeriodQueueResult} with flags denoting if the repeat mode change altered
* the current reading or pre-warming media periods.
*/ */
public boolean updateRepeatMode(Timeline timeline, @RepeatMode int repeatMode) { public int updateRepeatMode(Timeline timeline, @RepeatMode int repeatMode) {
this.repeatMode = repeatMode; this.repeatMode = repeatMode;
return updateForPlaybackModeChange(timeline); return updateForPlaybackModeChange(timeline);
} }
/** /**
* Sets whether shuffling is enabled and returns whether the shuffle mode change has been fully * Sets whether shuffling is enabled and returns whether the shuffle mode change has modified the
* handled. If not, it is necessary to seek to the current playback position. * reading or pre-warming media periods. If it has modified the reading period, then it is
* necessary to seek to the current playback position. If it has modified the pre-warming period
* then it is necessary to reset any pre-warming renderers. A value of {@code 0} is returned if it
* has neither modified the reading period nor the pre-warming period.
* *
* @param timeline The current timeline. * @param timeline The current timeline.
* @param shuffleModeEnabled Whether shuffling mode is enabled. * @param shuffleModeEnabled Whether shuffling mode is enabled.
* @return Whether the shuffle mode change has been fully handled. * @return {@link UpdatePeriodQueueResult} with flags denoting if the shuffle mode change altered
* the current reading or pre-warming media periods.
*/ */
public boolean updateShuffleModeEnabled(Timeline timeline, boolean shuffleModeEnabled) { public @UpdatePeriodQueueResult int updateShuffleModeEnabled(
Timeline timeline, boolean shuffleModeEnabled) {
this.shuffleModeEnabled = shuffleModeEnabled; this.shuffleModeEnabled = shuffleModeEnabled;
return updateForPlaybackModeChange(timeline); return updateForPlaybackModeChange(timeline);
} }
@ -431,13 +440,21 @@ import java.util.List;
} }
/** /**
* Removes all period holders after the given period holder. This process may also remove the * Removes all period holders after the given period holder.
* currently reading period holder. If that is the case, the reading period holder is set to be *
* the same as the playing period holder at the front of the queue. * <p>This process may remove the currently reading period holder. If that is the case, the
* reading period holder is set to be the same as the playing period holder at the front of the
* queue.
*
* <p>This process may remove the currently pre-warming period holder. If that is the case, the
* pre-warming period holder is set to be the same as the reading period holder.
*
* <p>A value of {@code 0} is returned if the process has neither removed the reading period nor
* the pre-warming period.
* *
* @param mediaPeriodHolder The media period holder that shall be the new end of the queue. * @param mediaPeriodHolder The media period holder that shall be the new end of the queue.
* @return {@link RemoveAfterResult} with flags denoting if the reading or pre-warming periods * @return {@link UpdatePeriodQueueResult} with flags denoting if the reading or pre-warming
* were removed. * periods were removed.
*/ */
public int removeAfter(MediaPeriodHolder mediaPeriodHolder) { public int removeAfter(MediaPeriodHolder mediaPeriodHolder) {
checkStateNotNull(mediaPeriodHolder); checkStateNotNull(mediaPeriodHolder);
@ -451,11 +468,12 @@ import java.util.List;
if (mediaPeriodHolder == reading) { if (mediaPeriodHolder == reading) {
reading = playing; reading = playing;
prewarming = playing; prewarming = playing;
removedResult |= REMOVE_AFTER_REMOVED_READING_PERIOD; removedResult |= UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD;
removedResult |= UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD;
} }
if (mediaPeriodHolder == prewarming) { if (mediaPeriodHolder == prewarming) {
prewarming = reading; prewarming = reading;
removedResult |= REMOVE_AFTER_REMOVED_PREWARMING_PERIOD; removedResult |= UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD;
} }
mediaPeriodHolder.release(); mediaPeriodHolder.release();
length--; length--;
@ -516,19 +534,29 @@ import java.util.List;
/** /**
* Updates media periods in the queue to take into account the latest timeline, and returns * Updates media periods in the queue to take into account the latest timeline, and returns
* whether the timeline change has been fully handled. If not, it is necessary to seek to the * whether the timeline change has modified the current reading or pre-warming periods. The method
* current playback position. The method assumes that the first media period in the queue is still * returns {@code 0} if all changes have been handled and the reading/pre-warming periods have not
* consistent with the new timeline. * been affected. If the reading period has been affected, then it is necessary to seek to the
* current playback position. If the pre-warming period has been affected, then it is necessary to
* reset any pre-warming renderers. The method assumes that the first media period in the queue is
* still consistent with the new timeline.
* *
* @param timeline The new timeline. * @param timeline The new timeline.
* @param rendererPositionUs The current renderer position in microseconds. * @param rendererPositionUs The current renderer position in microseconds.
* @param maxRendererReadPositionUs The maximum renderer position up to which renderers have read * @param maxRendererReadPositionUs The maximum renderer position up to which renderers have read
* the current reading media period in microseconds, or {@link C#TIME_END_OF_SOURCE} if they * the current reading media period in microseconds, or {@link C#TIME_END_OF_SOURCE} if they
* have read to the end. * have read to the end.
* @return Whether the timeline change has been handled completely. * @param maxRendererPrewarmingPositionUs The maximum renderer position up to which renderers have
* read the current pre-warming media period in microseconds, or {@link C#TIME_END_OF_SOURCE}
* if they have read to the end.
* @return {@link UpdatePeriodQueueResult} denoting whether the timeline change has modified the
* reading or pre-warming media periods.
*/ */
public boolean updateQueuedPeriods( public @MediaPeriodQueue.UpdatePeriodQueueResult int updateQueuedPeriods(
Timeline timeline, long rendererPositionUs, long maxRendererReadPositionUs) { Timeline timeline,
long rendererPositionUs,
long maxRendererReadPositionUs,
long maxRendererPrewarmingPositionUs) {
// TODO: Merge this into setTimeline so that the queue gets updated as soon as the new timeline // TODO: Merge this into setTimeline so that the queue gets updated as soon as the new timeline
// is set, once all cases handled by ExoPlayerImplInternal.handleMediaSourceListInfoRefreshed // is set, once all cases handled by ExoPlayerImplInternal.handleMediaSourceListInfoRefreshed
// can be handled here. // can be handled here.
@ -547,15 +575,10 @@ import java.util.List;
} else { } else {
newPeriodInfo = newPeriodInfo =
getFollowingMediaPeriodInfo(timeline, previousPeriodHolder, rendererPositionUs); getFollowingMediaPeriodInfo(timeline, previousPeriodHolder, rendererPositionUs);
if (newPeriodInfo == null) { if (newPeriodInfo == null || !canKeepMediaPeriodHolder(oldPeriodInfo, newPeriodInfo)) {
// We've loaded a next media period that is not in the new timeline. // We've loaded a next media period that is not in the new timeline
int removeAfterResult = removeAfter(previousPeriodHolder); // or the new media period has a different id or start position.
return (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) == 0; return removeAfter(previousPeriodHolder);
}
if (!canKeepMediaPeriodHolder(oldPeriodInfo, newPeriodInfo)) {
// The new media period has a different id or start position.
int removeAfterResult = removeAfter(previousPeriodHolder);
return (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) == 0;
} }
} }
@ -578,16 +601,28 @@ import java.util.List;
&& !periodHolder.info.isFollowedByTransitionToSameStream && !periodHolder.info.isFollowedByTransitionToSameStream
&& (maxRendererReadPositionUs == C.TIME_END_OF_SOURCE && (maxRendererReadPositionUs == C.TIME_END_OF_SOURCE
|| maxRendererReadPositionUs >= newDurationInRendererTime); || maxRendererReadPositionUs >= newDurationInRendererTime);
int removeAfterResult = removeAfter(periodHolder); boolean isPrewarmingAndReadBeyondNewDuration =
boolean readingPeriodRemoved = periodHolder == prewarming
(removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) != 0; && (maxRendererPrewarmingPositionUs == C.TIME_END_OF_SOURCE
return !readingPeriodRemoved && !isReadingAndReadBeyondNewDuration; || maxRendererPrewarmingPositionUs >= newDurationInRendererTime);
@MediaPeriodQueue.UpdatePeriodQueueResult int removeAfterResult = removeAfter(periodHolder);
if (removeAfterResult != 0) {
return removeAfterResult;
}
int result = 0;
if (isReadingAndReadBeyondNewDuration) {
result |= UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD;
}
if (isPrewarmingAndReadBeyondNewDuration) {
result |= UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD;
}
return result;
} }
previousPeriodHolder = periodHolder; previousPeriodHolder = periodHolder;
periodHolder = periodHolder.getNext(); periodHolder = periodHolder.getNext();
} }
return true; return 0;
} }
/** /**
@ -846,12 +881,14 @@ import java.util.List;
* handled. If not, it is necessary to seek to the current playback position. * handled. If not, it is necessary to seek to the current playback position.
* *
* @param timeline The current timeline. * @param timeline The current timeline.
* @return {@link UpdatePeriodQueueResult} with flags denoting if the playback mode change altered
* the current reading or pre-warming media periods.
*/ */
private boolean updateForPlaybackModeChange(Timeline timeline) { private int updateForPlaybackModeChange(Timeline timeline) {
// Find the last existing period holder that matches the new period order. // Find the last existing period holder that matches the new period order.
MediaPeriodHolder lastValidPeriodHolder = playing; MediaPeriodHolder lastValidPeriodHolder = playing;
if (lastValidPeriodHolder == null) { if (lastValidPeriodHolder == null) {
return true; return 0;
} }
int currentPeriodIndex = timeline.getIndexOfPeriod(lastValidPeriodHolder.uid); int currentPeriodIndex = timeline.getIndexOfPeriod(lastValidPeriodHolder.uid);
while (true) { while (true) {
@ -876,13 +913,13 @@ import java.util.List;
} }
// Release any period holders that don't match the new period order. // Release any period holders that don't match the new period order.
@MediaPeriodQueue.UpdatePeriodQueueResult
int removeAfterResult = removeAfter(lastValidPeriodHolder); int removeAfterResult = removeAfter(lastValidPeriodHolder);
boolean readingPeriodRemoved = (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) != 0;
// 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 removeAfterResult;
} }
/** /**
@ -1256,20 +1293,27 @@ import java.util.List;
} }
/** /**
* Result for {@link #removeAfter} that signifies whether the reading or pre-warming periods were * Results for calls to {link MediaPeriodQueue} methods that may alter the reading or prewarming
* removed during the process. * periods in the queue like {@link #updateQueuedPeriods}, {@link #removeAfter}, {@link
* #updateShuffleModeEnabled}, and {@link #updateRepeatMode}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef( @IntDef(
flag = true, flag = true,
value = {REMOVE_AFTER_REMOVED_READING_PERIOD, REMOVE_AFTER_REMOVED_PREWARMING_PERIOD}) value = {
/* package */ @interface RemoveAfterResult {} UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD,
UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD
})
/* package */ @interface UpdatePeriodQueueResult {}
/** The call to {@link #removeAfter} removed the reading period. */ /** The update altered the reading period which means that a seek is required. */
/* package */ static final int REMOVE_AFTER_REMOVED_READING_PERIOD = 1; /* package */ static final int UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD = 1;
/** The call to {@link #removeAfter} removed the pre-warming period. */ /**
/* package */ static final int REMOVE_AFTER_REMOVED_PREWARMING_PERIOD = 1 << 1; * The update altered the pre-warming period which means that pre-warming renderers should be
* reset.
*/
/* package */ static final int UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD = 1 << 1;
} }

View File

@ -15,6 +15,7 @@
*/ */
package androidx.media3.exoplayer; package androidx.media3.exoplayer;
import static androidx.media3.common.Player.REPEAT_MODE_ONE;
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.run; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.run;
@ -744,6 +745,309 @@ public class ExoPlayerWithPrewarmingRenderersTest {
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_DISABLED); assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_DISABLED);
} }
@Test
public void
removeMediaItem_onPlayingPeriodWithSecondaryRendererBeforeReadingPeriodAdvanced_swapsToPrimaryRenderer()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
// Use FakeBlockingMediaSource so that reading period is not advanced when pre-warming.
new FakeBlockingMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Play a bit until the second renderer is started and primary is pre-warming.
player.play();
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_STARTED);
run(player)
.untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
// Remove the reading period.
player.removeMediaItem(1);
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState2 = videoRenderer.getState();
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
player.release();
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_STARTED);
assertThat(videoState2).isEqualTo(Renderer.STATE_STARTED);
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_DISABLED);
}
@Test
public void
removeMediaItem_onPlayingPeriodWithSecondaryRendererAfterReadingPeriodAdvanced_swapsToPrimaryRenderer()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Play a bit until the second renderer is started and primary is pre-warming.
player.play();
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_STARTED);
run(player)
.untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
player.removeMediaItem(1);
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState2 = videoRenderer.getState();
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
player.release();
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_STARTED);
assertThat(videoState2).isEqualTo(Renderer.STATE_STARTED);
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_DISABLED);
}
@Test
public void
removeMediaItem_onPrewarmingPeriodWithPrewarmingPrimaryRendererAfterReadingPeriodAdvanced_swapsToPrimaryRenderer()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Play a bit until the second renderer is started and primary is pre-warming.
player.play();
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_STARTED);
run(player)
.untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
// Remove pre-warming media item.
player.removeMediaItem(2);
run(player).untilPendingCommandsAreFullyHandled();
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState2 = videoRenderer.getState();
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
player.release();
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_STARTED);
assertThat(videoState2).isEqualTo(Renderer.STATE_STARTED);
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_ENABLED);
}
@Test
public void
replaceMediaItem_pastPrewarmingPeriodWithSecondaryRendererOnPlayingPeriod_doesNotDisablePrewarming()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
// Use FakeBlockingMediaSource so that reading period is not advanced when pre-warming.
new FakeBlockingMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Play a bit until the second renderer is started.
run(player).untilStartOfMediaItem(/* mediaItemIndex= */ 1);
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
// Replace media item past pre-warming period.
player.replaceMediaItem(
3,
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT).getMediaItem());
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState2 = videoRenderer.getState();
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
player.release();
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_STARTED);
assertThat(videoState2).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_STARTED);
}
@Test
public void
replaceMediaItem_onPrewarmingPeriodWithPrimaryRendererBeforeReadingPeriodAdvanced_disablesPrewarmingRendererOnly()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
// Use FakeBlockingMediaSource so that reading period is not advanced when pre-warming.
new FakeBlockingMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Play a bit until the primary renderer is pre-warming.
run(player).untilStartOfMediaItem(/* mediaItemIndex= */ 1);
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
// Replace pre-warming media item.
player.replaceMediaItem(
2,
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT).getMediaItem());
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState2 = videoRenderer.getState();
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
player.release();
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_STARTED);
assertThat(videoState2).isEqualTo(Renderer.STATE_DISABLED);
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_STARTED);
}
@Test
public void
setRepeatMode_withPrewarmingBeforeReadingPeriodAdvanced_disablesPrewarmingRendererOnly()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
// Use FakeBlockingMediaSource so that reading period is not advanced when pre-warming.
new FakeBlockingMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Play a bit until the second renderer is started and primary is pre-warming.
player.play();
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_STARTED);
run(player)
.untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
player.setRepeatMode(REPEAT_MODE_ONE);
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState2 = videoRenderer.getState();
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
player.release();
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_STARTED);
assertThat(videoState2).isEqualTo(Renderer.STATE_DISABLED);
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_STARTED);
}
/** {@link FakeMediaSource} that prevents any reading of samples off the sample queue. */ /** {@link FakeMediaSource} that prevents any reading of samples off the sample queue. */
private static final class FakeBlockingMediaSource extends FakeMediaSource { private static final class FakeBlockingMediaSource extends FakeMediaSource {

View File

@ -16,6 +16,8 @@
package androidx.media3.exoplayer; package androidx.media3.exoplayer;
import static androidx.media3.common.util.Util.msToUs; import static androidx.media3.common.util.Util.msToUs;
import static androidx.media3.exoplayer.MediaPeriodQueue.UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD;
import static androidx.media3.exoplayer.MediaPeriodQueue.UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD;
import static androidx.media3.test.utils.ExoPlayerTestRunner.AUDIO_FORMAT; import static androidx.media3.test.utils.ExoPlayerTestRunner.AUDIO_FORMAT;
import static androidx.media3.test.utils.ExoPlayerTestRunner.VIDEO_FORMAT; import static androidx.media3.test.utils.ExoPlayerTestRunner.VIDEO_FORMAT;
import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_MS; import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_MS;
@ -861,13 +863,15 @@ public final class MediaPeriodQueueTest {
setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 0);
long maxRendererReadPositionUs = long maxRendererReadPositionUs =
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 3000; MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 3000;
boolean changeHandled = @MediaPeriodQueue.UpdatePeriodQueueResult
int updateQueuedPeriodsResult =
mediaPeriodQueue.updateQueuedPeriods( mediaPeriodQueue.updateQueuedPeriods(
playbackInfo.timeline, playbackInfo.timeline,
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
maxRendererReadPositionUs); maxRendererReadPositionUs,
/* maxRendererPrewarmingPositionUs= */ 0);
assertThat(changeHandled).isTrue(); assertThat(updateQueuedPeriodsResult).isEqualTo(0);
assertThat(getQueueLength()).isEqualTo(1); assertThat(getQueueLength()).isEqualTo(1);
assertThat(mediaPeriodQueue.getPlayingPeriod().info.endPositionUs) assertThat(mediaPeriodQueue.getPlayingPeriod().info.endPositionUs)
.isEqualTo(FIRST_AD_START_TIME_US - 2000); .isEqualTo(FIRST_AD_START_TIME_US - 2000);
@ -889,13 +893,15 @@ public final class MediaPeriodQueueTest {
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;
boolean changeHandled = @MediaPeriodQueue.UpdatePeriodQueueResult
int updateQueuedPeriodsResult =
mediaPeriodQueue.updateQueuedPeriods( mediaPeriodQueue.updateQueuedPeriods(
playbackInfo.timeline, playbackInfo.timeline,
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
maxRendererReadPositionUs); maxRendererReadPositionUs,
/* maxRendererPrewarmingPositionUs= */ 0);
assertThat(changeHandled).isFalse(); assertThat(updateQueuedPeriodsResult).isEqualTo(UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD);
assertThat(getQueueLength()).isEqualTo(1); assertThat(getQueueLength()).isEqualTo(1);
assertThat(mediaPeriodQueue.getPlayingPeriod().info.endPositionUs) assertThat(mediaPeriodQueue.getPlayingPeriod().info.endPositionUs)
.isEqualTo(FIRST_AD_START_TIME_US - 2000); .isEqualTo(FIRST_AD_START_TIME_US - 2000);
@ -926,13 +932,15 @@ public final class MediaPeriodQueueTest {
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;
boolean changeHandled = @MediaPeriodQueue.UpdatePeriodQueueResult
int updateQueuedPeriodsResult =
mediaPeriodQueue.updateQueuedPeriods( mediaPeriodQueue.updateQueuedPeriods(
playbackInfo.timeline, playbackInfo.timeline,
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
maxRendererReadPositionUs); maxRendererReadPositionUs,
/* maxRendererPrewarmingPositionUs= */ 0);
assertThat(changeHandled).isTrue(); assertThat(updateQueuedPeriodsResult).isEqualTo(0);
assertThat(getQueueLength()).isEqualTo(1); assertThat(getQueueLength()).isEqualTo(1);
assertThat(mediaPeriodQueue.getPlayingPeriod().info.endPositionUs) assertThat(mediaPeriodQueue.getPlayingPeriod().info.endPositionUs)
.isEqualTo(FIRST_AD_START_TIME_US - 2000); .isEqualTo(FIRST_AD_START_TIME_US - 2000);
@ -956,13 +964,15 @@ public final class MediaPeriodQueueTest {
/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000); /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000);
setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 0);
setAdGroupLoaded(/* adGroupIndex= */ 1); setAdGroupLoaded(/* adGroupIndex= */ 1);
boolean changeHandled = @MediaPeriodQueue.UpdatePeriodQueueResult
int updateQueuedPeriodsResult =
mediaPeriodQueue.updateQueuedPeriods( mediaPeriodQueue.updateQueuedPeriods(
playbackInfo.timeline, playbackInfo.timeline,
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
/* maxRendererReadPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US); /* maxRendererReadPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
/* maxRendererPrewarmingPositionUs= */ 0);
assertThat(changeHandled).isTrue(); assertThat(updateQueuedPeriodsResult).isEqualTo(0);
assertThat(getQueueLength()).isEqualTo(3); assertThat(getQueueLength()).isEqualTo(3);
} }
@ -987,13 +997,18 @@ public final class MediaPeriodQueueTest {
setAdGroupLoaded(/* adGroupIndex= */ 1); setAdGroupLoaded(/* adGroupIndex= */ 1);
long maxRendererReadPositionUs = long maxRendererReadPositionUs =
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US; MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US;
boolean changeHandled = @MediaPeriodQueue.UpdatePeriodQueueResult
int updateQueuedPeriodsResult =
mediaPeriodQueue.updateQueuedPeriods( mediaPeriodQueue.updateQueuedPeriods(
playbackInfo.timeline, playbackInfo.timeline,
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
maxRendererReadPositionUs); maxRendererReadPositionUs,
/* maxRendererPrewarmingPositionUs= */ 0);
assertThat(changeHandled).isFalse(); assertThat(updateQueuedPeriodsResult)
.isEqualTo(
UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD
| UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD);
assertThat(getQueueLength()).isEqualTo(3); assertThat(getQueueLength()).isEqualTo(3);
} }
@ -1019,13 +1034,15 @@ public final class MediaPeriodQueueTest {
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US
+ FIRST_AD_START_TIME_US + FIRST_AD_START_TIME_US
+ AD_DURATION_US; + AD_DURATION_US;
boolean changeHandled = @MediaPeriodQueue.UpdatePeriodQueueResult
int updateQueuedPeriodsResult =
mediaPeriodQueue.updateQueuedPeriods( mediaPeriodQueue.updateQueuedPeriods(
playbackInfo.timeline, playbackInfo.timeline,
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
/* maxRendererReadPositionUs= */ readingPositionAtStartOfContentBetweenAds); /* maxRendererReadPositionUs= */ readingPositionAtStartOfContentBetweenAds,
/* maxRendererPrewarmingPositionUs= */ 0);
assertThat(changeHandled).isTrue(); assertThat(updateQueuedPeriodsResult).isEqualTo(0);
assertThat(getQueueLength()).isEqualTo(3); assertThat(getQueueLength()).isEqualTo(3);
} }
@ -1051,13 +1068,15 @@ public final class MediaPeriodQueueTest {
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US
+ SECOND_AD_START_TIME_US + SECOND_AD_START_TIME_US
+ AD_DURATION_US; + AD_DURATION_US;
boolean changeHandled = @MediaPeriodQueue.UpdatePeriodQueueResult
int updateQueuedPeriodsResult =
mediaPeriodQueue.updateQueuedPeriods( mediaPeriodQueue.updateQueuedPeriods(
playbackInfo.timeline, playbackInfo.timeline,
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
/* maxRendererReadPositionUs= */ readingPositionAtEndOfContentBetweenAds); /* maxRendererReadPositionUs= */ readingPositionAtEndOfContentBetweenAds,
/* maxRendererPrewarmingPositionUs= */ 0);
assertThat(changeHandled).isFalse(); assertThat(updateQueuedPeriodsResult).isEqualTo(UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD);
assertThat(getQueueLength()).isEqualTo(3); assertThat(getQueueLength()).isEqualTo(3);
} }
@ -1079,13 +1098,49 @@ public final class MediaPeriodQueueTest {
/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000); /* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000);
setAdGroupLoaded(/* adGroupIndex= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 0);
setAdGroupLoaded(/* adGroupIndex= */ 1); setAdGroupLoaded(/* adGroupIndex= */ 1);
boolean changeHandled = @MediaPeriodQueue.UpdatePeriodQueueResult
int updateQueuedPeriodsResult =
mediaPeriodQueue.updateQueuedPeriods( mediaPeriodQueue.updateQueuedPeriods(
playbackInfo.timeline, playbackInfo.timeline,
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US, /* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
/* maxRendererReadPositionUs= */ C.TIME_END_OF_SOURCE); /* maxRendererReadPositionUs= */ C.TIME_END_OF_SOURCE,
/* maxRendererPrewarmingPositionUs= */ 0);
assertThat(changeHandled).isFalse(); assertThat(updateQueuedPeriodsResult).isEqualTo(UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD);
assertThat(getQueueLength()).isEqualTo(3);
}
@Test
public void
updateQueuedPeriods_withDurationChangeInPrewarmingPeriodBeforeReadingPosition_doesntHandleChangeAndRemovesPeriodsAfterChangedPeriod() {
setupAdTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US);
setAdGroupLoaded(/* adGroupIndex= */ 0);
setAdGroupLoaded(/* adGroupIndex= */ 1);
enqueueNext(); // Content before first ad.
enqueueNext(); // First ad.
enqueueNext(); // Content between ads.
enqueueNext(); // Second ad.
advanceReading(); // Reading first ad.
mediaPeriodQueue.advancePrewarmingPeriod(); // Pre-warming content between ads.
// Change position of second ad (= change duration of content between ads).
updateAdPlaybackStateAndTimeline(
/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000);
setAdGroupLoaded(/* adGroupIndex= */ 0);
setAdGroupLoaded(/* adGroupIndex= */ 1);
long readingPositionAtEndOfContentBetweenAds =
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US
+ SECOND_AD_START_TIME_US
+ AD_DURATION_US;
@MediaPeriodQueue.UpdatePeriodQueueResult
int updateQueuedPeriodsResult =
mediaPeriodQueue.updateQueuedPeriods(
playbackInfo.timeline,
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
/* maxRendererReadPositionUs= */ C.TIME_END_OF_SOURCE,
/* maxRendererPrewarmingPositionUs= */ readingPositionAtEndOfContentBetweenAds);
assertThat(updateQueuedPeriodsResult).isEqualTo(UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD);
assertThat(getQueueLength()).isEqualTo(3); assertThat(getQueueLength()).isEqualTo(3);
} }