From 310e0fec5c648d51c596a9ca67d963d02ea89f6f Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 7 Sep 2022 10:09:29 +0000 Subject: [PATCH] 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 --- RELEASENOTES.md | 2 ++ .../exoplayer/ExoPlayerImplInternal.java | 26 ++++++++++++++++--- .../media3/exoplayer/ExoPlayerTest.java | 20 ++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7d961a7012..85701913ed 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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` diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java index a9f0af6111..cd2ef713c2 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -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 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( - playbackPositionUs, bufferedDurationUs, mediaClock.getPlaybackParameters().speed); + 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() { diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java index fcb19b48ca..f0b55b63a4 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -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) {