Commit e1d960db by tonihei Committed by Oliver Woodman

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
parent fdb53ac8
...@@ -17,7 +17,6 @@ package com.google.android.exoplayer2; ...@@ -17,7 +17,6 @@ package com.google.android.exoplayer2;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource; 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.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.ActionSchedule;
...@@ -541,4 +540,30 @@ public final class ExoPlayerTest extends TestCase { ...@@ -541,4 +540,30 @@ public final class ExoPlayerTest extends TestCase {
Player.TIMELINE_CHANGE_REASON_DYNAMIC); 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);
}
} }
...@@ -393,17 +393,10 @@ import java.io.IOException; ...@@ -393,17 +393,10 @@ import java.io.IOException;
private void prepareInternal(MediaSource mediaSource, boolean resetPosition) { private void prepareInternal(MediaSource mediaSource, boolean resetPosition) {
pendingPrepareCount++; pendingPrepareCount++;
resetInternal(true); resetInternal(/* releaseMediaSource= */ true, resetPosition);
loadControl.onPrepared(); 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; this.mediaSource = mediaSource;
mediaSource.prepareSource(player, true, this); mediaSource.prepareSource(player, /* isTopLevelSource= */ true, /* listener = */ this);
setState(Player.STATE_BUFFERING); setState(Player.STATE_BUFFERING);
handler.sendEmptyMessage(MSG_DO_SOME_WORK); handler.sendEmptyMessage(MSG_DO_SOME_WORK);
} }
...@@ -638,18 +631,16 @@ import java.io.IOException; ...@@ -638,18 +631,16 @@ import java.io.IOException;
Pair<Integer, Long> periodPosition = resolveSeekPosition(seekPosition); Pair<Integer, Long> periodPosition = resolveSeekPosition(seekPosition);
if (periodPosition == null) { 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 // 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.
// 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); 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. // 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; return;
} }
...@@ -768,13 +759,13 @@ import java.io.IOException; ...@@ -768,13 +759,13 @@ import java.io.IOException;
} }
private void stopInternal() { private void stopInternal() {
resetInternal(true); resetInternal(/* releaseMediaSource= */ false, /* resetPosition= */ false);
loadControl.onStopped(); loadControl.onStopped();
setState(Player.STATE_IDLE); setState(Player.STATE_IDLE);
} }
private void releaseInternal() { private void releaseInternal() {
resetInternal(true); resetInternal(/* releaseMediaSource= */ true, /* resetPosition= */ true);
loadControl.onReleased(); loadControl.onReleased();
setState(Player.STATE_IDLE); setState(Player.STATE_IDLE);
internalPlaybackThread.quit(); internalPlaybackThread.quit();
...@@ -784,7 +775,7 @@ import java.io.IOException; ...@@ -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); handler.removeMessages(MSG_DO_SOME_WORK);
rebuffering = false; rebuffering = false;
mediaClock.stop(); mediaClock.stop();
...@@ -804,6 +795,20 @@ import java.io.IOException; ...@@ -804,6 +795,20 @@ import java.io.IOException;
readingPeriodHolder = null; readingPeriodHolder = null;
playingPeriodHolder = null; playingPeriodHolder = null;
setIsLoading(false); 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 (releaseMediaSource) {
if (mediaSource != null) { if (mediaSource != null) {
mediaSource.releaseSource(); mediaSource.releaseSource();
...@@ -1129,18 +1134,12 @@ import java.io.IOException; ...@@ -1129,18 +1134,12 @@ import java.io.IOException;
} }
private void handleSourceInfoRefreshEndedPlayback(int prepareAcks, int seekAcks) { 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); 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. // 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() { private void notifySourceInfoRefresh() {
......
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