every.channel/evolution/proposals/ECP-0063-cloudflare-moq-webtransport.md

4.7 KiB

ECP-0063: Cloudflare MoQ Relay + WebTransport-Only Web Watch

Status: Implemented

Decision

Adopt Cloudflare's MoQ relay preview as the default "global" distribution layer and make the web watcher path WebTransport-only.

Concrete changes:

  1. ec-node gains a WebTransport MoQ publisher path that can publish a live CMAF (fMP4) stream to a relay URL (default: https://cdn.moq.dev/anon).
  2. every.channel (the deployed static site) becomes a real web watcher by embedding a WebTransport-capable MoQ player component (<moq-watch> from @moq/watch).
  3. The existing WebRTC/WS bootstrap directory/relay remains temporarily for compatibility, but is treated as deprecated once MoQ/WebTransport is stable.

Motivation

The project goal is one-to-many live streaming at global scale without rebuilding a bespoke stateful SFU stack.

MoQ over WebTransport moves the core system boundary to:

  • publisher produces timed objects (CMAF fragments),
  • relays cache/fanout,
  • subscribers fetch via a single WebTransport session.

This aligns the "global watcher" story with an infrastructure-native model rather than point-to-point rendezvous.

Scope

In scope for this ECP:

  • "Watch from the web" using WebTransport to a MoQ relay.
  • "Publish from a node" to the relay, using ffmpeg to create fMP4 fragments.
  • A shareable link format for every.channel to open a specific relay + broadcast name.

Out of scope (explicitly deferred):

  • Global discovery/index of broadcasts.
  • Manifest signing / Merkle availability / anti-junk (separate ECPs already exist).
  • Safari support guarantees (implement anyway; provide guidance/flag notes).
  • Multi-variant ladders and ABR (follow-on ECP once single-variant publish/watch works end-to-end).

Technical Notes

Relay default

Default relay endpoint is:

  • https://cdn.moq.dev/anon

Nodes may override via CLI flags for self-hosted relays or future Cloudflare relay endpoints. As of February 21, 2026, browser sessions against https://interop-relay.cloudflare.mediaoverquic.com/ are observed to stall/fail during MoQ session establishment with current web clients, while https://cdn.moq.dev/anon establishes browser sessions in Chrome.

Web player

Use the @moq/watch web component:

  • <moq-watch url="..." path="...">

This is WebTransport + WebCodecs based, and is expected to interoperate with Cloudflare's current relay preview.

CMAF passthrough default

ec-node wt-publish defaults --passthrough=false (and Nix module default passthrough = false).

Reason:

  • Browser @moq/watch playback against live OTA ingest currently hits repeated Invalid sample duration 0 in trun decode failures when fed raw passthrough fMP4 fragments.
  • Non-passthrough mode avoids this incompatibility and restores end-to-end playback reliability.

Transport compatibility

Cloudflare's public relay currently implements a subset of the IETF MoQ Transport draft-07 and may not interoperate with newer draft implementations.

Implementation choice:

  • Cloudflare's relay preview currently does not support ANNOUNCE (namespace-style publishing). ec-node wt-publish uses the moq-lite publish model via quinn + web-transport-quinn + moq-lite and moq-mux (fMP4 ingestion) for Cloudflare relay compatibility.
  • ec-node wt-publish is QUIC/WebTransport-only (no WebSocket fallback). NixOS deployments also set MOQ_CLIENT_WEBSOCKET_ENABLED=false as a belt-and-suspenders default for any other binaries that use moq-native.
  • For Cloudflare relay interop, we patch web-transport-proto to send and accept the standard WebTransport subprotocol negotiation header (sec-webtransport-protocol) in addition to the legacy wt-available-protocols/wt-protocol headers. Without subprotocol negotiation, the relay may not select a moqt-* protocol and can close the session immediately after MoQ SETUP.
  • If the relay does not select a WebTransport subprotocol, ec-node wt-publish attempts MoQ SETUP with protocol overrides (moqt-16, moqt-15, moq-00) before falling back to "no protocol".

Web share link:

  • https://every.channel/watch?url=<relay-url>&name=<broadcast-name>

Alternatives considered

  • Keep the legacy WebRTC/WS path as primary. Rejected because it does not align with relay-native MoQ fanout goals.
  • Wait for full draft parity across all relays before shipping. Rejected because live interop was already sufficient on the chosen relay path.

Rollout / Reversibility

  • Keep existing /api/* bootstrap endpoints during migration.
  • Make the web site prefer MoQ/WebTransport; keep legacy paths hidden behind "advanced" until removed.
  • Reversible by switching the deployed assets back to the previous UI build, and/or pointing users at the legacy paths.