npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

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

About

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

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

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

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

Open Software & Tools

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

© 2025 – Pkg Stats / Ryan Hefner

mediasfu-angular

v2.2.2

Published

MediaSFU Prebuilt Angular SDK

Readme


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


MediaSFU Angular Module 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!

✅ Angular SDK Setup Guide

Coming soon! Watch this space for our comprehensive video tutorial on setting up the Angular SDK.


Table of Contents


Quick Reference: Component Props & UI Overrides

New: UI override parity now extends across Webinar and Chat layouts, unifying customization for every MediaSFU interface.

Every primary MediaSFU UI export—MediasfuGeneric, MediasfuBroadcast, MediasfuConference, MediasfuWebinar, and MediasfuChat—now ships with a consistent prop surface and a powerful uiOverrides input, so you can bend the bundled experience to match your product without losing MediaSFU's hardened real-time logic.

Shared component inputs (applies to every MediaSFU UI component)

| Input | Type | Default | What it does | | --- | --- | --- | --- | | PrejoinPage | ComponentType | WelcomePage | Swap in a custom pre-join experience. Receives unified pre-join options so you can add branding, legal copy, or warm-up flows. | | localLink | string | "" | Point the SDK at your self-hosted MediaSFU server. Leave empty when using MediaSFU Cloud. | | connectMediaSFU | boolean | true | Toggle automatic socket/WebRTC connections. Set to false when you only need the UI shell. | | credentials | { apiUserName: string; apiKey: string } | { apiUserName: "", apiKey: "" } | Supply cloud credentials without hard-coding them elsewhere. | | useLocalUIMode | boolean | false | Run the interface in local/demo mode with no remote signaling. | | seedData, useSeed | SeedData, boolean | {}, false | Pre-populate the UI for demos, snapshot tests, or onboarding tours. | | imgSrc | string | https://mediasfu.com/images/logo192.png | Default artwork used across pre-join and modal flows. | | sourceParameters | Record<string, unknown> | undefined | Shared helper bag (media devices, participant helpers, layout handlers). Pair with updateSourceParameters to mirror the SDK's internal utilities. | | updateSourceParameters | EventEmitter | undefined | Receive the latest helper bundle so you can bridge MediaSFU logic into your own components. | | returnUI | boolean | true | When false, mount the logic only—a perfect stepping stone to a fully bespoke interface. | | noUIPreJoinOptions | CreateJoinRoomParameters \| JoinLocalEventRoomParameters | undefined | Feed pre-join data when returnUI is false and you want to bypass the on-screen wizard. | | joinRoom, createRoom | Function | undefined | Inject your own networking layers for joining or creating rooms. | | customComponent | ComponentType | undefined | Replace the entire UI while retaining transports, sockets, and helpers. | | customVideoCard, customAudioCard, customMiniCard | ComponentType | undefined | Override participant card renders to add metadata, CTAs, or badges. | | [customStyles] | Record<string, any> | undefined | Apply inline styles to the root wrapper (dashboards, split views, etc.). | | [uiOverrides] | MediasfuUICustomOverrides | undefined | Targeted component/function overrides described below. |

Power combo: Set returnUI="false" to run MediaSFU logic headless, capture helpers via updateSourceParameters output, and selectively bring UI pieces back with uiOverrides. That gives you progressive migration with minimal code churn.

import type { MediasfuUICustomOverrides } from 'mediasfu-angular';

const overrides: MediasfuUICustomOverrides = { /* ... */ };

Bring the types into your project to unlock full IntelliSense for every override slot.

Custom UI Playbook

Use a toggle-driven "playbook" component to experiment with MediaSFU's customization layers. Flip a couple of booleans and you can watch the SDK jump between prebuilt layouts, headless logic, or a fully bespoke workspace driven by customComponent.

What the playbook demonstrates

  • Connection presets: toggle connectionScenario between cloud, hybrid, or ce to swap credentials, local links, and connection modes in one place.
  • Experience selector: the selectedExperience switch renders MediasfuGeneric, MediasfuBroadcast, MediasfuWebinar, MediasfuConference, or MediasfuChat without touching the rest of your stack.
  • UI strategy flags: booleans like showPrebuiltUI, enableFullCustomUI, and enableNoUIPreJoin demonstrate how to run the MediaSFU logic with or without the bundled UI.
  • Layered overrides: toggles enable the custom video/audio/mini cards, drop-in uiOverrides for layout and modal surfaces, container styling, and backend proxy helpers.
  • Custom workspace demo: a customComponent receives live MediaSFU helpers so you can build dashboards, CRM surfaces, or any bespoke host interface.
  • Debug panel & helpers: optional JSON panel exposes the updateSourceParameters payload so you can see exactly what to wire into your own components.

Try it quickly

@Component({
  selector: 'app-custom-ui-playbook',
  template: `
    <ng-container [ngSwitch]="selectedExperience">
      <app-mediasfu-generic *ngSwitchCase="'generic'"
        [credentials]="currentPreset.credentials"
        [localLink]="currentPreset.localLink"
        [connectMediaSFU]="currentPreset.connectMediaSFU"
        [returnUI]="showPrebuiltUI"
        [uiOverrides]="overrides"
        [customStyles]="containerStyles">
      </app-mediasfu-generic>
      
      <app-mediasfu-broadcast *ngSwitchCase="'broadcast'"
        [credentials]="currentPreset.credentials"
        [localLink]="currentPreset.localLink"
        [connectMediaSFU]="currentPreset.connectMediaSFU"
        [returnUI]="showPrebuiltUI"
        [uiOverrides]="overrides">
      </app-mediasfu-broadcast>
      
      <!-- Similar for webinar, conference, chat -->
    </ng-container>
  `
})
export class CustomUIPlaybookComponent {
  connectionScenario: 'cloud' | 'hybrid' | 'ce' = 'cloud';
  selectedExperience: 'generic' | 'broadcast' | 'webinar' | 'conference' | 'chat' = 'generic';
  showPrebuiltUI = true;
  enableFullCustomUI = false;

  connectionPresets = {
    cloud: { 
      credentials: { apiUserName: 'demo', apiKey: 'demo' }, 
      localLink: '', 
      connectMediaSFU: true 
    },
    hybrid: { 
      credentials: { apiUserName: 'demo', apiKey: 'demo' }, 
      localLink: 'http://localhost:3000', 
      connectMediaSFU: true 
    },
    ce: { 
      credentials: undefined, 
      localLink: 'http://localhost:3000', 
      connectMediaSFU: false 
    },
  };

  get currentPreset() {
    return this.connectionPresets[this.connectionScenario];
  }

  overrides: MediasfuUICustomOverrides = {
    mainContainer: this.enableFullCustomUI ? {
      component: CustomMainContainerComponent
    } : undefined,
  };

  containerStyles = {
    background: 'linear-gradient(135deg, #0f172a, #1e3a8a)',
    minHeight: '100vh'
  };
}

Toggle the configuration values at the top of the playbook and watch the UI reconfigure instantly. It's the fastest path to understand MediaSFU's override surface before you fold the patterns into your production entrypoint.

Passing custom props and UI overrides

Use the same playbook to validate bespoke cards, override bundles, and fully custom workspaces before you move them into production code:

@Component({
  selector: 'app-advanced-playbook',
  template: `
    <app-mediasfu-generic
      [credentials]="credentials"
      [customVideoCard]="videoCard"
      [customAudioCard]="audioCard"
      [customMiniCard]="miniCard"
      [customComponent]="enableFullCustomUI ? customWorkspace : undefined"
      [customStyles]="containerStyles"
      [uiOverrides]="uiOverrides">
    </app-mediasfu-generic>
  `
})
export class AdvancedPlaybookComponent {
  credentials = { apiUserName: 'demo', apiKey: 'demo' };
  enableFullCustomUI = false;

  // Custom card components with themed styling
  videoCard = VideoCardComponent; // Pass component class
  audioCard = AudioCardComponent;
  miniCard = MiniCardComponent;
  customWorkspace = CustomWorkspaceComponent;

  containerStyles = {
    background: '#0f172a',
    borderRadius: '32px',
    overflow: 'hidden'
  };

  uiOverrides: MediasfuUICustomOverrides = {
    mainContainer: {
      component: CustomMainContainerComponent
    },
    menuModal: {
      component: CustomMenuModalComponent
    },
    consumerResume: {
      wrap: (original) => async (params) => {
        const startedAt = performance.now();
        const result = await original(params);
        console.log('consumer_resume', {
          durationMs: performance.now() - startedAt,
          consumerId: params?.consumer?.id,
        });
        return result;
      },
    },
  };
}

Because the playbook surfaces updateSourceParameters, you can also log or snapshot the helper bundle (getParticipantMedia, toggleMenuModal, showAlert, and more) to ensure your custom UI always receives the hooks it expects.

uiOverrides input — override keys at a glance

Each key accepts a CustomComponentOverride<Props> or CustomFunctionOverride<Fn> object with optional component and wrap fields. You can fully replace the default implementation or wrap it while forwarding props.

Layout & control surfaces

| Key | Default component | Typical use | | --- | --- | --- | | mainContainer | MainContainerComponent | Inject theming providers or dashboard layouts. | | mainAspect | MainAspectComponent | Tune how the main region splits space. | | mainScreen | MainScreenComponent | Orchestrate hero video + gallery interplay. | | mainGrid | MainGridComponent | Modify layout or layering of primary participants. | | subAspect | SubAspectComponent | Restyle fixed control strips in webinar/conference modes. | | otherGrid | OtherGridComponent | Change presentation of off-stage attendees. | | flexibleGrid, flexibleGridAlt | FlexibleGrid | Implement AI-driven or branded array layouts. | | flexibleVideo | FlexibleVideo | Add captions, watermarks, or overlays to highlighted speakers. | | audioGrid | AudioGrid | Customise audio-only attendee presentation. | | pagination | Pagination | Introduce infinite scroll or auto-cycling carousels. | | controlButtons | ControlButtonsComponent | Rebrand the primary action bar. | | controlButtonsAlt | ControlButtonsAltComponent | Control secondary button clusters. | | controlButtonsTouch | ControlButtonsComponentTouch | Deliver mobile-first controls (used heavily by MediasfuChat). |

Participant cards & widgets

| Key | Default component | Typical use | | --- | --- | --- | | videoCard | VideoCard | Add host badges, reactions, or CRM overlays. | | audioCard | AudioCard | Swap avatars or expose spoken-language info. | | miniCard | MiniCard | Customize thumbnails in picture-in-picture modes. | | miniAudio | MiniAudio | Re-style the audio-only mini indicators. | | meetingProgressTimer | MeetingProgressTimer | Replace the elapsed-time widget with countdowns or milestones. | | miniAudioPlayer | MiniAudioPlayer | Provide alternative UI for recorded clip playback. |

Modals, dialogs, and collaboration surfaces

| Key | Default component | Typical use | | --- | --- | --- | | loadingModal | LoadingModal | Show branded skeletons while connecting. | | alert | AlertComponent | Route alerts through your notification system. | | menuModal | MenuModal | Redesign quick-action trays. | | eventSettingsModal | EventSettingsModal | Extend host tools with your own settings. | | requestsModal | RequestsModal | Build moderation queues tailored to your workflows. | | waitingRoomModal | WaitingRoomModal | Deliver custom waiting-room experiences. | | coHostModal | CoHostModal | Manage co-hosts with bespoke UX. | | mediaSettingsModal | MediaSettingsModal | Embed device tests or instructions. | | participantsModal | ParticipantsModal | Introduce advanced filters, search, or notes. | | messagesModal | MessagesModal | Drop in your full-featured chat module. | | displaySettingsModal | DisplaySettingsModal | Let users pick layouts, themes, or captions. | | confirmExitModal | ConfirmExitModal | Meet compliance wording requirements. | | confirmHereModal | ConfirmHereModal | Customize attendance confirmations for webinars. | | shareEventModal | ShareEventModal | Add referral codes or QR sharing. | | recordingModal | RecordingModal | Tailor recording confirmation flows. | | pollModal | PollModal | Integrate your polling/quiz engine. | | backgroundModal | BackgroundModal | Hook AI background replacement or brand presets. | | breakoutRoomsModal | BreakoutRoomsModal | Implement drag-and-drop or AI room suggestions. | | configureWhiteboardModal | ConfigureWhiteboardModal | Adjust collaboration permissions before launch. | | whiteboard | Whiteboard | Replace with your whiteboard provider. | | screenboard | Screenboard | Modify shared-screen annotation layers. | | screenboardModal | ScreenboardModal | Reimagine how users enable shared annotations. |

Entry flows & custom renderers

| Key | Default component | Typical use | | --- | --- | --- | | welcomePage | WelcomePage | Provide a fully branded welcome/marketing splash. | | preJoinPage | PrejoinPage | Override the wizard used before joining live sessions. | | customMenuButtonsRenderer | ControlButtonsAltComponent | Supply a bespoke renderer for menu button groups without overriding each button. |

Function overrides

| Key | Default function | Typical use | | --- | --- | --- | | consumerResume | consumerResume | Wrap errors, capture analytics, or rate-limit consumer resume behavior. | | addVideosGrid | addVideosGrid | Replace participant ordering or layout heuristics on the fly. | | prepopulateUserMedia | prepopulateUserMedia | Customize initial media setup with custom video/audio/mini cards. |

Function overrides support { implementation, wrap }. Provide implementation for a full replacement, or wrap to intercept the default behavior before/after it runs.

Example: swap the chat modal and theme the controls

import { Component } from '@angular/core';
import { MediasfuGeneric } from 'mediasfu-angular';
import { MyChatModalComponent } from './ui/my-chat-modal.component';
import { MyControlsComponent } from './ui/my-controls.component';

@Component({
  selector: 'app-my-meeting',
  template: `
    <app-mediasfu-generic
      [credentials]="credentials"
      [uiOverrides]="uiOverrides">
    </app-mediasfu-generic>
  `
})
export class MyMeetingComponent {
  credentials = { apiUserName: 'your-api-user', apiKey: 'your-api-key' };

  uiOverrides = {
    messagesModal: {
      component: MyChatModalComponent,
    },
    controlButtons: {
      component: MyControlsComponent,
    },
  };
}

Example: wrap a MediaSFU helper instead of replacing it

import { Component } from '@angular/core';
import { MediasfuConference } from 'mediasfu-angular';

@Component({
  selector: 'app-analytics-meeting',
  template: `
    <app-mediasfu-conference
      [credentials]="credentials"
      [uiOverrides]="uiOverrides">
    </app-mediasfu-conference>
  `
})
export class AnalyticsMeetingComponent {
  credentials = { apiUserName: 'your-api-user', apiKey: 'your-api-key' };

  uiOverrides = {
    consumerResume: {
      wrap: (original) => async (params) => {
        const startedAt = performance.now();
        const result = await original(params);
        
        // Send analytics
        this.analytics.track('consumer_resume', {
          durationMs: performance.now() - startedAt,
          consumerId: params?.consumer?.id,
        });
        
        return result;
      },
    },
  };

  constructor(private analytics: AnalyticsService) {}
}

Features

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

  1. Screen Sharing with Annotation Support: Share your screen with participants and annotate in real-time for enhanced presentations and collaborations.
  2. Collaborative Whiteboards: Create and share whiteboards for real-time collaborative drawing and brainstorming sessions.
  3. Breakout Rooms: Create multiple sub-meetings within a single session to enhance collaboration and focus.
  4. Pagination: Efficiently handle large participant lists with seamless pagination.
  5. Polls: Conduct real-time polls to gather instant feedback from participants.
  6. Media Access Requests Management: Manage media access requests with ease to ensure smooth operations.
  7. Video Effects: Apply various video effects, including virtual backgrounds, to enhance the visual experience.
  8. Chat (Direct & Group): Facilitate communication with direct and group chat options.
  9. Cloud Recording (track-based): Customize recordings with track-based options, including watermarks, name tags, background colors, and more.
  10. 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 Angular SDK now includes powerful utility methods for fine-grained control over media devices and participant streams:

getMediaDevicesList - Device Enumeration

Enumerate available cameras and microphones with automatic permission handling:

// Get all available cameras
const cameras = await sourceParameters.getMediaDevicesList('videoinput');
cameras.forEach(camera => {
  console.log(`Camera: ${camera.label} (${camera.deviceId})`);
});

// Get all available microphones
const microphones = await sourceParameters.getMediaDevicesList('audioinput');
microphones.forEach(mic => {
  console.log(`Microphone: ${mic.label} (${mic.deviceId})`);
});

Use Cases:

  • Build custom device selection interfaces
  • Detect available media hardware
  • Switch between multiple cameras/microphones
  • Pre-flight device checks before joining

See full documentation and examples →


getParticipantMedia - Stream Access

Retrieve specific participant's video or audio streams by ID or name:

// Get participant video stream by producer ID
const videoStream = await sourceParameters.getParticipantMedia({
  id: 'producer-123',
  kind: 'video'
});

// Get participant audio stream by name
const audioStream = await sourceParameters.getParticipantMedia({
  name: 'John Doe',
  kind: 'audio'
});

// Use the stream (e.g., attach to video element)
if (videoStream) {
  videoElement.srcObject = videoStream;
}

Use Cases:

  • Monitor specific participant streams
  • Create custom video layouts with individual control
  • Build stream recording features
  • Implement advanced audio/video processing
  • Create picture-in-picture views for specific users

See full documentation and examples →


These utilities enable advanced features like custom device selection interfaces, participant stream monitoring, and dynamic media routing. Both methods are fully integrated with Angular's reactive patterns using RxJS.

Getting Started

This section will guide users through the initial setup and installation of the npm module.

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

Instructions on how to install the module using npm.

1. Add the package to your project

```bash
npm install mediasfu-angular
```

2. Bootstrap Integration

The `mediasfu-angular` package requires Bootstrap for styling. Bootstrap is included by default with the package, so you do not need to install it separately. Ensure that Bootstrap's CSS is correctly added to your project's styles.

1. **Check `angular.json`:**

  Ensure that `node_modules/bootstrap/dist/css/bootstrap.min.css` is listed in the `styles` array of your Angular application's build options.

  ```json
  {
    "projects": {
      "your-app-name": {
        "architect": {
          "build": {
            "options": {
              "styles": [
                "node_modules/bootstrap/dist/css/bootstrap.min.css",
                "src/styles.css"
              ],
              // ... other configurations
            }
          }
        }
      }
    }
  }
  ```

Note: The mediasfu-angular package should handle the Bootstrap's package installation automatically. If it's not present, you may need to add it manually install Bootstrap.

3. Configure MediaSFU's PreJoinPage Requirements

If you intend to use MediaSFU's `PreJoinPage` component, additional configuration is required. You need to provide the `HttpClient` and `CookieService` providers in your application's configuration. These packages should have been installed by default as well else add manually. 

#### Update `app.config.ts`

Add the necessary providers to your `app.config.ts` file. Below is an example configuration:

```typescript
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideZoneChangeDetection } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { CookieService } from 'ngx-cookie-service';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideClientHydration(),
    provideHttpClient(),
    CookieService
  ],
};

```

4. Obtain an API Key (If Required)

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

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.

📘 Angular SDK Guide

This comprehensive guide will walk you through everything you need to know about building real-time communication apps with MediaSFU's Angular 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

npm install mediasfu-angular

Step 2: Import and Use

// app.component.ts
import { Component } from '@angular/core';
import { MediasfuGeneric } from 'mediasfu-angular';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MediasfuGeneric],
  template: `<app-mediasfu-generic></app-mediasfu-generic>`,
})
export class AppComponent { }

Alternative with Credentials:

import { Component } from '@angular/core';
import { MediasfuGeneric, PreJoinPage } from 'mediasfu-angular';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MediasfuGeneric],
  template: `
    <app-mediasfu-generic
      [PrejoinPage]="PreJoinPage"
      [credentials]="credentials">
    </app-mediasfu-generic>
  `,
})
export class AppComponent {
  PreJoinPage = PreJoinPage;
  credentials = {
    apiUserName: 'your_username',
    apiKey: 'your_api_key',
  };
}

Step 3: Run Your App

ng serve

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
  • ✅ Breakout rooms
  • ✅ Polls and whiteboards

Understanding MediaSFU Architecture

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

The Three-Layer Architecture

┌─────────────────────────────────────────────┐
│        Your Angular Application             │
│  (components, services, business logic)     │
└─────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────┐
│       MediaSFU Components Layer             │
│ (MediasfuGeneric, MediasfuBroadcast, etc.)  │
│        - Pre-built UI components            │
│        - Event handling                     │
│        - State management (RxJS)            │
└─────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────┐
│       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 { 
  MediasfuWebinar, 
  MediasfuBroadcast, 
  MediasfuConference 
} from 'mediasfu-angular';

@Component({
  // For a webinar
  template: `<app-mediasfu-webinar [credentials]="credentials"></app-mediasfu-webinar>`,
  // For a broadcast
  // template: `<app-mediasfu-broadcast [credentials]="credentials"></app-mediasfu-broadcast>`,
  // For a conference
  // template: `<app-mediasfu-conference [credentials]="credentials"></app-mediasfu-conference>`,
})

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.

import { Component } from '@angular/core';
import { MediasfuGeneric } from 'mediasfu-angular';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MediasfuGeneric],
  template: `<app-mediasfu-generic [credentials]="credentials"></app-mediasfu-generic>`,
})
export class AppComponent {
  credentials = { apiUserName: 'username', apiKey: 'key' };
}

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.

import { Component, OnInit } from '@angular/core';
import { MediasfuGeneric } from 'mediasfu-angular';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MediasfuGeneric, CommonModule],
  template: `
    <app-mediasfu-generic
      [returnUI]="false"
      [sourceParameters]="sourceParameters"
      [updateSourceParameters]="updateSourceParameters.bind(this)"
      [credentials]="credentials"
      [noUIPreJoinOptions]="preJoinOptions">
    </app-mediasfu-generic>

    <!-- Your custom UI -->
    @if (sourceParameters) {
      <div class="custom-controls">
        <button (click)="toggleVideo()">
          {{ sourceParameters.videoAlreadyOn ? 'Stop Video' : 'Start Video' }}
        </button>
        <button (click)="toggleAudio()">
          {{ sourceParameters.audioAlreadyOn ? 'Mute' : 'Unmute' }}
        </button>
        <button (click)="toggleScreenShare()">
          {{ sourceParameters.screenAlreadyOn ? 'Stop Sharing' : 'Share Screen' }}
        </button>
      </div>
    }
  `,
})
export class AppComponent implements OnInit {
  sourceParameters: any = null;
  credentials = { apiUserName: 'username', apiKey: 'key' };
  preJoinOptions = {
    action: 'create',
    userName: 'Your Name',
    capacity: 50,
    duration: 30,
    eventType: 'conference'
  };

  updateSourceParameters(params: any) {
    this.sourceParameters = params;
  }

  toggleVideo() {
    this.sourceParameters?.clickVideo({ parameters: this.sourceParameters });
  }

  toggleAudio() {
    this.sourceParameters?.clickAudio({ parameters: this.sourceParameters });
  }

  toggleScreenShare() {
    this.sourceParameters?.clickScreenShare({ parameters: this.sourceParameters });
  }
}

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.

import { Component } from '@angular/core';
import { 
  MediasfuGeneric, 
  FlexibleVideo, 
  FlexibleGrid 
} from 'mediasfu-angular';

@Component({
  selector: 'app-custom-main',
  standalone: true,
  imports: [FlexibleVideo, FlexibleGrid, CommonModule],
  template: `
    <div class="custom-layout">
      <!-- Custom header -->
      <div class="custom-header">
        <h1>{{ parameters.roomName }}</h1>
        <span>{{ parameters.participants.length }} participants</span>
      </div>

      <!-- Use MediaSFU's components in your layout -->
      <app-flexible-video
        [customWidth]="windowWidth"
        [customHeight]="600"
        [parameters]="parameters">
      </app-flexible-video>

      <app-flexible-grid
        [customWidth]="windowWidth"
        [customHeight]="400"
        [parameters]="parameters">
      </app-flexible-grid>

      <!-- Custom footer -->
      <div class="custom-footer">
        <button (click)="toggleVideo()">
          {{ parameters.videoAlreadyOn ? 'Stop Video' : 'Start Video' }}
        </button>
      </div>
    </div>
  `,
  styles: [`
    .custom-layout { 
      display: flex; 
      flex-direction: column; 
      height: 100vh; 
    }
    .custom-header { 
      padding: 20px; 
      background: #1976d2; 
      color: white; 
    }
  `]
})
export class CustomMainComponent {
  parameters: any;
  windowWidth = window.innerWidth;

  toggleVideo() {
    this.parameters?.clickVideo({ parameters: this.parameters });
  }
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MediasfuGeneric],
  template: `
    <app-mediasfu-generic
      [credentials]="credentials"
      [PrejoinPage]="PreJoinPage"
      [customComponent]="CustomMainComponent">
    </app-mediasfu-generic>
  `,
})
export class AppComponent {
  PreJoinPage = PreJoinPage;
  CustomMainComponent = CustomMainComponent;
  credentials = { apiUserName: 'username', apiKey: 'key' };
}

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 sourceParameters object (or parameters in custom components) is your gateway to all MediaSFU functionality. It's powered by RxJS BehaviorSubjects for reactive state management:

// Available in sourceParameters or parameters object
{
  // Media Controls (Methods)
  clickVideo: (options) => {},
  clickAudio: (options) => {},
  clickScreenShare: (options) => {},
  
  // Room State (BehaviorSubject values)
  roomName: 'meeting-123',
  participants: [...],
  allVideoStreams: [...],
  allAudioStreams: [...],
  
  // UI State (BehaviorSubject values)
  videoAlreadyOn: false,
  audioAlreadyOn: false,
  screenAlreadyOn: false,
  
  // Update Functions (BehaviorSubject next())
  updateVideoAlreadyOn: (value) => {},
  updateAudioAlreadyOn: (value) => {},
  
  // 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?.clickVideo({ parameters: sourceParameters });

// In Mode 3 (Component Replacement): Passed to your custom component
@Component({
  template: `<button (click)="toggleVideo()">Toggle</button>`
})
export class CustomComponent {
  @Input() parameters: any;
  
  toggleVideo() {
    this.parameters.clickVideo({ parameters: this.parameters });
  }
}

// Subscribing to reactive state changes
sourceParameters.participants.subscribe((participants) => {
  console.log('Participants updated:', participants);
});

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

import { FlexibleVideo } from 'mediasfu-angular';

@Component({
  template: `
    <app-flexible-video
      [customWidth]="windowWidth"
      [customHeight]="600"
      [parameters]="parameters">
    </app-flexible-video>
  `
})
  • Automatically handles main presenter or screen share
  • Smooth transitions between different video sources
  • Responsive sizing

FlexibleGrid - Participant grid layout

import { FlexibleGrid } from 'mediasfu-angular';

@Component({
  template: `
    <app-flexible-grid
      [customWidth]="windowWidth"
      [customHeight]="800"
      [parameters]="parameters">
    </app-flexible-grid>
  `
})
  • Intelligent grid sizing (2x2, 3x3, 4x4, etc.)
  • Pagination for large participant lists
  • Automatic reflow on window resize

AudioGrid - Audio-only participants

import { AudioGrid } from 'mediasfu-angular';

@Component({
  template: `<app-audio-grid [parameters]="parameters"></app-audio-grid>`
})
  • 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

import { Component } from '@angular/core';
import {
  MainContainerComponent,
  FlexibleVideo,
  FlexibleGrid,
  AudioGrid
} from 'mediasfu-angular';

@Component({
  selector: 'app-custom-layout',
  standalone: true,
  imports: [
    MainContainerComponent,
    FlexibleVideo,
    FlexibleGrid,
    AudioGrid,
    CommonModule
  ],
  template: `
    <app-main-container-component>
      <div class="layout-container">
        <!-- Main video area -->
        <div class="main-video">
          <app-flexible-video
            [customWidth]="windowWidth"
            [customHeight]="windowHeight * 0.6"
            [parameters]="parameters">
          </app-flexible-video>
        </div>
        
        <!-- Participant grid -->
        <div class="participant-grid">
          <app-flexible-grid
            [customWidth]="windowWidth"
            [customHeight]="windowHeight * 0.3"
            [parameters]="parameters">
          </app-flexible-grid>
        </div>
        
        <!-- Audio-only participants -->
        <div class="audio-participants">
          <app-audio-grid [parameters]="parameters"></app-audio-grid>
        </div>
      </div>
    </app-main-container-component>
  `,
  styles: [`
    .layout-container {
      display: flex;
      flex-direction: column;
      height: 100vh;
    }
    .main-video { flex: 3; }
    .participant-grid { flex: 2; }
    .audio-participants { height: 80px; }
  `]
})
export class CustomLayoutComponent {
  @Input() parameters: any;
  windowWidth = window.innerWidth;
  windowHeight = window.innerHeight;
}

2. Control Components: User Interactions

ControlButtonsComponent - Standard control bar

import { ControlButtonsComponent } from 'mediasfu-angular';

@Component({
  template: `
    <app-control-buttons-component
      [parameters]="parameters"
      [position]="'bottom'">
    </app-control-buttons-component>
  `
})

Includes: mute, video, screenshare, participants, chat, settings, etc.

ControlButtonsAltComponent - Alternative layout

import { ControlButtonsAltComponent } from 'mediasfu-angular';

@Component({
  template: `
    <app-control-buttons-alt-component
      [parameters]="parameters"
      [position]="'top'">
    </app-control-buttons-alt-component>
  `
})

Different button arrangement optimized for specific layouts.

ControlButtonsComponentTouch - Touch-optimized controls

import { ControlButtonsComponentTouch } from 'mediasfu-angular';

@Component({
  template: `
    <app-control-buttons-component-touch
      [parameters]="parameters">
    </app-control-buttons-component-touch>
  `
})

Floating action buttons optimized for mobile/tablet interfaces.

3. Modal Components: Feature Interfaces

MediaSFU includes modals for various features:

import {
  ParticipantsModal,
  MessagesModal,
  SettingsModal,
  DisplaySettingsModal,
  RecordingModal,
  PollModal,
  BreakoutRoomsModal
} from 'mediasfu-angular';

// These are automatically rendered when enabled
// Control their visibility via parameters
parameters.updateIsParticipantsModalVisible.next(true);
parameters.updateIsMessagesModalVisible.next(true);
parameters.updateIsSettingsModalVisible.next(true);

Available modals:

  • ParticipantsModal - Participant list management
  • MessagesModal - Chat interface
  • SettingsModal - Event and room settings
  • DisplaySettingsModal - Layout and display options
  • RecordingModal - Recording controls and settings
  • PollModal - Create and manage polls
  • BreakoutRoomsModal - Breakout room management
  • MediaSettingsModal - Camera/microphone selection
  • BackgroundModal - Virtual background settings
  • ConfigureWhiteboardModal - Whiteboard configuration

Example: Programmatically showing modals

@Component({
  selector: 'app-custom-toolbar',
  template: `
    <div class="custom-toolbar">
      <button (click)="showParticipants()">
        Show Participants ({{ participantCount }})
      </button>
      
      <button (click)="openChat()">
        Open Chat
      </button>
      
      <button (click)="createPoll()">
        Create Poll
      </button>
    </div>
  `
})
export class CustomToolbarComponent {
  @Input() parameters: any;

  get participantCount() {
    return this.parameters?.participants?.length || 0;
  }

  showParticipants() {
    this.parameters?.updateIsParticipantsModalVisible.next(true);
  }

  openChat() {
    this.parameters?.updateIsMessagesModalVisible.next(true);
  }

  createPoll() {
    this.parameters?.launchPoll?.launchPoll({ parameters: this.parameters });
  }
}

4. Video Cards: Individual Participant Display

VideoCard - Individual participant video element

import { VideoCard } from 'mediasfu-angular';

@Component({
  template: `
    <app-video-card
      [videoStream]="participantStream"
      [remoteProducerId]="'producer-id'"
      [eventType]="'conference'"
      [forceFullDisplay]="false"
      [participant]="participantObject"
      [backgroundColor]="'#000000'"
      [showControls]="true"
      [showInfo]="true"
      [name]="'Participant Name'"
      [parameters]="parameters">
    </app-video-card>
  `
})

AudioCard - Individual audio-only participant

import { AudioCard } from 'mediasfu-angular';

@Component({
  template: `
    <app-audio-card
      [name]="'Participant Name'"
      [barColor]="'#4CAF50'"
      [textColor]="'#FFFFFF'"
      [customStyle]="{ borderRadius: '10px' }"
      [controlsPosition]="'topLeft'"
      [infoPosition]="'topRight'"
      [participant]="participantObject"
      [parameters]="parameters">
    </app-audio-card>
  `
})

MiniCard - Compact participant display (for grids)

import { MiniCard } from 'mediasfu-angular';

@Component({
  template: `
    <app-mini-card
      [participant]="participantObject"
      [showControls]="false"
      [parameters]="parameters">
    </app-mini-card>
  `
})

Example: Custom Video Card

@Component({
  selector: 'app-my-custom-video-card',
  standalone: true,
  template: `
    <div class="custom-video-card">
      <video 
        #videoElement
        [srcObject]="stream"
        autoplay
        [muted]="true"
        playsinline>
      </video>
      
      <div class="participant-info">
        {{ participant.name }} 
        @if (participant.muted) {
          <span>🔇</span>
        }
      </div>
    </div>
  `,
  styles: [`
    .custom-video-card {
      border: 3px solid #00ff88;
      border-radius: 15px;
      overflow: hidden;
      position: relative;
    }
    video {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    .participant-info {
      position: absolute;
      bottom: 0;
      left: 0;
      right: 0;
      background: rgba(0, 255, 136, 0.8);
      color: black;
      padding: 8px;
      font-weight: bold;
    }
  `]
})
export class MyCustomVideoCardComponent {
  @Input() stream!: MediaStream;
  @Input() participant!: any;
  @Input() parameters!: any;
}

// Use it in MediasfuGeneric
@Component({
  template: `
    <app-mediasfu-generic 
      [credentials]="credentials"
      [customVideoCard]="CustomVideoCard">
    </app-mediasfu-generic>
  `
})
export class AppComponent {
  CustomVideoCard = MyCustomVideoCardComponent;
  credentials = { apiUserName: 'username', apiKey: 'key' };
}

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({ parameters });

// Switch camera (front/back on mobile)
parameters.switchVideoAlt({ parameters });

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

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

// Subscribe to video state changes (RxJS)
parameters.videoAlreadyOn.subscribe((isOn: boolean) => {
  console.log('Video is now:', isOn ? 'ON' : 'OFF');
});

// Update video state programmatically
parameters.updateVideoAlreadyOn.next(true);

Audio Control

// Toggle audio on/off
parameters.clickAudio({ parameters });

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

// Get current audio state
const isAudioOn = parameters.audioAlreadyOn;
const hasHostPermission = parameters.micAction; // Host approval status

// Subscribe to audio state changes
parameters.audioAlreadyOn.subscribe((isOn: boolean) => {
  console.log('Audio is now:', isOn ? 'ON' : 'OFF');
});

// Mute/unmute specific participant (host only)
parameters.controlMedia({
  participantId: 'participant-id',
  participantName: 'John Doe',
  type: 'audio',
  socket: parameters.socket,
  roomName: parameters.roomName
});

Screen Sharing

// Start screen sharing
parameters.clickScreenShare({ parameters });

// Stop screen sharing
parameters.stopShareScreen({ parameters });

// Check if screen sharing is available
const canShare = await parameters.checkScreenShare({ parameters });

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

// Subscribe to screen share state
parameters.screenAlreadyOn.subscribe((isSharing: boolean) => {
  console.log('Screen sharing:', isSharing ? 'ACTIVE' : 'INACTIVE');
});

Media Device and Stream Utility Methods

Get Available Media Devices

// Get available cameras
const cameras = await parameters.getMediaDevicesList('videoinput');
cameras.forEach(camera => {
  console.log(`Camera: ${camera.label} (${camera.deviceId})`);
});

// Get available microphones
const microphones = await parameters.getMediaDevicesList('audioinput');
microphones.forEach(mic => {
  console.log(`Microphone: ${mic.label} (${mic.deviceId})`);
});

// Building a device selector UI
@Component({
  selector: 'app-device-selector',
  template: `
    <div class="device-selector">
      <select (change)="onCameraChange($event)">
        <option value="">Select Camera</option>
        @for (camera of cameras; track camera.deviceId) {
          <option [value]="camera.deviceId">
            {{ camera.label }}
          </option>
        }
      </select>

      <select (change)="onMicrophoneChange($event)">
        <option value="">Select Microphone</option>
        @for (mic of microphones; track mic.deviceId) {
          <option [value]="mic.deviceId">
            {{ mic.label }}
          </option>
        }
      </select>
    </div>
  `
})
export class DeviceSelectorComponent implements OnInit {
  @Input() parameters: any;
  cameras: MediaDeviceInfo[] = [];
  microphones: MediaDeviceInfo[] = [];

  async ngOnInit() {
    await this.loadDevices();
  }

  async loadDevices() {
    this.cameras = await this.parameters.getMediaDevicesList('videoinput');
    this.microphones = await this.parameters.getMediaDevicesList('audioinput');
  }

  onCameraChange(event: any) {
    this.parameters.switchUserVideo({
      videoPreference: event.target.value,
      parameters: this.parameters
    });
  }

  onMicrophoneChange(event: any) {
    this.parameters.switchUserAudio({
      audioPreference: event.target.value,
      parameters: this.parameters
    });
  }
}

Get Participant Media Streams

// Get participant video stream by ID
const videoStream = await parameters.getParticipantMedia({ 
  id: 'producer-123', 
  kind: 'video' 
});

// Get participant audio stream by name
const audioStream = await parameters.getParticipantMedia({ 
  name: 'John Doe', 
  kind: 'audio' 
});

// Example: Custom participant stream monitor
@Component({
  selector: 'app-stream-monitor',
  template: `
    <div class="stream-monitor">
      <h3>Participant Streams</h3>
      @for (participant of participants; track participant.id) {
        <div class="participant-item">
          <span>{{ participant.name }}</span>
          <button (click)="viewStream(participant)">View Stream</button>
          <button (click)="monitorAudio(participant)">Monitor Audio</button>
        </div>
      }

      @if (selectedStream) {
        <div class="stream-viewer">
          <video #streamVideo autoplay playsinline></video>
        </div>
      }
    </div>
  `,
  styles: [`
    .stream-monitor { padding: 20px; }
    .participant-item { 
      margin: 10px 0; 
      display: flex; 
      gap: 10px; 
      align-items: center; 
    }
    .stream-viewer video { 
      width: 100%; 
      max-width: 640px; 
      border: 2px solid #1976d2; 
    }
  `]
})
export class StreamMonitorComponent {
  @Input() parameters: any;
  @ViewChild('streamVideo') videoElement!: ElementRef<HTMLVideoElement>;
  
  selectedStream: MediaStream | null = null;

  get participants() {
    return this.parameters?.participants || [];
  }

  async viewStream(participant: any) {
    const stream = await this.parameters.getParticipantMedia({
      id: participant.videoID,
      name: participant.name,
      kind: 'video'
    });

    if (stream && this.videoElement) {
      this.selectedStream = stream;
      this.videoElement.nativeElement.srcObject = stream;
    } else {
      console.log('No video stream found for participant');
    }
  }

  async monitorAudio(participant: any) {
    const stream = await this.parameters.getParticipantMedia({
      id: participant.audioID,
      name: participant.name,
      kind: 'audio'
    });

    if (stream) {
      // Create audio context for analysis
      const audioContext = new AudioContext();
      const analyser = audioContext.createAnalyser();
      const source = audioContext.createMediaStreamSource(stream);
      source.connect(analyser);
      
      console.log('Monitoring audio for:', participant.name);
      // Add your audio analysis logic here
    } else {
      console.log('No audio stream found for participant');
    }
  }
}

Participant Management Methods

// Get all participants
const participants = parameters.participants;
const participantCount = parameters.participantsCounter;

// Subscribe to participant changes
parameters.participants.subscribe((participants: any[]) => {
  console.log('Participants updated:', participants);
});

// Filter participants
const videoParticipants = participants.filter((p: any) => p.videoOn);
const audioOnlyParticipants = participants.filter((p: any) => !p.videoOn);
const mutedParticipants = participants.filter((p: any) => p.muted);

// Find specific participant
const participant = participants.find((p: any) => p.name === 'John Doe');

// Remove participant from room (host only)
parameters.disconnectUserInitiate({
  member: participantId,
  roomName: parameters.roomName,
  socket: parameters.socket
});

// Change participant role (host only)
parameters.updateParticipant({
  participantId: 'participant-id',
  islevel: '2', // '2' = host, '1' = co-host, '0' = participant
  parameters
});

// Request to unmute participant (sends request)
parameters.requestScreenShare({ parameters });

Chat & Messaging Methods

// Send a group message
parameters.sendMessage({
  message: 'Hello everyone!',
  type: 'group',
  parameters
});

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

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

// Subscribe to new messages (RxJS)
parameters.messages.subscribe((messages: any[]) => {
  console.log('Messages updated:', messages);
});

// Example: Custom chat component
@Component({
  selector: 'app-custom-chat',
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: `
    <div class="chat-container">
      <div class="messages">
        @for (msg of messages; track msg.timestamp) {
          <div class="message">
            <strong>{{ msg.sender }}:</strong> {{ msg.message }}
          </div>
        }
      </div>
      <div class="input-area">
        <input
          [(ngModel)]="message"
          (keyup.enter)="sendMessage()"
          placeholder="Type a message..."
        />
        <button (click)="sendMessage()">Send</button>
      </div>
    </div>
  `,
  styles: [`
    .chat-container { 
      display: flex; 
      flex-direction: column; 
      height: 400px; 
    }
    .messages { 
      flex: 1; 
      overflow-y: auto; 
      padding: 10px; 
    }
    .message { 
      margin: 5px 0; 
      padding: 8px; 
      background: #f5f5f5; 
      border-radius: 4px; 
    }
    .input-area { 
      display: flex; 
      gap: 10px; 
      padding: 10px; 
      border-top: 1px solid #ddd; 
    }
    input { 
      flex: 1; 
      padding: 8px; 
      border: 1px solid #ddd; 
      border-radius: 4px; 
    }
    button { 
      padding: 8px 16px; 
      background: #1976d2; 
      color: white; 
      border: none; 
      border-radius: 4px; 
      cursor: pointer; 
    }
  `]
})
export class CustomChatComponent implements OnInit {
  @Input() parameters: any;
  message = '';
  messages: any[] = [];

  ngOnInit() {
    // Subscribe to messages
    this.parameters?.messages?.subscribe((msgs: any[]) => {
      this.messages = msgs;
    });
  }

  sendMessage() {
    if (this.message.trim()) {
      this.parameters?.sendMessage({
        message: this.message,
        type: 'group',
        parameters: this.parameters
      });
      this.message = '';
    }
  }
}

Recording Methods

// Start recording
parameters.startRecording({ parameters });

// Stop recording
parameters.stopRecording({ parameters });

// Pause recording
parameters.pauseRecording({ parameters });

// Resume recording
parameters.resumeRecording({ parameters });

// Configure recording settings
parameters.updateRecording({
  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
});

// Check recording state
const isRecording = parameters.recordStarted;
const isPaused = parameters.recordPaused;
const recordingTime = parameters.recordElapsedTime;

// Subscribe to recording state changes
parameters.recordStarted.subscribe((isRecording: boolean) => {
  console.log('Recording:', isRecording ? 'ACTIVE' : 'STOPPED');
});

Polls & Surveys Methods

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

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

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

// Access poll data
const polls = parameters.polls;

// Subscribe to polls changes
parameters.polls.subscribe((polls: any[]) => {
  const activePoll = polls.find(p => p.status === 'active');
  console.log('Active poll:', activePoll);
});

// Example: Custom poll component
@Component({
  selector: 'app-custom-poll',
  standalone: true,
  imports: [CommonModule],
  template: `
    @if (activePoll) {
      <div class="poll-container">
        <h3>{{ activePoll.question }}</h3>
        <div class="poll-options">
          @for (option of activePoll.options; track $index) {
            <button 
              class="poll-option"
              (click)="vote($index)">
              {{ option }} 
              <span class="votes">({{ activePoll.votes?.[$index] || 0 }} votes)</span>
            </button>
          }
        </div>
        @if (isHost) {
          <button class="end-poll" (click)="endPoll()">
            End Poll
          </button>
        }
      </div>
    }
  `,
  styles: [`
    .poll-container {
      padding: 20px;
      background: white;
      border-radius: 8px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    .poll-options {
      display: flex;
      flex-direction: column;
      gap: 10px;
      margin: 20px 0;
    }
    .poll-option {
      padding: 12px;
      border: 2px solid #1976d2;
      background: white;
      border-radius: 4px;
      cursor: pointer;
      transition: all 0.3s;
      display: flex;
      justify-content: space-between;
    }
    .poll-option:hover {
      background: #1976d2;
      color: white;
    }
    .votes {
      font-size: 0.9em;
      opacity: 0.7;
    }
    .end-poll {
      padding: 10px 20px;
      background: #d32f2f;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
  `]
})
export class CustomPollComponent implements OnInit {
  @Input() parameters: any;
  activePoll: any = null;

  ngOnInit() {
    this.parameters?.polls?.subscribe((polls: any[]) => {
      this.activePoll = polls.find((p: any) => p.status === 'active');
    });
  }

  get isHost() {
    return this.parameters?.islevel === '2';
  }

  vote(optionIndex: number) {
    if (this.activePoll) {
      this.parameters?.handleVotePoll({
        pollId: this.activePoll.id,
        optionIndex,
        parameters: this.parameters
      });
    }
  }

  endPoll() {
    if (this.activePoll) {
      this.parameters?.handleEndPoll({
        pollId: this.activePoll.id,
        parameters: this.parameters
      });
    }
  }
}

Breakout Rooms Methods

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

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

// Start breakout rooms
parameters.startBreakoutRooms({ parameters });

// Stop breakout rooms
parameters.stopBreakoutRooms({ parameters });

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

// Subscribe to breakout room changes
parameters.breakoutRooms.subscribe((rooms: any[]) => {
  console.log('Breakout rooms:', rooms);
});

Whiteboard Methods

// Show/hide whiteboard
parameters.updateWhiteboardStarted.next(true);
parameters.updateWhiteboardEnded.next(false);

// Configure whiteboard
parameters.launchConfigureWhiteboard?.launchConfigureWhiteboard({ parameters });

// Access whiteboard state
const isWhiteboardActive = parameters.whiteboardStarted;

// Subscribe to whiteboard state
parameters.whiteboardStarted.subscribe((isActive: boolean) => {
  console.log('Whiteboard:', isActive ? 'ACTIVE' : 'INACTIVE');
});

// Access whiteboard users
const whiteboardUsers = parameters.whiteboardUsers;

Utility Methods

// Check permissions
const hasPermission = await parameters.checkPermission({
  permissionType: 'video', // or 'audio'
  parameters
});

// Format large numbers
const formatted = parameters.formatNumber(1250000); // Returns "1.25M"

// Sleep/delay
await parameters.sleep({ ms: 1000 });

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

// Trigger layout recalculation
parameters.onScreenChanges({