// server.js const path = require('path'); const express = require('express'); const http = require('http'); const WebSocket = require('ws'); const mediasoup = require('mediasoup'); const app = express(); const PORT = 7860; // Serve static files from ./public app.use(express.static(path.join(__dirname, 'public'))); let worker, router; const producerTransports = new Map(); // ws → { transport, producer } const consumerTransports = new Map(); // ws → [ { transport, consumer } ] ;(async () => { // 1) Create the mediasoup Worker + Router worker = await mediasoup.createWorker(); router = await worker.createRouter({ mediaCodecs: [ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2 }, { kind: 'video', mimeType: 'video/VP8', clockRate: 90000 } ] }); // 2) Expose RTP capabilities via HTTP for the clients app.get('/rtpCapabilities', (req, res) => { res.json({ rtpCapabilities: router.rtpCapabilities }); }); // 3) Create HTTP + WebSocket server const server = http.createServer(app); const wss = new WebSocket.Server({ server }); wss.on('connection', ws => { ws.on('message', async msg => { const { action, data } = JSON.parse(msg); switch (action) { case 'createProducerTransport': { const transport = await router.createWebRtcTransport({ listenIps: ['0.0.0.0'], // listenIps: ['127.0.0.1'], enableUdp: true, enableTcp: true }); producerTransports.set(ws, { transport, producer: null }); ws.send(JSON.stringify({ action: 'producerTransportCreated', data: { id: transport.id, iceParameters: transport.iceParameters, iceCandidates: transport.iceCandidates, dtlsParameters: transport.dtlsParameters } })); break; } case 'connectProducerTransport': { const { dtlsParameters } = data; const { transport } = producerTransports.get(ws); await transport.connect({ dtlsParameters }); ws.send(JSON.stringify({ action: 'producerTransportConnected' })); break; } case 'produce': { const { kind, rtpParameters } = data; const { transport } = producerTransports.get(ws); const producer = await transport.produce({ kind, rtpParameters }); producerTransports.get(ws).producer = producer; ws.send(JSON.stringify({ action: 'produced', data: { producerId: producer.id } })); break; } case 'createConsumerTransport': { const transport = await router.createWebRtcTransport({ listenIps: ['0.0.0.0'], // listenIps: ['127.0.0.1'], enableUdp: true, enableTcp: true }); const arr = consumerTransports.get(ws) || []; arr.push({ transport, consumer: null }); consumerTransports.set(ws, arr); ws.send(JSON.stringify({ action: 'consumerTransportCreated', data: { id: transport.id, iceParameters: transport.iceParameters, iceCandidates: transport.iceCandidates, dtlsParameters: transport.dtlsParameters } })); break; } case 'connectConsumerTransport': { const { dtlsParameters } = data; const arr = consumerTransports.get(ws); await arr[arr.length - 1].transport.connect({ dtlsParameters }); ws.send(JSON.stringify({ action: 'consumerTransportConnected' })); break; } case 'consume': { const producerEntry = [...producerTransports.values()].find(e => e.producer); if (!producerEntry) { ws.send(JSON.stringify({ action: 'consumeError', data: 'No producer available yet' })); return; } const producer = producerEntry.producer; const arr = consumerTransports.get(ws); const { transport } = arr[arr.length - 1]; const consumer = await transport.consume({ producerId: producer.id, rtpCapabilities: router.rtpCapabilities, paused: false }); arr[arr.length - 1].consumer = consumer; ws.send(JSON.stringify({ action: 'consumed', data: { producerId: producer.id, id: consumer.id, kind: consumer.kind, rtpParameters: consumer.rtpParameters } })); break; } } }); ws.on('close', () => { // Clean up transports & producers/consumers for this ws const pEntry = producerTransports.get(ws); if (pEntry) { pEntry.producer?.close(); pEntry.transport.close(); producerTransports.delete(ws); } const cEntries = consumerTransports.get(ws) || []; cEntries.forEach(({ consumer, transport }) => { consumer?.close(); transport.close(); }); consumerTransports.delete(ws); }); }); // 4) Start the server server.listen(PORT, () => console.log(`SFU demo listening on http://localhost:${PORT}`)); })();