Commit 652c2f9c by tonihei Committed by Toni

Advance playing period even if the next one isn't prepared yet.

This solves various issues around event association for buffering and
error throwing around period discontinuities.

The main changes are:
 - Logic around being "ready" at the end of a period no longer checks if the
   next period is prepared.
 - Advancing the playing period no longer checks if the next one is prepared.
 - Prepare errors are always thrown for the playing period.

This changes the semantics and assumptions about the "playing" period:
 1. The playing period can no longer assumed to be prepared.
 2. We no longer have a case where the queue is non-empty and the playing or
    reading periods are unassigned (=null).
Most other code changes ensure that these changed assumptions are handled.

Issue:#5407
PiperOrigin-RevId: 263776304
parent 14f77cb8
...@@ -38,6 +38,8 @@ ...@@ -38,6 +38,8 @@
`ExoPlayer.Builder`. `ExoPlayer.Builder`.
* Inject `DrmSessionManager` into the `MediaSources` instead of `Renderers` * Inject `DrmSessionManager` into the `MediaSources` instead of `Renderers`
([#5619](https://github.com/google/ExoPlayer/issues/5619)). ([#5619](https://github.com/google/ExoPlayer/issues/5619)).
* Fix issue where player errors are thrown too early at playlist transitions
([#5407](https://github.com/google/ExoPlayer/issues/5407)).
### 2.10.4 ### ### 2.10.4 ###
......
...@@ -83,8 +83,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -83,8 +83,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 16; private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 16;
private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 17; private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 17;
private static final int PREPARING_SOURCE_INTERVAL_MS = 10; private static final int ACTIVE_INTERVAL_MS = 10;
private static final int RENDERING_INTERVAL_MS = 10;
private static final int IDLE_INTERVAL_MS = 1000; private static final int IDLE_INTERVAL_MS = 1000;
private final Renderer[] renderers; private final Renderer[] renderers;
...@@ -514,22 +513,25 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -514,22 +513,25 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private void updatePlaybackPositions() throws ExoPlaybackException { private void updatePlaybackPositions() throws ExoPlaybackException {
if (!queue.hasPlayingPeriod()) { MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
if (playingPeriodHolder == null) {
return; return;
} }
// Update the playback position. // Update the playback position.
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); long discontinuityPositionUs =
long periodPositionUs = playingPeriodHolder.mediaPeriod.readDiscontinuity(); playingPeriodHolder.prepared
if (periodPositionUs != C.TIME_UNSET) { ? playingPeriodHolder.mediaPeriod.readDiscontinuity()
resetRendererPosition(periodPositionUs); : C.TIME_UNSET;
if (discontinuityPositionUs != C.TIME_UNSET) {
resetRendererPosition(discontinuityPositionUs);
// A MediaPeriod may report a discontinuity at the current playback position to ensure the // A MediaPeriod may report a discontinuity at the current playback position to ensure the
// renderers are flushed. Only report the discontinuity externally if the position changed. // renderers are flushed. Only report the discontinuity externally if the position changed.
if (periodPositionUs != playbackInfo.positionUs) { if (discontinuityPositionUs != playbackInfo.positionUs) {
playbackInfo = playbackInfo =
playbackInfo.copyWithNewPosition( playbackInfo.copyWithNewPosition(
playbackInfo.periodId, playbackInfo.periodId,
periodPositionUs, discontinuityPositionUs,
playbackInfo.contentPositionUs, playbackInfo.contentPositionUs,
getTotalBufferedDurationUs()); getTotalBufferedDurationUs());
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
...@@ -538,7 +540,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -538,7 +540,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
rendererPositionUs = rendererPositionUs =
mediaClock.syncAndGetPositionUs( mediaClock.syncAndGetPositionUs(
/* isReadingAhead= */ playingPeriodHolder != queue.getReadingPeriod()); /* isReadingAhead= */ playingPeriodHolder != queue.getReadingPeriod());
periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs); long periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs);
maybeTriggerPendingMessages(playbackInfo.positionUs, periodPositionUs); maybeTriggerPendingMessages(playbackInfo.positionUs, periodPositionUs);
playbackInfo.positionUs = periodPositionUs; playbackInfo.positionUs = periodPositionUs;
} }
...@@ -552,60 +554,71 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -552,60 +554,71 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void doSomeWork() throws ExoPlaybackException, IOException { private void doSomeWork() throws ExoPlaybackException, IOException {
long operationStartTimeMs = clock.uptimeMillis(); long operationStartTimeMs = clock.uptimeMillis();
updatePeriods(); updatePeriods();
if (!queue.hasPlayingPeriod()) {
// We're still waiting for the first period to be prepared. MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
maybeThrowPeriodPrepareError(); if (playingPeriodHolder == null) {
scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS); // We're still waiting until the playing period is available.
scheduleNextWork(operationStartTimeMs, ACTIVE_INTERVAL_MS);
return; return;
} }
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
TraceUtil.beginSection("doSomeWork"); TraceUtil.beginSection("doSomeWork");
updatePlaybackPositions(); updatePlaybackPositions();
long rendererPositionElapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs - backBufferDurationUs,
retainBackBufferFromKeyframe);
boolean renderersEnded = true; boolean renderersEnded = true;
boolean renderersReadyOrEnded = true; boolean renderersAllowPlayback = true;
for (Renderer renderer : enabledRenderers) { if (playingPeriodHolder.prepared) {
// TODO: Each renderer should return the maximum delay before which it wishes to be called long rendererPositionElapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
// again. The minimum of these values should then be used as the delay before the next playingPeriodHolder.mediaPeriod.discardBuffer(
// invocation of this method. playbackInfo.positionUs - backBufferDurationUs, retainBackBufferFromKeyframe);
renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs); for (int i = 0; i < renderers.length; i++) {
renderersEnded = renderersEnded && renderer.isEnded(); Renderer renderer = renderers[i];
// Determine whether the renderer is ready (or ended). We override to assume the renderer is if (renderer.getState() == Renderer.STATE_DISABLED) {
// ready if it needs the next sample stream. This is necessary to avoid getting stuck if continue;
// tracks in the current period have uneven durations. See: }
// https://github.com/google/ExoPlayer/issues/1874 // TODO: Each renderer should return the maximum delay before which it wishes to be called
boolean rendererReadyOrEnded = renderer.isReady() || renderer.isEnded() // again. The minimum of these values should then be used as the delay before the next
|| rendererWaitingForNextStream(renderer); // invocation of this method.
if (!rendererReadyOrEnded) { renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
renderer.maybeThrowStreamError(); renderersEnded = renderersEnded && renderer.isEnded();
// Determine whether the renderer allows playback to continue. Playback can continue if the
// renderer is ready or ended. Also continue playback if the renderer is reading ahead into
// the next stream or is waiting for the next stream. This is to avoid getting stuck if
// tracks in the current period have uneven durations and are still being read by another
// renderer. See: https://github.com/google/ExoPlayer/issues/1874.
boolean isReadingAhead = playingPeriodHolder.sampleStreams[i] != renderer.getStream();
boolean isWaitingForNextStream =
!isReadingAhead
&& playingPeriodHolder.getNext() != null
&& renderer.hasReadStreamToEnd();
boolean allowsPlayback =
isReadingAhead || isWaitingForNextStream || renderer.isReady() || renderer.isEnded();
renderersAllowPlayback = renderersAllowPlayback && allowsPlayback;
if (!allowsPlayback) {
renderer.maybeThrowStreamError();
}
} }
renderersReadyOrEnded = renderersReadyOrEnded && rendererReadyOrEnded; } else {
} playingPeriodHolder.mediaPeriod.maybeThrowPrepareError();
if (!renderersReadyOrEnded) {
maybeThrowPeriodPrepareError();
} }
long playingPeriodDurationUs = playingPeriodHolder.info.durationUs; long playingPeriodDurationUs = playingPeriodHolder.info.durationUs;
if (renderersEnded if (renderersEnded
&& playingPeriodHolder.prepared
&& (playingPeriodDurationUs == C.TIME_UNSET && (playingPeriodDurationUs == C.TIME_UNSET
|| playingPeriodDurationUs <= playbackInfo.positionUs) || playingPeriodDurationUs <= playbackInfo.positionUs)
&& playingPeriodHolder.info.isFinal) { && playingPeriodHolder.info.isFinal) {
setState(Player.STATE_ENDED); setState(Player.STATE_ENDED);
stopRenderers(); stopRenderers();
} else if (playbackInfo.playbackState == Player.STATE_BUFFERING } else if (playbackInfo.playbackState == Player.STATE_BUFFERING
&& shouldTransitionToReadyState(renderersReadyOrEnded)) { && shouldTransitionToReadyState(renderersAllowPlayback)) {
setState(Player.STATE_READY); setState(Player.STATE_READY);
if (playWhenReady) { if (playWhenReady) {
startRenderers(); startRenderers();
} }
} else if (playbackInfo.playbackState == Player.STATE_READY } else if (playbackInfo.playbackState == Player.STATE_READY
&& !(enabledRenderers.length == 0 ? isTimelineReady() : renderersReadyOrEnded)) { && !(enabledRenderers.length == 0 ? isTimelineReady() : renderersAllowPlayback)) {
rebuffering = playWhenReady; rebuffering = playWhenReady;
setState(Player.STATE_BUFFERING); setState(Player.STATE_BUFFERING);
stopRenderers(); stopRenderers();
...@@ -619,7 +632,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -619,7 +632,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
if ((playWhenReady && playbackInfo.playbackState == Player.STATE_READY) if ((playWhenReady && playbackInfo.playbackState == Player.STATE_READY)
|| playbackInfo.playbackState == Player.STATE_BUFFERING) { || playbackInfo.playbackState == Player.STATE_BUFFERING) {
scheduleNextWork(operationStartTimeMs, RENDERING_INTERVAL_MS); scheduleNextWork(operationStartTimeMs, ACTIVE_INTERVAL_MS);
} else if (enabledRenderers.length != 0 && playbackInfo.playbackState != Player.STATE_ENDED) { } else if (enabledRenderers.length != 0 && playbackInfo.playbackState != Player.STATE_ENDED) {
scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS); scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS);
} else { } else {
...@@ -681,7 +694,9 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -681,7 +694,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
long newPeriodPositionUs = periodPositionUs; long newPeriodPositionUs = periodPositionUs;
if (periodId.equals(playbackInfo.periodId)) { if (periodId.equals(playbackInfo.periodId)) {
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
if (playingPeriodHolder != null && newPeriodPositionUs != 0) { if (playingPeriodHolder != null
&& playingPeriodHolder.prepared
&& newPeriodPositionUs != 0) {
newPeriodPositionUs = newPeriodPositionUs =
playingPeriodHolder.mediaPeriod.getAdjustedSeekPositionUs( playingPeriodHolder.mediaPeriod.getAdjustedSeekPositionUs(
newPeriodPositionUs, seekParameters); newPeriodPositionUs, seekParameters);
...@@ -771,10 +786,11 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -771,10 +786,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException { private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException {
MediaPeriodHolder playingMediaPeriod = queue.getPlayingPeriod();
rendererPositionUs = rendererPositionUs =
!queue.hasPlayingPeriod() playingMediaPeriod == null
? periodPositionUs ? periodPositionUs
: queue.getPlayingPeriod().toRendererTime(periodPositionUs); : playingMediaPeriod.toRendererTime(periodPositionUs);
mediaClock.resetPosition(rendererPositionUs); mediaClock.resetPosition(rendererPositionUs);
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
renderer.resetPosition(rendererPositionUs); renderer.resetPosition(rendererPositionUs);
...@@ -1092,10 +1108,6 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1092,10 +1108,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private void reselectTracksInternal() throws ExoPlaybackException { private void reselectTracksInternal() throws ExoPlaybackException {
if (!queue.hasPlayingPeriod()) {
// We don't have tracks yet, so we don't care.
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 = queue.getPlayingPeriod(); MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
...@@ -1182,8 +1194,8 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1182,8 +1194,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) { private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) {
MediaPeriodHolder periodHolder = queue.getFrontPeriod(); MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null && periodHolder.prepared) { while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll(); TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll();
for (TrackSelection trackSelection : trackSelections) { for (TrackSelection trackSelection : trackSelections) {
if (trackSelection != null) { if (trackSelection != null) {
...@@ -1195,7 +1207,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1195,7 +1207,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private void notifyTrackSelectionDiscontinuity() { private void notifyTrackSelectionDiscontinuity() {
MediaPeriodHolder periodHolder = queue.getFrontPeriod(); MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null) { while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll(); TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll();
for (TrackSelection trackSelection : trackSelections) { for (TrackSelection trackSelection : trackSelections) {
...@@ -1230,12 +1242,10 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1230,12 +1242,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
private boolean isTimelineReady() { private boolean isTimelineReady() {
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
MediaPeriodHolder nextPeriodHolder = playingPeriodHolder.getNext();
long playingPeriodDurationUs = playingPeriodHolder.info.durationUs; long playingPeriodDurationUs = playingPeriodHolder.info.durationUs;
return playingPeriodDurationUs == C.TIME_UNSET return playingPeriodHolder.prepared
|| playbackInfo.positionUs < playingPeriodDurationUs && (playingPeriodDurationUs == C.TIME_UNSET
|| (nextPeriodHolder != null || playbackInfo.positionUs < playingPeriodDurationUs);
&& (nextPeriodHolder.prepared || nextPeriodHolder.info.id.isAd()));
} }
private void maybeThrowSourceInfoRefreshError() throws IOException { private void maybeThrowSourceInfoRefreshError() throws IOException {
...@@ -1251,21 +1261,6 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1251,21 +1261,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
mediaSource.maybeThrowSourceInfoRefreshError(); mediaSource.maybeThrowSourceInfoRefreshError();
} }
private void maybeThrowPeriodPrepareError() throws IOException {
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
if (loadingPeriodHolder != null
&& !loadingPeriodHolder.prepared
&& (readingPeriodHolder == null || readingPeriodHolder.getNext() == loadingPeriodHolder)) {
for (Renderer renderer : enabledRenderers) {
if (!renderer.hasReadStreamToEnd()) {
return;
}
}
loadingPeriodHolder.mediaPeriod.maybeThrowPrepareError();
}
}
private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo) private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo)
throws ExoPlaybackException { throws ExoPlaybackException {
if (sourceRefreshInfo.source != mediaSource) { if (sourceRefreshInfo.source != mediaSource) {
...@@ -1335,7 +1330,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1335,7 +1330,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
} else { } else {
// Something changed. Seek to new start position. // Something changed. Seek to new start position.
MediaPeriodHolder periodHolder = queue.getFrontPeriod(); MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
if (periodHolder != null) { if (periodHolder != null) {
// Update the new playing media period info if it already exists. // Update the new playing media period info if it already exists.
while (periodHolder.getNext() != null) { while (periodHolder.getNext() != null) {
...@@ -1361,6 +1356,9 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1361,6 +1356,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
return 0; return 0;
} }
long maxReadPositionUs = readingHolder.getRendererOffset(); long maxReadPositionUs = readingHolder.getRendererOffset();
if (!readingHolder.prepared) {
return maxReadPositionUs;
}
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
if (renderers[i].getState() == Renderer.STATE_DISABLED if (renderers[i].getState() == Renderer.STATE_DISABLED
|| renderers[i].getStream() != readingHolder.sampleStreams[i]) { || renderers[i].getStream() != readingHolder.sampleStreams[i]) {
...@@ -1494,23 +1492,26 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1494,23 +1492,26 @@ import java.util.concurrent.atomic.AtomicBoolean;
maybeUpdatePlayingPeriod(); maybeUpdatePlayingPeriod();
} }
private void maybeUpdateLoadingPeriod() throws IOException { private void maybeUpdateLoadingPeriod() throws ExoPlaybackException, IOException {
queue.reevaluateBuffer(rendererPositionUs); queue.reevaluateBuffer(rendererPositionUs);
if (queue.shouldLoadNextMediaPeriod()) { if (queue.shouldLoadNextMediaPeriod()) {
MediaPeriodInfo info = queue.getNextMediaPeriodInfo(rendererPositionUs, playbackInfo); MediaPeriodInfo info = queue.getNextMediaPeriodInfo(rendererPositionUs, playbackInfo);
if (info == null) { if (info == null) {
maybeThrowSourceInfoRefreshError(); maybeThrowSourceInfoRefreshError();
} else { } else {
MediaPeriod mediaPeriod = MediaPeriodHolder mediaPeriodHolder =
queue.enqueueNextMediaPeriod( queue.enqueueNextMediaPeriodHolder(
rendererCapabilities, rendererCapabilities,
trackSelector, trackSelector,
loadControl.getAllocator(), loadControl.getAllocator(),
mediaSource, mediaSource,
info, info,
emptyTrackSelectorResult); emptyTrackSelectorResult);
mediaPeriod.prepare(this, info.startPositionUs); mediaPeriodHolder.mediaPeriod.prepare(this, info.startPositionUs);
setIsLoading(true); setIsLoading(true);
if (queue.getPlayingPeriod() == mediaPeriodHolder) {
resetRendererPosition(mediaPeriodHolder.getStartPositionRendererTime());
}
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
} }
} }
...@@ -1522,7 +1523,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1522,7 +1523,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
} }
private void maybeUpdateReadingPeriod() throws ExoPlaybackException, IOException { private void maybeUpdateReadingPeriod() throws ExoPlaybackException {
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
if (readingPeriodHolder == null) { if (readingPeriodHolder == null) {
return; return;
...@@ -1552,7 +1553,6 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1552,7 +1553,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (!readingPeriodHolder.getNext().prepared) { if (!readingPeriodHolder.getNext().prepared) {
// The successor is not prepared yet. // The successor is not prepared yet.
maybeThrowPeriodPrepareError();
return; return;
} }
...@@ -1607,6 +1607,11 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1607,6 +1607,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
maybeNotifyPlaybackInfoChanged(); maybeNotifyPlaybackInfoChanged();
} }
MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod(); MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod();
if (oldPlayingPeriodHolder == queue.getReadingPeriod()) {
// The reading period hasn't advanced yet, so we can't seamlessly replace the SampleStreams
// anymore and need to re-enable the renderers. Set all current streams final to do that.
setAllRendererStreamsFinal();
}
MediaPeriodHolder newPlayingPeriodHolder = queue.advancePlayingPeriod(); MediaPeriodHolder newPlayingPeriodHolder = queue.advancePlayingPeriod();
updatePlayingPeriodRenderers(oldPlayingPeriodHolder); updatePlayingPeriodRenderers(oldPlayingPeriodHolder);
playbackInfo = playbackInfo =
...@@ -1633,17 +1638,22 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1633,17 +1638,22 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (playingPeriodHolder == null) { if (playingPeriodHolder == null) {
return false; return false;
} }
MediaPeriodHolder nextPlayingPeriodHolder = playingPeriodHolder.getNext();
if (nextPlayingPeriodHolder == null) {
return false;
}
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
if (playingPeriodHolder == readingPeriodHolder) { if (playingPeriodHolder == readingPeriodHolder && !hasReadingPeriodFinishedReading()) {
return false; return false;
} }
MediaPeriodHolder nextPlayingPeriodHolder =
Assertions.checkNotNull(playingPeriodHolder.getNext());
return rendererPositionUs >= nextPlayingPeriodHolder.getStartPositionRendererTime(); return rendererPositionUs >= nextPlayingPeriodHolder.getStartPositionRendererTime();
} }
private boolean hasReadingPeriodFinishedReading() { private boolean hasReadingPeriodFinishedReading() {
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
if (!readingPeriodHolder.prepared) {
return false;
}
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i]; Renderer renderer = renderers[i];
SampleStream sampleStream = readingPeriodHolder.sampleStreams[i]; SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
...@@ -1674,10 +1684,9 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1674,10 +1684,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
mediaClock.getPlaybackParameters().speed, playbackInfo.timeline); mediaClock.getPlaybackParameters().speed, playbackInfo.timeline);
updateLoadControlTrackSelection( updateLoadControlTrackSelection(
loadingPeriodHolder.getTrackGroups(), loadingPeriodHolder.getTrackSelectorResult()); loadingPeriodHolder.getTrackGroups(), loadingPeriodHolder.getTrackSelectorResult());
if (!queue.hasPlayingPeriod()) { if (loadingPeriodHolder == queue.getPlayingPeriod()) {
// This is the first prepared period, so start playing it. // This is the first prepared period, so update the position and the renderers.
MediaPeriodHolder playingPeriodHolder = queue.advancePlayingPeriod(); resetRendererPosition(loadingPeriodHolder.info.startPositionUs);
resetRendererPosition(playingPeriodHolder.info.startPositionUs);
updatePlayingPeriodRenderers(/* oldPlayingPeriodHolder= */ null); updatePlayingPeriodRenderers(/* oldPlayingPeriodHolder= */ null);
} }
maybeContinueLoading(); maybeContinueLoading();
...@@ -1805,12 +1814,6 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1805,12 +1814,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
} }
private boolean rendererWaitingForNextStream(Renderer renderer) {
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
MediaPeriodHolder nextPeriodHolder = readingPeriodHolder.getNext();
return nextPeriodHolder != null && nextPeriodHolder.prepared && renderer.hasReadStreamToEnd();
}
private void handleLoadingMediaPeriodChanged(boolean loadingTrackSelectionChanged) { private void handleLoadingMediaPeriodChanged(boolean loadingTrackSelectionChanged) {
MediaPeriodHolder loadingMediaPeriodHolder = queue.getLoadingPeriod(); MediaPeriodHolder loadingMediaPeriodHolder = queue.getLoadingPeriod();
MediaPeriodId loadingMediaPeriodId = MediaPeriodId loadingMediaPeriodId =
......
...@@ -128,8 +128,8 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -128,8 +128,8 @@ import com.google.android.exoplayer2.util.Assertions;
} }
/** /**
* Enqueues a new media period based on the specified information as the new loading media period, * Enqueues a new media period holder based on the specified information as the new loading media
* and returns it. * period, and returns it.
* *
* @param rendererCapabilities The renderer capabilities. * @param rendererCapabilities The renderer capabilities.
* @param trackSelector The track selector. * @param trackSelector The track selector.
...@@ -139,7 +139,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -139,7 +139,7 @@ import com.google.android.exoplayer2.util.Assertions;
* @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each * @param emptyTrackSelectorResult A {@link TrackSelectorResult} with empty selections for each
* renderer. * renderer.
*/ */
public MediaPeriod enqueueNextMediaPeriod( public MediaPeriodHolder enqueueNextMediaPeriodHolder(
RendererCapabilities[] rendererCapabilities, RendererCapabilities[] rendererCapabilities,
TrackSelector trackSelector, TrackSelector trackSelector,
Allocator allocator, Allocator allocator,
...@@ -162,13 +162,15 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -162,13 +162,15 @@ import com.google.android.exoplayer2.util.Assertions;
info, info,
emptyTrackSelectorResult); emptyTrackSelectorResult);
if (loading != null) { if (loading != null) {
Assertions.checkState(hasPlayingPeriod());
loading.setNext(newPeriodHolder); loading.setNext(newPeriodHolder);
} else {
playing = newPeriodHolder;
reading = newPeriodHolder;
} }
oldFrontPeriodUid = null; oldFrontPeriodUid = null;
loading = newPeriodHolder; loading = newPeriodHolder;
length++; length++;
return newPeriodHolder.mediaPeriod; return newPeriodHolder;
} }
/** /**
...@@ -182,37 +184,20 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -182,37 +184,20 @@ import com.google.android.exoplayer2.util.Assertions;
/** /**
* Returns the playing period holder which is at the front of the queue, or null if the queue is * 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. * empty.
*/ */
@Nullable @Nullable
public MediaPeriodHolder getPlayingPeriod() { public MediaPeriodHolder getPlayingPeriod() {
return playing; return playing;
} }
/** /** Returns the reading period holder, or null if the queue is empty. */
* Returns the reading period holder, or null if the queue is empty or the player hasn't started
* reading.
*/
@Nullable @Nullable
public MediaPeriodHolder getReadingPeriod() { public MediaPeriodHolder getReadingPeriod() {
return reading; 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.
*/
@Nullable
public MediaPeriodHolder getFrontPeriod() {
return hasPlayingPeriod() ? playing : loading;
}
/** 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. * Continues reading from the next period holder in the queue.
* *
* @return The updated reading period holder. * @return The updated reading period holder.
...@@ -225,29 +210,26 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -225,29 +210,26 @@ import com.google.android.exoplayer2.util.Assertions;
/** /**
* Dequeues the playing period holder from the front of the queue and advances the playing period * 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 * holder to be the next item in the queue.
* item in the front of the queue.
* *
* @return The updated playing period holder, or null if the queue is or becomes empty. * @return The updated playing period holder, or null if the queue is or becomes empty.
*/ */
@Nullable @Nullable
public MediaPeriodHolder advancePlayingPeriod() { public MediaPeriodHolder advancePlayingPeriod() {
if (playing != null) { if (playing == null) {
if (playing == reading) { return null;
reading = playing.getNext(); }
} if (playing == reading) {
playing.release(); reading = playing.getNext();
length--; }
if (length == 0) { playing.release();
loading = null; length--;
oldFrontPeriodUid = playing.uid; if (length == 0) {
oldFrontPeriodWindowSequenceNumber = playing.info.id.windowSequenceNumber; loading = null;
} oldFrontPeriodUid = playing.uid;
playing = playing.getNext(); oldFrontPeriodWindowSequenceNumber = playing.info.id.windowSequenceNumber;
} else {
playing = loading;
reading = loading;
} }
playing = playing.getNext();
return playing; return playing;
} }
...@@ -283,7 +265,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -283,7 +265,7 @@ import com.google.android.exoplayer2.util.Assertions;
* of queue (typically the playing one) for later reuse. * of queue (typically the playing one) for later reuse.
*/ */
public void clear(boolean keepFrontPeriodUid) { public void clear(boolean keepFrontPeriodUid) {
MediaPeriodHolder front = getFrontPeriod(); MediaPeriodHolder front = playing;
if (front != null) { if (front != null) {
oldFrontPeriodUid = keepFrontPeriodUid ? front.uid : null; oldFrontPeriodUid = keepFrontPeriodUid ? front.uid : null;
oldFrontPeriodWindowSequenceNumber = front.info.id.windowSequenceNumber; oldFrontPeriodWindowSequenceNumber = front.info.id.windowSequenceNumber;
...@@ -315,7 +297,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -315,7 +297,7 @@ import com.google.android.exoplayer2.util.Assertions;
// is set, once all cases handled by ExoPlayerImplInternal.handleSourceInfoRefreshed can be // is set, once all cases handled by ExoPlayerImplInternal.handleSourceInfoRefreshed can be
// handled here. // handled here.
MediaPeriodHolder previousPeriodHolder = null; MediaPeriodHolder previousPeriodHolder = null;
MediaPeriodHolder periodHolder = getFrontPeriod(); MediaPeriodHolder periodHolder = playing;
while (periodHolder != null) { while (periodHolder != null) {
MediaPeriodInfo oldPeriodInfo = periodHolder.info; MediaPeriodInfo oldPeriodInfo = periodHolder.info;
...@@ -451,7 +433,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -451,7 +433,7 @@ import com.google.android.exoplayer2.util.Assertions;
} }
} }
} }
MediaPeriodHolder mediaPeriodHolder = getFrontPeriod(); MediaPeriodHolder mediaPeriodHolder = playing;
while (mediaPeriodHolder != null) { while (mediaPeriodHolder != null) {
if (mediaPeriodHolder.uid.equals(periodUid)) { if (mediaPeriodHolder.uid.equals(periodUid)) {
// Reuse window sequence number of first exact period match. // Reuse window sequence number of first exact period match.
...@@ -459,7 +441,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -459,7 +441,7 @@ import com.google.android.exoplayer2.util.Assertions;
} }
mediaPeriodHolder = mediaPeriodHolder.getNext(); mediaPeriodHolder = mediaPeriodHolder.getNext();
} }
mediaPeriodHolder = getFrontPeriod(); mediaPeriodHolder = playing;
while (mediaPeriodHolder != null) { while (mediaPeriodHolder != null) {
int indexOfHolderInTimeline = timeline.getIndexOfPeriod(mediaPeriodHolder.uid); int indexOfHolderInTimeline = timeline.getIndexOfPeriod(mediaPeriodHolder.uid);
if (indexOfHolderInTimeline != C.INDEX_UNSET) { if (indexOfHolderInTimeline != C.INDEX_UNSET) {
...@@ -496,7 +478,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -496,7 +478,7 @@ import com.google.android.exoplayer2.util.Assertions;
*/ */
private boolean updateForPlaybackModeChange() { private boolean updateForPlaybackModeChange() {
// 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 = getFrontPeriod(); MediaPeriodHolder lastValidPeriodHolder = playing;
if (lastValidPeriodHolder == null) { if (lastValidPeriodHolder == null) {
return true; return true;
} }
...@@ -529,7 +511,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -529,7 +511,7 @@ import com.google.android.exoplayer2.util.Assertions;
lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info); lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(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 || !hasPlayingPeriod(); return !readingPeriodRemoved;
} }
/** /**
......
...@@ -370,7 +370,9 @@ public final class MediaPeriodQueueTest { ...@@ -370,7 +370,9 @@ public final class MediaPeriodQueueTest {
private void advance() { private void advance() {
enqueueNext(); enqueueNext();
advancePlaying(); if (mediaPeriodQueue.getLoadingPeriod() != mediaPeriodQueue.getPlayingPeriod()) {
advancePlaying();
}
} }
private void advancePlaying() { private void advancePlaying() {
...@@ -382,7 +384,7 @@ public final class MediaPeriodQueueTest { ...@@ -382,7 +384,7 @@ public final class MediaPeriodQueueTest {
} }
private void enqueueNext() { private void enqueueNext() {
mediaPeriodQueue.enqueueNextMediaPeriod( mediaPeriodQueue.enqueueNextMediaPeriodHolder(
rendererCapabilities, rendererCapabilities,
trackSelector, trackSelector,
allocator, allocator,
...@@ -460,7 +462,7 @@ public final class MediaPeriodQueueTest { ...@@ -460,7 +462,7 @@ public final class MediaPeriodQueueTest {
private int getQueueLength() { private int getQueueLength() {
int length = 0; int length = 0;
MediaPeriodHolder periodHolder = mediaPeriodQueue.getFrontPeriod(); MediaPeriodHolder periodHolder = mediaPeriodQueue.getPlayingPeriod();
while (periodHolder != null) { while (periodHolder != null) {
length++; length++;
periodHolder = periodHolder.getNext(); periodHolder = periodHolder.getNext();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment