Exoplayer: IndexOutOfBoundsException switching between progressive and adaptive streams

Created on 15 Feb 2019  Â·  5Comments  Â·  Source: google/ExoPlayer

Issue description

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

Reproduction steps

  1. Load and begin playing an MP4 stream.
  2. Try and load a Dash stream, keeping playback position (player.prepare(mediaSource, false, true);).
  3. Application crash.

Link to test content

SampleExoPlayerVideoSwitchCrash.zip

To reproduce the steps in the sample app:

  1. Build and launch the app.
  2. Tap on either the "Play MP4" or "Play Dash" buttons.
  3. Once the video starts playing, tap on the other button,
  4. The application will crash.

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.

Version of ExoPlayer being used

2.9.5

Device(s) and version(s) of Android being used

Google Pixel 3 XL

bug

All 5 comments

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:

  1. Set a start position before preparing the media.
  2. Re-prepare the same media after an error or after stop.

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);
Was this page helpful?
0 / 5 - 0 ratings