From 5ab1f7b89d65282e051884b0d59d86c71a4aa457 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:25:15 +0200 Subject: [PATCH] fix: SoundController identity mismatch for federated users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: SoundController compared LiveKit participant p.userId (which is homeUserId from the home instance) against currentUser.id (local snowflake on the current instance). For federated users these are different IDs, so the controller thought the user's own presence was a stranger — playing user_join/user_leave for self. Fix: use homeUserId || id for the self-check. This matches the LiveKit identity format used in federated calls. --- .../web/src/components/voice/SoundController.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/web/src/components/voice/SoundController.tsx b/packages/web/src/components/voice/SoundController.tsx index 5e0f8689..5514db5b 100644 --- a/packages/web/src/components/voice/SoundController.tsx +++ b/packages/web/src/components/voice/SoundController.tsx @@ -12,6 +12,10 @@ function getSfxVolume(): number { export function SoundController() { const audioManager = AudioManager.getInstance(); const currentUser = useAuthStore((s) => s.user); + // Federation-aware self-ID: in federated calls, LiveKit participant identity + // uses homeUserId (from the home instance), not the local snowflake ID. + // Without this, the SoundController thinks our own presence is a stranger. + const myId = currentUser?.homeUserId || currentUser?.id; // Refs to track previous states const isInitialMount = useRef(true); @@ -86,28 +90,28 @@ export function SoundController() { if (state.isLiveKitConnected && !justDisconnected) { // Someone joined voice (Others only) state.participants.forEach(p => { - if (!prevParticipantIds.current.has(p.userId) && p.userId !== currentUser?.id) { + if (!prevParticipantIds.current.has(p.userId) && p.userId !== myId) { audioManager.playSound('user_join', sfxOpts); } }); // Someone left voice (Others only) prevParticipantIds.current.forEach(userId => { - if (!currentParticipantIds.has(userId) && userId !== currentUser?.id) { + if (!currentParticipantIds.has(userId) && userId !== myId) { audioManager.playSound('user_leave', sfxOpts); } }); // Someone started screen sharing (Others only) state.participants.forEach(p => { - if (p.isScreenSharing && !prevScreenShareUserIds.current.has(p.userId) && p.userId !== currentUser?.id) { + if (p.isScreenSharing && !prevScreenShareUserIds.current.has(p.userId) && p.userId !== myId) { audioManager.playSound('stream_user_joined', sfxOpts); } }); // Someone stopped screen sharing (Others only) prevScreenShareUserIds.current.forEach(userId => { - if (!currentScreenShareUserIds.has(userId) && userId !== currentUser?.id) { + if (!currentScreenShareUserIds.has(userId) && userId !== myId) { audioManager.playSound('stream_user_left', sfxOpts); } }); @@ -179,7 +183,7 @@ export function SoundController() { if (incomingCallLoop.current) incomingCallLoop.current.stop(); if (outgoingCallLoop.current) outgoingCallLoop.current.stop(); }; - }, [audioManager, currentUser?.id]); + }, [audioManager, myId]); return null; }