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);