androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android
https://developer.android.com/media/media3
Apache License 2.0
1.65k stars 390 forks source link

Shuffle play is buggy when shufflePlay is enabled before addition of mediaitems #273

Open tzugen opened 1 year ago

tzugen commented 1 year ago

Media3 Version

1.0.0-rc02

Devices that reproduce the issue

Pixel 3

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Not tested

Reproduction steps

  1. addMediaItems to controller
  2. setPlayWhenReady = true
  3. prepare
  4. They will start playing
  5. Wait for playback to finish
  6. Playbackstate is now 4
  7. addMediaItems once more
  8. setPlayWhenReady = true
  9. prepare
  10. They don't play

Expected result

It plays each time

Actual result

--

Media

--

Bug Report

tzugen commented 1 year ago

This can be "fixed" by calling seekTo(0,0L) on the controller. But if shuffle mode is enabled it will cause the playlist always to start with the first item.

marcbaechinger commented 1 year ago

Once the player has transitioned to STATE_ENDED you need to seek to a position before the end of the playlist to get out of STATE_ENDED. This is working as intended.

When player.prepare() has been called, any subsequent calls to prepare are no-ops.

marcbaechinger commented 1 year ago

But if shuffle mode is enabled it will cause the playlist always to start with the first item.

If you call seekTo(/* windowIndex=*/ 0, /* positionMs= */ 0L), then the player starts at windowIndex 0 not at the index 0 of the shuffle sequence.

To seek to the first window of the shuffle sequence you can do:

player.getCurrentTimeline();
int windowIndex = timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true);
seekTo(windowIndex, /* positionMs= */ C.TIME_UNSET)

I'm closing this issue as it seem answered. Please reopen if you think something needs to be added.

tzugen commented 1 year ago

Ok, that answers it. Maybe its something that could be added to the docs. I'll open a PR

tzugen commented 1 year ago

But if shuffle mode is enabled it will cause the playlist always to start with the first item.

If you call seekTo(/* windowIndex=*/ 0, /* positionMs= */ 0L), then the player starts at windowIndex 0 not at the index 0 of the shuffle sequence.

To seek to the first window of the shuffle sequence you can do:

player.getCurrentTimeline();
int windowIndex = timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true);
seekTo(windowIndex, /* positionMs= */ C.TIME_UNSET)

I'm closing this issue as it seem answered. Please reopen if you think something needs to be added.

Unfortunately I have to report that this does not work.

    var isShufflePlayEnabled: Boolean
        get() = controller?.shuffleModeEnabled == true
        set(enabled) {
            controller?.shuffleModeEnabled = enabled
        }

    @Synchronized
    fun addToPlaylist(
        songs: List<Track>,
        cachePermanently: Boolean,
        autoPlay: Boolean,
        shuffle: Boolean
    ) {

        if (shuffle) isShufflePlayEnabled = true
        controller?.addMediaItems(insertAt, mediaItems)

        prepare()

        // Playback doesn't start correctly when the player is in STATE_ENDED.
        // So we need to call seek before (this is what play(0,0)) does.
        // We can't just use play(0,0) then all random playlists will start with the first track.
        // This means that we need to generate the random first track ourselves.
        if (autoPlay) {
            debugShuffleState(isShufflePlayEnabled)
            val start = controller?.currentTimeline?.getFirstWindowIndex(isShufflePlayEnabled) ?: 0
            play(start)
        }
    }

This is our current code. But if shuffle play is enabled before adding the items, the items are not shuffled after insertion. Meaning that getFirstWindowIndex will be 0 anyway...

Console:

13:43:34.715 PlaybackStateSerializer   org.moire.ultrasonic.debug  I  Serialized currentPlayingIndex: -1, currentPlayingPosition: 0, shuffle: true
13:43:34.787 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: 0, at: 0
13:43:34.788 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: 1, at: 1
13:43:34.788 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: 2, at: 2
13:43:34.788 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: 3, at: 3
13:43:34.789 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: 4, at: 4
13:43:34.789 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: 5, at: 5
13:43:34.789 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: 6, at: 6
13:43:34.789 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: 7, at: 7
13:43:34.790 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: 8, at: 8
13:43:34.790 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: 9, at: 9
13:43:34.790 MediaPlayerController     org.moire.ultrasonic.debug  D  Shuffle: windowIndex: -1, at: 10
13:43:35.244 PlaybackStateSerializer   org.moire.ultrasonic.debug  I  Serialized currentPlayingIndex: 0, currentPlayingPosition: 0, shuffle: true

As you can see I call debugShuffleState() to log the current shuffle windows just before calling getFirstWindowIndex and seekTo(), but at this point its not shuffled.

Afterwards two things might happen. Either it continues in a shuffled way (with the problems I mention in the other bug) or often it refuses to play after the first song at all.

tzugen commented 1 year ago

ps. i don't have the rights to reopen