mediasfu-angular 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/README.md CHANGED
@@ -4,19 +4,34 @@
4
4
 
5
5
  <p align="center">
6
6
  <a href="https://twitter.com/media_sfu">
7
- <img src="https://img.icons8.com/color/48/000000/twitter--v1.png" alt="Twitter" style="margin-right: 10px;">
7
+ <img src="https://img.shields.io/badge/Twitter-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white" alt="Twitter" />
8
8
  </a>
9
9
  <a href="https://www.mediasfu.com/forums">
10
- <img src="https://img.icons8.com/color/48/000000/communication--v1.png" alt="Community Forum" style="margin-right: 10px;">
10
+ <img src="https://img.shields.io/badge/Community-Forum-blue?style=for-the-badge&logo=discourse&logoColor=white" alt="Community Forum" />
11
11
  </a>
12
12
  <a href="https://github.com/MediaSFU">
13
- <img src="https://img.icons8.com/fluent/48/000000/github.png" alt="Github" style="margin-right: 10px;">
13
+ <img src="https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=github&logoColor=white" alt="Github" />
14
14
  </a>
15
15
  <a href="https://www.mediasfu.com/">
16
- <img src="https://img.icons8.com/color/48/000000/domain--v1.png" alt="Website" style="margin-right: 10px;">
16
+ <img src="https://img.shields.io/badge/Website-4285F4?style=for-the-badge&logo=google-chrome&logoColor=white" alt="Website" />
17
17
  </a>
18
18
  <a href="https://www.youtube.com/channel/UCELghZRPKMgjih5qrmXLtqw">
19
- <img src="https://img.icons8.com/color/48/000000/youtube--v1.png" alt="Youtube" style="margin-right: 10px;">
19
+ <img src="https://img.shields.io/badge/YouTube-FF0000?style=for-the-badge&logo=youtube&logoColor=white" alt="Youtube" />
20
+ </a>
21
+ </p>
22
+
23
+ <p align="center">
24
+ <a href="https://opensource.org/licenses/MIT">
25
+ <img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square" alt="License: MIT" />
26
+ </a>
27
+ <a href="https://mediasfu.com">
28
+ <img src="https://img.shields.io/badge/Built%20with-MediaSFU-blue?style=flat-square" alt="Built with MediaSFU" />
29
+ </a>
30
+ <a href="https://angular.io">
31
+ <img src="https://img.shields.io/badge/Angular-DD0031?style=flat-square&logo=angular&logoColor=white" alt="Angular" />
32
+ </a>
33
+ <a href="https://www.typescriptlang.org">
34
+ <img src="https://img.shields.io/badge/TypeScript-007ACC?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript" />
20
35
  </a>
21
36
  </p>
22
37
 
@@ -59,16 +74,22 @@ MediaSFU offers a cutting-edge streaming experience that empowers users to custo
59
74
 
60
75
  **[Get started now on GitHub!](https://github.com/MediaSFU/MediaSFUOpen)**
61
76
 
77
+ ### ✅ Angular SDK Setup Guide
78
+ Coming soon! Watch this space for our comprehensive video tutorial on setting up the Angular SDK.
79
+
62
80
  ---
63
81
 
64
82
  ## Table of Contents
65
83
 
66
84
  - [Features](#features)
67
85
  - [Getting Started](#getting-started)
68
- - [Basic Usage Guide](#basic-usage-guide)
69
- - [Intermediate Usage Guide](#intermediate-usage-guide)
70
- - [Advanced Usage Guide](#advanced-usage-guide)
71
- - [Custom Component Injection Service](#custom-component-injection-service)
86
+ - [📘 Angular SDK Guide](#angular-sdk-guide)
87
+ - [Quick Start](#quick-start-5-minutes)
88
+ - [Understanding the Architecture](#understanding-mediasfu-architecture)
89
+ - [Core Concepts & Components](#core-concepts--components)
90
+ - [Working with Methods](#working-with-methods)
91
+ - [Media Streams & Participants](#media-streams--participants)
92
+ - [Customization & Styling](#customization--styling)
72
93
  - [API Reference](#api-reference)
73
94
  - [Troubleshooting](#troubleshooting)
74
95
  - [Contributing](#contributing)
@@ -88,6 +109,74 @@ MediaSFU's Angular SDK comes with a host of powerful features out of the box:
88
109
  9. **Cloud Recording (track-based)**: Customize recordings with track-based options, including watermarks, name tags, background colors, and more.
89
110
  10. **Managed Events**: Manage events with features to handle abandoned and inactive participants, as well as enforce time and capacity limits.
90
111
 
112
+ ## 🆕 **New Advanced Media Access**
113
+
114
+ The Angular SDK now includes powerful utility methods for fine-grained control over media devices and participant streams:
115
+
116
+ ### **`getMediaDevicesList`** - Device Enumeration
117
+
118
+ Enumerate available cameras and microphones with automatic permission handling:
119
+
120
+ ```typescript
121
+ // Get all available cameras
122
+ const cameras = await sourceParameters.getMediaDevicesList('videoinput');
123
+ cameras.forEach(camera => {
124
+ console.log(`Camera: ${camera.label} (${camera.deviceId})`);
125
+ });
126
+
127
+ // Get all available microphones
128
+ const microphones = await sourceParameters.getMediaDevicesList('audioinput');
129
+ microphones.forEach(mic => {
130
+ console.log(`Microphone: ${mic.label} (${mic.deviceId})`);
131
+ });
132
+ ```
133
+
134
+ **Use Cases:**
135
+ - Build custom device selection interfaces
136
+ - Detect available media hardware
137
+ - Switch between multiple cameras/microphones
138
+ - Pre-flight device checks before joining
139
+
140
+ [See full documentation and examples →](#media-device-and-stream-utility-methods)
141
+
142
+ ---
143
+
144
+ ### **`getParticipantMedia`** - Stream Access
145
+
146
+ Retrieve specific participant's video or audio streams by ID or name:
147
+
148
+ ```typescript
149
+ // Get participant video stream by producer ID
150
+ const videoStream = await sourceParameters.getParticipantMedia({
151
+ id: 'producer-123',
152
+ kind: 'video'
153
+ });
154
+
155
+ // Get participant audio stream by name
156
+ const audioStream = await sourceParameters.getParticipantMedia({
157
+ name: 'John Doe',
158
+ kind: 'audio'
159
+ });
160
+
161
+ // Use the stream (e.g., attach to video element)
162
+ if (videoStream) {
163
+ videoElement.srcObject = videoStream;
164
+ }
165
+ ```
166
+
167
+ **Use Cases:**
168
+ - Monitor specific participant streams
169
+ - Create custom video layouts with individual control
170
+ - Build stream recording features
171
+ - Implement advanced audio/video processing
172
+ - Create picture-in-picture views for specific users
173
+
174
+ [See full documentation and examples →](#media-device-and-stream-utility-methods)
175
+
176
+ ---
177
+
178
+ 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.
179
+
91
180
  # Getting Started <a name="getting-started"></a>
92
181
 
93
182
  This section will guide users through the initial setup and installation of the npm module.
@@ -180,153 +269,1475 @@ If you plan to self-host MediaSFU or use it without MediaSFU Cloud services, you
180
269
  This setup allows full flexibility and customization while bypassing the need for cloud-dependent credentials.
181
270
 
182
271
 
183
- # Basic Usage Guide <a name="basic-usage-guide"></a>
272
+ # 📘 Angular SDK Guide <a name="angular-sdk-guide"></a>
273
+
274
+ 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.
275
+
276
+ ---
277
+
278
+ ## Quick Start (5 Minutes) <a name="quick-start-5-minutes"></a>
279
+
280
+ Get your first MediaSFU app running in just a few minutes.
281
+
282
+ ### Step 1: Install the Package
283
+
284
+ ```bash
285
+ npm install mediasfu-angular
286
+ ```
287
+
288
+ ### Step 2: Import and Use
289
+
290
+ ```typescript
291
+ // app.component.ts
292
+ import { Component } from '@angular/core';
293
+ import { MediasfuGeneric } from 'mediasfu-angular';
294
+
295
+ @Component({
296
+ selector: 'app-root',
297
+ standalone: true,
298
+ imports: [MediasfuGeneric],
299
+ template: `<app-mediasfu-generic></app-mediasfu-generic>`,
300
+ })
301
+ export class AppComponent { }
302
+ ```
303
+
304
+ **Alternative with Credentials:**
305
+
306
+ ```typescript
307
+ import { Component } from '@angular/core';
308
+ import { MediasfuGeneric, PreJoinPage } from 'mediasfu-angular';
309
+
310
+ @Component({
311
+ selector: 'app-root',
312
+ standalone: true,
313
+ imports: [MediasfuGeneric],
314
+ template: `
315
+ <app-mediasfu-generic
316
+ [PrejoinPage]="PreJoinPage"
317
+ [credentials]="credentials">
318
+ </app-mediasfu-generic>
319
+ `,
320
+ })
321
+ export class AppComponent {
322
+ PreJoinPage = PreJoinPage;
323
+ credentials = {
324
+ apiUserName: 'your_username',
325
+ apiKey: 'your_api_key',
326
+ };
327
+ }
328
+ ```
329
+
330
+ ### Step 3: Run Your App
331
+
332
+ ```bash
333
+ ng serve
334
+ ```
335
+
336
+ **That's it!** You now have a fully functional video conferencing app with:
337
+ - ✅ Video and audio streaming
338
+ - ✅ Screen sharing
339
+ - ✅ Chat messaging
340
+ - ✅ Participant management
341
+ - ✅ Recording capabilities
342
+ - ✅ Breakout rooms
343
+ - ✅ Polls and whiteboards
344
+
345
+ ---
346
+
347
+ ## Understanding MediaSFU Architecture <a name="understanding-mediasfu-architecture"></a>
348
+
349
+ Before diving deeper, let's understand how MediaSFU is structured.
350
+
351
+ ### The Three-Layer Architecture
352
+
353
+ ```
354
+ ┌─────────────────────────────────────────────┐
355
+ │ Your Angular Application │
356
+ │ (components, services, business logic) │
357
+ └─────────────────────────────────────────────┘
358
+
359
+ ┌─────────────────────────────────────────────┐
360
+ │ MediaSFU Components Layer │
361
+ │ (MediasfuGeneric, MediasfuBroadcast, etc.) │
362
+ │ - Pre-built UI components │
363
+ │ - Event handling │
364
+ │ - State management (RxJS) │
365
+ └─────────────────────────────────────────────┘
366
+
367
+ ┌─────────────────────────────────────────────┐
368
+ │ MediaSFU Core Methods Layer │
369
+ │ (Stream control, room management, │
370
+ │ WebRTC handling, socket communication) │
371
+ └─────────────────────────────────────────────┘
372
+
373
+ ┌─────────────────────────────────────────────┐
374
+ │ MediaSFU Backend Services │
375
+ │ (MediaSFU Cloud or Community Edition) │
376
+ └─────────────────────────────────────────────┘
377
+ ```
378
+
379
+ ### Key Concepts
380
+
381
+ #### 1. **Event Room Types**
382
+
383
+ MediaSFU provides 5 specialized room types, each optimized for specific use cases:
384
+
385
+ | Room Type | Best For | Key Features |
386
+ |-----------|----------|--------------|
387
+ | **MediasfuGeneric** | General purpose meetings | Flexible layout, all features enabled |
388
+ | **MediasfuBroadcast** | Live streaming events | Optimized for one-to-many communication |
389
+ | **MediasfuWebinar** | Educational sessions | Presenter focus, Q&A features |
390
+ | **MediasfuConference** | Business meetings | Equal participant layout, collaboration tools |
391
+ | **MediasfuChat** | Interactive discussions | Chat-first interface, quick connections |
392
+
393
+ ```typescript
394
+ // Choose the right room type for your use case
395
+ import {
396
+ MediasfuWebinar,
397
+ MediasfuBroadcast,
398
+ MediasfuConference
399
+ } from 'mediasfu-angular';
400
+
401
+ @Component({
402
+ // For a webinar
403
+ template: `<app-mediasfu-webinar [credentials]="credentials"></app-mediasfu-webinar>`,
404
+ // For a broadcast
405
+ // template: `<app-mediasfu-broadcast [credentials]="credentials"></app-mediasfu-broadcast>`,
406
+ // For a conference
407
+ // template: `<app-mediasfu-conference [credentials]="credentials"></app-mediasfu-conference>`,
408
+ })
409
+ ```
410
+
411
+ #### 2. **The Three Usage Modes**
412
+
413
+ MediaSFU offers three progressive levels of customization:
414
+
415
+ ##### Mode 1: Default UI (Simplest)
416
+ Use MediaSFU's complete pre-built interface - perfect for rapid development.
417
+
418
+ ```typescript
419
+ import { Component } from '@angular/core';
420
+ import { MediasfuGeneric } from 'mediasfu-angular';
421
+
422
+ @Component({
423
+ selector: 'app-root',
424
+ standalone: true,
425
+ imports: [MediasfuGeneric],
426
+ template: `<app-mediasfu-generic [credentials]="credentials"></app-mediasfu-generic>`,
427
+ })
428
+ export class AppComponent {
429
+ credentials = { apiUserName: 'username', apiKey: 'key' };
430
+ }
431
+ ```
432
+
433
+ **When to use:**
434
+ - ✅ Prototyping or MVP development
435
+ - ✅ Need a production-ready UI quickly
436
+ - ✅ Standard video conferencing features are sufficient
437
+
438
+ ##### Mode 2: Custom UI with MediaSFU Backend (Most Flexible)
439
+ Build your own UI while using MediaSFU's powerful backend infrastructure.
440
+
441
+ ```typescript
442
+ import { Component, OnInit } from '@angular/core';
443
+ import { MediasfuGeneric } from 'mediasfu-angular';
444
+
445
+ @Component({
446
+ selector: 'app-root',
447
+ standalone: true,
448
+ imports: [MediasfuGeneric, CommonModule],
449
+ template: `
450
+ <app-mediasfu-generic
451
+ [returnUI]="false"
452
+ [sourceParameters]="sourceParameters"
453
+ [updateSourceParameters]="updateSourceParameters.bind(this)"
454
+ [credentials]="credentials"
455
+ [noUIPreJoinOptions]="preJoinOptions">
456
+ </app-mediasfu-generic>
457
+
458
+ <!-- Your custom UI -->
459
+ @if (sourceParameters) {
460
+ <div class="custom-controls">
461
+ <button (click)="toggleVideo()">
462
+ {{ sourceParameters.videoAlreadyOn ? 'Stop Video' : 'Start Video' }}
463
+ </button>
464
+ <button (click)="toggleAudio()">
465
+ {{ sourceParameters.audioAlreadyOn ? 'Mute' : 'Unmute' }}
466
+ </button>
467
+ <button (click)="toggleScreenShare()">
468
+ {{ sourceParameters.screenAlreadyOn ? 'Stop Sharing' : 'Share Screen' }}
469
+ </button>
470
+ </div>
471
+ }
472
+ `,
473
+ })
474
+ export class AppComponent implements OnInit {
475
+ sourceParameters: any = null;
476
+ credentials = { apiUserName: 'username', apiKey: 'key' };
477
+ preJoinOptions = {
478
+ action: 'create',
479
+ userName: 'Your Name',
480
+ capacity: 50,
481
+ duration: 30,
482
+ eventType: 'conference'
483
+ };
484
+
485
+ updateSourceParameters(params: any) {
486
+ this.sourceParameters = params;
487
+ }
488
+
489
+ toggleVideo() {
490
+ this.sourceParameters?.clickVideo({ parameters: this.sourceParameters });
491
+ }
492
+
493
+ toggleAudio() {
494
+ this.sourceParameters?.clickAudio({ parameters: this.sourceParameters });
495
+ }
496
+
497
+ toggleScreenShare() {
498
+ this.sourceParameters?.clickScreenShare({ parameters: this.sourceParameters });
499
+ }
500
+ }
501
+ ```
502
+
503
+ **When to use:**
504
+ - ✅ Need complete control over UI/UX
505
+ - ✅ Building a custom branded experience
506
+ - ✅ Integrating into existing app design
507
+
508
+ ##### Mode 3: Component Replacement (Balanced)
509
+ Replace specific MediaSFU components while keeping the rest of the infrastructure.
510
+
511
+ ```typescript
512
+ import { Component } from '@angular/core';
513
+ import {
514
+ MediasfuGeneric,
515
+ FlexibleVideo,
516
+ FlexibleGrid
517
+ } from 'mediasfu-angular';
518
+
519
+ @Component({
520
+ selector: 'app-custom-main',
521
+ standalone: true,
522
+ imports: [FlexibleVideo, FlexibleGrid, CommonModule],
523
+ template: `
524
+ <div class="custom-layout">
525
+ <!-- Custom header -->
526
+ <div class="custom-header">
527
+ <h1>{{ parameters.roomName }}</h1>
528
+ <span>{{ parameters.participants.length }} participants</span>
529
+ </div>
530
+
531
+ <!-- Use MediaSFU's components in your layout -->
532
+ <app-flexible-video
533
+ [customWidth]="windowWidth"
534
+ [customHeight]="600"
535
+ [parameters]="parameters">
536
+ </app-flexible-video>
537
+
538
+ <app-flexible-grid
539
+ [customWidth]="windowWidth"
540
+ [customHeight]="400"
541
+ [parameters]="parameters">
542
+ </app-flexible-grid>
543
+
544
+ <!-- Custom footer -->
545
+ <div class="custom-footer">
546
+ <button (click)="toggleVideo()">
547
+ {{ parameters.videoAlreadyOn ? 'Stop Video' : 'Start Video' }}
548
+ </button>
549
+ </div>
550
+ </div>
551
+ `,
552
+ styles: [`
553
+ .custom-layout {
554
+ display: flex;
555
+ flex-direction: column;
556
+ height: 100vh;
557
+ }
558
+ .custom-header {
559
+ padding: 20px;
560
+ background: #1976d2;
561
+ color: white;
562
+ }
563
+ `]
564
+ })
565
+ export class CustomMainComponent {
566
+ parameters: any;
567
+ windowWidth = window.innerWidth;
568
+
569
+ toggleVideo() {
570
+ this.parameters?.clickVideo({ parameters: this.parameters });
571
+ }
572
+ }
573
+
574
+ @Component({
575
+ selector: 'app-root',
576
+ standalone: true,
577
+ imports: [MediasfuGeneric],
578
+ template: `
579
+ <app-mediasfu-generic
580
+ [credentials]="credentials"
581
+ [PrejoinPage]="PreJoinPage"
582
+ [customComponent]="CustomMainComponent">
583
+ </app-mediasfu-generic>
584
+ `,
585
+ })
586
+ export class AppComponent {
587
+ PreJoinPage = PreJoinPage;
588
+ CustomMainComponent = CustomMainComponent;
589
+ credentials = { apiUserName: 'username', apiKey: 'key' };
590
+ }
591
+ ```
592
+
593
+ **When to use:**
594
+ - ✅ Need custom main interface but want to keep MediaSFU's components
595
+ - ✅ Partial customization with minimal effort
596
+ - ✅ Want to maintain MediaSFU's functionality while customizing layout
597
+
598
+ #### 3. **Parameters: Your Control Center**
599
+
600
+ 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:
601
+
602
+ ```typescript
603
+ // Available in sourceParameters or parameters object
604
+ {
605
+ // Media Controls (Methods)
606
+ clickVideo: (options) => {},
607
+ clickAudio: (options) => {},
608
+ clickScreenShare: (options) => {},
609
+
610
+ // Room State (BehaviorSubject values)
611
+ roomName: 'meeting-123',
612
+ participants: [...],
613
+ allVideoStreams: [...],
614
+ allAudioStreams: [...],
615
+
616
+ // UI State (BehaviorSubject values)
617
+ videoAlreadyOn: false,
618
+ audioAlreadyOn: false,
619
+ screenAlreadyOn: false,
620
+
621
+ // Update Functions (BehaviorSubject next())
622
+ updateVideoAlreadyOn: (value) => {},
623
+ updateAudioAlreadyOn: (value) => {},
624
+
625
+ // And 200+ more properties and methods...
626
+ }
627
+ ```
628
+
629
+ **Access patterns:**
630
+
631
+ ```typescript
632
+ // In Mode 1 (Default UI): Parameters are managed internally
633
+ // You don't need to access them directly
634
+
635
+ // In Mode 2 (Custom UI): Access via sourceParameters
636
+ sourceParameters?.clickVideo({ parameters: sourceParameters });
637
+
638
+ // In Mode 3 (Component Replacement): Passed to your custom component
639
+ @Component({
640
+ template: `<button (click)="toggleVideo()">Toggle</button>`
641
+ })
642
+ export class CustomComponent {
643
+ @Input() parameters: any;
644
+
645
+ toggleVideo() {
646
+ this.parameters.clickVideo({ parameters: this.parameters });
647
+ }
648
+ }
649
+
650
+ // Subscribing to reactive state changes
651
+ sourceParameters.participants.subscribe((participants) => {
652
+ console.log('Participants updated:', participants);
653
+ });
654
+ ```
655
+
656
+ ---
657
+
658
+ ## Core Concepts & Components <a name="core-concepts--components"></a>
659
+
660
+ Now that you understand the architecture, let's explore the building blocks.
661
+
662
+ ### 1. Display Components: Building Your Video Layout
663
+
664
+ MediaSFU provides powerful components for organizing and displaying media streams.
665
+
666
+ #### Primary Layout Components
667
+
668
+ **FlexibleVideo** - Main video display area
669
+
670
+ ```typescript
671
+ import { FlexibleVideo } from 'mediasfu-angular';
672
+
673
+ @Component({
674
+ template: `
675
+ <app-flexible-video
676
+ [customWidth]="windowWidth"
677
+ [customHeight]="600"
678
+ [parameters]="parameters">
679
+ </app-flexible-video>
680
+ `
681
+ })
682
+ ```
683
+ - Automatically handles main presenter or screen share
684
+ - Smooth transitions between different video sources
685
+ - Responsive sizing
686
+
687
+ **FlexibleGrid** - Participant grid layout
688
+
689
+ ```typescript
690
+ import { FlexibleGrid } from 'mediasfu-angular';
691
+
692
+ @Component({
693
+ template: `
694
+ <app-flexible-grid
695
+ [customWidth]="windowWidth"
696
+ [customHeight]="800"
697
+ [parameters]="parameters">
698
+ </app-flexible-grid>
699
+ `
700
+ })
701
+ ```
702
+ - Intelligent grid sizing (2x2, 3x3, 4x4, etc.)
703
+ - Pagination for large participant lists
704
+ - Automatic reflow on window resize
705
+
706
+ **AudioGrid** - Audio-only participants
707
+
708
+ ```typescript
709
+ import { AudioGrid } from 'mediasfu-angular';
710
+
711
+ @Component({
712
+ template: `<app-audio-grid [parameters]="parameters"></app-audio-grid>`
713
+ })
714
+ ```
715
+ - Displays participants without video
716
+ - Audio level indicators
717
+ - Compact layout for efficiency
718
+
719
+ #### Container Components
720
+
721
+ | Component | Purpose | Use Case |
722
+ |-----------|---------|----------|
723
+ | **MainContainerComponent** | Primary content wrapper | Wraps all main content areas |
724
+ | **MainAspectComponent** | Aspect ratio container | Maintains proper video proportions |
725
+ | **MainScreenComponent** | Screen layout manager | Organizes screen regions |
726
+ | **SubAspectComponent** | Secondary content container | For picture-in-picture, sidebars |
727
+
728
+ **Example: Building a custom layout**
729
+
730
+ ```typescript
731
+ import { Component } from '@angular/core';
732
+ import {
733
+ MainContainerComponent,
734
+ FlexibleVideo,
735
+ FlexibleGrid,
736
+ AudioGrid
737
+ } from 'mediasfu-angular';
738
+
739
+ @Component({
740
+ selector: 'app-custom-layout',
741
+ standalone: true,
742
+ imports: [
743
+ MainContainerComponent,
744
+ FlexibleVideo,
745
+ FlexibleGrid,
746
+ AudioGrid,
747
+ CommonModule
748
+ ],
749
+ template: `
750
+ <app-main-container-component>
751
+ <div class="layout-container">
752
+ <!-- Main video area -->
753
+ <div class="main-video">
754
+ <app-flexible-video
755
+ [customWidth]="windowWidth"
756
+ [customHeight]="windowHeight * 0.6"
757
+ [parameters]="parameters">
758
+ </app-flexible-video>
759
+ </div>
760
+
761
+ <!-- Participant grid -->
762
+ <div class="participant-grid">
763
+ <app-flexible-grid
764
+ [customWidth]="windowWidth"
765
+ [customHeight]="windowHeight * 0.3"
766
+ [parameters]="parameters">
767
+ </app-flexible-grid>
768
+ </div>
769
+
770
+ <!-- Audio-only participants -->
771
+ <div class="audio-participants">
772
+ <app-audio-grid [parameters]="parameters"></app-audio-grid>
773
+ </div>
774
+ </div>
775
+ </app-main-container-component>
776
+ `,
777
+ styles: [`
778
+ .layout-container {
779
+ display: flex;
780
+ flex-direction: column;
781
+ height: 100vh;
782
+ }
783
+ .main-video { flex: 3; }
784
+ .participant-grid { flex: 2; }
785
+ .audio-participants { height: 80px; }
786
+ `]
787
+ })
788
+ export class CustomLayoutComponent {
789
+ @Input() parameters: any;
790
+ windowWidth = window.innerWidth;
791
+ windowHeight = window.innerHeight;
792
+ }
793
+ ```
794
+
795
+ ### 2. Control Components: User Interactions
796
+
797
+ **ControlButtonsComponent** - Standard control bar
798
+
799
+ ```typescript
800
+ import { ControlButtonsComponent } from 'mediasfu-angular';
801
+
802
+ @Component({
803
+ template: `
804
+ <app-control-buttons-component
805
+ [parameters]="parameters"
806
+ [position]="'bottom'">
807
+ </app-control-buttons-component>
808
+ `
809
+ })
810
+ ```
811
+ Includes: mute, video, screenshare, participants, chat, settings, etc.
812
+
813
+ **ControlButtonsAltComponent** - Alternative layout
814
+
815
+ ```typescript
816
+ import { ControlButtonsAltComponent } from 'mediasfu-angular';
817
+
818
+ @Component({
819
+ template: `
820
+ <app-control-buttons-alt-component
821
+ [parameters]="parameters"
822
+ [position]="'top'">
823
+ </app-control-buttons-alt-component>
824
+ `
825
+ })
826
+ ```
827
+ Different button arrangement optimized for specific layouts.
828
+
829
+ **ControlButtonsComponentTouch** - Touch-optimized controls
830
+
831
+ ```typescript
832
+ import { ControlButtonsComponentTouch } from 'mediasfu-angular';
833
+
834
+ @Component({
835
+ template: `
836
+ <app-control-buttons-component-touch
837
+ [parameters]="parameters">
838
+ </app-control-buttons-component-touch>
839
+ `
840
+ })
841
+ ```
842
+ Floating action buttons optimized for mobile/tablet interfaces.
843
+
844
+ ### 3. Modal Components: Feature Interfaces
845
+
846
+ MediaSFU includes modals for various features:
847
+
848
+ ```typescript
849
+ import {
850
+ ParticipantsModal,
851
+ MessagesModal,
852
+ SettingsModal,
853
+ DisplaySettingsModal,
854
+ RecordingModal,
855
+ PollModal,
856
+ BreakoutRoomsModal
857
+ } from 'mediasfu-angular';
858
+
859
+ // These are automatically rendered when enabled
860
+ // Control their visibility via parameters
861
+ parameters.updateIsParticipantsModalVisible.next(true);
862
+ parameters.updateIsMessagesModalVisible.next(true);
863
+ parameters.updateIsSettingsModalVisible.next(true);
864
+ ```
865
+
866
+ Available modals:
867
+ - **ParticipantsModal** - Participant list management
868
+ - **MessagesModal** - Chat interface
869
+ - **SettingsModal** - Event and room settings
870
+ - **DisplaySettingsModal** - Layout and display options
871
+ - **RecordingModal** - Recording controls and settings
872
+ - **PollModal** - Create and manage polls
873
+ - **BreakoutRoomsModal** - Breakout room management
874
+ - **MediaSettingsModal** - Camera/microphone selection
875
+ - **BackgroundModal** - Virtual background settings
876
+ - **ConfigureWhiteboardModal** - Whiteboard configuration
877
+
878
+ **Example: Programmatically showing modals**
879
+
880
+ ```typescript
881
+ @Component({
882
+ selector: 'app-custom-toolbar',
883
+ template: `
884
+ <div class="custom-toolbar">
885
+ <button (click)="showParticipants()">
886
+ Show Participants ({{ participantCount }})
887
+ </button>
888
+
889
+ <button (click)="openChat()">
890
+ Open Chat
891
+ </button>
892
+
893
+ <button (click)="createPoll()">
894
+ Create Poll
895
+ </button>
896
+ </div>
897
+ `
898
+ })
899
+ export class CustomToolbarComponent {
900
+ @Input() parameters: any;
901
+
902
+ get participantCount() {
903
+ return this.parameters?.participants?.length || 0;
904
+ }
905
+
906
+ showParticipants() {
907
+ this.parameters?.updateIsParticipantsModalVisible.next(true);
908
+ }
909
+
910
+ openChat() {
911
+ this.parameters?.updateIsMessagesModalVisible.next(true);
912
+ }
913
+
914
+ createPoll() {
915
+ this.parameters?.launchPoll?.launchPoll({ parameters: this.parameters });
916
+ }
917
+ }
918
+ ```
919
+
920
+ ### 4. Video Cards: Individual Participant Display
921
+
922
+ **VideoCard** - Individual participant video element
923
+
924
+ ```typescript
925
+ import { VideoCard } from 'mediasfu-angular';
926
+
927
+ @Component({
928
+ template: `
929
+ <app-video-card
930
+ [videoStream]="participantStream"
931
+ [remoteProducerId]="'producer-id'"
932
+ [eventType]="'conference'"
933
+ [forceFullDisplay]="false"
934
+ [participant]="participantObject"
935
+ [backgroundColor]="'#000000'"
936
+ [showControls]="true"
937
+ [showInfo]="true"
938
+ [name]="'Participant Name'"
939
+ [parameters]="parameters">
940
+ </app-video-card>
941
+ `
942
+ })
943
+ ```
944
+
945
+ **AudioCard** - Individual audio-only participant
946
+
947
+ ```typescript
948
+ import { AudioCard } from 'mediasfu-angular';
949
+
950
+ @Component({
951
+ template: `
952
+ <app-audio-card
953
+ [name]="'Participant Name'"
954
+ [barColor]="'#4CAF50'"
955
+ [textColor]="'#FFFFFF'"
956
+ [customStyle]="{ borderRadius: '10px' }"
957
+ [controlsPosition]="'topLeft'"
958
+ [infoPosition]="'topRight'"
959
+ [participant]="participantObject"
960
+ [parameters]="parameters">
961
+ </app-audio-card>
962
+ `
963
+ })
964
+ ```
965
+
966
+ **MiniCard** - Compact participant display (for grids)
967
+
968
+ ```typescript
969
+ import { MiniCard } from 'mediasfu-angular';
970
+
971
+ @Component({
972
+ template: `
973
+ <app-mini-card
974
+ [participant]="participantObject"
975
+ [showControls]="false"
976
+ [parameters]="parameters">
977
+ </app-mini-card>
978
+ `
979
+ })
980
+ ```
981
+
982
+ **Example: Custom Video Card**
983
+
984
+ ```typescript
985
+ @Component({
986
+ selector: 'app-my-custom-video-card',
987
+ standalone: true,
988
+ template: `
989
+ <div class="custom-video-card">
990
+ <video
991
+ #videoElement
992
+ [srcObject]="stream"
993
+ autoplay
994
+ [muted]="true"
995
+ playsinline>
996
+ </video>
997
+
998
+ <div class="participant-info">
999
+ {{ participant.name }}
1000
+ @if (participant.muted) {
1001
+ <span>🔇</span>
1002
+ }
1003
+ </div>
1004
+ </div>
1005
+ `,
1006
+ styles: [`
1007
+ .custom-video-card {
1008
+ border: 3px solid #00ff88;
1009
+ border-radius: 15px;
1010
+ overflow: hidden;
1011
+ position: relative;
1012
+ }
1013
+ video {
1014
+ width: 100%;
1015
+ height: 100%;
1016
+ object-fit: cover;
1017
+ }
1018
+ .participant-info {
1019
+ position: absolute;
1020
+ bottom: 0;
1021
+ left: 0;
1022
+ right: 0;
1023
+ background: rgba(0, 255, 136, 0.8);
1024
+ color: black;
1025
+ padding: 8px;
1026
+ font-weight: bold;
1027
+ }
1028
+ `]
1029
+ })
1030
+ export class MyCustomVideoCardComponent {
1031
+ @Input() stream!: MediaStream;
1032
+ @Input() participant!: any;
1033
+ @Input() parameters!: any;
1034
+ }
1035
+
1036
+ // Use it in MediasfuGeneric
1037
+ @Component({
1038
+ template: `
1039
+ <app-mediasfu-generic
1040
+ [credentials]="credentials"
1041
+ [customVideoCard]="CustomVideoCard">
1042
+ </app-mediasfu-generic>
1043
+ `
1044
+ })
1045
+ export class AppComponent {
1046
+ CustomVideoCard = MyCustomVideoCardComponent;
1047
+ credentials = { apiUserName: 'username', apiKey: 'key' };
1048
+ }
1049
+ ```
1050
+
1051
+ ---
1052
+
1053
+ ## Working with Methods <a name="working-with-methods"></a>
1054
+
1055
+ MediaSFU provides 200+ methods for controlling every aspect of your real-time communication experience. Let's explore the most important categories.
1056
+
1057
+ ### Media Control Methods
1058
+
1059
+ #### Video Control
1060
+
1061
+ ```typescript
1062
+ // Toggle video on/off
1063
+ parameters.clickVideo({ parameters });
1064
+
1065
+ // Switch camera (front/back on mobile)
1066
+ parameters.switchVideoAlt({ parameters });
1067
+
1068
+ // Switch to specific camera by ID
1069
+ const cameras = await parameters.getMediaDevicesList('videoinput');
1070
+ parameters.switchUserVideo({
1071
+ videoPreference: cameras[1].deviceId,
1072
+ parameters
1073
+ });
1074
+
1075
+ // Get current video state
1076
+ const isVideoOn = parameters.videoAlreadyOn;
1077
+
1078
+ // Subscribe to video state changes (RxJS)
1079
+ parameters.videoAlreadyOn.subscribe((isOn: boolean) => {
1080
+ console.log('Video is now:', isOn ? 'ON' : 'OFF');
1081
+ });
1082
+
1083
+ // Update video state programmatically
1084
+ parameters.updateVideoAlreadyOn.next(true);
1085
+ ```
1086
+
1087
+ #### Audio Control
1088
+
1089
+ ```typescript
1090
+ // Toggle audio on/off
1091
+ parameters.clickAudio({ parameters });
1092
+
1093
+ // Switch microphone
1094
+ const microphones = await parameters.getMediaDevicesList('audioinput');
1095
+ parameters.switchUserAudio({
1096
+ audioPreference: microphones[1].deviceId,
1097
+ parameters
1098
+ });
1099
+
1100
+ // Get current audio state
1101
+ const isAudioOn = parameters.audioAlreadyOn;
1102
+ const hasHostPermission = parameters.micAction; // Host approval status
1103
+
1104
+ // Subscribe to audio state changes
1105
+ parameters.audioAlreadyOn.subscribe((isOn: boolean) => {
1106
+ console.log('Audio is now:', isOn ? 'ON' : 'OFF');
1107
+ });
1108
+
1109
+ // Mute/unmute specific participant (host only)
1110
+ parameters.controlMedia({
1111
+ participantId: 'participant-id',
1112
+ participantName: 'John Doe',
1113
+ type: 'audio',
1114
+ socket: parameters.socket,
1115
+ roomName: parameters.roomName
1116
+ });
1117
+ ```
1118
+
1119
+ #### Screen Sharing
184
1120
 
185
- A basic guide on how to use the module for common tasks.
1121
+ ```typescript
1122
+ // Start screen sharing
1123
+ parameters.clickScreenShare({ parameters });
186
1124
 
187
- This section will guide users through the initial setup and installation of the npm module.
188
- ## Introduction
1125
+ // Stop screen sharing
1126
+ parameters.stopShareScreen({ parameters });
189
1127
 
190
- MediaSFU is a 2-page application consisting of a prejoin/welcome page and the main events room page. This guide will walk you through the basic usage of the module for setting up these pages.
1128
+ // Check if screen sharing is available
1129
+ const canShare = await parameters.checkScreenShare({ parameters });
191
1130
 
192
- ### Documentation Reference
1131
+ // Get screen share state
1132
+ const isSharing = parameters.screenAlreadyOn;
1133
+ const shareAudio = parameters.shareScreenStarted; // Sharing with audio
193
1134
 
194
- 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.
1135
+ // Subscribe to screen share state
1136
+ parameters.screenAlreadyOn.subscribe((isSharing: boolean) => {
1137
+ console.log('Screen sharing:', isSharing ? 'ACTIVE' : 'INACTIVE');
1138
+ });
1139
+ ```
195
1140
 
196
- ## Prebuilt Event Rooms
1141
+ ### Media Device and Stream Utility Methods
197
1142
 
198
- MediaSFU provides prebuilt event rooms for various purposes. These rooms are rendered as full pages and can be easily imported and used in your application. Here are the available prebuilt event rooms:
1143
+ #### Get Available Media Devices
199
1144
 
200
- 1. **MediasfuGeneric**: A generic event room suitable for various types of events.
201
- 2. **MediasfuBroadcast**: A room optimized for broadcasting events.
202
- 3. **MediasfuWebinar**: Specifically designed for hosting webinars.
203
- 4. **MediasfuConference**: Ideal for hosting conferences.
204
- 5. **MediasfuChat**: A room tailored for interactive chat sessions.
1145
+ ```typescript
1146
+ // Get available cameras
1147
+ const cameras = await parameters.getMediaDevicesList('videoinput');
1148
+ cameras.forEach(camera => {
1149
+ console.log(`Camera: ${camera.label} (${camera.deviceId})`);
1150
+ });
1151
+
1152
+ // Get available microphones
1153
+ const microphones = await parameters.getMediaDevicesList('audioinput');
1154
+ microphones.forEach(mic => {
1155
+ console.log(`Microphone: ${mic.label} (${mic.deviceId})`);
1156
+ });
1157
+
1158
+ // Building a device selector UI
1159
+ @Component({
1160
+ selector: 'app-device-selector',
1161
+ template: `
1162
+ <div class="device-selector">
1163
+ <select (change)="onCameraChange($event)">
1164
+ <option value="">Select Camera</option>
1165
+ @for (camera of cameras; track camera.deviceId) {
1166
+ <option [value]="camera.deviceId">
1167
+ {{ camera.label }}
1168
+ </option>
1169
+ }
1170
+ </select>
1171
+
1172
+ <select (change)="onMicrophoneChange($event)">
1173
+ <option value="">Select Microphone</option>
1174
+ @for (mic of microphones; track mic.deviceId) {
1175
+ <option [value]="mic.deviceId">
1176
+ {{ mic.label }}
1177
+ </option>
1178
+ }
1179
+ </select>
1180
+ </div>
1181
+ `
1182
+ })
1183
+ export class DeviceSelectorComponent implements OnInit {
1184
+ @Input() parameters: any;
1185
+ cameras: MediaDeviceInfo[] = [];
1186
+ microphones: MediaDeviceInfo[] = [];
205
1187
 
206
- Users can easily pick an interface and render it in their app.
1188
+ async ngOnInit() {
1189
+ await this.loadDevices();
1190
+ }
207
1191
 
208
- If no API credentials are provided, a default home page will be displayed where users can scan or manually enter the event details.
1192
+ async loadDevices() {
1193
+ this.cameras = await this.parameters.getMediaDevicesList('videoinput');
1194
+ this.microphones = await this.parameters.getMediaDevicesList('audioinput');
1195
+ }
209
1196
 
210
- To use these prebuilt event rooms, simply import them into your application:
1197
+ onCameraChange(event: any) {
1198
+ this.parameters.switchUserVideo({
1199
+ videoPreference: event.target.value,
1200
+ parameters: this.parameters
1201
+ });
1202
+ }
211
1203
 
212
- ```javascript
213
- ## Simplest Usage
1204
+ onMicrophoneChange(event: any) {
1205
+ this.parameters.switchUserAudio({
1206
+ audioPreference: event.target.value,
1207
+ parameters: this.parameters
1208
+ });
1209
+ }
1210
+ }
1211
+ ```
214
1212
 
215
- The simplest way to use MediaSFU in your Angular application is by directly rendering the prebuilt event room component, such as `MediasfuGeneric`.
1213
+ #### Get Participant Media Streams
216
1214
 
217
1215
  ```typescript
218
- // app.component.ts
219
- import { Component } from '@angular/core';
220
- import { MediasfuGeneric } from 'mediasfu-angular';
221
-
1216
+ // Get participant video stream by ID
1217
+ const videoStream = await parameters.getParticipantMedia({
1218
+ id: 'producer-123',
1219
+ kind: 'video'
1220
+ });
1221
+
1222
+ // Get participant audio stream by name
1223
+ const audioStream = await parameters.getParticipantMedia({
1224
+ name: 'John Doe',
1225
+ kind: 'audio'
1226
+ });
1227
+
1228
+ // Example: Custom participant stream monitor
222
1229
  @Component({
223
- selector: 'app-root',
224
- standalone: true,
225
- imports: [MediasfuGeneric],
1230
+ selector: 'app-stream-monitor',
226
1231
  template: `
227
- <app-mediasfu-generic></app-mediasfu-generic>
1232
+ <div class="stream-monitor">
1233
+ <h3>Participant Streams</h3>
1234
+ @for (participant of participants; track participant.id) {
1235
+ <div class="participant-item">
1236
+ <span>{{ participant.name }}</span>
1237
+ <button (click)="viewStream(participant)">View Stream</button>
1238
+ <button (click)="monitorAudio(participant)">Monitor Audio</button>
1239
+ </div>
1240
+ }
1241
+
1242
+ @if (selectedStream) {
1243
+ <div class="stream-viewer">
1244
+ <video #streamVideo autoplay playsinline></video>
1245
+ </div>
1246
+ }
1247
+ </div>
228
1248
  `,
1249
+ styles: [`
1250
+ .stream-monitor { padding: 20px; }
1251
+ .participant-item {
1252
+ margin: 10px 0;
1253
+ display: flex;
1254
+ gap: 10px;
1255
+ align-items: center;
1256
+ }
1257
+ .stream-viewer video {
1258
+ width: 100%;
1259
+ max-width: 640px;
1260
+ border: 2px solid #1976d2;
1261
+ }
1262
+ `]
229
1263
  })
230
- export class AppComponent { }
231
- ```
1264
+ export class StreamMonitorComponent {
1265
+ @Input() parameters: any;
1266
+ @ViewChild('streamVideo') videoElement!: ElementRef<HTMLVideoElement>;
1267
+
1268
+ selectedStream: MediaStream | null = null;
1269
+
1270
+ get participants() {
1271
+ return this.parameters?.participants || [];
1272
+ }
232
1273
 
233
- That's it! With just 3 lines of code, you have a fully functional video conferencing application with:
1274
+ async viewStream(participant: any) {
1275
+ const stream = await this.parameters.getParticipantMedia({
1276
+ id: participant.videoID,
1277
+ name: participant.name,
1278
+ kind: 'video'
1279
+ });
234
1280
 
235
- - 🎥 **Video & Audio Streaming** - High-quality real-time communication
236
- - 🖥️ **Screen Sharing** - Share your screen with annotation support
237
- - 💬 **Interactive Chat** - Direct and group messaging
238
- - 👥 **Participant Management** - Join/leave controls and permissions
239
- - 🎨 **Customizable UI** - Professional, responsive design out of the box
240
- - 📱 **Mobile Responsive** - Works seamlessly on all devices
241
- - 🔧 **Zero Configuration** - No complex setup required
1281
+ if (stream && this.videoElement) {
1282
+ this.selectedStream = stream;
1283
+ this.videoElement.nativeElement.srcObject = stream;
1284
+ } else {
1285
+ console.log('No video stream found for participant');
1286
+ }
1287
+ }
242
1288
 
243
- ## Why Choose MediaSFU?
1289
+ async monitorAudio(participant: any) {
1290
+ const stream = await this.parameters.getParticipantMedia({
1291
+ id: participant.audioID,
1292
+ name: participant.name,
1293
+ kind: 'audio'
1294
+ });
244
1295
 
245
- ### **Lightning Fast Setup**
246
- From `npm install` to a working video conference in under 2 minutes. No WebRTC knowledge required.
1296
+ if (stream) {
1297
+ // Create audio context for analysis
1298
+ const audioContext = new AudioContext();
1299
+ const analyser = audioContext.createAnalyser();
1300
+ const source = audioContext.createMediaStreamSource(stream);
1301
+ source.connect(analyser);
1302
+
1303
+ console.log('Monitoring audio for:', participant.name);
1304
+ // Add your audio analysis logic here
1305
+ } else {
1306
+ console.log('No audio stream found for participant');
1307
+ }
1308
+ }
1309
+ }
1310
+ ```
247
1311
 
248
- ### 🎯 **Multiple Event Types Ready**
249
- Choose from pre-built, production-ready templates:
1312
+ ### Participant Management Methods
250
1313
 
251
1314
  ```typescript
252
- import {
253
- MediasfuGeneric, // 🌐 All-purpose meetings
254
- MediasfuBroadcast, // 📺 Live streaming events
255
- MediasfuWebinar, // 🎓 Educational sessions
256
- MediasfuConference, // 💼 Professional meetings
257
- MediasfuChat // 💬 Interactive chat rooms
258
- } from 'mediasfu-angular';
1315
+ // Get all participants
1316
+ const participants = parameters.participants;
1317
+ const participantCount = parameters.participantsCounter;
1318
+
1319
+ // Subscribe to participant changes
1320
+ parameters.participants.subscribe((participants: any[]) => {
1321
+ console.log('Participants updated:', participants);
1322
+ });
1323
+
1324
+ // Filter participants
1325
+ const videoParticipants = participants.filter((p: any) => p.videoOn);
1326
+ const audioOnlyParticipants = participants.filter((p: any) => !p.videoOn);
1327
+ const mutedParticipants = participants.filter((p: any) => p.muted);
1328
+
1329
+ // Find specific participant
1330
+ const participant = participants.find((p: any) => p.name === 'John Doe');
1331
+
1332
+ // Remove participant from room (host only)
1333
+ parameters.disconnectUserInitiate({
1334
+ member: participantId,
1335
+ roomName: parameters.roomName,
1336
+ socket: parameters.socket
1337
+ });
1338
+
1339
+ // Change participant role (host only)
1340
+ parameters.updateParticipant({
1341
+ participantId: 'participant-id',
1342
+ islevel: '2', // '2' = host, '1' = co-host, '0' = participant
1343
+ parameters
1344
+ });
1345
+
1346
+ // Request to unmute participant (sends request)
1347
+ parameters.requestScreenShare({ parameters });
259
1348
  ```
260
1349
 
261
- ### 🛠️ **Infinitely Customizable**
262
-
263
- **Replace Any Component:** Don't like our video card? Swap it with yours:
1350
+ ### Chat & Messaging Methods
264
1351
 
265
1352
  ```typescript
266
- // Your custom video component
1353
+ // Send a group message
1354
+ parameters.sendMessage({
1355
+ message: 'Hello everyone!',
1356
+ type: 'group',
1357
+ parameters
1358
+ });
1359
+
1360
+ // Send direct message
1361
+ parameters.sendMessage({
1362
+ message: 'Private message',
1363
+ type: 'direct',
1364
+ receivers: ['participant-id'],
1365
+ parameters
1366
+ });
1367
+
1368
+ // Access message history
1369
+ const messages = parameters.messages;
1370
+
1371
+ // Subscribe to new messages (RxJS)
1372
+ parameters.messages.subscribe((messages: any[]) => {
1373
+ console.log('Messages updated:', messages);
1374
+ });
1375
+
1376
+ // Example: Custom chat component
267
1377
  @Component({
268
- selector: 'my-video-card',
269
- template: `<div class="my-custom-video">{{ participant.name }}</div>`
1378
+ selector: 'app-custom-chat',
1379
+ standalone: true,
1380
+ imports: [CommonModule, FormsModule],
1381
+ template: `
1382
+ <div class="chat-container">
1383
+ <div class="messages">
1384
+ @for (msg of messages; track msg.timestamp) {
1385
+ <div class="message">
1386
+ <strong>{{ msg.sender }}:</strong> {{ msg.message }}
1387
+ </div>
1388
+ }
1389
+ </div>
1390
+ <div class="input-area">
1391
+ <input
1392
+ [(ngModel)]="message"
1393
+ (keyup.enter)="sendMessage()"
1394
+ placeholder="Type a message..."
1395
+ />
1396
+ <button (click)="sendMessage()">Send</button>
1397
+ </div>
1398
+ </div>
1399
+ `,
1400
+ styles: [`
1401
+ .chat-container {
1402
+ display: flex;
1403
+ flex-direction: column;
1404
+ height: 400px;
1405
+ }
1406
+ .messages {
1407
+ flex: 1;
1408
+ overflow-y: auto;
1409
+ padding: 10px;
1410
+ }
1411
+ .message {
1412
+ margin: 5px 0;
1413
+ padding: 8px;
1414
+ background: #f5f5f5;
1415
+ border-radius: 4px;
1416
+ }
1417
+ .input-area {
1418
+ display: flex;
1419
+ gap: 10px;
1420
+ padding: 10px;
1421
+ border-top: 1px solid #ddd;
1422
+ }
1423
+ input {
1424
+ flex: 1;
1425
+ padding: 8px;
1426
+ border: 1px solid #ddd;
1427
+ border-radius: 4px;
1428
+ }
1429
+ button {
1430
+ padding: 8px 16px;
1431
+ background: #1976d2;
1432
+ color: white;
1433
+ border: none;
1434
+ border-radius: 4px;
1435
+ cursor: pointer;
1436
+ }
1437
+ `]
270
1438
  })
271
- export class MyVideoCard { }
1439
+ export class CustomChatComponent implements OnInit {
1440
+ @Input() parameters: any;
1441
+ message = '';
1442
+ messages: any[] = [];
1443
+
1444
+ ngOnInit() {
1445
+ // Subscribe to messages
1446
+ this.parameters?.messages?.subscribe((msgs: any[]) => {
1447
+ this.messages = msgs;
1448
+ });
1449
+ }
1450
+
1451
+ sendMessage() {
1452
+ if (this.message.trim()) {
1453
+ this.parameters?.sendMessage({
1454
+ message: this.message,
1455
+ type: 'group',
1456
+ parameters: this.parameters
1457
+ });
1458
+ this.message = '';
1459
+ }
1460
+ }
1461
+ }
1462
+ ```
272
1463
 
273
- // Use it in MediaSFU
274
- const customVideoCard = this.customComponentService.createComponentWithInjector(
275
- MyVideoCard, { participant: participantData }
276
- );
1464
+ ### Recording Methods
277
1465
 
278
- // MediaSFU will use your component instead
279
- this.mediasfuParameters = { customVideoCard };
1466
+ ```typescript
1467
+ // Start recording
1468
+ parameters.startRecording({ parameters });
1469
+
1470
+ // Stop recording
1471
+ parameters.stopRecording({ parameters });
1472
+
1473
+ // Pause recording
1474
+ parameters.pauseRecording({ parameters });
1475
+
1476
+ // Resume recording
1477
+ parameters.resumeRecording({ parameters });
1478
+
1479
+ // Configure recording settings
1480
+ parameters.updateRecording({
1481
+ recordingMediaOptions: 'video', // or 'audio'
1482
+ recordingAudioOptions: 'all', // or 'host'
1483
+ recordingVideoOptions: 'all', // or 'host'
1484
+ recordingVideoType: 'fullDisplay', // or 'bestDisplay', 'all'
1485
+ recordingDisplayType: 'video', // 'media', 'video', 'all'
1486
+ recordingBackgroundColor: '#000000',
1487
+ recordingNameTagsColor: '#ffffff',
1488
+ recordingOrientationVideo: 'landscape', // or 'portrait'
1489
+ recordingNameTags: true,
1490
+ recordingAddHLS: false,
1491
+ parameters
1492
+ });
1493
+
1494
+ // Check recording state
1495
+ const isRecording = parameters.recordStarted;
1496
+ const isPaused = parameters.recordPaused;
1497
+ const recordingTime = parameters.recordElapsedTime;
1498
+
1499
+ // Subscribe to recording state changes
1500
+ parameters.recordStarted.subscribe((isRecording: boolean) => {
1501
+ console.log('Recording:', isRecording ? 'ACTIVE' : 'STOPPED');
1502
+ });
280
1503
  ```
281
1504
 
282
- **Custom Layouts:** Build your own UI while keeping MediaSFU's powerful backend:
1505
+ ### Polls & Surveys Methods
283
1506
 
284
1507
  ```typescript
285
- // Your WhatsApp-style video call UI
1508
+ // Create a poll
1509
+ parameters.handleCreatePoll({
1510
+ poll: {
1511
+ question: 'What time works best?',
1512
+ type: 'multiple', // or 'single'
1513
+ options: ['10 AM', '2 PM', '5 PM']
1514
+ },
1515
+ parameters
1516
+ });
1517
+
1518
+ // Vote on a poll
1519
+ parameters.handleVotePoll({
1520
+ pollId: 'poll-id',
1521
+ optionIndex: 1,
1522
+ parameters
1523
+ });
1524
+
1525
+ // End a poll
1526
+ parameters.handleEndPoll({
1527
+ pollId: 'poll-id',
1528
+ parameters
1529
+ });
1530
+
1531
+ // Access poll data
1532
+ const polls = parameters.polls;
1533
+
1534
+ // Subscribe to polls changes
1535
+ parameters.polls.subscribe((polls: any[]) => {
1536
+ const activePoll = polls.find(p => p.status === 'active');
1537
+ console.log('Active poll:', activePoll);
1538
+ });
1539
+
1540
+ // Example: Custom poll component
286
1541
  @Component({
1542
+ selector: 'app-custom-poll',
1543
+ standalone: true,
1544
+ imports: [CommonModule],
287
1545
  template: `
288
- <div class="whatsapp-style-layout">
289
- <header>My Custom Header</header>
290
- <main>Your video layout here</main>
291
- </div>
292
- `
1546
+ @if (activePoll) {
1547
+ <div class="poll-container">
1548
+ <h3>{{ activePoll.question }}</h3>
1549
+ <div class="poll-options">
1550
+ @for (option of activePoll.options; track $index) {
1551
+ <button
1552
+ class="poll-option"
1553
+ (click)="vote($index)">
1554
+ {{ option }}
1555
+ <span class="votes">({{ activePoll.votes?.[$index] || 0 }} votes)</span>
1556
+ </button>
1557
+ }
1558
+ </div>
1559
+ @if (isHost) {
1560
+ <button class="end-poll" (click)="endPoll()">
1561
+ End Poll
1562
+ </button>
1563
+ }
1564
+ </div>
1565
+ }
1566
+ `,
1567
+ styles: [`
1568
+ .poll-container {
1569
+ padding: 20px;
1570
+ background: white;
1571
+ border-radius: 8px;
1572
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1573
+ }
1574
+ .poll-options {
1575
+ display: flex;
1576
+ flex-direction: column;
1577
+ gap: 10px;
1578
+ margin: 20px 0;
1579
+ }
1580
+ .poll-option {
1581
+ padding: 12px;
1582
+ border: 2px solid #1976d2;
1583
+ background: white;
1584
+ border-radius: 4px;
1585
+ cursor: pointer;
1586
+ transition: all 0.3s;
1587
+ display: flex;
1588
+ justify-content: space-between;
1589
+ }
1590
+ .poll-option:hover {
1591
+ background: #1976d2;
1592
+ color: white;
1593
+ }
1594
+ .votes {
1595
+ font-size: 0.9em;
1596
+ opacity: 0.7;
1597
+ }
1598
+ .end-poll {
1599
+ padding: 10px 20px;
1600
+ background: #d32f2f;
1601
+ color: white;
1602
+ border: none;
1603
+ border-radius: 4px;
1604
+ cursor: pointer;
1605
+ }
1606
+ `]
293
1607
  })
294
- export class MyCustomLayout { }
1608
+ export class CustomPollComponent implements OnInit {
1609
+ @Input() parameters: any;
1610
+ activePoll: any = null;
295
1611
 
296
- // MediaSFU handles all the complex WebRTC, you handle the UI
297
- this.mediasfuParameters = {
298
- customMainComponent: MyCustomLayout,
299
- returnUI: false // Disable default MediaSFU UI
300
- };
1612
+ ngOnInit() {
1613
+ this.parameters?.polls?.subscribe((polls: any[]) => {
1614
+ this.activePoll = polls.find((p: any) => p.status === 'active');
1615
+ });
1616
+ }
1617
+
1618
+ get isHost() {
1619
+ return this.parameters?.islevel === '2';
1620
+ }
1621
+
1622
+ vote(optionIndex: number) {
1623
+ if (this.activePoll) {
1624
+ this.parameters?.handleVotePoll({
1625
+ pollId: this.activePoll.id,
1626
+ optionIndex,
1627
+ parameters: this.parameters
1628
+ });
1629
+ }
1630
+ }
1631
+
1632
+ endPoll() {
1633
+ if (this.activePoll) {
1634
+ this.parameters?.handleEndPoll({
1635
+ pollId: this.activePoll.id,
1636
+ parameters: this.parameters
1637
+ });
1638
+ }
1639
+ }
1640
+ }
301
1641
  ```
302
1642
 
303
- ### 🚀 **Production-Ready Features**
1643
+ ### Breakout Rooms Methods
304
1644
 
305
- - **Cloud Recording** with custom watermarks and layouts
306
- - **Breakout Rooms** for team collaboration
307
- - **Polls & Surveys** for audience engagement
308
- - **Waiting Rooms** with host controls
309
- - **Advanced Permissions** and moderation tools
310
- - **Real-time Analytics** and monitoring
311
- - **Enterprise Security** with end-to-end encryption
1645
+ ```typescript
1646
+ // Create breakout rooms
1647
+ parameters.createBreakoutRooms({
1648
+ numberOfRooms: 3,
1649
+ participants: parameters.participants,
1650
+ parameters
1651
+ });
1652
+
1653
+ // Assign participant to room
1654
+ parameters.assignParticipantToRoom({
1655
+ participantId: 'participant-id',
1656
+ roomIndex: 0,
1657
+ parameters
1658
+ });
1659
+
1660
+ // Start breakout rooms
1661
+ parameters.startBreakoutRooms({ parameters });
1662
+
1663
+ // Stop breakout rooms
1664
+ parameters.stopBreakoutRooms({ parameters });
1665
+
1666
+ // Access breakout room data
1667
+ const breakoutRooms = parameters.breakoutRooms;
1668
+ const currentRoom = parameters.currentBreakoutRoom;
1669
+
1670
+ // Subscribe to breakout room changes
1671
+ parameters.breakoutRooms.subscribe((rooms: any[]) => {
1672
+ console.log('Breakout rooms:', rooms);
1673
+ });
1674
+ ```
312
1675
 
313
- ### 🌍 **Flexible Deployment Options**
1676
+ ### Whiteboard Methods
314
1677
 
315
1678
  ```typescript
316
- // Option 1: MediaSFU Cloud (Fastest setup)
317
- const credentials = { apiUserName: 'your-key', apiKey: 'your-secret' };
1679
+ // Show/hide whiteboard
1680
+ parameters.updateWhiteboardStarted.next(true);
1681
+ parameters.updateWhiteboardEnded.next(false);
1682
+
1683
+ // Configure whiteboard
1684
+ parameters.launchConfigureWhiteboard?.launchConfigureWhiteboard({ parameters });
1685
+
1686
+ // Access whiteboard state
1687
+ const isWhiteboardActive = parameters.whiteboardStarted;
318
1688
 
319
- // Option 2: Self-hosted Community Edition (Free)
320
- const localLink = 'http://your-server.com';
1689
+ // Subscribe to whiteboard state
1690
+ parameters.whiteboardStarted.subscribe((isActive: boolean) => {
1691
+ console.log('Whiteboard:', isActive ? 'ACTIVE' : 'INACTIVE');
1692
+ });
321
1693
 
322
- // Option 3: Hybrid (Best of both worlds)
323
- const config = {
324
- localLink: 'http://your-server.com',
325
- connectMediaSFU: true, // Use cloud for advanced features
326
- credentials: credentials
1694
+ // Access whiteboard users
1695
+ const whiteboardUsers = parameters.whiteboardUsers;
1696
+ ```
1697
+
1698
+ ### Utility Methods
1699
+
1700
+ ```typescript
1701
+ // Check permissions
1702
+ const hasPermission = await parameters.checkPermission({
1703
+ permissionType: 'video', // or 'audio'
1704
+ parameters
1705
+ });
1706
+
1707
+ // Format large numbers
1708
+ const formatted = parameters.formatNumber(1250000); // Returns "1.25M"
1709
+
1710
+ // Sleep/delay
1711
+ await parameters.sleep({ ms: 1000 });
1712
+
1713
+ // Update display settings
1714
+ parameters.updateMainWindow.next(true); // Show/hide main window
1715
+
1716
+ // Trigger layout recalculation
1717
+ parameters.onScreenChanges({ changed: true, parameters });
1718
+
1719
+ // Get room information
1720
+ const roomInfo = {
1721
+ name: parameters.roomName,
1722
+ host: parameters.host,
1723
+ capacity: parameters.capacity,
1724
+ eventType: parameters.eventType,
1725
+ participants: parameters.participants,
1726
+ isRecording: parameters.recordStarted
327
1727
  };
1728
+
1729
+ // Subscribe to room state changes
1730
+ parameters.roomName.subscribe((name: string) => {
1731
+ console.log('Room name:', name);
1732
+ });
1733
+
1734
+ parameters.participantsCounter.subscribe((count: number) => {
1735
+ console.log('Participant count:', count);
1736
+ });
328
1737
  ```
329
1738
 
1739
+ ---
1740
+
330
1741
  ## Programmatically Fetching Tokens
331
1742
 
332
1743
  If you prefer to fetch the required tokens programmatically without visiting MediaSFU's website, you can use the `PreJoinPage` component and pass your credentials as inputs.
@@ -2590,335 +4001,6 @@ Once your custom components are built, modify the imports of `prepopulateUserMed
2590
4001
 
2591
4002
  This allows for full flexibility in how media is displayed in both the main and mini grids, giving you the ability to tailor the user experience to your specific needs.
2592
4003
 
2593
- ## Custom Component Injection Service
2594
-
2595
- MediaSFU provides a powerful `CustomComponentInjectionService` that enables advanced custom component integration across all MediaSFU components. This service offers a comprehensive set of utilities for managing custom component rendering, validation, and lifecycle management.
2596
-
2597
- ### Features
2598
-
2599
- - **Type-safe component validation**: Check if components are Angular components, HTML elements, or function components
2600
- - **Dynamic component injection**: Create components with custom parameters and injectors
2601
- - **Safe rendering utilities**: Handle different component types with proper error handling and fallbacks
2602
- - **Component lifecycle management**: Proper cleanup and destruction of custom components
2603
- - **Template integration helpers**: Utilities for the custom component pattern used throughout MediaSFU
2604
-
2605
- ### Basic Usage
2606
-
2607
- First, import the service and types:
2608
-
2609
- ```typescript
2610
- import {
2611
- CustomComponentInjectionService,
2612
- CustomComponent,
2613
- CustomComponentType,
2614
- CustomComponentParameters,
2615
- CustomComponentContext
2616
- } from 'mediasfu-angular';
2617
- ```
2618
-
2619
- #### Injecting the Service
2620
-
2621
- ```typescript
2622
- import { Component, inject } from '@angular/core';
2623
-
2624
- @Component({
2625
- selector: 'app-my-component',
2626
- template: `...`
2627
- })
2628
- export class MyComponent {
2629
- private customComponentService = inject(CustomComponentInjectionService);
2630
- }
2631
- ```
2632
-
2633
- #### Creating Custom Components with Parameters
2634
-
2635
- ```typescript
2636
- // Create a custom component with injected parameters
2637
- const customVideoCard = this.customComponentService.createComponentWithInjector(
2638
- MyCustomVideoCard,
2639
- {
2640
- participant: participantData,
2641
- options: videoOptions,
2642
- callbacks: eventHandlers
2643
- }
2644
- );
2645
-
2646
- // Use in MediaSFU component
2647
- const params = {
2648
- customVideoCard: customVideoCard,
2649
- // ... other parameters
2650
- };
2651
- ```
2652
-
2653
- #### Component Type Validation
2654
-
2655
- ```typescript
2656
- // Check component types
2657
- if (this.customComponentService.isCustomComponent(component)) {
2658
- // Handle Angular component with injector
2659
- console.log('Angular component detected');
2660
- } else if (this.customComponentService.isHTMLElement(component)) {
2661
- // Handle HTML element
2662
- console.log('HTML element detected');
2663
- } else if (this.customComponentService.isFunctionComponent(component)) {
2664
- // Handle function component
2665
- console.log('Function component detected');
2666
- }
2667
- ```
2668
-
2669
- #### Safe HTML Element Access
2670
-
2671
- ```typescript
2672
- // Safely get outerHTML from components
2673
- const htmlContent = this.customComponentService.getHtmlElementOuterHTML(component);
2674
- if (htmlContent) {
2675
- // Use the HTML content
2676
- element.innerHTML = htmlContent;
2677
- }
2678
- ```
2679
-
2680
- #### Component Resolution Pattern
2681
-
2682
- ```typescript
2683
- // Resolve between custom and default components
2684
- const resolvedComponent = this.customComponentService.resolveComponent(
2685
- customComponent, // May be undefined
2686
- defaultComponent // Fallback component
2687
- );
2688
-
2689
- // Check if custom main component should be used
2690
- const useCustomLayout = this.customComponentService.shouldUseCustomMainComponent(
2691
- customMainComponent
2692
- );
2693
- ```
2694
-
2695
- ### Advanced Usage Examples
2696
-
2697
- #### 1. Dynamic Component Rendering
2698
-
2699
- ```typescript
2700
- @Component({
2701
- selector: 'app-dynamic-renderer',
2702
- template: `
2703
- <ng-container #dynamicContainer></ng-container>
2704
- `
2705
- })
2706
- export class DynamicRenderer {
2707
- @ViewChild('dynamicContainer', { read: ViewContainerRef })
2708
- container!: ViewContainerRef;
2709
-
2710
- private customComponentService = inject(CustomComponentInjectionService);
2711
-
2712
- renderCustomComponent(customComponent: CustomComponentType<any>) {
2713
- const context: CustomComponentContext = {
2714
- parameters: {
2715
- data: this.componentData,
2716
- events: this.eventHandlers
2717
- },
2718
- injector: this.injector,
2719
- config: {
2720
- enabled: true,
2721
- fallbackToDefault: true
2722
- }
2723
- };
2724
-
2725
- const componentRef = this.customComponentService.renderCustomComponent(
2726
- customComponent,
2727
- this.container,
2728
- context
2729
- );
2730
-
2731
- // Store reference for cleanup
2732
- this.activeComponents.push(componentRef);
2733
- }
2734
-
2735
- ngOnDestroy() {
2736
- // Clean up all custom components
2737
- this.activeComponents.forEach(component => {
2738
- this.customComponentService.destroyCustomComponent(component);
2739
- });
2740
- }
2741
- }
2742
- ```
2743
-
2744
- #### 2. Custom Video Card Implementation
2745
-
2746
- ```typescript
2747
- // Define your custom video card component
2748
- @Component({
2749
- selector: 'app-custom-video-card',
2750
- template: `
2751
- <div class="custom-video-wrapper">
2752
- <video [srcObject]="videoStream" autoplay playsinline></video>
2753
- <div class="custom-controls">
2754
- <button (click)="toggleAudio()">🎤</button>
2755
- <button (click)="toggleVideo()">📹</button>
2756
- </div>
2757
- <div class="custom-info">{{ participantName }}</div>
2758
- </div>
2759
- `,
2760
- imports: [CommonModule]
2761
- })
2762
- export class CustomVideoCard {
2763
- @Input() participant!: Participant;
2764
- @Input() videoStream!: MediaStream;
2765
- @Input() options!: any;
2766
-
2767
- get participantName() {
2768
- return this.participant?.name || 'Unknown';
2769
- }
2770
-
2771
- toggleAudio() {
2772
- // Custom audio toggle logic
2773
- }
2774
-
2775
- toggleVideo() {
2776
- // Custom video toggle logic
2777
- }
2778
- }
2779
-
2780
- // In your main component
2781
- export class MyMediaComponent {
2782
- private customComponentService = inject(CustomComponentInjectionService);
2783
-
2784
- ngOnInit() {
2785
- // Create custom video card with parameters
2786
- const customVideoCard = this.customComponentService.createComponentWithInjector(
2787
- CustomVideoCard,
2788
- {
2789
- participant: this.currentParticipant,
2790
- options: this.videoOptions
2791
- }
2792
- );
2793
-
2794
- // Use in MediaSFU parameters
2795
- this.mediasfuParameters = {
2796
- customVideoCard: customVideoCard,
2797
- // ... other parameters
2798
- };
2799
- }
2800
- }
2801
- ```
2802
-
2803
- #### 3. Custom Main Component with Template Restructuring
2804
-
2805
- ```typescript
2806
- @Component({
2807
- selector: 'app-custom-main-layout',
2808
- template: `
2809
- <div class="custom-main-layout">
2810
- <header class="custom-header">
2811
- <h1>My Custom Stream</h1>
2812
- <div class="custom-controls">
2813
- <!-- Custom control buttons -->
2814
- </div>
2815
- </header>
2816
-
2817
- <main class="custom-content">
2818
- <div class="video-area">
2819
- <!-- Your custom video layout -->
2820
- </div>
2821
- <div class="participant-list">
2822
- <!-- Custom participant list -->
2823
- </div>
2824
- </main>
2825
-
2826
- <footer class="custom-footer">
2827
- <!-- Custom footer content -->
2828
- </footer>
2829
- </div>
2830
- `,
2831
- imports: [CommonModule]
2832
- })
2833
- export class CustomMainLayout {
2834
- @Input() parameters!: any;
2835
- @Input() credentials!: any;
2836
- }
2837
-
2838
- // Usage
2839
- const customMainComponent = this.customComponentService.createComponentWithInjector(
2840
- CustomMainLayout,
2841
- {
2842
- theme: 'dark',
2843
- layout: 'grid'
2844
- }
2845
- );
2846
-
2847
- // MediaSFU will use this instead of default layout
2848
- this.mediasfuParameters = {
2849
- customMainComponent: customMainComponent,
2850
- // ... other parameters
2851
- };
2852
- ```
2853
-
2854
- ### Component Validation and Error Handling
2855
-
2856
- ```typescript
2857
- // Validate before using
2858
- if (this.customComponentService.validateCustomComponent(customComponent)) {
2859
- // Component is valid, proceed with rendering
2860
- this.renderComponent(customComponent);
2861
- } else {
2862
- // Handle invalid component, use fallback
2863
- console.warn('Invalid custom component, using default');
2864
- this.renderComponent(this.defaultComponent);
2865
- }
2866
-
2867
- // Merge options safely
2868
- const mergedOptions = this.customComponentService.mergeComponentOptions(
2869
- customOptions,
2870
- defaultOptions
2871
- );
2872
- ```
2873
-
2874
- ### TypeScript Types
2875
-
2876
- The service works with these main types:
2877
-
2878
- ```typescript
2879
- // Union type for all supported component types
2880
- type CustomComponentType<T = any> = CustomComponent<T> | HTMLElement | CustomComponentFunction;
2881
-
2882
- // Angular component with injector
2883
- interface CustomComponent<T = any> {
2884
- component: Type<T>;
2885
- injector?: Injector;
2886
- }
2887
-
2888
- // Function that returns HTMLElement
2889
- type CustomComponentFunction = () => HTMLElement;
2890
-
2891
- // Parameters for component injection
2892
- interface CustomComponentParameters {
2893
- [key: string]: any;
2894
- }
2895
-
2896
- // Context for component rendering
2897
- interface CustomComponentContext {
2898
- parameters: CustomComponentParameters;
2899
- injector?: Injector;
2900
- config?: ComponentInjectionConfig;
2901
- }
2902
-
2903
- // Configuration options
2904
- interface ComponentInjectionConfig {
2905
- enabled: boolean;
2906
- overrideDefaults: boolean;
2907
- fallbackToDefault: boolean;
2908
- }
2909
- ```
2910
-
2911
- ### Best Practices
2912
-
2913
- 1. **Always validate components** before rendering
2914
- 2. **Use proper cleanup** to prevent memory leaks
2915
- 3. **Leverage type guards** for safe component handling
2916
- 4. **Provide fallbacks** for failed component rendering
2917
- 5. **Use meaningful parameter names** when creating injectors
2918
- 6. **Test custom components** in isolation before integration
2919
-
2920
- The `CustomComponentInjectionService` provides a robust foundation for building highly customizable MediaSFU applications while maintaining type safety and proper Angular practices.
2921
-
2922
4004
 
2923
4005
  # API Reference <a name="api-reference"></a>
2924
4006