Hi, I encountered an issue while creating a group video call. The video is functioning as expected, but unfortunately, there seems to be a problem with the audio. Below is the code I've implemented. Can you spot any mistakes?
export function MultipleWebRTCVoiceChat() {
const navigation = useNavigation();
let peerConnections = React.useRef<Record<string, RTCPeerConnection | null>>(
{}
).current;
const [localStream, setLocalStream, getLocalStream] =
useAdvancedState<MediaStream | null>(null);
const [remoteStreams, setRemoteStreams] = React.useState<
Record<string, MediaStream>
>({});
const [socket, setSocket, getSocket] = useAdvancedState<any>(null);
const mySocketId = React.useRef<string>("");
const localStreamRef = React.useRef<MediaStream | null>(null);
const [room, setRoom] = React.useState<string>("123");
const [isMuted, setIsMuted] = React.useState<boolean>(false);
const [isCamera, setIsCamera] = React.useState<boolean>(true);
const [isConnected, setIsConnected] = React.useState<boolean>(false);
const [isJoined, setIsJoined] = React.useState<boolean>(false);
const [isConnectError, setIsConnectError] = React.useState<boolean>(false);
const getPeerConnection = async (socketId: string) => {
if (peerConnections[socketId]) {
return peerConnections[socketId];
}
const localStream = getLocalStream();
const configuration = {
iceServers: [
{
urls: "stun:stun.l.google.com:19302",
},
{
urls: "stun:stun1.l.google.com:19302",
},
],
};
const pc = new RTCPeerConnection(configuration);
// localStream?.getAudioTracks().forEach((track: any) => {
// pc.addTrack(track, localStream as any);
// });
// localStream?.getVideoTracks().forEach((track: any) => {
// pc.addTrack(track, localStream as any);
// });
localStream?.getTracks().forEach((track: any) => {
pc.addTrack(track, localStream as any);
});
pc.onsignalingstatechange = (event: any) => {
console.log("onsignalingstatechange", event);
};
pc.onaddstream = (event: any) => {
console.log("onaddstream", event);
setRemoteStreams((old) => ({
...old,
[socketId]: event.stream,
}));
};
pc.onicecandidate = (event: any) => {
const socket = getSocket();
if (event.candidate) {
socket.emit("ice-candidate", {
room: room,
candidate: event.candidate,
});
}
};
pc.ontrack = (event: any) => {
if (mySocketId.current !== socketId) {
console.log("ontrack", mySocketId.current, socketId, event.track.kind);
}
setRemoteStreams((old) => ({
...old,
[socketId]: event.streams[0],
}));
};
peerConnections[socketId] = pc;
return pc;
};
const init = async () => {
localStreamRef.current = await mediaDevices.getUserMedia({
audio: true,
video: isCamera,
});
setLocalStream(localStreamRef.current as any);
const socket = io(SOCKET_STREAM_URL, {
transports: ["websocket"],
});
socket.on("connect_error", async (err: any) => {
console.log("connect_error", err, err.message);
setIsConnectError(true);
setIsConnected(false);
setIsJoined(false);
});
socket.on("connect", () => {
setIsConnectError(false);
setIsConnected(true);
const socketId = socket.id;
setSocket(socket);
mySocketId.current = socketId;
handleJoinRoom();
});
socket.on("offer", handleOffer);
socket.on("answer", handleAnswer);
socket.on("ice-candidate", handleIceCandidate);
socket.on("user-connected", handleUserConnected);
socket.on("user-disconnected", handleUserDisconnected);
};
const handleUserConnected = async (socketId: string) => {
console.log("handleUserConnected", socketId);
const socket = getSocket();
const pc = await getPeerConnection(socketId);
const offer = await pc.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
});
await pc.setLocalDescription(new RTCSessionDescription(offer));
socket.emit("offer", {
target: socketId,
offer: offer,
});
};
const handleUserDisconnected = (socketId: string) => {
peerConnections[socketId]?.close();
peerConnections[socketId] = null;
setRemoteStreams((old) => {
delete old[socketId];
return old;
});
};
const handleOffer = async (event: { source: string; offer: any }) => {
const { source, offer } = event;
console.log("handleOffer", event);
const socket = getSocket();
const pc = await getPeerConnection(source);
await pc.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await pc.createAnswer();
await pc.setLocalDescription(new RTCSessionDescription(answer));
socket.emit("answer", {
target: source,
answer: answer,
});
};
const handleAnswer = async (payload: { source: string; answer: any }) => {
console.log("handleAnswer", payload);
const { source, answer } = payload;
const pc = await getPeerConnection(source);
await pc.setRemoteDescription(new RTCSessionDescription(answer));
};
const handleIceCandidate = async (event: {
source: string;
candidate: any;
}) => {
console.log("handleIceCandidate", event);
const { source, candidate } = event;
const pc = await getPeerConnection(source);
if (!pc) {
return;
}
await pc.addIceCandidate(new RTCIceCandidate(candidate));
};
useEffect(() => {
init();
}, []);
const handleJoinRoom = async () => {
const socket = getSocket();
socket.emit("join-room", room);
setIsJoined(true);
};
const handleLeaveRoom = () => {
const socket = getSocket();
socket.emit("leave-room", room);
setIsJoined(false);
};
const handleShareScreen = async () => {
const stream = await mediaDevices.getDisplayMedia({
audio: true,
video: true,
});
setLocalStream(stream as any);
stream.getTracks().forEach((track: any) => {
peerConnections[room]?.addTrack(track, stream);
});
};
const handleShareCamera = async () => {
localStream?.getVideoTracks().forEach((track: any) => {
track.enabled = true;
});
};
const handleShareCameraOff = async () => {
localStream?.getVideoTracks().forEach((track: any) => {
track.enabled = false;
});
};
const handleShareAudio = async () => {
const stream = await mediaDevices.getUserMedia({
audio: true,
video: false,
});
setLocalStream(stream as any);
stream.getTracks().forEach((track: any) => {
peerConnections[room]?.addTrack(track, stream);
});
};
const handleMute = () => {
const localStream = getLocalStream();
localStream?.getAudioTracks().forEach((track: any) => {
track.enabled = !track.enabled;
});
setIsMuted(true);
};
const handleUnmute = () => {
const localStream = getLocalStream();
localStream?.getAudioTracks().forEach((track: any) => {
track.enabled = !track.enabled;
});
setIsMuted(false);
};
useEffect(() => {
if (isMuted) {
handleMute();
} else {
handleUnmute();
}
}, [isMuted]);
useEffect(() => {
if (isCamera) {
handleShareCamera();
} else {
handleShareCameraOff();
}
}, [isCamera]);
console.log("remoteStreams", remoteStreams);
if (isConnectError) {
return (
<View
style={{
backgroundColor: "transparent",
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text
style={{
color: "white",
fontSize: 20,
textAlign: "center",
}}
>
Cannot connect to video call
</Text>
</View>
);
}
return (
<View
style={{
backgroundColor: "transparent",
}}
>
{isConnected ? (
<View
style={{
backgroundColor: "transparent",
}}
>
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
backgroundColor: "transparent",
padding: 16,
}}
>
<StreamMedia
isMuted={isMuted}
fullName="You"
isVideoOff={!isCamera}
stream={localStream}
/>
{Object.keys(remoteStreams).map((id) => (
<StreamMedia stream={remoteStreams[id]} />
))}
</View>
</View>
) : (
<View></View>
)}
<Row
style={{
marginLeft: 26,
}}
>
<Col>
<SwitchButton
value={isMuted}
onChange={setIsMuted}
views={[
{
value: false,
view: <Octicons name="unmute" size={14} color="gray" />,
},
{
value: true,
view: <Octicons name="mute" size={14} color="gray" />,
},
]}
/>
</Col>
<Col
style={{
marginLeft: 6,
}}
>
<SwitchButton
value={isCamera}
onChange={setIsCamera}
views={[
{
value: false,
view: <Feather name="video-off" size={14} color="gray" />,
},
{
value: true,
view: <Feather name="video" size={14} color="gray" />,
},
]}
/>
</Col>
{isConnected && (
<Col
style={{
marginLeft: 6,
}}
>
<SwitchButton
value={isJoined}
onChange={!isJoined ? handleJoinRoom : handleLeaveRoom}
views={[
{
value: false,
view: <Text>Join</Text>,
},
{
value: true,
view: <Text>End Calling</Text>,
},
]}
/>
</Col>
)}
</Row>
</View>
);
}