diff --git a/src/bot/instance.ts b/src/bot/instance.ts index 8af5c0b..0478483 100755 --- a/src/bot/instance.ts +++ b/src/bot/instance.ts @@ -734,7 +734,13 @@ export class BotInstance extends EventEmitter { ].join("\n"); } - private async playNext(): Promise { + /** + * Advance the queue and play the next song. If the resolved URL fails + * (e.g., copyright/region restrictions for QQ), skips up to 3 more + * songs looking for a playable one. Public so REST endpoints that + * seed the queue can fall back to this retry-skip behavior. + */ + async playNext(): Promise { if (this.isAdvancing || !this.connected) return; this.isAdvancing = true; try { diff --git a/src/web/api/player.ts b/src/web/api/player.ts index 312ee6a..68f7cb6 100644 --- a/src/web/api/player.ts +++ b/src/web/api/player.ts @@ -265,11 +265,22 @@ export function createPlayerRouter( first = queue.play(); } - if (first) { - await bot.resolveAndPlay(first); + // If the first picked song can't resolve (e.g., QQ song with no + // streaming entitlement → result 104003), fall back to the same + // retry-skip behavior playNext uses for trackEnd auto-advance. + // Otherwise the bot would sit silently on a dead song. + let started = first ? await bot.resolveAndPlay(first) : false; + if (first && !started) { + await bot.playNext(); + started = !!queue.current(); } - res.json({ message: `Loaded ${songs.length} songs. Now playing: ${first?.name ?? "unknown"}` }); + const playing = queue.current(); + if (started && playing) { + res.json({ message: `Loaded ${songs.length} songs. Now playing: ${playing.name}` }); + } else { + res.json({ message: `Loaded ${songs.length} songs but none were playable (likely copyright/region restrictions).` }); + } } catch (err) { logger.error({ err }, "Play playlist failed"); res.status(500).json({ error: (err as Error).message });