Fix call-cluster bugs #11, #4a, #4b
#11: adding a 3rd person to a 1:1 call then having one participant close the app disconnected the call for everyone. leaveMeeting() ended a DM room for ALL peers on any leave; now it only tears down when <2 people remain, otherwise it falls through to the normal peer-left path (call continues). #4b: after someone left a call they never reappeared under "Add people". meeting-peer-left cleaned meetPeers/tiles but not meetPeerUids/meetNames, so the departed uid stayed in hereUids and was filtered out. Now deleted. #4a: a guest who enabled mic/cam on the pre-join screen had to re-tap after being admitted — the choices were applied on a blind 900ms timer that fired while still in the lobby. Now applied in the meeting-joined handler, after admission + media connect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4469,6 +4469,7 @@ let meetReturn=null; // {kind:'dm'|'group', id} — chat to land on when
|
|||||||
const meetVU=new Map(); // peerId|'__local' -> {ctx,analyser,data,raf} active-speaker meters
|
const meetVU=new Map(); // peerId|'__local' -> {ctx,analyser,data,raf} active-speaker meters
|
||||||
let MEET_ICE={ iceServers:[{ urls:'stun:stun.l.google.com:19302' }] };
|
let MEET_ICE={ iceServers:[{ urls:'stun:stun.l.google.com:19302' }] };
|
||||||
let meetMic=true, meetCam=true;
|
let meetMic=true, meetCam=true;
|
||||||
|
let _pendingJoinMic=false, _pendingJoinCam=false; // #4a: pre-join (lobby) mic/cam choices, applied once actually admitted
|
||||||
let meetIsHost=false, meetHostId=null; // host = the meeting creator (transferable by the host only)
|
let meetIsHost=false, meetHostId=null; // host = the meeting creator (transferable by the host only)
|
||||||
let meetScreen=false, meetScreenStream=null; // am I sharing my screen + the display stream
|
let meetScreen=false, meetScreenStream=null; // am I sharing my screen + the display stream
|
||||||
const meetSharers=new Set(); // peerIds of OTHERS currently sharing their screen
|
const meetSharers=new Set(); // peerIds of OTHERS currently sharing their screen
|
||||||
@@ -5541,6 +5542,10 @@ async function onMeetMsg(e){
|
|||||||
// for the UI only. Media is native; tell peers our mic is live.
|
// for the UI only. Media is native; tell peers our mic is live.
|
||||||
meetSend({type:'meeting-state', muted:!meetMic, camOff:!meetCam}); // tell existing peers my state
|
meetSend({type:'meeting-state', muted:!meetMic, camOff:!meetCam}); // tell existing peers my state
|
||||||
if(meetIsHost) meetSend({type:'meeting-host', to:meetMyId}); // announce host so others know
|
if(meetIsHost) meetSend({type:'meeting-host', to:meetMyId}); // announce host so others know
|
||||||
|
// #4a: now that we're actually in the call (post-admission, media connected), honor the mic/cam the
|
||||||
|
// user turned on at the pre-join/lobby screen — so they don't have to re-tap after being admitted.
|
||||||
|
if(_pendingJoinMic){ _pendingJoinMic=false; if(!meetMic){ try{ await toggleMic(); }catch(_){} } }
|
||||||
|
if(_pendingJoinCam){ _pendingJoinCam=false; if(!meetCam){ try{ await toggleCam(); }catch(_){} } }
|
||||||
refreshMeetPanel(); updateHostControls();
|
refreshMeetPanel(); updateHostControls();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -5570,7 +5575,7 @@ async function onMeetMsg(e){
|
|||||||
if(m.type==='meeting-peer-screen'){ if(m.on) meetSharers.add(m.from); else meetSharers.delete(m.from); setTileScreen(m.from, !!m.on); refreshMeetPanel(); return; }
|
if(m.type==='meeting-peer-screen'){ if(m.on) meetSharers.add(m.from); else meetSharers.delete(m.from); setTileScreen(m.from, !!m.on); refreshMeetPanel(); return; }
|
||||||
if(m.type==='meeting-sharemode'){ meetMultiShare=!!m.multi; refreshMeetPanel(); return; }
|
if(m.type==='meeting-sharemode'){ meetMultiShare=!!m.multi; refreshMeetPanel(); return; }
|
||||||
if(m.type==='meeting-muteall'){ if(meetMic && meetLocalStream){ meetMic=false; meetLocalStream.getAudioTracks().forEach(t=>t.enabled=false); updateMicBtn(); setTileMute('__local', true); meetSend({type:'meeting-state', muted:true, camOff:!meetCam}); } toast('You were muted by the host'); return; }
|
if(m.type==='meeting-muteall'){ if(meetMic && meetLocalStream){ meetMic=false; meetLocalStream.getAudioTracks().forEach(t=>t.enabled=false); updateMicBtn(); setTileMute('__local', true); meetSend({type:'meeting-state', muted:true, camOff:!meetCam}); } toast('You were muted by the host'); return; }
|
||||||
if(m.type==='meeting-peer-left'){ const p=meetPeers.get(m.peerId); if(p){ try{p.pc.close();}catch(_){} meetPeers.delete(m.peerId);} if(SFU.on) sfuDropPeer(m.peerId); meetSharers.delete(m.peerId); removeTile(m.peerId); refreshMeetPanel(); return; }
|
if(m.type==='meeting-peer-left'){ const p=meetPeers.get(m.peerId); if(p){ try{p.pc.close();}catch(_){} meetPeers.delete(m.peerId);} if(SFU.on) sfuDropPeer(m.peerId); meetSharers.delete(m.peerId); meetPeerUids.delete(m.peerId); meetNames.delete(m.peerId); meetAvatars.delete(m.peerId); removeTile(m.peerId); refreshMeetPanel(); return; } // #4b: drop uid/name too, else they stay in hereUids and never reappear under "Add people"
|
||||||
if(m.type==='meeting-signal'){
|
if(m.type==='meeting-signal'){
|
||||||
const from=m.from, d=m.data||{};
|
const from=m.from, d=m.data||{};
|
||||||
if(d.sdp){
|
if(d.sdp){
|
||||||
@@ -6071,9 +6076,10 @@ async function startGuestMeeting(code){
|
|||||||
ME={ id:'guest-'+Math.random().toString(36).slice(2,10), name:nm, email:'', guest:true, avatarUrl:null };
|
ME={ id:'guest-'+Math.random().toString(36).slice(2,10), name:nm, email:'', guest:true, avatarUrl:null };
|
||||||
_guestRoom=code; stopPrev(); ov.remove();
|
_guestRoom=code; stopPrev(); ov.remove();
|
||||||
try{ await sfuInit(); }catch(_){}
|
try{ await sfuInit(); }catch(_){}
|
||||||
|
// #4a: remember the pre-join choices; they're applied in the meeting-joined handler — i.e. AFTER the
|
||||||
|
// host admits the guest and media is connected — not on a blind timer that fires while still in the lobby.
|
||||||
|
_pendingJoinMic=joinMic; _pendingJoinCam=joinCam;
|
||||||
switchTab('meeting'); await enterMeeting(code);
|
switchTab('meeting'); await enterMeeting(code);
|
||||||
// Apply their pre-join choices once the call is up.
|
|
||||||
setTimeout(()=>{ try{ if(joinMic && !meetMic) toggleMic(); if(joinCam && !meetCam) toggleCam(); }catch(_){} }, 900);
|
|
||||||
};
|
};
|
||||||
go.onclick=join;
|
go.onclick=join;
|
||||||
inp.addEventListener('keydown',e=>{ if(e.key==='Enter'){ e.preventDefault(); join(); } });
|
inp.addEventListener('keydown',e=>{ if(e.key==='Enter'){ e.preventDefault(); join(); } });
|
||||||
|
|||||||
+5
-2
@@ -406,8 +406,11 @@ async function leaveMeeting(ws) {
|
|||||||
if (!peers) { if (leaverId) CHAT.broadcastPresence(leaverId); return; }
|
if (!peers) { if (leaverId) CHAT.broadcastPresence(leaverId); return; }
|
||||||
try { await require('./calls').finalizeTranscript(room, ws._meetingUserId); } catch (_) {} // save THIS user's transcript
|
try { await require('./calls').finalizeTranscript(room, ws._meetingUserId); } catch (_) {} // save THIS user's transcript
|
||||||
peers.delete(pid);
|
peers.delete(pid);
|
||||||
// 1:1 call: when either party leaves, end it for everyone (a DM call has no "remaining" call).
|
// 1:1 call: end it for everyone ONLY when fewer than two people would remain. A DM call that had
|
||||||
if (roomToDmCall.has(room)) {
|
// extra people ADDED (via "Add people") is effectively a group now — one participant closing their
|
||||||
|
// app must NOT hang up the call for the rest (#11). We only tear the whole thing down when ≤1 person
|
||||||
|
// is left (nobody to talk to). With 2+ remaining we fall through to the normal peer-left path below.
|
||||||
|
if (roomToDmCall.has(room) && peers.size < 2) {
|
||||||
const others = [...peers.values()].map((p) => p.ws && p.ws._meetingUserId).filter(Boolean);
|
const others = [...peers.values()].map((p) => p.ws && p.ws._meetingUserId).filter(Boolean);
|
||||||
for (const [, p] of peers) { if (p.ws.readyState === 1) { try { p.ws.send(JSON.stringify({ type: 'meeting-ended' })); } catch (_) {} p.ws._meetingRoom = null; } }
|
for (const [, p] of peers) { if (p.ws.readyState === 1) { try { p.ws.send(JSON.stringify({ type: 'meeting-ended' })); } catch (_) {} p.ws._meetingRoom = null; } }
|
||||||
await persistCallHistory(room); // #7: log the call BEFORE endCallByRoom clears roomToDmCall/roomToGroupCall
|
await persistCallHistory(room); // #7: log the call BEFORE endCallByRoom clears roomToDmCall/roomToGroupCall
|
||||||
|
|||||||
Reference in New Issue
Block a user