|
| 1 | +--- |
| 2 | +type: explanation |
| 3 | +sidebar_position: 6 |
| 4 | +title: MoQ Streaming |
| 5 | +description: Understand how Media over QUIC (MoQ) works in Fishjam — the relay model, publish/subscribe architecture, paths, and token-based access control. |
| 6 | +--- |
| 7 | + |
| 8 | +# MoQ Streaming with Fishjam |
| 9 | + |
| 10 | +_How Media over QUIC (MoQ) works in Fishjam_ |
| 11 | + |
| 12 | +## What is MoQ? |
| 13 | + |
| 14 | +[Media over QUIC (MoQ)](https://datatracker.ietf.org/wg/moq/about/) is a new internet standard for live media delivery, designed from the ground up for **scalable, low-latency streaming to large audiences**. |
| 15 | + |
| 16 | +Unlike WebRTC — which was primarily built for interactive, peer-to-peer conferencing — MoQ is optimized for the one-to-many broadcast model: one publisher, potentially thousands of simultaneous subscribers, all receiving the stream with minimal delay. |
| 17 | + |
| 18 | +A few properties make MoQ stand out: |
| 19 | + |
| 20 | +- **Built on QUIC.** QUIC is a modern transport protocol that eliminates head-of-line blocking, recovers from packet loss more gracefully than TCP, and establishes connections faster. For live video, this means more resilient delivery at low latency. |
| 21 | +- **Standardized negotiation.** Because MoQ defines a common signaling and subscription protocol, any MoQ-compliant client can connect to any MoQ-compliant relay — you are not locked into a proprietary stack. |
| 22 | +- **Relay-based architecture.** The relay is a first-class part of the MoQ protocol, not an add-on. Because relaying is built into the protocol's design, scaling delivery to large audiences is a native capability. |
| 23 | + |
| 24 | +:::info |
| 25 | +Fishjam also supports WebRTC-based livestreaming (WHIP/WHEP). See [Livestreams](./livestreams) for that approach. |
| 26 | +::: |
| 27 | + |
| 28 | +## How MoQ Works in Fishjam |
| 29 | + |
| 30 | +Fishjam provides a **MoQ relay** that publishers push media to and subscribers pull media from. |
| 31 | + |
| 32 | +``` |
| 33 | +Publisher → Fishjam MoQ Relay → Subscriber(s) |
| 34 | +``` |
| 35 | + |
| 36 | +The relay is responsible for distributing the stream: it receives media from the publisher once and fans it out to every subscriber. |
| 37 | + |
| 38 | +### The publish/subscribe model |
| 39 | + |
| 40 | +MoQ uses a **publish/subscribe** model: |
| 41 | + |
| 42 | +- A **publisher** connects to the relay, announces a stream under a specific path, and starts sending media. |
| 43 | +- **Subscribers** connect to the relay, discover announced paths, and receive the media. |
| 44 | + |
| 45 | +The relay manages the flow between them. Neither side needs a direct connection to the other. |
| 46 | + |
| 47 | +### Paths |
| 48 | + |
| 49 | +Every stream is identified by a **path** — a slash-separated string like `my-room/alice-camera`. Paths are used in two distinct ways: |
| 50 | + |
| 51 | +1. **Addressing** — a subscriber consumes an exact path to receive that specific stream (e.g. `my-room/alice-camera`). |
| 52 | +2. **Discovery** — a subscriber watches a prefix (e.g. `my-room`) to learn which streams are currently live under it. This returns a live feed of announced paths — each of which must then be consumed individually. This is how you can display all participants in a room without knowing their paths in advance. |
| 53 | + |
| 54 | +Note that consuming an exact path and discovering a prefix are separate operations. Consuming `my-room` directly would fail unless a publisher is broadcasting at that exact path. |
| 55 | + |
| 56 | +### Path Scoping |
| 57 | + |
| 58 | +Every connection goes to `relay.fishjam.io/<fishjam-id>`. Your Fishjam ID is automatically used as the token's root namespace by the Fishjam Server — you never include it in `publishPath` or `subscribePath`; it is set for you. All paths you specify are **relative to that root**. |
| 59 | + |
| 60 | +Path matching is **prefix-based**: a path of `"stream-name"` permits any broadcast whose full path starts with `stream-name/`, not just the exact string `"stream-name"`. |
| 61 | + |
| 62 | +#### Publisher paths |
| 63 | + |
| 64 | +The `publishPath` you set determines how much freedom the broadcaster has when naming their broadcast: |
| 65 | + |
| 66 | +- **Broad path** (`publishPath: "stream-name"`) — the client can publish as any sub-path under `stream-name`, such as `stream-name/alice` or `stream-name/bob-camera`. The client chooses its own identity; the relay only enforces the prefix. |
| 67 | +- **Specific path** (`publishPath: "stream-name/alice"`) — the client can **only** publish as `stream-name/alice`. If the broadcaster tries to use `stream-name/bob`, the relay rejects the announcement. This is how you enforce a broadcaster's identity from the server side. |
| 68 | + |
| 69 | +Use the broad form when clients self-identify (e.g., users pick their own stream name). Use the specific form when your backend assigns identities (e.g., you issue a per-user token for a managed conference). |
| 70 | + |
| 71 | +#### Subscriber paths |
| 72 | + |
| 73 | +The `subscribePath` works the same way: it is a prefix that limits which broadcasts the subscriber can consume and discover. |
| 74 | + |
| 75 | +- **Broad path** (`subscribePath: "stream-name"`) — the subscriber can consume any broadcast under `stream-name/` and will surface all publishers in that namespace as they come and go. |
| 76 | +- **Specific path** (`subscribePath: "stream-name/alice"`) — the subscriber can only receive from `stream-name/alice`. Broadcasts at `stream-name/bob` are invisible to this client. |
| 77 | + |
| 78 | +#### Example: a multi-publisher room |
| 79 | + |
| 80 | +A typical room setup uses a combination of both patterns: |
| 81 | + |
| 82 | +1. The backend issues each broadcaster a **specific** publisher token — `publishPath: "my-room/<user-id>"` — so each user can only occupy their own slot. |
| 83 | +2. The backend issues viewers a **broad** subscriber token — `subscribePath: "my-room"` — so they discover and consume every broadcast in the room. |
| 84 | +3. When a new publisher joins or leaves, the viewer is informed by the relay |
| 85 | + |
| 86 | +## Access Control: MoQ Tokens |
| 87 | + |
| 88 | +Access to the relay is controlled by **MoQ tokens** — short-lived JWTs that are path-scoped: |
| 89 | + |
| 90 | +| Token type | Grants | Typical recipient | |
| 91 | +| ---------------- | ---------------------------------- | ----------------- | |
| 92 | +| Publisher token | Write access to a specific path | Streamer | |
| 93 | +| Subscriber token | Read access to a path or namespace | Viewer | |
| 94 | + |
| 95 | +A token is attached to the relay URL as a query parameter (`?jwt=<token>`). The relay validates the token and enforces its scope before allowing any media to flow. |
| 96 | + |
| 97 | +Keeping publisher and subscriber tokens separate ensures that a viewer can never accidentally publish to the stream, and a publisher cannot subscribe to paths it does not own. |
| 98 | + |
| 99 | +## Getting Tokens |
| 100 | + |
| 101 | +There are two ways to obtain MoQ tokens, depending on where you are in the development lifecycle. |
| 102 | + |
| 103 | +### Sandbox API (prototyping) |
| 104 | + |
| 105 | +The **Sandbox API** is a ready-made backend provided by Fishjam for development and prototyping. It issues tokens without requiring you to build your own server, so you can start streaming immediately. |
| 106 | + |
| 107 | +To get a publisher token, call: |
| 108 | + |
| 109 | +``` |
| 110 | +GET https://fishjam.io/api/v1/connect/{FISHJAM_ID}/room-manager/moq/{PUBLISHER-PATH}/publisher |
| 111 | +``` |
| 112 | + |
| 113 | +To get a subscriber token, call: |
| 114 | + |
| 115 | +``` |
| 116 | +GET https://fishjam.io/api/v1/connect/{FISHJAM_ID}/room-manager/moq/{SUBSCRIBER-PATH}/subscriber |
| 117 | +``` |
| 118 | + |
| 119 | +The Sandbox API is **not intended for production** — it has no authentication and is only available in the Sandbox environment. See [What is the Sandbox API?](./sandbox-api-concept) for more context. |
| 120 | + |
| 121 | +### Fishjam Server SDK (production) |
| 122 | + |
| 123 | +In production, your backend generates tokens using the **Fishjam Server SDK**. This gives you full control over who can publish and who can subscribe. |
| 124 | + |
| 125 | +The SDK's `createMoqToken` method accepts either a `publishPath` or a `subscribePath`: |
| 126 | + |
| 127 | +- `publishPath` — issues a publisher token scoped to that path. |
| 128 | +- `subscribePath` — issues a subscriber token scoped to that path or namespace prefix. |
| 129 | + |
| 130 | +Your backend then delivers each token to the appropriate client (publisher or viewer), which uses it to connect to the relay. |
| 131 | + |
| 132 | +See the [MoQ Streaming tutorial](../tutorials/moq) for working code examples of both approaches. |
| 133 | + |
| 134 | +## See also |
| 135 | + |
| 136 | +- [MoQ Streaming tutorial](../tutorials/moq) — step-by-step guide to publishing and subscribing |
| 137 | +- [What is the Sandbox API?](./sandbox-api-concept) — when and why to use the Sandbox API |
| 138 | +- [Security & Token Model](./security-tokens) — broader overview of Fishjam's token system |
| 139 | +- [Livestreams](./livestreams) — WebRTC-based livestreaming with WHIP/WHEP |
0 commit comments