captureMicrophone— runs the browser’s AEC / AGC / noise-suppression and Opus encoder in a loopbackRTCPeerConnection, then diverts the encoded frames (raw PCM never crosses the wire) onto your uplink track.OpusPlayer— decodes received Opus with WebCodecs and renders it through anAudioWorkletring buffer.attachCaller— the browser-caller side of the audio bridge: it subscribes the downlink (voice/<sid>/downlink, what the cloud sends the caller) and publishes the uplink (voice/<sid>/uplink, the caller’s mic).
How the pieces connect
Mint the call server-side
Your backend originates the call with an
agent attached and hands the
browser back the sid plus a browser-scoped token. Your secret API key
never ships to the client.Start playback
Create and
start() an OpusPlayer (must happen inside a user gesture so
the AudioContext can resume).Attach as the caller
attachCaller(sid, { codec: "opus", onDownlink }) subscribes the agent’s
audio and pushes each frame into the player.Front end: the click-to-talk component
This is the whole browser side. It assumes a page with a dial button and holds a browser-scoped key — a publishable,voice-scoped, short-TTL token, not
your server secret (see the backend below).
The browser mic capture is inherently a Web API, so this half is TypeScript only;
the backend shows both TypeScript and Python.
captureMicrophone runs the browser’s echo-cancellation, gain control, and
noise suppression, then diverts the encoded Opus frames onto the uplink —
raw PCM never leaves the tab. The engine has no server-side AEC/AGC, so those
constraints doing their job in the browser is what keeps the agent from hearing
its own voice. Details in
WebRTC diversion and
RTCRtpScriptTransform.Minimal backend
The browser must not hold your secret API key, so a tiny server route mints the call — attaching the AI agent that will answer — and returns thesid plus a
publishable token scoped to this one call. The call is created over a
voice-routing trunk you provision for web sessions; to / from label the
leg (they need not be dialable numbers for a pure-web agent).
- TypeScript
- Python
agent binds a server-side agent (ASR→LLM→TTS or a speech-to-speech provider)
that answers the call and drives both legs — you don’t open an AudioBridge on
the server. To run your own dialog logic instead, drop agent and bridge the
audio yourself; see
Bring your own ASR / LLM / TTS.Local development
WebTransport with a browser needs a secure context and a valid certificate. For local work:- Serve the page over HTTPS (secure context is required for
getUserMedia, WebTransport, and WebCodecs). - Use a short-lived ECDSA dev certificate (≤14 days) and pass its SHA-256 to
the transport via
serverCertificateHashes— RSA certs are rejected by the QUIC handshake. - Dial
127.0.0.1, not::1— the IPv6 loopback breaks the WebTransport handshake.
Native mobile (Swift / Kotlin) apps are on the roadmap, not shipped — there
is no native mobile SDK today. A browser tab on the device is the supported path;
track the plan in
Mobile apps.
Related
Browser audio capture
The capture pipeline in depth — loopback PC, the worker transform, framing.
JavaScript SDK
Full signatures for
Voice, attachCaller, captureMicrophone, OpusPlayer.Turn detection
Tune VAD, barge-in, and idle timeouts for the agent leg.
Support handoff
Escalate the browser call from the AI agent to a human, sid preserved.