mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Rollback of d48dc4c159
*** 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 LoadControl lets playback get stuck. This includes the case where
DefaultLoadControl reaches its maximum buffer size and not even a mim...
***
PiperOrigin-RevId: 286071115
This commit is contained in:
parent
36fa9d5a43
commit
3c56b113e4
@ -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 targetBufferBytes;
|
private int targetBufferSize;
|
||||||
private boolean isBuffering;
|
private boolean isBuffering;
|
||||||
private boolean hasVideo;
|
private boolean hasVideo;
|
||||||
|
|
||||||
@ -334,10 +334,6 @@ 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;
|
||||||
@ -352,11 +348,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);
|
||||||
targetBufferBytes =
|
targetBufferSize =
|
||||||
targetBufferBytesOverwrite == C.LENGTH_UNSET
|
targetBufferBytesOverwrite == C.LENGTH_UNSET
|
||||||
? calculateTargetBufferBytes(renderers, trackSelections)
|
? calculateTargetBufferSize(renderers, trackSelections)
|
||||||
: targetBufferBytesOverwrite;
|
: targetBufferBytesOverwrite;
|
||||||
allocator.setTargetBufferSize(targetBufferBytes);
|
allocator.setTargetBufferSize(targetBufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -386,7 +382,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() >= targetBufferBytes;
|
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
|
||||||
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
|
||||||
@ -395,8 +391,6 @@ 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) {
|
||||||
@ -413,7 +407,7 @@ public class DefaultLoadControl implements LoadControl {
|
|||||||
return minBufferDurationUs <= 0
|
return minBufferDurationUs <= 0
|
||||||
|| bufferedDurationUs >= minBufferDurationUs
|
|| bufferedDurationUs >= minBufferDurationUs
|
||||||
|| (!prioritizeTimeOverSizeThresholds
|
|| (!prioritizeTimeOverSizeThresholds
|
||||||
&& allocator.getTotalBytesAllocated() >= targetBufferBytes);
|
&& allocator.getTotalBytesAllocated() >= targetBufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -424,7 +418,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 calculateTargetBufferBytes(
|
protected int calculateTargetBufferSize(
|
||||||
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++) {
|
||||||
@ -436,10 +430,7 @@ public class DefaultLoadControl implements LoadControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void reset(boolean resetAllocator) {
|
private void reset(boolean resetAllocator) {
|
||||||
targetBufferBytes =
|
targetBufferSize = 0;
|
||||||
targetBufferBytesOverwrite == C.LENGTH_UNSET
|
|
||||||
? DEFAULT_MUXED_BUFFER_SIZE
|
|
||||||
: targetBufferBytesOverwrite;
|
|
||||||
isBuffering = false;
|
isBuffering = false;
|
||||||
if (resetAllocator) {
|
if (resetAllocator) {
|
||||||
allocator.reset();
|
allocator.reset();
|
||||||
|
@ -830,14 +830,6 @@ 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)
|
||||||
@ -1992,6 +1984,13 @@ 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,7 +46,6 @@ 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();
|
||||||
@ -57,27 +56,11 @@ 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();
|
||||||
@ -98,7 +81,6 @@ 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();
|
||||||
}
|
}
|
||||||
@ -109,6 +91,7 @@ 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 loadControlNeverWantsToLoad_throwsIllegalStateException() throws Exception {
|
public void loadControlNeverWantsToLoadOrPlay_playbackDoesNotGetStuck() throws Exception {
|
||||||
LoadControl neverLoadingLoadControl =
|
LoadControl neverLoadingOrPlayingLoadControl =
|
||||||
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 true;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -3418,18 +3418,13 @@ 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()));
|
||||||
|
|
||||||
try {
|
new ExoPlayerTestRunner.Builder()
|
||||||
new ExoPlayerTestRunner.Builder()
|
.setLoadControl(neverLoadingOrPlayingLoadControl)
|
||||||
.setLoadControl(neverLoadingLoadControl)
|
.setMediaSources(chunkedMediaSource)
|
||||||
.setMediaSources(chunkedMediaSource)
|
.build(context)
|
||||||
.build(context)
|
.start()
|
||||||
.start()
|
// This throws if playback doesn't finish within timeout.
|
||||||
.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…
x
Reference in New Issue
Block a user