| const socket = io(); |
| const chatBox = document.getElementById('chat-box'); |
| const chatInput = document.getElementById('chat-input'); |
| const sendBtn = document.getElementById('send-btn'); |
| const joinBtn = document.getElementById('join-btn'); |
| const statusText = document.getElementById('status'); |
| const audioContainer = document.getElementById('audio-container'); |
|
|
| let localStream; |
| const peers = {}; |
|
|
| |
| |
| const rtcConfig = { |
| iceServers: [ |
| { urls: 'stun:stun.l.google.com:19302' }, |
| { urls: 'stun:stun1.l.google.com:19302' }, |
| { |
| urls: 'turn:saman.metered.live:80', |
| username: '1bb6c42832c6a26b4db391e4', |
| credential: 'axHWrYuQbsBaG5u9' |
| }, |
| { |
| urls: 'turn:saman.metered.live:443', |
| username: '1bb6c42832c6a26b4db391e4', |
| credential: 'axHWrYuQbsBaG5u9' |
| }, |
| { |
| urls: 'turn:saman.metered.live:443?transport=tcp', |
| username: '1bb6c42832c6a26b4db391e4', |
| credential: 'axHWrYuQbsBaG5u9' |
| } |
| ] |
| }; |
|
|
| |
| sendBtn.onclick = () => { |
| const text = chatInput.value.trim(); |
| if (text) { |
| socket.emit('chat-message', text); |
| chatInput.value = ''; |
| } |
| }; |
|
|
| socket.on('chat-message', (data) => { |
| const div = document.createElement('div'); |
| div.className = 'message ' + (data.id === socket.id ? 'self' : ''); |
| div.innerText = `${data.id === socket.id ? 'You' : data.id.substring(0,4)}: ${data.text}`; |
| chatBox.appendChild(div); |
| chatBox.scrollTop = chatBox.scrollHeight; |
| }); |
|
|
| |
| chatInput.addEventListener("keypress", function(event) { |
| if (event.key === "Enter") { |
| event.preventDefault(); |
| sendBtn.click(); |
| } |
| }); |
|
|
| |
| joinBtn.onclick = async () => { |
| try { |
| |
| localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); |
| statusText.innerText = "Mic Online - You are in the call"; |
| joinBtn.disabled = true; |
| |
| |
| socket.emit('join-room'); |
| } catch (err) { |
| alert("Microphone access denied or not found."); |
| console.error(err); |
| } |
| }; |
|
|
| |
| socket.on('user-connected', (userId) => { |
| if (!localStream) return; |
| |
| appendSystemMessage(`User ${userId.substring(0,4)} joined. Attempting connection...`); |
| const peer = createPeerConnection(userId, true); |
| peers[userId] = peer; |
| }); |
|
|
| |
| socket.on('signal', async (data) => { |
| if (!localStream) return; |
|
|
| const { from, signal } = data; |
| |
| |
| if (!peers[from]) { |
| peers[from] = createPeerConnection(from, false); |
| } |
|
|
| const peer = peers[from]; |
|
|
| try { |
| if (signal.type === 'offer') { |
| await peer.setRemoteDescription(new RTCSessionDescription(signal)); |
| const answer = await peer.createAnswer(); |
| await peer.setLocalDescription(answer); |
| socket.emit('signal', { to: from, signal: peer.localDescription }); |
| } else if (signal.type === 'answer') { |
| await peer.setRemoteDescription(new RTCSessionDescription(signal)); |
| } else if (signal.candidate) { |
| await peer.addIceCandidate(new RTCIceCandidate(signal)); |
| } |
| } catch (err) { |
| console.error("Signaling error:", err); |
| } |
| }); |
|
|
| |
| socket.on('user-disconnected', (userId) => { |
| if (peers[userId]) { |
| peers[userId].close(); |
| delete peers[userId]; |
| |
| |
| const audioEl = document.getElementById(`audio-${userId}`); |
| if (audioEl) audioEl.remove(); |
| |
| appendSystemMessage(`User ${userId.substring(0,4)} left.`); |
| } |
| }); |
|
|
| |
| function createPeerConnection(targetUserId, isInitiator) { |
| const peer = new RTCPeerConnection(rtcConfig); |
|
|
| |
| localStream.getTracks().forEach(track => peer.addTrack(track, localStream)); |
|
|
| |
| peer.onicecandidate = (event) => { |
| if (event.candidate) { |
| socket.emit('signal', { to: targetUserId, signal: event.candidate }); |
| } |
| }; |
|
|
| |
| peer.oniceconnectionstatechange = () => { |
| console.log(`Connection state with ${targetUserId}:`, peer.iceConnectionState); |
| if (peer.iceConnectionState === 'failed') { |
| appendSystemMessage(`❌ Connection to ${targetUserId.substring(0,4)} failed. Firewalls blocked it.`); |
| } else if (peer.iceConnectionState === 'connected') { |
| appendSystemMessage(`✅ Successfully connected to ${targetUserId.substring(0,4)}! Audio flowing.`); |
| } else if (peer.iceConnectionState === 'disconnected') { |
| appendSystemMessage(`⚠️ Connection to ${targetUserId.substring(0,4)} lost. Trying to reconnect...`); |
| } |
| }; |
|
|
| |
| peer.ontrack = (event) => { |
| let audioEl = document.getElementById(`audio-${targetUserId}`); |
| if (!audioEl) { |
| audioEl = document.createElement('audio'); |
| audioEl.id = `audio-${targetUserId}`; |
| audioEl.autoplay = true; |
| audioContainer.appendChild(audioEl); |
| } |
| audioEl.srcObject = event.streams[0]; |
| |
| |
| audioEl.play().catch(error => { |
| console.error("Browser blocked autoplay:", error); |
| appendSystemMessage("Audio blocked by browser. Click anywhere on the page to allow audio."); |
| }); |
| }; |
|
|
| |
| if (isInitiator) { |
| peer.createOffer().then(offer => { |
| peer.setLocalDescription(offer); |
| socket.emit('signal', { to: targetUserId, signal: offer }); |
| }); |
| } |
|
|
| return peer; |
| } |
|
|
| function appendSystemMessage(msg) { |
| const div = document.createElement('div'); |
| div.className = 'message system'; |
| div.innerText = msg; |
| chatBox.appendChild(div); |
| chatBox.scrollTop = chatBox.scrollHeight; |
| } |