Extrapolate playback position during audio offload sleep
While playback thread is 'asleep' during audio offload playback, the playbackInfo.positionUs is not being constantly updated. During this time, the returned value from getCurrentPosition should return an estimate based on the most recent value and playback speed. PiperOrigin-RevId: 516550509
This commit is contained in:
parent
df724b517e
commit
81e73f6f19
@ -1002,6 +1002,9 @@ import java.util.concurrent.TimeoutException;
|
||||
listeners.release();
|
||||
playbackInfoUpdateHandler.removeCallbacksAndMessages(null);
|
||||
bandwidthMeter.removeEventListener(analyticsCollector);
|
||||
if (playbackInfo.sleepingForOffload) {
|
||||
playbackInfo = playbackInfo.copyWithEstimatedPosition();
|
||||
}
|
||||
playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE);
|
||||
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(playbackInfo.periodId);
|
||||
playbackInfo.bufferedPositionUs = playbackInfo.positionUs;
|
||||
@ -1792,11 +1795,18 @@ import java.util.concurrent.TimeoutException;
|
||||
private long getCurrentPositionUsInternal(PlaybackInfo playbackInfo) {
|
||||
if (playbackInfo.timeline.isEmpty()) {
|
||||
return Util.msToUs(maskingWindowPositionMs);
|
||||
} else if (playbackInfo.periodId.isAd()) {
|
||||
return playbackInfo.positionUs;
|
||||
}
|
||||
|
||||
long positionUs =
|
||||
playbackInfo.sleepingForOffload
|
||||
? playbackInfo.getEstimatedPositionUs()
|
||||
: playbackInfo.positionUs;
|
||||
|
||||
if (playbackInfo.periodId.isAd()) {
|
||||
return positionUs;
|
||||
} else {
|
||||
return periodPositionUsToWindowPositionUs(
|
||||
playbackInfo.timeline, playbackInfo.periodId, playbackInfo.positionUs);
|
||||
playbackInfo.timeline, playbackInfo.periodId, positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2009,10 +2019,10 @@ import java.util.concurrent.TimeoutException;
|
||||
listener.onPlaybackSuppressionReasonChanged(
|
||||
newPlaybackInfo.playbackSuppressionReason));
|
||||
}
|
||||
if (isPlaying(previousPlaybackInfo) != isPlaying(newPlaybackInfo)) {
|
||||
if (previousPlaybackInfo.isPlaying() != newPlaybackInfo.isPlaying()) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_IS_PLAYING_CHANGED,
|
||||
listener -> listener.onIsPlayingChanged(isPlaying(newPlaybackInfo)));
|
||||
listener -> listener.onIsPlayingChanged(newPlaybackInfo.isPlaying()));
|
||||
}
|
||||
if (!previousPlaybackInfo.playbackParameters.equals(newPlaybackInfo.playbackParameters)) {
|
||||
listeners.queueEvent(
|
||||
@ -2628,8 +2638,14 @@ import java.util.concurrent.TimeoutException;
|
||||
return;
|
||||
}
|
||||
pendingOperationAcks++;
|
||||
|
||||
// Position estimation and copy must occur before changing/masking playback state.
|
||||
PlaybackInfo playbackInfo =
|
||||
this.playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason);
|
||||
this.playbackInfo.sleepingForOffload
|
||||
? this.playbackInfo.copyWithEstimatedPosition()
|
||||
: this.playbackInfo;
|
||||
playbackInfo = playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason);
|
||||
|
||||
internalPlayer.setPlayWhenReady(playWhenReady, playbackSuppressionReason);
|
||||
updatePlaybackInfo(
|
||||
playbackInfo,
|
||||
@ -2751,12 +2767,6 @@ import java.util.concurrent.TimeoutException;
|
||||
: PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
|
||||
}
|
||||
|
||||
private static boolean isPlaying(PlaybackInfo playbackInfo) {
|
||||
return playbackInfo.playbackState == Player.STATE_READY
|
||||
&& playbackInfo.playWhenReady
|
||||
&& playbackInfo.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
|
||||
}
|
||||
|
||||
private static final class MediaSourceHolderSnapshot implements MediaSourceInfoHolder {
|
||||
|
||||
private final Object uid;
|
||||
|
@ -938,7 +938,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
/* isReadingAhead= */ playingPeriodHolder != queue.getReadingPeriod());
|
||||
long periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs);
|
||||
maybeTriggerPendingMessages(playbackInfo.positionUs, periodPositionUs);
|
||||
playbackInfo.positionUs = periodPositionUs;
|
||||
playbackInfo.updatePositionUs(periodPositionUs);
|
||||
}
|
||||
|
||||
// Update the buffered position and total buffered duration.
|
||||
@ -1486,6 +1486,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
/* bufferedPositionUs= */ startPositionUs,
|
||||
/* totalBufferedDurationUs= */ 0,
|
||||
/* positionUs= */ startPositionUs,
|
||||
/* positionUpdateTimeMs= */ 0,
|
||||
/* sleepingForOffload= */ false);
|
||||
if (releaseMediaSourceList) {
|
||||
mediaSourceList.release();
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.Player.PlaybackSuppressionReason;
|
||||
@ -22,6 +23,7 @@ import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.List;
|
||||
|
||||
@ -88,6 +90,11 @@ import java.util.List;
|
||||
* in the {@link #timeline}, in microseconds.
|
||||
*/
|
||||
public volatile long positionUs;
|
||||
/**
|
||||
* The value of {@link SystemClock#elapsedRealtime()} when {@link #positionUs} was updated, in
|
||||
* milliseconds.
|
||||
*/
|
||||
public volatile long positionUpdateTimeMs;
|
||||
|
||||
/**
|
||||
* Creates an empty placeholder playback info which can be used for masking as long as no real
|
||||
@ -116,6 +123,7 @@ import java.util.List;
|
||||
/* bufferedPositionUs= */ 0,
|
||||
/* totalBufferedDurationUs= */ 0,
|
||||
/* positionUs= */ 0,
|
||||
/* positionUpdateTimeMs= */ 0,
|
||||
/* sleepingForOffload= */ false);
|
||||
}
|
||||
|
||||
@ -138,6 +146,7 @@ import java.util.List;
|
||||
* @param bufferedPositionUs See {@link #bufferedPositionUs}.
|
||||
* @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}.
|
||||
* @param positionUs See {@link #positionUs}.
|
||||
* @param positionUpdateTimeMs See {@link #positionUpdateTimeMs}.
|
||||
* @param sleepingForOffload See {@link #sleepingForOffload}.
|
||||
*/
|
||||
public PlaybackInfo(
|
||||
@ -158,6 +167,7 @@ import java.util.List;
|
||||
long bufferedPositionUs,
|
||||
long totalBufferedDurationUs,
|
||||
long positionUs,
|
||||
long positionUpdateTimeMs,
|
||||
boolean sleepingForOffload) {
|
||||
this.timeline = timeline;
|
||||
this.periodId = periodId;
|
||||
@ -176,6 +186,7 @@ import java.util.List;
|
||||
this.bufferedPositionUs = bufferedPositionUs;
|
||||
this.totalBufferedDurationUs = totalBufferedDurationUs;
|
||||
this.positionUs = positionUs;
|
||||
this.positionUpdateTimeMs = positionUpdateTimeMs;
|
||||
this.sleepingForOffload = sleepingForOffload;
|
||||
}
|
||||
|
||||
@ -227,6 +238,7 @@ import java.util.List;
|
||||
bufferedPositionUs,
|
||||
totalBufferedDurationUs,
|
||||
positionUs,
|
||||
/* positionUpdateTimeMs= */ SystemClock.elapsedRealtime(),
|
||||
sleepingForOffload);
|
||||
}
|
||||
|
||||
@ -256,6 +268,7 @@ import java.util.List;
|
||||
bufferedPositionUs,
|
||||
totalBufferedDurationUs,
|
||||
positionUs,
|
||||
positionUpdateTimeMs,
|
||||
sleepingForOffload);
|
||||
}
|
||||
|
||||
@ -285,6 +298,7 @@ import java.util.List;
|
||||
bufferedPositionUs,
|
||||
totalBufferedDurationUs,
|
||||
positionUs,
|
||||
positionUpdateTimeMs,
|
||||
sleepingForOffload);
|
||||
}
|
||||
|
||||
@ -314,6 +328,7 @@ import java.util.List;
|
||||
bufferedPositionUs,
|
||||
totalBufferedDurationUs,
|
||||
positionUs,
|
||||
positionUpdateTimeMs,
|
||||
sleepingForOffload);
|
||||
}
|
||||
|
||||
@ -343,6 +358,7 @@ import java.util.List;
|
||||
bufferedPositionUs,
|
||||
totalBufferedDurationUs,
|
||||
positionUs,
|
||||
positionUpdateTimeMs,
|
||||
sleepingForOffload);
|
||||
}
|
||||
|
||||
@ -372,6 +388,7 @@ import java.util.List;
|
||||
bufferedPositionUs,
|
||||
totalBufferedDurationUs,
|
||||
positionUs,
|
||||
positionUpdateTimeMs,
|
||||
sleepingForOffload);
|
||||
}
|
||||
|
||||
@ -405,6 +422,7 @@ import java.util.List;
|
||||
bufferedPositionUs,
|
||||
totalBufferedDurationUs,
|
||||
positionUs,
|
||||
positionUpdateTimeMs,
|
||||
sleepingForOffload);
|
||||
}
|
||||
|
||||
@ -434,6 +452,7 @@ import java.util.List;
|
||||
bufferedPositionUs,
|
||||
totalBufferedDurationUs,
|
||||
positionUs,
|
||||
positionUpdateTimeMs,
|
||||
sleepingForOffload);
|
||||
}
|
||||
|
||||
@ -463,6 +482,99 @@ import java.util.List;
|
||||
bufferedPositionUs,
|
||||
totalBufferedDurationUs,
|
||||
positionUs,
|
||||
positionUpdateTimeMs,
|
||||
sleepingForOffload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies playback info with new estimated playing position.
|
||||
*
|
||||
* <p>Position is estimated with {@link #positionUs}, {@link #positionUpdateTimeMs}, and {@link
|
||||
* PlaybackParameters#speed}.
|
||||
*
|
||||
* @return Copied playback info with new, estimated playback position.
|
||||
*/
|
||||
@CheckResult
|
||||
public PlaybackInfo copyWithEstimatedPosition() {
|
||||
return new PlaybackInfo(
|
||||
timeline,
|
||||
periodId,
|
||||
requestedContentPositionUs,
|
||||
discontinuityStartPositionUs,
|
||||
playbackState,
|
||||
playbackError,
|
||||
isLoading,
|
||||
trackGroups,
|
||||
trackSelectorResult,
|
||||
staticMetadata,
|
||||
loadingMediaPeriodId,
|
||||
playWhenReady,
|
||||
playbackSuppressionReason,
|
||||
playbackParameters,
|
||||
bufferedPositionUs,
|
||||
totalBufferedDurationUs,
|
||||
getEstimatedPositionUs(),
|
||||
SystemClock.elapsedRealtime(),
|
||||
sleepingForOffload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets new playing position with update time of {@link SystemClock#elapsedRealtime()}, time
|
||||
* relative to the start of the associated period in the {@link #timeline}
|
||||
*
|
||||
* @param positionUs The new playing position.
|
||||
*/
|
||||
public void updatePositionUs(long positionUs) {
|
||||
// Write order of positionUs then positionUpdateTimeMs in order to be reverse of
|
||||
// retrieval in getExtrapolatedPositionUs().
|
||||
this.positionUs = positionUs;
|
||||
this.positionUpdateTimeMs = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves estimated position based on {@link #positionUs}, {@link #positionUpdateTimeMs}, and
|
||||
* {@link PlaybackParameters#speed}.
|
||||
*
|
||||
* <p>If not playing, then the estimated position is {@link #positionUs}.
|
||||
*
|
||||
* @return The estimated position.
|
||||
*/
|
||||
public long getEstimatedPositionUs() {
|
||||
if (!isPlaying()) {
|
||||
return this.positionUs;
|
||||
}
|
||||
|
||||
// Snapshot of volatile position info
|
||||
long positionUs;
|
||||
long positionUpdateTimeMs;
|
||||
do {
|
||||
// Read order of positionUpdateTimeMs then positionUs to be reverse of updatePositionUs write.
|
||||
positionUpdateTimeMs = this.positionUpdateTimeMs;
|
||||
positionUs = this.positionUs;
|
||||
} while (positionUpdateTimeMs != this.positionUpdateTimeMs);
|
||||
|
||||
long elapsedTimeMs = SystemClock.elapsedRealtime() - positionUpdateTimeMs;
|
||||
long estimatedPositionMs =
|
||||
Util.usToMs(positionUs) + (long) (elapsedTimeMs * playbackParameters.speed);
|
||||
return Util.msToUs(estimatedPositionMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this object represents a playing state.
|
||||
*
|
||||
* <p>Returns true if the following conditions are met:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #playbackState} is {@link Player#STATE_READY}
|
||||
* <li>{@link #playWhenReady} is true.
|
||||
* <li>{@link #playbackSuppressionReason} is {@link Player#PLAYBACK_SUPPRESSION_REASON_NONE}
|
||||
* </ul>
|
||||
*
|
||||
* @return Whether the playbackInfo represents a playing state.
|
||||
*/
|
||||
public boolean isPlaying() {
|
||||
return playbackState == Player.STATE_READY
|
||||
&& playWhenReady
|
||||
&& playbackSuppressionReason == Player.PLAYBACK_SUPPRESSION_REASON_NONE;
|
||||
}
|
||||
}
|
||||
|
@ -9865,6 +9865,82 @@ public final class ExoPlayerTest {
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableOffloadScheduling_duringSleepGetCurrentPosition_returnsEstimatedPosition()
|
||||
throws Exception {
|
||||
FakeClock fakeClock =
|
||||
new FakeClock(/* initialTimeMs= */ 987_654_321L, /* isAutoAdvancing= */ true);
|
||||
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO);
|
||||
ExoPlayer player =
|
||||
new TestExoPlayerBuilder(context).setClock(fakeClock).setRenderers(sleepRenderer).build();
|
||||
Timeline timeline = new FakeTimeline();
|
||||
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
|
||||
player.experimentalSetOffloadSchedulingEnabled(true);
|
||||
player.prepare();
|
||||
player.play();
|
||||
sleepRenderer.sleepOnNextRender();
|
||||
runUntilSleepingForOffload(player, /* expectedSleepForOffload= */ true);
|
||||
|
||||
long currentPosition = player.getCurrentPosition();
|
||||
fakeClock.advanceTime(/* timeDiffMs= */ 800);
|
||||
long newPosition = player.getCurrentPosition();
|
||||
|
||||
assertThat(newPosition - currentPosition).isNotEqualTo(0);
|
||||
assertThat(newPosition).isEqualTo(800);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableOffloadScheduling_pauseAndSeekDuringSleep_currentPositionIsSeekedPosition()
|
||||
throws Exception {
|
||||
FakeClock fakeClock =
|
||||
new FakeClock(/* initialTimeMs= */ 987_654_321L, /* isAutoAdvancing= */ true);
|
||||
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO);
|
||||
ExoPlayer player =
|
||||
new TestExoPlayerBuilder(context).setClock(fakeClock).setRenderers(sleepRenderer).build();
|
||||
Timeline timeline = new FakeTimeline();
|
||||
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
|
||||
player.experimentalSetOffloadSchedulingEnabled(true);
|
||||
player.prepare();
|
||||
player.play();
|
||||
sleepRenderer.sleepOnNextRender();
|
||||
runUntilSleepingForOffload(player, /* expectedSleepForOffload= */ true);
|
||||
|
||||
// Pause, advance clock and then seek.
|
||||
player.pause();
|
||||
fakeClock.advanceTime(/* timeDiffMs= */ 1000);
|
||||
player.seekTo(800);
|
||||
long currentPosition = player.getCurrentPosition();
|
||||
|
||||
assertThat(currentPosition).isEqualTo(800);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableOffloadScheduling_seekThenPauseDuringSleep_returnsEstimatePositionByPauseTime()
|
||||
throws Exception {
|
||||
FakeClock fakeClock =
|
||||
new FakeClock(/* initialTimeMs= */ 987_654_321L, /* isAutoAdvancing= */ true);
|
||||
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO);
|
||||
ExoPlayer player =
|
||||
new TestExoPlayerBuilder(context).setClock(fakeClock).setRenderers(sleepRenderer).build();
|
||||
Timeline timeline = new FakeTimeline();
|
||||
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
|
||||
player.experimentalSetOffloadSchedulingEnabled(true);
|
||||
player.prepare();
|
||||
player.play();
|
||||
sleepRenderer.sleepOnNextRender();
|
||||
runUntilSleepingForOffload(player, /* expectedSleepForOffload= */ true);
|
||||
|
||||
// Seek, advance clock, then pause.
|
||||
player.seekTo(800);
|
||||
sleepRenderer.sleepOnNextRender();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
fakeClock.advanceTime(/* timeDiffMs= */ 1000);
|
||||
player.pause();
|
||||
long currentPosition = player.getCurrentPosition();
|
||||
|
||||
assertThat(currentPosition).isEqualTo(1800);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void targetLiveOffsetInMedia_adjustsLiveOffsetToTargetOffset() throws Exception {
|
||||
long windowStartUnixTimeMs = 987_654_321_000L;
|
||||
|
@ -1356,6 +1356,7 @@ public final class MediaPeriodQueueTest {
|
||||
/* bufferedPositionUs= */ 0,
|
||||
/* totalBufferedDurationUs= */ 0,
|
||||
/* positionUs= */ 0,
|
||||
/* positionUpdateTimeMs= */ 0,
|
||||
/* sleepingForOffload= */ false);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user