From c604f494eb5cfe320e9c8a42f35c62b2af4c5b1d Mon Sep 17 00:00:00 2001 From: Harshada2103 Date: Wed, 28 Oct 2026 12:31:40 +0530 Subject: [PATCH] Updated changes --- .../websocket/SocketIoWebSocketHandler.java | 6 + frontend/src/app/room/[roomId]/page.tsx | 12 +- frontend/src/components/CameraGrid.tsx | 24 ++-- frontend/src/hooks/useWebRTC.ts | 128 +++++++++++------- frontend/src/store/useRoomStore.ts | 25 +++- 5 files changed, 131 insertions(+), 64 deletions(-) diff --git a/backend/src/main/java/com/project/websocket/SocketIoWebSocketHandler.java b/backend/src/main/java/com/project/websocket/SocketIoWebSocketHandler.java index 2fbaa6d..3ac0336 100644 --- a/backend/src/main/java/com/project/websocket/SocketIoWebSocketHandler.java +++ b/backend/src/main/java/com/project/websocket/SocketIoWebSocketHandler.java @@ -177,6 +177,7 @@ private void handleSocketEvent(WebSocketSession session, String event, JsonNode case "webrtc-offer" -> handleWebRtcSignaling("webrtc-offer", socketId, dataMap); case "webrtc-answer" -> handleWebRtcSignaling("webrtc-answer", socketId, dataMap); case "webrtc-ice-candidate" -> handleWebRtcSignaling("webrtc-ice-candidate", socketId, dataMap); + case "webrtc-renegotiate" -> handleWebRtcSignaling("webrtc-renegotiate", socketId, dataMap); case "create-poll" -> handleCreatePoll(roomId, dataMap); case "vote-poll" -> handleVotePoll(roomId, dataMap); case "close-poll" -> handleClosePoll(roomId, dataMap); @@ -205,6 +206,11 @@ private void handleJoinRoom(WebSocketSession session, Map dataMa Map enhancedUser; synchronized (roomLifecycleLock) { + if (roomId.equals(socketToRoom.get(socketId))) { + emitToSocket(socketId, "room-users", roomStateManager.getUsersInRoom(roomId)); + return; + } + if (!roomService.prepareRoomForJoin(roomId, ownerId)) { emitToSocket(socketId, "room-closed", Map.of("roomId", roomId)); try { diff --git a/frontend/src/app/room/[roomId]/page.tsx b/frontend/src/app/room/[roomId]/page.tsx index 3abfe37..658b8a6 100644 --- a/frontend/src/app/room/[roomId]/page.tsx +++ b/frontend/src/app/room/[roomId]/page.tsx @@ -79,8 +79,8 @@ export default function RoomPage() { // Start WebRTC connection — pass state refs so recovery restores correct state // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { localStream, toggleScreenShare, ensureAudioTrack, ensureVideoTrack, disableVideoTrack } = useWebRTC( - roomId as string, socketId, isMutedRef, isVideoOnRef + const { localStream, toggleScreenShare, ensureAudioTrack, ensureVideoTrack, disableVideoTrack, refreshMediaConnections } = useWebRTC( + roomId as string, socketId, isMutedRef, isVideoOnRef, socket ); const localStreamRef = useRef(null); @@ -331,6 +331,7 @@ export default function RoomPage() { // ── Stabilized toggle handlers (no re-creation on each render) ────── const toggleMic = useCallback(async () => { const newMuted = !isMutedRef.current; + let acquiredTrack = false; if (newMuted) { localStream?.getAudioTracks().forEach(track => { track.enabled = false; }); @@ -338,6 +339,7 @@ export default function RoomPage() { let track = localStreamRef.current?.getAudioTracks().find(audioTrack => audioTrack.readyState === "live"); if (!track) { track = await ensureAudioTrack() ?? undefined; + acquiredTrack = Boolean(track); } if (!track) { toast({ @@ -353,7 +355,8 @@ export default function RoomPage() { setIsMuted(newMuted); if (socketId) updateUser(socketId, { isMuted: newMuted }); if (socket) socket.emit("toggle-mic", { roomId, userId: socketId, isMuted: newMuted }); - }, [localStream, socketId, socket, roomId, updateUser, ensureAudioTrack, toast]); + if (acquiredTrack) refreshMediaConnections(); + }, [localStream, socketId, socket, roomId, updateUser, ensureAudioTrack, toast, refreshMediaConnections]); const toggleCamera = useCallback(async () => { if (isTogglingCamera.current) return; @@ -398,10 +401,11 @@ export default function RoomPage() { setIsVideoOn(newVideoOn); if (socketId) updateUser(socketId, { isVideoOn: newVideoOn }); if (socket) socket.emit("toggle-camera", { roomId, userId: socketId, isVideoOn: newVideoOn }); + refreshMediaConnections(); } finally { isTogglingCamera.current = false; } - }, [socketId, socket, roomId, updateUser, ensureVideoTrack, disableVideoTrack, toast]); + }, [socketId, socket, roomId, updateUser, ensureVideoTrack, disableVideoTrack, toast, refreshMediaConnections]); const toggleHand = useCallback(() => { setIsHandRaised(prev => { diff --git a/frontend/src/components/CameraGrid.tsx b/frontend/src/components/CameraGrid.tsx index dc69233..32be68b 100644 --- a/frontend/src/components/CameraGrid.tsx +++ b/frontend/src/components/CameraGrid.tsx @@ -10,7 +10,7 @@ import { useEffect, useRef, useState } from "react"; * The video element is ALWAYS mounted — we toggle visibility via CSS * instead of unmounting/remounting, which eliminates the flickering. */ -function VideoTile({ stream, isLocal, isVideoOn }: { stream: MediaStream | undefined; isLocal?: boolean; isVideoOn?: boolean }) { +function VideoTile({ stream, isVideoOn }: { stream: MediaStream | undefined; isVideoOn?: boolean }) { const videoRef = useRef(null); useEffect(() => { @@ -53,7 +53,7 @@ function VideoTile({ stream, isLocal, isVideoOn }: { stream: MediaStream | undef ref={videoRef} autoPlay playsInline - muted={isLocal} + muted disablePictureInPicture={true} className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${isVideoOn && stream ? 'opacity-100' : 'opacity-0 pointer-events-none'}`} /> @@ -84,7 +84,8 @@ function AudioTile({ stream }: { stream: MediaStream }) { } }; - attachAudio(); + const retryAudio = () => { void attachAudio(); }; + void attachAudio(); const handleVisibilityChange = () => { if (document.visibilityState === "visible") attachAudio(); @@ -93,17 +94,24 @@ function AudioTile({ stream }: { stream: MediaStream }) { document.addEventListener("visibilitychange", handleVisibilityChange); window.addEventListener("focus", handleFocus); - stream.addEventListener("addtrack", attachAudio); + document.addEventListener("pointerdown", retryAudio); + document.addEventListener("keydown", retryAudio); + stream.addEventListener("addtrack", retryAudio); + const audioTracks = stream.getAudioTracks(); + audioTracks.forEach((track) => track.addEventListener("unmute", retryAudio)); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); window.removeEventListener("focus", handleFocus); - stream.removeEventListener("addtrack", attachAudio); + document.removeEventListener("pointerdown", retryAudio); + document.removeEventListener("keydown", retryAudio); + stream.removeEventListener("addtrack", retryAudio); + audioTracks.forEach((track) => track.removeEventListener("unmute", retryAudio)); }; }, [stream]); return ( <> -