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:
2026-08-11 21:51:49 +05:30
parent 0cee72e73c
commit 100a092ff9
2 changed files with 14 additions and 5 deletions
+9 -3
View File
@@ -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
let MEET_ICE={ iceServers:[{ urls:'stun:stun.l.google.com:19302' }] };
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 meetScreen=false, meetScreenStream=null; // am I sharing my screen + the display stream
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.
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
// #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();
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-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-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'){
const from=m.from, d=m.data||{};
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 };
_guestRoom=code; stopPrev(); ov.remove();
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);
// 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;
inp.addEventListener('keydown',e=>{ if(e.key==='Enter'){ e.preventDefault(); join(); } });