Skip to main content

React Native Complete Starter App

This complete starter uses mediasfu-reactnative@2.4.0 to open a compact call, host dashboard, guest queue, classroom, or remote-podcast room. It displays the supplied MediaSFU room interface and asks your signed-in backend to create and join rooms.

Before you start

Install Node.js 22.11 or newer, Android Studio or Xcode, and React Native 0.86.2. Create the app first, then replace the files below. The temporary token field accepts an application session token only; a reusable MediaSFU credential stays on the authenticated backend.

The backend must accept authenticated POST requests at /api/mediasfu/create-room and /api/mediasfu/join-room, apply your room and role rules, and return the normal MediaSFU create-or-join response.

Complete example

package.json
{
"name": "my-mediasfu-app",
"private": true,
"version": "1.0.0",
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"check": "tsc --noEmit"
},
"dependencies": {
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-clipboard/clipboard": "1.14.3",
"@react-native-community/slider": "5.2.0",
"@react-native-picker/picker": "2.7.5",
"@react-navigation/native": "6.1.18",
"@react-navigation/native-stack": "6.11.0",
"mediasfu-reactnative": "2.4.0",
"mediasoup-client": "3.20.0",
"react": "19.2.3",
"react-native": "0.86.2",
"react-native-gesture-handler": "2.32.0",
"react-native-image-picker": "7.1.0",
"react-native-permissions": "5.0.2",
"react-native-picker-select": "9.0.0",
"react-native-reanimated": "4.5.3",
"react-native-safe-area-context": "5.7.0",
"react-native-screens": "4.26.0",
"react-native-sound": "0.11.2",
"react-native-status-bar-height": "2.6.0",
"react-native-vector-icons": "10.2.0",
"react-native-video": "6.7.0",
"react-native-webrtc": "124.0.8",
"react-native-webrtc-web-shim": "1.0.7",
"react-native-worklets": "0.11.3",
"reanimated-color-picker": "4.2.0",
"socket.io-client": "4.8.0"
},
"devDependencies": {
"@react-native-community/cli": "20.1.0",
"@react-native-community/cli-platform-android": "20.1.0",
"@react-native-community/cli-platform-ios": "20.1.0",
"@react-native/typescript-config": "0.86.2",
"@types/react": "19.2.0",
"typescript": "5.9.3"
}
}
tsconfig.json
{
"extends": "@react-native/typescript-config/tsconfig.json"
}
room-backend.ts
import type { CreateRoomOnMediaSFUType, JoinRoomOnMediaSFUType } from 'mediasfu-reactnative';

type Result = Awaited<ReturnType<CreateRoomOnMediaSFUType>>;
export type AppFetch = (url: string, init: { method: 'POST'; headers: { 'Content-Type': 'application/json'; Authorization: string }; body: string }) => Promise<{ ok: boolean; status: number; json(): Promise<unknown> }>;

function endpoint(baseUrl: string, path: string) {
const base = new URL(baseUrl);
if (base.protocol !== 'https:' || base.username || base.password || base.search || base.hash) throw new TypeError('Use an absolute HTTPS backend URL without embedded credentials');
return new URL(path, base.toString().endsWith('/') ? base : `${base}/`).toString();
}

async function post(fetchApp: AppFetch, url: string, appToken: string, payload: unknown): Promise<Result> {
if (!appToken.trim()) throw new Error('Sign in before creating or joining a room.');
const response = await fetchApp(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${appToken}` }, body: JSON.stringify(payload) });
if (!response.ok) throw new Error(`Room request failed (${response.status})`);
return await response.json() as Result;
}

export function createRoomBackend(fetchApp: AppFetch, baseUrl: string, appToken: string) {
return {
createMediaSFURoom: (async ({ payload }) => post(fetchApp, endpoint(baseUrl, 'api/mediasfu/create-room'), appToken, payload)) as CreateRoomOnMediaSFUType,
joinMediaSFURoom: (async ({ payload }) => post(fetchApp, endpoint(baseUrl, 'api/mediasfu/join-room'), appToken, payload)) as JoinRoomOnMediaSFUType,
};
}
CompactCall.tsx
import { useMemo, useState } from 'react';
import { ModernMediasfuGeneric, type MediasfuGenericOptions } from 'mediasfu-reactnative';
import { createRoomBackend, type AppFetch } from './room-backend';

export function CompactCall({ backendBaseUrl, appToken, fetchApp }: { backendBaseUrl: string; appToken: string; fetchApp: AppFetch }) {
const [parameters, setParameters] = useState<Record<string, unknown>>({});
const room = useMemo(() => createRoomBackend(fetchApp, backendBaseUrl, appToken), [appToken, backendBaseUrl, fetchApp]);
const updateSourceParameters: NonNullable<MediasfuGenericOptions['updateSourceParameters']> = setParameters;
return <ModernMediasfuGeneric connectMediaSFU returnUI sourceParameters={parameters} updateSourceParameters={updateSourceParameters} {...room} />;
}
ScenarioRooms.tsx
import { useMemo, useState } from 'react';
import { MediasfuConference, MediasfuWebinar, type MediasfuConferenceOptions, type MediasfuWebinarOptions } from 'mediasfu-reactnative';
import { createRoomBackend, type AppFetch } from './room-backend';

type RoomProps = { backendBaseUrl: string; appToken: string; fetchApp: AppFetch };

function useRoom(props: RoomProps) {
const [sourceParameters, updateSourceParameters] = useState<Record<string, unknown>>({});
const gateway = useMemo(() => createRoomBackend(props.fetchApp, props.backendBaseUrl, props.appToken), [props.appToken, props.backendBaseUrl, props.fetchApp]);
return { sourceParameters, updateSourceParameters, ...gateway };
}

export function HostDashboard(props: RoomProps) {
const room: MediasfuConferenceOptions = { connectMediaSFU: true, returnUI: true, ...useRoom(props) };
return <MediasfuConference {...room} />;
}

export function LiveGuestQueue(props: RoomProps) {
const room: MediasfuWebinarOptions = { connectMediaSFU: true, returnUI: true, ...useRoom(props) };
return <MediasfuWebinar {...room} />;
}

export function ClassroomWorkshop(props: RoomProps) {
const room: MediasfuConferenceOptions = { connectMediaSFU: true, returnUI: true, ...useRoom(props) };
return <MediasfuConference {...room} />;
}

export function RemotePodcast(props: RoomProps) {
const room: MediasfuConferenceOptions = { connectMediaSFU: true, returnUI: true, ...useRoom(props) };
return <MediasfuConference {...room} />;
}
App.tsx
import { useState } from 'react';
import { Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
import { CompactCall } from './CompactCall';
import { ClassroomWorkshop, HostDashboard, LiveGuestQueue, RemotePodcast } from './ScenarioRooms';

type TutorialScenario = 'compact-call' | 'host-dashboard' | 'live-guest-queue' | 'classroom-workshop' | 'remote-podcast';

const labels: Record<TutorialScenario, string> = {
'compact-call': 'Compact call',
'host-dashboard': 'Host dashboard',
'live-guest-queue': 'Live guest queue',
'classroom-workshop': 'Classroom workshop',
'remote-podcast': 'Remote podcast',
};

type Config = { backendBaseUrl: string; appToken: string; fetchApp: typeof fetch };

function Room({ scenario, config }: { scenario: TutorialScenario; config: Config }) {
if (scenario === 'compact-call') return <CompactCall {...config} />;
if (scenario === 'live-guest-queue') return <LiveGuestQueue {...config} />;
if (scenario === 'host-dashboard') return <HostDashboard {...config} />;
if (scenario === 'classroom-workshop') return <ClassroomWorkshop {...config} />;
return <RemotePodcast {...config} />;
}

export default function App() {
const [scenario, setScenario] = useState<TutorialScenario>('compact-call');
const [backendBaseUrl, setBackendBaseUrl] = useState('');
const [appToken, setAppToken] = useState('');
const ready = backendBaseUrl.startsWith('https://') && appToken.trim().length > 0;
if (ready) return <Room scenario={scenario} config={{ backendBaseUrl, appToken, fetchApp: fetch }} />;

return <SafeAreaView style={styles.page}><ScrollView contentContainerStyle={styles.content}>
<Text style={styles.title}>MediaSFU mobile starter</Text>
<Text style={styles.copy}>Choose an outcome, then enter the HTTPS address of your signed-in app backend and its short-lived app session token. These are not MediaSFU Cloud credentials.</Text>
<View style={styles.choices}>{(Object.keys(labels) as TutorialScenario[]).map((value) => <Pressable key={value} onPress={() => setScenario(value)} style={[styles.choice, scenario === value && styles.selected]}><Text>{labels[value]}</Text></Pressable>)}</View>
<TextInput value={backendBaseUrl} onChangeText={setBackendBaseUrl} autoCapitalize="none" keyboardType="url" placeholder="https://your-app.example" style={styles.input} />
<TextInput value={appToken} onChangeText={setAppToken} autoCapitalize="none" autoCorrect={false} secureTextEntry placeholder="Short-lived app session token" style={styles.input} />
<Text style={styles.hint}>Replace this temporary app-token screen with your normal signed-in session before release. Do not put a MediaSFU API key in this app.</Text>
</ScrollView></SafeAreaView>;
}

const styles = StyleSheet.create({ page: { flex: 1 }, content: { gap: 12, padding: 20 }, title: { fontSize: 24, fontWeight: '700' }, copy: { lineHeight: 21 }, choices: { gap: 8 }, choice: { borderColor: '#9ca3af', borderWidth: 1, borderRadius: 8, padding: 12 }, selected: { borderColor: '#0284c7', backgroundColor: '#e0f2fe' }, input: { borderColor: '#9ca3af', borderWidth: 1, borderRadius: 8, padding: 12 }, hint: { color: '#374151', lineHeight: 20 } });

Run

npx @react-native-community/cli@20.1.0 init my-mediasfu-app --version 0.86.2
cd my-mediasfu-app
npm install
npm run android

Replace the generated App.tsx, package.json, and tsconfig.json, then add the three TypeScript files beside App.tsx. For iOS, run npm run ios on a Mac after the package installation.

What you should see

The first screen lets you choose an outcome and enter application authority. After a successful backend response, compact call opens the supplied generic room, guest queue opens the supplied webinar room, and the other choices open the supplied conference room.

Release checklist

  • Add camera and microphone permissions to the generated Android and iOS projects.
  • Replace the temporary app-token field with the app's normal signed-in session.
  • Test create, join, local media, remote audio, leave, and authorized room end on physical devices.