:443 plane,
without ICE/DTLS/SRTP negotiation or a media server to operate.
There are two on-ramps:
LiveKit drop-in shim
Swap one import. Your
Room, RoomEvent, and track code keeps compiling and
runs over our transport instead of LiveKit’s SFU.Raw transport primitives
Wire your own runtime directly to
publishAudio / subscribeAudio. You own
capture, encode, and playback; we move the frames.Option 1 — the LiveKit drop-in shim
The compat shim exposes alivekit-client-compatible surface (Room,
RoomEvent, createLocalAudioTrack, participants, tracks) mapped onto our
MoQT/QUIC transport. For the common voice path — publish the mic, subscribe
to remote audio, exchange data messages — migration is a one-line import swap.
Preview. The
livekit-compat shim is early-access — it covers the core
voice path (publish/subscribe audio, data messages) but is not yet a drop-in
for every livekit-client API. Pin a version and test your flows. If you only
need transport, the raw MoQT primitives below are the stable path.Swap the import
Point your existing
livekit-client imports at the shim. Nothing else in
your component changes.What maps to what
Under the hood the shim keeps LiveKit semantics but replaces the media plane entirely — there is no SFU, no ICE, and WebRTC’s transport is never used.| LiveKit concept | Runs as |
|---|---|
Room.connect(url, token) | A MoQT session over WebTransport on :443 |
| Participant identity | A room-scoped MoQT namespace (relay enforces isolation via the token) |
| Microphone track | Encoded Opus frames published as MoQT objects |
| Remote audio track | A MoQT subscription decoded with WebCodecs into a Web Audio graph |
publishData / DataReceived | A reliable MoQT data track |
Scope of the shim. The voice surface is shipped and proven end-to-end
(two browsers exchanging live Opus over the transport). Video, screenshare,
and per-participant E2EE key rotation are deferred — the shim is
audio-first. Data-track auto-subscribe is off by default: subscribing to a
track a peer never published tears down the session, so pass
subscribeData: true only when peers are known to publish data. A
session-setup race can occasionally deliver zero frames on connect; retry the
connect if the first attempt is silent.The drop-in shim ships for the browser / TypeScript client (the
livekit-client surface) today. A server-side Python equivalent (the
livekit.rtc surface) is on the roadmap but not yet available — Python agents
should use the transport primitives below or point at a
realtime endpoint.Option 2 — use only the transport
If you are not a LiveKit app — you have your own capture/encode/playback, or a non-browser runtime — talk to the transport primitives directly. You keep your runtime; we only carry the frames.- TypeScript
- Native runtimes
captureMicrophone
and OpusPlayer is yours — your VAD, your turn-taking, your model. The transport
does not impose a runtime.
Namespace rule. Every track lives under a room-scoped namespace tuple with
a non-empty prefix and suffix; empty tuples are rejected by the media-over-QUIC
engine.
setRoomScope() (and the shim’s Room) handle this for you — if you
build namespaces by hand, keep them room-scoped so peers can discover each
other.Runtime on your side, or ours?
Keeping your runtime and using only the transport is one end of a spectrum. If you would rather hand us the conversation loop too, TeleQuick can run the agent for you and you bring only the models:From Self-Hosted LiveKit
The full migration path — trunks, tokens, and rollout — for a LiveKit
deployment moving onto our transport.
BYO Speech-to-Speech
Keep our runtime, point it at your own realtime speech-to-speech endpoint
with per-tenant credentials.
WebRTC Diversion
How the encoded-frame tap moves an existing WebRTC pipeline onto QUIC.
Agent Runtime Overview
What our managed runtime does when you do want us to drive the conversation.