mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Implement mediasource list update support for pre-warming renderers
PiperOrigin-RevId: 704381778
This commit is contained in:
parent
bb62278627
commit
be63e156bb
@ -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.Util.castNonNull;
|
||||
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_DISABLE_OFFLOAD_SCHEDULING;
|
||||
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)
|
||||
throws ExoPlaybackException {
|
||||
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);
|
||||
} else if ((result & UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD) != 0) {
|
||||
disableAndResetPrewarmingRenderers();
|
||||
}
|
||||
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
||||
}
|
||||
@ -980,8 +985,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
private void setShuffleModeEnabledInternal(boolean shuffleModeEnabled)
|
||||
throws ExoPlaybackException {
|
||||
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);
|
||||
} else if ((result & UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD) != 0) {
|
||||
disableAndResetPrewarmingRenderers();
|
||||
}
|
||||
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
||||
}
|
||||
@ -1950,8 +1959,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
if (selectionsChangedForReadPeriod) {
|
||||
// Update streams and rebuffer for the new selection, recreating all streams if reading ahead.
|
||||
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
|
||||
@MediaPeriodQueue.UpdatePeriodQueueResult
|
||||
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];
|
||||
long periodPositionUs =
|
||||
@ -2135,9 +2146,26 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
if (!periodPositionChanged) {
|
||||
// We can keep the current playing period. Update the rest of the queued periods.
|
||||
if (!queue.updateQueuedPeriods(
|
||||
timeline, rendererPositionUs, getMaxRendererReadPositionUs())) {
|
||||
long maxRendererReadPositionUs =
|
||||
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);
|
||||
} else if ((updateQueuedPeriodsResult & UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD)
|
||||
!= 0) {
|
||||
disableAndResetPrewarmingRenderers();
|
||||
}
|
||||
} else if (!timeline.isEmpty()) {
|
||||
// Something changed. Seek to new start position.
|
||||
@ -2239,21 +2267,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
}
|
||||
|
||||
private long getMaxRendererReadPositionUs() {
|
||||
MediaPeriodHolder readingHolder = queue.getReadingPeriod();
|
||||
if (readingHolder == null) {
|
||||
private long getMaxRendererReadPositionUs(MediaPeriodHolder periodHolder) {
|
||||
if (periodHolder == null) {
|
||||
return 0;
|
||||
}
|
||||
long maxReadPositionUs = readingHolder.getRendererOffset();
|
||||
if (!readingHolder.prepared) {
|
||||
long maxReadPositionUs = periodHolder.getRendererOffset();
|
||||
if (!periodHolder.prepared) {
|
||||
return maxReadPositionUs;
|
||||
}
|
||||
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.
|
||||
continue;
|
||||
}
|
||||
long readingPositionUs = renderers[i].getReadingPositionUs(readingHolder);
|
||||
long readingPositionUs = renderers[i].getReadingPositionUs(periodHolder);
|
||||
if (readingPositionUs == C.TIME_END_OF_SOURCE) {
|
||||
return C.TIME_END_OF_SOURCE;
|
||||
} else {
|
||||
|
@ -119,27 +119,36 @@ import java.util.List;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RepeatMode} and returns whether the repeat mode change has been fully handled.
|
||||
* If not, it is necessary to seek to the current playback position.
|
||||
* Sets the {@link RepeatMode} and returns whether the repeat mode change change has modified the
|
||||
* 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 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;
|
||||
return updateForPlaybackModeChange(timeline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether shuffling is enabled and returns whether the shuffle mode change has been fully
|
||||
* handled. If not, it is necessary to seek to the current playback position.
|
||||
* Sets whether shuffling is enabled and returns whether the shuffle mode change has modified the
|
||||
* 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 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;
|
||||
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
|
||||
* 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.
|
||||
* Removes all period holders after the given period holder.
|
||||
*
|
||||
* <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.
|
||||
* @return {@link RemoveAfterResult} with flags denoting if the reading or pre-warming periods
|
||||
* were removed.
|
||||
* @return {@link UpdatePeriodQueueResult} with flags denoting if the reading or pre-warming
|
||||
* periods were removed.
|
||||
*/
|
||||
public int removeAfter(MediaPeriodHolder mediaPeriodHolder) {
|
||||
checkStateNotNull(mediaPeriodHolder);
|
||||
@ -451,11 +468,12 @@ import java.util.List;
|
||||
if (mediaPeriodHolder == reading) {
|
||||
reading = 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) {
|
||||
prewarming = reading;
|
||||
removedResult |= REMOVE_AFTER_REMOVED_PREWARMING_PERIOD;
|
||||
removedResult |= UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD;
|
||||
}
|
||||
mediaPeriodHolder.release();
|
||||
length--;
|
||||
@ -516,19 +534,29 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* current playback position. The method assumes that the first media period in the queue is still
|
||||
* consistent with the new timeline.
|
||||
* whether the timeline change has modified the current reading or pre-warming periods. The method
|
||||
* returns {@code 0} if all changes have been handled and the reading/pre-warming periods have not
|
||||
* 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 rendererPositionUs The current renderer position in microseconds.
|
||||
* @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
|
||||
* 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(
|
||||
Timeline timeline, long rendererPositionUs, long maxRendererReadPositionUs) {
|
||||
public @MediaPeriodQueue.UpdatePeriodQueueResult int updateQueuedPeriods(
|
||||
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
|
||||
// is set, once all cases handled by ExoPlayerImplInternal.handleMediaSourceListInfoRefreshed
|
||||
// can be handled here.
|
||||
@ -547,15 +575,10 @@ import java.util.List;
|
||||
} else {
|
||||
newPeriodInfo =
|
||||
getFollowingMediaPeriodInfo(timeline, previousPeriodHolder, rendererPositionUs);
|
||||
if (newPeriodInfo == null) {
|
||||
// We've loaded a next media period that is not in the new timeline.
|
||||
int removeAfterResult = removeAfter(previousPeriodHolder);
|
||||
return (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) == 0;
|
||||
}
|
||||
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;
|
||||
if (newPeriodInfo == null || !canKeepMediaPeriodHolder(oldPeriodInfo, newPeriodInfo)) {
|
||||
// We've loaded a next media period that is not in the new timeline
|
||||
// or the new media period has a different id or start position.
|
||||
return removeAfter(previousPeriodHolder);
|
||||
}
|
||||
}
|
||||
|
||||
@ -578,16 +601,28 @@ import java.util.List;
|
||||
&& !periodHolder.info.isFollowedByTransitionToSameStream
|
||||
&& (maxRendererReadPositionUs == C.TIME_END_OF_SOURCE
|
||||
|| maxRendererReadPositionUs >= newDurationInRendererTime);
|
||||
int removeAfterResult = removeAfter(periodHolder);
|
||||
boolean readingPeriodRemoved =
|
||||
(removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) != 0;
|
||||
return !readingPeriodRemoved && !isReadingAndReadBeyondNewDuration;
|
||||
boolean isPrewarmingAndReadBeyondNewDuration =
|
||||
periodHolder == prewarming
|
||||
&& (maxRendererPrewarmingPositionUs == C.TIME_END_OF_SOURCE
|
||||
|| 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;
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
MediaPeriodHolder lastValidPeriodHolder = playing;
|
||||
if (lastValidPeriodHolder == null) {
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
int currentPeriodIndex = timeline.getIndexOfPeriod(lastValidPeriodHolder.uid);
|
||||
while (true) {
|
||||
@ -876,13 +913,13 @@ import java.util.List;
|
||||
}
|
||||
|
||||
// Release any period holders that don't match the new period order.
|
||||
@MediaPeriodQueue.UpdatePeriodQueueResult
|
||||
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.
|
||||
lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(timeline, lastValidPeriodHolder.info);
|
||||
// If renderers may have read from a period that's been removed, it is necessary to restart.
|
||||
return !readingPeriodRemoved;
|
||||
return removeAfterResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1256,20 +1293,27 @@ import java.util.List;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result for {@link #removeAfter} that signifies whether the reading or pre-warming periods were
|
||||
* removed during the process.
|
||||
* Results for calls to {link MediaPeriodQueue} methods that may alter the reading or prewarming
|
||||
* periods in the queue like {@link #updateQueuedPeriods}, {@link #removeAfter}, {@link
|
||||
* #updateShuffleModeEnabled}, and {@link #updateRepeatMode}.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef(
|
||||
flag = true,
|
||||
value = {REMOVE_AFTER_REMOVED_READING_PERIOD, REMOVE_AFTER_REMOVED_PREWARMING_PERIOD})
|
||||
/* package */ @interface RemoveAfterResult {}
|
||||
value = {
|
||||
UPDATE_PERIOD_QUEUE_ALTERED_READING_PERIOD,
|
||||
UPDATE_PERIOD_QUEUE_ALTERED_PREWARMING_PERIOD
|
||||
})
|
||||
/* package */ @interface UpdatePeriodQueueResult {}
|
||||
|
||||
/** The call to {@link #removeAfter} removed the reading period. */
|
||||
/* package */ static final int REMOVE_AFTER_REMOVED_READING_PERIOD = 1;
|
||||
/** The update altered the reading period which means that a seek is required. */
|
||||
/* 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;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
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.oneByteSample;
|
||||
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.run;
|
||||
@ -744,6 +745,309 @@ public class ExoPlayerWithPrewarmingRenderersTest {
|
||||
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. */
|
||||
private static final class FakeBlockingMediaSource extends FakeMediaSource {
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
package androidx.media3.exoplayer;
|
||||
|
||||
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.VIDEO_FORMAT;
|
||||
import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_MS;
|
||||
@ -861,13 +863,15 @@ public final class MediaPeriodQueueTest {
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||
long maxRendererReadPositionUs =
|
||||
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 3000;
|
||||
boolean changeHandled =
|
||||
@MediaPeriodQueue.UpdatePeriodQueueResult
|
||||
int updateQueuedPeriodsResult =
|
||||
mediaPeriodQueue.updateQueuedPeriods(
|
||||
playbackInfo.timeline,
|
||||
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
|
||||
maxRendererReadPositionUs);
|
||||
maxRendererReadPositionUs,
|
||||
/* maxRendererPrewarmingPositionUs= */ 0);
|
||||
|
||||
assertThat(changeHandled).isTrue();
|
||||
assertThat(updateQueuedPeriodsResult).isEqualTo(0);
|
||||
assertThat(getQueueLength()).isEqualTo(1);
|
||||
assertThat(mediaPeriodQueue.getPlayingPeriod().info.endPositionUs)
|
||||
.isEqualTo(FIRST_AD_START_TIME_US - 2000);
|
||||
@ -889,13 +893,15 @@ public final class MediaPeriodQueueTest {
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||
long maxRendererReadPositionUs =
|
||||
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 1000;
|
||||
boolean changeHandled =
|
||||
@MediaPeriodQueue.UpdatePeriodQueueResult
|
||||
int updateQueuedPeriodsResult =
|
||||
mediaPeriodQueue.updateQueuedPeriods(
|
||||
playbackInfo.timeline,
|
||||
/* 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(mediaPeriodQueue.getPlayingPeriod().info.endPositionUs)
|
||||
.isEqualTo(FIRST_AD_START_TIME_US - 2000);
|
||||
@ -926,13 +932,15 @@ public final class MediaPeriodQueueTest {
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||
long maxRendererReadPositionUs =
|
||||
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US - 1000;
|
||||
boolean changeHandled =
|
||||
@MediaPeriodQueue.UpdatePeriodQueueResult
|
||||
int updateQueuedPeriodsResult =
|
||||
mediaPeriodQueue.updateQueuedPeriods(
|
||||
playbackInfo.timeline,
|
||||
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
|
||||
maxRendererReadPositionUs);
|
||||
maxRendererReadPositionUs,
|
||||
/* maxRendererPrewarmingPositionUs= */ 0);
|
||||
|
||||
assertThat(changeHandled).isTrue();
|
||||
assertThat(updateQueuedPeriodsResult).isEqualTo(0);
|
||||
assertThat(getQueueLength()).isEqualTo(1);
|
||||
assertThat(mediaPeriodQueue.getPlayingPeriod().info.endPositionUs)
|
||||
.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);
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
||||
boolean changeHandled =
|
||||
@MediaPeriodQueue.UpdatePeriodQueueResult
|
||||
int updateQueuedPeriodsResult =
|
||||
mediaPeriodQueue.updateQueuedPeriods(
|
||||
playbackInfo.timeline,
|
||||
/* 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);
|
||||
}
|
||||
|
||||
@ -987,13 +997,18 @@ public final class MediaPeriodQueueTest {
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
||||
long maxRendererReadPositionUs =
|
||||
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US + FIRST_AD_START_TIME_US;
|
||||
boolean changeHandled =
|
||||
@MediaPeriodQueue.UpdatePeriodQueueResult
|
||||
int updateQueuedPeriodsResult =
|
||||
mediaPeriodQueue.updateQueuedPeriods(
|
||||
playbackInfo.timeline,
|
||||
/* 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);
|
||||
}
|
||||
|
||||
@ -1019,13 +1034,15 @@ public final class MediaPeriodQueueTest {
|
||||
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US
|
||||
+ FIRST_AD_START_TIME_US
|
||||
+ AD_DURATION_US;
|
||||
boolean changeHandled =
|
||||
@MediaPeriodQueue.UpdatePeriodQueueResult
|
||||
int updateQueuedPeriodsResult =
|
||||
mediaPeriodQueue.updateQueuedPeriods(
|
||||
playbackInfo.timeline,
|
||||
/* rendererPositionUs= */ MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US,
|
||||
/* maxRendererReadPositionUs= */ readingPositionAtStartOfContentBetweenAds);
|
||||
/* maxRendererReadPositionUs= */ readingPositionAtStartOfContentBetweenAds,
|
||||
/* maxRendererPrewarmingPositionUs= */ 0);
|
||||
|
||||
assertThat(changeHandled).isTrue();
|
||||
assertThat(updateQueuedPeriodsResult).isEqualTo(0);
|
||||
assertThat(getQueueLength()).isEqualTo(3);
|
||||
}
|
||||
|
||||
@ -1051,13 +1068,15 @@ public final class MediaPeriodQueueTest {
|
||||
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US
|
||||
+ SECOND_AD_START_TIME_US
|
||||
+ AD_DURATION_US;
|
||||
boolean changeHandled =
|
||||
@MediaPeriodQueue.UpdatePeriodQueueResult
|
||||
int updateQueuedPeriodsResult =
|
||||
mediaPeriodQueue.updateQueuedPeriods(
|
||||
playbackInfo.timeline,
|
||||
/* 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);
|
||||
}
|
||||
|
||||
@ -1079,13 +1098,49 @@ public final class MediaPeriodQueueTest {
|
||||
/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US - 1000);
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 0);
|
||||
setAdGroupLoaded(/* adGroupIndex= */ 1);
|
||||
boolean changeHandled =
|
||||
@MediaPeriodQueue.UpdatePeriodQueueResult
|
||||
int updateQueuedPeriodsResult =
|
||||
mediaPeriodQueue.updateQueuedPeriods(
|
||||
playbackInfo.timeline,
|
||||
/* 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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user