npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

mediasfu-reactjs

v4.2.3

Published

MediaSFU Prebuilt ReactJS SDK - Compatible with React 18 & 19, TypeScript & JavaScript

Downloads

148

Readme


MediaSFU ReactJS SDK

Build production-ready video conferencing, webinars, broadcasts, and chat in minutes.

MediaSFU ReactJS provides prebuilt, fully-featured event room components with real-time video/audio, screen sharing, recording, chat, polls, whiteboards, and more — ready to drop into your React app.


⚠️ Important: Backend Server Required

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 →


✨ Platform Features

MediaSFU delivers enterprise-grade real-time communication with these core capabilities:

🎥 Video & Audio

  • Multi-party video conferencing with adaptive quality
  • Screen sharing with real-time annotation
  • Virtual backgrounds and video effects
  • Audio-only participant support

🎤 Advanced Audio Features

  • Real-time Translation — Listen and speak in any language with live AI translation
  • Automatic echo cancellation and noise suppression
  • Spatial audio support

👥 Participant Management

  • Panelists Mode — Designate speakers in webinars with audience Q&A
  • Individual Permissions — Granular control per-participant (video/audio/screen/chat)
  • Co-host Delegation — Share moderation duties with configurable responsibilities
  • Waiting room with manual admit
  • Breakout rooms for focused discussions

📊 Engagement Tools

  • Live polls with real-time results
  • In-meeting chat (direct & group)
  • Collaborative whiteboards
  • Reactions and hand raising

🎬 Recording & Analytics

  • Cloud recording with track-based customization
  • Watermarks, name tags, custom backgrounds
  • Real-time call analytics

🔒 Security & Control

  • End-to-end encryption option
  • Managed events with time/capacity limits
  • Abandoned participant handling

📖 Table of Contents


🚀 Quick Start

npm install mediasfu-reactjs
import { MediasfuGeneric } from 'mediasfu-reactjs';

function App() {
  return (
    <MediasfuGeneric
      credentials={{ apiUserName: "yourUsername", apiKey: "yourAPIKey" }}
    />
  );
}

That's it! You now have a fully-featured video conferencing room.

ℹ️ Critical component styles are automatically injected at runtime. For additional styling options, see Optional CSS Import.


📦 Installation

# npm
npm install mediasfu-reactjs

# yarn
yarn add mediasfu-reactjs

# pnpm
pnpm add mediasfu-reactjs

Peer Dependencies

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"
}

Optional Peer Dependencies

For virtual background support (blur, image backgrounds):

npm install @mediapipe/[email protected]

This is optional — if not installed, virtual backgrounds simply won't be available.

Optional CSS Import

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.


🏛️ Prebuilt Event Rooms

| Component | Use Case | Description | |-----------|----------|-------------| | MediasfuGeneric | Universal | Supports all event types dynamically | | 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
}

💡 Usage Examples

Basic Conference Room

import { MediasfuConference } from 'mediasfu-reactjs';

function ConferenceApp() {
  return (
    <MediasfuConference
      credentials={{
        apiUserName: "yourUsername",
        apiKey: "yourAPIKey"
      }}
    />
  );
}

Webinar with Custom Branding

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)"
      }}
    />
  );
}

Demo/Preview Mode (No Server)

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"
      }}
    />
  );
}

Custom Video Cards

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}
    />
  );
}

Headless Mode (Custom UI)

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. */}
    </>
  );
}

UI Overrides

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}
    />
  );
}

🧩 Key Components

Display Components

| 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 |

Modal Components

| 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 |

Control Methods

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';

Socket Events

import { 
  connectSocket,
  disconnectSocket,
  joinRoomClient,
  // Event handlers
  personJoined,
  meetingEnded,
  receiveMessage,
} from 'mediasfu-reactjs';

🎨 Customization

CSS Variables

The package uses CSS variables for theming:

:root {
  --mediasfu-primary: #4c1d95;
  --mediasfu-background: #1a1a2e;
  --mediasfu-surface: #16213e;
  --mediasfu-text: #ffffff;
}

Custom Cards

// 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>
);

Complete UI Override

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 },
};

📚 API Reference

Types

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';

Utilities

import {
  // Room management
  joinRoomOnMediaSFU,
  createRoomOnMediaSFU,
  checkLimitsAndMakeRequest,
  
  // Demo utilities
  generateRandomParticipants,
  generateRandomMessages,
  generateRandomPolls,
  
  // State
  initialValuesState,
  
  // Helpers
  formatNumber,
  sleep,
  checkPermission,
} from 'mediasfu-reactjs';

🏠 Self-Hosting / Community Edition

For self-hosted MediaSFU servers:

<MediasfuGeneric
  localLink="https://your-mediasfu-server.com"
  connectMediaSFU={false}  // Don't connect to cloud
/>

Hybrid Mode (Local + Cloud)

<MediasfuGeneric
  localLink="https://your-server.com"
  connectMediaSFU={true}   // Also connect to MediaSFU cloud
  credentials={{ apiUserName: "user", apiKey: "key" }}
/>

📖 Detailed Documentation

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


🎨 Modern UI Components

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 Components Available

| 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 |


🌐 Advanced Features

Panelists Mode (Webinars)

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

Individual Permissions

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

Real-time Translation 🌍

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

🔧 sourceParameters - The Power API

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} />}
    </>
  );
}

Key sourceParameters Properties

Media Streams

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;

Participant Data

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;

Room State

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;

Helper Methods

getParticipantMedia - Get Individual Streams

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;
}

Media Control Methods

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 });

Modal Toggles

const {
  updateIsMenuModalVisible,
  updateIsRecordingModalVisible,
  updateIsParticipantsModalVisible,
  updateIsMessagesModalVisible,
  updateIsPollModalVisible,
  updateIsBackgroundModalVisible,
  updateIsBreakoutRoomsModalVisible,
  updateIsMediaSettingsModalVisible,
} = sourceParameters;

// Open the chat modal
updateIsMessagesModalVisible(true);

🎵 AudioGrid - Display All Audio Participants

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
        }
      }}
    />
  );
}

🎬 Using Modals Standalone

You can use any modal component independently with sourceParameters:

Recording Modal

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"
      />
    </>
  );
}

Virtual Background Modal

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}
      />
    </>
  );
}

Media Settings Modal

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}
      />
    </>
  );
}

🏗️ Building Your Own UI

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>
  );
}

🔗 Links


📄 License

MIT © MediaSFU