mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-11-04 10:13:59 +00:00
feat: implement shuffle play functionality for albums, playlists, and saved tracks
This commit is contained in:
parent
50c3b04eb1
commit
78168d269c
@ -36,9 +36,9 @@ class CollectionPlaybackHelper(
|
||||
private val audioPlayerQueue: AudioPlayerQueue,
|
||||
private val blacklistRepository: BlacklistRepository,
|
||||
) {
|
||||
suspend fun playAlbum(albumId: String) {
|
||||
if (audioPlayerQueue.isAlbumPlaying(albumId)) return
|
||||
val entries = fetchAllAlbumTracks(albumId)
|
||||
suspend fun playAlbum(albumId: String, shuffle: Boolean = false) {
|
||||
if (!shuffle && audioPlayerQueue.isAlbumPlaying(albumId)) return
|
||||
val entries = fetchAllAlbumTracks(albumId).let { if (shuffle) it.shuffled() else it }
|
||||
if (entries.isNotEmpty()) {
|
||||
audioPlayerQueue.load(
|
||||
entries = entries,
|
||||
@ -91,9 +91,9 @@ class CollectionPlaybackHelper(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun playPlaylist(playlistId: String) {
|
||||
if (audioPlayerQueue.isPlaylistPlaying(playlistId)) return
|
||||
val entries = fetchAllPlaylistTracks(playlistId)
|
||||
suspend fun playPlaylist(playlistId: String, shuffle: Boolean = false) {
|
||||
if (!shuffle && audioPlayerQueue.isPlaylistPlaying(playlistId)) return
|
||||
val entries = fetchAllPlaylistTracks(playlistId).let { if (shuffle) it.shuffled() else it }
|
||||
if (entries.isNotEmpty()) {
|
||||
audioPlayerQueue.load(
|
||||
entries = entries,
|
||||
@ -146,9 +146,9 @@ class CollectionPlaybackHelper(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun playSavedTracks() {
|
||||
if (audioPlayerQueue.isSavedTracksPlaying()) return
|
||||
val entries = fetchAllSavedTracks()
|
||||
suspend fun playSavedTracks(shuffle: Boolean = false) {
|
||||
if (!shuffle && audioPlayerQueue.isSavedTracksPlaying()) return
|
||||
val entries = fetchAllSavedTracks().let { if (shuffle) it.shuffled() else it }
|
||||
if (entries.isNotEmpty()) {
|
||||
audioPlayerQueue.load(
|
||||
entries = entries,
|
||||
|
||||
@ -172,7 +172,7 @@ class RemoteControlHandler(
|
||||
private suspend fun handleCommand(session: WebSocketServerSession, envelope: CommandEnvelope) {
|
||||
when (val command = envelope.command) {
|
||||
is RemoteControlCommand.Play -> {
|
||||
handleCollectionSource(command.source, RemoteCollectionAction.Play)
|
||||
handleCollectionSource(command.source, RemoteCollectionAction.Play, command.shuffle)
|
||||
}
|
||||
is RemoteControlCommand.Pause -> {
|
||||
audioPlayer.pause()
|
||||
@ -297,13 +297,17 @@ class RemoteControlHandler(
|
||||
* Resolves a `spotube://` collection source URI (playlist/album/artist top
|
||||
* tracks/saved tracks) and applies the requested action on the remote queue.
|
||||
*/
|
||||
private suspend fun handleCollectionSource(source: String, action: RemoteCollectionAction) {
|
||||
private suspend fun handleCollectionSource(
|
||||
source: String,
|
||||
action: RemoteCollectionAction,
|
||||
shuffle: Boolean = false,
|
||||
) {
|
||||
logger.d { "Remote collection $action for source: $source" }
|
||||
when {
|
||||
source.startsWith(COLLECTION_PLAYLIST_PREFIX) -> {
|
||||
val id = source.removePrefix(COLLECTION_PLAYLIST_PREFIX)
|
||||
when (action) {
|
||||
RemoteCollectionAction.Play -> collectionPlaybackHelper.playPlaylist(id)
|
||||
RemoteCollectionAction.Play -> collectionPlaybackHelper.playPlaylist(id, shuffle)
|
||||
RemoteCollectionAction.AddToQueue -> collectionPlaybackHelper.addPlaylistToQueue(id)
|
||||
RemoteCollectionAction.PlayNext -> collectionPlaybackHelper.playPlaylistNext(id)
|
||||
}
|
||||
@ -312,7 +316,7 @@ class RemoteControlHandler(
|
||||
source.startsWith(COLLECTION_ALBUM_PREFIX) -> {
|
||||
val id = source.removePrefix(COLLECTION_ALBUM_PREFIX)
|
||||
when (action) {
|
||||
RemoteCollectionAction.Play -> collectionPlaybackHelper.playAlbum(id)
|
||||
RemoteCollectionAction.Play -> collectionPlaybackHelper.playAlbum(id, shuffle)
|
||||
RemoteCollectionAction.AddToQueue -> collectionPlaybackHelper.addAlbumToQueue(id)
|
||||
RemoteCollectionAction.PlayNext -> collectionPlaybackHelper.playAlbumNext(id)
|
||||
}
|
||||
@ -329,7 +333,7 @@ class RemoteControlHandler(
|
||||
|
||||
source == COLLECTION_SAVED_TRACKS -> {
|
||||
when (action) {
|
||||
RemoteCollectionAction.Play -> collectionPlaybackHelper.playSavedTracks()
|
||||
RemoteCollectionAction.Play -> collectionPlaybackHelper.playSavedTracks(shuffle)
|
||||
RemoteCollectionAction.AddToQueue -> collectionPlaybackHelper.addSavedTracksToQueue()
|
||||
RemoteCollectionAction.PlayNext -> {
|
||||
// Saved tracks "play next" is not supported; add to queue instead
|
||||
@ -467,4 +471,4 @@ enum class ConnectionRequestResponse {
|
||||
Allow,
|
||||
AllowAlways,
|
||||
Deny,
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ import kotlinx.serialization.Serializable
|
||||
sealed class RemoteControlCommand {
|
||||
@Serializable
|
||||
@SerialName("play")
|
||||
data class Play(val source: String) : RemoteControlCommand()
|
||||
data class Play(val source: String, val shuffle: Boolean = false) : RemoteControlCommand()
|
||||
|
||||
@Serializable
|
||||
@SerialName("pause")
|
||||
@ -147,4 +147,4 @@ data class RemoteQueueEntry(
|
||||
val album: String?,
|
||||
val coverUrl: String?,
|
||||
val durationMs: Long,
|
||||
)
|
||||
)
|
||||
|
||||
@ -64,6 +64,7 @@ sealed interface PlaybackDestinationRequest {
|
||||
val type: RemoteCollectionType,
|
||||
val id: String,
|
||||
val startTrack: MetadataTrack? = null,
|
||||
val shuffle: Boolean = false,
|
||||
) : PlaybackDestinationRequest
|
||||
|
||||
data class Track(
|
||||
@ -112,8 +113,9 @@ class RemotePlaybackController(
|
||||
id: String,
|
||||
title: String,
|
||||
startTrack: MetadataTrack? = null,
|
||||
shuffle: Boolean = false,
|
||||
) {
|
||||
request(PlaybackDestinationRequest.Collection(title, PlaybackDestinationAction.Play, type, id, startTrack))
|
||||
request(PlaybackDestinationRequest.Collection(title, PlaybackDestinationAction.Play, type, id, startTrack, shuffle))
|
||||
}
|
||||
|
||||
fun requestCollectionAddToQueue(type: RemoteCollectionType, id: String, title: String) {
|
||||
@ -239,7 +241,7 @@ class RemotePlaybackController(
|
||||
if (startTrack != null) {
|
||||
collectionPlaybackHelper.playPlaylistFromTrack(request.id, startTrack)
|
||||
} else {
|
||||
collectionPlaybackHelper.playPlaylist(request.id)
|
||||
collectionPlaybackHelper.playPlaylist(request.id, request.shuffle)
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,7 +254,7 @@ class RemotePlaybackController(
|
||||
if (startTrack != null) {
|
||||
collectionPlaybackHelper.playAlbumFromTrack(request.id, startTrack)
|
||||
} else {
|
||||
collectionPlaybackHelper.playAlbum(request.id)
|
||||
collectionPlaybackHelper.playAlbum(request.id, request.shuffle)
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,7 +273,7 @@ class RemotePlaybackController(
|
||||
if (startTrack != null) {
|
||||
collectionPlaybackHelper.playSavedTracksFromTrack(startTrack)
|
||||
} else {
|
||||
collectionPlaybackHelper.playSavedTracks()
|
||||
collectionPlaybackHelper.playSavedTracks(request.shuffle)
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,7 +348,7 @@ class RemotePlaybackController(
|
||||
RemoteCollectionType.SavedTracks -> "spotube://saved_tracks"
|
||||
}
|
||||
when (request.action) {
|
||||
PlaybackDestinationAction.Play -> RemoteControlCommand.Play(source)
|
||||
PlaybackDestinationAction.Play -> RemoteControlCommand.Play(source, request.shuffle)
|
||||
PlaybackDestinationAction.AddToQueue -> RemoteControlCommand.AddToQueue(source)
|
||||
PlaybackDestinationAction.PlayNext -> RemoteControlCommand.PlayNext(source)
|
||||
}
|
||||
@ -385,4 +387,4 @@ class RemotePlaybackController(
|
||||
album?.id == other.album?.id &&
|
||||
artists.map { it.id.ifBlank { it.name } } == other.artists.map { it.id.ifBlank { it.name } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ fun AlbumScreen(
|
||||
}
|
||||
},
|
||||
onPlay = viewModel::playAlbum,
|
||||
onShufflePlay = {},
|
||||
onShufflePlay = viewModel::shufflePlayAlbum,
|
||||
onAddToQueue = viewModel::addAlbumToQueue,
|
||||
isPlaying = isAlbumPlaying,
|
||||
isFollowing = savedAlbumIds.contains(albumId),
|
||||
|
||||
@ -224,6 +224,11 @@ class AlbumViewModel(
|
||||
remotePlaybackController.requestCollectionPlay(RemoteCollectionType.Album, albumId, title)
|
||||
}
|
||||
|
||||
fun shufflePlayAlbum() {
|
||||
val title = (_state.value as? AlbumScreenState.Data)?.album?.title ?: "Album"
|
||||
remotePlaybackController.requestCollectionPlay(RemoteCollectionType.Album, albumId, title, shuffle = true)
|
||||
}
|
||||
|
||||
fun addAlbumToQueue() {
|
||||
val title = (_state.value as? AlbumScreenState.Data)?.album?.title ?: "Album"
|
||||
remotePlaybackController.requestCollectionAddToQueue(RemoteCollectionType.Album, albumId, title)
|
||||
|
||||
@ -111,7 +111,7 @@ fun PlaylistScreen(
|
||||
ownerImageURL = ownerImageURL,
|
||||
onOwnerClick = {},
|
||||
onPlay = viewModel::playPlaylist,
|
||||
onShufflePlay = {},
|
||||
onShufflePlay = viewModel::shufflePlayPlaylist,
|
||||
onAddToQueue = viewModel::addPlaylistToQueue,
|
||||
isPlaying = isPlaying,
|
||||
isFollowing = savedPlaylistIds.contains(playlistId),
|
||||
|
||||
@ -230,6 +230,11 @@ class PlaylistViewModel(
|
||||
remotePlaybackController.requestCollectionPlay(RemoteCollectionType.Playlist, playlistId, title)
|
||||
}
|
||||
|
||||
fun shufflePlayPlaylist() {
|
||||
val title = (_state.value as? PlaylistScreenState.Data)?.playlist?.title ?: "Playlist"
|
||||
remotePlaybackController.requestCollectionPlay(RemoteCollectionType.Playlist, playlistId, title, shuffle = true)
|
||||
}
|
||||
|
||||
fun addPlaylistToQueue() {
|
||||
val title = (_state.value as? PlaylistScreenState.Data)?.playlist?.title ?: "Playlist"
|
||||
remotePlaybackController.requestCollectionAddToQueue(RemoteCollectionType.Playlist, playlistId, title)
|
||||
|
||||
@ -70,7 +70,7 @@ fun SavedTracksScreen(
|
||||
ownerImageURL = null,
|
||||
onOwnerClick = {},
|
||||
onPlay = viewModel::playSavedTracks,
|
||||
onShufflePlay = {},
|
||||
onShufflePlay = viewModel::shufflePlaySavedTracks,
|
||||
onAddToQueue = viewModel::addSavedTracksToQueue,
|
||||
isPlaying = isPlaying,
|
||||
isFollowing = false,
|
||||
|
||||
@ -206,6 +206,15 @@ class SavedTracksViewModel(
|
||||
remotePlaybackController.requestCollectionPlay(RemoteCollectionType.SavedTracks, SAVED_TRACKS_COLLECTION_ID, "Saved Tracks")
|
||||
}
|
||||
|
||||
fun shufflePlaySavedTracks() {
|
||||
remotePlaybackController.requestCollectionPlay(
|
||||
RemoteCollectionType.SavedTracks,
|
||||
SAVED_TRACKS_COLLECTION_ID,
|
||||
"Saved Tracks",
|
||||
shuffle = true,
|
||||
)
|
||||
}
|
||||
|
||||
fun addSavedTracksToQueue() {
|
||||
remotePlaybackController.requestCollectionAddToQueue(RemoteCollectionType.SavedTracks, SAVED_TRACKS_COLLECTION_ID, "Saved Tracks")
|
||||
}
|
||||
@ -377,4 +386,4 @@ fun rememberIsSavedTracks(
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ materialKolor = "4.1.1"
|
||||
mqttClient = "2.1.1"
|
||||
mqttBuffer = "6.30.8"
|
||||
murmurhash = "0.4.2"
|
||||
newpipeextractor = "v0.26.2"
|
||||
newpipeextractor = "v0.26.5"
|
||||
newpipeExtractorKmp = "1.3.0"
|
||||
nucleusNucleusApplication = "2.4.6"
|
||||
semver = "2.1.0"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user