From 9cfaf78994e292ee2d9b7526890ac1584ccd851d Mon Sep 17 00:00:00 2001 From: michaelkatz Date: Thu, 3 Apr 2025 04:13:23 -0700 Subject: [PATCH] Do not enable offload scheduling while preparing next media ExoPlayer disables sleeping for offload when the reading period advances and re-evaluates turning it back on when the playing period advances. For playlists of short items where the reading period could advance much further than the playing period, sleeping should still be disabled until the playing period advances to match the current reading period. Issue: androidx/media#1920 PiperOrigin-RevId: 743503063 (cherry picked from commit 8327a2a52dd72a98d4abc123f33cfe1250898318) --- RELEASENOTES.md | 3 ++ .../exoplayer/ExoPlayerImplInternal.java | 4 ++ .../media3/exoplayer/ExoPlayerTest.java | 48 +++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 767eb49048..8699d4be69 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -39,6 +39,9 @@ * DataSource: * Audio: * Allow constant power upmixing/downmixing in DefaultAudioMixer. + * Fix offload issue where position might get stuck when playing a playlist + of short content + ([#1920](https://github.com/androidx/media/issues/1920)). * Video: * Add experimental `ExoPlayer` API to include the `MediaCodec.BUFFER_FLAG_DECODE_ONLY` flag when queuing decode-only input 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 3f29654723..135d14d2f7 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -2783,6 +2783,10 @@ import java.util.concurrent.atomic.AtomicBoolean; private void maybeUpdateOffloadScheduling() { // If playing period is audio-only with offload mode preference to enable, then offload // scheduling should be enabled. + if (queue.getPlayingPeriod() != queue.getReadingPeriod()) { + // Do not enable offload scheduling when starting to process the next media item. + return; + } @Nullable MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); if (playingPeriodHolder != null) { TrackSelectorResult trackSelectorResult = playingPeriodHolder.getTrackSelectorResult(); 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 944af0a284..d56c1c23ce 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -11786,6 +11786,54 @@ public class ExoPlayerTest { player.release(); } + @Test + public void enablingOffload_withFastReadingPeriodAdvancement_playerDoesNotSleep() + throws Exception { + FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO); + AtomicInteger sleepingForOffloadCounter = new AtomicInteger(); + ExoPlayer player = + parameterizeTestExoPlayerBuilder( + new TestExoPlayerBuilder(context).setRenderers(sleepRenderer)) + .build(); + ExoPlayer.AudioOffloadListener listener = + new ExoPlayer.AudioOffloadListener() { + @Override + public void onSleepingForOffloadChanged(boolean sleepingForOffload) { + if (sleepingForOffload) { + sleepingForOffloadCounter.getAndIncrement(); + } + } + }; + player.addAudioOffloadListener(listener); + // Set a playlist of multiple, short audio-only items such that the reading period quickly + // advances past the playing period. + Timeline timeline = new FakeTimeline(); + player.setMediaSources( + ImmutableList.of( + new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT), + new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT), + new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT))); + player.setTrackSelectionParameters( + player + .getTrackSelectionParameters() + .buildUpon() + .setAudioOffloadPreferences( + new AudioOffloadPreferences.Builder() + .setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_REQUIRED) + .build()) + .build()); + player.prepare(); + player.play(); + advance(player).untilStartOfMediaItem(/* mediaItemIndex= */ 1); + + sleepRenderer.sleepOnNextRender(); + runUntilPlaybackState(player, Player.STATE_ENDED); + + assertThat(sleepingForOffloadCounter.get()).isEqualTo(0); + + player.release(); + } + @Test public void wakeupListenerWhileSleepingForOffload_isWokenUp_renderingResumes() throws Exception { FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO).sleepOnNextRender();