From 466c1f34b60141f6cc08fc5628f179641e6ad6bb Mon Sep 17 00:00:00 2001 From: sarah kamal Date: Sun, 22 Dec 2024 01:41:54 +0200 Subject: [PATCH 1/5] fix: call ended --- app/src/features/calls/CallLayout.tsx | 2 +- .../features/calls/context/CallProvider.tsx | 51 +++++++++++-------- app/src/sockets/SocketProvider.tsx | 3 +- app/src/types/calls.ts | 4 +- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/app/src/features/calls/CallLayout.tsx b/app/src/features/calls/CallLayout.tsx index 99470a8b..760c0b63 100644 --- a/app/src/features/calls/CallLayout.tsx +++ b/app/src/features/calls/CallLayout.tsx @@ -252,7 +252,7 @@ export default function CallLayout({ {callStatus === "incoming" ? ( endCall()} + onClick={() => endCall(null)} $bgColor="var(--color-error)" $bgColorHover="var(--color-error-shade)" > diff --git a/app/src/features/calls/context/CallProvider.tsx b/app/src/features/calls/context/CallProvider.tsx index fb4d6320..25d6ba71 100644 --- a/app/src/features/calls/context/CallProvider.tsx +++ b/app/src/features/calls/context/CallProvider.tsx @@ -101,29 +101,38 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ [setCallStatus, userId] ); - const endCall = useCallback(() => { - setCallStatus("inactive"); - callIdRef.current = null; - senderIdRef.current = null; - chatIdRef.current = null; - if (localStream.current) { - localStream.current.getTracks().forEach((track) => { - track.stop(); - }); - } - clientIdRef.current.forEach((clientData, clientId) => { - if (clientData.connection) { - clientData.connection.close(); - } + const endCall = useCallback( + (clientId = null) => { + if (clientId) { + const clientData = clientIdRef.current.get(clientId); + if (clientData?.connection) clientData.connection.close(); + removeClientId(clientId); + } else { + setCallStatus("inactive"); + callIdRef.current = null; + senderIdRef.current = null; + chatIdRef.current = null; + if (localStream.current) { + localStream.current.getTracks().forEach((track) => { + track.stop(); + }); + } + clientIdRef.current.forEach((clientData, clientId) => { + if (clientData.connection) { + clientData.connection.close(); + } - removeClientId(clientId); - }); - const remoteAudioElements = document.querySelectorAll("audio"); - remoteAudioElements.forEach((audio) => audio.remove()); + removeClientId(clientId); + }); + const remoteAudioElements = document.querySelectorAll("audio"); + remoteAudioElements.forEach((audio) => audio.remove()); - localStream.current = null; - clearClientIds(); - }, [clearClientIds, removeClientId, setCallStatus]); + localStream.current = null; + clearClientIds(); + } + }, + [clearClientIds, removeClientId, setCallStatus] + ); const acceptCall = useCallback(() => { if (callIdRef.current && chatIdRef.current) { diff --git a/app/src/sockets/SocketProvider.tsx b/app/src/sockets/SocketProvider.tsx index 219014bf..6496ec5a 100644 --- a/app/src/sockets/SocketProvider.tsx +++ b/app/src/sockets/SocketProvider.tsx @@ -71,6 +71,7 @@ function SocketProvider({ children }: SocketProviderProps) { createAnswer, startPeerConnection, offer, + endCall, acceptCall: setAcceptedCall } = useCallContext(); const navigate = useNavigate(); @@ -139,7 +140,7 @@ function SocketProvider({ children }: SocketProviderProps) { const finishCall = useCallback(() => { if (socket?.connected && socket && callId.current) { socket.emit("LEAVE", { voiceCallId: callId.current }); - endCall(); + endCall(null); } }, [socket, callId, endCall]); const sendAnswer = useCallback( diff --git a/app/src/types/calls.ts b/app/src/types/calls.ts index a877a533..b5f96fab 100644 --- a/app/src/types/calls.ts +++ b/app/src/types/calls.ts @@ -13,7 +13,7 @@ export interface CallContextType { >; // callAccepted: React.RefObject; joinCall: (newCallId: string, newSenderId: string, newChatId: string) => void; - endCall: () => void; + endCall: (clientId: string | null) => void; acceptCall: () => void; startPeerConnection: ( @@ -31,7 +31,7 @@ export interface CallContextType { senderId: string ) => Promise; setChatId: (chatId: string) => void; - recieveAnswer: (answer: RTCSessionDescriptionInit, senderId: string) => void; + recieveAnswer: (answer: RTCSessionDescriptionInit, senderId: string) => void; } export type CallStatus = | "inactive" From c3f98b8a11fce20e82e33a365b5f12910b86a0d8 Mon Sep 17 00:00:00 2001 From: sarah kamal Date: Sun, 22 Dec 2024 02:11:35 +0200 Subject: [PATCH 2/5] fix: fix mute button --- app/src/components/ExpandingTextArea.tsx | 2 -- app/src/data/icons.tsx | 4 ++++ app/src/features/calls/CallLayout.tsx | 17 +++++++++++++++-- app/src/features/calls/context/CallProvider.tsx | 12 ++++++++++-- app/src/features/chats/hooks/useChatInput.ts | 3 +-- app/src/types/calls.ts | 3 ++- 6 files changed, 32 insertions(+), 9 deletions(-) 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"), + } }; 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..0fe2b818 100644 --- a/app/src/features/calls/context/CallProvider.tsx +++ b/app/src/features/calls/context/CallProvider.tsx @@ -6,7 +6,6 @@ import { CallStatus } from "types/calls"; import { TURN_USERNAME, TURN_PASSWORD } from "@constants"; 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"] }, @@ -133,7 +132,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 ( @@ -302,6 +308,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/types/calls.ts b/app/src/types/calls.ts index b5f96fab..bba20d52 100644 --- a/app/src/types/calls.ts +++ b/app/src/types/calls.ts @@ -14,7 +14,8 @@ export interface CallContextType { // callAccepted: React.RefObject; joinCall: (newCallId: string, newSenderId: string, newChatId: string) => void; endCall: (clientId: string | null) => void; - + mute: () => void; + unmute: () => void; acceptCall: () => void; startPeerConnection: ( clientId: string From f57372a1ebebbdf7704cacf9adb6ff707b4874ef Mon Sep 17 00:00:00 2001 From: sarah kamal Date: Sun, 22 Dec 2024 20:38:21 +0200 Subject: [PATCH 3/5] fix-0 --- .../features/calls/context/CallProvider.tsx | 38 ++++++++++++++++--- app/src/sockets/SocketProvider.tsx | 1 + 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/src/features/calls/context/CallProvider.tsx b/app/src/features/calls/context/CallProvider.tsx index b0a829d2..eb7dca54 100644 --- a/app/src/features/calls/context/CallProvider.tsx +++ b/app/src/features/calls/context/CallProvider.tsx @@ -4,6 +4,7 @@ import { useAppSelector } from "@hooks/useGlobalState"; import { callStatusEmitter } from "./callStatusEmitter"; import { CallStatus } from "types/calls"; import { TURN_USERNAME, TURN_PASSWORD } from "@constants"; +console.log(TURN_PASSWORD, TURN_USERNAME); const Servers = { iceServers: [ { @@ -171,9 +172,32 @@ 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.ontrack = (event) => { + console.log("audio"); if (event.track.kind === "audio") { const remoteAudio = document.createElement("audio"); remoteAudio.srcObject = event.streams[0]; @@ -183,6 +207,7 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ } }; const offer = await peerConnection.createOffer(); + console.log(clientIdRef.current); if (!hasClientId(clientId) || hasClientId(clientId, false)) { await peerConnection.setLocalDescription(offer); addClientId(clientId, peerConnection, true); @@ -243,15 +268,14 @@ 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; document.body.appendChild(remoteAudio); } }; @@ -276,7 +300,9 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ break; } }; - + await peerConnection.setRemoteDescription(data); + const answer = await peerConnection.createAnswer(); + await peerConnection.setLocalDescription(answer); addClientId(senderId, peerConnection, false); return answer; } catch (error) { diff --git a/app/src/sockets/SocketProvider.tsx b/app/src/sockets/SocketProvider.tsx index 6496ec5a..36289b30 100644 --- a/app/src/sockets/SocketProvider.tsx +++ b/app/src/sockets/SocketProvider.tsx @@ -288,6 +288,7 @@ function SocketProvider({ children }: SocketProviderProps) { } ); socket.on("CLIENT-JOINED", async ({ clientId }) => { + console.log("client joined", clientId); const offer = await startPeerConnection(clientId); try { if (offer && clientId) { From b14b9ade160f486dbdcdbc1da699fb822ca7ebb1 Mon Sep 17 00:00:00 2001 From: sarah kamal Date: Sun, 22 Dec 2024 21:21:20 +0200 Subject: [PATCH 4/5] fix-1 --- .../features/calls/context/CallProvider.tsx | 46 +++++++++++++++---- app/src/sockets/SocketProvider.tsx | 29 ------------ 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/app/src/features/calls/context/CallProvider.tsx b/app/src/features/calls/context/CallProvider.tsx index eb7dca54..24cff8a8 100644 --- a/app/src/features/calls/context/CallProvider.tsx +++ b/app/src/features/calls/context/CallProvider.tsx @@ -4,6 +4,8 @@ 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: [ @@ -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); @@ -195,7 +198,16 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ 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") { @@ -216,17 +228,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] @@ -300,17 +316,27 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ break; } }; + 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, false); + 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) => { diff --git a/app/src/sockets/SocketProvider.tsx b/app/src/sockets/SocketProvider.tsx index 36289b30..6b56ac7e 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 ( @@ -293,7 +266,6 @@ function SocketProvider({ children }: SocketProviderProps) { try { if (offer && clientId) { sendOffer(clientId, offer); - handleIceCandidates(clientId); } else throw new Error("Failed to send offer"); } catch { console.error("Failed to send offer"); @@ -307,7 +279,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"); } From 9d4edc5def53260e24eb97d2ff4732f3c90c9c44 Mon Sep 17 00:00:00 2001 From: sarah kamal Date: Sun, 22 Dec 2024 22:31:49 +0200 Subject: [PATCH 5/5] remove audio --- app/src/features/calls/context/CallProvider.tsx | 2 ++ app/src/sockets/SocketProvider.tsx | 3 +++ 2 files changed, 5 insertions(+) diff --git a/app/src/features/calls/context/CallProvider.tsx b/app/src/features/calls/context/CallProvider.tsx index 24cff8a8..0a321897 100644 --- a/app/src/features/calls/context/CallProvider.tsx +++ b/app/src/features/calls/context/CallProvider.tsx @@ -215,6 +215,7 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ remoteAudio.srcObject = event.streams[0]; remoteAudio.autoplay = true; remoteAudio.controls = true; + remoteAudio.style.display = "none"; document.body.appendChild(remoteAudio); } }; @@ -292,6 +293,7 @@ export const CallProvider: React.FC<{ children: ReactNode }> = ({ remoteAudio.srcObject = event.streams[0]; remoteAudio.autoplay = true; remoteAudio.controls = false; + remoteAudio.style.display = "none"; document.body.appendChild(remoteAudio); } }; diff --git a/app/src/sockets/SocketProvider.tsx b/app/src/sockets/SocketProvider.tsx index 6b56ac7e..c9040649 100644 --- a/app/src/sockets/SocketProvider.tsx +++ b/app/src/sockets/SocketProvider.tsx @@ -94,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, @@ -264,6 +265,8 @@ function SocketProvider({ children }: SocketProviderProps) { 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); } else throw new Error("Failed to send offer");