Reuse your browser WebRTC audio pipeline and tap the encoded Opus onto QUIC with captureMicrophone — no ICE/DTLS/SRTP transport.
You already have a browser app that runs getUserMedia and an RTCPeerConnection.
You do not have to rip it out to move media onto TeleQuick. captureMicrophone
keeps that exact pipeline: a loopback RTCPeerConnection runs the browser’s
AEC / AGC / noise-suppression and the Opus encoder, and a worker
RTCRtpScriptTransform taps the encoded Opus frames and writes them to a MoQT
audio track over QUIC. WebRTC’s ICE / DTLS / SRTP transport is never used — only
its capture graph and codec.
This is a browser / TypeScript technique — mic capture is a JavaScript-only
API. The browser SDK is WebTransport-only (no WebSocket fallback rung yet); if
you need a TCP/WebSocket fallback today, terminate on the server-side WebRTC
leg instead. See Browser compatibility.
Connect a MoQT client with the encoded-frame rule enforced up front, publish an
audio track, and hand that publication to captureMicrophone. Subscribe the
return track and decode it with OpusPlayer.
TypeScript
Python
import { MoqtClient, captureMicrophone, OpusPlayer } from "@telequick/sdk/moqt";async function divertToQuic(sid: string, token: string) { // requireEncodedTransform: refuse to connect if this browser can't run the // worker RTCRtpScriptTransform, so the encoded-frame rule holds all session. const client = await MoqtClient.connect( "quic://relay.telequick.dev", token, (state) => console.log("moqt", state), { requireEncodedTransform: true }, ); // Uplink: publish the mic track (distinct name from the speaker track so the // relay never fans your own audio back to you). const uplink = client.publishAudio(`moqtaudio/${sid}`, "mic", { capability: "agent.mic.opus", }); // captureMicrophone runs the loopback pc + worker transform and writes each // encoded Opus frame straight to `uplink` (raw PCM never crosses the wire). const mic = await captureMicrophone(uplink, { audioConstraints: { echoCancellation: true, autoGainControl: true, noiseSuppression: true, }, }); // Downlink: decode the agent/caller audio and play it back. const ctx = new AudioContext(); const player = new OpusPlayer(ctx, { sampleRate: 48000, channels: 1 }); await player.start(); client.subscribeAudio(`moqtaudio/${sid}`, "spk", (tsUs, opus) => player.push(tsUs, opus)); return function hangUp() { mic.stop(); // tears down the capture graph (not the publication) player.close(); client.close(); };}
# Browser mic capture is a JavaScript-only API (getUserMedia +# RTCRtpScriptTransform), so there is no Python captureMicrophone. From Python# you consume the same audio server-side over the AudioBridge — no WebRTC.import osfrom telequick.voice import Voicev = Voice( base_url="https://engine.telequick.dev", api_key=os.environ["TELEQUICK_CREDENTIALS"], org_id="org_abc",)def bridge(sid: str): b = v.audio_bridge.attach( sid, codec="pcm16", sample_rate=16000, on_uplink=lambda pcm, ts_us: my_asr.feed(pcm), # caller/browser audio in ) my_tts.on_chunk(lambda pcm: b.publish_downlink(pcm)) # audio back out return b
If your existing WebRTC app already called getUserMedia (or produced a
processed MediaStreamTrack), pass it in with track so you never prompt for the
mic twice — captureMicrophone skips acquisition and taps your track directly.
// `myExistingTrack` is the audio track your current WebRTC pipeline already owns.const mic = await captureMicrophone(uplink, { track: myExistingTrack });// mic.stop() will NOT stop a track you provided — you still own its lifecycle.
captureMicrophonethrows if RTCRtpScriptTransform is absent rather than
falling back to an insecure main-thread path — the worker transform is the only
encoded-frame path (it is what lets the relay carry frame-level E2EE later).
It ships in Chrome 110+, Edge, Safari, and Firefox 133+. Gate your UI with
encodedTransformSupported() before offering a “call” button. Local dev needs a
secure context and a short-lived ECDSA cert via serverCertificateHash; dial
127.0.0.1, not ::1.