File size: 6,700 Bytes
7ac31c4 71064ed cabf7d8 7ac31c4 1f5ac5b 71064ed 1f5ac5b cabf7d8 1f5ac5b cabf7d8 1f5ac5b cabf7d8 1f5ac5b 7ac31c4 05fa184 7ac31c4 71064ed 7ac31c4 71064ed 7ac31c4 cabf7d8 71064ed 7ac31c4 05fa184 7ac31c4 05fa184 7ac31c4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | 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 = {}; // Store connections to other users
// --- WebRTC Configuration ---
// Using Google STUN and your dedicated Metered.live TURN server
const rtcConfig = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }, // Backup STUN
{
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'
}
]
};
// --- CHAT LOGIC ---
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;
});
// Allow hitting "Enter" to send a chat message
chatInput.addEventListener("keypress", function(event) {
if (event.key === "Enter") {
event.preventDefault();
sendBtn.click();
}
});
// --- AUDIO/WEBRTC LOGIC ---
joinBtn.onclick = async () => {
try {
// Request microphone access
localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
statusText.innerText = "Mic Online - You are in the call";
joinBtn.disabled = true;
// Let the server know we are ready to connect to others
socket.emit('join-room');
} catch (err) {
alert("Microphone access denied or not found.");
console.error(err);
}
};
// When a new user connects, we (if we have our mic on) initiate a call to them
socket.on('user-connected', (userId) => {
if (!localStream) return; // Don't call if we aren't in the audio room
appendSystemMessage(`User ${userId.substring(0,4)} joined. Attempting connection...`);
const peer = createPeerConnection(userId, true);
peers[userId] = peer;
});
// Handle incoming signals (Offers, Answers, ICE candidates)
socket.on('signal', async (data) => {
if (!localStream) return;
const { from, signal } = data;
// If we don't have a peer connection for this user yet, create one
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);
}
});
// Clean up when someone leaves
socket.on('user-disconnected', (userId) => {
if (peers[userId]) {
peers[userId].close();
delete peers[userId];
// Remove their audio element
const audioEl = document.getElementById(`audio-${userId}`);
if (audioEl) audioEl.remove();
appendSystemMessage(`User ${userId.substring(0,4)} left.`);
}
});
// Helper function to create a WebRTC Peer Connection
function createPeerConnection(targetUserId, isInitiator) {
const peer = new RTCPeerConnection(rtcConfig);
// Add our local microphone stream to the connection
localStream.getTracks().forEach(track => peer.addTrack(track, localStream));
// Send ICE candidates to the other user
peer.onicecandidate = (event) => {
if (event.candidate) {
socket.emit('signal', { to: targetUserId, signal: event.candidate });
}
};
// TRACK THE CONNECTION STATUS
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...`);
}
};
// When we receive their audio stream, play it
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];
// Force the browser to play the audio and catch any autoplay errors
audioEl.play().catch(error => {
console.error("Browser blocked autoplay:", error);
appendSystemMessage("Audio blocked by browser. Click anywhere on the page to allow audio.");
});
};
// If we are initiating the call, create an offer
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;
} |