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