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.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 {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user