Graine AI

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

WhereSettingValue
Agent → Calls & schedulingIn-app call audioWebRTC (recommended), the default. Standard keeps every install's calls on the WebSocket, whatever the app chose, from each app's next start
Agent → Embed & WidgetsAllowed domainsapp://, as for every in-app conversation

Nothing else: the same agent, key and push credentials as your calls today.

What to do, in order

  1. Add WebRTC to the build: the iOS package GraineInAppRTC, the Android module ai.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.
  2. Choose, if you want to — callTransport in the calls configuration: auto (the default — WebRTC when the module is in the app), websocket or webrtc. Most apps leave it.
  3. 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.
  4. Read which one carried it once the call is active: transport (webrtc or websocket) and, for a call that tried WebRTC and runs on the WebSocket, transportFallbackReason — log it, or send it with your analytics.
  5. Try the switch: set the agent's In-app call audio to Standard, restart the app, place a call → transport is websocket. 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.

transportFallbackReasonMeans
mint_failedThe call's WebRTC session could not be set up: refused, rate-limited or unreachable
register_failedGraine's media service did not accept the connection, or never answered it
no_responseThe call got no reply at all
no_answerReplies came; the answer did not
ice_timeoutAnswered, but the audio path never connected — usually a network that blocks WebRTC
errorAnything else before the audio flowed
dashboard, unavailableAndroid: 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:

Package.swift
dependencies: [
    .package(url: "https://github.com/GraineAI/inapp-ios", from: "0.10.3"),
    .package(url: "https://github.com/GraineAI/inapp-ios-rtc", from: "0.10.3"),
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "GraineInApp", package: "inapp-ios"),
        .product(name: "GraineInAppRTC", package: "inapp-ios-rtc"),
    ]),
]

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:

import GraineInAppRTC
 
GraineRTC.install()   // optional: makes WebRTC available now, rather than when the first call looks for it

2. Choose (optional — .auto is the default):

GraineInApp.shared.calls.configure(GraineCallsConfig(displayName: "Asha from Acme", callTransport: .auto))

4. Read which one carried the call:

final class TransportLog: GraineCallsListener {
    func onCallState(_ state: GraineCallState, callId: String?) {
        guard state == .active else { return }
        let calls = GraineInApp.shared.calls
        print("call \(callId ?? "?") on \(calls.transport?.rawValue ?? "unknown")", calls.transportFallbackReason ?? "")
    }
}
 
let transportLog = TransportLog()
GraineInApp.shared.calls.addListener(transportLog)   // held weakly

Objective-C: callTransport, transport and transportFallbackReason on GraineObjCCalls, as strings.

Troubleshooting

What you seeWhyFix
transport is websocket, with no reasonThe module is not in this build (React Native and Flutter: the switch is off), or callTransport is websocketAdd 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 StandardChoose WebRTC (recommended); apps pick it up at their next start
ice_timeout on some networksThose networks block WebRTCNothing 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_failedA burst of calls from one network — more than about 10 a minute — is limitedSpace the test calls; the calls still connect, on the WebSocket
unavailable (Android)The app asked for webrtc and the build has no moduleAdd ai.graine:inapp-android-rtc at the SDK's version
iOS: the packages no longer resolveAnother package pins the same WebRTC build at a different versionAlign the versions, or leave the module out: calls stay on the WebSocket
CocoaPods (React Native, Flutter) cannot install beside another libraryThat library pins the same WebRTC build at another versionThe same: align them, or build without WebRTC

In depth: WebRTC or the WebSocket · SDK reference → Calls over WebRTC.

On this page