From 62aa87c737068e879e6fcc01dff74aa7429155fc Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:35:14 +0200 Subject: [PATCH] feat(sounds): receive stream_watch pings + evict watchers on participant disconnect --- packages/web/src/hooks/useLiveKit.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/web/src/hooks/useLiveKit.ts b/packages/web/src/hooks/useLiveKit.ts index 3e83d30f..d18f6734 100644 --- a/packages/web/src/hooks/useLiveKit.ts +++ b/packages/web/src/hooks/useLiveKit.ts @@ -32,6 +32,7 @@ import { handleScreenShareUnpublished, resolveNativeOverdrive, } from '../utils/screenShare'; +import { parseStreamWatch } from '../utils/streamWatchProtocol'; import { getMediaStreamTrack } from '../utils/livekitInternals'; import { deactivate as deactivateHwOverdrive } from '../utils/hwOverdrive'; @@ -293,6 +294,17 @@ export function useLiveKit() { }, []); const handleDataReceived = useCallback((payload: Uint8Array, participant?: RemoteParticipant) => { + // Try the stream_watch protocol first (typed parser; returns null on non-matches). + if (participant) { + const sw = parseStreamWatch(payload); + if (sw) { + // sw.target is the streamer's bare userId. participant.identity is the + // viewer's full LiveKit identity ("userId:username"); we key by identity + // so ParticipantDisconnected can evict cleanly. + useVoiceStore.getState().recordStreamWatch(sw.target, participant.identity, sw.watching); + return; + } + } try { const text = new TextDecoder().decode(payload); const msg = JSON.parse(text); @@ -532,6 +544,7 @@ export function useLiveKit() { } }); newRoom.on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => { + useVoiceStore.getState().evictWatcher(participant.identity); guardedUpdate(); // Clean up stale WS-based voice status for the departed participant const { userId } = parseIdentity(participant.identity);