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