mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Rollback of 3c56b113e4
*** Original commit *** Rollback ofd48dc4c159*** Original commit *** Move getting-stuck-prevention into DefaultLoadControl. We recently added code that prevents getting stuck if the buffer is low and the LoadControl refuses to continue loading (b84bde0252). Move this logic into DefaultLoadControl to keep the workaround, and also apply the maximum buffer size check in bytes if enabled. ExoPlayerImplInternal will now throw if a... *** PiperOrigin-RevId: 290273295
This commit is contained in:
parent
a7f7d8fb03
commit
3e41c0a1d2
4 changed files with 58 additions and 26 deletions
|
|
@ -246,7 +246,7 @@ public class DefaultLoadControl implements LoadControl {
|
||||||
private final long backBufferDurationUs;
|
private final long backBufferDurationUs;
|
||||||
private final boolean retainBackBufferFromKeyframe;
|
private final boolean retainBackBufferFromKeyframe;
|
||||||
|
|
||||||
private int targetBufferSize;
|
private int targetBufferBytes;
|
||||||
private boolean isBuffering;
|
private boolean isBuffering;
|
||||||
private boolean hasVideo;
|
private boolean hasVideo;
|
||||||
|
|
||||||
|
|
@ -334,6 +334,10 @@ public class DefaultLoadControl implements LoadControl {
|
||||||
this.bufferForPlaybackUs = C.msToUs(bufferForPlaybackMs);
|
this.bufferForPlaybackUs = C.msToUs(bufferForPlaybackMs);
|
||||||
this.bufferForPlaybackAfterRebufferUs = C.msToUs(bufferForPlaybackAfterRebufferMs);
|
this.bufferForPlaybackAfterRebufferUs = C.msToUs(bufferForPlaybackAfterRebufferMs);
|
||||||
this.targetBufferBytesOverwrite = targetBufferBytes;
|
this.targetBufferBytesOverwrite = targetBufferBytes;
|
||||||
|
this.targetBufferBytes =
|
||||||
|
targetBufferBytesOverwrite != C.LENGTH_UNSET
|
||||||
|
? targetBufferBytesOverwrite
|
||||||
|
: DEFAULT_MUXED_BUFFER_SIZE;
|
||||||
this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds;
|
this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds;
|
||||||
this.backBufferDurationUs = C.msToUs(backBufferDurationMs);
|
this.backBufferDurationUs = C.msToUs(backBufferDurationMs);
|
||||||
this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe;
|
this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe;
|
||||||
|
|
@ -348,11 +352,11 @@ public class DefaultLoadControl implements LoadControl {
|
||||||
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
|
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
|
||||||
TrackSelectionArray trackSelections) {
|
TrackSelectionArray trackSelections) {
|
||||||
hasVideo = hasVideo(renderers, trackSelections);
|
hasVideo = hasVideo(renderers, trackSelections);
|
||||||
targetBufferSize =
|
targetBufferBytes =
|
||||||
targetBufferBytesOverwrite == C.LENGTH_UNSET
|
targetBufferBytesOverwrite == C.LENGTH_UNSET
|
||||||
? calculateTargetBufferSize(renderers, trackSelections)
|
? calculateTargetBufferBytes(renderers, trackSelections)
|
||||||
: targetBufferBytesOverwrite;
|
: targetBufferBytesOverwrite;
|
||||||
allocator.setTargetBufferSize(targetBufferSize);
|
allocator.setTargetBufferSize(targetBufferBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -382,7 +386,7 @@ public class DefaultLoadControl implements LoadControl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
|
public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
|
||||||
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
|
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferBytes;
|
||||||
long minBufferUs = hasVideo ? minBufferVideoUs : minBufferAudioUs;
|
long minBufferUs = hasVideo ? minBufferVideoUs : minBufferAudioUs;
|
||||||
if (playbackSpeed > 1) {
|
if (playbackSpeed > 1) {
|
||||||
// The playback speed is faster than real time, so scale up the minimum required media
|
// The playback speed is faster than real time, so scale up the minimum required media
|
||||||
|
|
@ -391,6 +395,8 @@ public class DefaultLoadControl implements LoadControl {
|
||||||
Util.getMediaDurationForPlayoutDuration(minBufferUs, playbackSpeed);
|
Util.getMediaDurationForPlayoutDuration(minBufferUs, playbackSpeed);
|
||||||
minBufferUs = Math.min(mediaDurationMinBufferUs, maxBufferUs);
|
minBufferUs = Math.min(mediaDurationMinBufferUs, maxBufferUs);
|
||||||
}
|
}
|
||||||
|
// Prevent playback from getting stuck if minBufferUs is too small.
|
||||||
|
minBufferUs = Math.max(minBufferUs, 500_000);
|
||||||
if (bufferedDurationUs < minBufferUs) {
|
if (bufferedDurationUs < minBufferUs) {
|
||||||
isBuffering = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached;
|
isBuffering = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached;
|
||||||
} else if (bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) {
|
} else if (bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) {
|
||||||
|
|
@ -407,7 +413,7 @@ public class DefaultLoadControl implements LoadControl {
|
||||||
return minBufferDurationUs <= 0
|
return minBufferDurationUs <= 0
|
||||||
|| bufferedDurationUs >= minBufferDurationUs
|
|| bufferedDurationUs >= minBufferDurationUs
|
||||||
|| (!prioritizeTimeOverSizeThresholds
|
|| (!prioritizeTimeOverSizeThresholds
|
||||||
&& allocator.getTotalBytesAllocated() >= targetBufferSize);
|
&& allocator.getTotalBytesAllocated() >= targetBufferBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -418,7 +424,7 @@ public class DefaultLoadControl implements LoadControl {
|
||||||
* @param trackSelectionArray The selected tracks.
|
* @param trackSelectionArray The selected tracks.
|
||||||
* @return The target buffer size in bytes.
|
* @return The target buffer size in bytes.
|
||||||
*/
|
*/
|
||||||
protected int calculateTargetBufferSize(
|
protected int calculateTargetBufferBytes(
|
||||||
Renderer[] renderers, TrackSelectionArray trackSelectionArray) {
|
Renderer[] renderers, TrackSelectionArray trackSelectionArray) {
|
||||||
int targetBufferSize = 0;
|
int targetBufferSize = 0;
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
|
|
@ -430,7 +436,10 @@ public class DefaultLoadControl implements LoadControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset(boolean resetAllocator) {
|
private void reset(boolean resetAllocator) {
|
||||||
targetBufferSize = 0;
|
targetBufferBytes =
|
||||||
|
targetBufferBytesOverwrite == C.LENGTH_UNSET
|
||||||
|
? DEFAULT_MUXED_BUFFER_SIZE
|
||||||
|
: targetBufferBytesOverwrite;
|
||||||
isBuffering = false;
|
isBuffering = false;
|
||||||
if (resetAllocator) {
|
if (resetAllocator) {
|
||||||
allocator.reset();
|
allocator.reset();
|
||||||
|
|
|
||||||
|
|
@ -830,6 +830,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
for (Renderer renderer : enabledRenderers) {
|
for (Renderer renderer : enabledRenderers) {
|
||||||
renderer.maybeThrowStreamError();
|
renderer.maybeThrowStreamError();
|
||||||
}
|
}
|
||||||
|
if (!shouldContinueLoading
|
||||||
|
&& playbackInfo.totalBufferedDurationUs < 500_000
|
||||||
|
&& isLoadingPossible()) {
|
||||||
|
// Throw if the LoadControl prevents loading even if the buffer is empty or almost empty. We
|
||||||
|
// can't compare against 0 to account for small differences between the renderer position
|
||||||
|
// and buffered position in the media at the point where playback gets stuck.
|
||||||
|
throw new IllegalStateException("Playback stuck buffering and not loading");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((playWhenReady && playbackInfo.playbackState == Player.STATE_READY)
|
if ((playWhenReady && playbackInfo.playbackState == Player.STATE_READY)
|
||||||
|
|
@ -1991,13 +1999,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
}
|
}
|
||||||
long bufferedDurationUs =
|
long bufferedDurationUs =
|
||||||
getTotalBufferedDurationUs(queue.getLoadingPeriod().getNextLoadPositionUs());
|
getTotalBufferedDurationUs(queue.getLoadingPeriod().getNextLoadPositionUs());
|
||||||
if (bufferedDurationUs < 500_000) {
|
|
||||||
// Prevent loading from getting stuck even if LoadControl.shouldContinueLoading returns false
|
|
||||||
// when the buffer is empty or almost empty. We can't compare against 0 to account for small
|
|
||||||
// differences between the renderer position and buffered position in the media at the point
|
|
||||||
// where playback gets stuck.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
float playbackSpeed = mediaClock.getPlaybackParameters().speed;
|
float playbackSpeed = mediaClock.getPlaybackParameters().speed;
|
||||||
return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
|
return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ public class DefaultLoadControlTest {
|
||||||
@Test
|
@Test
|
||||||
public void testShouldContinueLoading_untilMaxBufferExceeded() {
|
public void testShouldContinueLoading_untilMaxBufferExceeded() {
|
||||||
createDefaultLoadControl();
|
createDefaultLoadControl();
|
||||||
|
|
||||||
assertThat(loadControl.shouldContinueLoading(/* bufferedDurationUs= */ 0, SPEED)).isTrue();
|
assertThat(loadControl.shouldContinueLoading(/* bufferedDurationUs= */ 0, SPEED)).isTrue();
|
||||||
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isTrue();
|
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isTrue();
|
||||||
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US - 1, SPEED)).isTrue();
|
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US - 1, SPEED)).isTrue();
|
||||||
|
|
@ -56,11 +57,27 @@ public class DefaultLoadControlTest {
|
||||||
public void testShouldNotContinueLoadingOnceBufferingStopped_untilBelowMinBuffer() {
|
public void testShouldNotContinueLoadingOnceBufferingStopped_untilBelowMinBuffer() {
|
||||||
createDefaultLoadControl();
|
createDefaultLoadControl();
|
||||||
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse();
|
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse();
|
||||||
|
|
||||||
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US - 1, SPEED)).isFalse();
|
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US - 1, SPEED)).isFalse();
|
||||||
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isFalse();
|
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isFalse();
|
||||||
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US - 1, SPEED)).isTrue();
|
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US - 1, SPEED)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
testContinueLoadingOnceBufferingStopped_andBufferAlmostEmpty_evenIfMinBufferNotReached() {
|
||||||
|
builder.setBufferDurationsMs(
|
||||||
|
/* minBufferMs= */ 0,
|
||||||
|
/* maxBufferMs= */ (int) C.usToMs(MAX_BUFFER_US),
|
||||||
|
/* bufferForPlaybackMs= */ 0,
|
||||||
|
/* bufferForPlaybackAfterRebufferMs= */ 0);
|
||||||
|
createDefaultLoadControl();
|
||||||
|
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse();
|
||||||
|
|
||||||
|
assertThat(loadControl.shouldContinueLoading(5 * C.MICROS_PER_SECOND, SPEED)).isFalse();
|
||||||
|
assertThat(loadControl.shouldContinueLoading(500L, SPEED)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testShouldContinueLoadingWithTargetBufferBytesReached_untilMinBufferReached() {
|
public void testShouldContinueLoadingWithTargetBufferBytesReached_untilMinBufferReached() {
|
||||||
createDefaultLoadControl();
|
createDefaultLoadControl();
|
||||||
|
|
@ -81,6 +98,7 @@ public class DefaultLoadControlTest {
|
||||||
makeSureTargetBufferBytesReached();
|
makeSureTargetBufferBytesReached();
|
||||||
|
|
||||||
assertThat(loadControl.shouldContinueLoading(/* bufferedDurationUs= */ 0, SPEED)).isFalse();
|
assertThat(loadControl.shouldContinueLoading(/* bufferedDurationUs= */ 0, SPEED)).isFalse();
|
||||||
|
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US - 1, SPEED)).isFalse();
|
||||||
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isFalse();
|
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isFalse();
|
||||||
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse();
|
assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +109,6 @@ public class DefaultLoadControlTest {
|
||||||
|
|
||||||
// At normal playback speed, we stop buffering when the buffer reaches the minimum.
|
// At normal playback speed, we stop buffering when the buffer reaches the minimum.
|
||||||
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isFalse();
|
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isFalse();
|
||||||
|
|
||||||
// At double playback speed, we continue loading.
|
// At double playback speed, we continue loading.
|
||||||
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, /* playbackSpeed= */ 2f)).isTrue();
|
assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, /* playbackSpeed= */ 2f)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3393,8 +3393,8 @@ public final class ExoPlayerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loadControlNeverWantsToLoadOrPlay_playbackDoesNotGetStuck() throws Exception {
|
public void loadControlNeverWantsToLoad_throwsIllegalStateException() throws Exception {
|
||||||
LoadControl neverLoadingOrPlayingLoadControl =
|
LoadControl neverLoadingLoadControl =
|
||||||
new DefaultLoadControl() {
|
new DefaultLoadControl() {
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
|
public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
|
||||||
|
|
@ -3404,7 +3404,7 @@ public final class ExoPlayerTest {
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldStartPlayback(
|
public boolean shouldStartPlayback(
|
||||||
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
|
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -3418,13 +3418,18 @@ public final class ExoPlayerTest {
|
||||||
new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)),
|
new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)),
|
||||||
new FakeChunkSource.Factory(dataSetFactory, new FakeDataSource.Factory()));
|
new FakeChunkSource.Factory(dataSetFactory, new FakeDataSource.Factory()));
|
||||||
|
|
||||||
new ExoPlayerTestRunner.Builder()
|
try {
|
||||||
.setLoadControl(neverLoadingOrPlayingLoadControl)
|
new ExoPlayerTestRunner.Builder()
|
||||||
.setMediaSources(chunkedMediaSource)
|
.setLoadControl(neverLoadingLoadControl)
|
||||||
.build(context)
|
.setMediaSources(chunkedMediaSource)
|
||||||
.start()
|
.build(context)
|
||||||
// This throws if playback doesn't finish within timeout.
|
.start()
|
||||||
.blockUntilEnded(TIMEOUT_MS);
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
|
fail();
|
||||||
|
} catch (ExoPlaybackException e) {
|
||||||
|
assertThat(e.type).isEqualTo(ExoPlaybackException.TYPE_UNEXPECTED);
|
||||||
|
assertThat(e.getUnexpectedException()).isInstanceOf(IllegalStateException.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue