captureMicrophone(publication, opts) for the send side,
and OpusPlayer for playback. You never touch WebCodecs or raw PCM by hand.
The capture side reuses the browser’s own audio pipeline. A loopback
connection runs the built-in echo cancellation, automatic gain control, and
noise suppression, then the platform’s Opus encoder. You tap the encoded
frames off the encoder and send the bytes. Nothing decodes the audio in the
page. The browser does the DSP and the codec; you only move the payload. See
WebRTC diversion for why
this design keeps you off a media SFU, and
RTCRtpScriptTransform
for how the tap works.
The encoded-frame tap runs only in a Worker, through the standard
RTCRtpScriptTransform. If the browser lacks it, captureMicrophone refuses
to capture. It does not fall back to an insecure path, and it never acquires
a mic it cannot use. Check support first with encodedTransformSupported().
Read Browser compatibility
for the support matrix.Capture the microphone
1
Connect a media session
Open a transport session to your relay and set
requireEncodedTransform: true.
That option enforces the encoded-frame rule for the whole session up front. On
an unsupported browser, you fail at connect time, not after you acquire the
mic.2
Publish an audio track
Create an outbound audio track. The Opus track runs 48 kHz mono, 20 ms frames on the wire. You do not set the
rate; the browser’s encoder does. Those are the defaults an
publishAudio(ns, name, opts) returns an
AudioPublication whose write(timestampUs, frame) accepts one encoded Opus
frame at a time. Namespaces are room-scoped by call. Key them off your
call_sid (for example, namespace voice/<sid>, track name uplink).AudioPublication carries.3
Start capture
Hand the publication to
captureMicrophone. It prompts for the mic (with
AEC/AGC/NS enabled), runs the encoder, installs the transform Worker, and calls
uplink.write(...) for every encoded frame. It returns a handle. Call
stop() on the handle to tear the capture graph down.Capture options
captureMicrophone(publication, opts) takes an optional MicCaptureOptions:
MediaTrackConstraints
Constraints passed to
getUserMedia({ audio }). Defaults enable
echoCancellation, autoGainControl, and noiseSuppression. Override to pin
a device or relax the DSP chain.MediaStreamTrack
Supply your own audio track instead of a
getUserMedia call — for example, a
track you already processed. When you pass this, stop() leaves the track
running (you own its lifecycle).The browser’s echo cancellation, gain control, and noise suppression are the
only cleanup on the send side. The server-side media path does not run AEC
or AGC of its own. On PSTN legs, where there is no browser AEC, the runtime
compensates with voice-activity gating instead (see
Turn detection).
Play received audio
OpusPlayer is the receive-side mirror. WebCodecs decodes the Opus frames you
receive. An AudioWorklet ring buffer plays them out and pads with silence
on underrun. Feed the player the frames delivered by subscribeAudio.
OpusPlayer accepts an options object: sampleRate (decoder output rate,
default 48000) and channels (default 1). Call start() once before the first
push, and close() to release the decoder and disconnect the worklet.
A minimal two-way softphone
Put both halves together for a complete browser voice leg: publish the mic, subscribe to the far side, and play it.These helpers are browser-only:
getUserMedia, RTCRtpScriptTransform,
and WebCodecs have no server-side equivalent. To send or receive audio from
Node, Python, Go, or Rust, use the native-core audio bridge instead. See the
voice SDKs. Native mobile capture is
plan-only today.In the browser, this transport is WebTransport-only. There is no automatic
QUIC→WebSocket fallback rung yet (that rung ships in the native SDK cores). If
a network blocks QUIC, the media session fails; it does not downgrade. Plan
your fallback UX around
WebTransport availability.
Next steps
WebRTC diversion
Why capture reuses the browser’s WebRTC pipeline as a codec tap, not a
transport.
RTCRtpScriptTransform
How the Worker taps encoded Opus frames and where E2EE fits in.
Browser compatibility
Which engines run the transform, WebCodecs, and the capture worklet.
Turn detection
How on-device VAD and barge-in decide when the caller is speaking.

