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)).
|
([#10057](https://github.com/google/ExoPlayer/issues/10057)).
|
||||||
* Limit parallel download removals to 1 to avoid excessive thread creation
|
* Limit parallel download removals to 1 to avoid excessive thread creation
|
||||||
([#10458](https://github.com/google/ExoPlayer/issues/10458)).
|
([#10458](https://github.com/google/ExoPlayer/issues/10458)).
|
||||||
|
* Discard back buffer before playback gets stuck due to insufficient
|
||||||
|
available memory.
|
||||||
* Downloads:
|
* Downloads:
|
||||||
* Fix potential infinite loop in `ProgressiveDownloader` caused by
|
* Fix potential infinite loop in `ProgressiveDownloader` caused by
|
||||||
simultaneous download and playback with the same `PriorityTaskManager`
|
simultaneous download and playback with the same `PriorityTaskManager`
|
||||||
|
@ -175,6 +175,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
* to load it.
|
* to load it.
|
||||||
*/
|
*/
|
||||||
private static final long PLAYBACK_STUCK_AFTER_MS = 4000;
|
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 Renderer[] renderers;
|
||||||
private final Set<Renderer> renderersToReset;
|
private final Set<Renderer> renderersToReset;
|
||||||
@ -1058,7 +1063,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!playbackInfo.isLoading
|
if (!playbackInfo.isLoading
|
||||||
&& playbackInfo.totalBufferedDurationUs < 500_000
|
&& playbackInfo.totalBufferedDurationUs < PLAYBACK_BUFFER_EMPTY_THRESHOLD_US
|
||||||
&& isLoadingPossible()) {
|
&& isLoadingPossible()) {
|
||||||
// The renderers are not ready, there is more media available to load, and the LoadControl
|
// 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
|
// 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.toPeriodTime(rendererPositionUs)
|
: loadingPeriodHolder.toPeriodTime(rendererPositionUs)
|
||||||
- loadingPeriodHolder.info.startPositionUs;
|
- loadingPeriodHolder.info.startPositionUs;
|
||||||
return loadControl.shouldContinueLoading(
|
boolean shouldContinueLoading =
|
||||||
playbackPositionUs, bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
|
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() {
|
private boolean isLoadingPossible() {
|
||||||
|
@ -12095,6 +12095,26 @@ public final class ExoPlayerTest {
|
|||||||
verify(listener, atLeast(2)).onDeviceVolumeChanged(anyInt(), anyBoolean());
|
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.
|
// Internal methods.
|
||||||
|
|
||||||
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user