I have implemented Videocall using WebRTc in React and in backend i am using Dotnet here is my Videocall.js code :
import React, {
createContext,
useContext,
useEffect,
useRef,
useState,
} from "react";
import {
HubConnectionBuilder,
LogLevel,
HttpTransportType,
} from "@microsoft/signalr";
import {notification} from 'antd';
const SignalRContext = createContext();
export const SignalRProvider = ({ children, emailid }) => {
const [connected, setConnected] = useState(false);
const [connectionId, setConnectionId] = useState(null);
const [connection, setConnection] = useState(null);
// const loggedInEmailId = localStorage.getItem("emailid");
const loggedInEmailId = emailid || localStorage.getItem("emailid");
const [remoteStream, setRemoteStream] = useState(null);
const [localStream, setLocalStream] = useState(null);
const [isInCall, setIsInCall] = useState(false);
const [receiverId, setReceiverId] = useState("");
const [callerFullName, setCallerFullName] = useState("");
const [Id, SetId] = useState("");
const [incomingCallVisible, setIncomingCallVisible] = useState(false); // State to control the incoming call modal
const [callingId, setCallingId] = useState(""); //save the generated connection id
const [isCaller, setIsCaller] = useState(false);
const [newOne, setNewOne] = useState("");
const [newTwo, setNewTwo] = useState("");
const audioRef = useRef(null); //handle audio for notification
const localVideoRef = useRef(null);
const remoteVideoRef = useRef(null);
const peerConnectionRef = useRef(null);
const connectionRef = useRef(null);
const [onCloseHandler, setOnCloseHandler] = useState(null);
const [isCallAccepted, setIsCallAccepted] = useState(false);
const [closeVideocallModal,setCloseVideocallModal]=useState(false);
const [ICE_SERVERS,setICEServers]=useState({iceServers:[]});
useEffect(()=>{
const loadTurnServer=async ()=>{
const servers = await fetchTurnCredentials();
setICEServers(servers);
};
loadTurnServer();
},[]);
useEffect(() => {
// Ensure the connection is initialized before trying to use it
if (connection) {
connection.on('CallEnded', (message) => {
// Handle call end on this side
if (peerConnectionRef.current) {
peerConnectionRef.current.close();
peerConnectionRef.current = null;
}
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
setLocalStream(null);
}
if (remoteStream) {
setRemoteStream(null);
}
setIsInCall(false);
closeVideocallModal();
setIsCallAccepted(false);
setIncomingCallVisible(false);
});
}
// Cleanup the event listener when the component unmounts
return () => {
if (connection) {
connection.off('CallEnded');
}
};
}, [connection]);
useEffect(() => {
if(emailid !== null && emailid !== ""){
const connectToHub = async () => {
const newConnection = new HubConnectionBuilder()
.withUrl(`https://localhost:44366/CallHub?email=${loggedInEmailId}`, {
transport: HttpTransportType.WebSockets | HttpTransportType.LongPolling | HttpTransportType.ServerSentEvents,
})
.configureLogging(LogLevel.Information)
.withAutomaticReconnect()
.build();
// Connection lifecycle events
newConnection.onreconnecting((error) => {
console.warn('Connection lost due to error. Reconnecting...', error);
});
newConnection.onreconnected((connectionId) => {
console.log('Reconnected successfully.');
});
newConnection.onclose((error) => {
console.error('Connection closed. Attempting to reconnect...', error);
connectToHub(); // Retry connection
});
try {
await newConnection.start();
setConnection(newConnection);
setConnected(true)
connectionRef.current = newConnection;
} catch (error) {
console.error('Connection failed:', error);
setTimeout(() => connectToHub(), 2000); // Retry after delay
}
// Other SignalR event listeners
newConnection.on('OnConnected', (connectionId) => {
setCallingId(connectionId);
});
newConnection.on('CallRequested', async (callerId,callerFullName) => {
if (callerId) {
setCallerFullName(callerFullName);
SetId(callerId);
setIncomingCallVisible(true);
await newConnection.invoke('RespondToCall', caller`your text`Id, true);
} else {
await newConnection.invoke('RespondToCall', callerId, false);
}
});
newConnection.on('CallResponse', async (partnerId, accepted) => {
if (accepted) {
startReceivingCall(partnerId);
} else {
notification.error({
message: 'Call Rejected',
description: 'The recipient unable to pick your call.',
duration: 5,
});
setIsInCall(false);
closeVideocallModal();
}
});
newConnection.on('ReceiveVideocallMessage', async (message) => {
const signal = JSON.parse(message);
try {
if (signal.sdp) {
// Check if peer connection is closed, and reinitialize if necessary
if (peerConnectionRef.current.signalingState === 'closed') {
await reinitializeWebRTC();
}
await peerConnectionRef.current.setRemoteDescription(new RTCSessionDescription(signal.sdp));
if (signal.sdp.type === 'offer') {
const answer = await peerConnectionRef.current.createAnswer();
await peerConnectionRef.current.setLocalDescription(answer);
await newConnection.invoke('SendVideocallMessage', Id, JSON.stringify({ sdp: answer }));
}
} else if (signal.candidate) {
// Check if peer connection is closed, and reinitialize if necessary
if (peerConnectionRef.current.signalingState === 'closed') {
console.log('Peer connection is closed. Reinitializing connection...');
await reinitializeWebRTC();
}
await peerConnectionRef.current.addIceCandidate(new RTCIceCandidate(signal.candidate));
}
} catch (error) {
console.error('Error processing signaling message:', error);
}
});
newConnection.on('IceCandidateReceived', async (candidate) => {
try {
const parsedCandidate = JSON.parse(candidate);
if (parsedCandidate && parsedCandidate.candidate) {
await retryWithDelay(() => {
peerConnectionRef.current.addIceCandidate(new RTCIceCandidate(parsedCandidate));
});
} else {
console.error('Invalid ICE candidate received:', candidate);
}
} catch (error) {
console.error('Failed to process ICE candidate:', error);
}
});
};
connectToHub();
return () => {
if (connectionRef.current) {
connectionRef.current.stop();
}
};}
}, [emailid]);
//retry after 5 tries
const retryWithDelay = async (fn, retries = 5, delay = 1000) => {
for (let i = 0; i < retries; i++) {
try {
await fn();
return;
} catch (error) {
console.warn(`Retry ${i + 1} failed:`, error);
if (i === retries - 1) throw error;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
};
//reinitialize the webrtc
const reinitializeWebRTC = async () => {
if (peerConnectionRef.current) {
peerConnectionRef.current.close();
peerConnectionRef.current = null;
}
const peerConnection = new RTCPeerConnection(ICE_SERVERS);
peerConnectionRef.current = peerConnection;
if (localStream) {
localStream
.getTracks()
.forEach((track) => peerConnection.addTrack(track, localStream));
}
peerConnection.ontrack = (event) => {
setRemoteStream(event.streams[0]);
remoteVideoRef.current.srcObject = event.streams[0];
};
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
connectionRef.current
.invoke(
"SendIceCandidate",
Id,
JSON.stringify({ candidate: event.candidate })
)
.catch((error) =>
console.error("Error sending ICE candidate:", error)
);
}
};
console.log("WebRTC reinitialized.");
};
const fetchTurnCredentials = async () => {
debugger;
try {
const response = await fetch("`https://localhost:44366/Twilio/GetTwilioIceServersdata`");`get the t`
const data = await response.json();
if (data.ice_servers) {
return { iceServers: data.ice_servers };
} else {
throw new Error("Failed to get TURN credentials");
}
} catch (error) {
console.error("Error fetching TURN credentials:", error);
}
};
//toggling Audio and Video
//mute and unmute video
const toggleAudio = () => {
if (localStream) {
const audioTrack = localStream.getAudioTracks()[0];
if (audioTrack) {
audioTrack.enabled = !audioTrack.enabled;
} else {
console.error("No audio track found");
}
}
};
//Video on and Off
const toggleVideo = () => {
if (localStream) {
const videoTrack = localStream.getVideoTracks()[0];
if (videoTrack) {
videoTrack.enabled = !videoTrack.enabled;
} else {
console.error("No video track found");
}
}
};
const handleToggleAudio = () => {
toggleAudio();
};
const handleToggleVideo = () => {
toggleVideo();
};
const rejectIncomingCall = async (callerId) => {
setIsCallAccepted(false);
const currentConnection = connectionRef.current;
if (!currentConnection) {
console.error("Connection is not established.");
return;
}
try {
if (peerConnectionRef.current) {
peerConnectionRef.current.close();
peerConnectionRef.current = null;
}
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
setLocalStream(null);
}
if (remoteStream) {
setRemoteStream(null);
}
await currentConnection.invoke('RespondToCall', callerId, false);
await connection.invoke('EndCall', callerId);
setIncomingCallVisible(false); // Close the dialog
await handleCloseVideoCall();
await closeVideocallModal();
} catch (error) {
console.error("Failed to reject the call:", error);
}
};
//intilize call
const initiateCall = async () => {
await connection.invoke('RequestCall', receiverId);
};
//reciever side
const startCall = async (id) => {
debugger;
setNewTwo(id);
setIncomingCallVisible(false);
const targetId = id
const currentConnection = connectionRef.current;
if (!currentConnection) {
console.error("Connection is null. Cannot proceed with the call.");
return;
}
if (!targetId) {
console.error("No receiver ID available for the call.");
return;
}
// Get local media stream
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
setLocalStream(stream);
localVideoRef.current.srcObject = stream;
// Create peer connection
const peerConnection = new RTCPeerConnection(ICE_SERVERS);
peerConnectionRef.current = peerConnection;
// Add local tracks to the peer connection
stream.getTracks().forEach((track) => {
peerConnection.addTrack(track, stream);
});
// Handle incoming tracks
peerConnection.ontrack = (event) => {
if (!event || !event.streams || event.streams.length === 0) {
console.error("ontrack event received with no streams:", event);
return;
}
// Set remote stream when a track is received
setRemoteStream(event.streams[0]);
remoteVideoRef.current.srcObject = event.streams[0];
};;
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
currentConnection.invoke('SendIceCandidate', targetId, JSON.stringify({ candidate: event.candidate }))
.catch(error => console.error('Error sending ICE candidate:', error));
} else {
console.error('SignalR connection is not ready yet.');
}
};
// Create offer and send it to the receiver
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
await currentConnection.invoke('SendVideocallMessage', targetId, JSON.stringify({ sdp: offer }));
setIsInCall(true);
};
//caller side
const startReceivingCall = async (callerId) => {
debugger;
setIsCaller(true);
setNewOne(callerId);
// setIsCallAccepted(true);
// debugger;
const currentConnection = connectionRef.current;
if (!currentConnection) {
console.error("SignalR connection is not ready for receiving a call.");
return;
}
// Get local media stream
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
setLocalStream(stream);
localVideoRef.current.srcObject = stream;
// Create peer connection
const peerConnection = new RTCPeerConnection(ICE_SERVERS);
peerConnectionRef.current = peerConnection;
//add local tracks to peer connection
stream.getTracks().forEach((track) => {
peerConnection.addTrack(track, stream);
});
// Handle incoming tracks
peerConnection.ontrack = (event) => {
setIsCallAccepted(true);
if (!event || !event.streams || event.streams.length === 0) {
console.error("ontrack event received with no streams:", event);
return;
}
// Set remote stream when a track is received
setRemoteStream(event.streams[0]);
remoteVideoRef.current.srcObject = event.streams[0];
};
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
currentConnection
.invoke('SendIceCandidate', callerId, JSON.stringify({ candidate: event.candidate }))
.catch((error) => console.error('Error sending ICE candidate:', error));
}
};
// Set up signaling for receiving an offer
currentConnection.on('ReceiveVideocallMessage', async (message) => {
const signal = JSON.parse(message);
if (signal.sdp) {
await peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp));
if (signal.sdp.type === 'offer') {
// Create and send an answer
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
await currentConnection.invoke('SendVideocallMessage', callerId, JSON.stringify({ sdp: answer }));
}
} else if (signal.candidate) {
await peerConnection.addIceCandidate(new RTCIceCandidate(signal.candidate));
}
});
setIsInCall(true);
};
const endCall = async () => {
const targetId = isCaller ? newOne:newTwo ;
if (peerConnectionRef.current) {
peerConnectionRef.current.close();
peerConnectionRef.current = null;
}
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
setLocalStream(null);
}
if (remoteStream) {
setRemoteStream(null);
}
setIsInCall(false);
setIsCallAccepted(false);
setIncomingCallVisible(false);
await connection.invoke('EndCall', targetId);
};
const handleCloseVideoCall = async () => {
await endCall();
};
const acceptCall=async()=>{
startCall(Id);
}
const rejectCall=async()=>{
rejectIncomingCall(Id);
}
return (
<SignalRContext.Provider value={{isCallAccepted,callerFullName,receiverId, setReceiverId,connectionId, initiateCall,connection, connected,acceptCall,rejectCall, localVideoRef,
remoteVideoRef,isInCall,handleCloseVideoCall,endCall,incomingCallVisible,handleToggleVideo,handleToggleAudio, setOnCloseHandler,setCloseVideocallModal}}>
{children}
</SignalRContext.Provider>
);
export const useSignalR = () => useContext(SignalRContext);
for ICE_SERVERS i got :
{
"username": "9e68e1192c3029a9b398b4649f67a41a045bb7a0cd90f1c2261243af7aee5e1b",
"ice_servers": [
{
"url": "stun:global.stun.twilio:3478",
"urls": "stun:global.stun.twilio:3478"
},
{
"url": "turn:global.turn.twilio:3478?transport=udp",
"username": "9e68e1192c3029a9b398b4649f67a41a045bb7a0cd90f1c2261243af7aee5e1b",
"urls": "turn:global.turn.twilio:3478?transport=udp",
"credential": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"url": "turn:global.turn.twilio:3478?transport=tcp",
"username": "9e68e1192c3029a9b398b4649f67a41a045bb7a0cd90f1c2261243af7aee5e1b",
"urls": "turn:global.turn.twilio:3478?transport=tcp",
"credential": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"url": "turn:global.turn.twilio:443?transport=tcp",
"username": "9e68e1192c3029a9b398b4649f67a41a045bb7a0cd90f1c2261243af7aee5e1b",
"urls": "turn:global.turn.twilio:443?transport=tcp",
"credential": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
],
"date_updated": "Mon, 17 Feb 2025 09:30:48 +0000",
"account_sid": "AC513a4f58bd72f7b127",
"ttl": "86400",
"date_created": "Mon, 17 Feb 2025 09:30:48 +0000",
"password": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
i have uploaded my project on Azure . During tests i found that the Videocall works when i am on same network example - my both devices are connected with same wifi . But when i try to connecting devices over different Network like mobile Hotspot and Wifi the it did not work .As i am using Twilio for Stun and Turn Server.Also There is an Scenerio when two devices connected on Videocall when i was on Two Different mobile Hotspot .
I have debbuged and console the ICEstatechange for same network , it always says that connecting and connectinng but when on different it gives me Disconnected or failed . Also i have enabled the Ports in Inbound/Outbound Rules of the Windows Firewall.Now what should i do so that it will work on different Networks foe every scenerio either its connected to mobile hotspot or Lan or Wifi .
I have implemented Videocall using WebRTc in React and in backend i am using Dotnet here is my Videocall.js code :
import React, {
createContext,
useContext,
useEffect,
useRef,
useState,
} from "react";
import {
HubConnectionBuilder,
LogLevel,
HttpTransportType,
} from "@microsoft/signalr";
import {notification} from 'antd';
const SignalRContext = createContext();
export const SignalRProvider = ({ children, emailid }) => {
const [connected, setConnected] = useState(false);
const [connectionId, setConnectionId] = useState(null);
const [connection, setConnection] = useState(null);
// const loggedInEmailId = localStorage.getItem("emailid");
const loggedInEmailId = emailid || localStorage.getItem("emailid");
const [remoteStream, setRemoteStream] = useState(null);
const [localStream, setLocalStream] = useState(null);
const [isInCall, setIsInCall] = useState(false);
const [receiverId, setReceiverId] = useState("");
const [callerFullName, setCallerFullName] = useState("");
const [Id, SetId] = useState("");
const [incomingCallVisible, setIncomingCallVisible] = useState(false); // State to control the incoming call modal
const [callingId, setCallingId] = useState(""); //save the generated connection id
const [isCaller, setIsCaller] = useState(false);
const [newOne, setNewOne] = useState("");
const [newTwo, setNewTwo] = useState("");
const audioRef = useRef(null); //handle audio for notification
const localVideoRef = useRef(null);
const remoteVideoRef = useRef(null);
const peerConnectionRef = useRef(null);
const connectionRef = useRef(null);
const [onCloseHandler, setOnCloseHandler] = useState(null);
const [isCallAccepted, setIsCallAccepted] = useState(false);
const [closeVideocallModal,setCloseVideocallModal]=useState(false);
const [ICE_SERVERS,setICEServers]=useState({iceServers:[]});
useEffect(()=>{
const loadTurnServer=async ()=>{
const servers = await fetchTurnCredentials();
setICEServers(servers);
};
loadTurnServer();
},[]);
useEffect(() => {
// Ensure the connection is initialized before trying to use it
if (connection) {
connection.on('CallEnded', (message) => {
// Handle call end on this side
if (peerConnectionRef.current) {
peerConnectionRef.current.close();
peerConnectionRef.current = null;
}
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
setLocalStream(null);
}
if (remoteStream) {
setRemoteStream(null);
}
setIsInCall(false);
closeVideocallModal();
setIsCallAccepted(false);
setIncomingCallVisible(false);
});
}
// Cleanup the event listener when the component unmounts
return () => {
if (connection) {
connection.off('CallEnded');
}
};
}, [connection]);
useEffect(() => {
if(emailid !== null && emailid !== ""){
const connectToHub = async () => {
const newConnection = new HubConnectionBuilder()
.withUrl(`https://localhost:44366/CallHub?email=${loggedInEmailId}`, {
transport: HttpTransportType.WebSockets | HttpTransportType.LongPolling | HttpTransportType.ServerSentEvents,
})
.configureLogging(LogLevel.Information)
.withAutomaticReconnect()
.build();
// Connection lifecycle events
newConnection.onreconnecting((error) => {
console.warn('Connection lost due to error. Reconnecting...', error);
});
newConnection.onreconnected((connectionId) => {
console.log('Reconnected successfully.');
});
newConnection.onclose((error) => {
console.error('Connection closed. Attempting to reconnect...', error);
connectToHub(); // Retry connection
});
try {
await newConnection.start();
setConnection(newConnection);
setConnected(true)
connectionRef.current = newConnection;
} catch (error) {
console.error('Connection failed:', error);
setTimeout(() => connectToHub(), 2000); // Retry after delay
}
// Other SignalR event listeners
newConnection.on('OnConnected', (connectionId) => {
setCallingId(connectionId);
});
newConnection.on('CallRequested', async (callerId,callerFullName) => {
if (callerId) {
setCallerFullName(callerFullName);
SetId(callerId);
setIncomingCallVisible(true);
await newConnection.invoke('RespondToCall', caller`your text`Id, true);
} else {
await newConnection.invoke('RespondToCall', callerId, false);
}
});
newConnection.on('CallResponse', async (partnerId, accepted) => {
if (accepted) {
startReceivingCall(partnerId);
} else {
notification.error({
message: 'Call Rejected',
description: 'The recipient unable to pick your call.',
duration: 5,
});
setIsInCall(false);
closeVideocallModal();
}
});
newConnection.on('ReceiveVideocallMessage', async (message) => {
const signal = JSON.parse(message);
try {
if (signal.sdp) {
// Check if peer connection is closed, and reinitialize if necessary
if (peerConnectionRef.current.signalingState === 'closed') {
await reinitializeWebRTC();
}
await peerConnectionRef.current.setRemoteDescription(new RTCSessionDescription(signal.sdp));
if (signal.sdp.type === 'offer') {
const answer = await peerConnectionRef.current.createAnswer();
await peerConnectionRef.current.setLocalDescription(answer);
await newConnection.invoke('SendVideocallMessage', Id, JSON.stringify({ sdp: answer }));
}
} else if (signal.candidate) {
// Check if peer connection is closed, and reinitialize if necessary
if (peerConnectionRef.current.signalingState === 'closed') {
console.log('Peer connection is closed. Reinitializing connection...');
await reinitializeWebRTC();
}
await peerConnectionRef.current.addIceCandidate(new RTCIceCandidate(signal.candidate));
}
} catch (error) {
console.error('Error processing signaling message:', error);
}
});
newConnection.on('IceCandidateReceived', async (candidate) => {
try {
const parsedCandidate = JSON.parse(candidate);
if (parsedCandidate && parsedCandidate.candidate) {
await retryWithDelay(() => {
peerConnectionRef.current.addIceCandidate(new RTCIceCandidate(parsedCandidate));
});
} else {
console.error('Invalid ICE candidate received:', candidate);
}
} catch (error) {
console.error('Failed to process ICE candidate:', error);
}
});
};
connectToHub();
return () => {
if (connectionRef.current) {
connectionRef.current.stop();
}
};}
}, [emailid]);
//retry after 5 tries
const retryWithDelay = async (fn, retries = 5, delay = 1000) => {
for (let i = 0; i < retries; i++) {
try {
await fn();
return;
} catch (error) {
console.warn(`Retry ${i + 1} failed:`, error);
if (i === retries - 1) throw error;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
};
//reinitialize the webrtc
const reinitializeWebRTC = async () => {
if (peerConnectionRef.current) {
peerConnectionRef.current.close();
peerConnectionRef.current = null;
}
const peerConnection = new RTCPeerConnection(ICE_SERVERS);
peerConnectionRef.current = peerConnection;
if (localStream) {
localStream
.getTracks()
.forEach((track) => peerConnection.addTrack(track, localStream));
}
peerConnection.ontrack = (event) => {
setRemoteStream(event.streams[0]);
remoteVideoRef.current.srcObject = event.streams[0];
};
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
connectionRef.current
.invoke(
"SendIceCandidate",
Id,
JSON.stringify({ candidate: event.candidate })
)
.catch((error) =>
console.error("Error sending ICE candidate:", error)
);
}
};
console.log("WebRTC reinitialized.");
};
const fetchTurnCredentials = async () => {
debugger;
try {
const response = await fetch("`https://localhost:44366/Twilio/GetTwilioIceServersdata`");`get the t`
const data = await response.json();
if (data.ice_servers) {
return { iceServers: data.ice_servers };
} else {
throw new Error("Failed to get TURN credentials");
}
} catch (error) {
console.error("Error fetching TURN credentials:", error);
}
};
//toggling Audio and Video
//mute and unmute video
const toggleAudio = () => {
if (localStream) {
const audioTrack = localStream.getAudioTracks()[0];
if (audioTrack) {
audioTrack.enabled = !audioTrack.enabled;
} else {
console.error("No audio track found");
}
}
};
//Video on and Off
const toggleVideo = () => {
if (localStream) {
const videoTrack = localStream.getVideoTracks()[0];
if (videoTrack) {
videoTrack.enabled = !videoTrack.enabled;
} else {
console.error("No video track found");
}
}
};
const handleToggleAudio = () => {
toggleAudio();
};
const handleToggleVideo = () => {
toggleVideo();
};
const rejectIncomingCall = async (callerId) => {
setIsCallAccepted(false);
const currentConnection = connectionRef.current;
if (!currentConnection) {
console.error("Connection is not established.");
return;
}
try {
if (peerConnectionRef.current) {
peerConnectionRef.current.close();
peerConnectionRef.current = null;
}
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
setLocalStream(null);
}
if (remoteStream) {
setRemoteStream(null);
}
await currentConnection.invoke('RespondToCall', callerId, false);
await connection.invoke('EndCall', callerId);
setIncomingCallVisible(false); // Close the dialog
await handleCloseVideoCall();
await closeVideocallModal();
} catch (error) {
console.error("Failed to reject the call:", error);
}
};
//intilize call
const initiateCall = async () => {
await connection.invoke('RequestCall', receiverId);
};
//reciever side
const startCall = async (id) => {
debugger;
setNewTwo(id);
setIncomingCallVisible(false);
const targetId = id
const currentConnection = connectionRef.current;
if (!currentConnection) {
console.error("Connection is null. Cannot proceed with the call.");
return;
}
if (!targetId) {
console.error("No receiver ID available for the call.");
return;
}
// Get local media stream
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
setLocalStream(stream);
localVideoRef.current.srcObject = stream;
// Create peer connection
const peerConnection = new RTCPeerConnection(ICE_SERVERS);
peerConnectionRef.current = peerConnection;
// Add local tracks to the peer connection
stream.getTracks().forEach((track) => {
peerConnection.addTrack(track, stream);
});
// Handle incoming tracks
peerConnection.ontrack = (event) => {
if (!event || !event.streams || event.streams.length === 0) {
console.error("ontrack event received with no streams:", event);
return;
}
// Set remote stream when a track is received
setRemoteStream(event.streams[0]);
remoteVideoRef.current.srcObject = event.streams[0];
};;
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
currentConnection.invoke('SendIceCandidate', targetId, JSON.stringify({ candidate: event.candidate }))
.catch(error => console.error('Error sending ICE candidate:', error));
} else {
console.error('SignalR connection is not ready yet.');
}
};
// Create offer and send it to the receiver
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
await currentConnection.invoke('SendVideocallMessage', targetId, JSON.stringify({ sdp: offer }));
setIsInCall(true);
};
//caller side
const startReceivingCall = async (callerId) => {
debugger;
setIsCaller(true);
setNewOne(callerId);
// setIsCallAccepted(true);
// debugger;
const currentConnection = connectionRef.current;
if (!currentConnection) {
console.error("SignalR connection is not ready for receiving a call.");
return;
}
// Get local media stream
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
setLocalStream(stream);
localVideoRef.current.srcObject = stream;
// Create peer connection
const peerConnection = new RTCPeerConnection(ICE_SERVERS);
peerConnectionRef.current = peerConnection;
//add local tracks to peer connection
stream.getTracks().forEach((track) => {
peerConnection.addTrack(track, stream);
});
// Handle incoming tracks
peerConnection.ontrack = (event) => {
setIsCallAccepted(true);
if (!event || !event.streams || event.streams.length === 0) {
console.error("ontrack event received with no streams:", event);
return;
}
// Set remote stream when a track is received
setRemoteStream(event.streams[0]);
remoteVideoRef.current.srcObject = event.streams[0];
};
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
currentConnection
.invoke('SendIceCandidate', callerId, JSON.stringify({ candidate: event.candidate }))
.catch((error) => console.error('Error sending ICE candidate:', error));
}
};
// Set up signaling for receiving an offer
currentConnection.on('ReceiveVideocallMessage', async (message) => {
const signal = JSON.parse(message);
if (signal.sdp) {
await peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp));
if (signal.sdp.type === 'offer') {
// Create and send an answer
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
await currentConnection.invoke('SendVideocallMessage', callerId, JSON.stringify({ sdp: answer }));
}
} else if (signal.candidate) {
await peerConnection.addIceCandidate(new RTCIceCandidate(signal.candidate));
}
});
setIsInCall(true);
};
const endCall = async () => {
const targetId = isCaller ? newOne:newTwo ;
if (peerConnectionRef.current) {
peerConnectionRef.current.close();
peerConnectionRef.current = null;
}
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
setLocalStream(null);
}
if (remoteStream) {
setRemoteStream(null);
}
setIsInCall(false);
setIsCallAccepted(false);
setIncomingCallVisible(false);
await connection.invoke('EndCall', targetId);
};
const handleCloseVideoCall = async () => {
await endCall();
};
const acceptCall=async()=>{
startCall(Id);
}
const rejectCall=async()=>{
rejectIncomingCall(Id);
}
return (
<SignalRContext.Provider value={{isCallAccepted,callerFullName,receiverId, setReceiverId,connectionId, initiateCall,connection, connected,acceptCall,rejectCall, localVideoRef,
remoteVideoRef,isInCall,handleCloseVideoCall,endCall,incomingCallVisible,handleToggleVideo,handleToggleAudio, setOnCloseHandler,setCloseVideocallModal}}>
{children}
</SignalRContext.Provider>
);
export const useSignalR = () => useContext(SignalRContext);
for ICE_SERVERS i got :
{
"username": "9e68e1192c3029a9b398b4649f67a41a045bb7a0cd90f1c2261243af7aee5e1b",
"ice_servers": [
{
"url": "stun:global.stun.twilio:3478",
"urls": "stun:global.stun.twilio:3478"
},
{
"url": "turn:global.turn.twilio:3478?transport=udp",
"username": "9e68e1192c3029a9b398b4649f67a41a045bb7a0cd90f1c2261243af7aee5e1b",
"urls": "turn:global.turn.twilio:3478?transport=udp",
"credential": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"url": "turn:global.turn.twilio:3478?transport=tcp",
"username": "9e68e1192c3029a9b398b4649f67a41a045bb7a0cd90f1c2261243af7aee5e1b",
"urls": "turn:global.turn.twilio:3478?transport=tcp",
"credential": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"url": "turn:global.turn.twilio:443?transport=tcp",
"username": "9e68e1192c3029a9b398b4649f67a41a045bb7a0cd90f1c2261243af7aee5e1b",
"urls": "turn:global.turn.twilio:443?transport=tcp",
"credential": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
],
"date_updated": "Mon, 17 Feb 2025 09:30:48 +0000",
"account_sid": "AC513a4f58bd72f7b127",
"ttl": "86400",
"date_created": "Mon, 17 Feb 2025 09:30:48 +0000",
"password": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
i have uploaded my project on Azure . During tests i found that the Videocall works when i am on same network example - my both devices are connected with same wifi . But when i try to connecting devices over different Network like mobile Hotspot and Wifi the it did not work .As i am using Twilio for Stun and Turn Server.Also There is an Scenerio when two devices connected on Videocall when i was on Two Different mobile Hotspot .
I have debbuged and console the ICEstatechange for same network , it always says that connecting and connectinng but when on different it gives me Disconnected or failed . Also i have enabled the Ports in Inbound/Outbound Rules of the Windows Firewall.Now what should i do so that it will work on different Networks foe every scenerio either its connected to mobile hotspot or Lan or Wifi .
Share Improve this question edited Feb 17 at 9:43 Predator_0923 asked Feb 17 at 9:39 Predator_0923Predator_0923 11 silver badge3 bronze badges New contributor Predator_0923 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 1- Please edit the title of your question to be descriptive, unambiguous, and specific to what you are asking. For more guidance, see How do I write a good title?. – DarkBee Commented Feb 17 at 9:59
1 Answer
Reset to default 1The RTCPeerConnection
constructor expects an object with certain properties, documented here. To pass it a list of ICE servers, it expects them in the property iceServers
:
new RTCPeerConnection({
iceServers: [
...
]
})
You're not passing it such an object; in yours, the ICE servers are in ice_servers
, which is a different property name it doesn't pick up on. And your object contains additional stuff that's irrelevant to RTCPeerConnection
. So you're not passing it any ICE servers, which is why it doesn't use any, and can't establish connections over anything but simple local connections.
Make sure you're passing only the list of ICE servers to the iceServers
property, and get rid of the rest:
new RTCPeerConnection({ iceServers: ICE_SERVERS.ice_servers })