# Mobile App Transport

> Carry voice from iOS and Android apps over QUIC. This page covers the planned native path and what works today.

Carry voice from an iOS or Android app into FirstByte Voice over the same
QUIC / WebTransport plane the browser uses. This page describes the **intended
native mobile path** and the **workarounds you can ship today**. The native
mobile SDKs are on the roadmap; they are not available yet.

> **WARNING:**
> **Native iOS (Swift) and Android (Kotlin) SDKs are plan-only.** There is no
>   Swift or Kotlin client source today. If you need voice in a mobile app right
>   now, use one of the [two workarounds](#what-works-today) below. Everything in
>   the "Planned native path" section is roadmap and may change.

## Why mobile is different

The browser path depends on the browser: a loopback capture pipeline runs Opus
encode, echo cancellation, and jitter buffering, and a
[WebRTC diversion](/modalities/voice/transport-web/webrtc-diversion) taps the
encoded frames onto QUIC. A native app has no browser to borrow. It needs a
QUIC/MoQT client compiled for the device, plus mic capture, Opus, and playback
wired up by hand.

The server-side engine reactor is a Linux, kernel-bypass runtime. It will not
run on a phone. So the mobile client is built on a **separate, portable
QUIC event loop** (the same one the non-browser SDK cores use), not on the
engine.

## What works today

  - **Run the browser SDK in a WebView** — Host the JavaScript SDK inside a system WebView and let the WebView's
    WebTransport stack carry audio. This is the fastest option to ship.
    WebView capability constrains it (see below).
  - **Embed a native-core SDK** — Bundle a non-browser SDK core (Go or Rust) into a cross-platform mobile
    runtime. This gives you the native QUIC→WebSocket fallback ladder that the
    browser SDK does not have.

### Option A — WebView + WebTransport

The browser transport works unchanged inside a WebView **if** that WebView ships
WebTransport and the W3C Encoded Transform API. In practice:

  
    Android System WebView is Chromium-backed, so recent versions expose
    WebTransport and `RTCRtpScriptTransform`. Load a page that runs
    [browser audio capture](/modalities/voice/transport-web/browser-audio-capture).
    Grant the mic permission through the WebView permission callback. Audio
    then flows over QUIC exactly as it does in Chrome. Check the device's
    WebView version. Older versions lack the Encoded Transform gate, and the
    SDK will refuse to capture.
  
  
    `WKWebView` does **not** ship WebTransport, so the browser path does not
    work on iOS today. Use Option B, or wait for the native client. Do not
    assume a fallback: the browser SDK is WebTransport-only. When the transport
    is missing, it hard-fails; it does not degrade.
  

> **NOTE:**
> The browser SDK has **no WebSocket fallback**; it is WebTransport-only. On a
>   flaky cellular network with UDP blocked, a WebView session can fail to connect
>   with no automatic retry over TCP. Option B avoids this.

### Option B — embed a native-core SDK

The non-browser SDKs (github.com/clutchcall/clutchcall-sdk/go, clutchcall, clutchcall-agents, plus
Java and .NET) reach WebTransport/MoQT through a **shared native transport
core**, a portable C++ QUIC engine wrapped for each language. That core carries
a **QUIC → WebSocket fallback ladder**: when QUIC cannot establish (UDP-blocked
networks, TCP-only proxies), the core stays on a WebSocket rung so audio still
flows.

You embed one of those cores in a cross-platform mobile framework — for
example, a Go or Rust core compiled for `arm64` and bridged into your app.
Then you drive the same `AudioBridge` surface documented in the SDK reference:

  
    ```ts
    // Same AudioBridge surface as server + browser; here the client core
    // is embedded natively, not the browser transport.
    const bridge = await voice.audioBridge.attach(callSid, {
      codec: "opus",            // app legs are Opus on the wire
    });
    bridge.onDownlink((frame) => player.enqueue(frame)); // agent → device
    mic.onFrame((pcm) => bridge.publishUplink(pcm));     // device → agent
    ```
  
  
    ```rust
    let bridge = voice.audio_bridge().attach(&call_sid, Codec::Opus).await?;
    bridge.on_downlink(|frame| player.enqueue(frame));
    mic.on_frame(|pcm| bridge.publish_uplink(pcm));
    ```
  

You are responsible for mic capture, Opus encode/decode, and playback on the
device. The SDK core owns the transport. See the
[SDK cores](/modalities/voice/sdk/rust) for the exact method names.

## Planned native path (roadmap)

The intended long-term shape gives you real Swift and Kotlin packages so you
never touch C directly:

  1. **One shared transport core, compiled per platform**
The portable QUIC/MoQT core is compiled to a **per-ABI native library** for
    Android and a **static library** for iOS, so both platforms share one
    audited transport implementation.
  2. **Thin platform bridges**
A Kotlin layer over the Android library and a Swift layer over the iOS
    static library expose an idiomatic `AudioBridge` (`publishUplink` /
    `onDownlink`) that matches the other SDKs.
  3. **Native fallback ladder included**
The mobile client is built on the same core as the other native SDKs, so
    it inherits the QUIC→WebSocket fallback ladder. The browser path lacks
    this resilience, which matters most on cellular.
  4. **Unity / game-engine target alongside**
The same core also backs a Unity/native-FFI target (per-ABI library on
    Android, static archive via `[DllImport("__Internal")]` on iOS) for
    game and XR clients.

> **NOTE:**
> Track the roadmap status in [status quick-reference](#status). Mobile native
>   clients are **aspirational**. No Swift/Kotlin source exists yet. The Unity
>   target and the native cores you can embed today are the concrete precursors.

## Audio on the app leg

However you connect, the wire contract for an app is the same as the browser:

- **Codec** — Opus on the app leg (`AudioCodec = "opus"`). The engine transcodes
  to and from its internal 8 kHz PCM16 bus and to G.711 on any phone leg, so your
  app never has to match the caller's codec.
- **Framing** — 20 ms Opus frames, mono. The RTP clock stays at 48 kHz even
  though the encoder's internal rate is narrowband. You do not manage this.
- **No on-device echo cancellation from us.** The runtime relies on the
  platform's own AEC. Enable the OS voice-processing audio mode (VoIP category
  on iOS, voice-communication mode on Android) so the mic is echo-cancelled
  before Opus encode. See
  [voice isolation](/modalities/voice/concepts/voice-isolation) for what the
  engine does and does not do.

## Status

| Path | Status |
|------|--------|
| WebView + WebTransport (Android) | Works where the WebView supports WebTransport |
| WebView + WebTransport (iOS) | Not available (no `WKWebView` WebTransport) |
| Embed a native-core SDK (Go/Rust) | Available — carries the QUIC→WebSocket fallback |
| Native Swift SDK | Roadmap — plan only |
| Native Kotlin SDK | Roadmap — plan only |
| Unity / FFI target | Planned, precursor to native mobile |

  - **[WebRTC diversion](/modalities/voice/transport-web/webrtc-diversion)** — How the browser taps encoded Opus onto QUIC — the model a WebView reuses.
  - **[WebTransport](/modalities/voice/transport-web/webtransport)** — The single-port QUIC plane every client dials.
  - **[Rust SDK](/modalities/voice/sdk/rust)** — A native-core SDK you can embed in a mobile runtime today.
  - **[Transport to Web & Apps](/modalities/voice/transport-web/overview)** — Where mobile fits in the overall client-transport picture.
