fix(player): retry-skip in /play-playlist when first song can't resolve

QQ Music sometimes refuses to issue a play URL for individual songs
(upstream returns result=104003, "no copyright/payment"). The
/play-playlist endpoint only called resolveAndPlay once on the first
song, so when that one was 104003 the bot would sit silently with no
feedback — exactly what looked like "播放全部 没有反应".

Make BotInstance.playNext() public and call it as a fallback when the
first resolveAndPlay fails. playNext already has the 3-attempt
retry-skip used by trackEnd auto-advance, so we get the same
graceful skip behavior for explicit playlist plays.

Also tighten the response message so the user can tell when the bot
loaded songs but none were playable.

Single-song /play-song still returns "Cannot play" on failure (no
queue to advance through); UX surfacing of that goes in a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.7 committed 2026-05-06 14:43:01 +08:00
1 parent 5799891e4f
commit 82655afa20
2 files changed
+21 -4

No files matched your search

+7 -1
View File
@@ -734,7 +734,13 @@ export class BotInstance extends EventEmitter {
].join("\n"); ].join("\n");
} }
private async playNext(): Promise<void> { /**
* 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<void> {
if (this.isAdvancing || !this.connected) return; if (this.isAdvancing || !this.connected) return;
this.isAdvancing = true; this.isAdvancing = true;
try { try {
+14 -3
View File
@@ -265,11 +265,22 @@ export function createPlayerRouter(
first = queue.play(); first = queue.play();
} }
if (first) { // If the first picked song can't resolve (e.g., QQ song with no
await bot.resolveAndPlay(first); // 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) { } catch (err) {
logger.error({ err }, "Play playlist failed"); logger.error({ err }, "Play playlist failed");
res.status(500).json({ error: (err as Error).message }); res.status(500).json({ error: (err as Error).message });