UNPKG

av-kit

Version:

AVFoundation Recorder kit for Node.js

167 lines (147 loc) 4.8 kB
import { ChildProcess, spawn } from "child_process"; import { ffmpegPath } from "./utils"; /** * FFmpegProcessManager - Handles the creation, management and termination of FFmpeg processes */ export class FFmpegProcessManager { private processes: Map<string, ChildProcess> = new Map(); /** * Start a new FFmpeg process * * @param processName Unique identifier for the process * @param args FFmpeg command line arguments * @returns Promise that resolves when the process has started successfully */ public startProcess(processName: string, args: string[]): Promise<void> { // Don't allow duplicate process names if (this.processes.has(processName)) { return Promise.reject(new Error(`Process ${processName} already exists`)); } // Start the process const process = spawn(ffmpegPath, args); this.processes.set(processName, process); // Set up error handling for process startup return new Promise<void>((resolve, reject) => { // Handle immediate errors process.on("error", (error: Error) => { console.error(`Error starting ${processName}:`, error); this.processes.delete(processName); reject(error); }); // Install early exit handler let earlyExitHandlerActive = true; process.once("exit", (code: number | null) => { if (earlyExitHandlerActive && code !== 0 && code !== null) { const errorMsg = `${processName} exited early with code ${code}`; console.error(errorMsg); this.processes.delete(processName); reject(new Error(errorMsg)); } }); // After a short delay, assume process started successfully setTimeout(() => { // Disable the early exit handler by setting the flag instead of removing earlyExitHandlerActive = false; // Set up regular exit handler process.on("exit", (code: number | null) => { if (code !== 0 && code !== null) { console.error(`${processName} exited with code ${code}`); } this.processes.delete(processName); }); resolve(); }, 500); // Set up stderr handling to check for startup errors let stderrData = ""; process.stderr.on("data", (data: Buffer) => { stderrData += data.toString(); // Check for common ffmpeg errors if ( stderrData.includes("Cannot open") || stderrData.includes("Error") || stderrData.includes("Invalid") ) { console.error(`FFmpeg error: ${stderrData}`); } }); }); } /** * Stop a specific process * * @param processName Identifier of the process to stop * @returns Promise that resolves when the process has been stopped */ public stopProcess(processName: string): Promise<void> { return new Promise((resolve) => { const process = this.processes.get(processName); if (!process) { resolve(); return; } // Define the cleanup function const cleanup = () => { process.removeAllListeners(); this.processes.delete(processName); resolve(); }; // Handle normal exit process.on("exit", cleanup); // Gracefully terminate the process with SIGTERM if (!process.killed) { process.kill("SIGTERM"); // Set a timeout to force kill if needed setTimeout(() => { if (!process.killed) { try { process.kill("SIGKILL"); } catch (e) { console.error(`Error killing ${processName}:`, e); } } cleanup(); }, 2000); } else { cleanup(); } }); } /** * Stop all processes * * @returns Promise that resolves when all processes have been stopped */ public stopAllProcesses(): Promise<void[]> { const processes = Array.from(this.processes.keys()); const stopPromises = processes.map((processName) => this.stopProcess(processName) ); return Promise.all(stopPromises); } /** * Check if a process exists * * @param processName Name of the process to check * @returns true if the process exists */ public hasProcess(processName: string): boolean { return this.processes.has(processName); } /** * Get a process by name * * @param processName Name of the process to get * @returns The ChildProcess or undefined if not found */ public getProcess(processName: string): ChildProcess | undefined { return this.processes.get(processName); } /** * Get the count of running processes * * @returns Number of running processes */ public getProcessCount(): number { return this.processes.size; } }