Skip to main content

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 localhost for microphone, camera, and display capture.
  • Two browser profiles for the final call test.
  • An authenticated application backend with /api/mediasfu/create-room and /api/mediasfu/join-room routes.

Project files

package.json
{
"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" }
}
src/room-gateway.ts
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);
src/App.tsx
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>;
}
src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')!).render(<StrictMode><App /></StrictMode>);
index.html
<!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>
vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({ plugins: [react()], server: { proxy: { '/api': 'http://localhost:8787' } } });
tsconfig.json
{"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.