Survive revoice's ffmpeg teardown and explain failed deletions
Switching tracks killed the process: revoice's #cleanUp() dereferences this.fProc unconditionally, and its own ffmpeg error handler calls stop() a second time after stop() has already nulled that field. Killing ffmpeg is what triggers that error, so its handlers are detached first, the instance's stop() is wrapped defensively (revoice calls it internally), and an uncaughtException handler keeps the bot in the channel if the dependency throws from another async callback. Failed command-message deletions were logged at debug, i.e. invisible in the default configuration; they now warn with Stoat's error type, and the README says which permission the bot's role needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
315760e076
commit
f22b08b350
+20
-1
@@ -185,6 +185,18 @@ export class GuildPlayer extends EventEmitter<GuildPlayerEvents> {
|
||||
connection.on("userJoin", () => this.clearIdleTimer());
|
||||
|
||||
const media = new MediaPlayer(true);
|
||||
// revoice's #cleanUp() dereferences this.fProc unconditionally, so a second
|
||||
// stop() (which its own ffmpeg error handler triggers) throws and would take
|
||||
// the whole process down. Everything it calls goes through this instance
|
||||
// method, so guarding it here covers its internal paths too.
|
||||
const stop = media.stop.bind(media);
|
||||
media.stop = (init?: boolean) => {
|
||||
try {
|
||||
stop(init);
|
||||
} catch (err) {
|
||||
this.log.debug({ err }, "revoice cleanup threw, ignoring");
|
||||
}
|
||||
};
|
||||
media.on("startplay", () => {
|
||||
this.setStatus(media.paused ? "paused" : "playing");
|
||||
this.startTicker();
|
||||
@@ -298,7 +310,14 @@ export class GuildPlayer extends EventEmitter<GuildPlayerEvents> {
|
||||
if (this.media) {
|
||||
this.expectingStop = true;
|
||||
try {
|
||||
this.media.fProc?.kill("SIGKILL");
|
||||
// Detach revoice's own handlers first: killing ffmpeg makes it emit
|
||||
// "error", and its handler would call stop() again on a half-reset player.
|
||||
const proc = this.media.fProc;
|
||||
if (proc) {
|
||||
proc.removeAllListeners("error");
|
||||
proc.removeAllListeners("end");
|
||||
proc.kill("SIGKILL");
|
||||
}
|
||||
} catch {
|
||||
// ffmpeg may already be gone.
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ export interface MediaPlayerLike {
|
||||
codecData?: { duration?: string } | null;
|
||||
paused: boolean;
|
||||
playing: boolean;
|
||||
fProc?: { kill(signal?: string): void } | null;
|
||||
fProc?: { kill(signal?: string): void; removeAllListeners(event?: string): void } | null;
|
||||
originStream?: { destroy(): void } | null;
|
||||
playStream(input: Readable | string, inputOptions?: string[]): Promise<void>;
|
||||
pause(): void;
|
||||
|
||||
Reference in New Issue
Block a user