From dfdac28f5323fe9d7361e182cbb59cbea0df16fd Mon Sep 17 00:00:00 2001 From: Lyall Date: Thu, 12 Mar 2026 13:41:50 +0000 Subject: [PATCH] Fix server queue saving/restoring on Navidrome and OpenSubsonic (#1828) * fix server queue saving * fix error when attempting to restore empty queue * queue items optional * make playQueueByIndex optional * fix incorrect error message --- .../api/navidrome/navidrome-controller.ts | 2 +- .../api/subsonic/subsonic-controller.ts | 12 ++--- .../player/hooks/use-queue-restore.ts | 44 ++++++------------- src/shared/api/navidrome/navidrome-types.ts | 2 +- src/shared/api/subsonic/subsonic-types.ts | 18 ++++---- 5 files changed, 32 insertions(+), 46 deletions(-) diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 23f5be0b5..660247c3b 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -636,7 +636,7 @@ export const NavidromeController: InternalControllerEndpoint = { throw new Error('Failed to get play queue'); } - const { changedBy, current, items, position, updatedAt } = res.body.data; + const { changedBy, current, items = [], position, updatedAt } = res.body.data; // if there is no queue saved, items is undefined const entries = items.map((song) => ndNormalize.song( diff --git a/src/renderer/api/subsonic/subsonic-controller.ts b/src/renderer/api/subsonic/subsonic-controller.ts index c4811c24a..35c777912 100644 --- a/src/renderer/api/subsonic/subsonic-controller.ts +++ b/src/renderer/api/subsonic/subsonic-controller.ts @@ -1137,15 +1137,15 @@ export const SubsonicController: InternalControllerEndpoint = { const res = await ssApiClient(apiClientProps).getPlayQueueByIndex(); if (res.status !== 200) { - throw new Error('Failed to get random songs'); + throw new Error('Failed to get play queue'); } const { changed, changedBy, currentIndex, entry, position, username } = - res.body.playQueueByIndex; + res.body.playQueueByIndex || {}; // if there is no queue saved, playQueueByIndex may be undefined from a bug in Navidrome return { - changed, - changedBy, + changed: changed ?? '', + changedBy: changedBy ?? '', currentIndex: currentIndex ?? 0, entry: entry?.map((song) => @@ -1157,13 +1157,13 @@ export const SubsonicController: InternalControllerEndpoint = { ), ) || [], positionMs: position ?? 0, - username, + username: username ?? '', }; } else { const res = await ssApiClient(apiClientProps).getPlayQueue(); if (res.status !== 200) { - throw new Error('Failed to get random songs'); + throw new Error('Failed to get play queue'); } const { changed, changedBy, current, entry, position, username } = res.body.playQueue; diff --git a/src/renderer/features/player/hooks/use-queue-restore.ts b/src/renderer/features/player/hooks/use-queue-restore.ts index bd3b38ab3..c889b536b 100644 --- a/src/renderer/features/player/hooks/use-queue-restore.ts +++ b/src/renderer/features/player/hooks/use-queue-restore.ts @@ -46,45 +46,29 @@ export const useSaveQueue = () => { throw new Error(t('error.serverRequired', { postProcess: 'sentenceCase' })); } - const { player, queue } = usePlayerStore.getState(); - let uniqueIds: string[] = []; + const state = usePlayerStore.getState(); + const queue = state.getQueue(); - if (queue.shuffled.length > 0) { - for (const shuffledIndex of queue.shuffled) { - uniqueIds.push(queue.default[shuffledIndex]); - } - } else { - uniqueIds = queue.default; - } + if (queue.items.some((item) => item._serverId !== serverId)) { + toast.error({ + message: t('error.multipleServerSaveQueueError', { + postProcess: 'sentenceCase', + }), + title: t('error.genericError', { postProcess: 'sentenceCase' }), + }); - const songs: string[] = []; - - if (uniqueIds.length > 0) { - for (const song of uniqueIds) { - if (queue.songs[song]._serverId !== serverId) { - toast.error({ - message: t('error.multipleServerSaveQueueError', { - postProcess: 'sentenceCase', - }), - title: t('error.genericError', { postProcess: 'sentenceCase' }), - }); - - throw new Error( - `${t('error.multipleServerSaveQueueError', { postProcess: 'sentenceCase' })}`, - ); - } - - songs?.push(queue.songs[song].id); - } + throw new Error( + `${t('error.multipleServerSaveQueueError', { postProcess: 'sentenceCase' })}`, + ); } try { await api.controller.savePlayQueue({ apiClientProps: { serverId }, query: { - currentIndex: queue.default.length > 0 ? player.index : undefined, + currentIndex: queue.items.length > 0 ? state.player.index : undefined, positionMs: useTimestampStoreBase.getState().timestamp * 1000, - songs, + songs: queue.items.map((item) => item.id), }, }); diff --git a/src/shared/api/navidrome/navidrome-types.ts b/src/shared/api/navidrome/navidrome-types.ts index 16788a271..269b3abed 100644 --- a/src/shared/api/navidrome/navidrome-types.ts +++ b/src/shared/api/navidrome/navidrome-types.ts @@ -709,7 +709,7 @@ const queue = z.object({ createdAt: z.string(), current: z.number(), id: z.string(), - items: z.array(song), + items: z.array(song).optional(), position: z.number(), updatedAt: z.string(), userId: z.string(), diff --git a/src/shared/api/subsonic/subsonic-types.ts b/src/shared/api/subsonic/subsonic-types.ts index 33a24cb70..849a79891 100644 --- a/src/shared/api/subsonic/subsonic-types.ts +++ b/src/shared/api/subsonic/subsonic-types.ts @@ -667,14 +667,16 @@ const playQueue = z.object({ }); const playQueueByIndex = z.object({ - playQueueByIndex: z.object({ - changed: z.string(), - changedBy: z.string(), - currentIndex: z.number().optional(), - entry: song.array().optional(), - position: z.number().optional(), - username: z.string(), - }), + playQueueByIndex: z + .object({ + changed: z.string(), + changedBy: z.string(), + currentIndex: z.number().optional(), + entry: song.array().optional(), + position: z.number().optional(), + username: z.string(), + }) + .optional(), }); const internetRadioStation = z.object({