delay sendCall/answerCall/unhold until after WebRTC ADM init, allow manual setupAudioSession#120
Conversation
159a8de to
380dfdf
Compare
There was a problem hiding this comment.
Pull request overview
This PR adjusts the iOS CallKit/WebRTC integration in cordova-plugin-callkit to (1) prevent duplicate CallKit reconciliation callbacks from being misclassified as UI-driven events, and (2) delay notifying JS that audio is ready until WebRTC’s audio engine has actually started.
Changes:
- Defers
pendingActivateAudioSessionEmitsprocessing fromdidActivateAudioSessionto WebRTC’saudioSessionDidStartPlayOrRecordcallback. - Introduces delayed cleanup of
callbackMapentries to avoid duplicate CallKit callbacks being treated as UI-initiated. - Updates the plugin header to conform to
RTCAudioSessionDelegate.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/ios/CordovaCall.m | Adds WebRTC audio-start gating for deferred JS emits and delays callbackMap cleanup to handle duplicate CallKit callbacks. |
| src/ios/CordovaCall.h | Adds WebRTC audio session delegate conformance/imports for the new callback path. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
aa1ffee to
6060f48
Compare
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@copilot explain what this PR does in the context of video meeting and phone call, listing the call flows and what is blocking what for js side usage. |
This PR fixes two race conditions in the iOS CallKit ↔ WebRTC integration. Here's what it does in the context of video/voice meetings: BackgroundWhen a call is made or answered via CallKit, iOS fires Call flowsOutgoing call (JS calls
|
| Step | What happens |
|---|---|
| 1 | JS calls sendCall → plugin asks CallKit to start the call |
| 2 | CallKit fires performStartCallAction → plugin calls setupAudioSession, stores the call data in pendingActivateAudioSessionEmits |
| 3 | CallKit fires didActivateAudioSession → plugin enables WebRTC audio (isAudioEnabled = YES) and waits |
| 4 | WebRTC fires audioSessionDidStartPlayOrRecord (ADM is now truly running) → only here does the plugin resolve the JS sendCall promise |
| ✅ | JS is now safe to call getUserMedia, mute, unmute, hold, etc. |
Incoming call (user taps Answer in CallKit UI)
| Step | What happens |
|---|---|
| 1 | User taps Answer → CallKit fires performAnswerCallAction → plugin calls setupAudioSession, queues an answer deferred emit |
| 2 | didActivateAudioSession → WebRTC ADM starts, JS not notified yet |
| 3 | audioSessionDidStartPlayOrRecord → plugin emits the answer event to JS listeners |
| ✅ | JS can now safely start WebRTC media |
Outgoing call from iOS Recents (UI-initiated, no prior JS call)
Same as outgoing above, except at step 4 the plugin emits a sendCall event to registered JS listeners instead of resolving a promise.
Unhold
Same deferred pattern — the unhold confirmation reaches JS only after audioSessionDidStartPlayOrRecord, ensuring audio is truly active before the app tries to resume media.
What was blocking what (the bugs)
Before this PR:
didActivateAudioSessionimmediately notified JS → JS called gUM / mute / unmute on a WebRTC ADM that hadn't finished initialising → crashes and race conditions.
After this PR:
- JS is gated on
audioSessionDidStartPlayOrRecord, so the ADM is guaranteed ready before JS acts on audio.
The setupAudioSession command
The second change exposes setupAudioSession as an explicit JS-callable command. When the app navigates to the in-call page, JS can call plugin.setupAudioSession() to pre-configure the AVAudioSession (voice-processing mode, etc.) before WebRTC's getUserMedia runs. Without this, gUM could race with audio session setup and configure the session in the wrong mode.
Yes — and that's precisely the contract this PR establishes. Why awaiting matters
If JS fires subsequent actions without awaiting:
The expected JS pattern await plugin.sendCall(name, id, sessionId); // ← audio is now ready
await plugin.mute(sessionId); // safeCalling |
What actually happens with two concurrent/un-awaited calls
Net result: not awaiting The await discipline matters more for |
Delay didActivateAudioSession until WebRTC init:
Bug:
didActivateAudioSessionwas emitting to JS that audio is ready when all it has done was tell WebRTC to spin up via[RTCAudioSession sharedInstance].isAudioEnabled = YES;Fix:
audioSessionDidStartPlayOrRecordso that we know the WebRTC Audio Engine is actually startingAllow manual setupAudioSession
Bug:
Fix:
setupAudioSessionas part of entering the InCallPage