const http = require("http"); const fs = require("fs"); const path = require("path"); const { WebSocketServer } = require("ws"); const PORT = process.env.PORT || 7860; const httpServer = http.createServer((req, res) => { let file = "index.html"; if (req.url === "/admin.html") file = "admin.html"; const filePath = path.join(__dirname, file); fs.readFile(filePath, (err, data) => { if (err) { res.writeHead(404); return res.end("Not found"); } res.writeHead(200, { "Content-Type": "text/html" }); res.end(data); }); }); const wss = new WebSocketServer({ server: httpServer }); let streamerWs = null; let isLive = false; const viewers = new Set(); function sendTo(ws, obj) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(obj)); } wss.on("connection", (ws) => { ws.role = null; ws._vid = null; ws.on("message", (raw) => { let msg; try { msg = JSON.parse(raw); } catch { return; } switch (msg.type) { case "streamer_join": ws.role = "streamer"; streamerWs = ws; sendTo(ws, { type: "streamer_welcome", viewerCount: viewers.size }); break; case "viewer_join": ws.role = "viewer"; ws._vid = Math.random().toString(36).slice(2); viewers.add(ws); sendTo(ws, { type: "viewer_welcome", isLive, vid: ws._vid }); sendTo(streamerWs, { type: "viewer_count", count: viewers.size }); if (isLive) sendTo(streamerWs, { type: "new_viewer", vid: ws._vid }); break; case "go_live": if (ws.role !== "streamer") return; isLive = true; viewers.forEach(v => { sendTo(v, { type: "stream_started" }); }); viewers.forEach(v => sendTo(streamerWs, { type: "new_viewer", vid: v._vid })); break; case "end_live": if (ws.role !== "streamer") return; isLive = false; viewers.forEach(v => sendTo(v, { type: "stream_ended" })); break; case "offer": if (ws.role !== "streamer") return; viewers.forEach(v => { if (v._vid === msg.vid) sendTo(v, { type: "offer", sdp: msg.sdp }); }); break; case "answer": sendTo(streamerWs, { type: "answer", sdp: msg.sdp, vid: ws._vid }); break; case "candidate": if (ws.role === "streamer") { viewers.forEach(v => { if (v._vid === msg.vid) sendTo(v, { type: "candidate", candidate: msg.candidate }); }); } else { sendTo(streamerWs, { type: "candidate", candidate: msg.candidate, vid: ws._vid }); } break; case "viewer_ready": ws._vid = msg.vid || ws._vid; sendTo(streamerWs, { type: "new_viewer", vid: ws._vid }); break; } }); ws.on("close", () => { if (ws.role === "streamer") { streamerWs = null; isLive = false; viewers.forEach(v => sendTo(v, { type: "stream_ended" })); } else if (ws.role === "viewer") { viewers.delete(ws); sendTo(streamerWs, { type: "viewer_count", count: viewers.size }); } }); }); httpServer.listen(PORT, () => console.log(`Running on port ${PORT}`));