MediaSFU ReactJS
    Preparing search index...

    MediaSFU ReactJS

    MediaSFU Logo

    Twitter Community Forum Github Website

    License: MIT npm version React 18 | 19 TypeScript


    MediaSFU ReactJS SDK

    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:

    • Multi-party video conferencing with adaptive quality
    • Screen sharing with real-time annotation
    • Virtual backgrounds and video effects
    • Audio-only participant support
    • Speak in any language — The system auto-detects what language you're speaking
    • Listen in any language — Hear others translated to your preferred language in real time
    • Live transcription — See real-time transcripts during meetings
    • 50+ languages supported — True borderless communication
    • No interpreters. No delay. Works with voice, video, and chat.
    • AI voice agents that answer, resolve, and escalate
    • Multimodal AI with voice + vision capabilities
    • AI-generated meeting summaries and transcription
    • Intelligent call routing and warm handoffs
    • Voice cloning options with custom TTS configurations
    • Panelists Mode — Designate speakers in webinars with audience Q&A
    • Individual Permissions — Granular per-participant control (video/audio/screen/chat)
    • Group Permissions — Apply permission templates to participant groups
    • Waiting room with manual admit
    • Co-host delegation with configurable responsibilities
    • Breakout rooms for focused discussions
    • Live polls with real-time results
    • In-meeting chat (direct & group)
    • Collaborative whiteboards
    • Cloud recording with track-based customization
    • Watermarks, name tags, custom backgrounds
    • Real-time call analytics
    • End-to-end encryption option
    • Domain-locked API keys
    • Managed events with time/capacity limits
    • Abandoned participant handling


    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:

    • Advanced customization patterns
    • Full API reference
    • All component props
    • Socket event handling
    • Recording configuration
    • Breakout rooms
    • Whiteboard integration
    • And much more...

    📄 See README_DETAILED.md


    ModernMediasfuGeneric is the most advanced, themed variant featuring:

    • Premium glass-morphism design with backdrop blur effects
    • Smooth animations and micro-interactions
    • Dark/Light theme support built-in
    • Accessibility-first components
    • Responsive layouts for all screen sizes
    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:

    • Set your spoken language — The system knows what language you're speaking
    • Choose listening language — Hear others translated to your preferred language
    • Real-time transcription — See live transcripts
    • Multiple language support — 50+ languages available

    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;
    const { 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