Album-Shuffle Mode
Checklist
- [X] I made sure that there are no existing issues - open or closed - to which I could contribute my information.
- [X] I made sure that there are no existing discussions - open or closed - to which I could contribute my information.
- [X] I have read the FAQs inside the app (Menu -> About -> FAQs) and my problem isn't listed.
- [X] I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise.
- [X] This issue contains only one feature request.
- [X] I have read and understood the contribution guidelines.
- [ ] I optionally donated to support the Fossify mission.
Feature description
My suggestion is an album shuffle mode, that considers full albums being the items to shuffle. This mode should play all songs of one album in it's orignial (tagged) song order and then switch to anothor random album.
As a fallback for missing track-numbers, alphabetical songorder could be chosen. If it's a mixture of both, it could be all available track numberes ordered first followed by the remaining album tracks in alphabetical order.
To expand this feature a bit more, there could be an option in the settings how to handle albums spanning over two or more disks, to keep each disk of an album sperated or consider all as one entity. To ease implementation efforts and looking at my personal preference, I would rather go with all disks belong to one album - no split.
Why do you want this feature?
If you like the concept of an album as a complete collection of songs and as one entity of an artits's output, it is currently not possible to apply this to a shuffle mode.
Additional information
No response
Agreed, this is an important one to me. Can anyone with a high-level view of how the app works let us know what needs to happen? I can do some entry level coding but it'd take ages to try to understand the overall structure of the app
edit: I started looking into this today. There's a function Player.shuffledMediaItemsIndices in musicplayer/extensions/Player.kt that seems promising. I think when shuffle mode is started, it uses an android builtin media player library to shuffle the list of track indices. Other functions then parse the indices into the actual tracks to play. If I can crowbar that into logic that returns indices for album-shuffled tracks, then shuffle mode should have the album-shuffling behavior we want. I haven't started digging into how to actually accomplish that, though.
I modified the code and got a hacky album shuffle working well enough for me. This in no way is good code!
**
* Executes [callback] with current track as quickly as possible and then proceeds to load the complete queue with all tracks.
*/
fun getQueuedTracksLazily(callback: (tracks: List<Track>, startIndex: Int, startPositionMs: Long) -> Unit) {
ensureBackgroundThread {
var queueItems = context.queueDAO.getAll()
if (queueItems.isEmpty()) {
initQueue()
queueItems = context.queueDAO.getAll()
}
val currentItem = context.queueDAO.getCurrent()
if (currentItem == null) {
callback(emptyList(), 0, 0)
return@ensureBackgroundThread
}
val currentTrack = getTrack(currentItem.trackId)
if (currentTrack == null) {
callback(emptyList(), 0, 0)
return@ensureBackgroundThread
}
// immediately return the current track.
val startPositionMs = currentItem.lastPosition.seconds.inWholeMilliseconds
callback(listOf(currentTrack), 0, startPositionMs)
/* original
// return the rest of the queued tracks.
val queuedTracks = getQueuedTracks(queueItems)
val currentIndex = queuedTracks.indexOfFirstOrNull { it.mediaStoreId == currentTrack.mediaStoreId } ?: 0
callback(queuedTracks, currentIndex, startPositionMs)
*/
//from chat gpt
val queuedTracks = getQueuedTracks(queueItems)
// Group tracks by album (using album ID or title)
val albums: List<List<Track>> = queuedTracks.groupBy { it.albumId /* or it.album */ }
.values
.map { it.sortedBy { track -> track.trackId } } // sort tracks within album
// Shuffle album order
val shuffledAlbums = albums.shuffled()
// Flatten back to a single list
val shuffledTracks = shuffledAlbums.flatten()
// Find current index in new list
val currentIndex = shuffledTracks.indexOfFirstOrNull { it.mediaStoreId == currentTrack.mediaStoreId } ?: 0
Log.d("AlbumShuffle", "Final shuffled queue:")
shuffledTracks.take(100).forEachIndexed { index, track ->
Log.d("AlbumShuffle", "[$index] ${track.title} from album ${track.album} (trackId: ${track.trackId})")
}
// Return shuffled track list to player
callback(shuffledTracks, currentIndex, startPositionMs)
}
}
To get a fresh album shuffle, it seems like I need to have every song in my UI queue, then empty the queue. Sometimes that crashes the app, sometimes I have to force close it. When it's back I can look at the queue and it's got a new album shuffle - but if I pick a track or album or anything, it'll modify the queue and I have to repeat the procedure to get a new album shuffle.
My mental model is that the app works about like this:
- Something calls Player.maybePreparePlayer . Haven't checked what, probably happens on app startup or first time you want to play something, maybe in the future as well
- In that function, getQueuedTracksLazily is called. Maybe only on the first call of maybePreparePlayer
- there's a queueDAO object that contains the queue. getQueuedTracksLazily gets the queue from it
- getQueuedTracksLazily returns the first track in the queue, then the rest of the queue (lazy loading). maybePreparePlayer now has the queue
- maybePreparePlayer passes the tracks to prepareUsingTracks, which uses setMediaItems to pass them to android's exoplayer library, where they becomes individual Windows in a Timeline
- functions like shuffledMediaItemsIndices ask exoplayer for the indices of the tracks in the Timeline, letting exoplayer handle the shuffling in that case
So there are a ton of places album shuffling could drop in. I think the cleanest would perhaps be to make each album a Window. Or maybe we could override exoplayer's shuffling function?
Is there any app that has this functionality?
PowerAmp does. "Categories"" refers to Albums, Genres, Years, Artists, etc depending on how the content was listed when you started playing. But it's closed source, so no way to see how they do it.
Musicbee has it. But it's not an Android app. I currently don't know any player on Android that can do it (besides PowerAmp, thx to @ttshaw1). That's why I filed the feature request.
I know of 'Oto Music', "Rocket Player', and 'Symfonium' that have this feature.
I'm thinking this could be a hidden dialog that appears when the shuffle button is long-pressed. Example options:
- Shuffle tracks
- Shuffle albums