From 54a1bb186e860c172c473713eb24f1cc5d073d07 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 28 Nov 2017 09:00:43 -0800 Subject: [PATCH] Allow resetInternal to release MediaSource but keep timeline. This allows to keep the state synced with ExoPlayerImpl after stopping the player, but still releases the media source immediately as it needs to be reprepared. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=177167980 --- .../android/exoplayer2/ExoPlayerTest.java | 24 ++++++++ .../exoplayer2/ExoPlayerImplInternal.java | 58 ++++++++++++------- .../exoplayer2/testutil/ActionSchedule.java | 9 +++ 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java index efb7b0e96c..2443f8b892 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -784,4 +784,28 @@ public final class ExoPlayerTest extends TestCase { testRunner.assertNoPositionDiscontinuities(); } + public void testStopAndSeekAfterStopDoesNotResetTimeline() throws Exception { + // Combining additional stop and seek after initial stop in one test to get the seek processed + // callback which ensures that all operations have been processed by the player. + Timeline timeline = new FakeTimeline(/* windowCount= */ 1); + ActionSchedule actionSchedule = + new ActionSchedule.Builder("testStopTwice") + .waitForPlaybackState(Player.STATE_READY) + .stop(false) + .stop(false) + .seek(0) + .waitForSeekProcessed() + .build(); + ExoPlayerTestRunner testRunner = + new ExoPlayerTestRunner.Builder() + .setTimeline(timeline) + .setActionSchedule(actionSchedule) + .build() + .start() + .blockUntilActionScheduleFinished(TIMEOUT_MS) + .blockUntilEnded(TIMEOUT_MS); + testRunner.assertTimelinesEqual(timeline); + testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED); + testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK); + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 909f52fad8..3bd1d2b00f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -390,11 +390,11 @@ import java.io.IOException; private void prepareInternal(MediaSource mediaSource, boolean resetPosition) { pendingPrepareCount++; - resetInternal(/* releaseMediaSource= */ true, resetPosition); + resetInternal(/* releaseMediaSource= */ true, resetPosition, /* resetState= */ true); loadControl.onPrepared(); this.mediaSource = mediaSource; setState(Player.STATE_BUFFERING); - mediaSource.prepareSource(player, /* isTopLevelSource= */ true, /* listener = */ this); + mediaSource.prepareSource(player, /* isTopLevelSource= */ true, /* listener= */ this); handler.sendEmptyMessage(MSG_DO_SOME_WORK); } @@ -629,10 +629,15 @@ import java.io.IOException; private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException { Timeline timeline = playbackInfo.timeline; - if (timeline == null) { + if (mediaSource == null || timeline == null) { pendingInitialSeekPosition = seekPosition; - eventHandler.obtainMessage(MSG_SEEK_ACK, /* seekAdjusted = */ 0, 0, - playbackInfo.copyWithTimeline(Timeline.EMPTY, null)).sendToTarget(); + eventHandler + .obtainMessage( + MSG_SEEK_ACK, + /* seekAdjusted */ 0, + 0, + timeline == null ? playbackInfo.copyWithTimeline(Timeline.EMPTY, null) : playbackInfo) + .sendToTarget(); return; } @@ -642,10 +647,11 @@ import java.io.IOException; // timeline has changed and a suitable seek position could not be resolved in the new one. setState(Player.STATE_ENDED); // Reset, but retain the source so that it can still be used should a seek occur. - resetInternal(false, true); + resetInternal( + /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); // 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, + eventHandler.obtainMessage(MSG_SEEK_ACK, /* seekAdjusted */ 1, 0, + playbackInfo.fromNewPosition(playbackInfo.periodId.periodIndex, /* startPositionUs= */ 0, /* contentPositionUs= */ C.TIME_UNSET)) .sendToTarget(); return; @@ -766,14 +772,15 @@ import java.io.IOException; } private void stopInternal(boolean reset) { - // Releasing the internal player sets the timeline to null. Use the current timeline or - // Timeline.EMPTY for notifying the eventHandler. - Timeline publicTimeline = reset || playbackInfo.timeline == null - ? Timeline.EMPTY : playbackInfo.timeline; - Object publicManifest = reset ? null : playbackInfo.manifest; - resetInternal(/* releaseMediaSource= */ true, reset); - PlaybackInfo publicPlaybackInfo = playbackInfo.copyWithTimeline(publicTimeline, publicManifest); - if (reset) { + resetInternal( + /* releaseMediaSource= */ true, /* resetPosition= */ reset, /* resetState= */ reset); + PlaybackInfo publicPlaybackInfo = playbackInfo; + if (playbackInfo.timeline == null) { + // Resetting the state sets the timeline to null. Use Timeline.EMPTY for notifying the + // eventHandler. + publicPlaybackInfo = publicPlaybackInfo.copyWithTimeline(Timeline.EMPTY, null); + } + if (playbackInfo.startPositionUs == C.TIME_UNSET) { // When resetting the state, set the playback position to 0 (instead of C.TIME_UNSET) for // notifying the eventHandler. publicPlaybackInfo = @@ -787,7 +794,8 @@ import java.io.IOException; } private void releaseInternal() { - resetInternal(/* releaseMediaSource= */ true, /* resetPosition= */ true); + resetInternal( + /* releaseMediaSource= */ true, /* resetPosition= */ true, /* resetState= */ true); loadControl.onReleased(); setState(Player.STATE_IDLE); internalPlaybackThread.quit(); @@ -797,7 +805,8 @@ import java.io.IOException; } } - private void resetInternal(boolean releaseMediaSource, boolean resetPosition) { + private void resetInternal( + boolean releaseMediaSource, boolean resetPosition, boolean resetState) { handler.removeMessages(MSG_DO_SOME_WORK); rebuffering = false; mediaClock.stop(); @@ -832,13 +841,15 @@ import java.io.IOException; playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, playbackInfo.positionUs, playbackInfo.contentPositionUs); } + if (resetState) { + mediaPeriodInfoSequence.setTimeline(null); + playbackInfo = playbackInfo.copyWithTimeline(null, null); + } if (releaseMediaSource) { if (mediaSource != null) { mediaSource.releaseSource(); mediaSource = null; } - mediaPeriodInfoSequence.setTimeline(null); - playbackInfo = playbackInfo.copyWithTimeline(null, null); } } @@ -1174,7 +1185,8 @@ import java.io.IOException; 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); + resetInternal( + /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); // Set the playback position to 0 for notifying the eventHandler (instead of C.TIME_UNSET). notifySourceInfoRefresh(prepareAcks, playbackInfo.fromNewPosition(playbackInfo.periodId.periodIndex, 0, C.TIME_UNSET)); @@ -1279,6 +1291,10 @@ import java.io.IOException; } private void updatePeriods() throws ExoPlaybackException, IOException { + if (mediaSource == null) { + // The player has no media source yet. + return; + } if (playbackInfo.timeline == null) { // We're waiting to get information about periods. mediaSource.maybeThrowSourceInfoRefreshError(); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index abca2cafdb..7a2ce9270c 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -183,6 +183,15 @@ public final class ActionSchedule { .apply(new WaitForPlaybackState(tag, Player.STATE_READY)); } + /** + * Schedules a delay until the player indicates that a seek has been processed. + * + * @return The builder, for convenience. + */ + public Builder waitForSeekProcessed() { + return apply(new WaitForSeekProcessed(tag)); + } + /** * Schedules a playback parameters setting action to be executed. *