Callback when sleeping for offload and existing from it
#exo-offload PiperOrigin-RevId: 333497538
This commit is contained in:
parent
294ae10ef1
commit
f37d79a4dd
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -604,6 +604,12 @@ public interface Player {
|
||||
* <p>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.
|
||||
*
|
||||
* <p>This method is experimental, and will be renamed or removed in a future release.
|
||||
*/
|
||||
default void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<Renderer.WakeupListener> wakeupListenerReceiver;
|
||||
|
||||
public FakeSleepRenderer(int trackType) {
|
||||
super(trackType);
|
||||
sleepOnNextRender = new AtomicBoolean(false);
|
||||
isSleeping = new AtomicBoolean(false);
|
||||
wakeupListenerReceiver = new AtomicReference<>();
|
||||
}
|
||||
|
||||
@ -8372,14 +8361,6 @@ public final class ExoPlayerTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@link Renderer.WakeupListener#onSleep(long)} was called on the last {@link
|
||||
* #render(long, long)}
|
||||
*/
|
||||
public boolean isSleeping() {
|
||||
return isSleeping.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(int what, @Nullable Object object) throws ExoPlaybackException {
|
||||
if (what == MSG_SET_WAKEUP_LISTENER) {
|
||||
@ -8394,11 +8375,6 @@ public final class ExoPlayerTest {
|
||||
super.render(positionUs, elapsedRealtimeUs);
|
||||
if (sleepOnNextRender.compareAndSet(/* expect= */ true, /* update= */ false)) {
|
||||
wakeupListenerReceiver.get().onSleep(WAKEUP_DEADLINE_MS);
|
||||
// TODO(b/163303129): Use an actual message from the player instead of guessing that the
|
||||
// player will always sleep for offload after calling `onSleep`.
|
||||
isSleeping.set(true);
|
||||
} else {
|
||||
isSleeping.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -439,7 +439,8 @@ public final class MediaPeriodQueueTest {
|
||||
/* bufferedPositionUs= */ 0,
|
||||
/* totalBufferedDurationUs= */ 0,
|
||||
/* positionUs= */ 0,
|
||||
/* offloadSchedulingEnabled= */ false);
|
||||
/* offloadSchedulingEnabled= */ false,
|
||||
/* sleepingForOffload= */ false);
|
||||
}
|
||||
|
||||
private void advance() {
|
||||
|
@ -466,6 +466,32 @@ public class TestExoPlayer {
|
||||
return Assertions.checkNotNull(offloadSchedulingEnabledReceiver.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until a {@link
|
||||
* Player.EventListener#onExperimentalSleepingForOffloadChanged(boolean)} callback occurred.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param expectedSleepForOffload The expected sleep of offload state.
|
||||
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
|
||||
* exceeded.
|
||||
*/
|
||||
public static void runUntilSleepingForOffload(Player player, boolean expectedSleepForOffload)
|
||||
throws TimeoutException {
|
||||
verifyMainTestThread(player);
|
||||
AtomicBoolean receiverCallback = new AtomicBoolean(false);
|
||||
Player.EventListener listener =
|
||||
new Player.EventListener() {
|
||||
@Override
|
||||
public void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) {
|
||||
if (sleepingForOffload == expectedSleepForOffload) {
|
||||
receiverCallback.set(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
player.addListener(listener);
|
||||
runMainLooperUntil(receiverCallback::get);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tasks of the main {@link Looper} until the {@link VideoListener#onRenderedFirstFrame}
|
||||
* callback has been called.
|
||||
@ -504,7 +530,7 @@ public class TestExoPlayer {
|
||||
verifyMainTestThread(player);
|
||||
Handler testHandler = Util.createHandlerForCurrentOrMainLooper();
|
||||
|
||||
AtomicBoolean messageHandled = new AtomicBoolean();
|
||||
AtomicBoolean messageHandled = new AtomicBoolean(false);
|
||||
player
|
||||
.createMessage(
|
||||
(messageType, payload) -> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user