[GH-ISSUE #264] Implement 'unshuffle' functionality #176

Closed
opened 2026-02-27 19:29:14 +03:00 by kerem · 19 comments
Owner

Originally created by @sashahilton00 on GitHub (Nov 12, 2018).
Original GitHub issue: https://github.com/librespot-org/librespot/issues/264

Currently when one shuffles the songs, the local queue in librespot is shuffled. when shuffle is disabled, the queue is not unshuffled. We should retrieve the original playlist on unshuffle, and then replace queue will all tracks after the index of the one currently playing.

Originally created by @sashahilton00 on GitHub (Nov 12, 2018). Original GitHub issue: https://github.com/librespot-org/librespot/issues/264 Currently when one shuffles the songs, the local queue in librespot is shuffled. when shuffle is disabled, the queue is not unshuffled. We should retrieve the original playlist on unshuffle, and then replace queue will all tracks after the index of the one currently playing.
kerem closed this issue 2026-02-27 19:29:14 +03:00
Author
Owner

@kingosticks commented on GitHub (Nov 12, 2018):

Indeed. We need to look up the context for this. In cases where this is no context, I'm not sure what you do.

<!-- gh-comment-id:437927934 --> @kingosticks commented on GitHub (Nov 12, 2018): Indeed. We need to look up the context for this. In cases where this is no context, I'm not sure what you do.
Author
Owner

@sashahilton00 commented on GitHub (Nov 12, 2018):

The other solution might be to take a random index for selecting the song, i.e. don't shuffle the list of tracks, but rather create a shuffle_array of sequential integers the length of the track array from 0 => len(track_array), shuffle the array, and move the current playing track's index to position 0, then map that across to the ordered track array when choosing the next song for playback.

So for a track_array ['a', 'b', 'c', 'd', 'e'] with currently playing song c (index 2), create an integer shuffle_array ([0, 1, 2, 3, 4]), shuffle to [3, 1, 0, 2, 4], move currently playing track to position 0 ([2, 3, 1, 0, 4]), then when the next song is requested, map the shuffle_array to track_array, to give a play queue of ['c', 'd', 'b', 'a', 'e']. Then to revert to ordered play, just stop mapping the shuffle_array, and instead use the current track_array index + 1 for the next song.

A bit more complicated, but means that one doesn't need to work out how to retrieve ordered lists when no context is provided.

<!-- gh-comment-id:437932684 --> @sashahilton00 commented on GitHub (Nov 12, 2018): The other solution might be to take a random index for selecting the song, i.e. don't shuffle the list of tracks, but rather create a `shuffle_array` of sequential integers the length of the track array from `0 => len(track_array)`, shuffle the array, and move the current playing track's index to position 0, then map that across to the ordered track array when choosing the next song for playback. So for a `track_array` `['a', 'b', 'c', 'd', 'e']` with currently playing song `c` (index 2), create an integer `shuffle_array` (`[0, 1, 2, 3, 4]`), shuffle to `[3, 1, 0, 2, 4]`, move currently playing track to position 0 (`[2, 3, 1, 0, 4]`), then when the next song is requested, map the `shuffle_array` to `track_array`, to give a play queue of `['c', 'd', 'b', 'a', 'e']`. Then to revert to ordered play, just stop mapping the `shuffle_array`, and instead use the current `track_array` `index + 1` for the next song. A bit more complicated, but means that one doesn't need to work out how to retrieve ordered lists when no context is provided.
Author
Owner

@ashthespy commented on GitHub (Nov 12, 2018):

If we can keep track of the seed, the Fisher–Yates algorithm (that you describe here?) might be a way to shuffle - unshuffle lists with small overheads?

<!-- gh-comment-id:438066414 --> @ashthespy commented on GitHub (Nov 12, 2018): If we can keep track of the seed, the [Fisher–Yates](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) algorithm (that you describe here?) might be a way to shuffle - unshuffle lists with small overheads?
Author
Owner

@sashahilton00 commented on GitHub (Nov 12, 2018):

Not sure it's an official algorithm, just made it up to be what I thought a sensible way to do things. How you shuffle the integer array initially is up to you

<!-- gh-comment-id:438074647 --> @sashahilton00 commented on GitHub (Nov 12, 2018): Not sure it's an official algorithm, just made it up to be what I thought a sensible way to do things. How you shuffle the integer array initially is up to you
Author
Owner

@kingosticks commented on GitHub (Nov 13, 2018):

I think when I was experimenting the official app just didn't let you unshuffle. However, most of the time you do have a context so you just don't notice. That to me seems fine, the less complicated the better. But maybe its changed, someone should investigate.

<!-- gh-comment-id:438088206 --> @kingosticks commented on GitHub (Nov 13, 2018): I think when I was experimenting the official app just didn't let you unshuffle. However, most of the time you do have a context so you just don't notice. That to me seems fine, the less complicated the better. But maybe its changed, someone should investigate.
Author
Owner

@sashahilton00 commented on GitHub (Nov 13, 2018):

That to me seems fine, the less complicated the better. But maybe its changed, someone should investigate.

What is that in this sentence?

<!-- gh-comment-id:438089063 --> @sashahilton00 commented on GitHub (Nov 13, 2018): > That to me seems fine, the less complicated the better. But maybe its changed, someone should investigate. What is `that` in this sentence?
Author
Owner

@sashahilton00 commented on GitHub (Nov 13, 2018):

Also, Spotify uses Fisher-Yates, so for the sake of consistency, we should probably aim for that.

<!-- gh-comment-id:438090238 --> @sashahilton00 commented on GitHub (Nov 13, 2018): Also, Spotify uses Fisher-Yates, so for the sake of consistency, we should probably aim for that.
Author
Owner

@sashahilton00 commented on GitHub (Nov 13, 2018):

Now this is interesting, Spotify filed a patent for a weighted playlist shuffling algorithm: https://patentimages.storage.googleapis.com/d2/65/e2/8d6d3df46e77da/US20170244770A1.pdf and the weights are included with the tracks in a playlist, when retrieved via a uri such as hm://playlist/user/spotify/playlist/37i9dQZF1DWWjGdmeTyeJ6?revision=1542071134%2C0000008f00000166ef91c8b900000166eb2e5322&handlesContent=

More food for thought, I don't think we need to implement the same level of complexity, something like fisher yates or a random shuffle and context retrieval should be plenty.

<!-- gh-comment-id:438096342 --> @sashahilton00 commented on GitHub (Nov 13, 2018): Now this is interesting, Spotify filed a patent for a weighted playlist shuffling algorithm: https://patentimages.storage.googleapis.com/d2/65/e2/8d6d3df46e77da/US20170244770A1.pdf and the weights are included with the tracks in a playlist, when retrieved via a uri such as `hm://playlist/user/spotify/playlist/37i9dQZF1DWWjGdmeTyeJ6?revision=1542071134%2C0000008f00000166ef91c8b900000166eb2e5322&handlesContent=` More food for thought, I don't think we need to implement the same level of complexity, something like fisher yates or a random shuffle and context retrieval should be plenty.
Author
Owner

@sashahilton00 commented on GitHub (Nov 13, 2018):

If we can keep track of the seed, the Fisher–Yates algorithm (that you describe here?) might be a way to shuffle - unshuffle lists with small overheads?

Agreed, we can just seed with a hash of the start time of librespot or something and Store that in memory. Shouldn't be too hard.

<!-- gh-comment-id:438097841 --> @sashahilton00 commented on GitHub (Nov 13, 2018): > If we can keep track of the seed, the Fisher–Yates algorithm (that you describe here?) might be a way to shuffle - unshuffle lists with small overheads? Agreed, we can just seed with a hash of the start time of librespot or something and Store that in memory. Shouldn't be too hard.
Author
Owner

@kingosticks commented on GitHub (Nov 13, 2018):

The problem with the non-context case is when some other client shuffles the tracks and sends them in an update, all you have is those tracks. You have no way to know how to unshuffle them. The other client may have shuffled a hundred time before librespot joined the connect session so it really has no idea of the shuffle history and the seed doesn't help.

What is that in this sentence?

Not letting you unshuffle tracks without a context. Or, rather, making it a no-op.

<!-- gh-comment-id:438099313 --> @kingosticks commented on GitHub (Nov 13, 2018): The problem with the non-context case is when some other client shuffles the tracks and sends them in an update, all you have is those tracks. You have no way to know how to unshuffle them. The other client may have shuffled a hundred time before librespot joined the connect session so it really has no idea of the shuffle history and the seed doesn't help. > What is that in this sentence? Not letting you unshuffle tracks without a context. Or, rather, making it a no-op.
Author
Owner

@sashahilton00 commented on GitHub (Nov 13, 2018):

Have just experimented, Spotify is syncing shuffle state across clients. If you shuffle on one client, skip a song, connect from another client and take over listening, you can go back to the previous song. However, if you shuffle a playlist on client 1, switch playback to client 2 as in the previous scenario, then kill client 1 and try to go back to the previous shuffled song on client 2, you can't. So it looks like there's some sort of shuffle state passed from client 1 to client 2 that is lost when one closes client 1. Additionally shuffle and repeat features are not available on dailymixes, which makes me think that there is some sort of additional protocol for communicating shuffle state that hasn't been upgraded to support dailymixes yet.

<!-- gh-comment-id:438270278 --> @sashahilton00 commented on GitHub (Nov 13, 2018): Have just experimented, Spotify is syncing shuffle state across clients. If you shuffle on one client, skip a song, connect from another client and take over listening, you can go back to the previous song. However, if you shuffle a playlist on `client 1`, switch playback to `client 2` as in the previous scenario, then kill `client 1` and try to go back to the previous shuffled song on `client 2`, you can't. So it looks like there's some sort of shuffle state passed from `client 1` to `client 2` that is lost when one closes `client 1`. Additionally shuffle and repeat features are not available on dailymixes, which makes me think that there is some sort of additional protocol for communicating shuffle state that hasn't been upgraded to support dailymixes yet.
Author
Owner

@kingosticks commented on GitHub (Nov 13, 2018):

I'm not sure I follow what you are saying here. The (shuffled) track list and current track index give you all the state you need to skip backwards. This is all already implemented in librespot, yes?

<!-- gh-comment-id:438300947 --> @kingosticks commented on GitHub (Nov 13, 2018): I'm not sure I follow what you are saying here. The (shuffled) track list and current track index give you all the state you need to skip backwards. This is all already implemented in librespot, yes?
Author
Owner

@sashahilton00 commented on GitHub (Nov 13, 2018):

I'm not sure I follow what you are saying here. The (shuffled) track list and current track index give you all the state you need to skip backwards. This is all already implemented in librespot, yes?

As in the shuffled track list is kept in sync between clients, and not just on the client locally

<!-- gh-comment-id:438306477 --> @sashahilton00 commented on GitHub (Nov 13, 2018): > I'm not sure I follow what you are saying here. The (shuffled) track list and current track index give you all the state you need to skip backwards. This is all already implemented in librespot, yes? As in the shuffled track list is kept in sync between clients, and not just on the client locally
Author
Owner

@devgianlu commented on GitHub (Nov 13, 2018):

@sashahilton00 In fact, you have to communicate it for Spotify to display the queue.

<!-- gh-comment-id:438306794 --> @devgianlu commented on GitHub (Nov 13, 2018): @sashahilton00 In fact, you have to communicate it for Spotify to display the queue.
Author
Owner

@kingosticks commented on GitHub (Nov 13, 2018):

Yes, exactly. That's how it all works already and that's what we support in the original shuffle/repeat/queue work.

<!-- gh-comment-id:438311984 --> @kingosticks commented on GitHub (Nov 13, 2018): Yes, exactly. That's how it all works already and that's what we support in the original shuffle/repeat/queue work.
Author
Owner

@sashahilton00 commented on GitHub (Nov 13, 2018):

sure, but that doesn't explain why you can skip back/forwards when the client that originally shuffled it is open, but when you move clients and close the previous, you lose the ability to skip back

<!-- gh-comment-id:438313675 --> @sashahilton00 commented on GitHub (Nov 13, 2018): sure, but that doesn't explain why you can skip back/forwards when the client that originally shuffled it is open, but when you move clients and close the previous, you lose the ability to skip back
Author
Owner

@sashahilton00 commented on GitHub (Nov 13, 2018):

it's a minor issue, i'm not bothered if we can't skip back after switching clients on a shuffled playlist, but just thought i'd note it.

<!-- gh-comment-id:438314188 --> @sashahilton00 commented on GitHub (Nov 13, 2018): it's a minor issue, i'm not bothered if we can't skip back after switching clients on a shuffled playlist, but just thought i'd note it.
Author
Owner

@kingosticks commented on GitHub (Nov 13, 2018):

I found the behaviour was inconsistent between phone and desktop clients. It might just be their buggy software.

<!-- gh-comment-id:438345845 --> @kingosticks commented on GitHub (Nov 13, 2018): I found the behaviour was inconsistent between phone and desktop clients. It might just be their buggy software.
Author
Owner

@devgianlu commented on GitHub (Nov 21, 2018):

I've looked at some captured packets and it looks like Spotify doesn't do much when you toggle shuffle, probably because he has already stored in memory a copy of the playlist. Anyway it does something:

  • Tries to contact hm://playlist/v2/playlist-v1-uris which gives 400 (why does it even do that?)
  • Sends a bunch of (I think) metadata to hm://event-service/v1/events just for analytics purposes
  • Sends a lot of data (5000> bytes) to hm://remote/3/user/{userId}/ and gets 200 in response

Some of the metadata includes:

  • UIInteraction 5 zlink player click enable-shuffle or UIInteraction 5 zlink player click disable-shuffle
  • Interaction 1 interactionID player/shuffle-button click shuffle nowplaying/queue spotify:app:queue
  • 268 1 desktop-stranger-things Enabled
<!-- gh-comment-id:440687347 --> @devgianlu commented on GitHub (Nov 21, 2018): I've looked at some captured packets and it looks like Spotify doesn't do much when you toggle shuffle, probably because he has already stored in memory a copy of the playlist. Anyway it does something: - Tries to contact `hm://playlist/v2/playlist-v1-uris` which gives `400` (why does it even do that?) - Sends a bunch of (I think) metadata to `hm://event-service/v1/events` just for analytics purposes - Sends a lot of data (5000> bytes) to `hm://remote/3/user/{userId}/` and gets `200` in response Some of the metadata includes: - `UIInteraction 5 zlink player click enable-shuffle` or `UIInteraction 5 zlink player click disable-shuffle ` - `Interaction 1 interactionID player/shuffle-button click shuffle nowplaying/queue spotify:app:queue` - `268 1 desktop-stranger-things Enabled`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/librespot#176
No description provided.