Commit 23ff4efd by olly Committed by Oliver Woodman

Tell LoadControl whether playback can start

- This gives LoadControl enough information in shouldContinueLoading
  to know whether returning false will result in a terminal non-playback
  state.
- DefaultLoadControl will always return true when returning false will
  result in a terminal non-playback state, unless the target buffer size
  is exceeded. This can help to avoid getting stuck in the case that a
  MediaPeriod is providing samples from an unexpected starting time.
- Make the terminal state actually terminal. Previously the player would
  end up in an indefinite buffering state. We now fail with an error.
- Also remove the opportunity for LoadControl implementations to livelock
  playback. No sane LoadControl should simultaneously tell the player that
  it doesn't want to load anything and that it doesn't want to start
  playback. So this change removes the opportunity and starts playback in
  EPII instead in this case.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=183114797
parent 35dad90b
...@@ -214,22 +214,8 @@ public class DefaultLoadControl implements LoadControl { ...@@ -214,22 +214,8 @@ public class DefaultLoadControl implements LoadControl {
} }
@Override @Override
public boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed, public boolean shouldContinueLoading(
boolean rebuffering) { boolean canStartPlayback, long bufferedDurationUs, float playbackSpeed) {
if (bufferedDurationUs >= minBufferUs) {
// It's possible that we're not loading, so allow playback to start unconditionally.
return true;
}
bufferedDurationUs = Util.getPlayoutDurationForMediaDuration(bufferedDurationUs, playbackSpeed);
long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
return minBufferDurationUs <= 0
|| bufferedDurationUs >= minBufferDurationUs
|| (!prioritizeTimeOverSizeThresholds
&& allocator.getTotalBytesAllocated() >= targetBufferSize);
}
@Override
public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize; boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
boolean wasBuffering = isBuffering; boolean wasBuffering = isBuffering;
if (prioritizeTimeOverSizeThresholds) { if (prioritizeTimeOverSizeThresholds) {
...@@ -244,6 +230,9 @@ public class DefaultLoadControl implements LoadControl { ...@@ -244,6 +230,9 @@ public class DefaultLoadControl implements LoadControl {
&& (bufferedDurationUs < minBufferUs // below low watermark && (bufferedDurationUs < minBufferUs // below low watermark
|| (bufferedDurationUs <= maxBufferUs && isBuffering)); // between watermarks || (bufferedDurationUs <= maxBufferUs && isBuffering)); // between watermarks
} }
if (!isBuffering && !canStartPlayback && !targetBufferSizeReached) {
isBuffering = true;
}
if (priorityTaskManager != null && isBuffering != wasBuffering) { if (priorityTaskManager != null && isBuffering != wasBuffering) {
if (isBuffering) { if (isBuffering) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK); priorityTaskManager.add(C.PRIORITY_PLAYBACK);
...@@ -254,6 +243,17 @@ public class DefaultLoadControl implements LoadControl { ...@@ -254,6 +243,17 @@ public class DefaultLoadControl implements LoadControl {
return isBuffering; return isBuffering;
} }
@Override
public boolean shouldStartPlayback(
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
bufferedDurationUs = Util.getPlayoutDurationForMediaDuration(bufferedDurationUs, playbackSpeed);
long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
return minBufferDurationUs <= 0
|| bufferedDurationUs >= minBufferDurationUs
|| (!prioritizeTimeOverSizeThresholds
&& allocator.getTotalBytesAllocated() >= targetBufferSize);
}
/** /**
* Calculate target buffer size in bytes based on the selected tracks. The player will try not to * Calculate target buffer size in bytes based on the selected tracks. The player will try not to
* exceed this target buffer. Only used when {@code targetBufferBytes} is {@link C#LENGTH_UNSET}. * exceed this target buffer. Only used when {@code targetBufferBytes} is {@link C#LENGTH_UNSET}.
......
...@@ -126,6 +126,7 @@ import java.util.Collections; ...@@ -126,6 +126,7 @@ import java.util.Collections;
private boolean released; private boolean released;
private boolean playWhenReady; private boolean playWhenReady;
private boolean rebuffering; private boolean rebuffering;
private boolean renderersReadyOrEnded;
private @Player.RepeatMode int repeatMode; private @Player.RepeatMode int repeatMode;
private boolean shuffleModeEnabled; private boolean shuffleModeEnabled;
...@@ -520,10 +521,10 @@ import java.util.Collections; ...@@ -520,10 +521,10 @@ import java.util.Collections;
} }
// Update the buffered position. // Update the buffered position.
long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE playbackInfo.bufferedPositionUs =
: playingPeriodHolder.mediaPeriod.getBufferedPositionUs(); enabledRenderers.length == 0
playbackInfo.bufferedPositionUs = bufferedPositionUs == C.TIME_END_OF_SOURCE ? playingPeriodHolder.info.durationUs
? playingPeriodHolder.info.durationUs : bufferedPositionUs; : playingPeriodHolder.getBufferedPositionUs(/* convertEosToDuration= */ true);
} }
private void doSomeWork() throws ExoPlaybackException, IOException { private void doSomeWork() throws ExoPlaybackException, IOException {
...@@ -545,15 +546,14 @@ import java.util.Collections; ...@@ -545,15 +546,14 @@ import java.util.Collections;
playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs - backBufferDurationUs, playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs - backBufferDurationUs,
retainBackBufferFromKeyframe); retainBackBufferFromKeyframe);
boolean allRenderersEnded = true; boolean renderersEnded = true;
boolean allRenderersReadyOrEnded = true; boolean renderersReadyOrEnded = true;
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
// TODO: Each renderer should return the maximum delay before which it wishes to be called // TODO: Each renderer should return the maximum delay before which it wishes to be called
// again. The minimum of these values should then be used as the delay before the next // again. The minimum of these values should then be used as the delay before the next
// invocation of this method. // invocation of this method.
renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs); renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
allRenderersEnded = allRenderersEnded && renderer.isEnded(); renderersEnded = renderersEnded && renderer.isEnded();
// Determine whether the renderer is ready (or ended). We override to assume the renderer is // Determine whether the renderer is ready (or ended). We override to assume the renderer is
// ready if it needs the next sample stream. This is necessary to avoid getting stuck if // ready if it needs the next sample stream. This is necessary to avoid getting stuck if
// tracks in the current period have uneven durations. See: // tracks in the current period have uneven durations. See:
...@@ -563,44 +563,44 @@ import java.util.Collections; ...@@ -563,44 +563,44 @@ import java.util.Collections;
if (!rendererReadyOrEnded) { if (!rendererReadyOrEnded) {
renderer.maybeThrowStreamError(); renderer.maybeThrowStreamError();
} }
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded; renderersReadyOrEnded = renderersReadyOrEnded && rendererReadyOrEnded;
} }
if (!allRenderersReadyOrEnded) { this.renderersReadyOrEnded = renderersReadyOrEnded;
if (!renderersReadyOrEnded) {
maybeThrowPeriodPrepareError(); maybeThrowPeriodPrepareError();
} }
long playingPeriodDurationUs = playingPeriodHolder.info.durationUs; long playingPeriodDurationUs = playingPeriodHolder.info.durationUs;
if (allRenderersEnded if (renderersEnded
&& (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) {
float playbackSpeed = mediaClock.getPlaybackParameters().speed; boolean shouldStartPlayback = isReady();
boolean isNewlyReady = if (shouldStartPlayback && playbackInfo.isLoading && enabledRenderers.length != 0) {
enabledRenderers.length > 0 MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
? (allRenderersReadyOrEnded long bufferedPositionUs = loadingHolder.getBufferedPositionUs(!loadingHolder.info.isFinal);
&& queue shouldStartPlayback =
.getLoadingPeriod() bufferedPositionUs == C.TIME_END_OF_SOURCE
.haveSufficientBuffer(rendererPositionUs, playbackSpeed, rebuffering)) || loadControl.shouldStartPlayback(
: isTimelineReady(playingPeriodDurationUs); bufferedPositionUs - loadingHolder.toPeriodTime(rendererPositionUs),
if (isNewlyReady) { mediaClock.getPlaybackParameters().speed,
rebuffering);
}
if (shouldStartPlayback) {
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 && !isReady()) {
boolean isStillReady = enabledRenderers.length > 0 ? allRenderersReadyOrEnded
: isTimelineReady(playingPeriodDurationUs);
if (!isStillReady) {
rebuffering = playWhenReady; rebuffering = playWhenReady;
setState(Player.STATE_BUFFERING); setState(Player.STATE_BUFFERING);
stopRenderers(); stopRenderers();
} }
}
if (playbackInfo.playbackState == Player.STATE_BUFFERING) { if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
...@@ -706,6 +706,7 @@ import java.util.Collections; ...@@ -706,6 +706,7 @@ import java.util.Collections;
throws ExoPlaybackException { throws ExoPlaybackException {
stopRenderers(); stopRenderers();
rebuffering = false; rebuffering = false;
renderersReadyOrEnded = false;
setState(Player.STATE_BUFFERING); setState(Player.STATE_BUFFERING);
// Clear the timeline, but keep the requested period if it is already prepared. // Clear the timeline, but keep the requested period if it is already prepared.
...@@ -813,6 +814,7 @@ import java.util.Collections; ...@@ -813,6 +814,7 @@ import java.util.Collections;
boolean releaseMediaSource, boolean resetPosition, boolean resetState) { boolean releaseMediaSource, boolean resetPosition, boolean resetState) {
handler.removeMessages(MSG_DO_SOME_WORK); handler.removeMessages(MSG_DO_SOME_WORK);
rebuffering = false; rebuffering = false;
renderersReadyOrEnded = false;
mediaClock.stop(); mediaClock.stop();
rendererPositionUs = RENDERER_TIMESTAMP_OFFSET_US; rendererPositionUs = RENDERER_TIMESTAMP_OFFSET_US;
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
...@@ -1053,8 +1055,10 @@ import java.util.Collections; ...@@ -1053,8 +1055,10 @@ import java.util.Collections;
boolean recreateStreams = queue.removeAfter(playingPeriodHolder); boolean recreateStreams = queue.removeAfter(playingPeriodHolder);
boolean[] streamResetFlags = new boolean[renderers.length]; boolean[] streamResetFlags = new boolean[renderers.length];
long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection( long periodPositionUs =
playingPeriodHolder.applyTrackSelection(
playbackInfo.positionUs, recreateStreams, streamResetFlags); playbackInfo.positionUs, recreateStreams, streamResetFlags);
updateLoadControlTrackSelection(playingPeriodHolder);
if (playbackInfo.playbackState != Player.STATE_ENDED if (playbackInfo.playbackState != Player.STATE_ENDED
&& periodPositionUs != playbackInfo.positionUs) { && periodPositionUs != playbackInfo.positionUs) {
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs, playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
...@@ -1092,7 +1096,8 @@ import java.util.Collections; ...@@ -1092,7 +1096,8 @@ import java.util.Collections;
long loadingPeriodPositionUs = long loadingPeriodPositionUs =
Math.max( Math.max(
periodHolder.info.startPositionUs, periodHolder.toPeriodTime(rendererPositionUs)); periodHolder.info.startPositionUs, periodHolder.toPeriodTime(rendererPositionUs));
periodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, false); periodHolder.applyTrackSelection(loadingPeriodPositionUs, false);
updateLoadControlTrackSelection(periodHolder);
} }
} }
if (playbackInfo.playbackState != Player.STATE_ENDED) { if (playbackInfo.playbackState != Player.STATE_ENDED) {
...@@ -1102,6 +1107,12 @@ import java.util.Collections; ...@@ -1102,6 +1107,12 @@ import java.util.Collections;
} }
} }
private void updateLoadControlTrackSelection(MediaPeriodHolder periodHolder) {
TrackSelectorResult trackSelectorResult = periodHolder.trackSelectorResult;
loadControl.onTracksSelected(
renderers, trackSelectorResult.groups, trackSelectorResult.selections);
}
private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) { private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) {
MediaPeriodHolder periodHolder = queue.getFrontPeriod(); MediaPeriodHolder periodHolder = queue.getFrontPeriod();
while (periodHolder != null) { while (periodHolder != null) {
...@@ -1117,8 +1128,13 @@ import java.util.Collections; ...@@ -1117,8 +1128,13 @@ import java.util.Collections;
} }
} }
private boolean isTimelineReady(long playingPeriodDurationUs) { private boolean isReady() {
if (enabledRenderers.length != 0) {
return renderersReadyOrEnded;
}
// Determine whether we're ready based on the timeline.
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
long playingPeriodDurationUs = playingPeriodHolder.info.durationUs;
return playingPeriodDurationUs == C.TIME_UNSET return playingPeriodDurationUs == C.TIME_UNSET
|| playbackInfo.positionUs < playingPeriodDurationUs || playbackInfo.positionUs < playingPeriodDurationUs
|| (playingPeriodHolder.next != null || (playingPeriodHolder.next != null
...@@ -1551,11 +1567,10 @@ import java.util.Collections; ...@@ -1551,11 +1567,10 @@ import java.util.Collections;
Object uid = playbackInfo.timeline.getPeriod(info.id.periodIndex, period, true).uid; Object uid = playbackInfo.timeline.getPeriod(info.id.periodIndex, period, true).uid;
MediaPeriodHolder newPeriodHolder = MediaPeriodHolder newPeriodHolder =
new MediaPeriodHolder( new MediaPeriodHolder(
renderers,
rendererCapabilities, rendererCapabilities,
rendererPositionOffsetUs, rendererPositionOffsetUs,
trackSelector, trackSelector,
loadControl, loadControl.getAllocator(),
mediaSource, mediaSource,
uid, uid,
info); info);
...@@ -1571,6 +1586,7 @@ import java.util.Collections; ...@@ -1571,6 +1586,7 @@ import java.util.Collections;
return; return;
} }
loadingPeriodHolder.handlePrepared(mediaClock.getPlaybackParameters().speed); loadingPeriodHolder.handlePrepared(mediaClock.getPlaybackParameters().speed);
updateLoadControlTrackSelection(loadingPeriodHolder);
if (!queue.hasPlayingPeriod()) { if (!queue.hasPlayingPeriod()) {
// This is the first prepared period, so start playing it. // This is the first prepared period, so start playing it.
MediaPeriodHolder playingPeriodHolder = queue.advancePlayingPeriod(); MediaPeriodHolder playingPeriodHolder = queue.advancePlayingPeriod();
...@@ -1592,8 +1608,20 @@ import java.util.Collections; ...@@ -1592,8 +1608,20 @@ import java.util.Collections;
private void maybeContinueLoading() { private void maybeContinueLoading() {
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod(); MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
boolean continueLoading = loadingPeriodHolder.shouldContinueLoading( long nextLoadPositionUs = loadingPeriodHolder.getNextLoadPositionUs();
rendererPositionUs, mediaClock.getPlaybackParameters().speed); if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
setIsLoading(false);
return;
}
boolean canStartPlayback = playbackInfo.playbackState == Player.STATE_READY || isReady();
long bufferedDurationUs =
nextLoadPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs);
boolean continueLoading =
loadControl.shouldContinueLoading(
canStartPlayback, bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
if (!canStartPlayback && !continueLoading) {
throw new StuckBufferingException();
}
setIsLoading(continueLoading); setIsLoading(continueLoading);
if (continueLoading) { if (continueLoading) {
loadingPeriodHolder.continueLoading(rendererPositionUs); loadingPeriodHolder.continueLoading(rendererPositionUs);
......
...@@ -88,25 +88,32 @@ public interface LoadControl { ...@@ -88,25 +88,32 @@ public interface LoadControl {
boolean retainBackBufferFromKeyframe(); boolean retainBackBufferFromKeyframe();
/** /**
* Called by the player to determine whether sufficient media is buffered for playback to be * Called by the player to determine whether it should continue to load the source.
* started or resumed.
* *
* @param canStartPlayback Whether the player has the minimum amount of data necessary to start
* playback. If {@code false}, this method must return {@code true} or playback will fail.
* Hence {@code true} should be returned in this case, unless some hard upper limit (e.g. on
* the amount of memory that the control will permit to be allocated) has been exceeded.
* Always true if playback is currently started.
* @param bufferedDurationUs The duration of media that's currently buffered. * @param bufferedDurationUs The duration of media that's currently buffered.
* @param playbackSpeed The current playback speed. * @param playbackSpeed The current playback speed.
* @param rebuffering Whether the player is rebuffering. A rebuffer is defined to be caused by * @return Whether the loading should continue.
* buffer depletion rather than a user action. Hence this parameter is false during initial
* buffering and when buffering as a result of a seek operation.
* @return Whether playback should be allowed to start or resume.
*/ */
boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed, boolean rebuffering); boolean shouldContinueLoading(
boolean canStartPlayback, long bufferedDurationUs, float playbackSpeed);
/** /**
* Called by the player to determine whether it should continue to load the source. * Called repeatedly by the player when it's loading the source, has yet to start playback, and
* has the minimum amount of data necessary for playback to be started. The value returned
* determines whether playback is actually started. The load control may opt to return {@code
* false} until some condition has been met (e.g. a certain amount of media is buffered).
* *
* @param bufferedDurationUs The duration of media that's currently buffered. * @param bufferedDurationUs The duration of media that's currently buffered.
* @param playbackSpeed The current playback speed. * @param playbackSpeed The current playback speed.
* @return Whether the loading should continue. * @param rebuffering Whether the player is rebuffering. A rebuffer is defined to be caused by
* buffer depletion rather than a user action. Hence this parameter is false during initial
* buffering and when buffering as a result of a seek operation.
* @return Whether playback should be allowed to start or resume.
*/ */
boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed); boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed, boolean rebuffering);
} }
...@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; ...@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
/** 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. */
...@@ -39,40 +40,35 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -39,40 +40,35 @@ import com.google.android.exoplayer2.util.Assertions;
public final boolean[] mayRetainStreamFlags; public final boolean[] mayRetainStreamFlags;
public long rendererPositionOffsetUs; public long rendererPositionOffsetUs;
public MediaPeriodInfo info;
public boolean prepared; public boolean prepared;
public boolean hasEnabledTracks; public boolean hasEnabledTracks;
public MediaPeriodInfo info;
public MediaPeriodHolder next; public MediaPeriodHolder next;
public TrackSelectorResult trackSelectorResult; public TrackSelectorResult trackSelectorResult;
private final Renderer[] renderers;
private final RendererCapabilities[] rendererCapabilities; private final RendererCapabilities[] rendererCapabilities;
private final TrackSelector trackSelector; private final TrackSelector trackSelector;
private final LoadControl loadControl;
private final MediaSource mediaSource; private final MediaSource mediaSource;
private TrackSelectorResult periodTrackSelectorResult; private TrackSelectorResult periodTrackSelectorResult;
public MediaPeriodHolder( public MediaPeriodHolder(
Renderer[] renderers,
RendererCapabilities[] rendererCapabilities, RendererCapabilities[] rendererCapabilities,
long rendererPositionOffsetUs, long rendererPositionOffsetUs,
TrackSelector trackSelector, TrackSelector trackSelector,
LoadControl loadControl, Allocator allocator,
MediaSource mediaSource, MediaSource mediaSource,
Object periodUid, Object periodUid,
MediaPeriodInfo info) { MediaPeriodInfo info) {
this.renderers = renderers;
this.rendererCapabilities = rendererCapabilities; this.rendererCapabilities = rendererCapabilities;
this.rendererPositionOffsetUs = rendererPositionOffsetUs - info.startPositionUs; this.rendererPositionOffsetUs = rendererPositionOffsetUs - info.startPositionUs;
this.trackSelector = trackSelector; this.trackSelector = trackSelector;
this.loadControl = loadControl;
this.mediaSource = mediaSource; this.mediaSource = mediaSource;
this.uid = Assertions.checkNotNull(periodUid); this.uid = Assertions.checkNotNull(periodUid);
this.info = info; this.info = info;
sampleStreams = new SampleStream[renderers.length]; sampleStreams = new SampleStream[rendererCapabilities.length];
mayRetainStreamFlags = new boolean[renderers.length]; mayRetainStreamFlags = new boolean[rendererCapabilities.length];
MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, loadControl.getAllocator()); MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, allocator);
if (info.endPositionUs != C.TIME_END_OF_SOURCE) { if (info.endPositionUs != C.TIME_END_OF_SOURCE) {
ClippingMediaPeriod clippingMediaPeriod = new ClippingMediaPeriod(mediaPeriod, true); ClippingMediaPeriod clippingMediaPeriod = new ClippingMediaPeriod(mediaPeriod, true);
clippingMediaPeriod.setClipping(0, info.endPositionUs); clippingMediaPeriod.setClipping(0, info.endPositionUs);
...@@ -98,24 +94,37 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -98,24 +94,37 @@ import com.google.android.exoplayer2.util.Assertions;
&& (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE); && (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE);
} }
public boolean haveSufficientBuffer( public long getDurationUs() {
long rendererPositionUs, float playbackSpeed, boolean rebuffering) { return info.durationUs;
long bufferedPositionUs = }
!prepared ? info.startPositionUs : mediaPeriod.getBufferedPositionUs();
if (bufferedPositionUs == C.TIME_END_OF_SOURCE) { /**
if (info.isFinal) { * Returns the buffered position in microseconds. If the period is buffered to the end then
return true; * {@link C#TIME_END_OF_SOURCE} is returned unless {@code convertEosToDuration} is true, in which
* case the period duration is returned.
*
* @param convertEosToDuration Whether to return the period duration rather than
* {@link C#TIME_END_OF_SOURCE} if the period is fully buffered.
* @return The buffered position in microseconds.
*/
public long getBufferedPositionUs(boolean convertEosToDuration) {
if (!prepared) {
return info.startPositionUs;
} }
bufferedPositionUs = info.durationUs; long bufferedPositionUs = mediaPeriod.getBufferedPositionUs();
return bufferedPositionUs == C.TIME_END_OF_SOURCE && convertEosToDuration
? info.durationUs
: bufferedPositionUs;
} }
return loadControl.shouldStartPlayback(
bufferedPositionUs - toPeriodTime(rendererPositionUs), playbackSpeed, rebuffering); public long getNextLoadPositionUs() {
return !prepared ? 0 : mediaPeriod.getNextLoadPositionUs();
} }
public void handlePrepared(float playbackSpeed) throws ExoPlaybackException { public void handlePrepared(float playbackSpeed) throws ExoPlaybackException {
prepared = true; prepared = true;
selectTracks(playbackSpeed); selectTracks(playbackSpeed);
long newStartPositionUs = updatePeriodTrackSelection(info.startPositionUs, false); long newStartPositionUs = applyTrackSelection(info.startPositionUs, false);
rendererPositionOffsetUs += info.startPositionUs - newStartPositionUs; rendererPositionOffsetUs += info.startPositionUs - newStartPositionUs;
info = info.copyWithStartPositionUs(newStartPositionUs); info = info.copyWithStartPositionUs(newStartPositionUs);
} }
...@@ -126,16 +135,6 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -126,16 +135,6 @@ import com.google.android.exoplayer2.util.Assertions;
} }
} }
public boolean shouldContinueLoading(long rendererPositionUs, float playbackSpeed) {
long nextLoadPositionUs = !prepared ? 0 : mediaPeriod.getNextLoadPositionUs();
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
return false;
} else {
long bufferedDurationUs = nextLoadPositionUs - toPeriodTime(rendererPositionUs);
return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
}
}
public void continueLoading(long rendererPositionUs) { public void continueLoading(long rendererPositionUs) {
long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs); long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs);
mediaPeriod.continueLoading(loadingPeriodPositionUs); mediaPeriod.continueLoading(loadingPeriodPositionUs);
...@@ -156,12 +155,12 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -156,12 +155,12 @@ import com.google.android.exoplayer2.util.Assertions;
return true; return true;
} }
public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams) { public long applyTrackSelection(long positionUs, boolean forceRecreateStreams) {
return updatePeriodTrackSelection( return applyTrackSelection(
positionUs, forceRecreateStreams, new boolean[renderers.length]); positionUs, forceRecreateStreams, new boolean[rendererCapabilities.length]);
} }
public long updatePeriodTrackSelection( public long applyTrackSelection(
long positionUs, boolean forceRecreateStreams, boolean[] streamResetFlags) { long positionUs, boolean forceRecreateStreams, boolean[] streamResetFlags) {
TrackSelectionArray trackSelections = trackSelectorResult.selections; TrackSelectionArray trackSelections = trackSelectorResult.selections;
for (int i = 0; i < trackSelections.length; i++) { for (int i = 0; i < trackSelections.length; i++) {
...@@ -196,8 +195,6 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -196,8 +195,6 @@ import com.google.android.exoplayer2.util.Assertions;
Assertions.checkState(trackSelections.get(i) == null); Assertions.checkState(trackSelections.get(i) == null);
} }
} }
// The track selection has changed.
loadControl.onTracksSelected(renderers, trackSelectorResult.groups, trackSelections);
return positionUs; return positionUs;
} }
......
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2;
/**
* Thrown when the player is stuck in a state where it has insufficient media to start playback, but
* its {@link LoadControl} is indicating that no further media should be loaded.
*/
public final class StuckBufferingException extends IllegalStateException {}
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