import { createRequire } from "module"; const require = createRequire(import.meta.url); const NodeMediaServer = require("node-media-server"); const { randomBytes } = require("crypto"); import path from "path"; let nms: any = null; let isRunning = false; const activeStreams = new Map(); const config = { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 30, ping_timeout: 60, listen: "0.0.0.0", // Bind to all interfaces }, http: { port: 8001, mediaroot: path.join(process.cwd(), "media"), allow_origin: "*", }, }; function generateStreamKey() { return randomBytes(16).toString("hex"); } export function startRTMPServer() { try { if (isRunning && nms) { console.log("[RTMP] Server is already running"); return true; } console.log("[RTMP] Starting server..."); nms = new NodeMediaServer(config); // Use property-based event assignment (NodeMediaServer v4+) nms.onPrePublish = async (id: string, streamPath: string, args: any) => { try { const streamKey = streamPath.split("/")[2]; console.log(`[RTMP] Stream started with key: ${streamKey}`); if (!isNaN(Number(streamKey))) { activeStreams.set(Number(streamKey), id); } } catch (error) { console.error("[RTMP] Error in onPrePublish:", error); } }; nms.onDonePublish = async (id: string, streamPath: string, args: any) => { try { const streamKey = streamPath.split("/")[2]; console.log(`[RTMP] Stream ended with key: ${streamKey}`); if (!isNaN(Number(streamKey))) { activeStreams.delete(Number(streamKey)); } } catch (error) { console.error("[RTMP] Error in onDonePublish:", error); } }; nms.onError = (err: any) => { console.error("[RTMP] Server Error:", err); isRunning = false; }; nms.run(); isRunning = true; console.log("[RTMP] Server started on port", config.rtmp.port); return true; } catch (error) { console.error("[RTMP] Error starting server:", error); isRunning = false; nms = null; return false; } } export function stopRTMPServer() { if (nms && isRunning) { try { nms.stop(); isRunning = false; nms = null; activeStreams.clear(); console.log("[RTMP] Server stopped"); return true; } catch (error) { console.error("[RTMP] Error stopping server:", error); isRunning = false; nms = null; return false; } } return false; } export function isRTMPServerRunning(): boolean { return isRunning && nms !== null; } export function getRTMPUrl(sessionId: number): string { const baseURL = process.env.RTMP_SERVER_URL || "rtmp://localhost"; return `${baseURL}/live/${sessionId}`; } export function isStreamActive(sessionId: number): boolean { try { if (!nms || !isRunning) { console.log("[RTMP] Server not running, returning false"); return false; } // Check in our activeStreams map first if (activeStreams.has(sessionId)) { console.log(`[RTMP] Found active stream for session ${sessionId} in activeStreams map`); return true; } // Get active clients from node-media-server const clients = nms.clients; if (!clients) { console.log("[RTMP] No clients object available"); return false; } // The stream path we're looking for const streamPath = `/live/${sessionId}`; // Check all active publishers for (let id in clients) { const client = clients[id]; if (client && client.publishing && client.publishStreamPath === streamPath) { console.log(`[RTMP] Found active publisher for session ${sessionId}`); // Update our map if we found an active stream activeStreams.set(sessionId, id); return true; } } console.log(`[RTMP] No active stream found for session ${sessionId}`); return false; } catch (error) { console.error(`[RTMP] Error checking stream status for session ${sessionId}:`, error); return false; } }