import type { Readable } from "node:stream"; import { playStream as defaultPlayStream, prepareStream as defaultPrepareStream, Encoders, Utils, } from "@dank074/discord-video-stream"; import { AppError } from "../errors"; import { discordPlayer } from "../player"; import type { DiscordPlayerOwner, ScreenSharePlayback } from "./mediaTypes"; import { createYtDlp } from "./ytdlp"; export interface ScreenShareVoiceStatus { connected: boolean; activeGuildId: string | null; activeChannelId: string | null; } interface PreparedScreenStream { command: { kill?: (signal: NodeJS.Signals) => unknown }; output: Readable; } type PrepareScreenStream = ( source: string, options: object, ) => PreparedScreenStream; type PlayScreenStream = ( output: Readable, streamer: unknown, options: { type: "go-live" }, ) => Promise; export interface ScreenShareControllerDependencies { getVoiceStatus: () => ScreenShareVoiceStatus; getPlayerOwner?: () => DiscordPlayerOwner; getDirectVideoUrl?: (source: string) => Promise; prepareStream?: PrepareScreenStream; playStream?: PlayScreenStream; streamer: unknown; } export function createScreenShareController( dependencies: ScreenShareControllerDependencies, ) { let active: ScreenSharePlayback | null = null; const ytdlp = createYtDlp(); const getPlayerOwner = dependencies.getPlayerOwner ?? (() => discordPlayer.getOwner()); const getDirectVideoUrl = dependencies.getDirectVideoUrl ?? ((source) => ytdlp.getDirectVideoUrl(source)); const prepareStream = dependencies.prepareStream ?? (defaultPrepareStream as unknown as PrepareScreenStream); const playStream = dependencies.playStream ?? (defaultPlayStream as unknown as PlayScreenStream); return { isActive(): boolean { return active !== null; }, async start(source: string): Promise { const status = dependencies.getVoiceStatus(); if ( !status.connected || !status.activeGuildId || !status.activeChannelId ) { throw new AppError( "Connect to a voice channel before sharing screen", "VOICE_NOT_CONNECTED", 409, ); } if (active || getPlayerOwner() !== "none") { throw new AppError("Another media mode is active", "MEDIA_BUSY", 409); } try { const directUrl = await getDirectVideoUrl(source); const { command, output } = prepareStream(directUrl, { encoder: Encoders.software({ x264: { preset: "superfast" } }), height: 720, frameRate: 30, bitrateVideo: 2500, bitrateVideoMax: 4000, includeAudio: true, videoCodec: Utils.normalizeVideoCodec("H264"), }); let stopped = false; const done = playStream(output, dependencies.streamer, { type: "go-live", }).finally(() => { active = null; }); active = { done, stop() { if (stopped) return; stopped = true; command.kill?.("SIGTERM"); active = null; }, }; return active; } catch (error) { active = null; throw new AppError( error instanceof Error ? error.message : "Screen stream failed", "SCREEN_STREAM_FAILED", 500, ); } }, }; }