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');
}
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(
@@ -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;
@@ -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),
},
});
+1 -1
View File
@@ -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(),
+10 -8
View File
@@ -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({