React Native Publishing
This tutorial explains how to publish a live stream from a React Native mobile app using Media over QUIC (MoQ) with Fishjam. To watch a stream instead, see React Native Subscribing.
It uses react-native-moq — React Native bindings for MoQKit, with a small, reactive hooks-based API. For the web equivalent, see Web Publishing.
If you're new to MoQ, then we recommend getting familiar with the MoQ with Fishjam explanation.
Requirements
react-native-moq targets the React Native New Architecture (Fabric / TurboModules):
- iOS 16+
- Android API 30+
Installation
- npm
- Yarn
- pnpm
- Bun
npm install react-native-moq
yarn add react-native-moq
pnpm add react-native-moq
bun add react-native-moq
Then install the iOS pods:
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.
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, 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.
Obtaining a publisher connection URL
For more on what the Sandbox API is and its limitations, see What is the Sandbox API?.
To obtain a MoQ connection URL you'll need your Sandbox API URL. If you don't have it already, see Sandbox API URL.
- React Native
- TS
The useSandbox hook from @fishjam-cloud/react-native-client wraps the Sandbox API request for you:
import {useSandbox } from "@fishjam-cloud/react-native-client"; constPUBLISHER_PATH = "stream-alice"; constSANDBOX_API_URL = "YOUR_SANDBOX_API_URL"; // Inside a React component: const {getSandboxMoqPublisherAccess } =useSandbox ({sandboxApiUrl :SANDBOX_API_URL , }); // Request a publisher connection URL scoped to the publisher path const {connection_url :publishUrl } = awaitgetSandboxMoqPublisherAccess (PUBLISHER_PATH );
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:
constPUBLISHER_PATH = "stream-alice"; constSANDBOX_API_URL = "YOUR_SANDBOX_API_URL"; constresponse = awaitfetch ( `${SANDBOX_API_URL }/moq/${PUBLISHER_PATH }/publisher`, ); const {connection_url :publishUrl } = awaitresponse .json ();
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.
Open the session with the publisher connection URL, capture the camera and microphone, and publish the tracks:
import {useEffect } from "react"; import {Button ,PermissionsAndroid ,Platform } from "react-native"; import {PublisherView ,useCamera ,useMicrophone ,usePublisher ,useSession , } from "react-native-moq"; constPUBLISHER_PATH = "stream-alice"; constpublishUrl = ""; // from the step above functionPublishScreen () { // Open the MoQ session against the Fishjam relay using the publisher connection URL constsession =useSession (publishUrl , (s ) =>s .connect ()); constcamera =useCamera ({position : "front" }); constmicrophone =useMicrophone (); constpublisher =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 , ]); } }, []); constisPublishing =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 (); elsepublisher .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.
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 gets you publishing quickly. In production, your backend generates tokens with proper authorization, so you control who can publish.
A publisher connection URL grants write access to a specific path. Generate one on your backend and deliver it to the broadcasting client:
- TypeScript
- Python
import {FishjamClient } from '@fishjam-cloud/js-server-sdk'; constfishjamClient = newFishjamClient ({fishjamId ,managementToken , }); conststreamPath = 'stream-alice'; // Generate a connection URL that allows publishing to 'stream-alice' const {connection_url :publishUrl } = awaitfishjamClient .createMoqAccess ({publishPath :streamPath , });
from fishjam import FishjamClient fishjam_client = FishjamClient( fishjam_id=fishjam_id, management_token=management_token, ) stream_path = 'stream-alice' # Generate a connection URL that allows publishing to 'stream-alice' publish_url = fishjam_client.create_moq_access(publish_path=stream_path).connection_url
Deliver this connection URL to the mobile client, then use it to connect as described in Connecting and publishing.
See also
- React Native Subscribing — watch a MoQ stream on mobile
- Web Publishing — publish from the browser instead
- MoQ with Fishjam — how MoQ works in Fishjam
- Livestreaming — the WebRTC (WHIP/WHEP) approach