const peerConnections = {}; async function joinChannelCall() { LogMessage("Current username: " + currentUsername); LogMessage("Current channel: " + currentChannelId); if (!currentUsername || !currentChannelId) { LogMessage("Cannot join RTC: missing username or channel."); return; } await RelaySocket.joinRtcChannel(); await Media.ensureLocalMedia(); const participants = await RelaySocket.getRtcParticipants(); LogMessage("Participants: " + JSON.stringify(participants)); const existingUsers = participants.filter(x => x !== currentUsername); if (existingUsers.length === 0) { LogMessage("Joined call as first participant. Waiting for others..."); return; } for (const username of existingUsers) { await sendOffer(username); } } async function sendOffer(username) { const pc = await ensurePeerConnectionForUser(username); await Media.applyLocalStreamToPeerConnection(pc, username); const offer = await pc.createOffer(); await pc.setLocalDescription(offer); await RelaySocket.sendRtcSignal({ type: "rtc_offer", channelId: currentChannelId, from: currentUsername, to: username, sdp: offer.sdp }); LogMessage(`Sent offer to ${username}`); } async function handleRtcSignal(rawJson) { try { const msg = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson; if (!msg || !msg.type) return; if (msg.from === currentUsername) return; if (msg.to && msg.to !== currentUsername) { LogMessage(`Ignoring RTC signal meant for ${msg.to}`); return; } LogMessage(`Received signal: ${msg.type} from ${msg.from}`); if (msg.type === "rtc_offer") { await handleOffer(msg); return; } if (msg.type === "rtc_answer") { await handleAnswer(msg); return; } if (msg.type === "rtc_ice") { await handleIce(msg); return; } if (msg.type === "rtc_leave") { closePeerConnection(msg.from); return; } LogMessage("Unhandled RTC signal type: " + msg.type); } catch (err) { LogMessage("handleRtcSignal failed: " + err); } } async function handleOffer(msg) { const pc = await ensurePeerConnectionForUser(msg.from); await Media.ensureLocalMedia(); await Media.applyLocalStreamToPeerConnection(pc, msg.from); await pc.setRemoteDescription({ type: "offer", sdp: msg.sdp }); const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); await RelaySocket.sendRtcSignal({ type: "rtc_answer", channelId: currentChannelId, from: currentUsername, to: msg.from, sdp: answer.sdp }); LogMessage(`Sent answer to ${msg.from}`); } async function handleAnswer(msg) { const pc = peerConnections[msg.from]; if (!pc) { LogMessage(`No peer connection found for answer from ${msg.from}`); return; } await pc.setRemoteDescription({ type: "answer", sdp: msg.sdp }); LogMessage(`Applied answer from ${msg.from}`); } async function handleIce(msg) { const pc = peerConnections[msg.from]; if (!pc) { LogMessage(`No peer connection found for ICE from ${msg.from}`); return; } if (!msg.candidate) return; await pc.addIceCandidate(msg.candidate); LogMessage(`Applied ICE from ${msg.from}`); } async function ensurePeerConnectionForUser(username) { if (peerConnections[username]) { return peerConnections[username]; } const pc = new RTCPeerConnection(configuration); peerConnections[username] = pc; pc.onicecandidate = async event => { if (!event.candidate) return; await RelaySocket.sendRtcSignal({ type: "rtc_ice", channelId: currentChannelId, from: currentUsername, to: username, candidate: JSON.stringify(event.candidate) }); }; pc.ontrack = event => { LogMessage(`Remote track received from ${username}`); const stream = event.streams[0]; if (!stream) return; Media.attachRemoteStream(username, stream); }; pc.onconnectionstatechange = () => { LogMessage(`Connection ${username}: ${pc.connectionState}`); if ( pc.connectionState === "failed" || pc.connectionState === "closed" || pc.connectionState === "disconnected" ) { closePeerConnection(username); } }; return pc; } async function leaveChannelCall() { await RelaySocket.sendRtcSignal({ type: "rtc_leave", channelId: currentChannelId, from: currentUsername }); for (const username of Object.keys(peerConnections)) { closePeerConnection(username); } await RelaySocket.leaveRtcChannel(); LogMessage("Left RTC channel"); } function closePeerConnection(username) { const pc = peerConnections[username]; if (!pc) return; pc.close(); delete peerConnections[username]; Media.removeRemoteStream(username); LogMessage(`Closed RTC connection with ${username}`); } window.RelayRtc = { joinChannelCall, leaveChannelCall, handleRtcSignal, peerConnections }; window.handleRtcSignal = handleRtcSignal;