fix(voice): screen share crashed gateway — Streamer never joined voice + unhandledRejection
Root cause: ScreenShareController created @dank074 Streamer but never called streamer.joinVoiceChannel() — playStream threw 'Bot is not connected to a voice channel', and since the code only used .finally() (no .catch), the rejection became an unhandledRejection that took down the whole gateway (graceful shutdown triggered, systemd restarted). Fixes: 1. Resolve active channel + streamer.joinVoiceChannel(channel) before prepareStream/playStream (dank074 needs its OWN WebRTC voice connection). 2. .catch() on the playStream done promise — log + kill ffmpeg instead of crashing the process. 3. .catch() on playback.done in media.handler too. 4. stop() now kills ffmpeg AND stops the streamer's voice connection.
This commit is contained in:
@@ -178,12 +178,19 @@ export class MediaHandler {
|
||||
addedAt: Date.now(),
|
||||
status: "playing",
|
||||
};
|
||||
playback.done.finally(() => {
|
||||
this.screenPlayback = null;
|
||||
if (currentTrackItem?.mode === "screen") {
|
||||
currentTrackItem = null;
|
||||
}
|
||||
});
|
||||
playback.done
|
||||
.catch((err) => {
|
||||
this.logger.error(
|
||||
{ error: err instanceof Error ? err.message : String(err) },
|
||||
"Screen playback promise rejected",
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
this.screenPlayback = null;
|
||||
if (currentTrackItem?.mode === "screen") {
|
||||
currentTrackItem = null;
|
||||
}
|
||||
});
|
||||
this.logger.info({ url }, "Screen share started");
|
||||
return { id: cmd.id, success: true, data: buildStatusPayload() };
|
||||
} catch (err) {
|
||||
|
||||
@@ -55,6 +55,12 @@ export interface ScreenSharePlayback {
|
||||
stop(): void;
|
||||
}
|
||||
|
||||
export interface ScreenShareVoiceStatus {
|
||||
connected: boolean;
|
||||
activeGuildId: string | null;
|
||||
activeChannelId: string | null;
|
||||
}
|
||||
|
||||
export interface ScreenShareController {
|
||||
isActive(): boolean;
|
||||
start(source: string): Promise<ScreenSharePlayback>;
|
||||
|
||||
@@ -60,6 +60,22 @@ export class ScreenShareController {
|
||||
this.streamer = new Streamer(this.client);
|
||||
}
|
||||
|
||||
// The dank074 Streamer needs its OWN voice connection — it cannot reuse
|
||||
// the @discordjs/voice connection owned by VoiceController/recorder.
|
||||
// Resolve the channel object and join via the Streamer's raw gateway
|
||||
// path (joinVoiceChannel → joinVoice → WebRTC).
|
||||
const guild = this.client.guilds.cache.get(status.activeGuildId);
|
||||
const channel = guild?.channels.cache.get(status.activeChannelId);
|
||||
if (
|
||||
!channel ||
|
||||
(channel.type !== "GUILD_VOICE" && channel.type !== "GUILD_STAGE_VOICE")
|
||||
) {
|
||||
throw new Error(
|
||||
`Voice channel ${status.activeChannelId} not found for screen share`,
|
||||
);
|
||||
}
|
||||
await this.streamer.joinVoiceChannel(channel);
|
||||
|
||||
const { command, output } = prepareStream(directUrl, {
|
||||
encoder: Encoders.software({ x264: { preset: "superfast" } }),
|
||||
width: 1280,
|
||||
@@ -74,15 +90,43 @@ export class ScreenShareController {
|
||||
let stopped = false;
|
||||
const done = playStream(output, this.streamer, {
|
||||
type: "go-live",
|
||||
}).finally(() => {
|
||||
this.active = null;
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
// Never let a stream failure become an unhandledRejection — that
|
||||
// crashed the whole gateway. Log + surface via the done promise.
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
this.logger.error(
|
||||
{ error: message, source },
|
||||
"Screen stream failed during playback",
|
||||
);
|
||||
if (!stopped) {
|
||||
stopped = true;
|
||||
try {
|
||||
command.kill("SIGTERM");
|
||||
} catch {
|
||||
/* already dead */
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.active = null;
|
||||
});
|
||||
this.active = {
|
||||
done,
|
||||
stop: () => {
|
||||
if (stopped) return;
|
||||
stopped = true;
|
||||
command.kill("SIGTERM");
|
||||
try {
|
||||
command.kill("SIGTERM");
|
||||
} catch {
|
||||
/* already dead */
|
||||
}
|
||||
// Leave the voice channel the Streamer joined (its own connection).
|
||||
try {
|
||||
this.streamer?.voiceConnection?.stop();
|
||||
} catch {
|
||||
/* already gone */
|
||||
}
|
||||
this.active = null;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user