MediaSFU Logo

License: MIT Twitter GitHub Website YouTube Community

Built with MediaSFU Flutter TypeScript React React Native

MediaSFU Flutter SDK provides a comprehensive solution for building real-time communication applications. Part of the MediaSFU ecosystem with cross-platform support for web, mobile, and desktop environments using React.js, Flutter, React Native, and Expo.


🚨 BREAKING: AI Phone Agents at $0.10 per 1,000 minutes

πŸ“ž Call our live AI demos right now:

  • πŸ‡ΊπŸ‡Έ +1 (785) 369-1724 - Mixed Support Demo
  • πŸ‡¬πŸ‡§ +44 7445 146575 - AI Conversation Demo
  • πŸ‡¨πŸ‡¦ +1 (587) 407-1990 - Technical Support Demo
  • πŸ‡¨πŸ‡¦ +1 (647) 558-6650 - Friendly AI Chat Demo

Traditional providers charge $0.05 per minute. We charge $0.10 per 1,000 minutes. That's 500x cheaper.

βœ… Deploy AI phone agents in 30 minutes
βœ… Works with ANY SIP provider (Twilio, Telnyx, Zadarma, etc.)
βœ… Seamless AI-to-human handoffs
βœ… Real-time call analytics & transcription

πŸ“– Complete SIP/PSTN Documentation β†’


MediaSFU offers a cutting-edge streaming experience that empowers users to customize their recordings and engage their audience with high-quality streams. Whether you're a content creator, educator, or business professional, MediaSFU provides the tools you need to elevate your streaming game.

Preview Page

MediaSFU Flutter Package Documentation

πŸš€ Quick Access to New Features

Media Device & Stream Utilities

The SDK now includes powerful utility methods for advanced media control:

getMediaDevicesList - Enumerate available cameras and microphones with automatic permission handling:

final cameras = await getMediaDevicesList('videoinput');
final microphones = await getMediaDevicesList('audioinput');

getParticipantMedia - Retrieve specific participant's video or audio stream from the session:

final videoStream = getParticipantMedia(
  options: GetParticipantMediaOptions(
    participantId: 'producer-123',
    mediaType: 'video',
    parameters: mediasfuParameters,
  ),
);

These utilities enable advanced features like custom device selection interfaces, participant stream monitoring, and dynamic media routing. See full documentation.


Unlock the Power of MediaSFU Community Edition

MediaSFU Community Edition is free and open-sourceβ€”perfect for developers who want to run their own media server without upfront costs. With robust features and simple setup, you can launch your media solution in minutes. Ready to scale? Upgrade seamlessly to MediaSFU Cloud for enterprise-grade performance and global scalability.

Get started now on GitHub!

βœ… Flutter SDK Setup Guide

Watch the Flutter SDK Setup Guide
πŸŽ₯ Watch the Flutter SDK Setup Guide

Table of Contents

Features

MediaSFU's Flutter SDK comes with a host of powerful features out of the box:

  1. Breakout Rooms: Create multiple sub-meetings within a single session to enhance collaboration and focus.
  2. Pagination: Efficiently handle large participant lists with seamless pagination.
  3. Polls: Conduct real-time polls to gather instant feedback from participants.
  4. Media Access Requests Management: Manage media access requests with ease to ensure smooth operations
  5. Chat (Direct & Group): Facilitate communication with direct and group chat options.
  6. Cloud Recording (track-based): Customize recordings with track-based options, including watermarks, name tags, background colors, and more.
  7. Managed Events: Manage events with features to handle abandoned and inactive participants, as well as enforce time and capacity limits.

πŸ†• New Advanced Media Access

The SDK now includes powerful utility methods for fine-grained control over media devices and participant streams:

  • getMediaDevicesList: Enumerate available cameras and microphones with permission handling
  • getParticipantMedia: Retrieve specific participant's video or audio streams by ID or name

These utilities enable advanced features like device selection interfaces, participant stream monitoring, and custom media routing. Learn more in the Media Device & Stream Utilities section.

Getting Started

This section will guide users through the initial setup and installation of the Flutter package.

Documentation Reference

For comprehensive documentation on the available methods, components, and functions, please visit mediasfu.com. This resource provides detailed information for this guide and additional documentation.

Installation

To install the package using Flutter, follow the instructions below:

1. Add the mediasfu_sdk package to your project by running the following command:

```bash
flutter pub add mediasfu_sdk
```

2. Obtain an API Key (If Required)

You can get your API key by signing up or logging into your account at mediasfu.com.

Important:

You must obtain an API key from mediasfu.com to use this package with MediaSFU Cloud. You do not need the API Key if self-hosting.

3. Self-Hosting MediaSFU

If you plan to self-host MediaSFU or use it without MediaSFU Cloud services, you don't need an API key. You can access the open-source version of MediaSFU from the MediaSFU Open Repository.

This setup allows full flexibility and customization while bypassing the need for cloud-dependent credentials.

4. Configure Your Project

To ensure that your Flutter app has the necessary permissions and configurations for camera, microphone, and network access on both iOS and Android platforms, follow the detailed steps below.

iOS Setup

  1. Minimum iOS Platform Version

    • Navigate to your ios/Podfile located in your project's iOS directory.

    • Find the line specifying the iOS platform version.

    • Update the minimum iOS platform version to 12.0:

      platform :ios, '12.0'
      
  2. Info.plist Updates

    • Open your Info.plist file located at <project root>/ios/Runner/Info.plist.

    • Add the following entries to allow camera and microphone usage:

      <key>NSCameraUsageDescription</key>
      <string>$(PRODUCT_NAME) Camera Usage</string>
      <key>NSMicrophoneUsageDescription</key>
      <string>$(PRODUCT_NAME) Microphone Usage</string>
      

macOS Setup (Optional)

  1. Minimum macOS Version

    • Navigate to your macos/Podfile located in your project's macOS directory.

    • Find the line specifying the macOS platform version.

    • Update the minimum macOS platform version to 10.15 (Catalina) or higher:

      platform :osx, '10.15'
      
  2. Info.plist Updates

    • Open your macos/Runner/Info.plist file located at <project root>/macos/Runner/Info.plist.

    • Add the following entries to allow camera and microphone usage:

      <key>NSCameraUsageDescription</key>
      <string>Camera access is required for video calls</string>
      <key>NSMicrophoneUsageDescription</key>
      <string>Microphone access is required for audio calls</string>
      
  3. Entitlements Configuration

    • Create a new file named Release.entitlements in your macos/Runner directory if it doesn't already exist.

    • Add the following entries to Release.entitlements to allow your app to access the camera, microphone, and open outgoing network connections:

      <key>com.apple.security.network.server</key>
      <true/>
      <key>com.apple.security.network.client</key>
      <true/>
      <key>com.apple.security.device.camera</key>
      <true/>
      <key>com.apple.security.device.microphone</key>
      <true/>
      

Android Setup

  1. Update Gradle Configuration

    • Open the build.gradle file located at <project root>/android/app/build.gradle.

    • Ensure the following SDK versions are set:

      android {
          compileSdkVersion 33
          defaultConfig {
              minSdkVersion 23
              targetSdkVersion 33
              // Other configurations...
          }
          compileOptions {
              sourceCompatibility JavaVersion.VERSION_1_8
              targetCompatibility JavaVersion.VERSION_1_8
          }
      }
      
  2. AndroidManifest Permissions

    • Open the AndroidManifest.xml file located at <project root>/android/app/src/main/AndroidManifest.xml.

    • Add the following permissions to allow camera, microphone, and network access:

      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.CAMERA" />
      <uses-permission android:name="android.permission.RECORD_AUDIO" />
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
      <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
      <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
      <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
      <uses-permission android:name="android.permission.WAKE_LOCK" />
      
    • If your app requires Bluetooth functionality, add these permissions:

      <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
      <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
      <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
      
  3. Enable Java 8 Compatibility

    • Ensure your app is compatible with Java 8 by adding the following code block to the android section of your build.gradle:

      android {
          //...
          compileOptions {
              sourceCompatibility JavaVersion.VERSION_1_8
              targetCompatibility JavaVersion.VERSION_1_8
          }
      }
      

Web Setup (Optional)

If you are targeting Flutter Web, ensure you have proper configurations for camera and microphone access:

  1. Secure Context

    • Use a secure context (HTTPS) to ensure that browser permissions for camera and microphone are handled correctly.
  2. Browser Permissions

    • Ensure that your web application requests and handles the necessary permissions for camera and microphone access.

By following these steps, you ensure that your Flutter app has the necessary permissions and configurations for camera, microphone, and network access on both iOS and Android platforms.


πŸ“± Flutter SDK Guide

This comprehensive guide will walk you through everything you need to know about building real-time communication apps with MediaSFU's Flutter SDK. Whether you're a beginner or an experienced developer, you'll find clear explanations, practical examples, and best practices.


Quick Start (5 Minutes)

Get your first MediaSFU app running in just a few minutes.

Step 1: Install the Package

flutter pub add mediasfu_sdk

Step 2: Import and Use

import 'package:flutter/material.dart';
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // Option 1: Use without credentials (for testing)
    final options = MediasfuGenericOptions();

    // Option 2: Use with MediaSFU Cloud credentials
    // final credentials = Credentials(
    //   apiUserName: 'your_username',
    //   apiKey: 'your_api_key',
    // );
    // final options = MediasfuGenericOptions(credentials: credentials);

    return MaterialApp(
      title: 'MediaSFU App',
      home: MediasfuGeneric(options: options),
    );
  }
}

Step 3: Run Your App

flutter run

That's it! You now have a fully functional video conferencing app with:

  • βœ… Video and audio streaming
  • βœ… Screen sharing
  • βœ… Chat messaging
  • βœ… Participant management
  • βœ… Recording capabilities

Understanding MediaSFU Architecture

Before diving deeper, let's understand how MediaSFU is structured.

The Three-Layer Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Your Flutter Application            β”‚
β”‚  (main.dart, custom widgets, business logic) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         MediaSFU Components Layer           β”‚
β”‚  (MediasfuGeneric, MediasfuBroadcast, etc.) β”‚
β”‚         - Pre-built UI components            β”‚
β”‚         - Event handling                     β”‚
β”‚         - State management                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         MediaSFU Core Methods Layer         β”‚
β”‚    (Stream control, room management,        β”‚
β”‚     WebRTC handling, socket communication)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         MediaSFU Backend Services           β”‚
β”‚  (MediaSFU Cloud or Community Edition)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Concepts

1. Event Room Types

MediaSFU provides 5 specialized room types, each optimized for specific use cases:

Room Type Best For Key Features
MediasfuGeneric General purpose meetings Flexible layout, all features enabled
MediasfuBroadcast Live streaming events Optimized for one-to-many communication
MediasfuWebinar Educational sessions Presenter focus, Q&A features
MediasfuConference Business meetings Equal participant layout, collaboration tools
MediasfuChat Interactive discussions Chat-first interface, quick connections
// Choose the right room type for your use case
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

// For a webinar
home: MediasfuWebinar(options: MediasfuWebinarOptions(...));

// For a broadcast
home: MediasfuBroadcast(options: MediasfuBroadcastOptions(...));

// For a conference
home: MediasfuConference(options: MediasfuConferenceOptions(...));

2. The Three Usage Modes

MediaSFU offers three progressive levels of customization:

Mode 1: Default UI (Simplest)

Use MediaSFU's complete pre-built interface - perfect for rapid development.

final options = MediasfuGenericOptions(
  credentials: credentials,
);

return MediasfuGeneric(options: options);

When to use:

  • βœ… Prototyping or MVP development
  • βœ… Need a production-ready UI quickly
  • βœ… Standard video conferencing features are sufficient
Mode 2: Custom UI with MediaSFU Backend (Most Flexible)

Build your own UI while using MediaSFU's powerful backend infrastructure.

final options = MediasfuGenericOptions(
  credentials: credentials,
  returnUI: false, // Don't render MediaSFU's UI
  sourceParameters: sourceParameters.value,
  updateSourceParameters: updateSourceParameters,
);

// Access MediaSFU features in your custom UI
sourceParameters.value?.clickVideo(...);
sourceParameters.value?.clickAudio(...);

When to use:

  • βœ… Need complete control over UI/UX
  • βœ… Building a custom branded experience
  • βœ… Integrating into existing app design
Mode 3: Component Replacement (Balanced)

Replace specific MediaSFU components while keeping the rest of the infrastructure.

Widget myCustomInterface({required MediasfuParameters parameters}) {
  return YourCustomWidget(
    // Use MediaSFU components in your layout
    child: FlexibleVideo(parameters: parameters),
  );
}

final options = MediasfuGenericOptions(
  credentials: credentials,
  customComponent: myCustomInterface,
);

When to use:

  • βœ… Need custom main interface but want to keep MediaSFU's components
  • βœ… Partial customization with minimal effort
  • βœ… Want to maintain MediaSFU's functionality while customizing layout

3. Parameters: Your Control Center

The MediasfuParameters object is your gateway to all MediaSFU functionality:

class MediasfuParameters {
  // Media Controls
  void clickVideo(ClickVideoOptions options) { }
  void clickAudio(ClickAudioOptions options) { }
  void clickScreenShare(ClickScreenShareOptions options) { }
  
  // Room State
  String roomName;
  List<Participant> participants;
  List<Stream> allVideoStreams;
  List<Stream> allAudioStreams;
  
  // UI State
  bool isVideoVisible;
  bool isAudioVisible;
  bool isScreenSharing;
  
  // And 200+ more properties and methods...
}

Access patterns:

// In Mode 1 (Default UI): Parameters are managed internally
// You don't need to access them directly

// In Mode 2 (Custom UI): Access via sourceParameters
sourceParameters.value?.clickVideo(
  ClickVideoOptions(parameters: sourceParameters.value!),
);

// In Mode 3 (Component Replacement): Passed to your custom component
Widget myCustomInterface({required MediasfuParameters parameters}) {
  // Use parameters directly
  parameters.clickVideo(ClickVideoOptions(parameters: parameters));
}

Core Concepts & Components

Now that you understand the architecture, let's explore the building blocks.

1. Display Components: Building Your Video Layout

MediaSFU provides powerful components for organizing and displaying media streams.

Primary Layout Components

FlexibleVideo - Main video display area

FlexibleVideo(
  customWidth: MediaQuery.of(context).size.width,
  customHeight: 400,
  parameters: parameters,
)
  • Automatically handles main presenter or screen share
  • Smooth transitions between different video sources
  • Responsive sizing

FlexibleGrid - Participant grid layout

FlexibleGrid(
  customWidth: MediaQuery.of(context).size.width,
  customHeight: 600,
  parameters: parameters,
  // Automatically arranges participants in optimal grid
)
  • Intelligent grid sizing (2x2, 3x3, 4x4, etc.)
  • Pagination for large participant lists
  • Automatic reflow on orientation change

AudioGrid - Audio-only participants

AudioGrid(
  parameters: parameters,
  // Shows visual indicators for audio-only participants
)
  • Displays participants without video
  • Audio level indicators
  • Compact layout for efficiency

Container Components

Component Purpose Use Case
MainContainerComponent Primary content wrapper Wraps all main content areas
MainAspectComponent Aspect ratio container Maintains proper video proportions
MainScreenComponent Screen layout manager Organizes screen regions
SubAspectComponent Secondary content container For picture-in-picture, sidebars

Example: Building a custom layout

Widget buildCustomLayout(MediasfuParameters parameters) {
  return Column(
    children: [
      // Main video area
      Expanded(
        flex: 3,
        child: FlexibleVideo(
          customWidth: double.infinity,
          customHeight: double.infinity,
          parameters: parameters,
        ),
      ),
      
      // Participant grid
      Expanded(
        flex: 2,
        child: FlexibleGrid(
          customWidth: double.infinity,
          customHeight: double.infinity,
          parameters: parameters,
        ),
      ),
      
      // Audio-only participants
      Container(
        height: 80,
        child: AudioGrid(parameters: parameters),
      ),
    ],
  );
}

2. Control Components: User Interactions

ControlButtonsComponent - Standard control bar

ControlButtonsComponent(
  parameters: parameters,
  position: 'bottom', // or 'top', 'left', 'right'
  // Includes: mute, video, screenshare, participants, chat, etc.
)

ControlButtonsAltComponent - Alternative layout

ControlButtonsAltComponent(
  parameters: parameters,
  // Different button arrangement or styling
)

ControlButtonsComponentTouch - Touch-optimized floating controls

ControlButtonsComponentTouch(
  parameters: parameters,
  // Floating action buttons for mobile
)

3. Modal Components: Feature Interfaces

MediaSFU includes modals for various features:

// Participants management
ParticipantsModal(parameters: parameters)

// Chat interface
MessagesModal(parameters: parameters)

// Settings panel
SettingsModal(parameters: parameters)

// Display settings
DisplaySettingsModal(parameters: parameters)

// Recording controls
RecordingModal(parameters: parameters)

// Polls interface
PollModal(parameters: parameters)

// Breakout rooms
BreakoutRoomsModal(parameters: parameters)

Accessing modals programmatically:

// Show/hide participants modal
parameters.updateIsParticipantsModalVisible(true);

// Show/hide chat
parameters.updateIsMessagesModalVisible(true);

// Show/hide settings
parameters.updateIsSettingsModalVisible(true);

4. Video Cards: Individual Participant Display

VideoCard - Individual participant video element

VideoCard(
  videoStream: participantStream,
  remoteProducerId: 'producer-id',
  eventType: 'conference',
  forceFullDisplay: false,
  participant: participantObject,
  backgroundColor: Colors.black,
  showControls: true,
  showInfo: true,
  parameters: parameters,
)

AudioCard - Individual audio-only participant

AudioCard(
  name: 'Participant Name',
  barColor: Colors.blue,
  textColor: Colors.white,
  customStyle: TextStyle(...),
  controlsPosition: 'topLeft',
  infoPosition: 'topRight',
  participant: participantObject,
  parameters: parameters,
)

MiniCard - Compact participant display (for grids)

MiniCard(
  participant: participantObject,
  showControls: false,
  parameters: parameters,
)

5. Working with Participants

Understanding participant objects is crucial for custom UI development.

// Participant structure
class Participant {
  String id;              // Unique identifier
  String name;            // Display name
  bool muted;             // Audio state
  bool videoOn;           // Video state
  String? audioID;        // Audio producer ID
  String? videoID;        // Video producer ID
  List<Stream> stream;    // Media streams
  bool islevel;           // Privilege level
  // ... and more properties
}

// Accessing participants
final participants = parameters.participants;

// Filter participants by criteria
final videoParticipants = participants.where((p) => p.videoOn).toList();
final audioOnlyParticipants = participants.where((p) => !p.videoOn).toList();

// Find specific participant
final participant = participants.firstWhere(
  (p) => p.id == 'participant-id',
  orElse: () => null,
);

// Access participant streams
for (var participant in parameters.participants) {
  if (participant.videoID != null) {
    // Participant has video
    final videoStream = parameters.allVideoStreams.firstWhere(
      (s) => s.producerId == participant.videoID,
      orElse: () => null,
    );
  }
}

6. Stream Management

Working with media streams directly for advanced use cases.

// Get all video streams
final videoStreams = parameters.allVideoStreams;

// Get all audio streams
final audioStreams = parameters.allAudioStreams;

// Stream structure
class Stream {
  String producerId;           // Producer identifier
  MediaStream? stream;         // Actual media stream
  String? kind;               // 'video' or 'audio'
  String? videoID;            // Associated video ID
  String? audioID;            // Associated audio ID
  String? name;               // Participant name
  // ... more properties
}

// Using the new utility methods
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

// Get specific participant's video stream
final videoStream = getParticipantMedia(
  options: GetParticipantMediaOptions(
    participantId: 'producer-123',
    mediaType: 'video',
    parameters: parameters,
  ),
);

// Or by participant name
final audioStream = getParticipantMedia(
  options: GetParticipantMediaOptions(
    participantName: 'John Doe',
    mediaType: 'audio',
    parameters: parameters,
  ),
);

7. Room Configuration

Understanding room setup options.

// Creating a room
final createOptions = CreateMediaSFURoomOptions(
  action: 'create',
  capacity: 50,              // Maximum participants
  duration: 60,              // Duration in minutes
  eventType: EventType.conference,
  userName: 'Your Name',
  // Optional advanced settings
  recordOnly: false,         // Record-only mode
  mediaType: 'video',        // or 'audio'
  videoOptimized: true,      // Optimize for video quality
);

// Joining an existing room
final joinOptions = JoinMediaSFURoomOptions(
  action: 'join',
  meetingID: 'room-id-here',
  userName: 'Your Name',
);

// Using the options
final options = MediasfuGenericOptions(
  credentials: credentials,
  noUIPreJoinOptionsCreate: createOptions,
  // or
  // noUIPreJoinOptionsJoin: joinOptions,
);

8. Event Handling & Callbacks

MediaSFU provides numerous callbacks for event handling.

final options = MediasfuGenericOptions(
  credentials: credentials,
  
  // Connection events
  onConnect: () {
    print('Connected to MediaSFU');
  },
  
  onDisconnect: () {
    print('Disconnected from MediaSFU');
  },
  
  // Participant events  
  onParticipantJoined: (Participant participant) {
    print('${participant.name} joined');
  },
  
  onParticipantLeft: (String participantId) {
    print('Participant $participantId left');
  },
  
  // Media events
  onVideoStateChanged: (bool isOn) {
    print('Video is now ${isOn ? 'on' : 'off'}');
  },
  
  onAudioStateChanged: (bool isOn) {
    print('Audio is now ${isOn ? 'on' : 'off'}');
  },
  
  // Room events
  onRoomClosed: () {
    print('Room has been closed');
  },
  
  onError: (String error) {
    print('Error: $error');
  },
);

Working with Methods

MediaSFU provides 200+ methods for controlling every aspect of your real-time communication experience. Let's explore the most important categories.

Media Control Methods

Video Control

// Toggle video on/off
parameters.clickVideo(
  ClickVideoOptions(parameters: parameters),
);

// Switch camera (front/back)
parameters.switchVideoAlt(
  SwitchVideoAltOptions(parameters: parameters),
);

// Switch to specific camera by ID
final cameras = await getMediaDevicesList('videoinput');
parameters.switchUserVideo(
  SwitchUserVideoOptions(
    videoPreference: cameras[1].deviceId,
    parameters: parameters,
  ),
);

// Get current video state
final isVideoOn = parameters.videoAlreadyOn;

// Listen for video state changes
parameters.updateVideoAlreadyOn = (bool isOn) {
  print('Video state changed: $isOn');
};

Audio Control

// Toggle audio on/off
parameters.clickAudio(
  ClickAudioOptions(parameters: parameters),
);

// Switch microphone
final microphones = await getMediaDevicesList('audioinput');
parameters.switchUserAudio(
  SwitchUserAudioOptions(
    audioPreference: microphones[1].deviceId,
    parameters: parameters,
  ),
);

// Get current audio state
final isAudioOn = parameters.audioAlreadyOn;
final hasHostPermission = parameters.micAction; // Host approval for mic usage

// Mute/unmute specific participant (host only)
parameters.muteParticipant(
  MuteParticipantOptions(
    participantId: 'participant-id',
    parameters: parameters,
  ),
);

Screen Sharing

// Start screen sharing
parameters.clickScreenShare(
  ClickScreenShareOptions(parameters: parameters),
);

// Stop screen sharing
parameters.stopShareScreen(
  StopShareScreenOptions(parameters: parameters),
);

// Check if screen sharing is available
final canShare = await parameters.checkScreenShare(
  CheckScreenShareOptions(parameters: parameters),
);

// Get screen share state
final isSharing = parameters.screenAlreadyOn;
final shareAudio = parameters.shareScreenStarted; // Sharing with audio

Device Management Methods

// Get available cameras
final cameras = await getMediaDevicesList('videoinput');
for (var camera in cameras) {
  print('Camera: ${camera.label} (${camera.deviceId})');
}

// Get available microphones
final microphones = await getMediaDevicesList('audioinput');
for (var mic in microphones) {
  print('Microphone: ${mic.label} (${mic.deviceId})');
}

// Building a device selector UI
Widget buildDeviceSelector(MediasfuParameters parameters) {
  return Column(
    children: [
      FutureBuilder<List<MediaDeviceInfo>>(
        future: getMediaDevicesList('videoinput'),
        builder: (context, snapshot) {
          if (!snapshot.hasData) return CircularProgressIndicator();
          
          return DropdownButton<String>(
            hint: Text('Select Camera'),
            items: snapshot.data!.map((device) {
              return DropdownMenuItem(
                value: device.deviceId,
                child: Text(device.label),
              );
            }).toList(),
            onChanged: (deviceId) {
              parameters.switchUserVideo(
                SwitchUserVideoOptions(
                  videoPreference: deviceId!,
                  parameters: parameters,
                ),
              );
            },
          );
        },
      ),
      
      // Similar for microphones...
    ],
  );
}

Participant Management Methods

// Get all participants
final participants = parameters.participants;

// Get participant count
final count = parameters.participantsCounter;

// Request to unmute participant (sends request)
parameters.requestMuteParticipant(
  RequestMuteParticipantOptions(
    participantId: 'participant-id',
    parameters: parameters,
  ),
);

// Remove participant from room (host only)
parameters.disconnectUserInitiate(
  DisconnectUserInitiateOptions(
    member: participantId,
    parameters: parameters,
  ),
);

// Change participant role (host only)
parameters.modifyParticipantRole(
  ModifyParticipantRoleOptions(
    participantId: 'participant-id',
    role: 'moderator', // or 'participant', 'host'
    parameters: parameters,
  ),
);

Chat & Messaging Methods

// Send a message
parameters.sendMessage(
  SendMessageOptions(
    message: 'Hello everyone!',
    type: 'chat', // or 'direct'
    receivers: [], // Empty for group, or list of IDs for direct
    parameters: parameters,
  ),
);

// Send direct message
parameters.sendMessage(
  SendMessageOptions(
    message: 'Private message',
    type: 'direct',
    receivers: ['participant-id'],
    parameters: parameters,
  ),
);

// Access message history
final messages = parameters.messages;

// Listen for new messages
parameters.updateMessages = (List<Message> newMessages) {
  // Handle new messages
};

Recording Methods

// Start recording
parameters.startRecording(
  StartRecordingOptions(
    parameters: parameters,
  ),
);

// Stop recording
parameters.stopRecording(
  StopRecordingOptions(
    parameters: parameters,
  ),
);

// Pause recording
parameters.pauseRecording(
  PauseRecordingOptions(
    parameters: parameters,
  ),
);

// Resume recording
parameters.resumeRecording(
  ResumeRecordingOptions(
    parameters: parameters,
  ),
);

// Configure recording settings
parameters.updateRecording(
  UpdateRecordingOptions(
    recordingMediaOptions: 'video', // or 'audio'
    recordingAudioOptions: 'all', // or 'host'
    recordingVideoOptions: 'all', // or 'host'
    recordingVideoType: 'fullDisplay', // or 'bestDisplay', 'all'
    recordingDisplayType: 'video', // 'media', 'video', 'all'
    recordingBackgroundColor: '#000000',
    recordingNameTagsColor: '#ffffff',
    recordingOrientationVideo: 'landscape', // or 'portrait'
    recordingNameTags: true,
    recordingAddHLS: false,
    parameters: parameters,
  ),
);

// Check recording state
final isRecording = parameters.recordStarted;
final isPaused = parameters.recordPaused;

Polls & Surveys Methods

// Create a poll
parameters.handleCreatePoll(
  HandleCreatePollOptions(
    poll: Poll(
      question: 'What time works best?',
      type: 'multiple', // or 'single'
      options: ['10 AM', '2 PM', '5 PM'],
    ),
    parameters: parameters,
  ),
);

// Vote on a poll
parameters.handleVotePoll(
  HandleVotePollOptions(
    pollId: 'poll-id',
    optionIndex: 1,
    parameters: parameters,
  ),
);

// End a poll
parameters.handleEndPoll(
  HandleEndPollOptions(
    pollId: 'poll-id',
    parameters: parameters,
  ),
);

// Access poll data
final polls = parameters.polls;
final activePoll = polls.firstWhere((p) => p.status == 'active');

Breakout Rooms Methods

// Create breakout rooms
parameters.createBreakoutRooms(
  CreateBreakoutRoomsOptions(
    numberOfRooms: 3,
    participants: participants,
    parameters: parameters,
  ),
);

// Assign participant to room
parameters.assignParticipantToRoom(
  AssignParticipantToRoomOptions(
    participantId: 'participant-id',
    roomIndex: 0,
    parameters: parameters,
  ),
);

// Start breakout rooms
parameters.startBreakoutRooms(
  StartBreakoutRoomsOptions(parameters: parameters),
);

// Stop breakout rooms
parameters.stopBreakoutRooms(
  StopBreakoutRoomsOptions(parameters: parameters),
);

// Access breakout room data
final breakoutRooms = parameters.breakoutRooms;
final currentRoom = parameters.currentBreakoutRoom;

Utility Methods

// Format large numbers
final formatted = formatNumber(1250000); // Returns "1.25M"

// Sleep/delay
await sleep(milliseconds: 1000);

// Check permissions
final hasPermission = await checkPermission(
  CheckPermissionOptions(
    permissionType: 'camera', // or 'audio'
    parameters: parameters,
  ),
);

// Update display settings
parameters.updateMainWindow(true); // Show/hide main window

// Navigate between pages
parameters.switchVideoAlt(
  SwitchVideoAltOptions(parameters: parameters),
);

// Trigger layout recalculation
parameters.onScreenChanges(
  OnScreenChangesOptions(
    changed: true,
    parameters: parameters,
  ),
);

Advanced WebRTC Methods

For developers who need low-level control:

// Connect to socket server
await connectSocket(
  ConnectSocketOptions(
    apiUserName: credentials.apiUserName,
    apiKey: credentials.apiKey,
    apiToken: token,
    link: serverLink,
  ),
);

// Create device (mediasoup device)
await createDeviceClient(
  CreateDeviceClientOptions(parameters: parameters),
);

// Handle transport connections
await connectSendTransportVideo(
  ConnectSendTransportVideoOptions(parameters: parameters),
);

await connectSendTransportAudio(
  ConnectSendTransportAudioOptions(parameters: parameters),
);

// Resume/pause consumer transports
await processConsumerTransports(
  ProcessConsumerTransportsOptions(
    consumerTransports: parameters.consumerTransports,
    lStreams: parameters.lStreams,
    parameters: parameters,
  ),
);

Complete method documentation: Visit mediasfu.com/flutter for detailed documentation on all 200+ methods.


Customization & Styling

Make MediaSFU match your brand and design requirements.

1. Customizing Pre-Join Page

Replace the default pre-join page with your own:

Widget myCustomPreJoinPage({
  PreJoinPageOptions? options,
  required Credentials credentials,
}) {
  return Scaffold(
    backgroundColor: Colors.blue[50],
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // Your logo
          Image.asset('assets/logo.png', width: 150),
          SizedBox(height: 40),
          
          // Custom welcome text
          Text(
            'Welcome to ${credentials.apiUserName}',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          
          SizedBox(height: 20),
          
          // Custom join button
          ElevatedButton(
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.blue,
              padding: EdgeInsets.symmetric(horizontal: 48, vertical: 16),
            ),
            onPressed: () {
              // Validate and join
              if (options != null) {
                options.parameters.updateValidated(true);
              }
            },
            child: Text('Join Meeting', style: TextStyle(fontSize: 18)),
          ),
        ],
      ),
    ),
  );
}

// Use it
final options = MediasfuGenericOptions(
  credentials: credentials,
  preJoinPageWidget: ({PreJoinPageOptions? options}) {
    return myCustomPreJoinPage(
      options: options,
      credentials: credentials,
    );
  },
);

2. Custom Control Buttons

Create your own control bar:

Widget myCustomControls(MediasfuParameters parameters) {
  return Container(
    padding: EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: Colors.black.withOpacity(0.8),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        // Custom video button
        IconButton(
          icon: Icon(
            parameters.videoAlreadyOn ? Icons.videocam : Icons.videocam_off,
            color: Colors.white,
          ),
          onPressed: () {
            parameters.clickVideo(
              ClickVideoOptions(parameters: parameters),
            );
          },
        ),
        
        // Custom audio button
        IconButton(
          icon: Icon(
            parameters.audioAlreadyOn ? Icons.mic : Icons.mic_off,
            color: Colors.white,
          ),
          onPressed: () {
            parameters.clickAudio(
              ClickAudioOptions(parameters: parameters),
            );
          },
        ),
        
        // Custom screen share button
        IconButton(
          icon: Icon(
            parameters.screenAlreadyOn ? Icons.stop_screen_share : Icons.screen_share,
            color: Colors.white,
          ),
          onPressed: () {
            parameters.clickScreenShare(
              ClickScreenShareOptions(parameters: parameters),
            );
          },
        ),
        
        // Add more custom buttons...
      ],
    ),
  );
}

3. Themed Components

Apply your app's theme to MediaSFU components:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My Video App',
      theme: ThemeData(
        primarySwatch: Colors.purple,
        scaffoldBackgroundColor: Colors.grey[100],
        
        // Button theme
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.purple,
            foregroundColor: Colors.white,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(8),
            ),
          ),
        ),
        
        // Icon theme
        iconTheme: IconThemeData(
          color: Colors.purple,
          size: 24,
        ),
        
        // Text theme
        textTheme: TextTheme(
          headlineMedium: TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
            color: Colors.purple[900],
          ),
        ),
      ),
      home: MediasfuGeneric(options: options),
    );
  }
}

4. Custom Video Card Styling

Customize how participant videos appear:

VideoCard(
  videoStream: stream,
  remoteProducerId: producerId,
  eventType: 'conference',
  forceFullDisplay: false,
  participant: participant,
  
  // Styling options
  backgroundColor: Colors.deepPurple[900]!,
  showControls: true,
  showInfo: true,
  rounded: true,
  border: Border.all(color: Colors.purple, width: 2),
  
  // Custom overlay
  overlayContent: Container(
    padding: EdgeInsets.all(8),
    child: Text(
      participant.name,
      style: TextStyle(
        color: Colors.white,
        fontWeight: FontWeight.bold,
        shadows: [Shadow(color: Colors.black, blurRadius: 4)],
      ),
    ),
  ),
  
  parameters: parameters,
)

5. Complete Custom Interface

Build a fully custom interface from scratch:

Widget myCompleteCustomUI({required MediasfuParameters parameters}) {
  return Scaffold(
    backgroundColor: Colors.black,
    
    // App bar with meeting info
    appBar: AppBar(
      backgroundColor: Colors.transparent,
      title: Row(
        children: [
          Icon(Icons.videocam, color: Colors.red),
          SizedBox(width: 8),
          Text('Live Meeting'),
          Spacer(),
          Text(
            '${parameters.participantsCounter} participants',
            style: TextStyle(fontSize: 14),
          ),
        ],
      ),
    ),
    
    body: Column(
      children: [
        // Main video area (80% of screen)
        Expanded(
          flex: 8,
          child: parameters.screenAlreadyOn
              ? FlexibleVideo(
                  customWidth: double.infinity,
                  customHeight: double.infinity,
                  parameters: parameters,
                )
              : FlexibleGrid(
                  customWidth: double.infinity,
                  customHeight: double.infinity,
                  parameters: parameters,
                ),
        ),
        
        // Control bar
        Container(
          height: 80,
          padding: EdgeInsets.symmetric(horizontal: 24),
          decoration: BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [
                Colors.transparent,
                Colors.black.withOpacity(0.8),
              ],
            ),
          ),
          child: myCustomControls(parameters),
        ),
        
        // Participant strip (10% of screen)
        Container(
          height: 100,
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            itemCount: parameters.participants.length,
            itemBuilder: (context, index) {
              final participant = parameters.participants[index];
              return Container(
                width: 100,
                margin: EdgeInsets.all(4),
                child: MiniCard(
                  participant: participant,
                  parameters: parameters,
                ),
              );
            },
          ),
        ),
      ],
    ),
    
    // Floating action buttons
    floatingActionButton: Column(
      mainAxisAlignment: MainAxisAlignment.end,
      children: [
        FloatingActionButton(
          heroTag: 'chat',
          mini: true,
          onPressed: () {
            parameters.updateIsMessagesModalVisible(true);
          },
          child: Icon(Icons.chat),
        ),
        SizedBox(height: 8),
        FloatingActionButton(
          heroTag: 'participants',
          mini: true,
          onPressed: () {
            parameters.updateIsParticipantsModalVisible(true);
          },
          child: Icon(Icons.people),
        ),
      ],
    ),
  );
}

// Use your custom UI
final options = MediasfuGenericOptions(
  credentials: credentials,
  customComponent: myCompleteCustomUI,
);

6. Responsive Design

Make your interface adapt to different screen sizes:

Widget buildResponsiveLayout(BuildContext context, MediasfuParameters parameters) {
  final screenSize = MediaQuery.of(context).size;
  final isTablet = screenSize.width > 600;
  final isDesktop = screenSize.width > 1200;
  
  if (isDesktop) {
    // Desktop layout: side-by-side
    return Row(
      children: [
        // Main content
        Expanded(
          flex: 3,
          child: FlexibleVideo(
            customWidth: double.infinity,
            customHeight: double.infinity,
            parameters: parameters,
          ),
        ),
        
        // Sidebar with participants
        Container(
          width: 300,
          child: Column(
            children: [
              Expanded(
                child: ParticipantsModal(parameters: parameters),
              ),
              Container(
                height: 200,
                child: MessagesModal(parameters: parameters),
              ),
            ],
          ),
        ),
      ],
    );
  } else if (isTablet) {
    // Tablet layout: stacked with controls on side
    return Row(
      children: [
        Expanded(
          child: Column(
            children: [
              Expanded(
                flex: 3,
                child: FlexibleVideo(
                  customWidth: double.infinity,
                  customHeight: double.infinity,
                  parameters: parameters,
                ),
              ),
              Expanded(
                child: FlexibleGrid(
                  customWidth: double.infinity,
                  customHeight: double.infinity,
                  parameters: parameters,
                ),
              ),
            ],
          ),
        ),
        Container(
          width: 80,
          child: ControlButtonsComponent(
            parameters: parameters,
            position: 'right',
          ),
        ),
      ],
    );
  } else {
    // Mobile layout: full screen with floating controls
    return Stack(
      children: [
        FlexibleVideo(
          customWidth: screenSize.width,
          customHeight: screenSize.height,
          parameters: parameters,
        ),
        Positioned(
          bottom: 20,
          left: 0,
          right: 0,
          child: Center(
            child: ControlButtonsComponentTouch(
              parameters: parameters,
            ),
          ),
        ),
      ],
    );
  }
}

7. Dark/Light Mode Support

class ThemedMediaSFU extends StatelessWidget {
  final bool isDarkMode;
  
  const ThemedMediaSFU({Key? key, required this.isDarkMode}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: isDarkMode
          ? ThemeData.dark().copyWith(
              primaryColor: Colors.deepPurple,
              scaffoldBackgroundColor: Colors.grey[900],
            )
          : ThemeData.light().copyWith(
              primaryColor: Colors.blue,
              scaffoldBackgroundColor: Colors.white,
            ),
      home: MediasfuGeneric(
        options: MediasfuGenericOptions(
          credentials: credentials,
          // MediaSFU will inherit theme colors
        ),
      ),
    );
  }
}

πŸ“š Complete Example: Production-Ready Custom App

Here's a complete example combining everything we've learned:

import 'package:flutter/material.dart';
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

void main() {
  runApp(const MyVideoApp());
}

class MyVideoApp extends StatelessWidget {
  const MyVideoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My Custom Video App',
      theme: ThemeData(
        primarySwatch: Colors.deepPurple,
        useMaterial3: true,
      ),
      home: const VideoRoomPage(),
    );
  }
}

class VideoRoomPage extends StatefulWidget {
  const VideoRoomPage({super.key});

  @override
  State<VideoRoomPage> createState() => _VideoRoomPageState();
}

class _VideoRoomPageState extends State<VideoRoomPage> {
  final ValueNotifier<MediasfuParameters?> sourceParameters = ValueNotifier(null);
  
  @override
  Widget build(BuildContext context) {
    final credentials = Credentials(
      apiUserName: 'your_username',
      apiKey: 'your_api_key',
    );

    // Build custom UI
    Widget customUI({required MediasfuParameters parameters}) {
      return Scaffold(
        backgroundColor: Colors.black,
        body: SafeArea(
          child: Column(
            children: [
              // Header
              _buildHeader(parameters),
              
              // Main video area
              Expanded(
                child: _buildMainContent(parameters),
              ),
              
              // Controls
              _buildControls(parameters),
            ],
          ),
        ),
      );
    }

    final options = MediasfuGenericOptions(
      credentials: credentials,
      customComponent: customUI,
      sourceParameters: sourceParameters.value,
      updateSourceParameters: (params) {
        sourceParameters.value = params;
      },
    );

    return MediasfuGeneric(options: options);
  }

  Widget _buildHeader(MediasfuParameters parameters) {
    return Container(
      padding: EdgeInsets.all(16),
      color: Colors.deepPurple,
      child: Row(
        children: [
          Icon(Icons.videocam, color: Colors.white),
          SizedBox(width: 8),
          Text(
            parameters.roomName,
            style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
          ),
          Spacer(),
          Chip(
            label: Text('${parameters.participantsCounter}'),
            avatar: Icon(Icons.people, size: 18),
          ),
        ],
      ),
    );
  }

  Widget _buildMainContent(MediasfuParameters parameters) {
    return FlexibleVideo(
      customWidth: double.infinity,
      customHeight: double.infinity,
      parameters: parameters,
    );
  }

  Widget _buildControls(MediasfuParameters parameters) {
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
          colors: [Colors.transparent, Colors.black87],
        ),
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          _controlButton(
            icon: parameters.videoAlreadyOn ? Icons.videocam : Icons.videocam_off,
            label: 'Video',
            onTap: () => parameters.clickVideo(ClickVideoOptions(parameters: parameters)),
            isActive: parameters.videoAlreadyOn,
          ),
          _controlButton(
            icon: parameters.audioAlreadyOn ? Icons.mic : Icons.mic_off,
            label: 'Audio',
            onTap: () => parameters.clickAudio(ClickAudioOptions(parameters: parameters)),
            isActive: parameters.audioAlreadyOn,
          ),
          _controlButton(
            icon: Icons.screen_share,
            label: 'Share',
            onTap: () => parameters.clickScreenShare(ClickScreenShareOptions(parameters: parameters)),
            isActive: parameters.screenAlreadyOn,
          ),
          _controlButton(
            icon: Icons.chat,
            label: 'Chat',
            onTap: () => parameters.updateIsMessagesModalVisible(true),
            isActive: false,
          ),
        ],
      ),
    );
  }

  Widget _controlButton({
    required IconData icon,
    required String label,
    required VoidCallback onTap,
    required bool isActive,
  }) {
    return InkWell(
      onTap: onTap,
      child: Container(
        padding: EdgeInsets.all(12),
        decoration: BoxDecoration(
          color: isActive ? Colors.deepPurple : Colors.white24,
          borderRadius: BorderRadius.circular(12),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(icon, color: Colors.white),
            SizedBox(height: 4),
            Text(label, style: TextStyle(color: Colors.white, fontSize: 12)),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    sourceParameters.dispose();
    super.dispose();
  }
}

πŸŽ“ Learning Path Recommendations

  1. Beginners: Start with Mode 1 (Default UI) to understand how everything works
  2. Intermediate: Experiment with custom pre-join pages and themed components
  3. Advanced: Build custom UIs with Mode 2 or Mode 3, integrate with state management

πŸ“– Additional Resources


Legacy Documentation

The following sections contain the original documentation for reference. For the structured guide above, please refer to the Flutter SDK Guide section.

Initial Setup and Installation

  1. Add Dependency

    Add mediasfu_sdk to your pubspec.yaml:

    dependencies:
      flutter:
        sdk: flutter
      mediasfu_sdk: ^2.0.0
    
  2. Install Packages

    Run the following command to install the dependencies:

    flutter pub get
    

Post-Installation

After installing the dependencies, ensure that you have configured your project correctly for both iOS and Android platforms. Follow the Configuration section below to set up the necessary permissions and settings.

Introduction

MediaSFU is a 2-page application consisting of a prejoin/welcome page and the main events room page. This guide will walk you through the basic usage of the module for setting up these pages.

Documentation Reference

For comprehensive documentation on the available methods, components, and functions, please visit mediasfu.com. This resource provides detailed information for this guide and additional documentation.

Prebuilt Event Rooms

MediaSFU provides prebuilt event rooms for various purposes. These rooms are rendered as full pages and can be easily imported and used in your application. Here are the available prebuilt event rooms:

  1. MediasfuGeneric: A generic event room suitable for various types of events.
  2. MediasfuBroadcast: A room optimized for broadcasting events.
  3. MediasfuWebinar: Specifically designed for hosting webinars.
  4. MediasfuConference: Ideal for hosting conferences.
  5. MediasfuChat: A room tailored for interactive chat sessions.

Users can easily pick an interface and render it in their app.

If no API credentials are provided, a default home page will be displayed where users can scan or manually enter the event details.

To use these prebuilt event rooms, simply import them into your application:

import 'package:mediasfu_sdk/mediasfu_sdk.dart';

Three Powerful Usage Modes

MediaSFU provides three flexible usage modes to fit any development need, from dead simple implementation to advanced customization. All modes give you access to the same powerful media infrastructure through sourceParameters.

🎯 Mode 1: Default MediaSFU UI - Dead Simple

Perfect for rapid prototyping and production apps that want MediaSFU's polished interface out-of-the-box.

// Just pass your credentials - everything else is handled automatically
final options = MediasfuGenericOptions(
  credentials: credentials,
);

return MediasfuGeneric(options: options);

What you get:

  • βœ… Automatic Layout: All media organized in FlexibleGrid and FlexibleVideo
  • βœ… Smart Audio Management: Audio participants contained in AudioGrid
  • βœ… Built-in Controls: Mute, video toggle, screen share, chat, polls, breakout rooms
  • βœ… Responsive Design: Adapts to any screen size and orientation
  • βœ… Professional UI: Polished interface ready for production

πŸ› οΈ Mode 2: Custom UI with MediaSFU Backend - Ultimate Flexibility

Build your own interface while leveraging MediaSFU's powerful media infrastructure.

final options = MediasfuGenericOptions(
  credentials: credentials,
  returnUI: false, // Use your custom UI
  sourceParameters: sourceParameters.value,
  updateSourceParameters: updateSourceParameters,
);

// Access everything through sourceParameters
sourceParameters.value?.clickVideo(ClickVideoOptions(parameters: sourceParameters.value!));
sourceParameters.value?.clickAudio(ClickAudioOptions(parameters: sourceParameters.value!));

What you get:

  • βœ… Full Control: Design any UI/UX you want
  • βœ… Complete Access: All MediaSFU functionality through sourceParameters
  • βœ… Media Components: Access FlexibleGrid, FlexibleVideo, AudioGrid as needed
  • βœ… Real-time State: Live participant data, media streams, chat messages
  • βœ… Zero Backend Work: All media infrastructure handled by MediaSFU

🎨 Mode 3: Custom Component Replacement - Best of Both Worlds

Replace the entire MediaSFU interface while keeping all the backend functionality.

Widget myCustomInterface({required MediasfuParameters parameters}) {
  return Scaffold(
    body: Column(
      children: [
        // Your custom header
        MyCustomHeader(roomName: parameters.roomName),
        
        // Use MediaSFU's flexible components in your layout
        Expanded(
          child: FlexibleVideo(
            customWidth: MediaQuery.of(context).size.width,
            customHeight: 400,
            parameters: parameters,
          ),
        ),
        
        // Your custom controls with MediaSFU functionality
        MyCustomControls(
          onToggleVideo: () => parameters.clickVideo(ClickVideoOptions(parameters: parameters)),
          onToggleAudio: () => parameters.clickAudio(ClickAudioOptions(parameters: parameters)),
        ),
      ],
    ),
  );
}

final options = MediasfuGenericOptions(
  credentials: credentials,
  customComponent: myCustomInterface,
);

What you get:

  • βœ… Custom Design: Your branded interface with your UX decisions
  • βœ… MediaSFU Components: Reuse FlexibleGrid, FlexibleVideo, AudioGrid in your layout
  • βœ… Full Functionality: All MediaSFU features accessible through parameters
  • βœ… Minimal Code: Focus on design, not infrastructure

πŸ“Š All Modes Include the Same Powerful Infrastructure

No matter which mode you choose, you get access to:

Media Management:

  • FlexibleGrid: Automatically arranges video participants in optimal grid layouts
  • FlexibleVideo: Manages main video display with smooth transitions
  • AudioGrid: Organizes audio-only participants with visual indicators

Real-time Features:

  • Live Participants: Dynamic participant list with join/leave notifications
  • Chat System: Direct and group messaging with emoji support
  • Screen Sharing: High-quality screen sharing with audience controls
  • Breakout Rooms: Create and manage sub-meetings within your session
  • Polls & Surveys: Real-time audience engagement tools

Advanced Controls:

  • Media Settings: Camera/microphone selection, quality settings
  • Recording: Cloud recording with custom layouts and branding
  • Broadcasting: Live streaming to social platforms
  • Whiteboard: Collaborative drawing and annotation tools

πŸš€ Start Simple, Scale Complex

// Start with this (5 lines)
final options = MediasfuGenericOptions(credentials: credentials);
return MediasfuGeneric(options: options);

// Scale to this when you need custom UI
final options = MediasfuGenericOptions(
  credentials: credentials,
  customComponent: myBrandedInterface, // Your complete custom UI
  // All the same powerful features, your design
);

The beauty of MediaSFU: You can start with the default UI and gradually customize as your needs grow, without ever losing functionality or starting over.


Simplest Usage

The simplest way to use MediaSFU is by directly rendering a predefined event room component, such as MediasfuGeneric. This allows you to quickly integrate real-time communication features without extensive configuration.

import 'package:flutter/material.dart';
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
   // Pass the MediasfuGenericOptions to the MediasfuGeneric component
    final MediasfuGenericOptions options = MediasfuGenericOptions(
    );

    return MaterialApp(
      title: 'Mediasfu Generic',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuGeneric(options: options),
    );
  }
}

Programmatically Fetching Tokens

If you prefer to fetch the required tokens programmatically without visiting MediaSFU's website, you can use the PreJoinPage component and pass your credentials as part of the options.

import 'package:flutter/material.dart';
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // Mediasfu account credentials
    // Replace 'your_api_username' and 'your_api_key' with your actual credentials
    final credentials = Credentials(
      apiUserName: 'your_api_username',
      apiKey: 'your_api_key',
    );

    final MediasfuGenericOptions options = MediasfuGenericOptions(
      credentials: credentials,
    );

    return MaterialApp(
      title: 'Mediasfu Generic',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuGeneric(options: options),
    );
  }
}

Preview of Welcome Page

Preview of Welcome Page

Preview of Prejoin Page

Preview of Prejoin Page

Custom Welcome/Prejoin Page

Alternatively, you can design your own welcome/prejoin page to tailor the user experience to your application's needs. The primary function of this page is to fetch user tokens from MediaSFU's API and establish a connection using the returned link if the credentials are valid.

Passing a Custom Widget

MediaSFU allows you to pass a custom widget to replace the default prejoin page. This flexibility enables you to design a personalized interface that aligns with your app's branding and user flow.

Implementing a Custom Prejoin Page

To implement a custom prejoin page, follow these steps:

Step 1: Define Your Custom Prejoin Page Widget

Create a custom widget that defines the UI and behavior of your prejoin page. This widget should interact with the provided parameters to handle user input and establish connections.

import 'package:flutter/material.dart';
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

/// A custom pre-join page widget that can be used instead of the default Mediasfu pre-join page.
///
/// This widget displays a personalized welcome message and includes a button to proceed to the session.
///
/// **Note:** Ensure this widget is passed to [MediasfuGenericOptions] only when you intend to use a custom pre-join page.
Widget myCustomPreJoinPage({
  PreJoinPageOptions? options,
  required Credentials credentials,
}) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Welcome to Mediasfu'),
    ),
    body: Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            'Hello, ${credentials.apiUserName}!',
            style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 20),
          const Text(
            'Get ready to join your session.',
            style: TextStyle(fontSize: 18),
          ),
          const SizedBox(height: 40),
          ElevatedButton(
            onPressed: () {
              // Proceed to the session by updating the validation status
              options?.parameters.updateValidated(true);
            },
            child: const Text('Join Now'),
          ),
        ],
      ),
    ),
  );
}

Step 2: Integrate the Custom Prejoin Page into Your Application

Update your main application widget to utilize the custom prejoin page by passing it to the MediasfuGenericOptions.

import 'package:flutter/material.dart';
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

void main() {
  runApp(const MyApp());
}

/// The main application widget for MediaSFU.
///
/// This widget initializes the necessary credentials and configuration for the MediaSFU application,
/// including options for using seed data to generate random participants and messages.
/// It allows selecting different event types such as broadcast, chat, webinar, and conference.
class MyApp extends StatelessWidget {
  /// Constructs a new instance of [MyApp].
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // MediaSFU account credentials
    // Replace 'your_api_username' and 'your_api_key' with your actual credentials
    final credentials = Credentials(
      apiUserName: 'your_api_username',
      apiKey: 'your_api_key',
    );

    
    // === Main Activated Example ===
    // Default to MediasfuGeneric with credentials
    // This will render the pre-join page requiring credentials
    final MediasfuGenericOptions options = MediasfuGenericOptions(
      credentials: credentials,

      preJoinPageWidget: (
          {PreJoinPageOptions? options, required Credentials credentials}) {
        return myCustomPreJoinPage(
          options: options,
          credentials: credentials,
        );
      },

    );

    return MaterialApp(
      title: 'Mediasfu Generic',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuGeneric(options: options),
    );
  }
}

Mediasfu Prejoin Page Core Parts

The default prejoin page included in the mediasfu_sdk package implements the logic for:

  1. Connection Validation:
    • Ensures valid credentials are provided.
    • Implements rate-limiting to prevent abuse.
  2. Room Creation & Joining:
    • Supports creating new rooms or joining existing ones.
    • Makes requests to MediaSFU’s backend API.
  3. Socket Connection:
    • Establishes a socket connection to MediaSFU servers upon successful validation.
  4. Alerts & Error Handling:
    • Displays alerts for invalid credentials, rate limits, and other errors.

Core logic within the default PreJoinPage includes:

class PreJoinPage extends StatelessWidget {
  final PreJoinPageOptions? options;
  final Credentials credentials;

  PreJoinPage({required this.options, required this.credentials});

  @override
  Widget build(BuildContext context) {
    // Core logic and integration
    // Includes validation, API calls, and socket connection

    return Container(); // Default UI, replaceable with a custom widget
  }
}

By passing your own widget as shown earlier, you can override this default behavior while retaining the logic and functionality provided by the PreJoinPageOptions and MediasfuGenericOptions. This ensures your app's custom design seamlessly integrates with MediaSFU's backend.

IP Blockage Warning And Local UI Development

Note: Local UI Development Mode is deprecated. Rather use your own Community Edition (CE) server for UI development and testing. You can later switch to MediaSFU Cloud for production. Nothing changes in the codebase, and you can use the same code for both environments.

Entering the event room without the correct credentials may result in IP blockage, as the page automatically attempts to connect with MediaSFU servers, which rate limit bad requests based on IP address.

If users attempt to enter the event room without valid credentials or tokens, it may lead to IP blockage due to MediaSFU servers' rate limiting mechanism. To avoid unintentional connections to MediaSFU servers during UI development, users can pass the useLocalUIMode parameter as true.

In this mode, the package will operate locally without making requests to MediaSFU servers. However, to render certain UI elements such as messages, participants, requests, etc., users may need to provide seed data. They can achieve this by importing random data generators and passing the generated data to the event room component.

Example for Broadcast Room

// ignore_for_file: unused_shown_name, unused_import
import 'package:flutter/material.dart';
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

void main() {
  runApp(const MyApp());
}

/// A custom pre-join page widget that can be used instead of the default MediaSFU pre-join page.
///
/// This widget displays a personalized welcome message and includes a button to proceed to the broadcast session.
///
/// **Note:** Ensure this widget is passed to [MediasfuBroadcastOptions] only when you intend to use a custom pre-join page.
Widget myCustomPreJoinPage({
  PreJoinPageOptions? options,
  required Credentials credentials,
}) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Welcome to MediaSFU Broadcast'),
    ),
    body: Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            'Hello, ${credentials.apiUserName}!',
            style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 20),
          const Text(
            'Get ready to join your broadcast session.',
            style: TextStyle(fontSize: 18),
          ),
          const SizedBox(height: 40),
          ElevatedButton(
            onPressed: () {
              // Proceed to the session by updating the validation status
              if (options != null) {
                options.parameters.updateValidated(true);
              }
            },
            child: const Text('Join Now'),
          ),
        ],
      ),
    ),
  );
}

/// The main application widget for MediaSFU Broadcast.
///
/// This widget initializes the necessary credentials and configuration for the MediaSFU Broadcast application,
/// including options for creating and joining broadcast rooms.
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  // ignore: library_private_types_in_public_api
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // Provides access to the source parameters if not using the default UI (returnUI = false in options). See more in the guide.
  final ValueNotifier<MediasfuParameters?> sourceParameters =
      ValueNotifier(null);

  // Update function to update source parameters if not using the default UI (returnUI = false in options). See more in the guide.
  void updateSourceParameters(MediasfuParameters? parameters) {
    sourceParameters.value = parameters;
    // _onSourceParametersChanged(parameters);
  }

  // =========================================================
  //   CUSTOM USAGE OF SOURCE PARAMETERS FOR NON-DEFAULT UI
  // =========================================================

  // trigger click video after 5 seconds; this is just an example, best usage is building a custom UI
  // to handle these functionalities with buttons clicks, etc.
  //
  void triggerClickVideo() {
    Future.delayed(const Duration(seconds: 5), () {
      sourceParameters.value
          ?.clickVideo(ClickVideoOptions(parameters: sourceParameters.value!));
    });
  }

  @override
  void initState() {
    super.initState();

    // Attach the listener
    sourceParameters.addListener(() {
      _onSourceParametersChanged(sourceParameters.value);
    });

    // trigger click video after 5 seconds, uncomment to trigger
    // triggerClickVideo();
  }

  @override
  void dispose() {
    // Detach the listener; irrelevant if not 'returnUI = false'
    // Comment out if not using sourceParameters
    sourceParameters.removeListener(() {
      _onSourceParametersChanged(sourceParameters.value);
    });
    sourceParameters.dispose();
    super.dispose();
  }

  // ==============================================================
  //  SOURCE PARAMETERS LISTENER (REQUIRED ONLY IF USING CUSTOM UI)
  // ==============================================================

  /// Listener for changes in sourceParameters.
  /// Prints the updated parameters to the console whenever they change.
  void _onSourceParametersChanged(MediasfuParameters? parameters) {
    if (parameters != null) {
      // if (kDebugMode) {
      //   print('Source Parameters Updated: ${parameters.roomName}');
      // }
      // Add custom logic here
    }
  }

  @override
  Widget build(BuildContext context) {
    // =========================================================
    //                API CREDENTIALS CONFIGURATION
    // =========================================================

    /**
     * Scenario A: Not using MediaSFU Cloud at all.
     * - Dummy credentials are needed to render PreJoinPage. 
     * Example:
     */
    /*
    final credentials = Credentials(
      apiUserName: 'dummyUsr',
      apiKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    );
    final localLink = 'http://your-ce-server.com'; // e.g., http://localhost:3000
    final connectMediaSFU = false; // Set to false if not using MediaSFU Cloud
    */

    /**
     * Scenario B: Using MediaSFU CE + MediaSFU Cloud for Egress only.
     * - Use dummy credentials (8 chars for userName, 64 chars for apiKey).
     * - Your CE backend will forward requests with your real credentials.
     */
    /*
    final credentials = Credentials(
      apiUserName: 'dummyUsr',
      apiKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    );
    final localLink = 'http://your-ce-server.com'; // e.g., http://localhost:3000
    final connectMediaSFU = true; // Set to true if using MediaSFU Cloud for egress
    */

    /**
     * Scenario C: Using MediaSFU Cloud without your own server.
     * - For development, use your actual or dummy credentials.
     * - In production, securely handle credentials server-side and use custom room functions for joining and creating rooms.
     */
    final credentials = Credentials(
      apiUserName: 'yourDevUser', // 8 chars recommended for dummy
      apiKey:
          'yourDevApiKey1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', // 64 chars
    );
    const localLink = ''; // Leave empty if not using your own server
    const connectMediaSFU =
        true; // Set to true if using MediaSFU Cloud since localLink is empty

    // =========================================================
    //                CUSTOM ROOM FUNCTIONS (OPTIONAL)
    // =========================================================

    /**
     * To securely forward requests to MediaSFU:
     * - Implement custom `createMediaSFURoom` and `joinMediaSFURoom` functions.
     * - These functions send requests to your server, which then communicates with MediaSFU Cloud.
     *
     * Already implemented `createRoomOnMediaSFU` and `joinRoomOnMediaSFU` are examples.
     */

    // =========================================================
    //                    RENDER COMPONENT
    // =========================================================

    /**
     * The MediasfuBroadcast component is used to handle broadcast events.
     * It allows you to create and join broadcast rooms with the provided options.
     * 
     * **Note:** Ensure that real credentials are not exposed in the frontend.
     * Use HTTPS and secure backend endpoints for production.
     */

    // === Main Activated Example ===
    // Configured for MediasfuBroadcast with credentials and custom pre-join page
    bool returnUI = true; // Set to false to use a custom UI

    // Example noUIPreJoinOptions for creating a room
    final CreateMediaSFURoomOptions noUIPreJoinOptionsCreate =
        CreateMediaSFURoomOptions(
      action: 'create',
      capacity: 10,
      duration: 15,
      eventType: EventType.broadcast,
      userName: 'Prince',
    );

    // Example noUIPreJoinOptions for joining a room
    /*
    final JoinMediaSFURoomOptions noUIPreJoinOptionsJoin = JoinMediaSFURoomOptions(
      action: 'join',
      userName: 'Prince',
      meetingID: 'yourMeetingID',
    );
    */

    final MediasfuBroadcastOptions options = MediasfuBroadcastOptions(
      // Uncomment the following lines to use a custom pre-join page
      /*
      preJoinPageWidget: ({PreJoinPageOptions? options}) {
        return myCustomPreJoinPage(
          credentials: credentials,
        );
      },
      */

      // Pass your Credentials if you will be using MediaSFU Cloud for recording and other egress processes or as your primary server
      credentials: credentials,

      // Use your own MediaSFU server link if you are using MediaSFU Community Edition
      // Pass your credentials if you will be using MediaSFU Cloud for recording and other egress processes or as your primary server
      connectMediaSFU: connectMediaSFU,

      // Specify your own MediaSFU Community Edition server if applicable
      localLink: localLink, // e.g., 'http://localhost:3000'

      // Set to false to use a custom UI, true to use the default MediaSFU UI
      returnUI: returnUI,

      // Provide pre-join options if not using the default UI (if creating a room)
      noUIPreJoinOptionsCreate: !returnUI ? noUIPreJoinOptionsCreate : null,

      // Provide pre-join options if not using the default UI (if joining a room)
      // noUIPreJoinOptionsJoin: !returnUI ? noUIPreJoinOptionsJoin : null,

      // Manage source parameters if not using the default UI
      sourceParameters: !returnUI ? sourceParameters.value : null,

      // Update source parameters if not using the default UI
      updateSourceParameters: !returnUI ? updateSourceParameters : null,

      // Provide custom room functions if not using the default functions
      createMediaSFURoom: createRoomOnMediaSFU,
      joinMediaSFURoom: joinRoomOnMediaSFU,
    );

    return MaterialApp(
      title: 'MediaSFU Broadcast',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuBroadcast(options: options),
    );
  }
}

/**
 * =======================
 * ====== EXTRA NOTES ======
 * =======================
 *
 * ### Handling Core Methods
 * With `sourceParameters`, you can access core methods such as `clickVideo` and `clickAudio`:
 *
 * ```dart
 * // Example of toggling video
 * void toggleVideo(SourceParameters sourceParameters) {
 *   sourceParameters.clickVideo({...sourceParameters});
 * }
 *
 * // Example of toggling audio
 * void toggleAudio(SourceParameters sourceParameters) {
 *   sourceParameters.clickAudio({...sourceParameters});
 * }
 * ```
 *
 * This allows your custom UI to interact with MediaSFU's functionalities seamlessly.
 *
 * ### Seed Data (Deprecated)
 * The seed data functionality is deprecated and maintained only for legacy purposes.
 * It is recommended to avoid using it in new implementations.
 *
 * ### Security Considerations
 * - **Protect API Credentials:** Ensure that API credentials are not exposed in the frontend. Use environment variables and secure backend services to handle sensitive information.
 * - **Use HTTPS:** Always use HTTPS to secure data transmission between the client and server.
 * - **Validate Inputs:** Implement proper validation and error handling on both client and server sides to prevent malicious inputs.
 *
 * ### Custom Backend Example for MediaSFU CE
 * Below is an example of how to set up custom backend endpoints for creating and joining rooms using MediaSFU CE. Assume all unlisted imports are exported by the package:
 *
 * ```typescript
 * import express from 'express';
 * import fetch from 'node-fetch';
 * 
 * const app = express();
 * app.use(express.json());
 * 
 * app.post("/createRoom", async (req, res) => {
 *   try {
 *     const { apiUserName, apiKey } = req.headers.authorization.replace("Bearer ", "").split(":");
 *     if (!apiUserName || !apiKey || !verifyCredentials(apiUserName, apiKey)) {
 *       return res.status(401).json({ error: "Invalid credentials" });
 *     }
 * 
 *     const response = await fetch("https://mediasfu.com/v1/rooms/", {
 *       method: "POST",
 *       headers: {
 *         "Content-Type": "application/json",
 *         Authorization: `Bearer ${apiUserName}:${apiKey}`,
 *       },
 *       body: JSON.stringify(req.body),
 *     });
 * 
 *     const result = await response.json();
 *     res.status(response.status).json(result);
 *   } catch (error) {
 *     res.status(500).json({ error: "Internal server error" });
 *   }
 * });
 * 
 * app.post("/joinRoom", async (req, res) => {
 *   try {
 *     const { apiUserName, apiKey } = req.headers.authorization.replace("Bearer ", "").split(":");
 *     if (!apiUserName || !apiKey || !verifyCredentials(apiUserName, apiKey)) {
 *       return res.status(401).json({ error: "Invalid credentials" });
 *     }
 * 
 *     const response = await fetch("https://mediasfu.com/v1/rooms/join", {
 *       method: "POST",
 *       headers: {
 *         "Content-Type": "application/json",
 *         Authorization: `Bearer ${apiUserName}:${apiKey}`,
 *       },
 *       body: JSON.stringify(req.body),
 *     });
 * 
 *     const result = await response.json();
 *     res.status(response.status).json(result);
 *   } catch (error) {
 *     res.status(500).json({ error: "Internal server error" });
 *   }
 * });
 * ```
 *
 * ### Custom Room Function Implementation
 * Below are examples of how to implement custom functions for creating and joining rooms securely in Dart. Assume all unlisted imports are exported by the package:
 *
 * ```dart
 * Future<CreateJoinRoomResult> createRoomOnMediaSFU(
 *   CreateMediaSFURoomOptions options,
 * ) async {
 *   try {
 *     final payload = options.payload;
 *     final apiUserName = options.apiUserName;
 *     final apiKey = options.apiKey;
 *     String endpoint = 'https://mediasfu.com/v1/rooms/';
 * 
 *     if (options.localLink.isNotEmpty) {
 *       endpoint = '${options.localLink}/createRoom';
 *     }
 * 
 *     final response = await http.post(
 *       Uri.parse(endpoint),
 *       headers: {
 *         "Content-Type": "application/json",
 *         "Authorization": "Bearer $apiUserName:$apiKey",
 *       },
 *       body: jsonEncode(payload.toMap()),
 *     );
 * 
 *     if (response.statusCode == 200 || response.statusCode == 201) {
 *       final data = jsonDecode(response.body);
 *       return CreateJoinRoomResult(
 *         data: CreateJoinRoomResponse.fromJson(data),
 *         success: true,
 *       );
 *     } else {
 *       final error = jsonDecode(response.body);
 *       return CreateJoinRoomResult(
 *         data: CreateJoinRoomError.fromJson(error),
 *         success: false,
 *       );
 *     }
 *   } catch (_) {
 *     return CreateJoinRoomResult(
 *       data: CreateJoinRoomError(error: 'Unknown error'),
 *       success: false,
 *     );
 *   }
 * }
 * 
 * Future<CreateJoinRoomResult> joinRoomOnMediaSFU(
 *   JoinMediaSFURoomOptions options,
 * ) async {
 *   try {
 *     final payload = options.payload;
 *     final apiUserName = options.apiUserName;
 *     final apiKey = options.apiKey;
 *     String endpoint = 'https://mediasfu.com/v1/rooms/join';
 * 
 *     if (options.localLink.isNotEmpty) {
 *       endpoint = '${options.localLink}/joinRoom';
 *     }
 * 
 *     final response = await http.post(
 *       Uri.parse(endpoint),
 *       headers: {
 *         "Content-Type": "application/json",
 *         "Authorization": "Bearer $apiUserName:$apiKey",
 *       },
 *       body: jsonEncode(payload.toMap()),
 *     );
 * 
 *     if (response.statusCode == 200 || response.statusCode == 201) {
 *       final data = jsonDecode(response.body);
 *       return CreateJoinRoomResult(
 *         data: CreateJoinRoomResponse.fromJson(data),
 *         success: true,
 *       );
 *     } else {
 *       final error = jsonDecode(response.body);
 *       return CreateJoinRoomResult(
 *         data: CreateJoinRoomError.fromJson(error),
 *         success: false,
 *       );
 *     }
 *   } catch (_) {
 *     return CreateJoinRoomResult(
 *       data: CreateJoinRoomError(error: 'Unknown error'),
 *       success: false,
 *     );
 *   }
 * }
 * ```
 *
 * #### Assumptions:
 * - All unlisted imports such as `http`, `CreateMediaSFURoomOptions`, `JoinMediaSFURoomOptions`, `CreateJoinRoomResult`, `CreateJoinRoomResponse`, and `CreateJoinRoomError` are exported by the package.
 * - This structure ensures type safety, clear error handling, and flexibility for customization.
 *
 * ========================
 * ====== END OF GUIDE ======
 * ========================
 */

Example for Generic View


// ignore_for_file: unused_import, unused_shown_name
import 'package:flutter/material.dart';
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

void main() {
  runApp(const MyApp());
}

/// A custom pre-join page widget that can be used instead of the default Mediasfu pre-join page.
///
/// This widget displays a personalized welcome message and includes a button to proceed to the session.
///
/// **Note:** Ensure this widget is passed to [MediasfuGenericOptions] only when you intend to use a custom pre-join page.
Widget myCustomPreJoinPage({
  PreJoinPageOptions? options,
  required Credentials credentials,
}) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Welcome to MediaSFU'),
    ),
    body: Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            'Hello, ${credentials.apiUserName}!',
            style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 20),
          const Text(
            'Get ready to join your session.',
            style: TextStyle(fontSize: 18),
          ),
          const SizedBox(height: 40),
          ElevatedButton(
            onPressed: () {
              // Proceed to the session by updating the validation status
              if (options != null) {
                options.parameters.updateValidated(true);
              }
            },
            child: const Text('Join Now'),
          ),
        ],
      ),
    ),
  );
}

/// The main application widget for MediaSFU.
///
/// This widget initializes the necessary credentials and configuration for the MediaSFU application,
/// including options for using seed data to generate random participants and messages.
/// It allows selecting different event types such as broadcast, chat, webinar, and conference.
///
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  // ignore: library_private_types_in_public_api
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // Provides access to the source parameters if not using the default UI (returnUI = false in options). See more in the guide.
  final ValueNotifier<MediasfuParameters?> sourceParameters =
      ValueNotifier(null);

  // Update function to update source parameters if not using the default UI (returnUI = false in options). See more in the guide.
  void updateSourceParameters(MediasfuParameters? parameters) {
    sourceParameters.value = parameters;
    // _onSourceParametersChanged(parameters);
  }

  // =========================================================
  //   CUSTOM USAGE OF SOURCE PARAMETERS FOR NON-DEFAULT UI
  // =========================================================

  // trigger click video after 5 seconds; this is just an example, best usage is building a custom UI
  // to handle these functionalities with buttons clicks, etc.
  //
  void triggerClickVideo() {
    Future.delayed(const Duration(seconds: 5), () {
      sourceParameters.value
          ?.clickVideo(ClickVideoOptions(parameters: sourceParameters.value!));
    });
  }

  @override
  void initState() {
    super.initState();

    // Attach the listener
    sourceParameters.addListener(() {
      _onSourceParametersChanged(sourceParameters.value);
    });

    // trigger click video after 5 seconds, uncomment to trigger
    // triggerClickVideo();
  }

  @override
  void dispose() {
    // Detach the listener; irrelevant if not 'returnUI = false'
    // Comment out if not using sourceParameters
    sourceParameters.removeListener(() {
      _onSourceParametersChanged(sourceParameters.value);
    });
    sourceParameters.dispose();
    super.dispose();
  }

  // ==============================================================
  //  SOURCE PARAMETERS LISTENER (REQUIRED ONLY IF USING CUSTOM UI)
  // ==============================================================

  /// Listener for changes in sourceParameters.
  /// Prints the updated parameters to the console whenever they change.
  void _onSourceParametersChanged(MediasfuParameters? parameters) {
    if (parameters != null) {
      // if (kDebugMode) {
      //   print('Source Parameters Updated: ${parameters.roomName}');
      // }
      // Add custom logic here
    }
  }

  @override
  Widget build(BuildContext context) {
    // =========================================================
    //                API CREDENTIALS CONFIGURATION
    // =========================================================

    /**
     * Scenario A: Not using MediaSFU Cloud at all.
     * - Dummy credentials are needed to render PreJoinPage. 
     * Example:
     */
    /*
    final credentials = Credentials(
      apiUserName: 'dummyUsr',
      apiKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    );
    final localLink = 'http://your-ce-server.com'; // e.g., http://localhost:3000
    final connectMediaSFU = false; // Set to false if not using MediaSFU Cloud
    */

    /**
     * Scenario B: Using MediaSFU CE + MediaSFU Cloud for Egress only.
     * - Use dummy credentials (8 chars for userName, 64 chars for apiKey).
     * - Your CE backend will forward requests with your real credentials.
     */
    /*
    final credentials = Credentials(
      apiUserName: 'dummyUsr',
      apiKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
    );
    final localLink = 'http://your-ce-server.com'; // e.g., http://localhost:3000
    final connectMediaSFU = true; // Set to true if using MediaSFU Cloud for egress
    */

    /**
     * Scenario C: Using MediaSFU Cloud without your own server.
     * - For development, use your actual or dummy credentials.
     * - In production, securely handle credentials server-side and use custom room functions.
     */
    final credentials = Credentials(
      apiUserName: 'yourDevUser', // 8 chars recommended for dummy
      apiKey:
          'yourDevApiKey1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', // 64 chars
    );
    const localLink = ''; // Leave empty if not using your own server
    const connectMediaSFU =
        true; // Set to true if using MediaSFU Cloud since localLink is empty

    // =========================================================
    //                    UI RENDERING OPTIONS
    // =========================================================

    /**
     * If you want a fully custom UI (e.g., a custom layout inspired by WhatsApp):
     * 1. Set `returnUI = false` to prevent the default MediaSFU UI from rendering.
     * 2. Provide `noUIPreJoinOptions` to simulate what would have been entered on a pre-join page.
     * 3. Use `sourceParameters` and `updateSourceParameters` to access and update state/actions.
     * 4. No need for any of the above if you're using the default MediaSFU UI.
     */

    // Example noUIPreJoinOptions for creating a room
    final CreateMediaSFURoomOptions noUIPreJoinOptionsCreate =
        CreateMediaSFURoomOptions(
      action: 'create',
      capacity: 10,
      duration: 15,
      eventType: EventType.broadcast,
      userName: 'Prince',
    );

    // Example noUIPreJoinOptions for joining a room
    /*
    final JoinMediaSFURoomOptions noUIPreJoinOptionsJoin = JoinMediaSFURoomOptions(
      action: 'join',
      userName: 'Prince',
      meetingID: 'yourMeetingID',
    );
    */

    const bool returnUI =
        true; // Set to false for custom UI, true for default MediaSFU UI

    // State management using ValueNotifier (can be replaced with other state management solutions)
    final ValueNotifier<MediasfuParameters?> sourceParameters =
        ValueNotifier(null);

    // void updateSourceParameters(MediasfuParameters? data) {
    //   sourceParameters.value = data;
    // }

    // =========================================================
    //                CUSTOM ROOM FUNCTIONS (OPTIONAL)
    // =========================================================

    /**
     * To securely forward requests to MediaSFU:
     * - Implement custom `createMediaSFURoom` and `joinMediaSFURoom` functions.
     * - These functions send requests to your server, which then communicates with MediaSFU Cloud.
     *
     * Already implemented `createRoomOnMediaSFU` and `joinRoomOnMediaSFU` are examples.
     *
     * If using MediaSFU CE backend, ensure your server endpoints:
     * - Validate dummy credentials.
     * - Forward requests to mediasfu.com with real credentials.
     */

    // =========================================================
    //              CHOOSE A USE CASE / COMPONENT
    // =========================================================

    /**
     * Multiple components are available depending on your event type:
     * MediasfuBroadcast, MediasfuChat, MediasfuWebinar, MediasfuConference
     *
     * By default, we'll use MediasfuGeneric with custom settings.
     */

    // =========================================================
    //                    RENDER COMPONENT
    // =========================================================

    /**
     * The MediasfuGeneric component is used by default.
     * You can replace it with any other component based on your event type.
     * Example: MediasfuBroadcast(options: ...)
     * Example: MediasfuChat(options: ...)
     * Example: MediasfuWebinar(options: ...)
     * Example: MediasfuConference(options: ...)
     *
     * The PreJoinPage component is displayed if `returnUI` is true.
     * If `returnUI` is false, `noUIPreJoinOptions` is used as a substitute.
     * You can also use `sourceParameters` to interact with MediaSFU functionalities directly.
     * Avoid using `useLocalUIMode` or `useSeed` in new implementations.
     * Ensure that real credentials are not exposed in the frontend.
     * Use HTTPS and secure backend endpoints for production.
     */

    // Example configurations (uncomment as needed)

    /*
    // Example of MediaSFU CE with no MediaSFU Cloud
    final options = MediasfuGenericOptions(
      preJoinPageWidget: PreJoinPage(),
      localLink: localLink,
    );
    */

    /*
    // Example of MediaSFU CE + MediaSFU Cloud for Egress only
    final options = MediasfuGenericOptions(
      preJoinPageWidget: PreJoinPage(),
      credentials: credentials,
      localLink: localLink,
      connectMediaSFU: connectMediaSFU,
    );
    */

    /*
    // Example of MediaSFU Cloud only
    final options = MediasfuGenericOptions(
      preJoinPageWidget: PreJoinPage(),
      credentials: credentials,
      connectMediaSFU: connectMediaSFU,
    );
    */

    /*
    // Example of MediaSFU CE + MediaSFU Cloud for Egress only with custom UI
    final options = MediasfuGenericOptions(
      preJoinPageWidget: PreJoinPage(),
      credentials: credentials,
      localLink: localLink,
      connectMediaSFU: connectMediaSFU,
      returnUI: false,
      noUIPreJoinOptions: noUIPreJoinOptionsCreate,
      sourceParameters: sourceParameters.value,
      updateSourceParameters: updateSourceParameters,
      createMediaSFURoom: createRoomOnMediaSFU,
      joinMediaSFURoom: joinRoomOnMediaSFU,
    );
    */

    /*
    // Example of MediaSFU Cloud only with custom UI
    final options = MediasfuGenericOptions(
      preJoinPageWidget: PreJoinPage(),
      credentials: credentials,
      connectMediaSFU: connectMediaSFU,
      returnUI: false,
      noUIPreJoinOptions: noUIPreJoinOptionsCreate,
      sourceParameters: sourceParameters.value,
      updateSourceParameters: updateSourceParameters,
      createMediaSFURoom: createRoomOnMediaSFU,
      joinMediaSFURoom: joinRoomOnMediaSFU,
    );
    */

    /*
    // Example of using MediaSFU CE only with custom UI
    final options = MediasfuGenericOptions(
      preJoinPageWidget: PreJoinPage(),
      localLink: localLink,
      connectMediaSFU: false,
      returnUI: false,
      noUIPreJoinOptions: noUIPreJoinOptionsCreate,
      sourceParameters: sourceParameters.value,
      updateSourceParameters: updateSourceParameters,
    );
    */

    // === Main Activated Example ===
    // Default to MediasfuGeneric with credentials and custom UI options
    final MediasfuGenericOptions options = MediasfuGenericOptions(
      // Uncomment the following lines to use a custom pre-join page
      /*
      preJoinPageWidget: ({PreJoinPageOptions? options}) {
        return myCustomPreJoinPage(
          credentials: credentials,
        );
      },
      */

      // Uncomment the following lines to enable local UI mode with seed data
      /*
      useLocalUIMode: true,
      useSeed: true,
      seedData: seedData,
      */

      // Pass your Credentials if you will be using MediaSFU Cloud for recording and other egress processes or as your primary server
      credentials: credentials,

      // Use your own MediaSFU server link if you are using MediaSFU Community Edition
      // Pass your credentials if you will be using MediaSFU Cloud for recording and other egress processes or as your primary server
      connectMediaSFU: connectMediaSFU,

      // Uncomment the following line to specify your own MediaSFU Community Edition server
      localLink: localLink, // e.g., 'http://localhost:3000'

      // Set to false to use a custom UI, true to use the default MediaSFU UI
      returnUI: returnUI,

      // Provide pre-join options if not using the default UI (if creating a room)
      noUIPreJoinOptionsCreate:
          !returnUI ? noUIPreJoinOptionsCreate : null, // if creating a room

      // Provide pre-join options if not using the default UI (if joining a room)
      // noUIPreJoinOptionsJoin: !returnUI ? noUIPreJoinOptionsJoin : null, // if joining a room

      // Manage source parameters if not using the default UI
      sourceParameters: !returnUI ? sourceParameters.value : null,

      // Update source parameters if not using the default UI
      updateSourceParameters: !returnUI ? updateSourceParameters : null,

      // Provide custom room functions if not using the default functions
      createMediaSFURoom: createRoomOnMediaSFU,
      joinMediaSFURoom: joinRoomOnMediaSFU,
    );

    return MaterialApp(
      title: 'MediaSFU Generic',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuGeneric(options: options),
    );

    // === Alternative Use Cases ===
    // Uncomment the desired block to use a different MediaSFU component

    /*
    // Simple Use Case (Welcome Page)
    // Renders the default welcome page
    // No additional inputs required
    return MaterialApp(
      title: 'MediaSFU Welcome',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuGeneric(),
    );
    */

    /*
    // Use Case with Pre-Join Page (Credentials Required)
    // Uses a pre-join page that requires users to enter credentials
    return MaterialApp(
      title: 'MediaSFU Pre-Join',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuGeneric(
        options: MediasfuGenericOptions(
          preJoinPageWidget: PreJoinPage(),
          credentials: credentials,
        ),
      ),
    );
    */

    /*
    // Use Case with Local UI Mode (Seed Data Required)
    // Runs the application in local UI mode using seed data
    return MaterialApp(
      title: 'MediaSFU Local UI',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuGeneric(
        options: MediasfuGenericOptions(
          useLocalUIMode: true,
          useSeed: true,
          seedData: seedData!,
        ),
      ),
    );
    */

    /*
    // MediasfuBroadcast Component
    // Uncomment to use the broadcast event type
    return MaterialApp(
      title: 'MediaSFU Broadcast',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuBroadcast(
        credentials: credentials,
        useLocalUIMode: useLocalUIMode,
        useSeed: useSeed,
        seedData: useSeed ? seedData! : SeedData(),
      ),
    );
    */

    /*
    // MediasfuChat Component
    // Uncomment to use the chat event type
    return MaterialApp(
      title: 'MediaSFU Chat',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuChat(
        credentials: credentials,
        useLocalUIMode: useLocalUIMode,
        useSeed: useSeed,
        seedData: useSeed ? seedData! : SeedData(),
      ),
    );
    */

    /*
    // MediasfuWebinar Component
    // Uncomment to use the webinar event type
    return MaterialApp(
      title: 'MediaSFU Webinar',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuWebinar(
        credentials: credentials,
        useLocalUIMode: useLocalUIMode,
        useSeed: useSeed,
        seedData: useSeed ? seedData! : SeedData(),
      ),
    );
    */

    /*
    // MediasfuConference Component
    // Uncomment to use the conference event type
    return MaterialApp(
      title: 'MediaSFU Conference',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MediasfuConference(
        credentials: credentials,
        useLocalUIMode: useLocalUIMode,
        useSeed: useSeed,
        seedData: useSeed ? seedData! : SeedData(),
      ),
    );
    */
  }
}

/**
 * =======================
 * ====== EXTRA NOTES ======
 * =======================
 *
 * ### Handling Core Methods
 * With `sourceParameters`, you can access core methods such as `clickVideo` and `clickAudio`:
 *
 * ```dart
 * // Example of toggling video
 * void toggleVideo(SourceParameters sourceParameters) {
 *   sourceParameters.clickVideo({...sourceParameters});
 * }
 *
 * // Example of toggling audio
 * void toggleAudio(SourceParameters sourceParameters) {
 *   sourceParameters.clickAudio({...sourceParameters});
 * }
 * ```
 *
 * This allows your custom UI to interact with MediaSFU's functionalities seamlessly.
 *
 * ### Seed Data (Deprecated)
 * The seed data functionality is deprecated and maintained only for legacy purposes.
 * It is recommended to avoid using it in new implementations.
 *
 * ### Security Considerations
 * - **Protect API Credentials:** Ensure that API credentials are not exposed in the frontend. Use environment variables and secure backend services to handle sensitive information.
 * - **Use HTTPS:** Always use HTTPS to secure data transmission between the client and server.
 * - **Validate Inputs:** Implement proper validation and error handling on both client and server sides to prevent malicious inputs.
 *
 * ### Custom Backend Example for MediaSFU CE
 * Below is an example of how to set up custom backend endpoints for creating and joining rooms using MediaSFU CE. Assume all unlisted imports are exported by the package:
 *
 * ```typescript
 * import express from 'express';
 * import fetch from 'node-fetch';
 * 
 * const app = express();
 * app.use(express.json());
 * 
 * app.post("/createRoom", async (req, res) => {
 *   try {
 *     const { apiUserName, apiKey } = req.headers.authorization.replace("Bearer ", "").split(":");
 *     if (!apiUserName || !apiKey || !verifyCredentials(apiUserName, apiKey)) {
 *       return res.status(401).json({ error: "Invalid credentials" });
 *     }
 * 
 *     const response = await fetch("https://mediasfu.com/v1/rooms/", {
 *       method: "POST",
 *       headers: {
 *         "Content-Type": "application/json",
 *         Authorization: `Bearer ${apiUserName}:${apiKey}`,
 *       },
 *       body: JSON.stringify(req.body),
 *     });
 * 
 *     const result = await response.json();
 *     res.status(response.status).json(result);
 *   } catch (error) {
 *     res.status(500).json({ error: "Internal server error" });
 *   }
 * });
 * 
 * app.post("/joinRoom", async (req, res) => {
 *   try {
 *     const { apiUserName, apiKey } = req.headers.authorization.replace("Bearer ", "").split(":");
 *     if (!apiUserName || !apiKey || !verifyCredentials(apiUserName, apiKey)) {
 *       return res.status(401).json({ error: "Invalid credentials" });
 *     }
 * 
 *     const response = await fetch("https://mediasfu.com/v1/rooms/join", {
 *       method: "POST",
 *       headers: {
 *         "Content-Type": "application/json",
 *         Authorization: `Bearer ${apiUserName}:${apiKey}`,
 *       },
 *       body: JSON.stringify(req.body),
 *     });
 * 
 *     const result = await response.json();
 *     res.status(response.status).json(result);
 *   } catch (error) {
 *     res.status(500).json({ error: "Internal server error" });
 *   }
 * });
 * ```
 *
 * ### Custom Room Function Implementation
 * Below are examples of how to implement custom functions for creating and joining rooms securely in Dart. Assume all unlisted imports are exported by the package:
 *
 * ```dart
 * Future<CreateJoinRoomResult> createRoomOnMediaSFU(
 *   CreateMediaSFURoomOptions options,
 * ) async {
 *   try {
 *     final payload = options.payload;
 *     final apiUserName = options.apiUserName;
 *     final apiKey = options.apiKey;
 *     String endpoint = 'https://mediasfu.com/v1/rooms/';
 * 
 *     if (options.localLink.isNotEmpty) {
 *       endpoint = '${options.localLink}/createRoom';
 *     }
 * 
 *     final response = await http.post(
 *       Uri.parse(endpoint),
 *       headers: {
 *         "Content-Type": "application/json",
 *         "Authorization": "Bearer $apiUserName:$apiKey",
 *       },
 *       body: jsonEncode(payload.toMap()),
 *     );
 * 
 *     if (response.statusCode == 200 || response.statusCode == 201) {
 *       final data = jsonDecode(response.body);
 *       return CreateJoinRoomResult(
 *         data: CreateJoinRoomResponse.fromJson(data),
 *         success: true,
 *       );
 *     } else {
 *       final error = jsonDecode(response.body);
 *       return CreateJoinRoomResult(
 *         data: CreateJoinRoomError.fromJson(error),
 *         success: false,
 *       );
 *     }
 *   } catch (_) {
 *     return CreateJoinRoomResult(
 *       data: CreateJoinRoomError(error: 'Unknown error'),
 *       success: false,
 *     );
 *   }
 * }
 * 
 * Future<CreateJoinRoomResult> joinRoomOnMediaSFU(
 *   JoinMediaSFURoomOptions options,
 * ) async {
 *   try {
 *     final payload = options.payload;
 *     final apiUserName = options.apiUserName;
 *     final apiKey = options.apiKey;
 *     String endpoint = 'https://mediasfu.com/v1/rooms/join';
 * 
 *     if (options.localLink.isNotEmpty) {
 *       endpoint = '${options.localLink}/joinRoom';
 *     }
 * 
 *     final response = await http.post(
 *       Uri.parse(endpoint),
 *       headers: {
 *         "Content-Type": "application/json",
 *         "Authorization": "Bearer $apiUserName:$apiKey",
 *       },
 *       body: jsonEncode(payload.toMap()),
 *     );
 * 
 *     if (response.statusCode == 200 || response.statusCode == 201) {
 *       final data = jsonDecode(response.body);
 *       return CreateJoinRoomResult(
 *         data: CreateJoinRoomResponse.fromJson(data),
 *         success: true,
 *       );
 *     } else {
 *       final error = jsonDecode(response.body);
 *       return CreateJoinRoomResult(
 *         data: CreateJoinRoomError.fromJson(error),
 *         success: false,
 *       );
 *     }
 *   } catch (_) {
 *     return CreateJoinRoomResult(
 *       data: CreateJoinRoomError(error: 'Unknown error'),
 *       success: false,
 *     );
 *   }
 * }
 * ```
 *
 * #### Assumptions:
 * - All unlisted imports such as `http`, `CreateMediaSFURoomOptions`, `JoinMediaSFURoomOptions`, `CreateJoinRoomResult`, `CreateJoinRoomResponse`, and `CreateJoinRoomError` are exported by the package.
 * - This structure ensures type safety, clear error handling, and flexibility for customization.
 *
 * ========================
 * ====== END OF GUIDE ======
 * ========================
 */


In the provided examples, users can set useLocalUIMode to true during UI development to prevent unwanted connections to MediaSFU servers. Additionally, they can generate seed data for rendering UI components locally by using random data generators provided by the package.

Local UI Development in MediaSFU

Note: Local UI Development Mode is deprecated. Rather use your own Community Edition (CE) server for UI development and testing. You can later switch to MediaSFU Cloud for production. Nothing changes in the codebase, and you can use the same code for both environments.

During local UI development, the MediaSFU view is designed to be responsive to changes in screen size and orientation, adapting its layout accordingly. However, since UI changes are typically linked to communication with servers, developing the UI locally might result in less responsiveness due to the lack of real-time data updates. To mitigate this, users can force trigger changes in the UI by rotating the device, resizing the window, or simulating server responses by clicking on buttons within the page.

While developing locally, users may encounter occasional error warnings as the UI attempts to communicate with the server. These warnings can be safely ignored, as they are simply indicative of unsuccessful server requests in the local development environment.

Note: this mode is experimental and have numerous null values that may cause the application to crash but it is useful for testing purposes.


Custom UI Components

MediaSFU provides extensive customization options that allow you to tailor the interface to your specific needs. You can customize individual components or completely replace the entire MediaSFU interface.

CustomComponentType - Complete Interface Replacement

The CustomComponentType functionality allows you to completely replace the default MediaSFU interface with your own custom implementation while retaining all the underlying MediaSFU functionality.

Key Features

  • Complete Interface Control: Replace the entire MediaSFU UI with your custom design
  • Full Access to MediaSFU Parameters: Access all MediaSFU state and methods through MediasfuParameters
  • Seamless Integration: Your custom component integrates seamlessly with MediaSFU's backend functionality
  • Event Type Support: Available for all MediaSFU event types (Generic, Broadcast, Chat, Conference, Webinar)

Basic Implementation

import 'package:flutter/material.dart';
import 'package:mediasfu_sdk/mediasfu_sdk.dart';

// Define your custom MediaSFU interface
Widget myCustomMediaSFUInterface({
  required MediasfuParameters parameters,
}) {
  return Scaffold(
    appBar: AppBar(
      title: Text('My Custom MediaSFU Interface'),
      backgroundColor: Colors.purple,
    ),
    body: Container(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [Colors.purple.shade100, Colors.purple.shade300],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
      ),
      child: Column(
        children: [
          // Your custom header
          Container(
            padding: EdgeInsets.all(16),
            child: Text(
              'Welcome to ${parameters.roomName}',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
          ),
          
          // Custom video display area
          Expanded(
            child: Container(
              margin: EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: Colors.black,
                borderRadius: BorderRadius.circular(12),
              ),
              child: Center(
                child: Text(
                  'Custom Video Area',
                  style: TextStyle(color: Colors.white, fontSize: 18),
                ),
              ),
            ),
          ),
          
          // Custom control buttons
          Container(
            padding: EdgeInsets.all(16),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                FloatingActionButton(
                  heroTag: "mic",
                  onPressed: () {
                    // Toggle microphone using MediaSFU functionality
                    parameters.clickAudio(
                      ClickAudioOptions(parameters: parameters)
                    );
                  },
                  backgroundColor: parameters.micActive ? Colors.green : Colors.red,
                  child: Icon(
                    parameters.micActive ? Icons.mic : Icons.mic_off,
                    color: Colors.white,
                  ),
                ),
                FloatingActionButton(
                  heroTag: "video",
                  onPressed: () {
                    // Toggle video using MediaSFU functionality
                    parameters.clickVideo(
                      ClickVideoOptions(parameters: parameters)
                    );
                  },
                  backgroundColor: parameters.videoActive ? Colors.green : Colors.red,
                  child: Icon(
                    parameters.videoActive ? Icons.videocam : Icons.videocam_off,
                    color: Colors.white,
                  ),
                ),
                FloatingActionButton(
                  heroTag: "endCall",
                  onPressed: () {
                    // End call using MediaSFU functionality
                    parameters.clickEndCall(
                      ClickEndCallOptions(parameters: parameters)
                    );
                  },
                  backgroundColor: Colors.red,
                  child: Icon(Icons.call_end, color: Colors.white),
                ),
              ],
            ),
          ),
        ],
      ),
    ),
  );
}

// Use the custom interface in your app
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final credentials = Credentials(
      apiUserName: 'your_api_username',
      apiKey: 'your_api_key',
    );

    final options = MediasfuGenericOptions(
      credentials: credentials,
      customComponent: myCustomMediaSFUInterface,
    );

    return MaterialApp(
      home: MediasfuGeneric(options: options),
    );
  }
}

Custom Component Builders

In addition to complete interface replacement, you can customize individual components like VideoCard, AudioCard, and MiniCard:

// Custom VideoCard with gradient design
Widget myCustomVideoCard({
  required Participant participant,
  required stream,
  required MediasfuParameters parameters,
}) {
  return Container(
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [Colors.blue.shade200, Colors.blue.shade400],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      ),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Stack(
      children: [
        // Video stream would be rendered here
        Positioned(
          bottom: 8,
          left: 8,
          child: Container(
            padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
            decoration: BoxDecoration(
              color: Colors.black54,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Text(
              participant.name ?? 'Unknown',
              style: TextStyle(color: Colors.white, fontSize: 12),
            ),
          ),
        ),
      ],
    ),
  );
}

// Apply custom builders to MediaSFU options
final options = MediasfuGenericOptions(
  credentials: credentials,
  videoCardComponent: myCustomVideoCard,
  audioCardComponent: myCustomAudioCard,
  miniCardComponent: myCustomMiniCard,
);

Available CustomComponentType Options

The customComponent option is available in all MediaSFU event type options:

  • MediasfuGenericOptions: customComponent
  • MediasfuBroadcastOptions: customComponent
  • MediasfuChatOptions: customComponent
  • MediasfuConferenceOptions: customComponent
  • MediasfuWebinarOptions: customComponent

Advanced Example - Type-Specific Custom Interfaces

// Different custom interfaces for different event types
Widget customBroadcastInterface({required MediasfuParameters parameters}) {
  return Scaffold(
    backgroundColor: Colors.red.shade50,
    appBar: AppBar(
      title: Text('πŸ”΄ Live Broadcast'),
      backgroundColor: Colors.red,
    ),
    body: Column(
      children: [
        Container(
          padding: EdgeInsets.all(16),
          child: Row(
            children: [
              Icon(Icons.circle, color: Colors.red, size: 12),
              SizedBox(width: 8),
              Text('LIVE', style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
              Spacer(),
              Text('Viewers: ${parameters.participants.length}'),
            ],
          ),
        ),
        // Your custom broadcast UI here
      ],
    ),
  );
}

Widget customConferenceInterface({required MediasfuParameters parameters}) {
  return Scaffold(
    backgroundColor: Colors.blue.shade50,
    appBar: AppBar(
      title: Text('πŸŽ₯ Conference Room'),
      backgroundColor: Colors.blue,
    ),
    body: Column(
      children: [
        Container(
          padding: EdgeInsets.all(16),
          child: Text(
            'Conference: ${parameters.roomName}',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
        ),
        // Your custom conference UI here
      ],
    ),
  );
}

// Apply different interfaces to different event types
final broadcastOptions = MediasfuBroadcastOptions(
  credentials: credentials,
  customComponent: customBroadcastInterface,
);

final conferenceOptions = MediasfuConferenceOptions(
  credentials: credentials,
  customComponent: customConferenceInterface,
);

Benefits of Custom Components

  1. Brand Consistency: Match your app's design language and branding
  2. Enhanced User Experience: Create interfaces optimized for your specific use case
  3. Feature Integration: Seamlessly integrate MediaSFU functionality with your app's features
  4. Complete Control: Full control over layout, styling, and user interactions
  5. Maintained Functionality: Keep all MediaSFU's powerful features while using your custom UI

Example Files

For complete working examples, check out:

  • example/lib/custom_builders_example.dart - Individual component customization
  • example/lib/complete_custom_component_example.dart - Complete interface replacement examples

Intermediate Usage Guide

Expands on the basic usage, covering more advanced features and scenarios.

Intermediate Usage Guide

In the Intermediate Usage Guide, we'll explore the core components and functionalities of the MediaSFU package, focusing on media display, controls, and modal interactions. Click on any listed component/method to open the full documentation.

Core Components Overview

The main items displayed on an event page are media components (such as video, audio, and blank cards) and control components (for pagination, navigation, etc.).

Media Display Components:
Media Display Components
Component Name Description
MainAspectComponent Serves as a container for the primary aspect of the user interface, typically containing the main content or focus of the application.
MainScreenComponent Responsible for rendering the main screen layout of the application, providing the foundation for displaying various elements and content.
MainGridComponent Crucial part of the user interface, organizing and displaying primary content or elements in a grid layout format.
SubAspectComponent Acts as a secondary container within the user interface, often housing additional elements or controls related to the main aspect.
MainContainerComponent Primary container for the application's content, encapsulating all major components and providing structural organization.
OtherGridComponent Complements the Main Grid Component by offering additional grid layouts, typically used for displaying secondary or auxiliary content.

Control Components
Component Name Description
ControlButtonsComponent Comprises a set of buttons or controls used for navigating, interacting, or managing various aspects of the application's functionality.
ControlButtonsAltComponent Provides alternative button configurations or styles for controlling different aspects of the application.
ControlButtonsComponent Touch Specialized component designed for touch-enabled devices, offering floating buttons or controls for intuitive interaction with the application's features.

These components collectively contribute to the overall user interface, facilitating navigation, interaction, and content display within the application.

Modal Component Description
LoadingModal Modal for displaying loading indicator during data fetching or processing.
MainAspectComponent Component responsible for displaying the main aspect of the event page.
ControlButtonsComponent Component for displaying control buttons such as pagination controls.
ControlButtonsAltComponent Alternate control buttons component for specific use cases.
ControlButtonsComponentTouch Touch-enabled control buttons component for mobile devices.
OtherGridComponent Component for displaying additional grid elements on the event page.
MainScreenComponent Component for rendering the main screen content of the event.
MainGridComponent Main grid component for displaying primary event content.
SubAspectComponent Component for displaying secondary aspects of the event page.
MainContainerComponent Main container component for the event page content.
AlertComponent Modal for displaying alert messages to the user.
MenuModal Modal for displaying a menu with various options.
RecordingModal Modal for managing recording functionality during the event.
RequestsModal Modal for handling requests from participants during the event.
WaitingRoomModal Modal for managing waiting room functionality during the event.
DisplaySettingsModal Modal for adjusting display settings during the event.
EventSettingsModal Modal for configuring event settings.
CoHostModal Modal for managing co-host functionality during the event.
ParticipantsModal Modal for displaying participant information and controls.
MessagesModal Modal for managing messages and chat functionality during the event.
MediaSettingsModal Modal for adjusting media settings during the event.
ConfirmExitModal Modal for confirming exit from the event.
ConfirmHereModal Modal for confirming certain actions or selections.
ShareEventModal Modal for sharing the event with others.
PollModal Modal for conducting polls or surveys during the event.
WelcomePage Serves as the initial page displayed to users upon entering the application for an event. It provides a welcoming interface with relevant information about the event.
PreJoinPage The PreJoinPage represents the page where users prepare to join the event. It includes functionalities for creating or joining a room, validating credentials, and initializing the connection for the event.
BreakoutRoomsModal Modal for managing breakout rooms during the event.

Each modal has corresponding functions to trigger its usage:

  1. launchMenuModal: Launches the menu modal for settings and configurations.
  2. launchRecording: Initiates the recording modal for recording functionalities.
  3. startRecording: Starts the recording process.
  4. confirmRecording: Confirms and finalizes the recording.
  5. launchWaiting: Opens the waiting room modal for managing waiting room interactions.
  6. launchCoHost: Opens the co-host modal for managing co-host functionalities.
  7. launchMediaSettings: Launches the media settings modal for adjusting media-related configurations.
  8. launchDisplaySettings: Opens the display settings modal for adjusting display configurations.
  9. launchSettings: Initiates the settings modal for general event settings and configurations.
  10. launchRequests: Opens the requests modal for managing user requests.
  11. launchParticipants: Displays the participants modal for viewing and managing event participants.
  12. launchMessages: Opens the messages modal for communication through chat messages.
  13. launchConfirmExit: Prompts users to confirm before exiting the event.
  14. launchPoll: Opens the poll modal for conducting polls or surveys.
  15. launchBreakoutRooms: Initiates the breakout rooms modal for managing breakout room functionalities.

Media Display and Controls

These components facilitate media display and control functionalities:

  1. Pagination: Handles pagination and page switching.
  2. FlexibleGrid: Renders flexible grid layouts for media display.
  3. FlexibleVideo: Displays videos in a flexible manner within the grid.
  4. AudioGrid: Renders audio components within the grid layout.

These components enable seamless media presentation and interaction within the event environment, providing users with a rich and immersive experience.

UI Media Component Description
MeetingProgressTimer Component for displaying a timer indicating the progress of a meeting or event.
MiniAudio Component for rendering a compact audio player with basic controls.
MiniCard Component for displaying a minimized card view with essential information.
AudioCard Component for displaying audio content with control elements, details, and audio decibels.
VideoCard Component for displaying video content with control elements, details, and audio decibels.
CardVideoDisplay Video player component for displaying embedded videos with controls and details.
MiniAudioPlayer Utility method for playing audio and rendering a mini audio modal when the user is not actively displayed on the page.

With the Intermediate Usage Guide, users can explore and leverage the core components and functionalities of the MediaSFU Package to enhance their event hosting and participation experiences.

Here's a sample import and usage code for a Broadcast screen:


import 'package:flutter/material.dart';
import 'package:mediasfu_sdk/mediasfu_sdk.dart';


class MediasfuBroadcast extends StatefulWidget {
  final MediasfuBroadcastOptions options;

  const MediasfuBroadcast({
    super.key,
    required this.options,
  });

  @override
  _MediasfuBroadcastState createState() => _MediasfuBroadcastState();
}


class _MediasfuBroadcastState extends State<MediasfuBroadcast> {

    //sample details 

    bool validated = false;

    void updateValidated(bool value) {
    setState(() {
      validated = value;
    });

    if (validated) {
      joinAndUpdate().then((value) => null);
    }
  }

  // Room Details
  final ValueNotifier<String> roomName = ValueNotifier(''); // Room name
  final ValueNotifier<String> member = ValueNotifier(''); // Member name
  final ValueNotifier<String> adminPasscode =
      ValueNotifier(''); // Admin passcode
  final ValueNotifier<String> islevel = ValueNotifier("0"); // Level

  //others ...

  
  //Record Button
  List<ButtonTouch> recordButton = [];

  void initializeRecordButton() {
    recordButton = [
      // Record Button
      ButtonTouch(
        icon: Icons.fiber_manual_record,
        active: false,
        onPress: () {
          // Action for the Record button
          launchRecording(
            LaunchRecordingOptions(
                updateIsRecordingModalVisible: updateIsRecordingModalVisible,
                isRecordingModalVisible: isRecordingModalVisible.value,
                stopLaunchRecord: stopLaunchRecord.value,
                canLaunchRecord: canLaunchRecord.value,
                recordingAudioSupport: recordingAudioSupport.value,
                recordingVideoSupport: recordingVideoSupport.value,
                updateCanRecord: updateCanRecord,
                updateClearedToRecord: updateClearedToRecord,
                recordStarted: recordStarted.value,
                recordPaused: recordPaused.value,
                localUIMode: widget.options.useLocalUIMode == true),
          );
        },
        activeColor: const Color.fromARGB(255, 244, 3, 3),
        inActiveColor: const Color.fromARGB(255, 251, 9, 9),
        show: true,
        size: 24,
      ),
    ];
  }



  // Record Buttons
  List<ButtonTouch> recordButtonsTouch = [];

   //Control Buttons Broadcast Events
  List<ButtonTouch> controlBroadcastButtons = [];



  Widget buildEventRoom(BuildContext context) {
  initializeRecordButton();
  initializeRecordButtons();
  initializeControlBroadcastButtons();


    @override
  Widget build(BuildContext context) {
    return SafeArea(
      // Top: true adjusts for iOS status bar, ignores for other platforms
      top: true,
      child: AnnotatedRegion<SystemUiOverlayStyle>(
        value: const SystemUiOverlayStyle(
          statusBarColor: Colors.transparent, // Makes status bar transparent
          statusBarIconBrightness:
              Brightness.light, // Sets status bar icons to light color
        ),
        child: OrientationBuilder(
          builder: (BuildContext context, Orientation orientation) {
            // Check if the device is in landscape mode
            isPortrait.value = orientation == Orientation.portrait;
            return Scaffold(
              body: _buildRoomInterface(),
              resizeToAvoidBottomInset: false,
            );
          },
        ),
      ),
    );
  }


    return validated
        ? MainContainerComponent(
            options: MainContainerComponentOptions(
              backgroundColor: const Color.fromRGBO(217, 227, 234, 0.99),
              children: [
                MainAspectComponent(
                  options: MainAspectComponentOptions(
                    backgroundColor: const Color.fromRGBO(217, 227, 234, 0.99),
                    updateIsWideScreen: updateIsWideScreen,
                    updateIsMediumScreen: updateIsMediumScreen,
                    updateIsSmallScreen: updateIsSmallScreen,
                    defaultFraction: 1 - controlHeight.value,
                    showControls: eventType.value == EventType.webinar ||
                        eventType.value == EventType.conference,
                    children: [
                      ValueListenableBuilder<ComponentSizes>(
                          valueListenable: componentSizes,
                          builder: (context, componentSizes, child) {
                            return MainScreenComponent(
                              options: MainScreenComponentOptions(
                                doStack: true,
                                mainSize: mainHeightWidth,
                                updateComponentSizes: updateComponentSizes,
                                defaultFraction: 1 - controlHeight.value,
                                showControls:
                                    eventType.value == EventType.webinar ||
                                        eventType.value == EventType.conference,
                                children: [
                                  ValueListenableBuilder<GridSizes>(
                                    valueListenable: gridSizes,
                                    builder: (context, gridSizes, child) {
                                      return MainGridComponent(
                                        options: MainGridComponentOptions(
                                          height: componentSizes.mainHeight,
                                          width: componentSizes.mainWidth,
                                          backgroundColor: const Color.fromRGBO(
                                              217, 227, 234, 0.99),
                                          mainSize: mainHeightWidth,
                                          showAspect: mainHeightWidth > 0,
                                          timeBackgroundColor:
                                              recordState == 'green'
                                                  ? Colors.green
                                                  : recordState == 'yellow'
                                                      ? Colors.yellow
                                                      : Colors.red,
                                          meetingProgressTime:
                                              meetingProgressTime.value,
                                          showTimer: true,
                                          children: [
                                            FlexibleVideo(
                                                options: FlexibleVideoOptions(
                                              backgroundColor:
                                                  const Color.fromRGBO(
                                                      217, 227, 234, 0.99),
                                              customWidth:
                                                  componentSizes.mainWidth,
                                              customHeight:
                                                  componentSizes.mainHeight,
                                              rows: 1,
                                              columns: 1,
                                              componentsToRender:
                                                  mainGridStream,
                                              showAspect:
                                                  mainGridStream.isNotEmpty,
                                            )),
                                            ControlButtonsComponentTouch(
                                                options:
                                                    ControlButtonsComponentTouchOptions(
                                              buttons: controlBroadcastButtons,
                                              direction: 'vertical',
                                              showAspect: eventType.value ==
                                                  EventType.broadcast,
                                              location: 'bottom',
                                              position: 'right',
                                            )),
                                            ControlButtonsComponentTouch(
                                                options:
                                                    ControlButtonsComponentTouchOptions(
                                              buttons: recordButton,
                                              direction: 'horizontal',
                                              showAspect: eventType.value ==
                                                      EventType.broadcast &&
                                                  !showRecordButtons.value &&
                                                  islevel.value == '2',
                                              location: 'bottom',
                                              position: 'middle',
                                            )),
                                            ControlButtonsComponentTouch(
                                                options:
                                                    ControlButtonsComponentTouchOptions(
                                              buttons: recordButtonsTouch,
                                              direction: 'horizontal',
                                              showAspect: eventType.value ==
                                                      EventType.broadcast &&
                                                  showRecordButtons.value &&
                                                  islevel.value == '2',
                                              location: 'bottom',
                                              position: 'middle',
                                            )),
                                            AudioGrid(
                                                options: AudioGridOptions(
                                              componentsToRender:
                                                  audioOnlyStreams.value,
                                            )),
                                            ValueListenableBuilder<String>(
                                                valueListenable:
                                                    meetingProgressTime,
                                                builder: (context,
                                                    meetingProgressTime,
                                                    child) {
                                                  return MeetingProgressTimer(
                                                      options:
                                                          MeetingProgressTimerOptions(
                                                    meetingProgressTime:
                                                        meetingProgressTime,
                                                    initialBackgroundColor:
                                                        recordState == 'green'
                                                            ? Colors.green
                                                            : recordState ==
                                                                    'yellow'
                                                                ? Colors.yellow
                                                                : Colors.red,
                                                    showTimer: true,
                                                  ));
                                                }),
                                          ],
                                        ),
                                      );
                                    },
                                  ),
                                ],
                              ),
                            );
                          }),
                    ],
                  ),
                ),
              ],
            ),
          )
        : widget.options.credentials != null &&
                widget.options.credentials!.apiKey.isNotEmpty &&
                widget.options.credentials!.apiKey != 'your_api_key'
            ? renderpreJoinPageWidget() ?? renderWelcomePage()
            : renderWelcomePage();
}


 Widget _buildRoomInterface() {
    return Stack(
      children: [
        buildEventRoom(context),

        _buildShareEventModal(), // Add Share Event Modal
        _buildRecordingModal(), // Add Recording Modal
        _buildParticipantsModal(), // Add Participants Modal
        _buildMessagesModal(), // Add Messages Modal

        _buildConfirmExitModal(), // Add Confirm Exit Modal

        _buildAlertModal(), // Add Alert Modal
        _buildConfirmHereModal(), // Add Confirm Here Modal
        _buildLoadingModal(), // Add Loading Modal
      ],
    );
  }

      Widget _buildParticipantsModal() {
    return ValueListenableBuilder<bool>(
      valueListenable: isParticipantsModalVisible,
      builder: (context, isParticipantsVisible, child) {
        return ValueListenableBuilder<List<dynamic>>(
          valueListenable: filteredParticipants,
          builder: (context, filteredParticipants, child) {
            return ParticipantsModal(
              options: ParticipantsModalOptions(
                backgroundColor: const Color.fromRGBO(217, 227, 234, 0.99),
                isParticipantsModalVisible: isParticipantsVisible,
                onParticipantsClose: () {
                  updateIsParticipantsModalVisible(false);
                },
                participantsCounter: participantsCounter.value,
                onParticipantsFilterChange: onParticipantsFilterChange,
                parameters: mediasfuParameters,
              ),
            );
          },
        );
      },
    );
  }



  Widget _buildConfirmExitModal() {
    return ValueListenableBuilder<bool>(
      valueListenable: isConfirmExitModalVisible,
      builder: (context, isConfirmExitVisible, child) {
        return ConfirmExitModal(
          options: ConfirmExitModalOptions(
            backgroundColor: const Color.fromRGBO(181, 233, 229, 0.97),
            isVisible: isConfirmExitVisible,
            onClose: () {
              updateIsConfirmExitModalVisible(false);
            },
            islevel: islevel.value,
            roomName: roomName.value,
            member: member.value,
            socket: socket.value,
          ),
        );
      },
    );
  }



//other widgets for the modals

}

This sample code demonstrates the import and usage of various components and features for a Broadcast screen, including rendering different UI components based on the validation state, handling socket connections, displaying video streams, controlling recording, and managing component sizes.

Here's a sample usage of the control button components as used above:

    void initializeRecordButtons() {
    recordButtons = [
      // Play/Pause Button
      AltButton(
        // name: Pause,
        icon: Icons.play_circle_filled,
        active: !recordPaused.value,
        onPress: () => updateRecording(UpdateRecordingOptions(
          parameters: mediasfuParameters,
        )),
        activeColor: Colors.black,
        inActiveColor: Colors.black,
        alternateIcon: Icons.pause_circle_filled,
        show: true,
      ),
      // Stop Button
      AltButton(
        // name: Stop,
        icon: Icons.stop_circle,
        active: false,
        onPress: () => stopRecording(
          StopRecordingOptions(
            parameters: mediasfuParameters,
          ),
        ),
        activeColor: Colors.green,
        inActiveColor: Colors.black,
        show: true,
      ),
      // Timer Display
      AltButton(
        customComponent: Container(
          color: Colors.transparent,
          padding: const EdgeInsets.all(0),
          margin: const EdgeInsets.all(2),
          child: Text(
            recordingProgressTime.value,
            style: const TextStyle(
              color: Colors.black,
              backgroundColor: Colors.transparent,
            ),
          ),
        ),
        show: true,
      ),
      // Status Button
      AltButton(
        // name: Status,
        icon: Icons.circle,
        active: false,
        onPress: () => (),
        activeColor: Colors.black,
        inActiveColor: recordPaused.value == false ? Colors.red : Colors.yellow,
        show: true,
      ),
      // Settings Button
      AltButton(
        // name: Settings,
        icon: Icons.settings,
        active: false,
        onPress: () => launchRecording(
          LaunchRecordingOptions(
              updateIsRecordingModalVisible: updateIsRecordingModalVisible,
              isRecordingModalVisible: isRecordingModalVisible.value,
              stopLaunchRecord: stopLaunchRecord.value,
              canLaunchRecord: canLaunchRecord.value,
              recordingAudioSupport: recordingAudioSupport.value,
              recordingVideoSupport: recordingVideoSupport.value,
              updateCanRecord: updateCanRecord,
              updateClearedToRecord: updateClearedToRecord,
              recordStarted: recordStarted.value,
              recordPaused: recordPaused.value,
              localUIMode: widget.options.useLocalUIMode == true),
        ),

        activeColor: Colors.green,
        inActiveColor: Colors.black,
        show: true,
      )
    ];
  }


  // Initialize Control Buttons Broadcast Events
   void initializeControlBroadcastButtons() {
    controlBroadcastButtons = [
      // Users button
      ButtonTouch(
        icon: Icons.group_outlined,
        active: participantsActive,
        onPress: () => launchParticipants(
          LaunchParticipantsOptions(
            updateIsParticipantsModalVisible: updateIsParticipantsModalVisible,
            isParticipantsModalVisible: isParticipantsModalVisible.value,
          ),
        ),
        activeColor: Colors.black,
        inActiveColor: Colors.black,
        show: true,
      ),

      // Share button
      ButtonTouch(
        icon: Icons.share,
        alternateIcon: Icons.share,
        active: true,
        onPress: () =>
            updateIsShareEventModalVisible(!isShareEventModalVisible.value),
        activeColor: Colors.black,
        inActiveColor: Colors.black,
        show: true,
      ),
      // Custom component
      ButtonTouch(
        customComponent: Stack(
          children: [
            // Your icon
            const Icon(
              Icons.comment,
              size: 20,
              color: Colors.black,
            ),
            // Conditionally render a badge
            if (showMessagesBadge.value)
              Positioned(
                top: -2,
                right: -2,
                child: Container(
                  decoration: BoxDecoration(
                    color: Colors.red,
                    borderRadius: BorderRadius.circular(12),
                  ),
                  padding:
                      const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
                  child: const Text(
                    '',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 12,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ),
          ],
        ),
        onPress: () => launchMessages(
          LaunchMessagesOptions(
            updateIsMessagesModalVisible: updateIsMessagesModalVisible,
            isMessagesModalVisible: isMessagesModalVisible.value,
          ),
        ),
        show: true,
      ),

      // Switch camera button
      ButtonTouch(
        icon: Icons.sync,
        alternateIcon: Icons.sync,
        active: true,
        onPress: () => switchVideoAlt(
          SwitchVideoAltOptions(
            parameters: mediasfuParameters,
          ),
        ),
        activeColor: Colors.black,
        inActiveColor: Colors.black,
        show: islevel.value == '2',
      ),
      // Video button
      ButtonTouch(
        icon: Icons.video_call,
        alternateIcon: Icons.video_call,
        active: videoActive,
        onPress: () => clickVideo(
          ClickVideoOptions(
            parameters: mediasfuParameters,
          ),
        ),
        activeColor: Colors.green,
        inActiveColor: Colors.red,
        show: islevel.value == '2',
      ),
      // Microphone button
      ButtonTouch(
        icon: Icons.mic,
        alternateIcon: Icons.mic,
        active: micActive,
        onPress: () => clickAudio(
          ClickAudioOptions(parameters: mediasfuParameters),
        ),
        activeColor: Colors.green,
        inActiveColor: Colors.red,
        show: islevel.value == '2',
      ),

      ButtonTouch(
        customComponent: Container(
          margin: const EdgeInsets.all(5),
          padding: const EdgeInsets.all(0),
          decoration: BoxDecoration(
            color: Colors.transparent,
            border: Border.all(color: Colors.transparent),
          ),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              const Icon(Icons.bar_chart, size: 20, color: Colors.black),
              const SizedBox(width: 5),
              Text(
                participantsCounter.value.toString(),
                style: const TextStyle(
                  backgroundColor: Colors.transparent,
                  color: Colors.black,
                  fontSize: 12,
                ),
              ),
            ],
          ),
        ),
        show: true,
      ),
      // End call button
      ButtonTouch(
        icon: Icons.call_end,
        active: endCallActive,
        onPress: () => launchConfirmExit(
          LaunchConfirmExitOptions(
            updateIsConfirmExitModalVisible: updateIsConfirmExitModalVisible,
            isConfirmExitModalVisible: isConfirmExitModalVisible.value,
          ),
        ),
        activeColor: Colors.green,
        inActiveColor: Colors.red,
        show: true,
      ),
    ];
  }

This sample code defines arrays recordButtons and controlBroadcastButtons, each containing configuration objects for different control buttons. These configurations include properties such as icon, active state, onPress function, activeColor, inActiveColor, and show flag to control the visibility of each button.

You can customize these configurations according to your requirements, adding, removing, or modifying buttons as needed. Additionally, you can refer to the relevant component files (control_buttons_alt_component.dart and control_buttons_component_touch.dart) for more details on how to add custom buttons.

Preview of Broadcast Page Preview of Welcome Page

Preview of Conference Page

Preview of Prejoin Page

Preview of Conference Page's Mini Grids

Preview of Prejoin Page

Advanced Usage Guide

In-depth documentation for advanced users, covering complex functionalities and customization options.

Introduction to Advanced Media Control Functions:

In advanced usage scenarios, users often encounter complex tasks related to media control, connectivity, and streaming management within their applications. To facilitate these tasks, a comprehensive set of functions is provided, offering granular control over various aspects of media handling and communication with servers.

These advanced media control functions encompass a wide range of functionalities, including connecting to and disconnecting from WebSocket servers, joining and updating room parameters, managing device creation, switching between different media streams, handling permissions, processing consumer transports, managing screen sharing, adjusting layouts dynamically, and much more.

This robust collection of functions empowers developers to tailor their applications to specific requirements, whether it involves intricate media streaming setups, real-time communication protocols, or sophisticated user interface interactions. With these tools at their disposal, developers can create rich and responsive media experiences that meet the demands of their users and applications.

Here's a tabulated list of advanced control functions along with brief explanations (click the function(link) for full usage guide):

Function Explanation
connectSocket Connects to the WebSocket server.
disconnectSocket Disconnects from the WebSocket server.
joinRoomClient Joins a room as a client.
updateRoomParametersClient Updates room parameters as a client.
createDeviceClient Creates a device as a client.
switchVideoAlt Switches video/camera streams.
clickVideo Handles clicking on video controls.
clickAudio Handles clicking on audio controls.
clickScreenShare Handles clicking on screen share controls.
streamSuccessVideo Handles successful video streaming.
streamSuccessAudio Handles successful audio streaming.
streamSuccessScreen Handles successful screen sharing.
streamSuccessAudioSwitch Handles successful audio switching.
checkPermission Checks for media access permissions.
producerClosed Handles the closure of a producer.
newPipeProducer Creates receive transport for a new piped producer.
updateMiniCardsGrid Updates the mini-grids (mini cards) grid.
mixStreams Mixes streams and prioritizes interesting ones together.
dispStreams Displays streams (media).
stopShareScreen Stops screen sharing.
checkScreenShare Checks for screen sharing availability.
startShareScreen Starts screen sharing.
requestScreenShare Requests permission for screen sharing.
reorderStreams Reorders streams (based on interest level).
prepopulateUserMedia Populates user media (for main grid).
getVideos Retrieves videos that are pending.
rePort Handles re-porting (updates of changes in UI when recording).
trigger Triggers actions (reports changes in UI to backend for recording).
consumerResume Resumes consumers.
connectSendTransportAudio Connects send transport for audio.
connectSendTransportVideo Connects send transport for video.
connectSendTransportScreen Connects send transport for screen sharing.
processConsumerTransports Processes consumer transports to pause/resume based on the current active page.
resumePauseStreams Resumes or pauses streams.
readjust Readjusts display elements.
checkGrid Checks the grid sizes to display.
getEstimate Gets an estimate of grids to add.
calculateRowsAndColumns Calculates rows and columns for the grid.
addVideosGrid Adds videos to the grid.
onScreenChanges Handles screen changes (orientation and resize).
sleep Pauses execution for a specified duration.
changeVids Changes videos.
compareActiveNames Compares active names (for recording UI changes reporting).
compareScreenStates Compares screen states (for recording changes in grid sizes reporting).
createSendTransport Creates a send transport.
resumeSendTransportAudio Resumes a send transport for audio.
receiveAllPipedTransports Receives all piped transports.
disconnectSendTransportVideo Disconnects send transport for video.
disconnectSendTransportAudio Disconnects send transport for audio.
disconnectSendTransportScreen Disconnects send transport for screen sharing.
connectSendTransport Connects a send transport.
getPipedProducersAlt Gets piped producers.
signalNewConsumerTransport Signals a new consumer transport.
connectRecvTransport Connects a receive transport.
reUpdateInter Re-updates the interface based on audio decibels.
updateParticipantAudioDecibels Updates participant audio decibels.
closeAndResize Closes and resizes the media elements.
autoAdjust Automatically adjusts display elements.
switchUserVideoAlt Switches user video (alternate) (back/front).
switchUserVideo Switches user video (specific video id).
switchUserAudio Switches user audio.
receiveRoomMessages Receives room messages.
formatNumber Formats a number (for broadcast viewers).
connectIps Connects IPs (connect to consuming servers).
startMeetingProgressTimer Starts the meeting progress timer.
updateRecording Updates the recording status.
stopRecording Stops the recording process.
pollUpdated Handles updated poll data.
handleVotePoll Handles voting in a poll.
handleCreatePoll Handles creating a poll.
handleEndPoll Handles ending a poll.
breakoutRoomUpdated Handles updated breakout room data.
resumePauseAudioStreams Resumes or pauses audio streams.
processConsumerTransportsAudio Processes consumer transports for audio.

Room Socket Events

In the context of a room's real-time communication, various events occur, such as user actions, room management updates, media controls, and meeting status changes. To effectively handle these events and synchronize the application's state with the server, specific functions are provided. These functions act as listeners for socket events, allowing the application to react accordingly.

Provided Socket Event Handling Functions:

Function Explanation
userWaiting Triggered when a user is waiting.
personJoined Triggered when a person joins the room.
allWaitingRoomMembers Triggered when information about all waiting room members is received.
roomRecordParams Triggered when room recording parameters are received.
banParticipant Triggered when a participant is banned.
updatedCoHost Triggered when the co-host information is updated.
participantRequested Triggered when a participant requests access.
screenProducerId Triggered when the screen producer ID is received.
updateMediaSettings Triggered when media settings are updated.
producerMediaPaused Triggered when producer media is paused.
producerMediaResumed Triggered when producer media is resumed.
producerMediaClosed Triggered when producer media is closed.
controlMediaHost Triggered when media control is hosted.
meetingEnded Triggered when the meeting ends.
disconnectUserSelf Triggered when a user disconnects.
receiveMessage Triggered when a message is received.
meetingTimeRemaining Triggered when meeting time remaining is received.
meetingStillThere Triggered when the meeting is still active.
startRecords Triggered when recording starts.
reInitiateRecording Triggered when recording needs to be re-initiated.
getDomains Triggered when domains are received.
updateConsumingDomains Triggered when consuming domains are updated.
RecordingNotice Triggered when a recording notice is received.
timeLeftRecording Triggered when time left for recording is received.
stoppedRecording Triggered when recording stops.
hostRequestResponse Triggered when the host request response is received.
allMembers Triggered when information about all members is received.
allMembersRest Triggered when information about all members is received (rest of the members).
disconnect Triggered when a disconnect event occurs.
pollUpdated Triggered when a poll is updated.
breakoutRoomUpdated Triggered when a breakout room is updated.

Sample Usage:

// Example usage of provided socket event handling functions
import 'package:mediasfu_sdk/mediasfu_sdk.dart'show participantRequested, screenProducerId, participantRequestedOptions, screenProducerIdOptions, Request;

  socket.value!.on('participantRequested', (data) async {
    try {
      // Convert userRequest data to Request object
      Request request = Request.fromMap(data['userRequest']);
      // Handle 'participantRequested' event
      participantRequested(
        ParticipantRequestedOptions(
          userRequest: request,
          requestList: requestList.value,
          waitingRoomList: waitingRoomList.value,
          updateRequestList: updateRequestList,
          updateTotalReqWait: updateTotalReqWait,
        ),
      );
    } catch (error) {
      if (kDebugMode) {
        // print('Error handling participantRequested event: $error');
      }
    }
  });

  socket.value!.on('screenProducerId', (data) async {
    // Handle 'screenProducerId' event
    try {
      screenProducerId(ScreenProducerIdOptions(
        producerId: data['producerId'],
        screenId: screenId.value,
        membersReceived: membersReceived.value,
        shareScreenStarted: shareScreenStarted.value,
        deferScreenReceived: deferScreenReceived.value,
        participants: participants.value,
        updateScreenId: updateScreenId,
        updateShareScreenStarted: updateShareScreenStarted,
        updateDeferScreenReceived: updateDeferScreenReceived,
      ));
    } catch (error) {
      if (kDebugMode) {
        // print('Error handling screenProducerId event: $error');
      }
    }
  });

These functions enable seamless interaction with the server and ensure that the application stays synchronized with the real-time events occurring within the room.

Customizing Media Display in MediaSFU

By default, media display in MediaSFU is handled by the following key functions:

  • prepopulateUserMedia: This function controls the main media grid, such as the host's video in webinar or broadcast views (MainGrid).
  • addVideosGrid: This function manages the mini grid's media, such as participants' media in MiniGrid views (MiniCards, AudioCards, VideoCards).

Customizing the Media Display

If you want to modify the default content displayed by MediaSFU components, such as the MiniCard, AudioCard, or VideoCard, you can replace the default UI with your own custom components.

To implement your custom UI for media display:

  1. Custom MainGrid (Host's Video):

    • Modify the UI in the prepopulateUserMedia function.
    • Example link to MediaSFU's default implementation: prepopulateUserMedia.
  2. Custom MiniGrid (Participants' Media):

    • Modify the UI in the addVideosGrid function.
    • Example link to MediaSFU's default implementation: addVideosGrid.

To create a custom UI, you can refer to existing MediaSFU implementations like:

Once your custom components are built, modify the imports of prepopulateUserMedia and addVideosGrid to point to your custom implementations instead of the default MediaSFU ones.

This allows for full flexibility in how media is displayed in both the main and mini grids, giving you the ability to tailor the user experience to your specific needs.

Media Device & Stream Utilities

MediaSFU provides two powerful utility methods for advanced media device management and participant stream access:

Method Description
getMediaDevicesList Enumerate available cameras and microphones with automatic permission handling
getParticipantMedia Retrieve specific participant's video or audio streams by ID or name

Method Details

getMediaDevicesList

Retrieves a list of available media devices (cameras or microphones) with automatic permission handling.

Signature:

Future<List<MediaDeviceInfo>> getMediaDevicesList(String kind)

Parameters:

  • kind (String): The type of media device to enumerate
    • 'videoinput' - Cameras/video devices
    • 'audioinput' - Microphones/audio input devices

Returns: Future<List<MediaDeviceInfo>> - List of available devices

Example Usage:

import 'package:mediasfu_sdk/mediasfu_sdk.dart';

// Get all available cameras
final cameras = await getMediaDevicesList('videoinput');
for (final device in cameras) {
  print('Camera: ${device.label} (${device.deviceId})');
}

// Get all available microphones
final microphones = await getMediaDevicesList('audioinput');
for (final device in microphones) {
  print('Microphone: ${device.label} (${device.deviceId})');
}

Common Use Cases:

  • Building custom device selection interfaces
  • Displaying available cameras/microphones to users
  • Validating device availability before streaming
  • Creating device switching functionality

getParticipantMedia

Retrieves a specific participant's video or audio stream from the session.

Signature:

MediaStream? getParticipantMedia({
  required GetParticipantMediaOptions options,
})

Parameters:

  • options (GetParticipantMediaOptions): Configuration object containing:
    • participantId (String, optional): Producer ID of the participant's stream
    • participantName (String, optional): Name of the participant
    • mediaType (String): Type of media to retrieve - 'video' or 'audio'
    • parameters (GetParticipantMediaParameters): MediaSFU parameters object with:
      • allVideoStreams (List
      • allAudioStreams (List
      • participants (List

Returns: MediaStream? - The participant's media stream, or null if not found

Example Usage:

import 'package:mediasfu_sdk/mediasfu_sdk.dart';

// Inside a MediaSFU component or with access to MediasfuParameters

// Get participant's video stream by producer ID
final videoStream = getParticipantMedia(
  options: GetParticipantMediaOptions(
    participantId: 'producer-123',
    mediaType: 'video',
    parameters: mediasfuParameters,
  ),
);

// Get participant's audio stream by name
final audioStream = getParticipantMedia(
  options: GetParticipantMediaOptions(
    participantName: 'John Doe',
    mediaType: 'audio',
    parameters: mediasfuParameters,
  ),
);

if (videoStream != null) {
  // Use the stream (e.g., display in a custom widget)
  print('Found video stream: ${videoStream.id}');
}

Common Use Cases:

  • Implementing custom participant video/audio displays
  • Creating picture-in-picture layouts with specific participants
  • Building participant spotlight features
  • Monitoring specific participant streams
  • Creating custom stream routing logic

Note: These methods are available through the MediaSFU SDK and are automatically passed to all MediaSFU components (MediasfuGeneric, MediasfuBroadcast, MediasfuChat, MediasfuConference, MediasfuWebinar). Access them through the MediasfuParameters object in your custom methods or event handlers.

API Reference

For detailed information on the API methods and usage, please refer to the MediaSFU API Documentation.

If you need further assistance or have any questions, feel free to ask!

For sample codes and practical implementations, visit the MediaSFU Sandbox.

Troubleshooting

Encountering issues while integrating MediaSFU into your Flutter application? Below are common problems and their solutions to help you get back on track.

1. Interactive Testing with MediaSFU's Frontend

Issue: Difficulty in verifying if events or media transmissions are functioning correctly within your application.

Solution: Utilize MediaSFU's frontend to join the same room as your Flutter application. This allows you to interactively monitor and analyze event handling and media streams.

Steps:

  • Open MediaSFU's Frontend: Navigate to the MediaSFU Frontend in your web browser.
  • Join the Same Room: Use the same room credentials in the frontend that your Flutter app is using.
  • Monitor Interactions: Perform actions in your Flutter app (e.g., start a call, share media) and observe the corresponding changes in the frontend. Similarly, perform actions in the frontend and verify their effects in your Flutter app.

This method helps ensure that interactions between users and media streams are functioning as expected.

2. Reducing Terminal Log Clutter

Issue: Excessive Java logs cluttering the terminal output when running your Flutter application in debug mode, making it difficult to identify relevant Flutter logs.

Solution: Filter out irrelevant logs using command-line tools to display only Flutter-related logs, enhancing readability and debugging efficiency.

For Unix-based Systems (Linux/macOS): Use the following command to run your Flutter app while filtering logs:

flutter run | sed '/^.\// { /^\(V\|I\|W\|E\)\/flutter/!d }'

Explanation:

  • flutter run: Starts your Flutter application.
  • |: Pipes the output to the next command.
  • sed '/^.\// { /^\(V\|I\|W\|E\)\/flutter/!d }': Filters the logs to display only those that start with Flutter's log tags (V for Verbose, I for Info, W for Warning, E for Error).

For Windows Users: Use PowerShell to achieve similar log filtering:

flutter run | Select-String "flutter/"

Note: Ensure you have the necessary command-line tools installed and configured for your operating system.

3. Issues with Remote Streams on Web

Issue: Remote streams not appearing when testing your Flutter application on the web, potentially due to localhost-related issues.

Solution: Follow these recommendations to ensure a reliable testing environment:

a. Build and Deploy

  • Build for Production: Create a production build of your Flutter web application using:

    flutter build web
    
  • Deploy to a Staging or Live Environment: Deploy the build to a web server or hosting service to test in a real-world scenario, which can bypass localhost limitations related to browser security and network configurations.

b. Use a Different Browser

  • Test Across Multiple Browsers: Some browsers handle WebRTC and media streams differently. Test your application on various browsers (e.g., Chrome, Firefox, Edge) to identify if the issue is browser-specific.

  • Check Browser Permissions: Ensure that the browser has granted the necessary permissions for camera and microphone access.

c. Verify Network Configurations

  • Firewall and Network Settings: Ensure that your network allows WebRTC traffic and that firewalls are not blocking necessary ports.

  • Use HTTPS: Browsers enforce stricter security policies for WebRTC on non-secure (HTTP) connections. Deploy your application over HTTPS to ensure proper functionality.

4. Additional Common Issues

a. Unable to Connect to MediaSFU Server

Solution:

  • Check Credentials: Ensure that your apiUserName and apiKey are correctly set and valid.
  • Network Connectivity: Verify that your device has a stable internet connection.
  • Server Status: Confirm that MediaSFU servers are operational by checking the MediaSFU Status Page.

b. Application Crashes or Freezes During Media Transmission

Solution:

  • Update Dependencies: Ensure that all Flutter dependencies are up-to-date by running:

    flutter pub get
    flutter pub upgrade
    
  • Review Logs: Use the filtered logs (as described in section 2) to identify any errors or warnings that could indicate the cause.

  • Device Resources: Ensure that the device running the application has sufficient resources (CPU, memory) to handle media transmissions.

c. UI Elements Not Displaying Correctly

Solution:

  • Hot Reload: Use Flutter's hot reload feature to apply code changes without restarting the application.
  • Check Widget Tree: Ensure that your widgets are properly nested and there are no conflicts in the UI hierarchy.
  • Responsive Design: Verify that your UI is responsive and adapts to different screen sizes and orientations.

Key Points to Remember

  • Interactive Testing: Utilize MediaSFU's frontend to verify event handling and media streams.
  • Log Filtering: Use command-line tools to filter out non-Flutter logs for a cleaner debugging experience.
  • Web Testing: Deploy your app to a live environment and test across multiple browsers to resolve remote stream issues.
  • Stay Updated: Regularly update your dependencies and monitor server statuses to prevent common issues.
  • Responsive UI: Ensure your application's UI is responsive and correctly structured to avoid display problems.
  1. Interactive Testing with MediaSFU's Frontend: Users can interactively join MediaSFU's frontend in the same room to analyze if various events or media transmissions are happening as expected. For example, adding a user there to check changes made by the host and vice versa.

  2. Reducing Terminal Log Clutter: When running your Flutter application on a physical device in debug mode, you might encounter excessive logs from Java that clutter the terminal output. To address this issue, you can use the following command to filter out irrelevant logs:

    flutter run | sed '/^.\// { /^\(V\|I\|W\|E\)\/flutter/!d }'
    

    This command will only display logs related to Flutter, excluding unnecessary logs from other sources. It helps in maintaining a cleaner terminal output for better debugging experience.

  3. Issues with Remote Streams on Web: If you have issues seeing remote streams on the web, it might be due to problems related to localhost. Testing on localhost can sometimes lead to issues due to various factors, such as browser security settings or network configurations. To ensure a more reliable testing environment, it is recommended to:

    • Build and Deploy: Make a production build of your application and deploy it to a staging or live environment. This will help you test the application in a real-world scenario, bypassing potential issues related to localhost.
    • Use a Different Browser: Sometimes, different browsers handle local streams differently. Testing on multiple browsers can help identify if the issue is browser-specific. These troubleshooting steps should help users address common issues and optimize their experience with MediaSFU. If the issues persist or additional assistance is needed, users can refer to the documentation or reach out to the support team for further assistance.

Contributing

We welcome contributions from the community to improve the project! If you'd like to contribute, please check out our GitHub repository and follow the guidelines outlined in the README.

If you encounter any issues or have suggestions for improvement, please feel free to open an issue on GitHub.

We appreciate your interest in contributing to the project!

If you need further assistance or have any questions, feel free to ask!

https://github.com/MediaSFU/MediaSFU-ReactJS/assets/157974639/a6396722-5b2f-4e93-a5b3-dd53ffd20eb7

Libraries

components
components/breakout_components/breakout_rooms_modal
components/co_host_components/co_host_modal
components/display_components/alert_component
components/display_components/audio_card
components/display_components/audio_decibel_check
components/display_components/audio_grid
components/display_components/card_video_display
components/display_components/control_buttons_alt_component
components/display_components/control_buttons_component
components/display_components/control_buttons_component_touch
components/display_components/flexible_grid
components/display_components/flexible_video
components/display_components/loading_modal
components/display_components/main_aspect_component
components/display_components/main_container_component
components/display_components/main_grid_component
components/display_components/main_screen_component
components/display_components/meeting_progress_timer
components/display_components/mini_audio
components/display_components/mini_card
components/display_components/other_grid_component
components/display_components/pagination
components/display_components/sub_aspect_component
components/display_components/video_card
components/display_settings_components/display_settings_modal
components/event_settings_components/event_settings_modal
components/exit_components/confirm_exit_modal
components/media_settings_components/media_settings_modal
components/mediasfu_components/mediasfu_broadcast
components/mediasfu_components/mediasfu_chat
components/mediasfu_components/mediasfu_conference
components/mediasfu_components/mediasfu_generic
components/mediasfu_components/mediasfu_webinar
components/menu_components/custom_buttons
components/menu_components/meeting_id_component
components/menu_components/meeting_passcode_component
components/menu_components/share_buttons_component
components/message_components/message_panel
components/message_components/messages_modal
components/misc_components/confirm_here_modal
components/misc_components/prejoin_page
components/misc_components/share_event_modal
components/misc_components/welcome_page
components/misc_components/welcome_page_qrcode
components/participants_components/participant_list
components/participants_components/participant_list_item
components/participants_components/participant_list_others
components/participants_components/participant_list_others_item
components/participants_components/participants_modal
components/polls_components/poll_modal
components/recording_components/advanced_panel_component
components/recording_components/recording_modal
components/recording_components/standard_panel_component
components/requests_components/render_request_component
components/requests_components/requests_modal
components/waiting_components/waiting_modal
consumers
consumers/add_videos_grid
consumers/auto_adjust
consumers/calculate_rows_and_columns
consumers/change_vids
consumers/check_grid
consumers/check_permission
consumers/check_screen_share
consumers/close_and_resize
consumers/compare_active_names
consumers/compare_screen_states
consumers/connect_ips
consumers/connect_local_ips
consumers/connect_recv_transport
consumers/connect_send_transport
consumers/connect_send_transport_audio
consumers/connect_send_transport_screen
consumers/connect_send_transport_video
consumers/consumer_resume
consumers/control_media
consumers/create_send_transport
consumers/disconnect_send_transport_audio
consumers/disconnect_send_transport_screen
consumers/disconnect_send_transport_video
consumers/disp_streams
consumers/generate_page_content
consumers/get_estimate
consumers/get_piped_producers_alt
consumers/get_producers_piped
consumers/get_videos
consumers/mix_streams
consumers/on_screen_changes
consumers/prepopulate_user_media
consumers/process_consumer_transports
consumers/process_consumer_transports_audio
consumers/re_port
consumers/re_update_inter
consumers/readjust
consumers/receive_all_piped_transports
consumers/receive_room_messages
consumers/reorder_streams
consumers/request_screen_share
consumers/resume_pause_audio_streams
consumers/resume_pause_streams
consumers/resume_send_transport_audio
consumers/signal_new_consumer_transport
consumers/socket_receive_methods/join_consume_room
consumers/socket_receive_methods/new_pipe_producer
consumers/socket_receive_methods/producer_closed
consumers/start_share_screen
consumers/stop_share_screen
consumers/stream_success_audio
consumers/stream_success_audio_switch
consumers/stream_success_screen
consumers/stream_success_video
consumers/switch_user_audio
consumers/switch_user_video
consumers/switch_user_video_alt
consumers/trigger
consumers/update_mini_cards_grid
consumers/update_participant_audio_decibels
main
main_broadcast
main_chat
main_conference
main_generic
main_webinar
mediasfu_sdk
methods
methods/breakout_rooms_methods/breakout_room_updated
methods/breakout_rooms_methods/launch_breakout_rooms
methods/co_host_methods/launch_co_host
methods/co_host_methods/modify_co_host_settings
methods/display_settings_methods/launch_display_settings
methods/display_settings_methods/modify_display_settings
methods/exit_methods/confirm_exit
methods/exit_methods/launch_confirm_exit
methods/media_settings_methods/launch_media_settings
methods/menu_methods/launch_menu_modal
methods/message_methods/launch_messages
methods/message_methods/send_message
methods/participants_methods/launch_participants
methods/participants_methods/message_participants
methods/participants_methods/mute_participants
methods/participants_methods/remove_participants
methods/polls_methods/handle_create_poll
methods/polls_methods/handle_end_poll
methods/polls_methods/handle_vote_poll
methods/polls_methods/launch_poll
methods/polls_methods/poll_updated
methods/recording_methods/check_pause_state
methods/recording_methods/check_resume_state
methods/recording_methods/confirm_recording
methods/recording_methods/launch_recording
methods/recording_methods/record_pause_timer
methods/recording_methods/record_resume_timer
methods/recording_methods/record_start_timer
methods/recording_methods/record_update_timer
methods/recording_methods/start_recording
methods/recording_methods/stop_recording
methods/recording_methods/update_recording
methods/requests_methods/launch_requests
methods/requests_methods/respond_to_requests
methods/settings_methods/launch_settings
methods/settings_methods/modify_settings
methods/stream_methods/click_audio
methods/stream_methods/click_chat
methods/stream_methods/click_screen_share
methods/stream_methods/click_video
methods/stream_methods/switch_audio
methods/stream_methods/switch_video
methods/stream_methods/switch_video_alt
methods/utils/check_limits_and_make_request
methods/utils/create_join_room
methods/utils/create_response_join_room
methods/utils/create_room_on_media_sfu
methods/utils/format_number
methods/utils/generate_random_messages
methods/utils/generate_random_participants
methods/utils/generate_random_polls
methods/utils/generate_random_request_list
methods/utils/generate_random_waiting_room_list
methods/utils/get_media_devices_list
Retrieves the list of available media devices.
methods/utils/get_modal_position
methods/utils/get_overlay_position
methods/utils/get_participant_media
Retrieves the media stream of a participant by ID or name.
methods/utils/initial_values
methods/utils/join_room_on_media_sfu
methods/utils/mediasfu_parameters
methods/utils/meeting_timer/start_meeting_progress_timer
methods/utils/mini_audio_player/mini_audio_player
methods/utils/producer/a_params
methods/utils/producer/h_params
methods/utils/producer/screen_params
methods/utils/producer/v_params
methods/utils/producer/video_capture_constraints
methods/utils/sleep
methods/utils/sound_player
methods/utils/validate_alphanumeric
methods/waiting_methods/launch_waiting
methods/waiting_methods/respond_to_waiting
misc
producer_client/producer_client_emits/create_device_client
producer_client/producer_client_emits/join_room_client
producer_client/producer_client_emits/update_room_parameters_client
producers
producers/producer_emits/join_con_room
producers/producer_emits/join_local_room
producers/producer_emits/join_room
producers/socket_receive_methods/all_members
producers/socket_receive_methods/all_members_rest
producers/socket_receive_methods/all_waiting_room_members
producers/socket_receive_methods/ban_participant
producers/socket_receive_methods/control_media_host
producers/socket_receive_methods/disconnect
producers/socket_receive_methods/disconnect_user_self
producers/socket_receive_methods/get_domains
producers/socket_receive_methods/host_request_response
producers/socket_receive_methods/meeting_ended
producers/socket_receive_methods/meeting_still_there
producers/socket_receive_methods/meeting_time_remaining
producers/socket_receive_methods/participant_requested
producers/socket_receive_methods/person_joined
producers/socket_receive_methods/producer_media_closed
producers/socket_receive_methods/producer_media_paused
producers/socket_receive_methods/producer_media_resumed
producers/socket_receive_methods/re_initiate_recording
producers/socket_receive_methods/receive_message
producers/socket_receive_methods/recording_notice
producers/socket_receive_methods/room_record_params
producers/socket_receive_methods/screen_producer_id
producers/socket_receive_methods/start_records
producers/socket_receive_methods/stopped_recording
producers/socket_receive_methods/time_left_recording
producers/socket_receive_methods/update_consuming_domains
producers/socket_receive_methods/update_media_settings
producers/socket_receive_methods/updated_co_host
producers/socket_receive_methods/user_waiting
sockets
sockets/socket_manager
types
types/custom_builders
types/types