import { EventEmitter } from "node:events"; import { PassThrough } from "node:stream"; import type { Readable } from "node:stream"; import type { ChildProcess } from "node:child_process"; import type { Client } from "discord.js-selfbot-v13"; import { Streamer as DankStreamer, prepareStream as dankPrepareStream, playStream as dankPlayStream, Utils, Encoders, } from "@dank074/discord-video-stream"; type VoiceConnectionLike = any; type StreamConnectionLike = any; export interface StreamPlayOptions { fps?: number; bitrate?: number | string; includeAudio?: boolean; presetH26x?: string; } export interface StreamSession { connection: VoiceConnectionLike; stream: StreamConnectionLike; play(source: string | Readable, options?: StreamPlayOptions): Promise; stop(): void; } export const UtilsAPI = { normalizeVideoCodec: (c: string) => c.toUpperCase?.() ?? c, }; export class Streamer { client: Client; dankStreamer: DankStreamer; constructor(client: Client) { this.client = client; this.dankStreamer = new DankStreamer(client); } async createSession(guildId: string, channelId: string): Promise { await this.dankStreamer.joinVoice(guildId, channelId); let stopped = false; let currentCommand: any = null; const stop = () => { if (stopped) return; stopped = true; try { if (currentCommand?.kill) currentCommand.kill("SIGKILL"); } catch (e) { // ignore } this.dankStreamer.stopStream(); this.dankStreamer.leaveVoice(); }; return { connection: {} as any, stream: {} as any, play: async (source: string | Readable, options: StreamPlayOptions = {}) => { if (stopped) return; let targetSource: string | Readable = source; if (typeof source === "string" && source.includes("\n")) { const urls = source.split("\n").filter((u) => u.trim()); targetSource = urls[0] ?? source; } const fps = options.fps ?? 30; const bitrateStr = String(options.bitrate ?? 2500).replace(/k$/i, ""); const bitrateVideo = parseInt(bitrateStr, 10) || 2500; const { command, output } = dankPrepareStream(targetSource, { encoder: Encoders.software({ x264: { preset: (options.presetH26x as any) ?? "superfast" }, x265: { preset: (options.presetH26x as any) ?? "superfast" }, }), videoCodec: Utils.normalizeVideoCodec("H264"), width: 1280, height: 720, bitrateVideo: bitrateVideo, frameRate: fps, includeAudio: options.includeAudio !== false, customHeaders: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.3", Connection: "keep-alive", }, }); currentCommand = command; const globalAny: any = globalThis; const onData = (chunk: Buffer) => { try { globalAny.broadcastVideoToWeb?.(chunk); } catch { // ignore } }; output.on("data", onData); command.on("error", (err: Error) => { console.error("Transcoder error:", err); }); try { await dankPlayStream(output, this.dankStreamer, { type: "go-live", width: 1280, height: 720, frameRate: fps, }); } finally { output.off("data", onData); stop(); } }, stop, }; } } export function prepareStream(source: string, _options: any): any { return { command: null, output: new PassThrough() }; } export async function playStream(): Promise { return; } export async function createStreamSession( client: Client, guildId: string, channelId: string, ): Promise { return new Streamer(client).createSession(guildId, channelId); } export async function playPreparedStream( source: string | Readable, session: StreamSession, options: StreamPlayOptions = {}, ): Promise { await session.play(source, options); } export async function playTranscodedPreparedStream( source: string | Readable, session: StreamSession, options: StreamPlayOptions = {}, ): Promise { await session.play(source, options); }