Remove initial seek counting in ExoPlayerImplInternal.

We can acknoledge seeks before preparation finished immediately now,
because ExoPlayerImpl won't leave the masking state until the first prepare
operation is processed.

As a side effect, it also cleans up the responsibility of the callbacks.
Prepares are always acknowledged with a SOURCE_INFO_REFRESHED, while seeks
are always acknowledged with a SEEK_ACK.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=177144089
This commit is contained in:
tonihei 2017-11-28 04:40:06 -08:00 committed by Oliver Woodman
parent 1ae50cb9e5
commit efc709f366
3 changed files with 49 additions and 43 deletions

View file

@ -306,11 +306,19 @@ public final class ExoPlayerTest extends TestCase {
public void testSeekProcessedCallback() throws Exception { public void testSeekProcessedCallback() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2); Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
ActionSchedule actionSchedule = new ActionSchedule.Builder("testSeekProcessedCallback") ActionSchedule actionSchedule = new ActionSchedule.Builder("testSeekProcessedCallback")
// Initial seek before timeline preparation finished. // Initial seek before timeline preparation started. Expect immediate seek processed while
.pause().seek(10).waitForPlaybackState(Player.STATE_READY) // the player is still in STATE_IDLE.
// Re-seek to same position, start playback and wait until playback reaches second window. .pause().seek(5)
.seek(10).play().waitForPositionDiscontinuity() // Wait until the media source starts preparing and issue more initial seeks. Expect only
// Seek twice in concession, expecting the first seek to be replaced. // one seek processed after the source has been prepared.
.waitForPlaybackState(Player.STATE_BUFFERING).seek(2).seek(10)
// Wait until media source prepared and re-seek to same position. Expect a seek processed
// while still being in STATE_READY.
.waitForPlaybackState(Player.STATE_READY).seek(10)
// Start playback and wait until playback reaches second window.
.play().waitForPositionDiscontinuity()
// Seek twice in concession, expecting the first seek to be replaced (and thus except only
// on seek processed callback).
.seek(5).seek(60).build(); .seek(5).seek(60).build();
final List<Integer> playbackStatesWhenSeekProcessed = new ArrayList<>(); final List<Integer> playbackStatesWhenSeekProcessed = new ArrayList<>();
Player.EventListener eventListener = new Player.DefaultEventListener() { Player.EventListener eventListener = new Player.DefaultEventListener() {
@ -329,10 +337,11 @@ public final class ExoPlayerTest extends TestCase {
new ExoPlayerTestRunner.Builder() new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setEventListener(eventListener).setActionSchedule(actionSchedule) .setTimeline(timeline).setEventListener(eventListener).setActionSchedule(actionSchedule)
.build().start().blockUntilEnded(TIMEOUT_MS); .build().start().blockUntilEnded(TIMEOUT_MS);
assertEquals(3, playbackStatesWhenSeekProcessed.size()); assertEquals(4, playbackStatesWhenSeekProcessed.size());
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(0)); assertEquals(Player.STATE_IDLE, (int) playbackStatesWhenSeekProcessed.get(0));
assertEquals(Player.STATE_READY, (int) playbackStatesWhenSeekProcessed.get(1)); assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(1));
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(2)); assertEquals(Player.STATE_READY, (int) playbackStatesWhenSeekProcessed.get(2));
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(3));
} }
public void testSeekDiscontinuity() throws Exception { public void testSeekDiscontinuity() throws Exception {
@ -742,7 +751,7 @@ public final class ExoPlayerTest extends TestCase {
.waitForPlaybackState(Player.STATE_IDLE) .waitForPlaybackState(Player.STATE_IDLE)
// If we were still using the first timeline, this would throw. // If we were still using the first timeline, this would throw.
.seek(/* windowIndex= */ 1, /* positionMs= */ 0) .seek(/* windowIndex= */ 1, /* positionMs= */ 0)
.prepareSource(secondSource) .prepareSource(secondSource, /* resetPosition= */ false, /* resetState= */ true)
.build(); .build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder() ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline) .setTimeline(timeline)

View file

@ -450,8 +450,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: { case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
int prepareOrStopAcks = msg.arg1; int prepareOrStopAcks = msg.arg1;
int seekAcks = msg.arg2; handlePlaybackInfo((PlaybackInfo) msg.obj, prepareOrStopAcks, 0, false,
handlePlaybackInfo((PlaybackInfo) msg.obj, prepareOrStopAcks, seekAcks, false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL); /* ignored */ DISCONTINUITY_REASON_INTERNAL);
break; break;
} }
@ -510,13 +509,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
boolean timelineOrManifestChanged = this.playbackInfo.timeline != playbackInfo.timeline boolean timelineOrManifestChanged = this.playbackInfo.timeline != playbackInfo.timeline
|| this.playbackInfo.manifest != playbackInfo.manifest; || this.playbackInfo.manifest != playbackInfo.manifest;
this.playbackInfo = playbackInfo; this.playbackInfo = playbackInfo;
if (playbackInfo.timeline.isEmpty()) {
// Update the masking variables, which are used when the timeline is empty.
maskingPeriodIndex = 0;
maskingWindowIndex = 0;
maskingWindowPositionMs = 0;
}
if (timelineOrManifestChanged || waitingForInitialTimeline) { if (timelineOrManifestChanged || waitingForInitialTimeline) {
if (playbackInfo.timeline.isEmpty()) {
// Update the masking variables, which are used when the timeline becomes empty.
maskingPeriodIndex = 0;
maskingWindowIndex = 0;
maskingWindowPositionMs = 0;
}
@Player.TimelineChangeReason int reason = waitingForInitialTimeline @Player.TimelineChangeReason int reason = waitingForInitialTimeline
? Player.TIMELINE_CHANGE_REASON_PREPARED : Player.TIMELINE_CHANGE_REASON_DYNAMIC; ? Player.TIMELINE_CHANGE_REASON_PREPARED : Player.TIMELINE_CHANGE_REASON_DYNAMIC;
waitingForInitialTimeline = false; waitingForInitialTimeline = false;

View file

@ -125,8 +125,7 @@ import java.io.IOException;
private long elapsedRealtimeUs; private long elapsedRealtimeUs;
private int pendingPrepareCount; private int pendingPrepareCount;
private int pendingInitialSeekCount; private SeekPosition pendingInitialSeekPosition;
private SeekPosition pendingSeekPosition;
private long rendererPositionUs; private long rendererPositionUs;
private MediaPeriodHolder loadingPeriodHolder; private MediaPeriodHolder loadingPeriodHolder;
@ -631,8 +630,9 @@ import java.io.IOException;
private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException { private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException {
Timeline timeline = playbackInfo.timeline; Timeline timeline = playbackInfo.timeline;
if (timeline == null) { if (timeline == null) {
pendingInitialSeekCount++; pendingInitialSeekPosition = seekPosition;
pendingSeekPosition = seekPosition; eventHandler.obtainMessage(MSG_SEEK_ACK, /* seekAdjusted = */ 0, 0,
playbackInfo.copyWithTimeline(Timeline.EMPTY, null)).sendToTarget();
return; return;
} }
@ -781,7 +781,7 @@ import java.io.IOException;
} }
int prepareOrStopAcks = pendingPrepareCount + 1; int prepareOrStopAcks = pendingPrepareCount + 1;
pendingPrepareCount = 0; pendingPrepareCount = 0;
notifySourceInfoRefresh(prepareOrStopAcks, 0, publicPlaybackInfo); notifySourceInfoRefresh(prepareOrStopAcks, publicPlaybackInfo);
loadControl.onStopped(); loadControl.onStopped();
setState(Player.STATE_IDLE); setState(Player.STATE_IDLE);
} }
@ -825,6 +825,7 @@ import java.io.IOException;
? 0 ? 0
: timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window) : timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
.firstPeriodIndex; .firstPeriodIndex;
pendingInitialSeekPosition = null;
playbackInfo = playbackInfo.fromNewPosition(firstPeriodIndex, C.TIME_UNSET, C.TIME_UNSET); playbackInfo = playbackInfo.fromNewPosition(firstPeriodIndex, C.TIME_UNSET, C.TIME_UNSET);
} else { } else {
// The new start position is the current playback position. // The new start position is the current playback position.
@ -1009,15 +1010,13 @@ import java.io.IOException;
if (oldTimeline == null) { if (oldTimeline == null) {
int processedPrepareAcks = pendingPrepareCount; int processedPrepareAcks = pendingPrepareCount;
pendingPrepareCount = 0; pendingPrepareCount = 0;
if (pendingInitialSeekCount > 0) { if (pendingInitialSeekPosition != null) {
Pair<Integer, Long> periodPosition = resolveSeekPosition(pendingSeekPosition); Pair<Integer, Long> periodPosition = resolveSeekPosition(pendingInitialSeekPosition);
int processedInitialSeekCount = pendingInitialSeekCount; pendingInitialSeekPosition = null;
pendingInitialSeekCount = 0;
pendingSeekPosition = null;
if (periodPosition == null) { if (periodPosition == null) {
// The seek position was valid for the timeline that it was performed into, but the // 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. // timeline has changed and a suitable seek position could not be resolved in the new one.
handleSourceInfoRefreshEndedPlayback(processedPrepareAcks, processedInitialSeekCount); handleSourceInfoRefreshEndedPlayback(processedPrepareAcks);
} else { } else {
int periodIndex = periodPosition.first; int periodIndex = periodPosition.first;
long positionUs = periodPosition.second; long positionUs = periodPosition.second;
@ -1025,11 +1024,11 @@ import java.io.IOException;
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs); mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs);
playbackInfo = playbackInfo.fromNewPosition(periodId, periodId.isAd() ? 0 : positionUs, playbackInfo = playbackInfo.fromNewPosition(periodId, periodId.isAd() ? 0 : positionUs,
positionUs); positionUs);
notifySourceInfoRefresh(processedPrepareAcks, processedInitialSeekCount); notifySourceInfoRefresh(processedPrepareAcks);
} }
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) { } else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
if (timeline.isEmpty()) { if (timeline.isEmpty()) {
handleSourceInfoRefreshEndedPlayback(processedPrepareAcks, 0); handleSourceInfoRefreshEndedPlayback(processedPrepareAcks);
} else { } else {
Pair<Integer, Long> defaultPosition = getPeriodPosition(timeline, Pair<Integer, Long> defaultPosition = getPeriodPosition(timeline,
timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET); timeline.getFirstWindowIndex(shuffleModeEnabled), C.TIME_UNSET);
@ -1039,10 +1038,10 @@ import java.io.IOException;
startPositionUs); startPositionUs);
playbackInfo = playbackInfo.fromNewPosition(periodId, playbackInfo = playbackInfo.fromNewPosition(periodId,
periodId.isAd() ? 0 : startPositionUs, startPositionUs); periodId.isAd() ? 0 : startPositionUs, startPositionUs);
notifySourceInfoRefresh(processedPrepareAcks, 0); notifySourceInfoRefresh(processedPrepareAcks);
} }
} else { } else {
notifySourceInfoRefresh(processedPrepareAcks, 0); notifySourceInfoRefresh(processedPrepareAcks);
} }
return; return;
} }
@ -1169,30 +1168,29 @@ import java.io.IOException;
} }
private void handleSourceInfoRefreshEndedPlayback() { private void handleSourceInfoRefreshEndedPlayback() {
handleSourceInfoRefreshEndedPlayback(0, 0); handleSourceInfoRefreshEndedPlayback(0);
} }
private void handleSourceInfoRefreshEndedPlayback(int prepareAcks, int seekAcks) { private void handleSourceInfoRefreshEndedPlayback(int prepareAcks) {
setState(Player.STATE_ENDED); setState(Player.STATE_ENDED);
// Reset, but retain the source so that it can still be used should a seek occur. // Reset, but retain the source so that it can still be used should a seek occur.
resetInternal(false, true); resetInternal(false, true);
// Set the playback position to 0 for notifying the eventHandler (instead of C.TIME_UNSET). // Set the playback position to 0 for notifying the eventHandler (instead of C.TIME_UNSET).
notifySourceInfoRefresh(prepareAcks, seekAcks, notifySourceInfoRefresh(prepareAcks,
playbackInfo.fromNewPosition(playbackInfo.periodId.periodIndex, 0, C.TIME_UNSET)); playbackInfo.fromNewPosition(playbackInfo.periodId.periodIndex, 0, C.TIME_UNSET));
} }
private void notifySourceInfoRefresh() { private void notifySourceInfoRefresh() {
notifySourceInfoRefresh(0, 0); notifySourceInfoRefresh(0);
} }
private void notifySourceInfoRefresh(int prepareOrStopAcks, int seekAcks) { private void notifySourceInfoRefresh(int prepareOrStopAcks) {
notifySourceInfoRefresh(prepareOrStopAcks, seekAcks, playbackInfo); notifySourceInfoRefresh(prepareOrStopAcks, playbackInfo);
} }
private void notifySourceInfoRefresh(int prepareOrStopAcks, int seekAcks, private void notifySourceInfoRefresh(int prepareOrStopAcks, PlaybackInfo playbackInfo) {
PlaybackInfo playbackInfo) { eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, prepareOrStopAcks, 0, playbackInfo)
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, prepareOrStopAcks, seekAcks, .sendToTarget();
playbackInfo).sendToTarget();
} }
/** /**