audio_service
audio_service copied to clipboard
Request for example of setting shuffle mode
To which pages does your suggestion apply?
Any of the example projects.
- https://github.com/ryanheise/audio_service/tree/one-isolate/audio_service/example/lib
Quote the sentences(s) from the documentation to be improved (if any)
N/A
Describe your suggestion
Audio Service includes an overridable method setShuffleMode
, but none of the example projects demonstrate managing shuffle mode. Shuffle mode has been the hardest topic for me to grasp. I'm fine when I'm only working with Just Audio, but when I try to integrate it into Audio Service I'm having trouble knowing how to keep the audio handler queue
and the Just Audio concatenating audio source sequence in sync. If one of the example projects could demonstrate this, it would be helpful.
Related: This issue describes a bug in my audio_service tutorial implementation that lead me to this documentation request.
I have a working example of a toggle shuffle function here:
https://github.com/IrosTheBeggar/mstream_music/blob/master/lib/media/audio_stuff.dart#L121
Hope that helps
I have a working example of a toggle shuffle function here:
https://github.com/IrosTheBeggar/mstream_music/blob/master/lib/media/audio_stuff.dart#L121
Hope that helps
Hello @IrosTheBeggar, I think what @suragch meant here is the way Shuffle method works. It should be working like VLC app, the conditions are:
- if the Shuffle button is active, then the next queue will be random
- if Shuffle has played all the indexes inside the playlist, it will stop the player.
- if Loop All is also active, then there will be infinite loop of random song.
That is the common ways the Shuffle method should be working with. I hope someone will give a guide or a hint to this kind of method.
@gOzaru I will eventually add such an example (based on the just_audio playlist example), but in the meantime, have you looked at @suragch 's tutorial?
Thank you for the feedback, @ryanheise I have tested the @suragch tutorial and it behaves like this:
- If shuffle is active, then the indexes of current playlist will be randomized
- If there are no more queue inside the playlist, then the player will be stopped
- If loop all is active, the randomized playlist will be only conducted once. After the first loop is over, the indexes of playlist will not be randomized again.
I hope I can find solution for this.
Alternative 01: I am thinking about creating my own class for ShuffleOrder method:
- implement the Shuffle condition if Loop.all is active
- override the seekToNext and seekToPrevious method if Shuffle is active and Loop.all is active
Probably the correct way is that I should replace the current default of ShuffleOrder in playlist variable:
ConcatenatingAudioSource(children: [], shuffleOrder: [_this_]);
Honestly, I don't know how to add my own class inside playlist variable.
I am still learning though.
Alternative 02 I am thinking to add conditions if Shuffle and Loop.all are active inside these methods:
- override seekToNext and seekToPrevious
- on condition: Shuffle indices of current playlist should be made also infinite with randomized order. So if player reaches the end of shuffle indices, it will randomize the current playlist and go to the first shuffle index. The shuffle playlist should be put inside loop for, I think.
By the way, I really need help from you @ryanheise I am in charge of developing music project and this shuffle thing is the last thing on my schedule. It is due on September 14, 2021. If you can help me out before then, I will grant you royalty share 10% from its feature' net income each month for the next three years after launching in Indonesia. Consider this offer as passive income. If you are interested to know more about the project, just let me know.
Thank you.
Hi @gOzaru , if you want an example of how to use ShuffleOrder
which is an API in the just_audio plugin, I would suggest making a feature request on the just_audio project.
Just a reminder @gOzaru to create an issue on just_audio for a ShuffleOrder
example.
My bad, @ryanheise I just tested the recent just_audio example and found out that the tutorial which @suragch created, actually didn't behave like the example does. The example really works like what I want to be. I think I am gonna remaking my project from the beginning. But, thank you for suggesting.
I've just added a new example example/lib/example_playlist.dart
which demonstrates a playlist with shuffling/looping. It's basically the just_audio example wrapped in an audio_service handler, except that I've made it so when you switch to shuffle mode, it visually shuffles the items in the list. It basically maps just_audio's effective sequence onto the handler's queue.
(I realise just_audio doesn't expose the effectiveSequence
getter and I've had to implement it in this example manually, but ideally I'd push that into just_audio itself to make it easier to use.)
@ryanheise
Sorry for my late reply.
It seems the shuffle is working, but when loop all is active and it reached the last index; the next button is disabled.
This has the same condition with @suragch tutorial.
Yep I'm aware of that, I just implemented rather cheap versions of hasNext and hasPrevious. But you can easily modify these to always return true when repeat mode is enabled.
The latest commit fixes hasNext/hasPrevious.
I just tested the latest version of example_playlist.dart
I found out that the Loop All is now working as it should be.
But the Shuffle method only ran for once. It changed the initial playlist indices into shuffleIndices. If Loop All is active and after the first loop, it won't reshuffle the playlist again. I tap next until the fifth loop; and yet, it didn't change the playlist indices like it should be in just_audio example_playlist.dart
.
I sought where it went wrong and didn't find anything.
We are using just_audio shuffle, right?
So it should have played like it has to be in just_audio example_playlist.dart
.
If Loop All is active and after the first loop, it won't reshuffle the playlist again.
That's the same as the way I implemented it in just_audio. It will only call shuffle()
when you press the shuffle button, so when you're doing "loop all", once reaching the end of the shuffled indices, it loops back to the beginning of the shuffled indices.
If you want to automatically reshuffle on each iteration, you'd need to just add a listener to currentIndex
and whenever it reaches the the loop point, you can just call shuffle. E.g. the following calls shuffle whenever reaching the last shuffle index:
_player.currentIndexStream.listen((currentIndex) {
final shuffleIndices = this.shuffleIndices;
if (shuffleIndices != null && currentIndex != null && currentIndex == shuffleIndices.last) {
_player.shuffle();
}
}
Customise as needed.
@ryanheise
Thank you very much for helping.
The whole problem is solved.
I declared the function you wrote inside AudioPlayerHandlerImpl > init()
.
Now I can work on other features for my project.
Thank you
@ryanheise
I think I have found a bug in the example_playlist.dart
.
Consider I have 5 songs from A to E.
If user click A, then example will display title A
But, if user click C, then example will keep displaying title A.
This happened if I click the song not in current order, it's random.
So no matter how many queue I skip, it won't update the title, album, and probably the cover image.
It will get updated only if it is the next index inside the queue.
For example, a sort queue is like this: A => B => C => D => E (The title always gets updated)
A random queue: A => C => E (The title never gets updated)
I spent two days looking where it goes wrong.
Unfortunately, I haven't found one.
I have checked in just_audio example_playlist.dart
, the updated title, album, and its cover image are working as it should be.
You can view on the screenshot that they are not updated.
Maybe this is a bug?
I will have to check this tomorrow since it's getting late over here, but can you confirm whether the unmodified example_playlist.dart
reproduces the bug? If so, I will just investigate using that.
@ryanheise
Yeah, I only added the MediaItem into 7 items, added some Favorite Icon and function reshuffle inside init().
Anyway, I just changed the StreamBuilder<MediaItem?>
into StreamBuilder<QueueState?>
and changed all of the variable and it seems it is working again.
StreamBuilder<QueueState?>(
stream: _audioHandler.queueState,
builder: (context, snapshot) {
final queueState = snapshot.data ?? QueueState.empty;
final queue = queueState.queue;
final index = queueState.queueIndex!;
if (queue.isEmpty) {
return const CircularProgressIndicator();
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (queue[index].artUri != null)
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Image.network(
'${queue[index].artUri!}',
width: 100.0,
height: 100.0,
),
),
),
),
Text(queue[index].album!, style: Theme.of(context).textTheme.headline6),
Text(queue[index].title),
],
);
}
},
),
@ryanheise I just found a strange thing. When I was about to clear the playlist based on the example above to add a new playlist, it seems I cannot clear them. Instead, it added the new playlist on top of the previous one. I tried to erase the previous playlist using these codes, but nothing works.
for (int index = mediaItems.length - 1; index >= 0; index--) {
log("Removing old playlist => [$index]: ${mediaItems[index].title}");
mediaItems.removeAt(index);
await removeQueueItemAt(index);
}
I hope you can help me out. Thank you.
Hi @gOzaru are you able to create a bug report? It's a bit difficult from an excerpt to see if there is a bug and where it is.
Okay, I will make the minimal reproduction for a bug report. Thank you.
@ryanheise
I think I know where it went wrong.
First we need to check whether _mediaLibrary.items[MediaLibrary.albumsRootId]
is empty or not.
Previously, I set it as empty in these below codes as my case study is different.
I also changed its type of variable as the playlist of MediaItem must be empty for first time users.
Map<String, List<MediaItem>?> items = {
AudioService.browsableRootId: const [
MediaItem(
id: albumsRootId,
title: "Albums",
playable: false,
),
],
albumsRootId: [], //this is empty
};
When it is empty, adding these codes are fine:
await updateQueue(_mediaLibrary.items[MediaLibrary.albumsRootId]!);
_playlist.addAll(queue.value.map(_itemToSource).toList());
await _player.setAudioSource(_playlist);
But when it is not, we should avoid adding these ones:
_playlist.addAll(queue.value.map(_itemToSource).toList());
Wrap those two examples of conditional into customAction function and call it when you are about to enter a new page to play a playlist. That's the only way to work if we want to switch playlist because the example is adding a Stream into queue. If I close the queue, I cannot switch the playlist as it gives error. Apparently, the fault is in the above codes. Now I can switch to any playlist I have made inside a Playlist class without having a duplicate list over and over again.
@ryanheise
Thank you for your assistance in making the example_playlist.dart