suragch / flutter_audio_service_demo

Companion project for Flutter audio_service tutorial
https://suragch.medium.com/background-audio-in-flutter-with-audio-service-and-just-audio-3cce17b4a7d?sk=0837a1b1773e27a4f879ff3072e90305
MIT License
57 stars 28 forks source link

When shuffling songs, the UI shows the shuffled order correctly. However, when calling skipToNext() and skipToPrevious(), _player will play the song in the original order, not the shuffled order #1

Closed duypham312 closed 3 years ago

duypham312 commented 3 years ago

As the title mentioned, skipToNext() and skipToPrevious() functions do not work properly when shuffleModeEnabled is true. For example, if the initial order is [song 1, song 2, song 3] and the shuffled order is [song 1, song 3, song 2], when skipping to the second song in the shuffled order (song 3 with the index is 1 in that array), calling skipToNext() will play song 2 even though the UI will show song 3 as the current song being played. I think this problem comes from the implementation of skipToNext() and skipToPrevious() that calling seek(Duration.zero, index: index) because seek(Duration.zero, index: index) operates on the initial order, not the shuffled order.

suragch commented 3 years ago

I'm not observing that behavior:

order

suragch commented 3 years ago

Oh, I understand now. You're talking about the audio. The title is changing but for some reason the audio is not matching the title. Let me look more into it.

duypham312 commented 3 years ago

Oh, I understand now. You're talking about the audio. The title is changing but for some reason the audio is not matching the title. Let me look more into it.

Yeah, that's what I meant

suragch commented 3 years ago

I'm a little stuck. I'm not sure how to solve this. If you find the solution can you let me know?

If I call _player.seekToNext() it will play the right song but the labels will be messed up. Somehow the audio handler and the player need to be synced.

duypham312 commented 3 years ago

Actually, I had to use a temporary solution. First of all, I'm calling seekToNext() and seekToPrevious() instead of _skip(). Then, I got the index of the song being played by the _player in the shuffled list by the following way:

_audioPlayer.durationStream.listen((duration) {
  var index = _audioPlayer.currentIndex;
  final newQueue = queue.value;

  if (index == null || newQueue.isEmpty) return;

  if (_audioPlayer.shuffleModeEnabled) {
    index = _audioPlayer.shuffleIndices!.indexOf(index);
  }

  final oldMediaItem = newQueue[index];
  final newMediaItem = oldMediaItem.copyWith(duration: duration);
  newQueue[index] = newMediaItem;

  queue.add(newQueue);
  mediaItem.add(newMediaItem);
});

_audioPlayer.currentIndexStream.listen((index) {
  final playlist = queue.value;
  if (index == null || playlist.isEmpty) return;

  if (_audioPlayer.shuffleModeEnabled) {
    index = _audioPlayer.shuffleIndices!.indexOf(index);
  }

  mediaItem.add(playlist[index]);
});

This solution can temporarily make the song's title sync with the song played by the _player. However, in my case, when the current song ends, the player plays the next song without following the shuffled order. I hope this temporary solution can help you find a more effective solution to this problem

duypham312 commented 3 years ago

In addition, this is how I implement skipToQueueItem(int index)

@override
Future<void> skipToQueueItem(int index) async {
  if (index < 0 || index >= queue.value.length) return;

  if (_audioPlayer.shuffleModeEnabled) {
    index = _audioPlayer.shuffleIndices![index];
  }

  _audioPlayer.seek(Duration.zero, index: index);
}

Besides, if you have a more efficient solution, you can share it in the issue that I have opened here

suragch commented 3 years ago

@duypham312 Thank you for posting the solution you have so far. I reproduced your results. Seeking to next and previous works but when one song finishes it automatically loads the incorrect song even though shuffling mode is enabled.

Steps to reproduce (for others interested in this problem):

  1. Replace the respective methods in audio_handler.dart with the following:
void _listenForDurationChanges() {
  _player.durationStream.listen((duration) {
    var index = _player.currentIndex;
    final newQueue = queue.value;
    if (index == null || newQueue.isEmpty) return;
    if (_player.shuffleModeEnabled) {
      index = _player.shuffleIndices!.indexOf(index);
    }
    final oldMediaItem = newQueue[index];
    final newMediaItem = oldMediaItem.copyWith(duration: duration);
    newQueue[index] = newMediaItem;
    queue.add(newQueue);
    mediaItem.add(newMediaItem);
  });
}

void _listenForCurrentSongIndexChanges() {
  _player.currentIndexStream.listen((index) {
    final playlist = queue.value;
    if (index == null || playlist.isEmpty) return;
    if (_player.shuffleModeEnabled) {
      index = _player.shuffleIndices!.indexOf(index);
    }
    mediaItem.add(playlist[index]);
  });
}

@override
Future<void> skipToQueueItem(int index) async {
  if (index < 0 || index >= queue.value.length) return;
  if (_player.shuffleModeEnabled) {
    index = _player.shuffleIndices![index];
  }
  _player.seek(Duration.zero, index: index);
}

@override
Future<void> skipToNext() async {
  _player.seekToNext();
}

@override
Future<void> skipToPrevious() async {
  _player.seekToPrevious();
}
  1. Run the demo app
  2. Press the shuffle button until the playlist order says Song 1, Song 3, Song 2.
  3. Play the first song and move the thumb on the seek bar so that it is almost finished. Then let it finish and automatically progress to the next song.

The next played song is Song 2 even though it should be Song 3.

suragch commented 3 years ago

I added a note to the shuffle section of the tutorial alerting readers to the bug and directing them here.

suragch commented 3 years ago

The source code for just_audio_background might provide some clues, but this is pretty different than my tutorial setup.

https://github.com/ryanheise/just_audio/blob/master/just_audio_background/lib/just_audio_background.dart

duypham312 commented 3 years ago

@suragch Hey, have you tried to reproduce this error on Android? This bug has gone when I test on my android phone. I think this might be a bug of just_audio and it only happens on iOS

duypham312 commented 3 years ago

I think I found the cause of the bug related to auto-next. The _player.setShuffleModeEnabled(true) is called before the _player.shuffle() is called and that resulted in the problem as I mentioned. When I call await _player.shuffle() before _player.setShuffleModeEnabled(true), it works properly.

duypham312 commented 3 years ago

@suragch hey, I found another bug related to the shuffle mode. If I play Song 3 while the shuffle mode is on and let it end, the app will crash without printing any error message.

suragch commented 3 years ago

@duypham312 Nice job on finding a workaround for the auto-next issue!

The play-song-3-on-shuffle-mode bug seems to only cause a crash on iOS and macOS. Android and web work for me.

suragch commented 3 years ago

I've updated the tutorial and the project code up to this point. Even though there is still a bug with shuffling, it's better than it was before.

duypham312 commented 3 years ago

@suragch hey, Ryanheise just informed me about the new fix/queue branch, I tested with this branch and the app doesn't crash anymore.

suragch commented 3 years ago

@duypham312 That's great! Could you ping me again when that branch gets merged?

duypham312 commented 3 years ago

That branch has been merged, you can update the tutorial now

suragch commented 3 years ago

I've updated the tutorial. Thank you for your help in resolving this problem! I'll close the issue now.