Files
javis_bot/bot/scripts/stream-test/stream-hold.ts
javis-bot 208fbbc851 feat(selfbot): broadcast desktop audio + smart subtitles in the browse scenario
Two broadcast-experience improvements:

- Audio: the Go-Live stream was video-only. Capture the desktop sound (the
  default PipeWire/Pulse sink monitor, @DEFAULT_MONITOR@) as a second ffmpeg
  input and mux AAC into the mpegts; the library re-encodes it to Opus for
  Discord. Controlled by STREAM_AUDIO / STREAM_AUDIO_SOURCE (default on). ffmpeg
  inherits XDG_RUNTIME_DIR to reach the pulse socket. Verified: the streamer now
  reports "Found audio stream" and the monitor carries Chrome audio (~-11 dB).
- Subtitles: in the browse scenario, default captions OFF, but auto-enable a
  Korean track when the video offers one (getOption captions tracklist ->
  setOption / unloadModule).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 15:50:32 +09:00

50 lines
2.0 KiB
TypeScript

// Persistent selfbot stream holder for manual/operational testing of the
// Go-Live broadcast. Joins the voice channel, goes live, and keeps the stream
// up until stopped (SIGTERM/SIGINT) or HOLD_MS elapses. All parameters come
// from the environment (.env).
//
// bun bot/scripts/stream-test/stream-hold.ts
//
// Requires in .env: DISCORD_SELFBOT_TOKEN, DISCORD_GUILD_ID,
// DISCORD_VOICE_CHANNEL_ID. Stream params: VNC_RESOLUTION, VNC_FRAMERATE,
// VNC_BITRATE_KBPS, STREAM_HW, VNC_DISPLAY (same vars the bot uses).
import "dotenv/config";
import { SelfbotStreamer } from "../../src/stream/selfbot.ts";
const config = {
selfbotToken: process.env.DISCORD_SELFBOT_TOKEN ?? "",
vncDisplay: process.env.VNC_DISPLAY ?? ":1",
vncResolution: process.env.VNC_RESOLUTION ?? "1920x1080",
vncFramerate: parseInt(process.env.VNC_FRAMERATE ?? "60", 10),
vncBitrateKbps: parseInt(process.env.VNC_BITRATE_KBPS ?? "8000", 10),
streamHw: (process.env.STREAM_HW ?? "1") !== "0",
streamAudio: (process.env.STREAM_AUDIO ?? "1") !== "0",
streamAudioSource: process.env.STREAM_AUDIO_SOURCE ?? "@DEFAULT_MONITOR@",
} as any;
const guildId = process.env.DISCORD_GUILD_ID;
const voiceChannelId = process.env.DISCORD_VOICE_CHANNEL_ID;
if (!config.selfbotToken || !guildId || !voiceChannelId) {
console.error("Missing DISCORD_SELFBOT_TOKEN / DISCORD_GUILD_ID / DISCORD_VOICE_CHANNEL_ID in .env");
process.exit(1);
}
const s = new SelfbotStreamer(config);
const maxMs = parseInt(process.env.HOLD_MS ?? "7200000", 10);
let stopped = false;
const stop = async () => {
if (stopped) return;
stopped = true;
console.log("STREAM_STOPPING");
await s.stop();
console.log("STREAM_STOPPED");
process.exit(0);
};
process.on("SIGTERM", stop);
process.on("SIGINT", stop);
const r = await s.start({ guildId, voiceChannelId } as any);
console.log(`STREAM_START: ${r} active:${s.isActive()} (${config.vncResolution}@${config.vncFramerate} ${config.vncBitrateKbps}k hw=${config.streamHw})`);
setTimeout(stop, maxMs);
setInterval(() => {}, 60000);