# Browser support

> Which browsers can run a Synento session, how the SDK reports it, and what has actually been verified.

# Browser support

Synento encodes in the browser with **WebCodecs** and ships frames over a
WebSocket. There is no WebRTC fallback — that is a deliberate architectural
choice, not a gap: every frame is written to a durable stream, which is what
makes each session its own recording — so a browser either has the APIs or
it cannot publish. The SDK checks before connecting and tells you which piece is
missing.

## Requirements

A browser needs all four:

| Capability | Used for |
|---|---|
| `WebSocket` | Transport for media, chat and control |
| `VideoEncoder` / `VideoDecoder` / `VideoFrame` (WebCodecs) | Encoding your camera, decoding everyone else's |
| `navigator.mediaDevices` | Camera and microphone capture (requires a secure context) |
| `AudioWorkletNode` | Microphone capture at 16 kHz mono |
| `AudioEncoder` / `AudioDecoder` (Opus) | **Optional.** Compresses audio ~8x. Without it the call falls back to uncompressed PCM and works exactly the same, so this does not affect whether a browser is supported — `checkSupport()` reports it as `webCodecsAudio` for visibility only. |

`navigator.mediaDevices` only exists on **HTTPS or `localhost`**. On a plain
HTTP origin — including a LAN IP such as `http://192.168.1.10:3000` — capture is
unavailable no matter which browser you use.

## Matrix

| Browser | Publish + subscribe | Notes |
|---|---|---|
| Chrome / Edge 94+ | Yes | The reference target. H.264, VP9 and VP8 all negotiate. |
| Safari 16.4+ (macOS) | Yes | WebCodecs shipped in 16.4. Verified manually, not in CI. |
| Safari 16.4+ (iOS / iPadOS) | Publish + subscribe, with caveats | See [iOS](#ios) below. |
| Firefox 133+ | Yes | WebCodecs enabled by default from 133. |
| Firefox < 133 | No | `VideoEncoder` is absent; the SDK reports it and refuses to connect. |
| Any browser over plain HTTP | No | No `mediaDevices` outside a secure context. |

## What has actually been verified

Being precise about this matters more than the table looking green.

- **Chromium — automated, every CI run.** A two-party call with synthetic camera
  and microphone hardware: both sides decode the other's video, chat is
  delivered, and both participants' camera and microphone tracks are checked for
  recorded data in S2. See `e2e/tests/live-call.spec.ts`.
- **Playwright WebKit — automated, non-media flows only.** It runs sign-in, the
  dashboard, recording and export. **A WebKit pass is not a Safari pass.**
  Playwright's WebKit is a build of the engine, not the shipping browser: its
  WebCodecs support and media handling differ from Safari's, and it has no
  equivalent of Chromium's fake-device switch. Treating it as Safari coverage
  would be exactly the false assurance the suite exists to prevent.
- **Safari on macOS and iOS — manual.** Run the checklist below before a release.
  The result belongs in this table, not in a test report.

### Manual Safari pass

Against a deployed environment over HTTPS (not `localhost`, so the iOS device can
reach it):

1. Open the demo in Safari, join a session, and confirm the local preview shows.
2. Join the same session from a second device or browser. Confirm video appears
   **both ways** and that you can hear audio in both directions.
3. Send a chat message each way.
4. Lock the phone / switch apps for ~15 seconds, then return: the SDK should
   reconnect and resume publishing without a reload.
5. Leave, then open the recording in the dashboard: replay should show both
   participants with audio.
6. Export to MP4 and play the file.

### iOS

Two behaviours are worth knowing about, and both are platform behaviour rather
than Synento bugs:

- **Backgrounding stops capture.** iOS suspends media capture when the tab is not
  frontmost. The connection resumes on return, but the recording has a gap.
- **Autoplay of remote audio needs a gesture.** Audio playback starts from a user
  interaction (the join tap counts). A page that connects on load without any tap
  may render video silently until the user touches something.

## What your code should do

`checkSupport()` returns a structured report, and `SynentoRoom.connect()` refuses
to open a socket when it fails, rather than half-joining a call:

```ts
import { SynentoRoom } from '@synento/client'

const support = SynentoRoom.checkSupport()
if (!support.supported) {
  // support.reason is written for humans and names the missing capability.
  showUnsupportedMessage(support.reason)
  return
}
```

The individual booleans (`webSocket`, `webCodecsVideo`, `mediaDevices`,
`audioWorklet`) are all on the report, so you can tell "update your browser"
apart from "this page needs HTTPS" — a distinction users can act on.
