Skip to content

Commit f1dfdb0

Browse files
committed
"feat(api-server): add /api/v1/queue/next endpoint
Adds a new API endpoint to get information about the next song in the queue without parsing the full queue response. This addresses the use case described in issue #3614 where users need to preload the next song for VR applications and other integrations. Endpoint returns: - HTTP 200 + song data when next song exists - HTTP 204 when at end of queue or queue is empty Closes #3614"
1 parent fa2862c commit f1dfdb0

File tree

1 file changed

+82
-0
lines changed

1 file changed

+82
-0
lines changed

src/plugins/api-server/backend/routes/control.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,31 @@ const routes = {
411411
},
412412
},
413413
}),
414+
nextSongInfo: createRoute({
415+
method: 'get',
416+
path: `/api/${API_VERSION}/queue/next`,
417+
summary: 'get next song info',
418+
description: 'Get information about the next song in the queue (relative index +1)',
419+
responses: {
420+
200: {
421+
description: 'Success',
422+
content: {
423+
'application/json': {
424+
schema: z.object({
425+
title: z.string().optional(),
426+
videoId: z.string().optional(),
427+
thumbnail: z.any().optional(),
428+
lengthText: z.any().optional(),
429+
shortBylineText: z.any().optional(),
430+
}),
431+
},
432+
},
433+
},
434+
204: {
435+
description: 'No next song in queue',
436+
},
437+
},
438+
}),
414439
queueInfo: createRoute({
415440
method: 'get',
416441
path: `/api/${API_VERSION}/queue`,
@@ -748,6 +773,63 @@ export const register = (
748773
app.openapi(routes.oldQueueInfo, queueInfo);
749774
app.openapi(routes.queueInfo, queueInfo);
750775

776+
app.openapi(routes.nextSongInfo, async (ctx) => {
777+
const queueResponsePromise = new Promise<QueueResponse>((resolve) => {
778+
ipcMain.once('peard:get-queue-response', (_, queue: QueueResponse) => {
779+
return resolve(queue);
780+
});
781+
782+
controller.requestQueueInformation();
783+
});
784+
785+
const queue = await queueResponsePromise;
786+
787+
if (!queue?.items || queue.items.length === 0) {
788+
ctx.status(204);
789+
return ctx.body(null);
790+
}
791+
792+
// Find the currently selected song
793+
const currentIndex = queue.items.findIndex((item) => {
794+
const renderer =
795+
item.playlistPanelVideoRenderer ||
796+
item.playlistPanelVideoWrapperRenderer?.primaryRenderer
797+
?.playlistPanelVideoRenderer;
798+
return renderer?.selected === true;
799+
});
800+
801+
// Get the next song (currentIndex + 1)
802+
const nextIndex = currentIndex + 1;
803+
if (nextIndex >= queue.items.length) {
804+
// No next song available
805+
ctx.status(204);
806+
return ctx.body(null);
807+
}
808+
809+
const nextItem = queue.items[nextIndex];
810+
const nextRenderer =
811+
nextItem.playlistPanelVideoRenderer ||
812+
nextItem.playlistPanelVideoWrapperRenderer?.primaryRenderer
813+
?.playlistPanelVideoRenderer;
814+
815+
if (!nextRenderer) {
816+
ctx.status(204);
817+
return ctx.body(null);
818+
}
819+
820+
// Extract relevant information similar to SongInfo format
821+
const nextSongInfo = {
822+
title: nextRenderer.title?.runs?.[0]?.text,
823+
videoId: nextRenderer.videoId,
824+
thumbnail: nextRenderer.thumbnail,
825+
lengthText: nextRenderer.lengthText,
826+
shortBylineText: nextRenderer.shortBylineText,
827+
};
828+
829+
ctx.status(200);
830+
return ctx.json(nextSongInfo);
831+
});
832+
751833
app.openapi(routes.addSongToQueue, (ctx) => {
752834
const { videoId, insertPosition } = ctx.req.valid('json');
753835
controller.addSongToQueue(videoId, insertPosition);

0 commit comments

Comments
 (0)