When trying to switch an ExoPlayer instance between a Dash and an MP4 stream (or MP4 to Dash) while trying to resume at the same playback position an IndexOutOfBoundsException occurs.
Specifically, calling prepare with resetPosition set to false while changing MediaSource types will cause the exception.
player.prepare(mediaSource, false, true);
player.prepare(mediaSource, false, true);).SampleExoPlayerVideoSwitchCrash.zip
To reproduce the steps in the sample app:
Replacing the currently playing MP4 with a new MP4 stream (pressing the "Play MP4" button a second time) switches playback as expected, likewise loading a new Dash stream while a current Dash stream is playing also switches correctly.
2.9.5
Google Pixel 3 XL
Thanks for reporting! I can confirm this behaviour. When the position is not reset on prepare, the new PlaybackInfo object is reset with the MediaPeriodId of the previous playbackInfo in ExoPlayerImpl:L670. If the previous timeline is not of the same type (like DashTimeline -> SinglePeriodTimeline), then getIndexOfPeriod() which is implemented by these concrete timelines, returns C.INDEX_UNSET which triggers the assertion in getPeriod(C.INDEX_UNSET, period, setIdentifiers).
This should not happen, so I marked it as a bug. It needs some further investigation though to see what's the best fix.
I wouldn't consider this a bug really. I think the option to keep the previous position was always intended to be for two cases:
Can you describe your use case in more detail to understand why you want to keep a position of an apparently unrelated piece of media?
I guess we could support such a case by converting the current position to an initial seek if needed, but that depends on whether we think that we should support the player.prepare(mediaSource, false, true) option at all.
Agreed there is not really a use case. Also I think the behaviour in such a case should be undefined in the sense of the API :). Converting to an initial seek sounds like a sensible way to me which avoids the app crash for users.
Our case that triggered this issue is:
We have multiple video streams that can represent what we'd consider to be the same content across different languages and qualities. We recently started rolling out adaptive videos for some of our content, and so we have some cases where our users can watch and switch between videos that are available across two languages; where one language might be available in a progressive only format (MP4 - 1080p, 720p, etc.) and the other language may be available in an adaptive format like Dash to handle auto quality switching.
As the user is switching between different instances of the same content, we wanted to resume playback at the same position in the video.
Otherwise it looks like we can do something like this:
…
long currentPosition = player.getCurrentPosition();
player.prepare(mediaSource, true, true);
player.seekTo(currentPosition);
…
It was only something we started to see reports of after updating from 2.7.3 to 2.9.4+ (we were previously releasing the previous MediaSource and setting the seek to position before calling prepare with the new MediaSource without issue).
Thanks for the explanation. We changed the identification of periods from indices to ids, that's why it stopped working. Before that change, it just happened to work because both MediaSource would have the same period index 0 at this position, but now they have ids which are no longer the same as they are generated in a different way.
We'll look into doing the initial seek with window index and position as documented. Until then, you can use the workaround you described. Note that it is slightly more efficient to stop the player first, then seek, and then prepare like that:
long currentPosition = player.getCurrentPosition();
player.stop(true);
player.seekTo(currentPosition);
player.prepare(mediaSource, false, true);