I am working on a problem where I am using WebSocket as a signalling server or use for live chats and use WebRTC for video and audio communication where I set sdp offer and answer and again set negotiation sdp offer and answer but still my stream is not showing other side or other side stream is not showing to me.
I am trying to implement an audio and video communication using WebRTC but my stream is not showing in other side or vice versa.
HomeMeetingSection.tsx----> where I implement button to initialize audio and video communication and switch tab to AudioVideoSection
import chatIcon from "../assets/images/chatIcon.png";
import screenshareIcon from "../assets/images/screenshareIcon.png";
import logoutIcon from "../assets/images/logoutIcon.png"
import ChatSection from "./ChatSection";
import callIcon from "../assets/images/callIcon.png"
import AudioVideoSection from "./AudioVideoSection";
import { useCallback, useState, useEffect } from "react";
import useSocket from "../hooks/useSocket";
import useCreatePeer from "../hooks/useCreatePeer";
import { useParams } from "react-router-dom";
function HomeMeetingSection() {
const socket=useSocket();
const {peer, createOffer, createAnswer}=useCreatePeer();
const {id}=useParams();
const [component, setComponent]=useState<React.ReactNode >(<ChatSection />)
const tabChange=(tab:string)=>{
switch (tab) {
case "chatSection":
setComponent( <ChatSection />);
break;
case "audioVideoSection":
setComponent(<AudioVideoSection />);
break;
default:
break;
}
}
const handleIncomingCall=useCallback(async(response:{offer:RTCSessionDescriptionInit, name:string})=>{
console.log(`Incoming call from `, response.name, response.offer);
const answer=await createAnswer(response.offer);
socket?.emit("call-accepted", {answer, id});
if(answer){
tabChange("audioVideoSection");
}
}, [socket, createAnswer]);
useEffect(()=>{
socket?.on("incoming-call", handleIncomingCall);
return ()=>{
socket?.off("incoming-call", handleIncomingCall);
}
}, [socket, handleIncomingCall]);
const handleCall=useCallback(async()=>{
try {
const offer= await createOffer();
socket?.emit("call-user", {offer, id});
if(offer){
tabChange("audioVideoSection");
}
} catch (error) {
console.log(`There is an error during create offer:${error}`);
}
},[socket, createOffer])
return (
<div className="w-full flex flex-col bg-secondarydark h-[42rem] mt-5 rounded-xl p-2 text-white shadow-md">
<div className="flex-grow mb-5 ">
{ component }
</div>
<div className="w-7/12 m-auto flex justify-around p-2 mb-3 h-1/6 bg-gray-600 rounded-md">
<button onClick={()=>tabChange("chatSection")}
className="px-8 py-8 bg-gray-700 hover:bg-gray-800 rounded-md">
<img className="w-10" src={chatIcon} />
</button>
<button onClick={handleCall}
className="px-8 py-8 bg-gray-700 hover:bg-gray-800 rounded-md">
<img className="w-10" src={callIcon} />
</button>
<button className="px-8 py-8 bg-gray-700 hover:bg-gray-800 rounded-md">
<img className="w-10" src={screenshareIcon} />
</button>
<button className="px-8 py-8 bg-gray-700 hover:bg-gray-800 rounded-md">
<img className="w-10" src={logoutIcon} />
</button>
</div>
</div>
);
}
export default HomeMeetingSection;
AudioVideoSection.tsx-----> where I implement sdp offer and answer setup with negotiation offer and answer with media pass
import micIcon from "../assets/images/micIcon.png";
import hangIcon from "../assets/images/hangIcon.png";
import videoIcon from "../assets/images/videoIcon.png";
import muteMicIcon from "../assets/images/muteMicIcon.png";
import muteVideoIcon from "../assets/images/muteVideoIcon.png";
import { useCallback, useEffect, useState } from "react";
import useCreatePeer from "../hooks/useCreatePeer";
import useSocket from "../hooks/useSocket";
import ReactPlayer from "react-player";
import { useParams } from "react-router-dom";
function AudioVideoSection() {
const [isMiceMute, setIsMiceMute] = useState<boolean>(false);
const [isVideoMute, setIsVideoMute] = useState<boolean>(false);
const [myStream, setMyStream] = useState<MediaStream | undefined>(undefined);
const [remoteStream, setRemoteStream] = useState<MediaStream | undefined>(
undefined
);
const socket = useSocket();
const { peer, createOffer, createAnswer, sendStream } = useCreatePeer();
const { id } = useParams();
const handleAudioFunction = () => {
setIsMiceMute((prevState) => !prevState);
};
const handleVideoFunction = () => {
setIsVideoMute((prevState) => !prevState);
};
const getMediaSteam = useCallback(async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
});
setMyStream(stream);
} catch (error) {
console.error("Error accessing media devices:", error);
}
}, []);
useEffect(() => {
getMediaSteam();
}, [getMediaSteam]);
const handleSdpAnswer = useCallback(
async (answer: RTCSessionDescriptionInit) => {
await peer.setRemoteDescription(answer);
console.log(`call got accepted`, answer);
if (myStream !=undefined) {
sendStream(myStream);
}
console.log("mystream-", myStream);
},
[peer, myStream, sendStream]
);
const handleNegotiationNeeded = useCallback(async () => {
const offer = await peer.createOffer();
socket?.emit("negotionNeeded", { offer, id });
}, [peer, socket, id]);
const handleNegoIncomming = useCallback(
async (offer: RTCSessionDescriptionInit) => {
console.log('incomming negotiation request-', offer);
const ans = await peer.createAnswer(offer);
socket?.emit("negotiationDone", { ans, id });
},
[socket, peer]
);
const handleNegotiationDone = useCallback(
async (ans: RTCSessionDescriptionInit) => {
await peer.setLocalDescription(ans);
console.log(`incomming negotiation ans-`, ans);
},
[peer]
);
const listenStream = (event: RTCTrackEvent) => {
console.log("incoming live stream", event.streams);
const remoteStreams = event.streams;
if (remoteStreams && remoteStreams[0]) {
setRemoteStream(remoteStreams[0]);
}
};
useEffect(() => {
socket?.on("call-accepted", handleSdpAnswer);
peer.addEventListener("negotiationneeded", handleNegotiationNeeded);
socket?.on("negotiationneeded", handleNegoIncomming);
socket?.on("negotiationDone", handleNegotiationDone);
peer.addEventListener("track", listenStream);
return () => {
socket?.off("call-accepted", handleSdpAnswer);
peer.removeEventListener("negotiationneeded", handleNegotiationNeeded);
socket?.off("negotiationneeded", handleNegoIncomming);
socket?.off("negotiationDone", handleNegotiationDone);
peer.removeEventListener("track", listenStream);
};
}, [
handleNegotiationNeeded,
handleNegoIncomming,
handleNegotiationDone,
peer,
socket,
listenStream,
handleSdpAnswer
]);
return (
<div className="flex flex-col h-full w-full items-center bg-gray-900 p-2 rounded-md">
<div className="bg-gray-800 z-20 w-full flex justify-center py-1 rounded-md sticky top-0">
<h2>Virtual Meeting Room</h2>
</div>
<div className="flex flex-grow w-full items-center">
<div className="w-10">
<ReactPlayer url={myStream} muted playing playsinline />
<ReactPlayer url={remoteStream} playing playsinline />
</div>
<div></div>
</div>
<div className="flex justify-around h-14 bg-slate-800 p-2 w-5/12 rounded-md ">
<button onClick={() => myStream && sendStream(myStream)}>
send stream
</button>
<button
onClick={handleAudioFunction}
className="w-10 p-2 bg-slate-500 hover:bg-slate-400 rounded-full "
>
{isMiceMute ? (
<img src={muteMicIcon} alt="audioIcon" />
) : (
<img src={micIcon} alt="audioIcon" />
)}
</button>
<button
onClick={handleVideoFunction}
className="w-10 p-2 bg-slate-700 rounded-full hover:bg-gray-600 "
>
{isVideoMute ? (
<img src={muteVideoIcon} alt="audioIcon" />
) : (
<img src={videoIcon} alt="audioIcon" />
)}
</button>
<button className="w-10 p-2 bg-slate-700 rounded-full hover:bg-slate-600">
<img src={hangIcon} alt="audioIcon" />
</button>
</div>
</div>
);
}
export default AudioVideoSection;
Peer.tsx-----> implement context for peer creation and offer and answer creation function
import React, { useMemo, useCallback } from "react";
import { withChildren } from "../types";
type PeerContextType = {
peer: RTCPeerConnection;
createOffer: () => Promise<RTCSessionDescriptionInit>;
createAnswer: (
offer: RTCSessionDescriptionInit
) => Promise<RTCSessionDescriptionInit>;
sendStream: (stream: MediaStream) => void;
} | null;
export const PeerContext = React.createContext<PeerContextType>(null);
export const PeerProvider = ({ children }: withChildren) => {
const peer = useMemo(
() =>
new RTCPeerConnection({
iceServers: [
{
urls: "stun:stunfi:3478",
},
],
}),
[]
);
const createOffer = useCallback(async () => {
const offer = await peer.createOffer();
await peer.setLocalDescription(offer);
return offer;
}, [peer]);
const createAnswer = useCallback(
async (offer: RTCSessionDescriptionInit) => {
await peer.setRemoteDescription(offer);
const answer = await peer.createAnswer();
await peer.setLocalDescription(answer);
return answer;
},
[peer]
);
const sendStream = async (stream: MediaStream) => {
// Remove existing tracks if any
const senders = peer.getSenders();
senders.forEach((sender) => peer.removeTrack(sender));
// Add new tracks
const tracks = stream.getTracks();
for (const track of tracks) {
peer.addTrack(track, stream);
}
};
return (
<PeerContext.Provider
value={{ peer, createOffer, createAnswer, sendStream }}
>
{children}
</PeerContext.Provider>
);
};
Backend for handling WebRTC comunication between two user------>
socket.on(
"call-user",
(data: { offer: RTCSessionDescriptionInit; id: string }) => {
try {
// console.log("incoming offer:", data.offer, "from id", data.id);
if (data.id) {
redisClient.hGet(`room:${data.id}`, socket.id).then((userInfo) => {
if (!userInfo) {
console.error("User info not found for socket:", socket.id);
return;
}
const user = JSON.parse(userInfo);
socket.to(data.id).emit("incoming-call", {
offer: data.offer,
name: user.name,
});
});
}
} catch (error) {
console.log(`there is error in call-user offer: ${error}`);
}
}
);
socket.on("call-accepted", (response:{answer:RTCSessionDescriptionInit, id:string})=>{
socket.to(response.id).emit("call-accepted", response.answer)
})
socket.on("negotiationneeded", (response:{offer:RTCSessionDescriptionInit, id:string})=>{
socket.to(response.id).emit("negotiationneeded", response.offer)
});
socket.on("negotiationDone", (response:{answer:RTCSessionDescriptionInit, id:string})=>{
socket.to(response.id).emit("negotiationDone", response.answer);
})