mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-01 20:42:50 +08:00
fix(spotify): resume re-attached PCM stream so mixed-queue Spotify tracks aren't silent [whole-branch C1]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b9fe770ab1
commit
dc763ec689
2 files changed
+64
No files matched your search
@@ -405,4 +405,59 @@ describe("AudioPlayer external-PCM mode (playPcmStream)", () => {
|
|||||||
expect(player.getState()).toBe("playing");
|
expect(player.getState()).toBe("playing");
|
||||||
player.stop();
|
player.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// CORRECTION C1 (whole-branch): mixed queue [spotifyA, neteaseB, spotifyC].
|
||||||
|
// Advancing A -> B (a NON-spotify track) calls stop(), whose detachExternalStream()
|
||||||
|
// PAUSES the backend's long-lived SHARED readable (state.flowing = false). When the
|
||||||
|
// LATER spotify track C reuses the SAME backend, the orchestrator re-attaches that
|
||||||
|
// SAME readable via playPcmStream(). Node's Readable.on('data') only auto-resumes
|
||||||
|
// when flowing !== false, so without an explicit resume() the shared stream stays
|
||||||
|
// paused, onData never fires, pcmBuffer stays empty, and C plays only silence frames.
|
||||||
|
// Regression: after re-attach the shared stream MUST be flowing again and real PCM
|
||||||
|
// MUST reach the player.
|
||||||
|
it("(C1) resumes a re-attached, previously-paused SHARED stream so a later Spotify track isn't silent", async () => {
|
||||||
|
const player = new AudioPlayer(silentLogger);
|
||||||
|
const frames: Buffer[] = [];
|
||||||
|
player.on("frame", (f) => frames.push(f));
|
||||||
|
|
||||||
|
// The backend's long-lived, SHARED readable, reused across every track.
|
||||||
|
const shared = openPcmReadable();
|
||||||
|
|
||||||
|
// --- Spotify track A: first attach (auto-resumes, flowing was null !== false).
|
||||||
|
player.playPcmStream(shared, {});
|
||||||
|
shared.push(Buffer.alloc(FRAME_BYTES * 4));
|
||||||
|
await wait(120);
|
||||||
|
expect(frames.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// --- Advance to a NON-spotify track (neteaseB): play(url) begins with stop(),
|
||||||
|
// which detaches AND pauses the shared stream (state.flowing = false).
|
||||||
|
player.stop();
|
||||||
|
expect(shared.isPaused()).toBe(true); // shared stream is now paused
|
||||||
|
expect(player.getState()).toBe("idle");
|
||||||
|
|
||||||
|
// --- Spotify track C reuses the SAME backend: isExternalActive() is false so the
|
||||||
|
// orchestrator re-attaches the SAME (paused) shared readable.
|
||||||
|
expect(player.isExternalActive()).toBe(false);
|
||||||
|
|
||||||
|
// Spy on the shared stream to observe whether real PCM actually flows to the
|
||||||
|
// player. Adding a 'data' listener while flowing===false does NOT resume it
|
||||||
|
// (Node semantics), so this spy cannot mask the bug — pre-fix it stays at 0.
|
||||||
|
let spyBytes = 0;
|
||||||
|
shared.on("data", (c: Buffer) => {
|
||||||
|
spyBytes += c.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
player.playPcmStream(shared, {}); // re-attach the SAME shared readable
|
||||||
|
|
||||||
|
// The re-attached stream must be flowing again, or track C is silent.
|
||||||
|
expect(shared.isPaused()).toBe(false);
|
||||||
|
|
||||||
|
shared.push(Buffer.alloc(FRAME_BYTES * 4)); // "track C" PCM
|
||||||
|
await wait(120);
|
||||||
|
|
||||||
|
// onData must have run (real PCM reached the player), not just silence frames.
|
||||||
|
expect(spyBytes).toBeGreaterThan(0);
|
||||||
|
expect(player.getState()).toBe("playing");
|
||||||
|
player.stop();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -478,6 +478,15 @@ export class AudioPlayer extends EventEmitter {
|
|||||||
readable.on("end", onEnd);
|
readable.on("end", onEnd);
|
||||||
readable.on("error", onError);
|
readable.on("error", onError);
|
||||||
|
|
||||||
|
// CORRECTION C1: explicitly resume a re-attached, previously-paused Readable.
|
||||||
|
// The backend's SHARED stdout is reused across every track; a prior non-spotify
|
||||||
|
// advance ran stop() -> detachExternalStream() which pause()d it (state.flowing =
|
||||||
|
// false). Node's Readable.on('data') only auto-resumes when flowing !== false, so
|
||||||
|
// re-attaching a paused stream would leave it stuck: onData never fires, pcmBuffer
|
||||||
|
// stays empty, and a later Spotify track plays only silence. resume() is safe/
|
||||||
|
// idempotent on a first attach (never-paused/already-flowing) stream.
|
||||||
|
readable.resume();
|
||||||
|
|
||||||
this.state = "playing";
|
this.state = "playing";
|
||||||
this.startFrameLoop();
|
this.startFrameLoop();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user