Add queue abstraction to ExoPlayerImplInternal.

This gets rid of the manual tracking of this queue with reading, playing,
and loading period holders. Still keeping these names for queue access methods.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=182378944
This commit is contained in:
tonihei 2018-01-18 08:20:35 -08:00 committed by Oliver Woodman
parent c577d9d351
commit 029c95832c

View File

@ -118,6 +118,7 @@ import java.util.Collections;
private final PlaybackInfoUpdate playbackInfoUpdate; private final PlaybackInfoUpdate playbackInfoUpdate;
private final ArrayList<CustomMessageInfo> customMessageInfos; private final ArrayList<CustomMessageInfo> customMessageInfos;
private final Clock clock; private final Clock clock;
private final MediaPeriodHolderQueue queue;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private SeekParameters seekParameters; private SeekParameters seekParameters;
@ -136,10 +137,6 @@ import java.util.Collections;
private long rendererPositionUs; private long rendererPositionUs;
private int nextCustomMessageInfoIndex; private int nextCustomMessageInfoIndex;
private MediaPeriodHolder loadingPeriodHolder;
private MediaPeriodHolder readingPeriodHolder;
private MediaPeriodHolder playingPeriodHolder;
public ExoPlayerImplInternal( public ExoPlayerImplInternal(
Renderer[] renderers, Renderer[] renderers,
TrackSelector trackSelector, TrackSelector trackSelector,
@ -161,6 +158,7 @@ import java.util.Collections;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.player = player; this.player = player;
this.clock = clock; this.clock = clock;
this.queue = new MediaPeriodHolderQueue();
backBufferDurationUs = loadControl.getBackBufferDurationUs(); backBufferDurationUs = loadControl.getBackBufferDurationUs();
retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe(); retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe();
@ -444,8 +442,7 @@ import java.util.Collections;
private void validateExistingPeriodHolders() throws ExoPlaybackException { private void validateExistingPeriodHolders() throws ExoPlaybackException {
// 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 = playingPeriodHolder != null MediaPeriodHolder lastValidPeriodHolder = queue.getFrontPeriod();
? playingPeriodHolder : loadingPeriodHolder;
if (lastValidPeriodHolder == null) { if (lastValidPeriodHolder == null) {
return; return;
} }
@ -465,30 +462,19 @@ import java.util.Collections;
} }
// Release any period holders that don't match the new period order. // Release any period holders that don't match the new period order.
int loadingPeriodHolderIndex = loadingPeriodHolder.index; boolean readingPeriodRemoved = queue.removeAfter(lastValidPeriodHolder);
int readingPeriodHolderIndex =
readingPeriodHolder != null ? readingPeriodHolder.index : C.INDEX_UNSET;
if (lastValidPeriodHolder.next != null) {
releasePeriodHoldersFrom(lastValidPeriodHolder.next);
lastValidPeriodHolder.next = null;
}
// 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 = lastValidPeriodHolder.info =
mediaPeriodInfoSequence.getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info); mediaPeriodInfoSequence.getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info);
// Handle cases where loadingPeriodHolder or readingPeriodHolder have been removed. if (readingPeriodRemoved && queue.hasPlayingPeriod()) {
boolean seenLoadingPeriodHolder = loadingPeriodHolderIndex <= lastValidPeriodHolder.index;
if (!seenLoadingPeriodHolder) {
loadingPeriodHolder = lastValidPeriodHolder;
}
boolean seenReadingPeriodHolder = readingPeriodHolderIndex != C.INDEX_UNSET
&& readingPeriodHolderIndex <= lastValidPeriodHolder.index;
if (!seenReadingPeriodHolder && playingPeriodHolder != null) {
// Renderers may have read from a period that's been removed. Seek back to the current // Renderers may have read from a period that's been removed. Seek back to the current
// position of the playing period to make sure none of the removed period is played. // position of the playing period to make sure none of the removed period is played.
MediaPeriodId periodId = playingPeriodHolder.info.id; MediaPeriodId periodId = queue.getPlayingPeriod().info.id;
long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.positionUs); long newPositionUs =
seekToPeriodPosition(
periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true);
if (newPositionUs != playbackInfo.positionUs) { if (newPositionUs != playbackInfo.positionUs) {
playbackInfo = playbackInfo =
playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs); playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs);
@ -513,11 +499,12 @@ import java.util.Collections;
} }
private void updatePlaybackPositions() throws ExoPlaybackException { private void updatePlaybackPositions() throws ExoPlaybackException {
if (playingPeriodHolder == null) { if (!queue.hasPlayingPeriod()) {
return; return;
} }
// Update the playback position. // Update the playback position.
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
long periodPositionUs = playingPeriodHolder.mediaPeriod.readDiscontinuity(); long periodPositionUs = playingPeriodHolder.mediaPeriod.readDiscontinuity();
if (periodPositionUs != C.TIME_UNSET) { if (periodPositionUs != C.TIME_UNSET) {
resetRendererPosition(periodPositionUs); resetRendererPosition(periodPositionUs);
@ -545,12 +532,13 @@ import java.util.Collections;
private void doSomeWork() throws ExoPlaybackException, IOException { private void doSomeWork() throws ExoPlaybackException, IOException {
long operationStartTimeMs = clock.uptimeMillis(); long operationStartTimeMs = clock.uptimeMillis();
updatePeriods(); updatePeriods();
if (playingPeriodHolder == null) { if (!queue.hasPlayingPeriod()) {
// We're still waiting for the first period to be prepared. // We're still waiting for the first period to be prepared.
maybeThrowPeriodPrepareError(); maybeThrowPeriodPrepareError();
scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS); scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
return; return;
} }
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
TraceUtil.beginSection("doSomeWork"); TraceUtil.beginSection("doSomeWork");
@ -594,10 +582,13 @@ import java.util.Collections;
stopRenderers(); stopRenderers();
} else if (playbackInfo.playbackState == Player.STATE_BUFFERING) { } else if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
float playbackSpeed = mediaClock.getPlaybackParameters().speed; float playbackSpeed = mediaClock.getPlaybackParameters().speed;
boolean isNewlyReady = enabledRenderers.length > 0 boolean isNewlyReady =
? (allRenderersReadyOrEnded && loadingPeriodHolder.haveSufficientBuffer( enabledRenderers.length > 0
rendererPositionUs, playbackSpeed, rebuffering)) ? (allRenderersReadyOrEnded
: isTimelineReady(playingPeriodDurationUs); && queue
.getLoadingPeriod()
.haveSufficientBuffer(rendererPositionUs, playbackSpeed, rebuffering))
: isTimelineReady(playingPeriodDurationUs);
if (isNewlyReady) { if (isNewlyReady) {
setState(Player.STATE_READY); setState(Player.STATE_READY);
if (playWhenReady) { if (playWhenReady) {
@ -672,6 +663,7 @@ import java.util.Collections;
try { try {
long newPeriodPositionUs = periodPositionUs; long newPeriodPositionUs = periodPositionUs;
if (periodId.equals(playbackInfo.periodId)) { if (periodId.equals(playbackInfo.periodId)) {
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
if (playingPeriodHolder != null && newPeriodPositionUs != 0) { if (playingPeriodHolder != null && newPeriodPositionUs != 0) {
newPeriodPositionUs = newPeriodPositionUs =
playingPeriodHolder.mediaPeriod.getAdjustedSeekPositionUs( playingPeriodHolder.mediaPeriod.getAdjustedSeekPositionUs(
@ -698,58 +690,50 @@ import java.util.Collections;
private long seekToPeriodPosition(MediaPeriodId periodId, long periodPositionUs) private long seekToPeriodPosition(MediaPeriodId periodId, long periodPositionUs)
throws ExoPlaybackException { throws ExoPlaybackException {
// Force disable renderers if they are reading from a period other than the one being played.
return seekToPeriodPosition(
periodId, periodPositionUs, queue.getPlayingPeriod() != queue.getReadingPeriod());
}
private long seekToPeriodPosition(
MediaPeriodId periodId, long periodPositionUs, boolean forceDisableRenderers)
throws ExoPlaybackException {
stopRenderers(); stopRenderers();
rebuffering = false; rebuffering = false;
setState(Player.STATE_BUFFERING); setState(Player.STATE_BUFFERING);
MediaPeriodHolder newPlayingPeriodHolder = null; // Clear the timeline, but keep the requested period if it is already prepared.
if (playingPeriodHolder == null) { MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod();
// We're still waiting for the first period to be prepared. MediaPeriodHolder newPlayingPeriodHolder = oldPlayingPeriodHolder;
if (loadingPeriodHolder != null) { while (newPlayingPeriodHolder != null) {
loadingPeriodHolder.release(); if (shouldKeepPeriodHolder(periodId, periodPositionUs, newPlayingPeriodHolder)) {
} queue.removeAfter(newPlayingPeriodHolder);
} else { break;
// Clear the timeline, but keep the requested period if it is already prepared.
MediaPeriodHolder periodHolder = playingPeriodHolder;
while (periodHolder != null) {
if (newPlayingPeriodHolder == null
&& shouldKeepPeriodHolder(periodId, periodPositionUs, periodHolder)) {
newPlayingPeriodHolder = periodHolder;
} else {
periodHolder.release();
}
periodHolder = periodHolder.next;
} }
newPlayingPeriodHolder = queue.advancePlayingPeriod();
} }
// Disable all the renderers if the period being played is changing, or if the renderers are // Disable all the renderers if the period being played is changing, or if forced.
// reading from a period other than the one being played. if (oldPlayingPeriodHolder != newPlayingPeriodHolder || forceDisableRenderers) {
if (playingPeriodHolder != newPlayingPeriodHolder
|| playingPeriodHolder != readingPeriodHolder) {
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
disableRenderer(renderer); disableRenderer(renderer);
} }
enabledRenderers = new Renderer[0]; enabledRenderers = new Renderer[0];
playingPeriodHolder = null; oldPlayingPeriodHolder = null;
} }
// Update the holders. // Update the holders.
if (newPlayingPeriodHolder != null) { if (newPlayingPeriodHolder != null) {
newPlayingPeriodHolder.next = null; updatePlayingPeriodRenderers(oldPlayingPeriodHolder);
loadingPeriodHolder = newPlayingPeriodHolder; if (newPlayingPeriodHolder.hasEnabledTracks) {
readingPeriodHolder = newPlayingPeriodHolder; periodPositionUs = newPlayingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs);
setPlayingPeriodHolder(newPlayingPeriodHolder); newPlayingPeriodHolder.mediaPeriod.discardBuffer(
if (playingPeriodHolder.hasEnabledTracks) { periodPositionUs - backBufferDurationUs, retainBackBufferFromKeyframe);
periodPositionUs = playingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs);
playingPeriodHolder.mediaPeriod.discardBuffer(periodPositionUs - backBufferDurationUs,
retainBackBufferFromKeyframe);
} }
resetRendererPosition(periodPositionUs); resetRendererPosition(periodPositionUs);
maybeContinueLoading(); maybeContinueLoading();
} else { } else {
loadingPeriodHolder = null; queue.clear();
readingPeriodHolder = null;
playingPeriodHolder = null;
resetRendererPosition(periodPositionUs); resetRendererPosition(periodPositionUs);
} }
@ -771,9 +755,10 @@ import java.util.Collections;
} }
private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException { private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException {
rendererPositionUs = playingPeriodHolder == null rendererPositionUs =
? periodPositionUs + RENDERER_TIMESTAMP_OFFSET_US !queue.hasPlayingPeriod()
: playingPeriodHolder.toRendererTime(periodPositionUs); ? periodPositionUs + RENDERER_TIMESTAMP_OFFSET_US
: queue.getPlayingPeriod().toRendererTime(periodPositionUs);
mediaClock.resetPosition(rendererPositionUs); mediaClock.resetPosition(rendererPositionUs);
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
renderer.resetPosition(rendererPositionUs); renderer.resetPosition(rendererPositionUs);
@ -825,11 +810,7 @@ import java.util.Collections;
} }
} }
enabledRenderers = new Renderer[0]; enabledRenderers = new Renderer[0];
releasePeriodHoldersFrom(playingPeriodHolder != null ? playingPeriodHolder queue.clear();
: loadingPeriodHolder);
loadingPeriodHolder = null;
readingPeriodHolder = null;
playingPeriodHolder = null;
setIsLoading(false); setIsLoading(false);
Timeline timeline = playbackInfo.timeline; Timeline timeline = playbackInfo.timeline;
int firstPeriodIndex = int firstPeriodIndex =
@ -1030,13 +1011,14 @@ import java.util.Collections;
} }
private void reselectTracksInternal() throws ExoPlaybackException { private void reselectTracksInternal() throws ExoPlaybackException {
if (playingPeriodHolder == null) { if (!queue.hasPlayingPeriod()) {
// We don't have tracks yet, so we don't care. // We don't have tracks yet, so we don't care.
return; return;
} }
float playbackSpeed = mediaClock.getPlaybackParameters().speed; float playbackSpeed = mediaClock.getPlaybackParameters().speed;
// Reselect tracks on each period in turn, until the selection changes. // Reselect tracks on each period in turn, until the selection changes.
MediaPeriodHolder periodHolder = playingPeriodHolder; MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
boolean selectionsChangedForReadPeriod = true; boolean selectionsChangedForReadPeriod = true;
while (true) { while (true) {
if (periodHolder == null || !periodHolder.prepared) { if (periodHolder == null || !periodHolder.prepared) {
@ -1056,11 +1038,8 @@ import java.util.Collections;
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.
boolean recreateStreams = readingPeriodHolder != playingPeriodHolder; MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
releasePeriodHoldersFrom(playingPeriodHolder.next); boolean recreateStreams = queue.removeAfter(playingPeriodHolder);
playingPeriodHolder.next = null;
loadingPeriodHolder = playingPeriodHolder;
readingPeriodHolder = playingPeriodHolder;
boolean[] streamResetFlags = new boolean[renderers.length]; boolean[] streamResetFlags = new boolean[renderers.length];
long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection( long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection(
@ -1092,21 +1071,17 @@ import java.util.Collections;
} }
} }
} }
playbackInfo = playbackInfo.copyWithTrackSelectorResult(periodHolder.trackSelectorResult); playbackInfo =
playbackInfo.copyWithTrackSelectorResult(playingPeriodHolder.trackSelectorResult);
enableRenderers(rendererWasEnabledFlags, enabledRendererCount); enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
} else { } else {
// Release and re-prepare/buffer periods after the one whose selection changed. // Release and re-prepare/buffer periods after the one whose selection changed.
loadingPeriodHolder = periodHolder; queue.removeAfter(periodHolder);
periodHolder = loadingPeriodHolder.next; if (periodHolder.prepared) {
while (periodHolder != null) { long loadingPeriodPositionUs =
periodHolder.release(); Math.max(
periodHolder = periodHolder.next; periodHolder.info.startPositionUs, periodHolder.toPeriodTime(rendererPositionUs));
} periodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, false);
loadingPeriodHolder.next = null;
if (loadingPeriodHolder.prepared) {
long loadingPeriodPositionUs = Math.max(loadingPeriodHolder.info.startPositionUs,
loadingPeriodHolder.toPeriodTime(rendererPositionUs));
loadingPeriodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, false);
} }
} }
if (playbackInfo.playbackState != Player.STATE_ENDED) { if (playbackInfo.playbackState != Player.STATE_ENDED) {
@ -1117,8 +1092,7 @@ import java.util.Collections;
} }
private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) { private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) {
MediaPeriodHolder periodHolder = MediaPeriodHolder periodHolder = queue.getFrontPeriod();
playingPeriodHolder != null ? playingPeriodHolder : loadingPeriodHolder;
while (periodHolder != null) { while (periodHolder != null) {
if (periodHolder.trackSelectorResult != null) { if (periodHolder.trackSelectorResult != null) {
TrackSelection[] trackSelections = periodHolder.trackSelectorResult.selections.getAll(); TrackSelection[] trackSelections = periodHolder.trackSelectorResult.selections.getAll();
@ -1133,6 +1107,7 @@ import java.util.Collections;
} }
private boolean isTimelineReady(long playingPeriodDurationUs) { private boolean isTimelineReady(long playingPeriodDurationUs) {
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
return playingPeriodDurationUs == C.TIME_UNSET return playingPeriodDurationUs == C.TIME_UNSET
|| playbackInfo.positionUs < playingPeriodDurationUs || playbackInfo.positionUs < playingPeriodDurationUs
|| (playingPeriodHolder.next != null || (playingPeriodHolder.next != null
@ -1140,6 +1115,8 @@ import java.util.Collections;
} }
private void maybeThrowPeriodPrepareError() throws IOException { private void maybeThrowPeriodPrepareError() throws IOException {
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
if (loadingPeriodHolder != null && !loadingPeriodHolder.prepared if (loadingPeriodHolder != null && !loadingPeriodHolder.prepared
&& (readingPeriodHolder == null || readingPeriodHolder.next == loadingPeriodHolder)) { && (readingPeriodHolder == null || readingPeriodHolder.next == loadingPeriodHolder)) {
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
@ -1202,8 +1179,7 @@ import java.util.Collections;
} }
int playingPeriodIndex = playbackInfo.periodId.periodIndex; int playingPeriodIndex = playbackInfo.periodId.periodIndex;
MediaPeriodHolder periodHolder = playingPeriodHolder != null ? playingPeriodHolder MediaPeriodHolder periodHolder = queue.getFrontPeriod();
: loadingPeriodHolder;
if (periodHolder == null && playingPeriodIndex >= oldTimeline.getPeriodCount()) { if (periodHolder == null && playingPeriodIndex >= oldTimeline.getPeriodCount()) {
return; return;
} }
@ -1283,22 +1259,15 @@ import java.util.Collections;
periodHolder = updatePeriodInfo(periodHolder, periodIndex); periodHolder = updatePeriodInfo(periodHolder, periodIndex);
} else { } else {
// The holder is inconsistent with the new timeline. // The holder is inconsistent with the new timeline.
boolean seenReadingPeriodHolder = boolean readingPeriodRemoved = queue.removeAfter(previousPeriodHolder);
readingPeriodHolder != null && readingPeriodHolder.index < periodHolder.index; if (readingPeriodRemoved) {
if (!seenReadingPeriodHolder) {
// Renderers may have read from a period that's been removed. Seek back to the current // Renderers may have read from a period that's been removed. Seek back to the current
// position of the playing period to make sure none of the removed period is played. // position of the playing period to make sure none of the removed period is played.
MediaPeriodId id = queue.getPlayingPeriod().info.id;
long newPositionUs = long newPositionUs =
seekToPeriodPosition(playingPeriodHolder.info.id, playbackInfo.positionUs); seekToPeriodPosition(id, playbackInfo.positionUs, /* forceDisableRenderers= */ true);
playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id, newPositionUs, playbackInfo =
playbackInfo.contentPositionUs); playbackInfo.fromNewPosition(id, newPositionUs, playbackInfo.contentPositionUs);
} else {
// Update the loading period to be the last period that's still valid, and release all
// subsequent periods.
loadingPeriodHolder = previousPeriodHolder;
loadingPeriodHolder.next = null;
// Release the rest of the timeline.
releasePeriodHoldersFrom(periodHolder);
} }
break; break;
} }
@ -1426,19 +1395,21 @@ import java.util.Collections;
// Update the loading period if required. // Update the loading period if required.
maybeUpdateLoadingPeriod(); maybeUpdateLoadingPeriod();
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) { if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) {
setIsLoading(false); setIsLoading(false);
} else if (loadingPeriodHolder != null && !playbackInfo.isLoading) { } else if (loadingPeriodHolder != null && !playbackInfo.isLoading) {
maybeContinueLoading(); maybeContinueLoading();
} }
if (playingPeriodHolder == null) { if (!queue.hasPlayingPeriod()) {
// We're waiting for the first period to be prepared. // We're waiting for the first period to be prepared.
return; return;
} }
// Advance the playing period if necessary. // Advance the playing period if necessary.
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
boolean advancedPlayingPeriod = false; boolean advancedPlayingPeriod = false;
while (playWhenReady && playingPeriodHolder != readingPeriodHolder while (playWhenReady && playingPeriodHolder != readingPeriodHolder
&& rendererPositionUs >= playingPeriodHolder.next.rendererPositionOffsetUs) { && rendererPositionUs >= playingPeriodHolder.next.rendererPositionOffsetUs) {
@ -1452,8 +1423,9 @@ import java.util.Collections;
playingPeriodHolder.info.isLastInTimelinePeriod playingPeriodHolder.info.isLastInTimelinePeriod
? Player.DISCONTINUITY_REASON_PERIOD_TRANSITION ? Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
: Player.DISCONTINUITY_REASON_AD_INSERTION; : Player.DISCONTINUITY_REASON_AD_INSERTION;
playingPeriodHolder.release(); MediaPeriodHolder oldPlayingPeriodHolder = playingPeriodHolder;
setPlayingPeriodHolder(playingPeriodHolder.next); playingPeriodHolder = queue.advancePlayingPeriod();
updatePlayingPeriodRenderers(oldPlayingPeriodHolder);
playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id, playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id,
playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs); playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs);
playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason); playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason);
@ -1492,7 +1464,7 @@ import java.util.Collections;
} }
TrackSelectorResult oldTrackSelectorResult = readingPeriodHolder.trackSelectorResult; TrackSelectorResult oldTrackSelectorResult = readingPeriodHolder.trackSelectorResult;
readingPeriodHolder = readingPeriodHolder.next; readingPeriodHolder = queue.advanceReadingPeriod();
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.trackSelectorResult; TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.trackSelectorResult;
boolean initialDiscontinuity = boolean initialDiscontinuity =
@ -1536,6 +1508,7 @@ import java.util.Collections;
private void maybeUpdateLoadingPeriod() throws IOException { private void maybeUpdateLoadingPeriod() throws IOException {
MediaPeriodInfo info; MediaPeriodInfo info;
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
if (loadingPeriodHolder == null) { if (loadingPeriodHolder == null) {
info = mediaPeriodInfoSequence.getFirstMediaPeriodInfo(playbackInfo); info = mediaPeriodInfoSequence.getFirstMediaPeriodInfo(playbackInfo);
} else { } else {
@ -1544,12 +1517,9 @@ import java.util.Collections;
|| loadingPeriodHolder.info.durationUs == C.TIME_UNSET) { || loadingPeriodHolder.info.durationUs == C.TIME_UNSET) {
return; return;
} }
if (playingPeriodHolder != null) { if (queue.getLength() == MAXIMUM_BUFFER_AHEAD_PERIODS) {
int bufferAheadPeriodCount = loadingPeriodHolder.index - playingPeriodHolder.index; // We are already buffering the maximum number of periods ahead.
if (bufferAheadPeriodCount == MAXIMUM_BUFFER_AHEAD_PERIODS) { return;
// We are already buffering the maximum number of periods ahead.
return;
}
} }
info = mediaPeriodInfoSequence.getNextMediaPeriodInfo(loadingPeriodHolder.info, info = mediaPeriodInfoSequence.getNextMediaPeriodInfo(loadingPeriodHolder.info,
loadingPeriodHolder.getRendererOffset(), rendererPositionUs); loadingPeriodHolder.getRendererOffset(), rendererPositionUs);
@ -1563,34 +1533,40 @@ import java.util.Collections;
loadingPeriodHolder == null loadingPeriodHolder == null
? (info.startPositionUs + RENDERER_TIMESTAMP_OFFSET_US) ? (info.startPositionUs + RENDERER_TIMESTAMP_OFFSET_US)
: (loadingPeriodHolder.getRendererOffset() + loadingPeriodHolder.info.durationUs); : (loadingPeriodHolder.getRendererOffset() + loadingPeriodHolder.info.durationUs);
int holderIndex = loadingPeriodHolder == null ? 0 : loadingPeriodHolder.index + 1;
Object uid = playbackInfo.timeline.getPeriod(info.id.periodIndex, period, true).uid; Object uid = playbackInfo.timeline.getPeriod(info.id.periodIndex, period, true).uid;
MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities, MediaPeriodHolder newPeriodHolder =
rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, uid, holderIndex, info); new MediaPeriodHolder(
if (loadingPeriodHolder != null) { renderers,
loadingPeriodHolder.next = newPeriodHolder; rendererCapabilities,
} rendererPositionOffsetUs,
loadingPeriodHolder = newPeriodHolder; trackSelector,
loadingPeriodHolder.mediaPeriod.prepare(this, info.startPositionUs); loadControl,
mediaSource,
uid,
info);
queue.enqueueLoadingPeriod(newPeriodHolder);
newPeriodHolder.mediaPeriod.prepare(this, info.startPositionUs);
setIsLoading(true); setIsLoading(true);
} }
private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException { private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException {
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) { if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) {
// Stale event. // Stale event.
return; return;
} }
loadingPeriodHolder.handlePrepared(mediaClock.getPlaybackParameters().speed); loadingPeriodHolder.handlePrepared(mediaClock.getPlaybackParameters().speed);
if (playingPeriodHolder == null) { if (!queue.hasPlayingPeriod()) {
// This is the first prepared period, so start playing it. // This is the first prepared period, so start playing it.
readingPeriodHolder = loadingPeriodHolder; MediaPeriodHolder playingPeriodHolder = queue.advancePlayingPeriod();
resetRendererPosition(readingPeriodHolder.info.startPositionUs); resetRendererPosition(playingPeriodHolder.info.startPositionUs);
setPlayingPeriodHolder(readingPeriodHolder); updatePlayingPeriodRenderers(/* oldPlayingPeriodHolder= */ null);
} }
maybeContinueLoading(); maybeContinueLoading();
} }
private void handleContinueLoadingRequested(MediaPeriod period) { private void handleContinueLoadingRequested(MediaPeriod period) {
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) { if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) {
// Stale event. // Stale event.
return; return;
@ -1600,6 +1576,7 @@ import java.util.Collections;
} }
private void maybeContinueLoading() { private void maybeContinueLoading() {
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
boolean continueLoading = loadingPeriodHolder.shouldContinueLoading( boolean continueLoading = loadingPeriodHolder.shouldContinueLoading(
rendererPositionUs, mediaClock.getPlaybackParameters().speed); rendererPositionUs, mediaClock.getPlaybackParameters().speed);
setIsLoading(continueLoading); setIsLoading(continueLoading);
@ -1608,38 +1585,32 @@ import java.util.Collections;
} }
} }
private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) { private void updatePlayingPeriodRenderers(@Nullable MediaPeriodHolder oldPlayingPeriodHolder)
while (periodHolder != null) { throws ExoPlaybackException {
periodHolder.release(); MediaPeriodHolder newPlayingPeriodHolder = queue.getPlayingPeriod();
periodHolder = periodHolder.next; if (newPlayingPeriodHolder == null || oldPlayingPeriodHolder == newPlayingPeriodHolder) {
}
}
private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException {
if (playingPeriodHolder == periodHolder) {
return; return;
} }
int enabledRendererCount = 0; int enabledRendererCount = 0;
boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i]; Renderer renderer = renderers[i];
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
if (periodHolder.trackSelectorResult.renderersEnabled[i]) { if (newPlayingPeriodHolder.trackSelectorResult.renderersEnabled[i]) {
enabledRendererCount++; enabledRendererCount++;
} }
if (rendererWasEnabledFlags[i] && (!periodHolder.trackSelectorResult.renderersEnabled[i] if (rendererWasEnabledFlags[i]
|| (renderer.isCurrentStreamFinal() && (!newPlayingPeriodHolder.trackSelectorResult.renderersEnabled[i]
&& renderer.getStream() == playingPeriodHolder.sampleStreams[i]))) { || (renderer.isCurrentStreamFinal()
&& renderer.getStream() == oldPlayingPeriodHolder.sampleStreams[i]))) {
// The renderer should be disabled before playing the next period, either because it's not // The renderer should be disabled before playing the next period, either because it's not
// needed to play the next period, or because we need to re-enable it as its current stream // needed to play the next period, or because we need to re-enable it as its current stream
// is final and it's not reading ahead. // is final and it's not reading ahead.
disableRenderer(renderer); disableRenderer(renderer);
} }
} }
playbackInfo =
playingPeriodHolder = periodHolder; playbackInfo.copyWithTrackSelectorResult(newPlayingPeriodHolder.trackSelectorResult);
playbackInfo = playbackInfo.copyWithTrackSelectorResult(periodHolder.trackSelectorResult);
enableRenderers(rendererWasEnabledFlags, enabledRendererCount); enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
} }
@ -1647,6 +1618,7 @@ import java.util.Collections;
throws ExoPlaybackException { throws ExoPlaybackException {
enabledRenderers = new Renderer[totalEnabledRendererCount]; enabledRenderers = new Renderer[totalEnabledRendererCount];
int enabledRendererCount = 0; int enabledRendererCount = 0;
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
if (playingPeriodHolder.trackSelectorResult.renderersEnabled[i]) { if (playingPeriodHolder.trackSelectorResult.renderersEnabled[i]) {
enableRenderer(i, rendererWasEnabledFlags[i], enabledRendererCount++); enableRenderer(i, rendererWasEnabledFlags[i], enabledRendererCount++);
@ -1656,6 +1628,7 @@ import java.util.Collections;
private void enableRenderer(int rendererIndex, boolean wasRendererEnabled, private void enableRenderer(int rendererIndex, boolean wasRendererEnabled,
int enabledRendererIndex) throws ExoPlaybackException { int enabledRendererIndex) throws ExoPlaybackException {
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
Renderer renderer = renderers[rendererIndex]; Renderer renderer = renderers[rendererIndex];
enabledRenderers[enabledRendererIndex] = renderer; enabledRenderers[enabledRendererIndex] = renderer;
if (renderer.getState() == Renderer.STATE_DISABLED) { if (renderer.getState() == Renderer.STATE_DISABLED) {
@ -1681,6 +1654,7 @@ import java.util.Collections;
} }
private boolean rendererWaitingForNextStream(Renderer renderer) { private boolean rendererWaitingForNextStream(Renderer renderer) {
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
return readingPeriodHolder.next != null && readingPeriodHolder.next.prepared return readingPeriodHolder.next != null && readingPeriodHolder.next.prepared
&& renderer.hasReadStreamToEnd(); && renderer.hasReadStreamToEnd();
} }
@ -1696,6 +1670,146 @@ import java.util.Collections;
return formats; return formats;
} }
/**
* Holds a queue of {@link MediaPeriodHolder}s from the currently playing period holder at the
* front to the loading period holder at the end of the queue. Also has a reference to the reading
* period holder.
*/
private static final class MediaPeriodHolderQueue {
private MediaPeriodHolder playing;
private MediaPeriodHolder reading;
private MediaPeriodHolder loading;
private int length;
/**
* Returns the loading period holder which is at the end of the queue, or null if the queue is
* empty.
*/
public MediaPeriodHolder getLoadingPeriod() {
return loading;
}
/**
* Returns the playing period holder which is at the front of the queue, or null if the queue is
* empty or hasn't started playing.
*/
public MediaPeriodHolder getPlayingPeriod() {
return playing;
}
/**
* Returns the reading period holder, or null if the queue is empty or the player hasn't started
* reading.
*/
public MediaPeriodHolder getReadingPeriod() {
return reading;
}
/**
* Returns the period holder in the front of the queue which is the playing period holder when
* playing, or null if the queue is empty.
*/
public MediaPeriodHolder getFrontPeriod() {
return hasPlayingPeriod() ? playing : loading;
}
/** Returns the current length of the queue. */
public int getLength() {
return length;
}
/** Returns whether the reading and playing period holders are set. */
public boolean hasPlayingPeriod() {
return playing != null;
}
/**
* Continues reading from the next period holder in the queue.
*
* @return The updated reading period holder.
*/
public MediaPeriodHolder advanceReadingPeriod() {
Assertions.checkState(reading != null && reading.next != null);
reading = reading.next;
return reading;
}
/** Enqueues a new period holder at the end, which becomes the new loading period holder. */
public void enqueueLoadingPeriod(MediaPeriodHolder mediaPeriodHolder) {
Assertions.checkState(mediaPeriodHolder != null);
if (loading != null) {
Assertions.checkState(hasPlayingPeriod());
loading.next = mediaPeriodHolder;
}
loading = mediaPeriodHolder;
length++;
}
/**
* Dequeues the playing period holder from the front of the queue and advances the playing
* period holder to be the next item in the queue. If the playing period holder is unset, set it
* to the item in the front of the queue.
*
* @return The updated playing period holder, or null if the queue is or becomes empty.
*/
public MediaPeriodHolder advancePlayingPeriod() {
if (playing != null) {
if (playing == reading) {
reading = playing.next;
}
playing.release();
playing = playing.next;
length--;
if (length == 0) {
loading = null;
}
} else {
playing = loading;
reading = loading;
}
return playing;
}
/**
* 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.
*
* @param mediaPeriodHolder The media period holder that shall be the new end of the queue.
* @return Whether the reading period has been removed.
*/
public boolean removeAfter(MediaPeriodHolder mediaPeriodHolder) {
Assertions.checkState(mediaPeriodHolder != null);
boolean removedReading = false;
loading = mediaPeriodHolder;
while (mediaPeriodHolder.next != null) {
mediaPeriodHolder = mediaPeriodHolder.next;
if (mediaPeriodHolder == reading) {
reading = playing;
removedReading = true;
}
mediaPeriodHolder.release();
length--;
}
loading.next = null;
return removedReading;
}
/** Clears the queue. */
public void clear() {
MediaPeriodHolder front = getFrontPeriod();
if (front != null) {
front.release();
removeAfter(front);
}
playing = null;
loading = null;
reading = null;
length = 0;
}
}
/** /**
* Holds a {@link MediaPeriod} with information required to play it as part of a timeline. * Holds a {@link MediaPeriod} with information required to play it as part of a timeline.
*/ */
@ -1703,7 +1817,6 @@ import java.util.Collections;
public final MediaPeriod mediaPeriod; public final MediaPeriod mediaPeriod;
public final Object uid; public final Object uid;
public final int index;
public final SampleStream[] sampleStreams; public final SampleStream[] sampleStreams;
public final boolean[] mayRetainStreamFlags; public final boolean[] mayRetainStreamFlags;
@ -1722,9 +1835,15 @@ import java.util.Collections;
private TrackSelectorResult periodTrackSelectorResult; private TrackSelectorResult periodTrackSelectorResult;
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, public MediaPeriodHolder(
long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl, Renderer[] renderers,
MediaSource mediaSource, Object periodUid, int index, MediaPeriodInfo info) { RendererCapabilities[] rendererCapabilities,
long rendererPositionOffsetUs,
TrackSelector trackSelector,
LoadControl loadControl,
MediaSource mediaSource,
Object periodUid,
MediaPeriodInfo info) {
this.renderers = renderers; this.renderers = renderers;
this.rendererCapabilities = rendererCapabilities; this.rendererCapabilities = rendererCapabilities;
this.rendererPositionOffsetUs = rendererPositionOffsetUs - info.startPositionUs; this.rendererPositionOffsetUs = rendererPositionOffsetUs - info.startPositionUs;
@ -1732,7 +1851,6 @@ import java.util.Collections;
this.loadControl = loadControl; this.loadControl = loadControl;
this.mediaSource = mediaSource; this.mediaSource = mediaSource;
this.uid = Assertions.checkNotNull(periodUid); this.uid = Assertions.checkNotNull(periodUid);
this.index = index;
this.info = info; this.info = info;
sampleStreams = new SampleStream[renderers.length]; sampleStreams = new SampleStream[renderers.length];
mayRetainStreamFlags = new boolean[renderers.length]; mayRetainStreamFlags = new boolean[renderers.length];