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

View File

@ -450,8 +450,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
int prepareOrStopAcks = msg.arg1;
int seekAcks = msg.arg2;
handlePlaybackInfo((PlaybackInfo) msg.obj, prepareOrStopAcks, seekAcks, false,
handlePlaybackInfo((PlaybackInfo) msg.obj, prepareOrStopAcks, 0, false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL);
break;
}
@ -510,13 +509,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
boolean timelineOrManifestChanged = this.playbackInfo.timeline != playbackInfo.timeline
|| this.playbackInfo.manifest != playbackInfo.manifest;
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 (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.TIMELINE_CHANGE_REASON_PREPARED : Player.TIMELINE_CHANGE_REASON_DYNAMIC;
waitingForInitialTimeline = false;

View File

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