diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index b1f5736465..1b0b34bd7b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -1353,6 +1353,7 @@ import java.util.concurrent.TimeoutException; private final boolean isPlayingChanged; private final boolean playbackParametersChanged; private final boolean offloadSchedulingEnabledChanged; + private final boolean sleepingForOffloadChanged; public PlaybackInfoUpdate( PlaybackInfo playbackInfo, @@ -1394,6 +1395,8 @@ import java.util.concurrent.TimeoutException; !previousPlaybackInfo.playbackParameters.equals(playbackInfo.playbackParameters); offloadSchedulingEnabledChanged = previousPlaybackInfo.offloadSchedulingEnabled != playbackInfo.offloadSchedulingEnabled; + sleepingForOffloadChanged = + previousPlaybackInfo.sleepingForOffload != playbackInfo.sleepingForOffload; } @SuppressWarnings("deprecation") @@ -1476,6 +1479,12 @@ import java.util.concurrent.TimeoutException; listener.onExperimentalOffloadSchedulingEnabledChanged( playbackInfo.offloadSchedulingEnabled)); } + if (sleepingForOffloadChanged) { + invokeAll( + listenerSnapshot, + listener -> + listener.onExperimentalSleepingForOffloadChanged(playbackInfo.sleepingForOffload)); + } } private static boolean isPlaying(PlaybackInfo playbackInfo) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index e33b93ac0e..0752c08949 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -960,14 +960,18 @@ import java.util.concurrent.atomic.AtomicBoolean; playbackInfo = playbackInfo.copyWithOffloadSchedulingEnabled(offloadSchedulingEnabled); } + boolean sleepingForOffload = false; if ((shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY) || playbackInfo.playbackState == Player.STATE_BUFFERING) { - maybeScheduleWakeup(operationStartTimeMs, ACTIVE_INTERVAL_MS); + sleepingForOffload = !maybeScheduleWakeup(operationStartTimeMs, ACTIVE_INTERVAL_MS); } else if (enabledRendererCount != 0 && playbackInfo.playbackState != Player.STATE_ENDED) { scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS); } else { handler.removeMessages(MSG_DO_SOME_WORK); } + if (playbackInfo.sleepingForOffload != sleepingForOffload) { + playbackInfo = playbackInfo.copyWithSleepingForOffload(sleepingForOffload); + } requestForRendererSleep = false; // A sleep request is only valid for the current doSomeWork. TraceUtil.endSection(); @@ -978,12 +982,13 @@ import java.util.concurrent.atomic.AtomicBoolean; handler.sendEmptyMessageAtTime(MSG_DO_SOME_WORK, thisOperationStartTimeMs + intervalMs); } - private void maybeScheduleWakeup(long operationStartTimeMs, long intervalMs) { + private boolean maybeScheduleWakeup(long operationStartTimeMs, long intervalMs) { if (offloadSchedulingEnabled && requestForRendererSleep) { - return; + return false; } scheduleNextWork(operationStartTimeMs, intervalMs); + return true; } private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException { @@ -1308,7 +1313,8 @@ import java.util.concurrent.atomic.AtomicBoolean; startPositionUs, /* totalBufferedDurationUs= */ 0, startPositionUs, - offloadSchedulingEnabled); + offloadSchedulingEnabled, + /* sleepingForOffload= */ false); if (releaseMediaSourceList) { mediaSourceList.release(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 9fb6563005..e7f200d8b7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -67,6 +67,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; public final PlaybackParameters playbackParameters; /** Whether offload scheduling is enabled for the main player loop. */ public final boolean offloadSchedulingEnabled; + /** Whether the main player loop is sleeping, while using offload scheduling. */ + public final boolean sleepingForOffload; /** * Position up to which media is buffered in {@link #loadingMediaPeriodId) relative to the start @@ -109,7 +111,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; /* bufferedPositionUs= */ 0, /* totalBufferedDurationUs= */ 0, /* positionUs= */ 0, - /* offloadSchedulingEnabled= */ false); + /* offloadSchedulingEnabled= */ false, + /* sleepingForOffload= */ false); } /** @@ -131,6 +134,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; * @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}. * @param positionUs See {@link #positionUs}. * @param offloadSchedulingEnabled See {@link #offloadSchedulingEnabled}. + * @param sleepingForOffload See {@link #sleepingForOffload}. */ public PlaybackInfo( Timeline timeline, @@ -148,7 +152,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; long bufferedPositionUs, long totalBufferedDurationUs, long positionUs, - boolean offloadSchedulingEnabled) { + boolean offloadSchedulingEnabled, + boolean sleepingForOffload) { this.timeline = timeline; this.periodId = periodId; this.requestedContentPositionUs = requestedContentPositionUs; @@ -165,6 +170,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; this.totalBufferedDurationUs = totalBufferedDurationUs; this.positionUs = positionUs; this.offloadSchedulingEnabled = offloadSchedulingEnabled; + this.sleepingForOffload = sleepingForOffload; } /** Returns a placeholder period id for an empty timeline. */ @@ -209,7 +215,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; bufferedPositionUs, totalBufferedDurationUs, positionUs, - offloadSchedulingEnabled); + offloadSchedulingEnabled, + sleepingForOffload); } /** @@ -236,7 +243,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; bufferedPositionUs, totalBufferedDurationUs, positionUs, - offloadSchedulingEnabled); + offloadSchedulingEnabled, + sleepingForOffload); } /** @@ -263,7 +271,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; bufferedPositionUs, totalBufferedDurationUs, positionUs, - offloadSchedulingEnabled); + offloadSchedulingEnabled, + sleepingForOffload); } /** @@ -290,7 +299,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; bufferedPositionUs, totalBufferedDurationUs, positionUs, - offloadSchedulingEnabled); + offloadSchedulingEnabled, + sleepingForOffload); } /** @@ -317,7 +327,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; bufferedPositionUs, totalBufferedDurationUs, positionUs, - offloadSchedulingEnabled); + offloadSchedulingEnabled, + sleepingForOffload); } /** @@ -344,7 +355,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; bufferedPositionUs, totalBufferedDurationUs, positionUs, - offloadSchedulingEnabled); + offloadSchedulingEnabled, + sleepingForOffload); } /** @@ -375,7 +387,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; bufferedPositionUs, totalBufferedDurationUs, positionUs, - offloadSchedulingEnabled); + offloadSchedulingEnabled, + sleepingForOffload); } /** @@ -402,7 +415,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; bufferedPositionUs, totalBufferedDurationUs, positionUs, - offloadSchedulingEnabled); + offloadSchedulingEnabled, + sleepingForOffload); } /** @@ -430,6 +444,35 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; bufferedPositionUs, totalBufferedDurationUs, positionUs, - offloadSchedulingEnabled); + offloadSchedulingEnabled, + sleepingForOffload); + } + + /** + * Copies playback info with new sleepingForOffload. + * + * @param sleepingForOffload New main player loop sleeping state. See {@link #sleepingForOffload}. + * @return Copied playback info with new main player loop sleeping state. + */ + @CheckResult + public PlaybackInfo copyWithSleepingForOffload(boolean sleepingForOffload) { + return new PlaybackInfo( + timeline, + periodId, + requestedContentPositionUs, + playbackState, + playbackError, + isLoading, + trackGroups, + trackSelectorResult, + loadingMediaPeriodId, + playWhenReady, + playbackSuppressionReason, + playbackParameters, + bufferedPositionUs, + totalBufferedDurationUs, + positionUs, + offloadSchedulingEnabled, + sleepingForOffload); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 7a52aae738..89a00eb475 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -604,6 +604,12 @@ public interface Player { *
This method is experimental, and will be renamed or removed in a future release. */ default void onExperimentalOffloadSchedulingEnabledChanged(boolean offloadSchedulingEnabled) {} + /** + * Called when the player has started or finished sleeping for offload. + * + *
This method is experimental, and will be renamed or removed in a future release.
+ */
+ default void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) {}
}
/**
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
index 7934298df0..a1b3b9014d 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
@@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSample
import static com.google.android.exoplayer2.testutil.TestExoPlayer.playUntilStartOfWindow;
import static com.google.android.exoplayer2.testutil.TestExoPlayer.runUntilPlaybackState;
import static com.google.android.exoplayer2.testutil.TestExoPlayer.runUntilReceiveOffloadSchedulingEnabledNewState;
+import static com.google.android.exoplayer2.testutil.TestExoPlayer.runUntilSleepingForOffload;
import static com.google.android.exoplayer2.testutil.TestExoPlayer.runUntilTimelineChanged;
import static com.google.android.exoplayer2.testutil.TestUtil.runMainLooperUntil;
import static com.google.common.truth.Truth.assertThat;
@@ -111,7 +112,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -8249,16 +8249,14 @@ public final class ExoPlayerTest {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
player.experimentalSetOffloadSchedulingEnabled(true);
- runUntilReceiveOffloadSchedulingEnabledNewState(player);
player.prepare();
player.play();
- runMainLooperUntil(sleepRenderer::isSleeping);
+ runUntilSleepingForOffload(player, /* expectedSleepForOffload= */ true);
player.experimentalSetOffloadSchedulingEnabled(false);
assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isFalse();
}
-
@Test
public void enableOffloadScheduling_isEnable_playerSleeps() throws Exception {
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO);
@@ -8271,14 +8269,7 @@ public final class ExoPlayerTest {
sleepRenderer.sleepOnNextRender();
- runMainLooperUntil(sleepRenderer::isSleeping);
- // TODO(b/163303129): There is currently no way to check that the player is sleeping for
- // offload, for now use a timeout to check that the renderer is never woken up.
- final int renderTimeoutMs = 500;
- assertThrows(
- TimeoutException.class,
- () ->
- runMainLooperUntil(() -> !sleepRenderer.isSleeping(), renderTimeoutMs, Clock.DEFAULT));
+ runUntilSleepingForOffload(player, /* expectedSleepForOffload= */ true);
}
@Test
@@ -8292,11 +8283,11 @@ public final class ExoPlayerTest {
player.experimentalSetOffloadSchedulingEnabled(true);
player.prepare();
player.play();
- runMainLooperUntil(sleepRenderer::isSleeping);
+ runUntilSleepingForOffload(player, /* expectedSleepForOffload= */ true);
player.experimentalSetOffloadSchedulingEnabled(false); // Force the player to exit offload sleep
- runMainLooperUntil(() -> !sleepRenderer.isSleeping());
+ runUntilSleepingForOffload(player, /* expectedSleepForOffload= */ false);
runUntilPlaybackState(player, Player.STATE_ENDED);
}
@@ -8309,11 +8300,11 @@ public final class ExoPlayerTest {
player.experimentalSetOffloadSchedulingEnabled(true);
player.prepare();
player.play();
- runMainLooperUntil(sleepRenderer::isSleeping);
+ runUntilSleepingForOffload(player, /* expectedSleepForOffload= */ true);
sleepRenderer.wakeup();
- runMainLooperUntil(() -> !sleepRenderer.isSleeping());
+ runUntilSleepingForOffload(player, /* expectedSleepForOffload= */ false);
runUntilPlaybackState(player, Player.STATE_ENDED);
}
@@ -8350,13 +8341,11 @@ public final class ExoPlayerTest {
private static class FakeSleepRenderer extends FakeRenderer {
private static final long WAKEUP_DEADLINE_MS = 60 * C.MICROS_PER_SECOND;
private final AtomicBoolean sleepOnNextRender;
- private final AtomicBoolean isSleeping;
private final AtomicReference