ReactJS Compact Call Tutorial
Build a compact browser call with mediasfu-reactjs@4.3.0. The supplied room
gives your first version a join screen, participant list, microphone, camera,
screen sharing, remote-media rendering, leave, and host end controls.
Prerequisites
- Node.js 18–20 and a React 18 application.
- HTTPS or
localhostfor microphone, camera, and display capture. - Two browser profiles for the final call test.
- An authenticated application backend with
/api/mediasfu/create-roomand/api/mediasfu/join-roomroutes.
Project files
{
"private": true,
"scripts": { "dev": "vite" },
"dependencies": { "react": "18.2.0", "react-dom": "18.2.0", "mediasfu-reactjs": "4.3.0" },
"devDependencies": { "@types/react": "18.3.24", "@types/react-dom": "18.3.7", "@vitejs/plugin-react": "4.3.4", "typescript": "5.9.3", "vite": "5.4.19" }
}
import type { CreateRoomOnMediaSFUType, JoinRoomOnMediaSFUType } from 'mediasfu-reactjs';
async function postRoom<T>(url: string, payload: unknown): Promise<T> {
const response = await fetch(url, {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload),
});
if (!response.ok) throw new Error(`Room request failed (HTTP ${response.status}).`);
return response.json() as Promise<T>;
}
export const createRoomViaBackend: CreateRoomOnMediaSFUType = ({ payload }) =>
postRoom('/api/mediasfu/create-room', payload);
export const joinRoomViaBackend: JoinRoomOnMediaSFUType = ({ payload }) =>
postRoom('/api/mediasfu/join-room', payload);
import { useState } from 'react';
import { ModernMediasfuGeneric, type Participant } from 'mediasfu-reactjs';
import { createRoomViaBackend, joinRoomViaBackend } from './room-gateway';
type RoomSnapshot = {
participants?: Participant[]; audioAlreadyOn?: boolean;
videoAlreadyOn?: boolean; screenAlreadyOn?: boolean;
};
export default function App() {
const [room, setRoom] = useState<RoomSnapshot>({});
return <main>
<ModernMediasfuGeneric
connectMediaSFU={true}
createMediaSFURoom={createRoomViaBackend}
joinMediaSFURoom={joinRoomViaBackend}
sourceParameters={room}
updateSourceParameters={setRoom}
/>
<output aria-live="polite">Participants: {room.participants?.length ?? 0}; microphone: {room.audioAlreadyOn ? 'on' : 'off'}; camera: {room.videoAlreadyOn ? 'on' : 'off'}; screen: {room.screenAlreadyOn ? 'on' : 'off'}</output>
</main>;
}
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')!).render(<StrictMode><App /></StrictMode>);
<!doctype html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>MediaSFU compact call</title></head><body><div id="root"></div><script type="module" src="/src/main.tsx"></script></body></html>
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({ plugins: [react()], server: { proxy: { '/api': 'http://localhost:8787' } } });
{"compilerOptions":{"target":"ES2022","lib":["DOM","DOM.Iterable","ES2022"],"strict":true,"module":"ESNext","moduleResolution":"Bundler","skipLibCheck":true,"isolatedModules":true,"noEmit":true,"jsx":"react-jsx"},"include":["src"]}
For isolated local development, the backend may use a restricted, revocable development credential that is never committed. Before distributing the app, opening it to external testers, or using production rooms, keep reusable MediaSFU Cloud credentials only in the authenticated backend.
Room-authority server
Create the two /api/mediasfu routes with the complete Node + Express example
in the public production room credential boundary.
It keeps MEDIASFU_API_USERNAME and MEDIASFU_API_KEY in server environment
variables and returns only a room result to this browser. For a local test,
replace the route's authentication middleware with your own temporary,
uncommitted app-session check; before external testing or release, use your
application's normal session or JWT middleware. Do not put either MediaSFU
variable in browser configuration.
Run
npm install
npm run dev
Expected result
Create or join with two browser profiles. Both people appear in the participant list. Turn on microphone and camera, confirm the other profile receives them, then share and stop a screen. The supplied End control gives an authorized host the room-wide finish flow; the Leave control removes only that participant.
Recovery
- For 401 or 403, ask the person to sign in or request the correct room role.
- For 429 or 5xx, keep the person out of the call and offer a deliberate retry.
- For a denied device or display prompt, leave the person connected and let them retry the specific control after changing browser permission.
- If remote audio is silent, check the receiving browser's autoplay/audio state before reconnecting the room.
Cleanup
Use Leave for a participant and End only for the host's room-wide action. On navigation away from an app-owned wrapper, discard any app-held room snapshot. The backend must expire temporary room authority and apply its room-cleanup policy.