-
Notifications
You must be signed in to change notification settings - Fork 3
Add MoQ streaming tutorial to docs #252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6300a24
537bd60
46e5600
b0775dd
78d33e6
d9f4e5d
3af1bb8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| --- | ||
| type: explanation | ||
| sidebar_position: 6 | ||
| title: MoQ Streaming | ||
| description: Understand how Media over QUIC (MoQ) works in Fishjam — the relay model, publish/subscribe architecture, paths, and token-based access control. | ||
| --- | ||
|
|
||
| # MoQ Streaming with Fishjam | ||
|
|
||
| _How Media over QUIC (MoQ) works in Fishjam_ | ||
|
|
||
| ## What is MoQ? | ||
|
|
||
| [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**. | ||
|
|
||
| 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. | ||
|
|
||
| A few properties make MoQ stand out: | ||
|
|
||
| - **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. | ||
| - **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. | ||
| - **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. | ||
|
|
||
| :::info | ||
| Fishjam also supports WebRTC-based livestreaming (WHIP/WHEP). See [Livestreams](./livestreams) for that approach. | ||
| ::: | ||
|
|
||
| ## How MoQ Works in Fishjam | ||
|
|
||
| Fishjam provides a **MoQ relay** that publishers push media to and subscribers pull media from. | ||
|
|
||
| ``` | ||
| Publisher → Fishjam MoQ Relay → Subscriber(s) | ||
| ``` | ||
|
|
||
| The relay is responsible for distributing the stream: it receives media from the publisher once and fans it out to every subscriber. | ||
|
|
||
| ### The publish/subscribe model | ||
|
|
||
| MoQ uses a **publish/subscribe** model: | ||
|
|
||
| - A **publisher** connects to the relay, announces a stream under a specific path, and starts sending media. | ||
| - **Subscribers** connect to the relay, discover announced paths, and receive the media. | ||
|
|
||
| The relay manages the flow between them. Neither side needs a direct connection to the other. | ||
|
|
||
| ### Paths | ||
|
|
||
| Every stream is identified by a **path** — a slash-separated string like `my-room/alice-camera`. Paths are used in two distinct ways: | ||
|
|
||
| 1. **Addressing** — a subscriber consumes an exact path to receive that specific stream (e.g. `my-room/alice-camera`). | ||
| 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. | ||
|
|
||
| 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. | ||
|
|
||
| ### Path Scoping | ||
|
|
||
| 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**. | ||
|
|
||
| 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"`. | ||
|
|
||
| #### Publisher paths | ||
|
|
||
| The `publishPath` you set determines how much freedom the broadcaster has when naming their broadcast: | ||
|
|
||
| - **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. | ||
| - **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. | ||
|
|
||
| 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). | ||
|
|
||
| #### Subscriber paths | ||
|
|
||
| The `subscribePath` works the same way: it is a prefix that limits which broadcasts the subscriber can consume and discover. | ||
|
|
||
| - **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. | ||
| - **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. | ||
|
|
||
| #### Example: a multi-publisher room | ||
|
|
||
| A typical room setup uses a combination of both patterns: | ||
|
|
||
| 1. The backend issues each broadcaster a **specific** publisher token — `publishPath: "my-room/<user-id>"` — so each user can only occupy their own slot. | ||
| 2. The backend issues viewers a **broad** subscriber token — `subscribePath: "my-room"` — so they discover and consume every broadcast in the room. | ||
| 3. When a new publisher joins or leaves, the viewer is informed by the relay | ||
|
|
||
| ## Access Control: MoQ Tokens | ||
|
|
||
| Access to the relay is controlled by **MoQ tokens** — short-lived JWTs that are path-scoped: | ||
|
|
||
| | Token type | Grants | Typical recipient | | ||
| | ---------------- | ---------------------------------- | ----------------- | | ||
| | Publisher token | Write access to a specific path | Streamer | | ||
| | Subscriber token | Read access to a path or namespace | Viewer | | ||
|
|
||
| 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: why not the classic Bearer token in the Authorization header?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on my knowledge, this is the recommended way (probably the only way). But I will research this topic. |
||
|
|
||
| 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. | ||
|
|
||
| ## Getting Tokens | ||
|
|
||
| There are two ways to obtain MoQ tokens, depending on where you are in the development lifecycle. | ||
|
|
||
| ### Sandbox API (prototyping) | ||
|
|
||
| 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. | ||
|
|
||
| To get a publisher token, call: | ||
|
|
||
| ``` | ||
| GET https://fishjam.io/api/v1/connect/{FISHJAM_ID}/room-manager/moq/{PUBLISHER-PATH}/publisher | ||
| ``` | ||
|
|
||
| To get a subscriber token, call: | ||
|
|
||
| ``` | ||
| GET https://fishjam.io/api/v1/connect/{FISHJAM_ID}/room-manager/moq/{SUBSCRIBER-PATH}/subscriber | ||
| ``` | ||
|
|
||
| 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. | ||
|
|
||
| ### Fishjam Server SDK (production) | ||
|
|
||
| In production, your backend generates tokens using the **Fishjam Server SDK**. This gives you full control over who can publish and who can subscribe. | ||
|
|
||
| The SDK's `createMoqToken` method accepts either a `publishPath` or a `subscribePath`: | ||
|
|
||
| - `publishPath` — issues a publisher token scoped to that path. | ||
| - `subscribePath` — issues a subscriber token scoped to that path or namespace prefix. | ||
|
|
||
| Your backend then delivers each token to the appropriate client (publisher or viewer), which uses it to connect to the relay. | ||
|
|
||
| See the [MoQ Streaming tutorial](../tutorials/moq) for working code examples of both approaches. | ||
|
|
||
| ## See also | ||
|
|
||
| - [MoQ Streaming tutorial](../tutorials/moq) — step-by-step guide to publishing and subscribing | ||
| - [What is the Sandbox API?](./sandbox-api-concept) — when and why to use the Sandbox API | ||
| - [Security & Token Model](./security-tokens) — broader overview of Fishjam's token system | ||
| - [Livestreams](./livestreams) — WebRTC-based livestreaming with WHIP/WHEP | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: could I, in this case, publish at
stream-name/alice/foo/bar?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes