Calls over WebRTC
Carry the audio of WhatsApp-style calls — the ones the user places and the ones Graine rings — over WebRTC: add the module or switch it on in the build, choose in code, read which one carried a call, and what the dashboard's In-app call audio setting does.
The SDK's calls carry their audio over a WebSocket, which works on every network but sounds lossy on a lossy one. Add the SDK's WebRTC module and the same calls — the ones the user places and the ones Graine rings — carry it over WebRTC: a jitter buffer and loss concealment keep the agent's voice steady through a moment's loss, and the platform's echo canceller runs on the call. New in 0.9.0.
Nothing else changes. The ring, the call screen, Connecting…, mute, the loudspeaker, the outcomes, who the user is, memory, variables, screen context, actions and Call History are the same. A call that WebRTC cannot set up continues on the WebSocket inside the same call.
What it needs in the dashboard
| Where | Setting | Value |
|---|---|---|
| Agent → Calls & scheduling | In-app call audio | WebRTC (recommended), the default. Standard keeps every install's calls on the WebSocket, whatever the app chose, from each app's next start |
| Agent → Embed & Widgets | Allowed domains | app://, as for every in-app conversation |
Nothing else: the same agent, key and push credentials as your calls today.
What to do, in order
- Add WebRTC to the build: the iOS package
GraineInAppRTC, the Android moduleai.graine:inapp-android-rtc— each at the same version as its SDK — or, in React Native and Flutter, the build switch. It is native code: a new store build, not an over-the-air update. - Choose, if you want to —
callTransportin the calls configuration:auto(the default — WebRTC when the module is in the app),websocketorwebrtc. Most apps leave it. - Place a call, or answer one Graine rang. → The call is prepared at the tap, or while the ring shows, so it connects as fast as before; the agent greets the user with nothing clipped.
- Read which one carried it once the call is active:
transport(webrtcorwebsocket) and, for a call that tried WebRTC and runs on the WebSocket,transportFallbackReason— log it, or send it with your analytics. - Try the switch: set the agent's In-app call audio to Standard, restart
the app, place a call →
transportiswebsocket. Set it back.
What the user notices: on a weak or changing network — a lift, a train, a crowded café — the agent's voice holds up instead of stalling and catching up. Over WebRTC it arrives at telephone bandwidth today: steadier through a weak network, narrower than the WebSocket's on a perfect one.
When WebRTC cannot carry a call — a network that blocks it, a refusal, no
answer within 12 seconds in all — the call continues on the WebSocket inside the
same call: no second ring, no new call screen, no error; Connecting… lasts a
moment longer. A call the agent cannot take at all (no credit left, the agent
switched off) ends with that reason, as it does without the module. Once the
audio flows a call never changes road: a dropped connection gets one chance to
recover, then the call ends failed.
transportFallbackReason | Means |
|---|---|
mint_failed | The call's WebRTC session could not be set up: refused, rate-limited or unreachable |
register_failed | Graine's media service did not accept the connection, or never answered it |
no_response | The call got no reply at all |
no_answer | Replies came; the answer did not |
ice_timeout | Answered, but the audio path never connected — usually a network that blocks WebRTC |
error | Anything else before the audio flowed |
dashboard, unavailable | Android: the agent's In-app call audio is Standard; the app asked for webrtc and this build cannot run it |
What it adds to your app: a prebuilt WebRTC library (Google's WebRTC, BSD
3-Clause), its symbols renamed so it never clashes with another copy in your app —
about 12 MB installed on an iPhone (about 6 MB to download) and about 7.4 MB on a
64-bit Android phone. List it in your app's open-source licences. On Android the
module adds ACCESS_NETWORK_STATE, a normal permission: nothing is asked.
The code, platform by platform
1. Add the package — Xcode → File → Add Package Dependencies →
https://github.com/GraineAI/inapp-ios-rtc, the same version as GraineInApp,
and its GraineInAppRTC library in your app's target. In a Package.swift:
With CocoaPods, pod 'GraineInAppRTC', :git => 'https://github.com/GraineAI/inapp-ios-rtc.git', :tag => '0.10.3' beside pod 'GraineInApp', at the same
version. No code, no capability: the calls find the module
by themselves. One Info.plist entry: NSCameraUsageDescription — WebRTC contains camera code, and App Store processing refuses a build that references the camera without it (ITMS-90683: the build never reaches TestFlight). Calls are audio only; say so, for example Calls are audio only. The camera is never turned on. A build that strips code nothing references calls
GraineRTC.install() at launch:
2. Choose (optional — .auto is the default):
4. Read which one carried the call:
Objective-C: callTransport, transport and transportFallbackReason on
GraineObjCCalls, as strings.
Troubleshooting
| What you see | Why | Fix |
|---|---|---|
transport is websocket, with no reason | The module is not in this build (React Native and Flutter: the switch is off), or callTransport is websocket | Add the module or switch, rebuild; leave callTransport at auto |
Every install's calls are on the WebSocket (dashboard on Android) | The agent's In-app call audio is Standard | Choose WebRTC (recommended); apps pick it up at their next start |
ice_timeout on some networks | Those networks block WebRTC | Nothing to do: those calls connect on the WebSocket. If most of your users' networks block it, choose Standard |
Some test calls fall back with mint_failed | A burst of calls from one network — more than about 10 a minute — is limited | Space the test calls; the calls still connect, on the WebSocket |
unavailable (Android) | The app asked for webrtc and the build has no module | Add ai.graine:inapp-android-rtc at the SDK's version |
| iOS: the packages no longer resolve | Another package pins the same WebRTC build at a different version | Align the versions, or leave the module out: calls stay on the WebSocket |
| CocoaPods (React Native, Flutter) cannot install beside another library | That library pins the same WebRTC build at another version | The same: align them, or build without WebRTC |
In depth: WebRTC or the WebSocket · SDK reference → Calls over WebRTC.

