Callback when sleeping for offload and existing from it

#exo-offload

PiperOrigin-RevId: 333497538
This commit is contained in:
krocard 2020-09-24 13:50:57 +01:00 committed by kim-vde
parent 294ae10ef1
commit f37d79a4dd
7 changed files with 115 additions and 48 deletions

View File

@ -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) {

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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) {}
}
/**

View File

@ -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);
}
}
}

View File

@ -439,7 +439,8 @@ public final class MediaPeriodQueueTest {
/* bufferedPositionUs= */ 0,
/* totalBufferedDurationUs= */ 0,
/* positionUs= */ 0,
/* offloadSchedulingEnabled= */ false);
/* offloadSchedulingEnabled= */ false,
/* sleepingForOffload= */ false);
}
private void advance() {

View File

@ -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) -> {