diff --git a/app/src/components/ExpandingTextArea.tsx b/app/src/components/ExpandingTextArea.tsx index 74beb98e..c49ea04b 100644 --- a/app/src/components/ExpandingTextArea.tsx +++ b/app/src/components/ExpandingTextArea.tsx @@ -39,8 +39,6 @@ function ExpandingTextArea({ input, setInput, onKeyDown }: PropsType) { } }, [input]); - console.log(filteredMembers); - return ( = { Mention:{ importFn: () => import ("@mui/icons-material/AlternateEmail"), }, + UnMute: { + importFn: () => import("@mui/icons-material/MicOff"), + defaultProps: { fontSize: "large" }, + } }; const iconCache = new Map(); diff --git a/app/src/features/calls/CallLayout.tsx b/app/src/features/calls/CallLayout.tsx index 760c0b63..c36dc049 100644 --- a/app/src/features/calls/CallLayout.tsx +++ b/app/src/features/calls/CallLayout.tsx @@ -8,6 +8,7 @@ import { getChatByID } from "@features/chats/utils/helpers"; import { useAppSelector } from "@hooks/useGlobalState"; import { useSocket } from "@hooks/useSocket"; import { EnableSpeaker } from "./SpeakerEnable"; +import { useState } from "react"; const ModalContainer = styled.div` position: fixed; @@ -203,12 +204,22 @@ export default function CallLayout({ callStatus }: PropsType) { const { acceptCall, finishCall } = useSocket(); - const { endCall, chatId } = useCallContext(); + const { endCall, chatId, mute, unmute } = useCallContext(); + const [isMuted, setIsMuted] = useState(false); const chats = useAppSelector((state) => state.chats.chats); const chat = getChatByID({ chatID: chatId.current ?? "", chats: chats }); + const toggleMute = () => { + if (isMuted) { + setIsMuted(false); + unmute(); + } else { + setIsMuted(true); + mute(); + } + }; return ( <> @@ -233,7 +244,9 @@ export default function CallLayout({ - {getIcon("Mute")} + + {isMuted ? getIcon("UnMute") : getIcon("Mute")} + unmute {callStatus === "incoming" && ( diff --git a/app/src/features/calls/context/CallProvider.tsx b/app/src/features/calls/context/CallProvider.tsx index 25d6ba71..0a321897 100644 --- a/app/src/features/calls/context/CallProvider.tsx +++ b/app/src/features/calls/context/CallProvider.tsx @@ -4,9 +4,11 @@ import { useAppSelector } from "@hooks/useGlobalState"; import { callStatusEmitter } from "./callStatusEmitter"; import { CallStatus } from "types/calls"; import { TURN_USERNAME, TURN_PASSWORD } from "@constants"; +import { useSocket } from "utils/socket"; + +console.log(TURN_PASSWORD, TURN_USERNAME); const Servers = { iceServers: [ - { urls: ["stun:stun.l.google.com:19302", "stun:stun.l.google.com:5349"] }, { urls: ["stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"] }, @@ -35,6 +37,7 @@ const Servers = { export const CallProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const socket = useSocket(); const userId = useAppSelector((state) => state.user.userInfo.id); const callIdRef = useRef(null); const senderIdRef = useRef(null); @@ -134,6 +137,14 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ [clearClientIds, removeClientId, setCallStatus] ); + const mute = () => { + if (localStream.current) + localStream.current.getAudioTracks()[0].enabled = false; + }; + const unmute = () => { + if (localStream.current) + localStream.current.getAudioTracks()[0].enabled = true; + }; const acceptCall = useCallback(() => { if (callIdRef.current && chatIdRef.current) { if ( @@ -164,18 +175,52 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ } const peerConnection = new RTCPeerConnection(Servers); localStream.current.getTracks().forEach((track) => { - peerConnection?.addTrack(track, localStream.current); + peerConnection.addTrack(track, localStream.current); }); + peerConnection.oniceconnectionstatechange = () => { + const state = peerConnection.iceConnectionState; + + switch (state) { + case "connected": + console.log("Peer connection established."); + break; + case "disconnected": + console.warn("Peer connection disconnected."); + break; + case "failed": + console.error("Peer connection failed. Restarting ICE?"); + break; + case "closed": + console.log("Peer connection closed."); + break; + default: + console.log("ICE connection state:", state); + break; + } + }; + peerConnection.onicecandidate = (event) => { + if (event.candidate && socket?.connected) { + socket.emit("SIGNAL-SERVER", { + type: "ICE", + voiceCallId: callIdRef.current, + data: event.candidate, + targetId: clientId + }); + } + }; peerConnection.ontrack = (event) => { + console.log("audio"); if (event.track.kind === "audio") { const remoteAudio = document.createElement("audio"); remoteAudio.srcObject = event.streams[0]; remoteAudio.autoplay = true; remoteAudio.controls = true; + remoteAudio.style.display = "none"; document.body.appendChild(remoteAudio); } }; const offer = await peerConnection.createOffer(); + console.log(clientIdRef.current); if (!hasClientId(clientId) || hasClientId(clientId, false)) { await peerConnection.setLocalDescription(offer); addClientId(clientId, peerConnection, true); @@ -184,17 +229,21 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ } return null; }, - [addClientId, endCall, hasClientId, setCallStatus] + [addClientId, endCall, hasClientId, setCallStatus, socket] ); const recieveICE = useCallback( async (candidate: RTCIceCandidateInit, senderId: string) => { - if (hasClientId(senderId, true)) { - const clientData = clientIdRef.current.get(senderId); - if (clientData) { - if (!clientData.connection) return; - clientData.connection.addIceCandidate(candidate); - } + try { + if (hasClientId(senderId, true)) { + const clientData = clientIdRef.current.get(senderId); + if (clientData) { + if (!clientData.connection) throw new Error("No connection found."); + clientData.connection.addIceCandidate(candidate); + } else throw new Error("No connection found."); + } else throw new Error("No connection found."); + } catch (error) { + console.error("Error recieving ICE candidate:", error); } }, [hasClientId] @@ -236,15 +285,15 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ localStream.current.getTracks().forEach((track) => { peerConnection.addTrack(track, localStream.current); }); - await peerConnection.setRemoteDescription(data); - const answer = await peerConnection.createAnswer(); - await peerConnection.setLocalDescription(answer); + peerConnection.ontrack = (event) => { + console.log("adiooo"); if (event.track.kind === "audio") { const remoteAudio = document.createElement("audio"); remoteAudio.srcObject = event.streams[0]; remoteAudio.autoplay = true; - remoteAudio.controls = true; + remoteAudio.controls = false; + remoteAudio.style.display = "none"; document.body.appendChild(remoteAudio); } }; @@ -269,15 +318,27 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ break; } }; - - addClientId(senderId, peerConnection, false); + peerConnection.onicecandidate = (event) => { + if (event.candidate && socket?.connected) { + socket.emit("SIGNAL-SERVER", { + type: "ICE", + voiceCallId: callIdRef.current, + data: event.candidate, + targetId: senderId + }); + } + }; + await peerConnection.setRemoteDescription(data); + const answer = await peerConnection.createAnswer(); + await peerConnection.setLocalDescription(answer); + addClientId(senderId, peerConnection, true); return answer; } catch (error) { console.error("Error creating answer:", error); return null; } }, - [addClientId, endCall] + [addClientId, endCall, socket] ); const getPeerConnection = useCallback((clientId: string) => { @@ -302,6 +363,8 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ endCall, recieveICE, recieveAnswer, + mute, + unmute, getPeerConnection }; diff --git a/app/src/features/chats/hooks/useChatInput.ts b/app/src/features/chats/hooks/useChatInput.ts index b52e8f69..3cec2f65 100644 --- a/app/src/features/chats/hooks/useChatInput.ts +++ b/app/src/features/chats/hooks/useChatInput.ts @@ -28,8 +28,7 @@ function useChatInput() { setIsEmojiSelectorOpen(false); }; - const handleSubmit = (e: Event, voiceNoteName = "") => { - console.log(voiceNoteName); + const handleSubmit = (e: Event) => { e.preventDefault(); setIsEmojiSelectorOpen(false); if (isRecording !== "idle") return; diff --git a/app/src/sockets/SocketProvider.tsx b/app/src/sockets/SocketProvider.tsx index 6496ec5a..c9040649 100644 --- a/app/src/sockets/SocketProvider.tsx +++ b/app/src/sockets/SocketProvider.tsx @@ -67,7 +67,6 @@ function SocketProvider({ children }: SocketProviderProps) { recieveICE, recieveAnswer, setChatId, - getPeerConnection, createAnswer, startPeerConnection, offer, @@ -85,32 +84,6 @@ function SocketProvider({ children }: SocketProviderProps) { const { decrypt } = useEncryptDecrypt(); const { chat } = useChat(); - const handleIceCandidates = useCallback( - async (clientId: string) => { - return new Promise((resolve) => { - const timeout = setTimeout(() => { - resolve(null); - }, 10000); - if (socket?.connected && socket && callId.current) { - const peerConnection = getPeerConnection(clientId); - if (!peerConnection) return; - peerConnection.onicecandidate = (event) => { - if (!event.candidate) { - clearTimeout(timeout); - resolve(null); - } - socket.emit("SIGNAL-SERVER", { - type: "ICE", - voiceCallId: callId.current, - data: event.candidate, - targetId: clientId - }); - }; - } - }); - }, - [socket, callId, getPeerConnection] - ); const sendOffer = useCallback( (clientId: string, offer: RTCSessionDescriptionInit) => { if ( @@ -121,6 +94,7 @@ function SocketProvider({ children }: SocketProviderProps) { callId.current && clientId ) { + console.log("Sending offer to", clientId); socket.emit("SIGNAL-SERVER", { type: "OFFER", voiceCallId: callId.current, @@ -288,11 +262,13 @@ function SocketProvider({ children }: SocketProviderProps) { } ); socket.on("CLIENT-JOINED", async ({ clientId }) => { + console.log("client joined", clientId); const offer = await startPeerConnection(clientId); try { + console.log("offer", offer); + console.log("clientId", clientId); if (offer && clientId) { sendOffer(clientId, offer); - handleIceCandidates(clientId); } else throw new Error("Failed to send offer"); } catch { console.error("Failed to send offer"); @@ -306,7 +282,6 @@ function SocketProvider({ children }: SocketProviderProps) { const answer = await createAnswer(data, senderId); if (answer) { sendAnswer(senderId, answer); - handleIceCandidates(senderId); } else { console.error("Failed to create answer"); } diff --git a/app/src/types/calls.ts b/app/src/types/calls.ts index b5f96fab..36758e03 100644 --- a/app/src/types/calls.ts +++ b/app/src/types/calls.ts @@ -15,6 +15,9 @@ export interface CallContextType { joinCall: (newCallId: string, newSenderId: string, newChatId: string) => void; endCall: (clientId: string | null) => void; + mute: () => void; + unmute: () => void; + acceptCall: () => void; startPeerConnection: ( clientId: string