mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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
This commit is contained in:
parent
35dad90bea
commit
23ff4efda1
5 changed files with 158 additions and 104 deletions
|
|
@ -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 {
|
||||||
&& (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 {
|
||||||
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;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
|
|
||||||
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,8 +88,25 @@ 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 playbackSpeed The current playback speed.
|
||||||
|
* @return Whether the loading should continue.
|
||||||
|
*/
|
||||||
|
boolean shouldContinueLoading(
|
||||||
|
boolean canStartPlayback, long bufferedDurationUs, float playbackSpeed);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
|
@ -99,14 +116,4 @@ public interface LoadControl {
|
||||||
* @return Whether playback should be allowed to start or resume.
|
* @return Whether playback should be allowed to start or resume.
|
||||||
*/
|
*/
|
||||||
boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed, boolean rebuffering);
|
boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed, boolean rebuffering);
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the player to determine whether it should continue to load the source.
|
|
||||||
*
|
|
||||||
* @param bufferedDurationUs The duration of media that's currently buffered.
|
|
||||||
* @param playbackSpeed The current playback speed.
|
|
||||||
* @return Whether the loading should continue.
|
|
||||||
*/
|
|
||||||
boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
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;
|
||||||
&& (!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) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
bufferedPositionUs = info.durationUs;
|
|
||||||
|
/**
|
||||||
|
* Returns the buffered position in microseconds. If the period is buffered to the end then
|
||||||
|
* {@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;
|
||||||
}
|
}
|
||||||
return loadControl.shouldStartPlayback(
|
long bufferedPositionUs = mediaPeriod.getBufferedPositionUs();
|
||||||
bufferedPositionUs - toPeriodTime(rendererPositionUs), playbackSpeed, rebuffering);
|
return bufferedPositionUs == C.TIME_END_OF_SOURCE && convertEosToDuration
|
||||||
|
? info.durationUs
|
||||||
|
: bufferedPositionUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* 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 {}
|
||||||
Loading…
Reference in a new issue