From f1dfdb04bacfb076b88f5e56b0ee66e4a5dbf7bf Mon Sep 17 00:00:00 2001 From: amontariol <51965949+amontariol@users.noreply.github.com> Date: Thu, 27 Nov 2025 12:00:15 +0100 Subject: [PATCH 1/3] "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" --- .../api-server/backend/routes/control.ts | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/plugins/api-server/backend/routes/control.ts b/src/plugins/api-server/backend/routes/control.ts index bd8cf78447..731cd18ade 100644 --- a/src/plugins/api-server/backend/routes/control.ts +++ b/src/plugins/api-server/backend/routes/control.ts @@ -411,6 +411,31 @@ const routes = { }, }, }), + nextSongInfo: createRoute({ + method: 'get', + path: `/api/${API_VERSION}/queue/next`, + summary: 'get next song info', + description: 'Get information about the next song in the queue (relative index +1)', + responses: { + 200: { + description: 'Success', + content: { + 'application/json': { + schema: z.object({ + title: z.string().optional(), + videoId: z.string().optional(), + thumbnail: z.any().optional(), + lengthText: z.any().optional(), + shortBylineText: z.any().optional(), + }), + }, + }, + }, + 204: { + description: 'No next song in queue', + }, + }, + }), queueInfo: createRoute({ method: 'get', path: `/api/${API_VERSION}/queue`, @@ -748,6 +773,63 @@ export const register = ( app.openapi(routes.oldQueueInfo, queueInfo); app.openapi(routes.queueInfo, queueInfo); + app.openapi(routes.nextSongInfo, async (ctx) => { + const queueResponsePromise = new Promise((resolve) => { + ipcMain.once('peard:get-queue-response', (_, queue: QueueResponse) => { + return resolve(queue); + }); + + controller.requestQueueInformation(); + }); + + const queue = await queueResponsePromise; + + if (!queue?.items || queue.items.length === 0) { + ctx.status(204); + return ctx.body(null); + } + + // Find the currently selected song + const currentIndex = queue.items.findIndex((item) => { + const renderer = + item.playlistPanelVideoRenderer || + item.playlistPanelVideoWrapperRenderer?.primaryRenderer + ?.playlistPanelVideoRenderer; + return renderer?.selected === true; + }); + + // Get the next song (currentIndex + 1) + const nextIndex = currentIndex + 1; + if (nextIndex >= queue.items.length) { + // No next song available + ctx.status(204); + return ctx.body(null); + } + + const nextItem = queue.items[nextIndex]; + const nextRenderer = + nextItem.playlistPanelVideoRenderer || + nextItem.playlistPanelVideoWrapperRenderer?.primaryRenderer + ?.playlistPanelVideoRenderer; + + if (!nextRenderer) { + ctx.status(204); + return ctx.body(null); + } + + // Extract relevant information similar to SongInfo format + const nextSongInfo = { + title: nextRenderer.title?.runs?.[0]?.text, + videoId: nextRenderer.videoId, + thumbnail: nextRenderer.thumbnail, + lengthText: nextRenderer.lengthText, + shortBylineText: nextRenderer.shortBylineText, + }; + + ctx.status(200); + return ctx.json(nextSongInfo); + }); + app.openapi(routes.addSongToQueue, (ctx) => { const { videoId, insertPosition } = ctx.req.valid('json'); controller.addSongToQueue(videoId, insertPosition); From 0deedc4dcd12171f831b7d17cc706a009a964004 Mon Sep 17 00:00:00 2001 From: Adrien Montariol <51965949+amontariol@users.noreply.github.com> Date: Thu, 27 Nov 2025 12:08:04 +0100 Subject: [PATCH 2/3] Update src/plugins/api-server/backend/routes/control.ts Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/plugins/api-server/backend/routes/control.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/api-server/backend/routes/control.ts b/src/plugins/api-server/backend/routes/control.ts index 731cd18ade..32b4f235da 100644 --- a/src/plugins/api-server/backend/routes/control.ts +++ b/src/plugins/api-server/backend/routes/control.ts @@ -415,7 +415,8 @@ const routes = { method: 'get', path: `/api/${API_VERSION}/queue/next`, summary: 'get next song info', - description: 'Get information about the next song in the queue (relative index +1)', + description: + 'Get information about the next song in the queue (relative index +1)', responses: { 200: { description: 'Success', From 6bd792cc09d2958165f3d5661395ede40d5aad27 Mon Sep 17 00:00:00 2001 From: amontariol <51965949+amontariol@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:08:20 +0100 Subject: [PATCH 3/3] changed z.any() to SongInfoSchema --- src/plugins/api-server/backend/routes/control.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/plugins/api-server/backend/routes/control.ts b/src/plugins/api-server/backend/routes/control.ts index 32b4f235da..8c1d2db450 100644 --- a/src/plugins/api-server/backend/routes/control.ts +++ b/src/plugins/api-server/backend/routes/control.ts @@ -422,13 +422,7 @@ const routes = { description: 'Success', content: { 'application/json': { - schema: z.object({ - title: z.string().optional(), - videoId: z.string().optional(), - thumbnail: z.any().optional(), - lengthText: z.any().optional(), - shortBylineText: z.any().optional(), - }), + schema: SongInfoSchema, }, }, },