Commit 9de0123e by tonihei Committed by Oliver Woodman

When seeking while player is idle, ensure EPII's position is in sync with EPI.

As soon as the seek gets acknowledged by EPII, EPI returns the actual position
from the playback info again which is set by EPII. Thus, EPII needs to update
the position to reflect the changes expected by EPI.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=182515106
parent 32f8c2e9
...@@ -370,9 +370,22 @@ public final class ExoPlayerTest extends TestCase { ...@@ -370,9 +370,22 @@ public final class ExoPlayerTest extends TestCase {
playbackStatesWhenSeekProcessed.add(currentPlaybackState); playbackStatesWhenSeekProcessed.add(currentPlaybackState);
} }
}; };
new ExoPlayerTestRunner.Builder() ExoPlayerTestRunner testRunner =
.setTimeline(timeline).setEventListener(eventListener).setActionSchedule(actionSchedule) new ExoPlayerTestRunner.Builder()
.build().start().blockUntilEnded(TIMEOUT_MS); .setTimeline(timeline)
.setEventListener(eventListener)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityReasonsEqual(
Player.DISCONTINUITY_REASON_SEEK,
Player.DISCONTINUITY_REASON_SEEK,
Player.DISCONTINUITY_REASON_SEEK,
Player.DISCONTINUITY_REASON_SEEK,
Player.DISCONTINUITY_REASON_PERIOD_TRANSITION,
Player.DISCONTINUITY_REASON_SEEK,
Player.DISCONTINUITY_REASON_SEEK);
assertEquals(4, playbackStatesWhenSeekProcessed.size()); assertEquals(4, playbackStatesWhenSeekProcessed.size());
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(0)); assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(0));
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(1)); assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(1));
...@@ -914,6 +927,61 @@ public final class ExoPlayerTest extends TestCase { ...@@ -914,6 +927,61 @@ public final class ExoPlayerTest extends TestCase {
Player.TIMELINE_CHANGE_REASON_PREPARED, Player.TIMELINE_CHANGE_REASON_PREPARED); Player.TIMELINE_CHANGE_REASON_PREPARED, Player.TIMELINE_CHANGE_REASON_PREPARED);
} }
public void testSeekAndReprepareAfterPlaybackError() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[2];
ActionSchedule actionSchedule =
new ActionSchedule.Builder("testReprepareAfterPlaybackError")
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
// Cause an internal exception by seeking to an invalid position while the media source
// is still being prepared and the player doesn't immediately know it will fail.
.seek(/* windowIndex= */ 100, /* positionMs= */ 0)
.waitForSeekProcessed()
.waitForPlaybackState(Player.STATE_IDLE)
.seek(/* positionMs= */ 50)
.waitForSeekProcessed()
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
positionHolder[0] = player.getCurrentPosition();
}
})
.prepareSource(
new FakeMediaSource(timeline, /* manifest= */ null),
/* resetPosition= */ false,
/* resetState= */ false)
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
positionHolder[1] = player.getCurrentPosition();
}
})
.play()
.build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder()
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
.build();
try {
testRunner.start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
fail();
} catch (ExoPlaybackException e) {
// Expected exception.
}
testRunner.assertTimelinesEqual(timeline, timeline);
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PREPARED, Player.TIMELINE_CHANGE_REASON_PREPARED);
testRunner.assertPositionDiscontinuityReasonsEqual(
Player.DISCONTINUITY_REASON_SEEK, Player.DISCONTINUITY_REASON_SEEK);
assertEquals(50, positionHolder[0]);
assertEquals(50, positionHolder[1]);
}
public void testPlaybackErrorDuringSourceInfoRefreshStillUpdatesTimeline() throws Exception { public void testPlaybackErrorDuringSourceInfoRefreshStillUpdatesTimeline() throws Exception {
final Timeline timeline = new FakeTimeline(/* windowCount= */ 1); final Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final FakeMediaSource mediaSource = final FakeMediaSource mediaSource =
......
...@@ -629,38 +629,48 @@ import java.util.Collections; ...@@ -629,38 +629,48 @@ import java.util.Collections;
} }
private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException { private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
Timeline timeline = playbackInfo.timeline; Timeline timeline = playbackInfo.timeline;
if (mediaSource == null || timeline == null) { playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
pendingInitialSeekPosition = seekPosition;
return; MediaPeriodId periodId;
long periodPositionUs;
long contentPositionUs;
boolean seekPositionAdjusted;
Pair<Integer, Long> resolvedSeekPosition =
resolveSeekPosition(seekPosition, /* trySubsequentPeriods= */ true);
if (resolvedSeekPosition == null) {
// The seek position was valid for the timeline that it was performed into, but the
// timeline has changed or is not ready and a suitable seek position could not be resolved.
periodId = new MediaPeriodId(getFirstPeriodIndex());
periodPositionUs = C.TIME_UNSET;
contentPositionUs = C.TIME_UNSET;
seekPositionAdjusted = true;
} else {
// Update the resolved seek position to take ads into account.
periodId =
mediaPeriodInfoSequence.resolvePeriodPositionForAds(
resolvedSeekPosition.first, resolvedSeekPosition.second);
contentPositionUs = resolvedSeekPosition.second;
if (periodId.isAd()) {
periodPositionUs = 0;
seekPositionAdjusted = true;
} else {
periodPositionUs = resolvedSeekPosition.second;
seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
}
} }
boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
try { try {
Pair<Integer, Long> periodPosition = if (mediaSource == null || timeline == null) {
resolveSeekPosition(seekPosition, /* trySubsequentPeriods= */ true); // Save seek position for later, as we are still waiting for a prepared source.
if (periodPosition == null) { pendingInitialSeekPosition = seekPosition;
// The seek position was valid for the timeline that it was performed into, but the } else if (periodPositionUs == C.TIME_UNSET) {
// timeline has changed and a suitable seek position could not be resolved in the new one. // End playback, as we didn't manage to find a valid seek position.
setState(Player.STATE_ENDED); setState(Player.STATE_ENDED);
// Reset, but retain the source so that it can still be used should a seek occur.
resetInternal( resetInternal(
/* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false);
seekPositionAdjusted = true; } else {
return; // Execute the seek in the current media periods.
}
int periodIndex = periodPosition.first;
long periodPositionUs = periodPosition.second;
long contentPositionUs = periodPositionUs;
MediaPeriodId periodId =
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, periodPositionUs);
if (periodId.isAd()) {
seekPositionAdjusted = true;
periodPositionUs = 0;
}
try {
long newPeriodPositionUs = periodPositionUs; long newPeriodPositionUs = periodPositionUs;
if (periodId.equals(playbackInfo.periodId)) { if (periodId.equals(playbackInfo.periodId)) {
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
...@@ -669,7 +679,7 @@ import java.util.Collections; ...@@ -669,7 +679,7 @@ import java.util.Collections;
playingPeriodHolder.mediaPeriod.getAdjustedSeekPositionUs( playingPeriodHolder.mediaPeriod.getAdjustedSeekPositionUs(
newPeriodPositionUs, seekParameters); newPeriodPositionUs, seekParameters);
} }
if ((newPeriodPositionUs / 1000) == (playbackInfo.positionUs / 1000)) { if (C.usToMs(newPeriodPositionUs) == C.usToMs(playbackInfo.positionUs)) {
// Seek will be performed to the current position. Do nothing. // Seek will be performed to the current position. Do nothing.
periodPositionUs = playbackInfo.positionUs; periodPositionUs = playbackInfo.positionUs;
return; return;
...@@ -678,10 +688,9 @@ import java.util.Collections; ...@@ -678,10 +688,9 @@ import java.util.Collections;
newPeriodPositionUs = seekToPeriodPosition(periodId, newPeriodPositionUs); newPeriodPositionUs = seekToPeriodPosition(periodId, newPeriodPositionUs);
seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs; seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs;
periodPositionUs = newPeriodPositionUs; periodPositionUs = newPeriodPositionUs;
} finally {
playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs);
} }
} finally { } finally {
playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs);
if (seekPositionAdjusted) { if (seekPositionAdjusted) {
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
} }
...@@ -795,6 +804,14 @@ import java.util.Collections; ...@@ -795,6 +804,14 @@ import java.util.Collections;
} }
} }
private int getFirstPeriodIndex() {
Timeline timeline = playbackInfo.timeline;
return timeline == null || timeline.isEmpty()
? 0
: timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
.firstPeriodIndex;
}
private void resetInternal( private void resetInternal(
boolean releaseMediaSource, boolean resetPosition, boolean resetState) { boolean releaseMediaSource, boolean resetPosition, boolean resetState) {
handler.removeMessages(MSG_DO_SOME_WORK); handler.removeMessages(MSG_DO_SOME_WORK);
...@@ -812,12 +829,6 @@ import java.util.Collections; ...@@ -812,12 +829,6 @@ import java.util.Collections;
enabledRenderers = new Renderer[0]; enabledRenderers = new Renderer[0];
queue.clear(); queue.clear();
setIsLoading(false); setIsLoading(false);
Timeline timeline = playbackInfo.timeline;
int firstPeriodIndex =
timeline == null || timeline.isEmpty()
? 0
: timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
.firstPeriodIndex;
if (resetPosition) { if (resetPosition) {
pendingInitialSeekPosition = null; pendingInitialSeekPosition = null;
} }
...@@ -833,7 +844,7 @@ import java.util.Collections; ...@@ -833,7 +844,7 @@ import java.util.Collections;
new PlaybackInfo( new PlaybackInfo(
resetState ? null : playbackInfo.timeline, resetState ? null : playbackInfo.timeline,
resetState ? null : playbackInfo.manifest, resetState ? null : playbackInfo.manifest,
resetPosition ? new MediaPeriodId(firstPeriodIndex) : playbackInfo.periodId, resetPosition ? new MediaPeriodId(getFirstPeriodIndex()) : playbackInfo.periodId,
// Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored. // Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored.
resetPosition ? C.TIME_UNSET : playbackInfo.startPositionUs, resetPosition ? C.TIME_UNSET : playbackInfo.startPositionUs,
resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs, resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs,
...@@ -1334,8 +1345,12 @@ import java.util.Collections; ...@@ -1334,8 +1345,12 @@ import java.util.Collections;
SeekPosition seekPosition, boolean trySubsequentPeriods) { SeekPosition seekPosition, boolean trySubsequentPeriods) {
Timeline timeline = playbackInfo.timeline; Timeline timeline = playbackInfo.timeline;
Timeline seekTimeline = seekPosition.timeline; Timeline seekTimeline = seekPosition.timeline;
if (timeline == null) {
// We don't have a timeline yet, so we can't resolve the position.
return null;
}
if (seekTimeline.isEmpty()) { if (seekTimeline.isEmpty()) {
// The application performed a blind seek without a non-empty timeline (most likely based on // The application performed a blind seek with an empty timeline (most likely based on
// knowledge of what the future timeline will be). Use the internal timeline. // knowledge of what the future timeline will be). Use the internal timeline.
seekTimeline = timeline; seekTimeline = timeline;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment