Native calls: answer MUTED by default (house rule)

Plugin connects the LiveKit room without enabling the mic (nothing captured/
published until the user taps Mic, which is also when iOS asks permission).
Web: meetMic starts false so the mic button shows muted; on callConnected the
web pushes the muted state to the plugin so builds whose plugin still connects
the mic live are muted too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 10:46:13 +05:30
parent c2b339e284
commit 199c32a319
2 changed files with 8 additions and 3 deletions
@@ -66,7 +66,10 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega
await old?.disconnect() // drop any previous/stale connection so we never leave DUPLICATE participants
do {
try await r.connect(url: url, token: token)
try await r.localParticipant.setMicrophone(enabled: true)
// House rule: answer MUTED. Not enabling the mic here means nothing is captured/published
// until the user taps Mic in the meeting UI ( setMuted(false) setMicrophone(true)), which
// is also when iOS asks for mic permission. Playback (hearing others) is unaffected.
try await r.localParticipant.setMicrophone(enabled: false)
self?.notifyListeners("callConnected", data: ["ok": true])
} catch {
self?.notifyListeners("callError", data: ["error": String(describing: error)])
+4 -2
View File
@@ -3810,7 +3810,9 @@ async function setupNativeCall(){
meetReturn = isGroup ? { kind:'group', id:d.groupId } : { kind:'dm', id:d.callerId };
switchTab('meeting'); enterMeeting(d.room, false, { native:true, uuid:d.callUUID });
}catch(e){ pdbg('nc-answer-err', { err:String((e&&e.message)||e) }); } });
NC.addListener('callConnected', ()=>{ pdbg('nc-connected'); });
// Enforce the muted-by-default rule against the plugin's actual mic once the room connects (belt-and-braces
// for builds whose plugin still connects the mic live). meetMic is the source of truth for the mic button.
NC.addListener('callConnected', ()=>{ pdbg('nc-connected'); if(meetNative){ try{ NC.setMuted({ muted:!meetMic }); }catch(_){} } });
NC.addListener('callError', (e)=>{ pdbg('nc-error', { err:(e&&e.error)||'' }); });
// Muted from the CallKit system UI → reflect it in the meeting window's mic button.
NC.addListener('setMuted', (e)=>{ if(!e||meetState!=='call'||!meetNative) return; meetMic=!e.muted; try{ updateMicBtn(); setTileMute('__local', !meetMic); meetSend({type:'meeting-state', muted:!meetMic, camOff:!meetCam}); }catch(_){} });
@@ -5307,7 +5309,7 @@ async function enterMeeting(code, audioOnly, opts){
// Start with NO media — mic & cam OFF by default (no permission prompt until the user
// turns one on). Tracks are acquired on demand by toggleMic / toggleCam.
meetLocalStream=new MediaStream();
meetMic=meetNative; meetCam=false; meetIsHost=false; meetHostId=null; // native: the plugin connects with the mic LIVE → show unmuted
meetMic=false; meetCam=false; meetIsHost=false; meetHostId=null; // answer MUTED by default (house rule) — tap Mic to speak
meetScreen=false; meetScreenStream=null; meetSharers.clear(); meetMultiShare=false;
meetRec=null; meetTranscribe=false; meetRoomTx=false; meetSR=null; _addPool=null; meetStageId=null;
try{ const c=await fetch('/api/ice').then(r=>r.json()); if(c&&c.iceServers) MEET_ICE=c; }catch(_){}