fix: comprehensive client-side session management for federated DM calls
Four fixes addressing the full state management problem: 1. Passive ready handler: no longer auto-connects to LiveKit on page refresh. Prevents identity conflicts when the same user has multiple sessions fighting for one LiveKit identity slot. The user must re-accept to join; state is shown but not acted on. 2. SoundController sync guard: incomingCallLoading/outgoingCallLoading refs prevent multiple playSound calls during async audio load. If call is cancelled while sound loads, stops it immediately on completion. Eliminates the "5 ringtones at once" bug. 3. Host dm_call_accepted broadcasts now include federatedCallId so all clients (including remote instances) can match the event. 4. Removed all diagnostic console.log statements.
This commit is contained in:
@@ -24,7 +24,9 @@ export function SoundController() {
|
||||
const prevScreenShareUserIds = useRef<Set<string>>(new Set(useVoiceStore.getState().participants.filter(p => p.isScreenSharing).map(p => p.userId)));
|
||||
|
||||
const incomingCallLoop = useRef<AudioBufferSourceNode | null>(null);
|
||||
const incomingCallLoading = useRef(false); // sync guard for async playSound
|
||||
const outgoingCallLoop = useRef<AudioBufferSourceNode | null>(null);
|
||||
const outgoingCallLoading = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Set initial mount flag to false after first run
|
||||
@@ -109,24 +111,43 @@ export function SoundController() {
|
||||
prevParticipantIds.current = currentParticipantIds;
|
||||
prevScreenShareUserIds.current = currentScreenShareUserIds;
|
||||
|
||||
// Incoming Call (Ringing)
|
||||
if (state.incomingCall && !incomingCallLoop.current) {
|
||||
// Incoming Call (Ringing) — sync guard prevents multiple playSound during async load
|
||||
if (state.incomingCall && !incomingCallLoop.current && !incomingCallLoading.current) {
|
||||
incomingCallLoading.current = true;
|
||||
audioManager.playSound('call_ringing', { loop: true, volume: getSfxVolume() }).then(source => {
|
||||
incomingCallLoop.current = source;
|
||||
// If call was cancelled while sound was loading, stop immediately
|
||||
if (!useVoiceStore.getState().incomingCall) {
|
||||
source?.stop();
|
||||
} else {
|
||||
incomingCallLoop.current = source;
|
||||
}
|
||||
incomingCallLoading.current = false;
|
||||
});
|
||||
} else if (!state.incomingCall && incomingCallLoop.current) {
|
||||
incomingCallLoop.current.stop();
|
||||
incomingCallLoop.current = null;
|
||||
} else if (!state.incomingCall) {
|
||||
if (incomingCallLoop.current) {
|
||||
incomingCallLoop.current.stop();
|
||||
incomingCallLoop.current = null;
|
||||
}
|
||||
incomingCallLoading.current = false;
|
||||
}
|
||||
|
||||
// Outgoing Call (Calling)
|
||||
if (state.outgoingCall && !outgoingCallLoop.current) {
|
||||
// Outgoing Call (Calling) — same sync guard pattern
|
||||
if (state.outgoingCall && !outgoingCallLoop.current && !outgoingCallLoading.current) {
|
||||
outgoingCallLoading.current = true;
|
||||
audioManager.playSound('call_calling', { loop: true, volume: getSfxVolume() }).then(source => {
|
||||
outgoingCallLoop.current = source;
|
||||
if (!useVoiceStore.getState().outgoingCall) {
|
||||
source?.stop();
|
||||
} else {
|
||||
outgoingCallLoop.current = source;
|
||||
}
|
||||
outgoingCallLoading.current = false;
|
||||
});
|
||||
} else if (!state.outgoingCall && outgoingCallLoop.current) {
|
||||
outgoingCallLoop.current.stop();
|
||||
outgoingCallLoop.current = null;
|
||||
} else if (!state.outgoingCall) {
|
||||
if (outgoingCallLoop.current) {
|
||||
outgoingCallLoop.current.stop();
|
||||
outgoingCallLoop.current = null;
|
||||
}
|
||||
outgoingCallLoading.current = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -332,11 +332,10 @@ function handleEvent(origin: string, event: ServerEvent): void {
|
||||
if (call.federatedCallId) {
|
||||
setFederatedCallId(call.federatedCallId);
|
||||
}
|
||||
if (connectFn && callDmId) {
|
||||
connectFn(callDmId, true).catch((err) => {
|
||||
console.error('[WS] DM call reconnect failed:', err);
|
||||
});
|
||||
}
|
||||
// PASSIVE: do NOT auto-connect to LiveKit on ready.
|
||||
// The user must click "Join" or re-accept. Auto-connecting causes
|
||||
// identity conflicts when the same user has multiple sessions —
|
||||
// both sessions fight for the same LiveKit identity slot.
|
||||
break;
|
||||
} else if (call.state === 'ringing' && call.callerId !== myId) {
|
||||
const dmCh = event.dmChannels?.find((d: any) => d.id === call.dmChannelId);
|
||||
|
||||
Reference in New Issue
Block a user