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
This commit is contained in:
Lyall
2026-03-12 13:41:50 +00:00
committed by GitHub
parent 16b713bc85
commit dfdac28f53
5 changed files with 32 additions and 46 deletions
@@ -636,7 +636,7 @@ export const NavidromeController: InternalControllerEndpoint = {
throw new Error('Failed to get play queue'); 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) => const entries = items.map((song) =>
ndNormalize.song( ndNormalize.song(
@@ -1137,15 +1137,15 @@ export const SubsonicController: InternalControllerEndpoint = {
const res = await ssApiClient(apiClientProps).getPlayQueueByIndex(); const res = await ssApiClient(apiClientProps).getPlayQueueByIndex();
if (res.status !== 200) { 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 } = 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 { return {
changed, changed: changed ?? '',
changedBy, changedBy: changedBy ?? '',
currentIndex: currentIndex ?? 0, currentIndex: currentIndex ?? 0,
entry: entry:
entry?.map((song) => entry?.map((song) =>
@@ -1157,13 +1157,13 @@ export const SubsonicController: InternalControllerEndpoint = {
), ),
) || [], ) || [],
positionMs: position ?? 0, positionMs: position ?? 0,
username, username: username ?? '',
}; };
} else { } else {
const res = await ssApiClient(apiClientProps).getPlayQueue(); const res = await ssApiClient(apiClientProps).getPlayQueue();
if (res.status !== 200) { 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; const { changed, changedBy, current, entry, position, username } = res.body.playQueue;
@@ -46,22 +46,10 @@ export const useSaveQueue = () => {
throw new Error(t('error.serverRequired', { postProcess: 'sentenceCase' })); throw new Error(t('error.serverRequired', { postProcess: 'sentenceCase' }));
} }
const { player, queue } = usePlayerStore.getState(); const state = usePlayerStore.getState();
let uniqueIds: string[] = []; const queue = state.getQueue();
if (queue.shuffled.length > 0) { if (queue.items.some((item) => item._serverId !== serverId)) {
for (const shuffledIndex of queue.shuffled) {
uniqueIds.push(queue.default[shuffledIndex]);
}
} else {
uniqueIds = queue.default;
}
const songs: string[] = [];
if (uniqueIds.length > 0) {
for (const song of uniqueIds) {
if (queue.songs[song]._serverId !== serverId) {
toast.error({ toast.error({
message: t('error.multipleServerSaveQueueError', { message: t('error.multipleServerSaveQueueError', {
postProcess: 'sentenceCase', postProcess: 'sentenceCase',
@@ -74,17 +62,13 @@ export const useSaveQueue = () => {
); );
} }
songs?.push(queue.songs[song].id);
}
}
try { try {
await api.controller.savePlayQueue({ await api.controller.savePlayQueue({
apiClientProps: { serverId }, apiClientProps: { serverId },
query: { query: {
currentIndex: queue.default.length > 0 ? player.index : undefined, currentIndex: queue.items.length > 0 ? state.player.index : undefined,
positionMs: useTimestampStoreBase.getState().timestamp * 1000, positionMs: useTimestampStoreBase.getState().timestamp * 1000,
songs, songs: queue.items.map((item) => item.id),
}, },
}); });
+1 -1
View File
@@ -709,7 +709,7 @@ const queue = z.object({
createdAt: z.string(), createdAt: z.string(),
current: z.number(), current: z.number(),
id: z.string(), id: z.string(),
items: z.array(song), items: z.array(song).optional(),
position: z.number(), position: z.number(),
updatedAt: z.string(), updatedAt: z.string(),
userId: z.string(), userId: z.string(),
+4 -2
View File
@@ -667,14 +667,16 @@ const playQueue = z.object({
}); });
const playQueueByIndex = z.object({ const playQueueByIndex = z.object({
playQueueByIndex: z.object({ playQueueByIndex: z
.object({
changed: z.string(), changed: z.string(),
changedBy: z.string(), changedBy: z.string(),
currentIndex: z.number().optional(), currentIndex: z.number().optional(),
entry: song.array().optional(), entry: song.array().optional(),
position: z.number().optional(), position: z.number().optional(),
username: z.string(), username: z.string(),
}), })
.optional(),
}); });
const internetRadioStation = z.object({ const internetRadioStation = z.object({