Discard backbuffer before playback gets stuck.
If the back buffer is using too much memory, there is a risk playback could get stuck because LoadControl refuses to load further data. This eventually results in a stuck-buffering playback error. We can detect this case, clear the back buffer and then ask the LoadControl again to avoid failing playback in such a case. PiperOrigin-RevId: 472679797
This commit is contained in:
parent
c401fb9771
commit
310e0fec5c
@ -11,6 +11,8 @@
|
||||
([#10057](https://github.com/google/ExoPlayer/issues/10057)).
|
||||
* Limit parallel download removals to 1 to avoid excessive thread creation
|
||||
([#10458](https://github.com/google/ExoPlayer/issues/10458)).
|
||||
* Discard back buffer before playback gets stuck due to insufficient
|
||||
available memory.
|
||||
* Downloads:
|
||||
* Fix potential infinite loop in `ProgressiveDownloader` caused by
|
||||
simultaneous download and playback with the same `PriorityTaskManager`
|
||||
|
@ -175,6 +175,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* to load it.
|
||||
*/
|
||||
private static final long PLAYBACK_STUCK_AFTER_MS = 4000;
|
||||
/**
|
||||
* Threshold under which a buffered duration is assumed to be empty. We cannot use zero to account
|
||||
* for buffers currently hold but not played by the renderer.
|
||||
*/
|
||||
private static final long PLAYBACK_BUFFER_EMPTY_THRESHOLD_US = 500_000;
|
||||
|
||||
private final Renderer[] renderers;
|
||||
private final Set<Renderer> renderersToReset;
|
||||
@ -1058,7 +1063,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
}
|
||||
if (!playbackInfo.isLoading
|
||||
&& playbackInfo.totalBufferedDurationUs < 500_000
|
||||
&& playbackInfo.totalBufferedDurationUs < PLAYBACK_BUFFER_EMPTY_THRESHOLD_US
|
||||
&& isLoadingPossible()) {
|
||||
// The renderers are not ready, there is more media available to load, and the LoadControl
|
||||
// is refusing to load it (indicated by !playbackInfo.isLoading). This could be because the
|
||||
@ -2314,8 +2319,23 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
? loadingPeriodHolder.toPeriodTime(rendererPositionUs)
|
||||
: loadingPeriodHolder.toPeriodTime(rendererPositionUs)
|
||||
- loadingPeriodHolder.info.startPositionUs;
|
||||
return loadControl.shouldContinueLoading(
|
||||
boolean shouldContinueLoading =
|
||||
loadControl.shouldContinueLoading(
|
||||
playbackPositionUs, bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
|
||||
if (!shouldContinueLoading
|
||||
&& bufferedDurationUs < PLAYBACK_BUFFER_EMPTY_THRESHOLD_US
|
||||
&& (backBufferDurationUs > 0 || retainBackBufferFromKeyframe)) {
|
||||
// LoadControl doesn't want to continue loading despite no buffered data. Clear back buffer
|
||||
// and try again in case it's blocked on memory usage of the back buffer.
|
||||
queue
|
||||
.getPlayingPeriod()
|
||||
.mediaPeriod
|
||||
.discardBuffer(playbackInfo.positionUs, /* toKeyframe= */ false);
|
||||
shouldContinueLoading =
|
||||
loadControl.shouldContinueLoading(
|
||||
playbackPositionUs, bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
|
||||
}
|
||||
return shouldContinueLoading;
|
||||
}
|
||||
|
||||
private boolean isLoadingPossible() {
|
||||
|
@ -12095,6 +12095,26 @@ public final class ExoPlayerTest {
|
||||
verify(listener, atLeast(2)).onDeviceVolumeChanged(anyInt(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadControlBackBuffer_withInsufficientMemoryLimits_stillContinuesPlayback()
|
||||
throws Exception {
|
||||
DefaultLoadControl loadControl =
|
||||
new DefaultLoadControl.Builder()
|
||||
.setTargetBufferBytes(500_000)
|
||||
.setBackBuffer(
|
||||
/* backBufferDurationMs= */ 1_000_000, /* retainBackBufferFromKeyframe= */ true)
|
||||
.build();
|
||||
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).setLoadControl(loadControl).build();
|
||||
player.setMediaItem(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"));
|
||||
player.prepare();
|
||||
player.play();
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
|
||||
// Assert that playing works without getting stuck due to the memory used by the back buffer.
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user