Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
---
type: explanation
sidebar_position: 6
title: Media over QUIC (MoQ)
sidebar_position: 9
title: Media over QUIC in Fishjam
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
# Media over QUIC in Fishjam

_How Media over QUIC (MoQ) works in Fishjam_

:::warning[MoQ is a standalone feature]
MoQ runs as a **separate delivery path** in Fishjam — it handles publishing and subscribing to a live broadcast over the MoQ relay, and the rest of Fishjam's feature set does not apply to a MoQ stream. In particular, a MoQ broadcast is not part of a WebRTC [room](./rooms), so you cannot use features like [Agents](../tutorials/agents), [data channels](./data-channels), and others that are part of the Fishjam WebRTC ecosystem.

What MoQ supports is covered end to end in the [MoQ tutorials](../tutorials/moq/web-publishing).
:::

## 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**.
Expand Down Expand Up @@ -129,11 +135,11 @@ The SDK's `createMoqToken` method accepts either a `publishPath` or a `subscribe

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 the [Web Publishing](../tutorials/moq/web-publishing) and [Web Subscribing](../tutorials/moq/web-subscribing) tutorials (or their [React Native](../tutorials/moq/react-native-publishing) [counterparts](../tutorials/moq/react-native-subscribing)) for working code examples.

## See also

- [MoQ Streaming tutorial](../tutorials/moq) — step-by-step guide to publishing and subscribing
- [Web Publishing](../tutorials/moq/web-publishing) / [Web Subscribing](../tutorials/moq/web-subscribing) — step-by-step guides 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
9 changes: 9 additions & 0 deletions docs/tutorials/moq/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"label": "Media over QUIC (MoQ)",
"position": 5,
"collapsed": false,
"link": {
"type": "doc",
"id": "tutorials/moq/index"
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: i'd add an index page to moq (as in the web&mobile example) and also add the "limitations" note there. right now clicking on "MoQ" only opens the subcategory in the sidebar, this way it would also display the note

Image

17 changes: 17 additions & 0 deletions docs/tutorials/moq/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
type: tutorial
title: Media over QUIC (MoQ)
description: Step-by-step guides for publishing and subscribing to live broadcasts over Media over QUIC (MoQ) with Fishjam, on both web and React Native.
---

import DocCardList from "@theme/DocCardList";

# Media over QUIC (MoQ)

Step-by-step guides for publishing and subscribing to live broadcasts over [Media over QUIC (MoQ)](https://datatracker.ietf.org/wg/moq/about/) with Fishjam. For the big-picture overview of how MoQ fits into Fishjam, see [Media over QUIC in Fishjam](../../explanation/moq-with-fishjam.mdx).

:::warning[MoQ is a standalone feature]
MoQ runs as a **separate delivery path** in Fishjam — it handles publishing and subscribing to a live broadcast over the MoQ relay, and the rest of Fishjam's feature set does not apply to a MoQ stream. In particular, a MoQ broadcast is not part of a WebRTC [room](../../explanation/rooms.mdx), so you cannot use features like [Agents](../agents.mdx), [data channels](../../explanation/data-channels.mdx), and others that are part of the Fishjam WebRTC ecosystem.
:::

<DocCardList />
230 changes: 230 additions & 0 deletions docs/tutorials/moq/react-native-publishing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
---
type: tutorial
sidebar_position: 3
title: React Native Publishing
description: Publish a live video and audio broadcast over Media over QUIC (MoQ) from a React Native mobile app with Fishjam, from sandbox prototyping to production.
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

# React Native Publishing

This tutorial explains how to **publish** a live stream from a **React Native mobile app** using [Media over QUIC (MoQ)](https://datatracker.ietf.org/wg/moq/about/) with Fishjam. To watch a stream instead, see [React Native Subscribing](./react-native-subscribing).

It uses [`react-native-moq`](https://github.com/software-mansion-labs/react-native-moq) — React Native bindings for MoQKit, with a small, reactive hooks-based API. For the web equivalent, see [Web Publishing](./web-publishing).

:::info
If you're new to MoQ, then we recommend getting familiar with the [MoQ with Fishjam](../../explanation/moq-with-fishjam) explanation.
:::

## Requirements

`react-native-moq` targets the React Native **New Architecture** (Fabric / TurboModules):

- iOS 16+
- Android API 30+

## Installation

```bash npm2yarn
npm install react-native-moq
```

Then install the iOS pods:

```sh
cd ios && pod install
```

Publishing captures the camera and microphone, so the host app is responsible for runtime permissions: request `CAMERA` / `RECORD_AUDIO` on Android, and add `NSCameraUsageDescription` / `NSMicrophoneUsageDescription` to `Info.plist` on iOS. The library does not request these for you.

:::tip
MoQ is a protocol with a well-defined negotiation, so a publisher and a subscriber don't need to use the same client library. A React Native app published with `react-native-moq` can be watched in the browser with [`@moq/watch`](./web-subscribing#connecting-and-subscribing), and vice versa.
:::

## Quickstart with the Sandbox API

If you don't have a backend server set up, you can prototype publishing using the [Sandbox API](../../explanation/sandbox-api-concept).

### Obtaining a publisher token

For more on what the Sandbox API is and its limitations, see [What is the Sandbox API?](../../explanation/sandbox-api-concept).

:::info
To obtain a MoQ token you'll need your Fishjam ID and Sandbox API URL. If you don't have them already, see [Sandbox API URL and Fishjam ID](../../explanation/sandbox-api-concept#sandbox-api-url-and-fishjam-id).
Comment thread
Karolk99 marked this conversation as resolved.
:::

<Tabs groupId="sandbox-token">
<TabItem value="react" label="React Native">

The `useSandbox` hook from `@fishjam-cloud/react-native-client` wraps the Sandbox API request for you:

```tsx
// @noErrors
import { useSandbox } from "@fishjam-cloud/react-native-client";

const FISHJAM_ID = "YOUR_FISHJAM_ID";
const PUBLISHER_PATH = "stream-alice";
const SANDBOX_API_URL = "YOUR_SANDBOX_API_URL";

// Inside a React component:
const { getSandboxMoqPublisherToken } = useSandbox({
sandboxApiUrl: SANDBOX_API_URL,
});

// Request a publisher token scoped to the publisher path
const publishToken = await getSandboxMoqPublisherToken(PUBLISHER_PATH);
```

</TabItem>

<TabItem value="ts" label="TS">

If you don't want to pull in the whole client library just for the `useSandbox` hook, you can call the Sandbox API directly with `fetch`:

```ts
// @noErrors
const FISHJAM_ID = "YOUR_FISHJAM_ID";
const PUBLISHER_PATH = "stream-alice";
const SANDBOX_API_URL = "YOUR_SANDBOX_API_URL";

const response = await fetch(
`${SANDBOX_API_URL}/moq/${PUBLISHER_PATH}/publisher`,
);
const { token: publishToken } = await response.json();
```

</TabItem>
</Tabs>

### Connecting and publishing

`react-native-moq` is hooks-based. You open a session against the relay, capture the camera and microphone, and hand them to a publisher.

Build the relay URL using the publisher token, open the session, and publish the camera + microphone tracks:

```tsx
// @noErrors
import { useEffect } from "react";
import { Button, PermissionsAndroid, Platform } from "react-native";
import {
PublisherView,
useCamera,
useMicrophone,
usePublisher,
useSession,
} from "react-native-moq";

const FISHJAM_ID = "YOUR_FISHJAM_ID";
const PUBLISHER_PATH = "stream-alice";
const publishToken = ""; // from the step above

// Build the relay URL using the publisher token
const relayUrl = `https://relay.fishjam.io/${FISHJAM_ID}?jwt=${publishToken}`;

function PublishScreen() {
// Open the MoQ session against the Fishjam relay
const session = useSession(relayUrl, (s) => s.connect());
const camera = useCamera({ position: "front" });
const microphone = useMicrophone();
const publisher = usePublisher(session);

// Request camera + mic permissions on Android (iOS handles it automatically when Info.plist is configured)
useEffect(() => {
if (Platform.OS === "android") {
PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.CAMERA,
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
]);
}
}, []);

const isPublishing = publisher.state === "publishing";

return (
<>
<PublisherView camera={camera} style={{ aspectRatio: 9 / 16 }} />
<Button title="Flip" onPress={camera.flip} />
<Button
title={isPublishing ? "Stop" : "Publish"}
disabled={session.state !== "connected" && !isPublishing}
onPress={() => {
if (isPublishing) publisher.stop();
else
publisher.publish({
path: PUBLISHER_PATH,
tracks: [camera, microphone],
});
}}
/>
</>
);
}
```

Once `publisher.state === "publishing"`, the stream is live on the MoQ relay! Viewers can start watching by following [React Native Subscribing](./react-native-subscribing).

:::info
**Why a separate `useSession` and `publish()`?** \
The session owns the connection to the relay; the publisher reuses it. Because publishing rides on top of an open session, the same connection can subscribe and publish at once — pair `usePublisher(session)` with `useBroadcasts(session, prefix)` to do both.
:::

## Production with Server SDKs

The [Quickstart](#quickstart-with-the-sandbox-api) gets you publishing quickly. In production, your backend generates tokens with proper authorization, so you control who can publish.

A **publisher token** grants write access to a specific path. Generate one on your backend and deliver it to the broadcasting client:

<Tabs groupId="language">
<TabItem value="ts" label="TypeScript">

```ts
const fishjamId = '';
const managementToken = '';

// ---cut---
import { FishjamClient } from '@fishjam-cloud/js-server-sdk';

const fishjamClient = new FishjamClient({
fishjamId,
managementToken,
});

const streamPath = 'stream-alice';

// Generate a token that allows publishing to 'stream-alice'
const { token: publishToken } = await fishjamClient.createMoqToken({
publishPath: streamPath,
});
```

</TabItem>

<TabItem value="python" label="Python">

```python
from fishjam import FishjamClient

fishjam_client = FishjamClient(
fishjam_id=fishjam_id,
management_token=management_token,
)

stream_path = 'stream-alice'

# Generate a token that allows publishing to 'stream-alice'
publish_token = fishjam_client.create_moq_token(publish_path=stream_path)
```

</TabItem>
</Tabs>

Deliver this token to the mobile client, then use it to build the relay URL and connect as described in [Connecting and publishing](#connecting-and-publishing).

## See also

- [React Native Subscribing](./react-native-subscribing) — watch a MoQ stream on mobile
- [Web Publishing](./web-publishing) — publish from the browser instead
- [MoQ with Fishjam](../../explanation/moq-with-fishjam) — how MoQ works in Fishjam
- [Livestreaming](../../tutorials/livestreaming) — the WebRTC (WHIP/WHEP) approach
Loading
Loading