提交 9de0123e 编写于 作者: T tonihei 提交者: 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
上级 32f8c2e9
......@@ -370,9 +370,22 @@ public final class ExoPlayerTest extends TestCase {
playbackStatesWhenSeekProcessed.add(currentPlaybackState);
}
};
new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setEventListener(eventListener).setActionSchedule(actionSchedule)
.build().start().blockUntilEnded(TIMEOUT_MS);
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder()
.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(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(0));
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(1));
......@@ -914,6 +927,61 @@ public final class ExoPlayerTest extends TestCase {
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 {
final Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final FakeMediaSource mediaSource =
......
......@@ -629,38 +629,48 @@ import java.util.Collections;
}
private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
Timeline timeline = playbackInfo.timeline;
if (mediaSource == null || timeline == null) {
pendingInitialSeekPosition = seekPosition;
return;
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
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 {
Pair<Integer, Long> periodPosition =
resolveSeekPosition(seekPosition, /* trySubsequentPeriods= */ true);
if (periodPosition == null) {
// The seek position was valid for the timeline that it was performed into, but the
// timeline has changed and a suitable seek position could not be resolved in the new one.
if (mediaSource == null || timeline == null) {
// Save seek position for later, as we are still waiting for a prepared source.
pendingInitialSeekPosition = seekPosition;
} else if (periodPositionUs == C.TIME_UNSET) {
// End playback, as we didn't manage to find a valid seek position.
setState(Player.STATE_ENDED);
// Reset, but retain the source so that it can still be used should a seek occur.
resetInternal(
/* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false);
seekPositionAdjusted = true;
return;
}
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 {
} else {
// Execute the seek in the current media periods.
long newPeriodPositionUs = periodPositionUs;
if (periodId.equals(playbackInfo.periodId)) {
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
......@@ -669,7 +679,7 @@ import java.util.Collections;
playingPeriodHolder.mediaPeriod.getAdjustedSeekPositionUs(
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.
periodPositionUs = playbackInfo.positionUs;
return;
......@@ -678,10 +688,9 @@ import java.util.Collections;
newPeriodPositionUs = seekToPeriodPosition(periodId, newPeriodPositionUs);
seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs;
periodPositionUs = newPeriodPositionUs;
} finally {
playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs);
}
} finally {
playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs);
if (seekPositionAdjusted) {
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
}
......@@ -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(
boolean releaseMediaSource, boolean resetPosition, boolean resetState) {
handler.removeMessages(MSG_DO_SOME_WORK);
......@@ -812,12 +829,6 @@ import java.util.Collections;
enabledRenderers = new Renderer[0];
queue.clear();
setIsLoading(false);
Timeline timeline = playbackInfo.timeline;
int firstPeriodIndex =
timeline == null || timeline.isEmpty()
? 0
: timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
.firstPeriodIndex;
if (resetPosition) {
pendingInitialSeekPosition = null;
}
......@@ -833,7 +844,7 @@ import java.util.Collections;
new PlaybackInfo(
resetState ? null : playbackInfo.timeline,
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.
resetPosition ? C.TIME_UNSET : playbackInfo.startPositionUs,
resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs,
......@@ -1334,8 +1345,12 @@ import java.util.Collections;
SeekPosition seekPosition, boolean trySubsequentPeriods) {
Timeline timeline = playbackInfo.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()) {
// 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.
seekTimeline = timeline;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册