# Browser Compatibility

> Which browsers run the in-browser voice path today, what each one is missing, and what to do everywhere else.

The in-browser voice path depends on three web platform APIs that are still
maturing: **WebTransport** (media and data over QUIC), **`RTCRtpScriptTransform`**
(the tap that lifts encoded Opus frames out of the browser's codec — see
[WebRTC diversion](/modalities/voice/transport-web/webrtc-diversion)), and
**WebCodecs** (the decoder behind downlink playback). Support for all three is
uneven across browsers. This page tells you where the full path runs, where it
partly runs, and what to do when it does not run.

> **NOTE:**
> This page is about the **browser SDK** (@clutchcall/agents) running in a web page.
>   Native SDK cores (Python, Go, Rust, Java, .NET) reach the media plane through a
>   shared QUIC transport and already ship a QUIC-to-WebSocket fallback ladder.
>   The browser does not have that fallback yet. The sections below describe that
>   gap.

## The short answer

  - **Chromium family** — **Full path.** Chrome, Edge, and other Chromium-based browsers (Brave, Arc,
    Opera) run capture, publish, subscribe, and playback end to end.
  - **Firefox** — **Uplink works, downlink needs your own decoder.** Capture and publish run.
    The bundled player relies on a Chromium-only decoder.
  - **Safari** — **Not yet.** The encoded-frame tap ships only in Safari Technology Preview.
    Use a telephony leg today, or wait for the planned fallback.

## Support matrix

This matrix is accurate as of this writing. Browser support moves quickly, so
treat the version floors as a guide. Feature-detect at runtime (the SDK does
this for you).

<div style={{overflowX: "auto"}}>

| Capability | What it powers | Chromium (Chrome / Edge) | Firefox | Safari |
|---|---|---|---|---|
| **WebTransport** | Media + data over QUIC on `:443` | Yes (97+) | Yes (114+) | Preview only |
| **`RTCRtpScriptTransform`** | Encoded-Opus uplink tap (in a Worker) | Yes (110+) | Yes (133+) | Technology Preview |
| **WebCodecs `AudioDecoder`** | Bundled downlink player (Opus decode) | Yes | Partial / no | No |
| **`MediaStreamTrackProcessor`** | Frame-level media helpers | Yes | No | No |
| **`getUserMedia` + loopback WebRTC** | Mic capture + browser Opus encoder | Yes | Yes | Yes |
| **Full browser SDK voice path** | Capture → publish → subscribe → play | **Yes** | **Uplink only** | **No** |
| **`serverCertificateHashes` (dev cert)** | Local-dev QUIC without a public CA | Yes | Yes | No |

</div>

The last row is the one that matters for a product decision: only Chromium runs
the whole loop out of the box today.

## Why each browser lands where it does

  **Chromium — the reference target**

Everything the browser SDK needs is stable in Chromium. WebTransport
    carries MoQT objects. `RTCRtpScriptTransform` runs in a Worker to lift
    encoded Opus off the loopback sender. The WebCodecs `AudioDecoder` feeds an
    `AudioWorklet` ring buffer for playback. If you build and test in Chrome or
    Edge, you are on the path we exercise most.

  **Firefox — uplink yes, bundled player no**

Firefox has both WebTransport (114+) and `RTCRtpScriptTransform` (133+), so
    **capture and publish** of microphone audio work. The downlink half is
    missing: the bundled Opus player is built on WebCodecs
    `AudioDecoder`, which is Chromium-only in practice. To hear audio in
    Firefox, supply your own decode-and-render path (for example, decode Opus
    in WebAssembly and push PCM into an `AudioWorklet`). Subscribe to the
    downlink track yourself. You can publish your voice into a call today.

  **Safari — waiting on the encoded tap**

The uplink depends on the encoded-frame tap, which ships only in **Safari
    Technology Preview**. WebTransport is also still behind a preview flag.
    Until both land in stable Safari, the browser SDK media path does not run
    there. You have two options today. Route those users over a telephony leg
    ([SIP / PSTN](/modalities/voice/transport-telephony/overview)) instead of
    in-browser media. Or wait for the planned browser fallback described below.

## What the SDK does when a capability is missing

The browser SDK **feature-detects and refuses. It does not degrade silently.**
This is deliberate: a half-working audio path is worse than a clear error you
can branch on.

- `captureMicrophone(...)` checks for encoded-transform support first. If
  support is absent, it **throws** before it touches the microphone.
- A connect with encoded transform required will **reject the session** on a
  browser that cannot provide it.

So on Safari, or an old Firefox, a capture call fails fast. Guard it and fall
back to your own path:

```ts
import { encodedTransformSupported } from "@clutchcall/agents";

if (!encodedTransformSupported()) {
  // No in-browser publish here. Offer a phone-number / SIP option,
  // or a "best experienced in Chrome" prompt, instead of a dead mic.
  showTelephonyFallback();
} else {
  await captureMicrophone(publication, { /* ... */ });
}
```

> **WARNING:**
> There is no automatic WebSocket or WebRTC fallback **in the browser** today.
>   The encoded-transform Worker is the only uplink path the browser SDK ships.
>   This path is what enables frame-level end-to-end encryption (the relay only
>   ever sees ciphertext). If `encodedTransformSupported()` is false, the browser
>   cannot publish audio through the SDK. Plan for that branch.

## The fallback story (what exists, what is planned)

The stack's standing transport rule is **QUIC and MoQT first-class, with
WebSocket and WebRTC as required fallbacks**. This is where each rung stands
for browsers today:

  1. **QUIC / WebTransport — shipped (Chromium, Firefox uplink)**
The primary path. MoQT rides WebTransport on the shared `:443` listener,
    using the `moqt-16` subprotocol. This is what the matrix above measures.
  2. **WebSocket media fallback — native cores only, not browser yet**
The native SDK cores fall back from QUIC to a WebSocket media rung when
    UDP is blocked or a TCP proxy sits in the way. That ladder is **not wired
    into the browser SDK**. Treat browser WebSocket media as planned, not
    shipped.
  3. **WebRTC media fallback — server side exists, browser leg planned**
The engine can terminate a real WebRTC leg server-side (ICE-lite with
    in-process DTLS-SRTP). This is how a UDP-blocked or Safari client would
    eventually reach the media plane. The server capability is in place. The
    browser SDK wiring for this automatic fallback is planned, not yet
    available.

> **NOTE:**
> Until the browser WebSocket and WebRTC rungs land, the practical fallback for
>   a non-Chromium browser is a **telephony leg**: dial the user in, or let them
>   dial a provisioned number. The audio path is then SIP/RTP rather than
>   in-browser media.
>   See [Transport to telephony](/modalities/voice/transport-telephony/overview).

## Local development gotchas

WebTransport and the dev-certificate flow are strict about the environment.
These items trip people up before any compatibility question:

- **Secure context required.** Serve over HTTPS (or `localhost`, which browsers
  treat as secure). `RTCRtpScriptTransform`, WebTransport, and `getUserMedia`
  all refuse to run on an insecure origin.
- **Use `127.0.0.1`, not `::1`.** The IPv6 loopback literal breaks the QUIC
  handshake during local testing. The IPv4 loopback works.
- **Short-lived ECDSA dev cert.** For local QUIC without a public CA, pass the
  certificate fingerprint via `serverCertificateHashes`. The certificate must be
  ECDSA and valid for **no more than 14 days**. Browsers reject longer-lived
  certs on that code path. This flow is unavailable in Safari.
- **Multi-brand SNI in production.** The WebTransport listener presents
  per-brand certificates by SNI. Connect to the tenant's own host
  (`<workspace-id>.webrtc.firstbyte.fast` / `relay.firstbyte.fast`) so the
  relay selects the right certificate.

## Related

  - **[Browser audio capture](/modalities/voice/transport-web/browser-audio-capture)** — Capture and encode microphone audio, and publish it as a MoQT track.
  - **[WebRTC diversion](/modalities/voice/transport-web/webrtc-diversion)** — Borrow the browser's Opus encoder and divert encoded frames onto QUIC.
  - **[RTCRtpScriptTransform](/modalities/voice/transport-web/rtcrtpscripttransform)** — The Worker tap that makes the encoded-frame path — and its E2EE — possible.
  - **[WebTransport](/modalities/voice/transport-web/webtransport)** — The browser-native QUIC path for media and data.
  - **[Mobile app transport](/modalities/voice/transport-web/mobile-app)** — Where native mobile clients stand today.
  - **[Transport to telephony](/modalities/voice/transport-telephony/overview)** — The SIP/PSTN fallback for browsers that can't run the media path.
