UNPKG

mediasfu-angular

Version:
1,743 lines (1,443 loc) β€’ 166 kB
<p align="center"> <img src="https://www.mediasfu.com/logo192.png" width="100" alt="MediaSFU Logo"> </p> <p align="center"> <a href="https://twitter.com/media_sfu"> <img src="https://img.shields.io/badge/Twitter-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white" alt="Twitter" /> </a> <a href="https://www.mediasfu.com/forums"> <img src="https://img.shields.io/badge/Community-Forum-blue?style=for-the-badge&logo=discourse&logoColor=white" alt="Community Forum" /> </a> <a href="https://github.com/MediaSFU"> <img src="https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=github&logoColor=white" alt="Github" /> </a> <a href="https://www.mediasfu.com/"> <img src="https://img.shields.io/badge/Website-4285F4?style=for-the-badge&logo=google-chrome&logoColor=white" alt="Website" /> </a> <a href="https://www.youtube.com/channel/UCELghZRPKMgjih5qrmXLtqw"> <img src="https://img.shields.io/badge/YouTube-FF0000?style=for-the-badge&logo=youtube&logoColor=white" alt="Youtube" /> </a> </p> <p align="center"> <a href="https://opensource.org/licenses/MIT"> <img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square" alt="License: MIT" /> </a> <a href="https://mediasfu.com"> <img src="https://img.shields.io/badge/Built%20with-MediaSFU-blue?style=flat-square" alt="Built with MediaSFU" /> </a> <a href="https://angular.io"> <img src="https://img.shields.io/badge/Angular-DD0031?style=flat-square&logo=angular&logoColor=white" alt="Angular" /> </a> <a href="https://www.typescriptlang.org"> <img src="https://img.shields.io/badge/TypeScript-007ACC?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript" /> </a> </p> --- ## 🚨 **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 β†’](https://mediasfu.com/telephony)** --- 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. <div style="text-align: center;"> <img src="https://mediasfu.com/images/header_1.jpg" alt="Preview Page" title="Preview Page" style="max-height: 600px;"> </div> --- # 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!](https://github.com/MediaSFU/MediaSFUOpen)** ### βœ… Angular SDK Setup Guide Coming soon! Watch this space for our comprehensive video tutorial on setting up the Angular SDK. --- ## Table of Contents - [Features](#features) - [Getting Started](#getting-started) - [πŸ“˜ Angular SDK Guide](#angular-sdk-guide) - [Quick Start](#quick-start-5-minutes) - [Understanding the Architecture](#understanding-mediasfu-architecture) - [Core Concepts & Components](#core-concepts--components) - [Working with Methods](#working-with-methods) - [Media Streams & Participants](#media-streams--participants) - [Customization & Styling](#customization--styling) - [API Reference](#api-reference) - [Troubleshooting](#troubleshooting) - [Contributing](#contributing) # Features <a name="features"></a> 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: ```typescript // 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 β†’](#media-device-and-stream-utility-methods) --- ### **`getParticipantMedia`** - Stream Access Retrieve specific participant's video or audio streams by ID or name: ```typescript // 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 β†’](#media-device-and-stream-utility-methods) --- 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 <a name="getting-started"></a> 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](https://www.mediasfu.com/angular/). 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](https://www.mediasfu.com/). <div style="background-color:#f0f0f0; padding: 10px; border-radius: 5px;"> <h4 style="color:#d9534f;">Important:</h4> <p style="font-size: 1.2em; color: black;">You must obtain an API key from <a href="https://www.mediasfu.com/">mediasfu.com</a> to use this package with MediaSFU Cloud. You do not need the API Key if self-hosting.</p> </div> ## **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](https://github.com/MediaSFU/MediaSFUOpen). This setup allows full flexibility and customization while bypassing the need for cloud-dependent credentials. # πŸ“˜ Angular SDK Guide <a name="angular-sdk-guide"></a> 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) <a name="quick-start-5-minutes"></a> Get your first MediaSFU app running in just a few minutes. ### Step 1: Install the Package ```bash npm install mediasfu-angular ``` ### Step 2: Import and Use ```typescript // 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:** ```typescript 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 ```bash 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 <a name="understanding-mediasfu-architecture"></a> 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 | ```typescript // 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. ```typescript 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. ```typescript 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. ```typescript 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: ```typescript // 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:** ```typescript // 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 <a name="core-concepts--components"></a> 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 ```typescript 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 ```typescript 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 ```typescript 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** ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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: ```typescript 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** ```typescript @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 ```typescript 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 ```typescript 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) ```typescript import { MiniCard } from 'mediasfu-angular'; @Component({ template: ` <app-mini-card [participant]="participantObject" [showControls]="false" [parameters]="parameters"> </app-mini-card> ` }) ``` **Example: Custom Video Card** ```typescript @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 <a name="working-with-methods"></a> 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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({ changed: true, parameters }); // Get room information const roomInfo = { name: parameters.roomName, host: parameters.host, capacity: parameters.capacity, eventType: parameters.eventType, participants: parameters.participants, isRecording: parameters.recordStarted }; // Subscribe to room state changes parameters.roomName.subscribe((name: string) => { console.log('Room name:', name); }); parameters.participantsCounter.subscribe((count: number) => { console.log('Participant count:', count); }); ``` --- ## Programmatically Fetching Tokens If you prefer to fetch the required tokens programmatically with