fix(player): bigger retry budget + return value for /play-playlist

The previous fix for "播放全部 没有反应" hooked into playNext but had
two problems:

1. Used !!queue.current() to detect success, which is always true
   after queue.next() advanced — so the response message would lie
   about playback starting even when all retries failed.

2. The default 3-retry budget is sized for "next song couldn't
   resolve, skip it" cases. User-initiated playlist plays commonly
   hit long contiguous runs of QQ 104003 songs in collected playlists
   (entire ACG/古风 packs can be uniformly unstreamable in some
   regions), so 3-4 attempts wasn't enough.

Make playNext return whether a song actually started, and accept a
maxRetries parameter. /play-playlist now uses 20 retries — high
enough to clear typical unplayable runs, bounded enough that fully
unstreamable playlists still surface a clear "none were playable"
message rather than hanging.

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 15:25:59 +08:00
1 parent 82655afa20
commit 4301da2e37
2 files changed
+18 -14

No files matched your search

+13 -9
View File
@@ -736,21 +736,23 @@ export class BotInstance extends EventEmitter {
/**
* 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
* (e.g., copyright/region restrictions for QQ), skips up to `maxRetries`
* more songs looking for a playable one. Public so REST endpoints that
* seed the queue can fall back to this retry-skip behavior.
*
* Returns true if a song actually started playing, false otherwise.
*/
async playNext(): Promise<void> {
if (this.isAdvancing || !this.connected) return;
async playNext(maxRetries = 3): Promise<boolean> {
if (this.isAdvancing || !this.connected) return false;
this.isAdvancing = true;
try {
this.voteSkipUsers.clear();
const next = this.queue.next();
let started = false;
if (next) {
let started = await this.resolveAndPlay(next);
started = await this.resolveAndPlay(next);
if (!started) {
// Skip to next if URL resolve fails (up to 3 retries)
for (let i = 0; i < 3 && this.connected; i++) {
for (let i = 0; i < maxRetries && this.connected; i++) {
const retry = this.queue.next();
if (!retry) break;
if (await this.resolveAndPlay(retry)) {
@@ -772,8 +774,9 @@ export class BotInstance extends EventEmitter {
await this.refillFm();
const refillNext = this.queue.next();
if (refillNext) {
await this.resolveAndPlay(refillNext);
} else {
started = await this.resolveAndPlay(refillNext);
}
if (!started) {
this.player.stop();
this.profileManager.onSongChange(null).catch(() => {});
}
@@ -783,6 +786,7 @@ export class BotInstance extends EventEmitter {
}
}
this.emit("stateChange");
return started;
} finally {
this.isAdvancing = false;
}