I am trying to setup this web rtc video call function for a site i am building, the call will only be connected if the admin has in the function the same name as the client side’s name, in my server in the logs i get
Forwarding candidate for admin Received message: { type:
‘client_join’, name: ‘admin’ } Client trying to join: admin Client
successfully connected: admin Connection closed New connection
established
But the remoteVideo in each side won’t load, i dont know what more to try
Server.js:
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 3000 });
let activeCalls = {};
wss.on("connection", (ws) => {
console.log("New connection established");
ws.on("message", (message) => {
const data = JSON.parse(message);
console.log("Received message:", data);
if (data.type === "admin_join") {
if (activeCalls[data.name]) {
console.log(`Admin already in another call: ${data.name}`);
ws.send(JSON.stringify({ type: "admin_busy" }));
return;
}
activeCalls[data.name] = { admin: ws, client: null };
console.log(`Admin joined the call: ${data.name}`);
}
if (data.type === "client_join") {
console.log(`Client trying to join: ${data.name}`);
if (!activeCalls[data.name]) {
console.log(`No admin call for ${data.name}. Client rejected.`);
ws.send(JSON.stringify({ type: "waiting", name: data.name }));
return;
}
if (activeCalls[data.name].client) {
console.log(`Another client is already in call with admin: ${data.name}`);
ws.send(JSON.stringify({ type: "admin_busy" }));
return;
}
activeCalls[data.name].client = ws;
ws.send(JSON.stringify({ type: "client_joined", name: data.name }));
activeCalls[data.name].admin.send(JSON.stringify({ type: "client_joined", name: data.name }));
console.log(`Client successfully connected: ${data.name}`);
}
if (data.type === "offer" || data.type === "answer" || data.type === "candidate") {
console.log(`Forwarding ${data.type} for ${data.name}`);
if (activeCalls[data.name] && activeCalls[data.name].client && activeCalls[data.name].admin) {
const target = data.type === "offer" ? activeCalls[data.name].client : activeCalls[data.name].admin;
if (target && target.readyState === WebSocket.OPEN) {
target.send(JSON.stringify(data));
}
}
}
if (data.type === "leave") {
console.log(`${data.name} left the call`);
if (activeCalls[data.name]) {
if (activeCalls[data.name].client) activeCalls[data.name].client.send(JSON.stringify({ type: "leave", name: data.name }));
if (activeCalls[data.name].admin) activeCalls[data.name].admin.send(JSON.stringify({ type: "leave", name: data.name }));
delete activeCalls[data.name];
}
}
});
ws.on("close", () => {
console.log("Connection closed");
});
});
console.log("WebSocket server running on ws://localhost:3000");
Client side:
<section>
<script>
const name = "<?php echo $_SESSION['name']; ?>";
console.log("Client is:", name);
const videoCallSection = document.createElement("div");
document.body.appendChild(videoCallSection);
videoCallSection.innerHTML = `
<h1>Client Video Call: ${name}</h1>
<video id="localVideo" autoplay playsinline style="display: none;"></video>
<video id="remoteVideo" autoplay playsinline style="display: none;"></video>
<button id="joinCall" style="margin-top:10px; padding:10px; background:green; color:white; border:none; cursor:pointer;">Join Call</button>
<button id="leaveCall" style="margin-top:10px; padding:10px; background:red; color:white; border:none; cursor:pointer; display:none;">Leave Call</button>
<p id="statusMessage" style="color: red; font-weight: bold;"></p>
`;
const localVideo = document.getElementById("localVideo");
const remoteVideo = document.getElementById("remoteVideo");
const joinButton = document.getElementById("joinCall");
const leaveButton = document.getElementById("leaveCall");
const statusMessage = document.getElementById("statusMessage");
const peerConnection = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});
const socket = new WebSocket("ws://localhost:3000");
let localStream;
joinButton.addEventListener("click", async () => {
joinButton.style.display = "none";
leaveButton.style.display = "block";
localVideo.style.display = "block";
remoteVideo.style.display = "block";
socket.send(JSON.stringify({ type: "client_join", name }));
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
localStream = stream;
localVideo.srcObject = stream;
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
})
.catch(error => console.error("Error accessing media devices.", error));
});
socket.onmessage = async (message) => {
const data = JSON.parse(message.data);
console.log("Received message:", data);
if (data.name !== name) {
statusMessage.innerText = "This call is not for you!";
return;
}
if (data.type === "admin_busy") {
statusMessage.innerText = "Admin is in another call.";
return;
}
if (data.type === "waiting") {
statusMessage.innerText = "Waiting for admin to start the call.";
return;
}
if (data.type === "client_joined") {
statusMessage.innerText = "Call connected!";
}
if (data.type === "offer") {
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.send(JSON.stringify({ type: "answer", answer, name }));
} else if (data.type === "candidate") {
await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
} else if (data.type === "leave") {
endCall();
}
};
function endCall() {
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
}
if (peerConnection) {
peerConnection.close();
}
socket.send(JSON.stringify({ type: "leave", name }));
socket.close();
videoCallSection.innerHTML = "";
}
leaveButton.addEventListener("click", endCall);
</script>
</section>
Admin side:
function showvideo(name) {
console.log("Admin wants to call:", name);
const videoCallSection = document.getElementById("videocall");
videoCallSection.style.display = "block";
videoCallSection.innerHTML = `
<h1>Admin Video Call: ${name}</h1>
<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
<button id="leaveCall" style="margin-top:10px; padding:10px; background:red; color:white; border:none; cursor:pointer;">Leave Call</button>
<p id="statusMessage" style="color: red; font-weight: bold;"></p>
`;
const localVideo = document.getElementById("localVideo");
const remoteVideo = document.getElementById("remoteVideo");
const leaveButton = document.getElementById("leaveCall");
const statusMessage = document.getElementById("statusMessage");
const peerConnection = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});
const socket = new WebSocket("ws://localhost:3000");
let localStream;
let inCall = false;
let connectedClient = null;
// Start local video only if user is valid
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
localStream = stream;
localVideo.srcObject = stream;
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
})
.catch(error => console.error("Error accessing media devices:", error));
peerConnection.ontrack = event => {
remoteVideo.srcObject = event.streams[0];
};
socket.onmessage = async (message) => {
const data = JSON.parse(message.data);
console.log("Received message:", data);
if (data.type === "admin_busy") {
statusMessage.innerText = "Admin is in another call.";
return;
}
if (data.type === "waiting") {
statusMessage.innerText = "Waiting for client to join...";
return;
}
if (data.type === "client_joined") {
if (data.name !== name) {
statusMessage.innerText = "Client name mismatch!";
return;
}
connectedClient = data.name;
statusMessage.innerText = "Call connected!";
}
if (data.type === "offer") {
if (data.name !== name) {
statusMessage.innerText = "Wrong client trying to connect!";
return;
}
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.send(JSON.stringify({ type: "answer", answer, name }));
} else if (data.type === "answer") {
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.answer));
} else if (data.type === "candidate") {
if (data.name !== name) return;
await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
} else if (data.type === "leave") {
endCall();
}
};
peerConnection.onicecandidate = event => {
if (event.candidate) {
socket.send(JSON.stringify({ type: "candidate", candidate: event.candidate, name }));
}
};
socket.onopen = () => {
socket.send(JSON.stringify({ type: "admin_join", name }));
};
async function startCall() {
if (inCall) {
statusMessage.innerText = "Admin is already in another call.";
return;
}
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
socket.send(JSON.stringify({ type: "offer", offer, name }));
inCall = true;
}
function endCall() {
if (localStream) localStream.getTracks().forEach(track => track.stop());
if (peerConnection) peerConnection.close();
socket.send(JSON.stringify({ type: "leave", name }));
socket.close();
videoCallSection.innerHTML = "";
inCall = false;
}
leaveButton.addEventListener("click", endCall);
setTimeout(startCall, 1000);
}