diff --git a/packages/server/src/ws/events.ts b/packages/server/src/ws/events.ts index d65537e5..8eb70705 100644 --- a/packages/server/src/ws/events.ts +++ b/packages/server/src/ws/events.ts @@ -384,6 +384,23 @@ function handleVoiceJoin(event: Record, userId: string): void { return; } + // If the user is already in this exact channel (e.g. WS reconnect re-registration), + // skip the leave+join broadcast to avoid visual flicker for other users. + const currentChannel = connectionManager.getUserVoiceChannel(userId); + if (currentChannel === channelId) { + const status = connectionManager.getVoiceUserStatus(userId); + if (status) { + connectionManager.sendToServer(serverId, { + type: 'voice_status_update', + userId, + channelId, + isMuted: status.isMuted, + isDeafened: status.isDeafened, + }); + } + return; + } + // Leave any current voice channel const previousChannel = connectionManager.leaveAllVoice(userId); if (previousChannel) { diff --git a/packages/web/src/components/voice/SoundController.tsx b/packages/web/src/components/voice/SoundController.tsx index f3515a46..e9e31268 100644 --- a/packages/web/src/components/voice/SoundController.tsx +++ b/packages/web/src/components/voice/SoundController.tsx @@ -24,11 +24,14 @@ export function SoundController() { const incomingCallLoop = useRef(null); const outgoingCallLoop = useRef(null); - // WebSocket Reconnect Sound + // WebSocket Reconnect Sound — suppress during active voice (LiveKit handles its own reconnection) useEffect(() => { if (isInitialMount.current) return; if (isWsConnected && !prevIsWsConnected.current) { - audioManager.playSound('reconnect'); + const isInActiveVoice = useVoiceStore.getState().isLiveKitConnected; + if (!isInActiveVoice) { + audioManager.playSound('reconnect'); + } } prevIsWsConnected.current = isWsConnected; }, [isWsConnected, audioManager]);