fix: guard music playback process lifecycle

This commit is contained in:
MythEclipse
2026-05-15 17:23:36 +07:00
parent 9e07a0a1f3
commit 1e0a00d82d
2 changed files with 29 additions and 3 deletions
+10 -1
View File
@@ -21,16 +21,23 @@ export function createMusicPlayer(
return {
play(source: ResolvedMediaSource): MusicPlayback {
if (!audioPlayer.isConnected()) {
throw new Error("Discord audio player is not connected");
}
const proc = spawn("ffmpeg", buildFfmpegArgs(source.source), {
stdio: ["ignore", "pipe", "pipe"],
}) as unknown as ChildProcessWithoutNullStreams;
proc.stderr.resume();
audioPlayer.playStream(proc.stdout);
let stopped = false;
const done = new Promise<void>((resolve, reject) => {
proc.on("error", reject);
proc.stdout.on("error", reject);
proc.on("close", (code) => {
if (code === 0) {
if (code === 0 || stopped) {
resolve();
return;
}
@@ -41,6 +48,8 @@ export function createMusicPlayer(
return {
done,
stop() {
if (stopped) return;
stopped = true;
proc.kill("SIGTERM");
audioPlayer.stop();
},
+19 -2
View File
@@ -54,7 +54,22 @@ describe("createMusicPlayer", () => {
expect(discordPlayer.playStream).toHaveBeenCalledWith(proc.stdout);
});
it("kills ffmpeg and stops Discord playback", () => {
it("rejects playback when Discord is not connected", () => {
const spawn = vi.fn(() => new FakeProcess());
const discordPlayer: DiscordAudioPlayer = {
isConnected: () => false,
playStream: vi.fn(),
stop: vi.fn(),
};
const player = createMusicPlayer({ spawn, discordPlayer });
expect(() =>
player.play({ source: "/tmp/song.ogg", title: "song.ogg", kind: "local" }),
).toThrow("Discord audio player is not connected");
expect(spawn).not.toHaveBeenCalled();
});
it("kills ffmpeg and stops Discord playback once", () => {
const proc = new FakeProcess();
const discordPlayer: DiscordAudioPlayer = {
isConnected: () => true,
@@ -65,8 +80,10 @@ describe("createMusicPlayer", () => {
const playback = player.play({ source: "/tmp/song.ogg", title: "song.ogg", kind: "local" });
playback.stop();
playback.stop();
expect(proc.kill).toHaveBeenCalledTimes(1);
expect(proc.kill).toHaveBeenCalledWith("SIGTERM");
expect(discordPlayer.stop).toHaveBeenCalled();
expect(discordPlayer.stop).toHaveBeenCalledTimes(1);
});
});