import { spawn as nodeSpawn } from "node:child_process"; import type { ChildProcessWithoutNullStreams } from "node:child_process"; import { discordPlayer } from "../player"; import type { DiscordAudioPlayer, MusicPlayback, MusicPlayer, ResolvedMediaSource, } from "./mediaTypes"; export interface MusicPlayerDependencies { spawn?: typeof nodeSpawn; discordPlayer?: DiscordAudioPlayer; } export function createMusicPlayer( dependencies: MusicPlayerDependencies = {}, ): MusicPlayer { const spawn = dependencies.spawn ?? nodeSpawn; const audioPlayer = dependencies.discordPlayer ?? discordPlayer; return { play(source: ResolvedMediaSource): MusicPlayback { const proc = spawn("ffmpeg", buildFfmpegArgs(source.source), { stdio: ["ignore", "pipe", "pipe"], }) as unknown as ChildProcessWithoutNullStreams; audioPlayer.playStream(proc.stdout); const done = new Promise((resolve, reject) => { proc.on("error", reject); proc.on("close", (code) => { if (code === 0) { resolve(); return; } reject(new Error(`ffmpeg exited with code ${code}`)); }); }); return { done, stop() { proc.kill("SIGTERM"); audioPlayer.stop(); }, }; }, }; } export function buildFfmpegArgs(source: string): string[] { return [ "-hide_banner", "-loglevel", "warning", "-i", source, "-vn", "-acodec", "libopus", "-ar", "48000", "-ac", "2", "-f", "ogg", "pipe:1", ]; }