最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

reactjs - Webrtc Videocall - Stack Overflow

programmeradmin5浏览0评论

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
Add a comment  | 

1 Answer 1

Reset to default 1

The 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 })
发布评论

评论列表(0)

  1. 暂无评论