const socket = io(); const remoteVideo = document.getElementById('remoteVideo'); const overlay = document.getElementById('overlay'); let peerConnection; const config = { iceServers: [ { urls: "stun:localhost:3478" }, { urls: "turn:localhost:3478", username: "myuser", credential: "mypassword" } ] }; socket.on('offer', (id, description) => { peerConnection = new RTCPeerConnection(config); peerConnection.ontrack = event => { remoteVideo.srcObject = event.streams[0]; remoteVideo.classList.add('active'); overlay.classList.add('hidden'); }; // Auto-unmute when the user interacts with the document to bypass browser autoplay restrictions document.addEventListener('click', () => { remoteVideo.muted = false; // Set sink to max possible volume explicitly avoiding browser gain staging remoteVideo.volume = 1.0; }, {once: true}); peerConnection.onicecandidate = event => { if (event.candidate) { socket.emit('candidate', id, event.candidate); } }; peerConnection .setRemoteDescription(description) .then(() => peerConnection.createAnswer()) .then(sdp => { let sdpLines = sdp.sdp.split('\r\n'); let opusPayloadType = null; for (let i = 0; i < sdpLines.length; i++) { if (sdpLines[i].includes('a=rtpmap:') && sdpLines[i].includes('opus/48000/2')) { const match = sdpLines[i].match(/a=rtpmap:(\d+) /); if (match) opusPayloadType = match[1]; } } if (opusPayloadType) { let fmtpFound = false; for (let i = 0; i < sdpLines.length; i++) { if (sdpLines[i].startsWith(`a=fmtp:${opusPayloadType}`)) { sdpLines[i] = `a=fmtp:${opusPayloadType} minptime=10;useinbandfec=1;maxplaybackrate=48000;stereo=1;sprop-stereo=1;maxaveragebitrate=510000;cbr=1`; fmtpFound = true; } } if (!fmtpFound) { sdpLines.push(`a=fmtp:${opusPayloadType} minptime=10;useinbandfec=1;maxplaybackrate=48000;stereo=1;sprop-stereo=1;maxaveragebitrate=510000;cbr=1`); } } sdp.sdp = sdpLines.join('\r\n'); return peerConnection.setLocalDescription(sdp); }) .then(() => { socket.emit('answer', id, peerConnection.localDescription); }); }); socket.on('candidate', (id, candidate) => { if (peerConnection) { peerConnection.addIceCandidate(new RTCIceCandidate(candidate)) .catch(e => console.error(e)); } }); socket.on('broadcaster', () => { socket.emit('viewer'); }); socket.on('disconnectPeer', () => { if (peerConnection) { peerConnection.close(); peerConnection = null; } remoteVideo.classList.remove('active'); remoteVideo.srcObject = null; overlay.classList.remove('hidden'); overlay.querySelector('h1').innerText = 'Stream Ended'; overlay.querySelector('.status-indicator span:last-child').innerText = 'Waiting for new stream...'; }); socket.on('connect', () => { socket.emit('viewer'); });