From 2ae07936baf638bfd2a714a1a73ef9f31eab8e9c Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 28 Feb 2019 13:00:20 +0000 Subject: [PATCH] Handle !resetPosition as initial seek position. The flag in ExoPlayer.prepare is documented as keeping the current window index and window position. We are currently using the current period UID and period position instead. This causes problems when the media source is changed but the position is not reset. Using the initial seek position instead ensures we actually use the window index and position. Issue:#5520 PiperOrigin-RevId: 236101024 --- RELEASENOTES.md | 3 ++ .../exoplayer2/ExoPlayerImplInternal.java | 6 +++ .../android/exoplayer2/ExoPlayerTest.java | 39 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index db15ff8653..3136cdadcc 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -64,6 +64,9 @@ ([#5179](https://github.com/google/ExoPlayer/issues/5179)). * Fix issue with `TimelineQueueNavigator` not publishing the queue in shuffled order when in shuffle mode. +* Fix issue where not resetting the position for a new `MediaSource` in calls to + `ExoPlayer.prepare` causes an `IndexOutOfBoundsException` + ([#5520](https://github.com/google/ExoPlayer/issues/5520)). ### 2.9.6 ### 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 f6013212d0..0a1a73023f 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 @@ -436,6 +436,12 @@ import java.util.concurrent.atomic.AtomicBoolean; private void prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) { pendingPrepareCount++; + if (!resetPosition && pendingInitialSeekPosition == null && !playbackInfo.timeline.isEmpty()) { + playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); + long windowPositionUs = playbackInfo.positionUs + period.getPositionInWindowUs(); + pendingInitialSeekPosition = + new SeekPosition(Timeline.EMPTY, period.windowIndex, windowPositionUs); + } resetInternal( /* resetRenderers= */ false, /* releaseMediaSource= */ true, resetPosition, resetState); loadControl.onPrepared(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 9b41d19123..fa8445475b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -1124,6 +1124,45 @@ public final class ExoPlayerTest { testRunner.assertPlayedPeriodIndices(0, 1); } + @Test + public void testReprepareAndKeepPositionWithNewMediaSource() throws Exception { + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ new Object())); + Timeline secondTimeline = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ new Object())); + MediaSource secondSource = new FakeMediaSource(secondTimeline, /* manifest= */ null); + AtomicLong positionAfterReprepare = new AtomicLong(); + ActionSchedule actionSchedule = + new ActionSchedule.Builder("testReprepareAndKeepPositionWithNewMediaSource") + .pause() + .waitForPlaybackState(Player.STATE_READY) + .playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 2000) + .prepareSource(secondSource, /* resetPosition= */ false, /* resetState= */ true) + .waitForTimelineChanged(secondTimeline) + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + positionAfterReprepare.set(player.getCurrentPosition()); + } + }) + .play() + .build(); + ExoPlayerTestRunner testRunner = + new ExoPlayerTestRunner.Builder() + .setTimeline(timeline) + .setActionSchedule(actionSchedule) + .build(context) + .start() + .blockUntilActionScheduleFinished(TIMEOUT_MS) + .blockUntilEnded(TIMEOUT_MS); + + testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, secondTimeline); + assertThat(positionAfterReprepare.get()).isAtLeast(2000L); + } + @Test public void testStopDuringPreparationOverwritesPreparation() throws Exception { Timeline timeline = new FakeTimeline(/* windowCount= */ 1);