Native calls: CallKit audio-session coordination, no re-ring blip, mute sync, WS ring path
Plugin (rides next build): - Audio: disable LiveKit auto audio-session config + keep the engine OFF, then configure the session and start the engine ONLY in CXProvider didActivate (stop in didDeactivate). Fixes intermittent dead mic / no audio and the "speaker turns on late" routing. Request mic permission on connect so enabling the engine in didActivate can't block on undetermined permission (SDK #815). - Re-ring blip: a cancel push for a call we already ended/known no longer reports a NEW incoming call (that was the phantom "rings back for a second"); it ends the known call cleanly, and only reports+ends for a truly unknown (cold) call. - Mute display: answer/outgoing reflect muted-by-default on the CallKit screen; setMuted now drives mute THROUGH CallKit so the system screen and the in-app meeting UI stay in sync. - reportIncomingCall: new method to ring CallKit from a WebSocket call event — a 2nd path alongside the VoIP push for when the app is open (push can be delayed); deduped by UUID. Web (deploys now; the WS ring path activates once the build has the new method): - onDmCall/onGroupCall call nativeReportIncoming for native incoming calls. - audioActivated telemetry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2556,6 +2556,7 @@ function onGroupCall(d){
|
||||
if(selected&&selected.kind==='group'&&selected.id===d.group) updateCallBtn(!!d.active);
|
||||
// Ring members in. On a CallKit device the system rings it (VoIP push) — skip the in-app popup.
|
||||
if(d.active && d.by && d.by!==ME.id && meetRoom!==d.room && !nativeCallOn()) showCallInvite(d.room, d.startedByName, {kind:'group',id:d.group}, d.groupName||(it&&it.name)||'Group');
|
||||
if(d.active && d.by && d.by!==ME.id && meetRoom!==d.room && nativeCallOn() && d.uuid) nativeReportIncoming({ callUUID:d.uuid, room:d.room, kind:'group', groupId:d.group, callerName:(d.groupName||(it&&it.name)||'Group call') }); // WS path → CallKit
|
||||
if(!d.active){ dismissCallInvite(d.room); if(nativeCallOn() && d.uuid) callkitEnd(d.uuid); } // ended → stop ringing + clear CallKit
|
||||
renderChats(searchVal());
|
||||
}
|
||||
@@ -2583,6 +2584,7 @@ function onDmCall(d){
|
||||
// Incoming 1:1 call. On a CallKit device the SYSTEM rings it (via the VoIP push) — don't also show the
|
||||
// in-app popup, and let CallKit answer drive the join. Off CallKit, show the in-app invite as before.
|
||||
if(d.active && d.by && d.by!==ME.id && !nativeCallOn()) showCallInvite(d.room, d.byName, {kind:'dm',id:d.with});
|
||||
if(d.active && d.by && d.by!==ME.id && nativeCallOn() && d.uuid) nativeReportIncoming({ callUUID:d.uuid, room:d.room, kind:'dm', callerId:d.with, callerName:d.byName }); // WS path → CallKit (2nd path alongside the VoIP push)
|
||||
if(!d.active){ dismissCallInvite(d.room); if(nativeCallOn() && d.uuid) callkitEnd(d.uuid); } // ended/declined → stop ringing + clear CallKit
|
||||
renderChats(searchVal());
|
||||
}
|
||||
@@ -3813,6 +3815,7 @@ async function setupNativeCall(){
|
||||
// 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('audioActivated', (e)=>{ pdbg('nc-audio', { ok:!!(e&&e.ok), err:(e&&e.error)||'' }); }); // CallKit audio-session activation (debug intermittent audio)
|
||||
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(_){} });
|
||||
@@ -3839,6 +3842,14 @@ async function callkitReportOutgoing(uuid, room, kind, peerName, hasVideo){
|
||||
}
|
||||
// End the CallKit call (remote hung up / we left / call ended). Safe no-op off-CallKit.
|
||||
function callkitEnd(uuid){ const NC=nativeCallPlugin(); if(!NC||!uuid) return; try{ NC.endCall({ callUUID:uuid }); }catch(_){} }
|
||||
// Ring CallKit from a WebSocket call event — a 2nd, reliable path alongside the VoIP push for when the app is
|
||||
// OPEN (the push can be delayed/dropped). Deduped by UUID in the plugin, so double-firing is harmless.
|
||||
async function nativeReportIncoming(o){
|
||||
const NC=nativeCallPlugin(); if(!NC||!o||!o.callUUID||!o.room) return;
|
||||
let tk={}; try{ tk=await postJSON('/api/meetings/token',{ room:o.room }); }catch(_){}
|
||||
// .catch: on older builds without this plugin method, Capacitor REJECTS the promise (not a sync throw) — swallow it.
|
||||
try{ const p=NC.reportIncomingCall({ callUUID:o.callUUID, room:o.room, kind:o.kind||'dm', callerId:o.callerId||'', callerName:o.callerName||'Incoming call', groupId:o.groupId||'', groupName:o.groupName||'', hasVideo:false, url:(tk&&tk.url)||'', token:(tk&&tk.token)||'' }); if(p&&p.catch) p.catch(()=>{}); }catch(_){}
|
||||
}
|
||||
|
||||
// NATIVE calls use your REAL meeting window: the plugin owns this user's ONE LiveKit connection (media +
|
||||
// CallKit ring/background/lock-screen), and the WebView joins the same mesh room for the UI — so the caller/
|
||||
|
||||
Reference in New Issue
Block a user