require('dotenv').config(); const express = require('express'); const http = require('http'); const { Server } = require("socket.io"); const axios = require('axios'); const app = express(); const server = http.createServer(app); const io = new Server(server); app.use(express.static('public')); // --- Cloudflare Credentials --- app.get('/api/get-turn-credentials', async (req, res) => { try { const response = await axios.post( `https://rtc.live.cloudflare.com/v1/turn/keys/${process.env.CLOUDFLARE_APP_ID}/credentials/generate-ice-servers`, { ttl: 86400 }, { headers: { 'Authorization': `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`, 'Content-Type': 'application/json' } } ); res.json(response.data); } catch (error) { console.error("Cloudflare error:", error.message); res.status(500).json({ error: "Failed to fetch credentials" }); } }); // --- Signaling Logic --- let streamers = {}; // socketId -> { name: "Gaming Stream" } // Add a map to track relationships: viewerSocketId -> streamerSocketId let relationships = {}; io.on('connection', (socket) => { // 1. Immediately send the list of active streams to the new user socket.emit('streamer_list_update', streamers); // 2. User wants to go live socket.on('start_stream', (name) => { streamers[socket.id] = { name: name }; io.emit('streamer_list_update', streamers); // Notify everyone }); // 3. User wants to watch a specific streamer socket.on('join_stream', (streamerId) => { relationships[socket.id] = streamerId; // Record the link io.to(streamerId).emit('viewer_joined', { viewerId: socket.id }); }); // 4. WebRTC Signaling (Offer/Answer/ICE) socket.on('webrtc_offer', (data) => { io.to(data.target).emit('webrtc_offer', { sdp: data.sdp, sender: socket.id }); }); socket.on('webrtc_answer', (data) => { io.to(data.target).emit('webrtc_answer', { sdp: data.sdp, sender: socket.id }); }); socket.on('ice_candidate', (data) => { io.to(data.target).emit('ice_candidate', { candidate: data.candidate, sender: socket.id }); }); // 5. Cleanup socket.on('disconnect', () => { // 1. If a STREAMER left (existing code) if (streamers[socket.id]) { delete streamers[socket.id]; io.emit('streamer_list_update', streamers); } // 2. If a VIEWER left (NEW CODE) if (relationships[socket.id]) { const streamerId = relationships[socket.id]; // Tell the specific streamer that this specific viewer is gone io.to(streamerId).emit('viewer_left', { viewerId: socket.id }); delete relationships[socket.id]; } }); }); const PORT = process.env.PORT || 3000; server.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));