Livestreaming
This guide shows you how to implement livestreaming in React Native using Fishjam. You'll learn how to broadcast video streams and receive them in your app.
📋 Prerequisites
Before you begin, ensure you have:
- React Native project set up with Expo or bare React Native
- Fishjam SDK installed:
npm install @fishjam-cloud/react-native-client
- A room of
livestream
type created in Fishjam - Camera and microphone permissions configured
See the livestreaming overview for more information about Fishjam's livestreaming in general.
🏗️ Architecture Overview
Fishjam livestreaming follows a one-to-many broadcasting model:
Key characteristics:
- One streamer per room (broadcasts video/audio)
- Unlimited viewers per room (receive the stream)
- Single video track and single audio track per stream
📱 Broadcasting a Stream
Prerequisites
Before you start broadcasting, you need:
- Camera and microphone permissions configured
- A room of
livestream
type - A peer token for that room
Setup Steps
- Join the room using the connection guide
- Start streaming following the streaming guide
Livestreaming rooms accept only one video track and one audio track. Fishjam ignores additional tracks to maintain broadcast quality.
📺 Receiving a Stream
Prerequisites
To receive a stream, you need:
- An active streamer in a livestream room
- A viewer token for that room
Setup Steps
- Use the
useLivestream
hook to connect to the stream - Pass the viewer token to the
connect
method - Display the stream using the
LivestreamView
component
The LivestreamView
automatically renders the video once the connection establishes.
🖼️ Picture-in-Picture Support
The LivestreamView
component supports Picture-in-Picture (PiP) mode. Users can continue watching the stream while using other apps.
Enable PiP Support
- iOS
- Android
Add this configuration to your app.json
:
{
"expo": {
"ios": {
"supportsPictureInPicture": true
}
}
}
Add this configuration to your app.json
:
{
"expo": {
"android": {
"supportsPictureInPicture": true
}
}
}
Configure PiP Behavior
The LivestreamView
component accepts these PiP-related props:
Prop | Type | Description |
---|---|---|
pipEnabled | boolean | Enables Picture-in-Picture mode |
autoStartPip | boolean | Starts PiP automatically when app backgrounds |
autoStopPip | boolean | Stops PiP automatically when app foregrounds (iOS only) |
pipSize | object | Sets PiP window size with width and height properties |
Android only supports PiP when the app is in the background. The autoStopPip
prop is always enabled on Android.
Basic PiP Usage
<LivestreamView
pipEnabled
autoStartPip
autoStopPip
pipSize={{ width: 1920, height: 1080 }}
/>
Control PiP Programmatically
You can control PiP behavior using the component ref:
import { useRef } from "react";
import {
LivestreamView,
LivestreamViewRef,
} from "@fishjam-cloud/react-native-client";
function LivestreamPlayer() {
const livestreamRef = useRef<LivestreamViewRef>(null);
// Control PiP programmatically
livestreamRef.current?.startPip();
livestreamRef.current?.stopPip();
livestreamRef.current?.togglePip();
return (
<LivestreamView
ref={livestreamRef}
style={{ width: "90%", aspectRatio: 1, backgroundColor: "black" }}
/>
);
}
Platform Differences
Platform | Foreground Support | Background Support | Auto-stop Behavior |
---|---|---|---|
iOS | ✅ Yes | ✅ Yes | Controlled by autoStopPip prop |
Android | ❌ No | ✅ Yes | Always enabled |
💻 Complete Examples
These examples show complete implementations for both streaming and viewing.
This guide assumes you completed either the Quick Setup or Installation guide.
Required Permissions
Configure permissions before livestreaming
Your app needs to have permissions configured in order to use the microphone and camera.
Android
Permissions below are required to stream audio and video with Fishjam on Android.
android.permission.CAMERA
android.permission.RECORD_AUDIO
android.permission.MODIFY_AUDIO_SETTINGS
- Expo
- Bare workflow
Add required permissions to the app.json
file.
{
"expo": {
...
"android": {
...
"permissions": [
"android.permission.CAMERA",
"android.permission.RECORD_AUDIO",
"android.permission.MODIFY_AUDIO_SETTINGS"
]
}
}
}
Add required permissions to the AndroidManifest.xml
file.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTING"/>
...
</manifest>
iOS
- Expo
- Bare workflow
You don't have to make any changes to run app on iOS.
To update default content of permission alert, you can add these settings to app.json
:
{
"expo": {
...
"ios": {
...
"infoPlist": {
"NSCameraUsageDescription": "Allow $(PRODUCT_NAME) to access your camera.",
"NSMicrophoneUsageDescription": "Allow $(PRODUCT_NAME) to access your microphone."
}
},
}
}
Ensure Info.plist
contains camera and microphone usage description entries:
<key>NSCameraUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your camera.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your microphone.</string>
Streamer Example
This component automatically starts streaming when mounted:
import { useEffect } from "react";
import {
useCamera,
useConnection,
VideoPreviewView,
} from "@fishjam-cloud/react-native-client";
const ROOM_MANAGER_ID = "";
export default function Streamer() {
const { prepareCamera } = useCamera();
const { joinRoom, leaveRoom } = useConnection();
useEffect(() => {
const startStreaming = async () => {
console.log("Getting room details...");
const { url, peerToken } = await getRoomDetails(
"TestRoom",
"StreamerUserName",
);
console.log("Preparing camera...");
await prepareCamera({ cameraEnabled: true });
await joinRoom({ url, peerToken });
console.log("Streaming...");
};
startStreaming();
return () => {
leaveRoom();
};
}, [prepareCamera, joinRoom, leaveRoom]);
return (
<VideoPreviewView
style={{ width: "90%", aspectRatio: 1, alignSelf: "center" }}
/>
);
}
async function getRoomDetails(roomName: string, peerName: string) {
// This will work ONLY for the Sandbox App
const response = await fetch(
`https://fishjam.io/api/v1/connect/${ROOM_MANAGER_ID}/room-manager/?roomName=${roomName}&peerName=${peerName}&roomType=livestream`,
);
return response.json();
}
Viewer Example
This component receives and displays a livestream:
import { useCallback, useEffect } from "react";
import { View } from "react-native";
import {
useLivestream,
LivestreamView,
} from "@fishjam-cloud/react-native-client";
const ROOM_MANAGER_ID = "";
export function Receiver() {
const { connect, disconnect } = useLivestream();
const handleConnect = useCallback(async () => {
try {
console.log("Getting stream details...");
const { token } = await getViewerToken("TestRoom");
await connect(`https://fishjam.io/api/v1/live`, token);
console.log("Receiving...");
} catch (err) {
console.log(err);
}
}, [connect]);
useEffect(() => {
handleConnect();
return () => {
disconnect();
};
}, [handleConnect, disconnect]);
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<LivestreamView
style={{ width: "90%", aspectRatio: 1, backgroundColor: "black" }}
/>
</View>
);
}
async function getViewerToken(roomName: string) {
// This will work ONLY for the Sandbox App
// see https://fishjam.io/docs/livestreaming
const response = await fetch(
`https://fishjam.io/api/v1/connect/${ROOM_MANAGER_ID}/room-manager/${roomName}/broadcast-viewer-token`,
);
return response.json();
}
🚨 Troubleshooting
Common Issues
Stream not appearing for viewers:
- Verify the streamer successfully joined the room. If no one is streaming, there is nothing to join to.
- Ensure the room type is
livestream
, when streaming.
PiP not working:
- Confirm PiP support is enabled in
app.json
- Check that
pipEnabled
prop is set totrue
📚 Additional Resources
- Livestreaming Overview - Learn about Fishjam's livestreaming architecture
- Connection Guide - Detailed connection setup
- Streaming Guide - Advanced streaming configuration
- API Reference - Complete API documentation