Unify internal reset method to support state and position resets.

The ExoPlayerImplInternal.reset method now takes the same set of options
as the ExoPlayer.prepare method. This also allows to
- Remove some code duplication within ExoPlayerImplInternal
- Fix calls to prepare(sameSource, resetPosition=true, resetState=false)
  with enabled shuffle mode where the position was not correctly reset to the
  first period index.
- Keep the current timeline when calling stop (in line with ExoPlayerImpl).

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=176481878
This commit is contained in:
tonihei 2017-11-21 02:13:04 -08:00 committed by Oliver Woodman
parent fdb53ac8d1
commit e1d960db68
2 changed files with 55 additions and 31 deletions

View file

@ -17,7 +17,6 @@ package com.google.android.exoplayer2;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.Listener;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.testutil.ActionSchedule;
@ -541,4 +540,30 @@ public final class ExoPlayerTest extends TestCase {
Player.TIMELINE_CHANGE_REASON_DYNAMIC);
}
public void testRepreparationWithPositionResetAndShufflingUsesFirstPeriod() throws Exception {
Timeline fakeTimeline = new FakeTimeline(new TimelineWindowDefinition(/* isSeekable= */ true,
/* isDynamic= */ false, /* durationUs= */ 100000));
ConcatenatingMediaSource firstMediaSource = new ConcatenatingMediaSource(/* isAtomic= */ false,
new FakeShuffleOrder(/* length= */ 2),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT)
);
ConcatenatingMediaSource secondMediaSource = new ConcatenatingMediaSource(/* isAtomic= */ false,
new FakeShuffleOrder(/* length= */ 2),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT),
new FakeMediaSource(fakeTimeline, null, Builder.VIDEO_FORMAT)
);
ActionSchedule actionSchedule = new ActionSchedule.Builder("testRepreparationWithShuffle")
// Wait for first preparation and enable shuffling. Plays period 0.
.waitForPlaybackState(Player.STATE_READY).setShuffleModeEnabled(true)
// Reprepare with second media source (keeping state, but with position reset).
// Plays period 1 and 0 because of the reversed fake shuffle order.
.prepareSource(secondMediaSource, /* resetPosition= */ true, /* resetState= */ false)
.build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setMediaSource(firstMediaSource).setActionSchedule(actionSchedule)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPlayedPeriodIndices(0, 1, 0);
}
}

View file

@ -393,17 +393,10 @@ import java.io.IOException;
private void prepareInternal(MediaSource mediaSource, boolean resetPosition) {
pendingPrepareCount++;
resetInternal(true);
resetInternal(/* releaseMediaSource= */ true, resetPosition);
loadControl.onPrepared();
if (resetPosition) {
playbackInfo = new PlaybackInfo(null, null, 0, C.TIME_UNSET);
} else {
// The new start position is the current playback position.
playbackInfo = new PlaybackInfo(null, null, playbackInfo.periodId, playbackInfo.positionUs,
playbackInfo.contentPositionUs);
}
this.mediaSource = mediaSource;
mediaSource.prepareSource(player, true, this);
mediaSource.prepareSource(player, /* isTopLevelSource= */ true, /* listener = */ this);
setState(Player.STATE_BUFFERING);
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
@ -638,18 +631,16 @@ import java.io.IOException;
Pair<Integer, Long> periodPosition = resolveSeekPosition(seekPosition);
if (periodPosition == null) {
int firstPeriodIndex = timeline.isEmpty() ? 0 : timeline.getWindow(
timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex;
// 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.
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
// (firstPeriodIndex,0) isn't ignored.
playbackInfo = playbackInfo.fromNewPosition(firstPeriodIndex, C.TIME_UNSET, C.TIME_UNSET);
setState(Player.STATE_ENDED);
eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0,
playbackInfo.fromNewPosition(firstPeriodIndex, 0, C.TIME_UNSET)).sendToTarget();
// Reset, but retain the source so that it can still be used should a seek occur.
resetInternal(false);
resetInternal(false, true);
// Set the playback position to 0 for notifying the eventHandler (instead of C.TIME_UNSET).
eventHandler.obtainMessage(MSG_SEEK_ACK, /* seekAdjusted = */ 1, 0,
playbackInfo.fromNewPosition(playbackInfo.periodId.periodIndex, /* startPositionUs = */ 0,
/* contentPositionUs= */ C.TIME_UNSET))
.sendToTarget();
return;
}
@ -768,13 +759,13 @@ import java.io.IOException;
}
private void stopInternal() {
resetInternal(true);
resetInternal(/* releaseMediaSource= */ false, /* resetPosition= */ false);
loadControl.onStopped();
setState(Player.STATE_IDLE);
}
private void releaseInternal() {
resetInternal(true);
resetInternal(/* releaseMediaSource= */ true, /* resetPosition= */ true);
loadControl.onReleased();
setState(Player.STATE_IDLE);
internalPlaybackThread.quit();
@ -784,7 +775,7 @@ import java.io.IOException;
}
}
private void resetInternal(boolean releaseMediaSource) {
private void resetInternal(boolean releaseMediaSource, boolean resetPosition) {
handler.removeMessages(MSG_DO_SOME_WORK);
rebuffering = false;
mediaClock.stop();
@ -804,6 +795,20 @@ import java.io.IOException;
readingPeriodHolder = null;
playingPeriodHolder = null;
setIsLoading(false);
if (resetPosition) {
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
// (firstPeriodIndex,0) isn't ignored.
Timeline timeline = playbackInfo.timeline;
int firstPeriodIndex = timeline == null || timeline.isEmpty()
? 0
: timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
.firstPeriodIndex;
playbackInfo = playbackInfo.fromNewPosition(firstPeriodIndex, C.TIME_UNSET, C.TIME_UNSET);
} else {
// The new start position is the current playback position.
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, playbackInfo.positionUs,
playbackInfo.contentPositionUs);
}
if (releaseMediaSource) {
if (mediaSource != null) {
mediaSource.releaseSource();
@ -1129,18 +1134,12 @@ import java.io.IOException;
}
private void handleSourceInfoRefreshEndedPlayback(int prepareAcks, int seekAcks) {
Timeline timeline = playbackInfo.timeline;
int firstPeriodIndex = timeline.isEmpty() ? 0 : timeline.getWindow(
timeline.getFirstWindowIndex(shuffleModeEnabled), window).firstPeriodIndex;
// Set the internal position to (firstPeriodIndex,TIME_UNSET) so that a subsequent seek to
// (firstPeriodIndex,0) isn't ignored.
playbackInfo = playbackInfo.fromNewPosition(firstPeriodIndex, C.TIME_UNSET, C.TIME_UNSET);
setState(Player.STATE_ENDED);
// Set the playback position to (firstPeriodIndex,0) for notifying the eventHandler.
notifySourceInfoRefresh(prepareAcks, seekAcks,
playbackInfo.fromNewPosition(firstPeriodIndex, 0, C.TIME_UNSET));
// Reset, but retain the source so that it can still be used should a seek occur.
resetInternal(false);
resetInternal(false, true);
// Set the playback position to 0 for notifying the eventHandler (instead of C.TIME_UNSET).
notifySourceInfoRefresh(prepareAcks, seekAcks,
playbackInfo.fromNewPosition(playbackInfo.periodId.periodIndex, 0, C.TIME_UNSET));
}
private void notifySourceInfoRefresh() {