const socket = io(); const peerConnections = {}; const startBtn = document.getElementById('startBtn'); const localVideo = document.getElementById('localVideo'); const statusDot = document.getElementById('statusDot'); const statusText = document.getElementById('statusText'); const passwordInput = document.getElementById('broadcasterPassword'); const authContainer = document.getElementById('authContainer'); let activeStream; const config = { iceServers: [ { urls: "stun:localhost:3478" }, { urls: "turn:localhost:3478", username: "myuser", credential: "mypassword" } ] }; socket.on('authError', (msg) => { alert(msg); stopSharing(); }); socket.on('viewer', id => { if (!activeStream) return; const peerConnection = new RTCPeerConnection(config); peerConnections[id] = peerConnection; activeStream.getTracks().forEach(track => { const sender = peerConnection.addTrack(track, activeStream); // Force high bitrate for best possible 1080p60 quality if (track.kind === 'video') { const params = sender.getParameters(); if (!params.encodings) params.encodings = [{}]; // Set max bandwidth to 10 Mbps (10,000,000 bps) params.encodings[0].maxBitrate = 10000000; sender.setParameters(params).catch(e => console.error("Could not set high bitrate:", e)); } }); peerConnection.onicecandidate = event => { if (event.candidate) { socket.emit('candidate', id, event.candidate); } }; peerConnection .createOffer() .then(sdp => { // Force VP8/H264 on the SDP offer to maximize compatibility with Chromium if (window.RTCRtpSender && window.RTCRtpSender.getCapabilities) { const capabilities = window.RTCRtpSender.getCapabilities('video'); if (capabilities && capabilities.codecs) { const preferredCodecs = capabilities.codecs.filter(c => c.mimeType.toLowerCase() === 'video/vp8' || c.mimeType.toLowerCase() === 'video/h264' ); if (preferredCodecs.length > 0) { const transceivers = peerConnection.getTransceivers(); transceivers.forEach(transceiver => { if (transceiver.receiver.track.kind === 'video') { try { transceiver.setCodecPreferences(preferredCodecs); } catch (e) { console.warn('Failed to set codec preferences:', e); } } }); } } } return peerConnection.setLocalDescription(sdp); }) .then(() => { socket.emit('offer', id, peerConnection.localDescription); }); }); socket.on('answer', (id, description) => { if (peerConnections[id]) { peerConnections[id].setRemoteDescription(description); } }); socket.on('candidate', (id, candidate) => { if (peerConnections[id]) { peerConnections[id].addIceCandidate(new RTCIceCandidate(candidate)); } }); socket.on('disconnectPeer', id => { if (peerConnections[id]) { peerConnections[id].close(); delete peerConnections[id]; } }); startBtn.addEventListener('click', async () => { const password = passwordInput.value; if (!password) { alert("Please enter the stream password to broadcast."); return; } try { const stream = await navigator.mediaDevices.getDisplayMedia({ video: { width: { ideal: 1920 }, height: { ideal: 1080 }, frameRate: { ideal: 60 } }, audio: false }); activeStream = stream; localVideo.srcObject = stream; localVideo.classList.add('active'); startBtn.style.display = 'none'; authContainer.style.display = 'none'; statusDot.classList.add('active'); statusText.innerText = 'Sharing Screen (1080p60)'; socket.emit('broadcaster', password); stream.getVideoTracks()[0].onended = () => { stopSharing(); }; } catch (err) { console.error("Error accessing display media.", err); statusText.innerText = 'Failed to access screen'; statusDot.style.backgroundColor = 'var(--error-color)'; } }); function stopSharing() { startBtn.style.display = 'inline-block'; authContainer.style.display = 'block'; localVideo.classList.remove('active'); statusDot.classList.remove('active'); statusText.innerText = 'Not Sharing'; activeStream = null; socket.emit('disconnect'); }