Ship production-ready voice, video, and AI communication in minutes — not months.
MediaSFU gives you prebuilt, fully-featured event room components with real-time video/audio, screen sharing, recording, chat, polls, whiteboards, real-time translation, and more. Drop them into your React app with a few lines of code and go live.
| Without MediaSFU | With MediaSFU |
|---|---|
| 6–12 months of WebRTC, TURN/STUN, codec work | npm install mediasfu-reactjs → done |
| $1–5 per 1,000 minutes (Twilio, Vonage, Daily) | $0.10 per 1,000 minutes — 10–50× cheaper |
| Build every modal, card, and control from scratch | Prebuilt components — classic & modern themes |
| Wire up third-party AI, TTS, vision APIs yourself | Built-in AI agents, voice translation, transcription |
| Separate codebases per platform | One API surface → React, React Native, Flutter, Angular, Vue |
| Weeks of infrastructure setup | Under 5 minutes |
| Embeddable by Design — Other platforms give you raw APIs and leave you to build the UI. MediaSFU gives you finished, production-ready components — video grids, control bars, modals, chat panels, recording controls — that you drop in and customize. | Pricing That Makes Sense — $0.10 per 1,000 minutes. MediaSFU runs its own media infrastructure. No reselling. No hidden fees. Free tier included, no credit card required. |
| AI-Native, Not Bolted On — AI agents, real-time voice translation (50+ languages), live transcription, and intelligent routing are built into the platform — not third-party add-ons you wire up yourself. | Full Stack, One Platform — Voice + video + chat + screen sharing + recording + polls + whiteboards + breakout rooms + AI + translation + SIP. All included. Replace 5 communication tools with one SDK. |
MediaSFU is a frontend SDK that requires a backend media server to function.
You have two options:
| Option | Description | Best For |
|---|---|---|
| ☁️ MediaSFU Cloud | Managed service at mediasfu.com | Production apps, zero infrastructure |
| 🏠 MediaSFU Open | Self-hosted open-source server | Full control, on-premise requirements |
# Option 1: Use MediaSFU Cloud
# Sign up at https://www.mediasfu.com and get your API credentials
# Option 2: Self-host with MediaSFU Open
git clone https://github.com/MediaSFU/MediaSFUOpen
cd MediaSFUOpen
📖 MediaSFU Cloud Documentation →
📖 MediaSFU Open Repository →
Everything you need for enterprise-grade real-time communication — included at $0.10/1K min:
Three steps. Under 5 minutes. First video call live.
1. Install
npm install mediasfu-reactjs
2. Import & Render
import { MediasfuGeneric } from 'mediasfu-reactjs';
function App() {
return (
<MediasfuGeneric
credentials={{ apiUserName: "yourUsername", apiKey: "yourAPIKey" }}
/>
);
}
3. Run
npm start
That's it. You have a fully-featured video conferencing room with screen sharing, chat, recording, and more.
ℹ️ Critical component styles are automatically injected at runtime. For additional styling options, see Optional CSS Import.
Want to try without a server? Use demo mode:
<MediasfuGeneric
useLocalUIMode={true}
useSeed={true}
seedData={{ member: "DemoUser", eventType: "conference" }}
/>
# npm
npm install mediasfu-reactjs
# yarn
yarn add mediasfu-reactjs
# pnpm
pnpm add mediasfu-reactjs
The following are required peer dependencies:
{
"react": "^18.2.0 || ^19.0.0",
"react-dom": "^18.2.0 || ^19.0.0",
"@fortawesome/fontawesome-svg-core": "^6.0.0",
"@fortawesome/free-solid-svg-icons": "^6.0.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"bootstrap": "^5.0.0",
"mediasoup-client": "^3.7.0",
"socket.io-client": "^4.0.0",
"universal-cookie": "^7.0.0"
}
For virtual background support (blur, image backgrounds):
npm install @mediapipe/selfie_segmentation@0.1.1675465747
This is optional — if not installed, virtual backgrounds simply won't be available.
Critical styles for control buttons, containers, and core components are automatically injected at runtime — no manual import needed.
If you need additional styling (e.g., custom modal themes, waiting room lists), you can optionally import the full stylesheet:
// Optional: Import for additional component styles
import 'mediasfu-reactjs/dist/main.css';
Most applications work perfectly without this import.
Choose the room type that fits your use case — or use MediasfuGeneric for maximum flexibility:
| Component | Use Case | Description |
|---|---|---|
MediasfuGeneric |
Universal | Supports all event types dynamically |
ModernMediasfuGeneric |
Universal (Premium) | Theme-aware, glassmorphism UI |
MediasfuConference |
Meetings | Multi-party video conferencing |
MediasfuWebinar |
Webinars | Presenters + audience model |
MediasfuBroadcast |
Broadcasting | One-to-many live streaming |
MediasfuChat |
Chat Rooms | Text-based with optional media |
All prebuilt components share the same props interface:
interface MediasfuProps {
// Authentication
credentials?: { apiUserName: string; apiKey: string };
// Connection
localLink?: string; // Self-hosted server URL
connectMediaSFU?: boolean; // Toggle auto-connection
// Customization
PrejoinPage?: (options) => ReactNode;
customVideoCard?: CustomVideoCardType;
customAudioCard?: CustomAudioCardType;
customMiniCard?: CustomMiniCardType;
uiOverrides?: MediasfuUICustomOverrides;
// Advanced
returnUI?: boolean; // Set false for headless mode
useLocalUIMode?: boolean; // Demo/local mode
seedData?: SeedData; // Pre-populate for demos
}
import { MediasfuConference } from 'mediasfu-reactjs';
function ConferenceApp() {
return (
<MediasfuConference
credentials={{
apiUserName: "yourUsername",
apiKey: "yourAPIKey"
}}
/>
);
}
import { MediasfuWebinar, PreJoinPage } from 'mediasfu-reactjs';
function WebinarApp() {
return (
<MediasfuWebinar
credentials={{ apiUserName: "user", apiKey: "key" }}
PrejoinPage={(options) => (
<PreJoinPage
{...options}
imgSrc="/your-logo.png"
/>
)}
containerStyle={{
background: "linear-gradient(135deg, #1a1a2e, #16213e)"
}}
/>
);
}
import {
MediasfuGeneric,
generateRandomParticipants,
generateRandomMessages
} from 'mediasfu-reactjs';
function DemoApp() {
return (
<MediasfuGeneric
useLocalUIMode={true}
useSeed={true}
seedData={{
member: "DemoUser",
participants: generateRandomParticipants({ count: 5 }),
messages: generateRandomMessages({ count: 10 }),
eventType: "conference"
}}
/>
);
}
import { MediasfuGeneric, VideoCard, CustomVideoCardType } from 'mediasfu-reactjs';
const customVideoCard: CustomVideoCardType = (props) => (
<VideoCard
{...props}
customStyle={{
border: "3px solid #4c1d95",
borderRadius: 20,
boxShadow: "0 10px 40px rgba(76, 29, 149, 0.4)"
}}
/>
);
function App() {
return (
<MediasfuGeneric
credentials={{ apiUserName: "user", apiKey: "key" }}
customVideoCard={customVideoCard}
/>
);
}
import { MediasfuGeneric } from 'mediasfu-reactjs';
import { useState, useCallback } from 'react';
function CustomApp() {
const [helpers, setHelpers] = useState<Record<string, unknown>>({});
const updateSourceParameters = useCallback((data: Record<string, unknown>) => {
setHelpers(data);
}, []);
return (
<>
<MediasfuGeneric
credentials={{ apiUserName: "user", apiKey: "key" }}
returnUI={false} // No default UI
noUIPreJoinOptions={{
action: "create",
capacity: 10,
eventType: "conference",
userName: "Host"
}}
sourceParameters={helpers}
updateSourceParameters={updateSourceParameters}
/>
{/* Use `helpers` to build your completely custom UI */}
{/* helpers.clickVideo(), helpers.clickAudio(), helpers.participants, etc. */}
</>
);
}
import {
MediasfuGeneric,
MainContainerComponent,
MediasfuUICustomOverrides
} from 'mediasfu-reactjs';
const overrides: MediasfuUICustomOverrides = {
// Override component rendering
mainContainer: {
render: (props) => (
<div style={{ border: "4px dashed purple", padding: 16 }}>
<MainContainerComponent {...props} />
</div>
)
},
// Wrap function behavior
consumerResume: {
wrap: (originalFn) => async (params) => {
console.log("Consumer resuming:", params);
return await originalFn(params);
}
}
};
function App() {
return (
<MediasfuGeneric
credentials={{ apiUserName: "user", apiKey: "key" }}
uiOverrides={overrides}
/>
);
}
| Component | Purpose |
|---|---|
MainContainerComponent |
Root container for meeting UI |
MainGridComponent |
Grid layout for video tiles |
FlexibleGrid |
Dynamic responsive grid |
VideoCard |
Individual video participant |
AudioCard |
Audio-only participant with waveform |
MiniCard |
Thumbnail participant card |
Pagination |
Navigate participant pages |
| Component | Purpose |
|---|---|
LoadingModal |
Loading overlay |
AlertComponent |
Toast notifications |
ParticipantsModal |
Participant list/management |
MessagesModal |
Chat interface |
RecordingModal |
Recording controls |
PollModal |
Create/vote on polls |
BackgroundModal |
Virtual backgrounds |
BreakoutRoomsModal |
Breakout room management |
ConfigureWhiteboardModal |
Whiteboard settings |
MediaSettingsModal |
Audio/video device selection |
import {
clickVideo, // Toggle video
clickAudio, // Toggle audio
clickScreenShare, // Toggle screen share
startRecording, // Start recording
stopRecording, // Stop recording
launchPoll, // Open poll modal
launchMessages, // Open chat modal
} from 'mediasfu-reactjs';
import {
connectSocket,
disconnectSocket,
joinRoomClient,
// Event handlers
personJoined,
meetingEnded,
receiveMessage,
} from 'mediasfu-reactjs';
The package uses CSS variables for theming:
:root {
--mediasfu-primary: #4c1d95;
--mediasfu-background: #1a1a2e;
--mediasfu-surface: #16213e;
--mediasfu-text: #ffffff;
}
// Custom video card with overlay
const customVideoCard: CustomVideoCardType = (props) => (
<div style={{ position: 'relative' }}>
<VideoCard {...props} />
<div className="custom-overlay">
<span>{props.name}</span>
<button onClick={() => props.onMute?.()}>Mute</button>
</div>
</div>
);
const overrides: MediasfuUICustomOverrides = {
// Layout components
mainContainer: { render: CustomMainContainer },
mainGrid: { render: CustomGrid },
// Modal components
menuModal: { component: CustomMenuModal },
participantsModal: { component: CustomParticipantsModal },
messagesModal: { component: CustomMessagesModal },
// Functions
consumerResume: { wrap: loggingWrapper },
addVideosGrid: { implementation: customGridLogic },
};
import type {
// Core types
Participant,
Stream,
Message,
CoHostResponsibility,
Poll,
// Event types
EventType, // 'conference' | 'webinar' | 'chat' | 'broadcast'
CreateMediaSFURoomOptions,
JoinMediaSFURoomOptions,
ResponseJoinRoom,
// UI types
MediasfuUICustomOverrides,
CustomVideoCardType,
CustomAudioCardType,
CustomMiniCardType,
// Socket types
ConnectSocketType,
ConnectLocalSocketType,
} from 'mediasfu-reactjs';
import {
// Room management
joinRoomOnMediaSFU,
createRoomOnMediaSFU,
checkLimitsAndMakeRequest,
// Demo utilities
generateRandomParticipants,
generateRandomMessages,
generateRandomPolls,
// State
initialValuesState,
// Helpers
formatNumber,
sleep,
checkPermission,
} from 'mediasfu-reactjs';
For self-hosted MediaSFU servers:
<MediasfuGeneric
localLink="https://your-mediasfu-server.com"
connectMediaSFU={false} // Don't connect to cloud
/>
<MediasfuGeneric
localLink="https://your-server.com"
connectMediaSFU={true} // Also connect to MediaSFU cloud
credentials={{ apiUserName: "user", apiKey: "key" }}
/>
For comprehensive documentation including:
📄 See README_DETAILED.md
ModernMediasfuGeneric is the most advanced, themed variant featuring:
import { ModernMediasfuGeneric } from 'mediasfu-reactjs';
function App() {
return (
<ModernMediasfuGeneric
credentials={{ apiUserName: "user", apiKey: "key" }}
containerStyle={{
background: "linear-gradient(135deg, #0f172a, #1e3a8a)",
minHeight: "100vh"
}}
/>
);
}
| Modern Component | Classic Equivalent | Features |
|---|---|---|
ModernVideoCard |
VideoCard |
Glass effect, animated borders |
ModernAudioCard |
AudioCard |
Gradient waveforms, glow effects |
ModernMiniCard |
MiniCard |
Sleek thumbnails with status |
ModernMenuModal |
MenuModal |
Slide animations, blur backdrop |
ModernMessagesModal |
MessagesModal |
Chat bubbles, typing indicators |
ModernRecordingModal |
RecordingModal |
Status animations, progress rings |
ModernParticipantsModal |
ParticipantsModal |
Search, filters, role badges |
ModernBackgroundModal |
BackgroundModal |
Image gallery, blur previews |
ModernPollModal |
PollModal |
Real-time voting, animations |
ModernBreakoutRoomsModal |
BreakoutRoomsModal |
Drag-and-drop, room previews |
ModernPanelistsModal |
PanelistsModal |
Panelist management for webinars |
ModernPermissionsModal |
PermissionsModal |
Per-participant permission control |
TranslationSettingsModal |
— | Real-time translation configuration |
In webinar mode, designate specific participants as panelists who can speak, while others remain audience members.
// Panelists are managed via sourceParameters
const { panelists, updatePanelists } = sourceParameters;
// Listen for panelist changes
// Events: panelistsUpdated, addedAsPanelist, removedFromPanelists, panelistFocusChanged
Control each participant's capabilities individually:
import { ModernPermissionsModal } from 'mediasfu-reactjs';
// Permission levels:
// "0" - Standard participant
// "1" - Elevated (co-host level)
// "2" - Host (full control)
// Configure per-participant capabilities:
// - Video on/off
// - Audio on/off
// - Screen sharing
// - Chat access
Enable participants to speak in their native language and listen in any language with live AI translation.
import { TranslationSettingsModal } from 'mediasfu-reactjs';
function TranslationExample({ sourceParameters }) {
const [showTranslation, setShowTranslation] = useState(false);
return (
<>
<button onClick={() => setShowTranslation(true)}>
🌐 Translation Settings
</button>
<TranslationSettingsModal
isVisible={showTranslation}
onClose={() => setShowTranslation(false)}
parameters={sourceParameters}
/>
</>
);
}
// Translation events available:
// - translation:roomConfig
// - translation:languageSet
// - translation:subscribed
// - translation:transcript
Features include:
When building custom UIs or using headless mode (returnUI={false}), you need both props:
| Prop | Purpose |
|---|---|
sourceParameters |
Initial state object (can be empty {}) |
updateSourceParameters |
Callback that receives the complete helper bundle |
The updateSourceParameters callback delivers a comprehensive object containing all room state, methods, and streams. This is your bridge to building completely custom UIs.
import { MediasfuGeneric } from 'mediasfu-reactjs';
import { useState, useCallback } from 'react';
function CustomUI() {
const [helpers, setHelpers] = useState<Record<string, unknown>>({});
// This callback receives ALL MediaSFU state and methods
const updateSourceParameters = useCallback((data: Record<string, unknown>) => {
setHelpers(data);
}, []);
return (
<>
<MediasfuGeneric
credentials={{ apiUserName: "user", apiKey: "key" }}
returnUI={false} // Headless mode - no UI rendered
noUIPreJoinOptions={{
action: "create",
capacity: 10,
eventType: "conference",
userName: "Host"
}}
sourceParameters={helpers} // Required: pass state object
updateSourceParameters={updateSourceParameters} // Required: receive updates
/>
{helpers.validated && <MyCustomMeetingUI sourceParameters={helpers} />}
</>
);
}
const {
allVideoStreams, // All video MediaStream objects
allAudioStreams, // All audio MediaStream objects
localStream, // Your local camera stream
localStreamAudio, // Your local microphone stream
localStreamScreen, // Your screen share stream (if active)
remoteScreenStream, // Remote screen share stream
} = sourceParameters;
const {
participants, // Full participant list
participantsCounter, // Current count
filteredParticipants, // Filtered by search
waitingRoomList, // Users in waiting room
coHost, // Current co-host name
member, // Your username
islevel, // Your permission level ('0', '1', '2')
youAreHost, // Boolean
youAreCoHost, // Boolean
} = sourceParameters;
const {
roomName, // Current room name
eventType, // 'conference' | 'webinar' | 'broadcast' | 'chat'
recordStarted, // Is recording active
recordPaused, // Is recording paused
shareScreenStarted, // Is screen sharing active
validated, // Is room validated/connected
messages, // Chat messages array
polls, // Active polls
} = sourceParameters;
getParticipantMedia - Get Individual Streamsconst { getParticipantMedia } = sourceParameters;
// Get video stream by participant name
const videoStream = await getParticipantMedia({
name: "Alice",
kind: "video"
});
// Get audio stream by producer ID
const audioStream = await getParticipantMedia({
id: "producer-id-123",
kind: "audio"
});
// Use in a video element
if (videoStream) {
videoRef.current.srcObject = videoStream;
}
const {
clickVideo, // Toggle local video
clickAudio, // Toggle local audio
clickScreenShare, // Toggle screen share
switchVideoAlt, // Switch camera device
switchUserAudio, // Switch audio input device
} = sourceParameters;
// Toggle video with parameters
await clickVideo({ parameters: sourceParameters });
const {
updateIsMenuModalVisible,
updateIsRecordingModalVisible,
updateIsParticipantsModalVisible,
updateIsMessagesModalVisible,
updateIsPollModalVisible,
updateIsBackgroundModalVisible,
updateIsBreakoutRoomsModalVisible,
updateIsMediaSettingsModalVisible,
} = sourceParameters;
// Open the chat modal
updateIsMessagesModalVisible(true);
Use AudioGrid with audio streams from sourceParameters:
import { AudioGrid, AudioCard } from 'mediasfu-reactjs';
function AudioParticipantsView({ sourceParameters }) {
const { allAudioStreams, participants } = sourceParameters;
// Build audio components from streams
const audioComponents = allAudioStreams.map((streamObj, index) => {
const participant = participants.find(p => p.audioID === streamObj.producerId);
return (
<AudioCard
key={streamObj.producerId || index}
name={participant?.name || 'Unknown'}
audioStream={streamObj.stream}
showWaveform={true}
barColor="#22c55e"
customStyle={{
background: "rgba(34, 197, 94, 0.1)",
borderRadius: 16,
padding: 12
}}
/>
);
});
return (
<AudioGrid
componentsToRender={audioComponents}
containerProps={{
style: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
gap: 16,
padding: 20
}
}}
/>
);
}
You can use any modal component independently with sourceParameters:
import { ModernRecordingModal } from 'mediasfu-reactjs';
function MyRecordingButton({ sourceParameters }) {
const [showRecording, setShowRecording] = useState(false);
return (
<>
<button onClick={() => setShowRecording(true)}>
🔴 Recording Settings
</button>
<ModernRecordingModal
isVisible={showRecording}
onClose={() => setShowRecording(false)}
parameters={sourceParameters}
position="center"
/>
</>
);
}
import { ModernBackgroundModal } from 'mediasfu-reactjs';
function BackgroundSelector({ sourceParameters }) {
const [showBg, setShowBg] = useState(false);
return (
<>
<button onClick={() => setShowBg(true)}>
🖼️ Change Background
</button>
<ModernBackgroundModal
isVisible={showBg}
onClose={() => setShowBg(false)}
parameters={sourceParameters}
/>
</>
);
}
import { ModernMediaSettingsModal } from 'mediasfu-reactjs';
function DeviceSelector({ sourceParameters }) {
const [showSettings, setShowSettings] = useState(false);
return (
<>
<button onClick={() => setShowSettings(true)}>
⚙️ Audio/Video Settings
</button>
<ModernMediaSettingsModal
isVisible={showSettings}
onClose={() => setShowSettings(false)}
parameters={sourceParameters}
/>
</>
);
}
Here's a complete example of building a custom meeting interface:
import {
MediasfuGeneric,
VideoCard,
AudioCard,
ModernMessagesModal,
ModernParticipantsModal,
ModernRecordingModal,
} from 'mediasfu-reactjs';
import { useState, useEffect } from 'react';
function CustomMeetingApp() {
const [params, setParams] = useState<any>(null);
const [showChat, setShowChat] = useState(false);
const [showParticipants, setShowParticipants] = useState(false);
const [showRecording, setShowRecording] = useState(false);
if (!params?.validated) {
return (
<MediasfuGeneric
credentials={{ apiUserName: "user", apiKey: "key" }}
returnUI={true} // Show pre-join UI
updateSourceParameters={setParams}
/>
);
}
const {
participants,
allVideoStreams,
allAudioStreams,
member,
roomName,
clickVideo,
clickAudio,
clickScreenShare,
videoAlreadyOn,
audioAlreadyOn,
shareScreenStarted,
recordStarted,
} = params;
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh', background: '#0f172a' }}>
{/* Header */}
<header style={{ padding: 16, borderBottom: '1px solid #334155', display: 'flex', justifyContent: 'space-between' }}>
<h1 style={{ color: 'white', margin: 0 }}>{roomName}</h1>
<span style={{ color: '#94a3b8' }}>{participants.length} participants</span>
</header>
{/* Video Grid */}
<main style={{ flex: 1, display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 16, padding: 16 }}>
{allVideoStreams.map((stream, i) => {
const participant = participants.find(p => p.videoID === stream.producerId);
return (
<VideoCard
key={stream.producerId || i}
videoStream={stream.stream}
name={participant?.name || 'Unknown'}
customStyle={{ borderRadius: 16, overflow: 'hidden' }}
/>
);
})}
</main>
{/* Controls */}
<footer style={{ padding: 16, borderTop: '1px solid #334155', display: 'flex', justifyContent: 'center', gap: 16 }}>
<button
onClick={() => clickVideo({ parameters: params })}
style={{
padding: '12px 24px',
borderRadius: 12,
background: videoAlreadyOn ? '#22c55e' : '#ef4444',
color: 'white',
border: 'none',
cursor: 'pointer'
}}
>
{videoAlreadyOn ? '📹 Video On' : '📹 Video Off'}
</button>
<button
onClick={() => clickAudio({ parameters: params })}
style={{
padding: '12px 24px',
borderRadius: 12,
background: audioAlreadyOn ? '#22c55e' : '#ef4444',
color: 'white',
border: 'none',
cursor: 'pointer'
}}
>
{audioAlreadyOn ? '🎤 Mic On' : '🎤 Mic Off'}
</button>
<button onClick={() => setShowParticipants(true)} style={{ padding: '12px 24px', borderRadius: 12, background: '#3b82f6', color: 'white', border: 'none', cursor: 'pointer' }}>
👥 Participants
</button>
<button onClick={() => setShowChat(true)} style={{ padding: '12px 24px', borderRadius: 12, background: '#8b5cf6', color: 'white', border: 'none', cursor: 'pointer' }}>
💬 Chat
</button>
<button onClick={() => setShowRecording(true)} style={{ padding: '12px 24px', borderRadius: 12, background: recordStarted ? '#ef4444' : '#6b7280', color: 'white', border: 'none', cursor: 'pointer' }}>
🔴 Record
</button>
</footer>
{/* Modals */}
<ModernMessagesModal
isVisible={showChat}
onClose={() => setShowChat(false)}
parameters={params}
/>
<ModernParticipantsModal
isVisible={showParticipants}
onClose={() => setShowParticipants(false)}
parameters={params}
/>
<ModernRecordingModal
isVisible={showRecording}
onClose={() => setShowRecording(false)}
parameters={params}
/>
</div>
);
}
MediaSFU isn't just React. The same communication platform is available across 7 frameworks — same API surface, same capabilities, same pricing:
| Framework | Package |
|---|---|
| React | mediasfu-reactjs (you are here) |
| React Native | @mediasfu/mediasfu-reactnative |
| Expo | @mediasfu/mediasfu-reactnative-expo |
| Flutter | mediasfu_sdk |
| Angular | @mediasfu/mediasfu-angular |
| Vue | @mediasfu/mediasfu-vue |
| Android (Kotlin) | MediaSFU Android |
MIT © MediaSFU
Built with ❤️ by MediaSFU
Voice · Video · AI · Translation · 7 Frameworks · $0.10/1K min