mediasfu-angular 2.2.0 → 2.2.2
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/README.md +2288 -375
- package/dist/README.md +2288 -375
- package/dist/fesm2022/mediasfu-angular.mjs +9519 -4188
- package/dist/fesm2022/mediasfu-angular.mjs.map +1 -1
- package/dist/lib/@types/types.d.ts +6 -1
- package/dist/lib/@types/ui-overrides.types.d.ts +310 -0
- package/dist/lib/components/background-components/background-modal/background-modal.component.d.ts +1023 -2
- package/dist/lib/components/breakout-components/breakout-rooms-modal.component.d.ts +1069 -45
- package/dist/lib/components/breakout-components/edit-room-modal/edit-room-modal.component.d.ts +1054 -42
- package/dist/lib/components/co-host-components/co-host-modal/co-host-modal.component.d.ts +1067 -56
- package/dist/lib/components/display-components/alert-component/alert.component.component.d.ts +80 -24
- package/dist/lib/components/display-components/audio-grid/audio-grid.component.d.ts +83 -11
- package/dist/lib/components/display-components/flexible-grid/flexible-grid.component.d.ts +96 -33
- package/dist/lib/components/display-components/loading-modal/loading-modal.component.d.ts +16 -26
- package/dist/lib/components/display-components/main-aspect-component/main-aspect-component.component.d.ts +6 -2
- package/dist/lib/components/display-components/main-container-component/main-container-component.component.d.ts +6 -2
- package/dist/lib/components/display-components/main-grid-component/main-grid-component.component.d.ts +7 -13
- package/dist/lib/components/display-components/main-screen-component/main-screen-component.component.d.ts +508 -7
- package/dist/lib/components/display-components/other-grid-component/other-grid-component.component.d.ts +7 -1
- package/dist/lib/components/display-components/sub-aspect-component/sub-aspect-component.component.d.ts +7 -2
- package/dist/lib/components/display-settings-components/display-settings-modal.component.d.ts +1107 -27
- package/dist/lib/components/event-settings-components/event-settings-modal/event-settings-modal.component.d.ts +1134 -49
- package/dist/lib/components/exit-components/confirm-exit-modal/confirm-exit-modal.component.d.ts +94 -32
- package/dist/lib/components/media-settings-components/media-settings-modal/media-settings-modal.component.d.ts +1123 -47
- package/dist/lib/components/mediasfu-components/mediasfu-broadcast.component.d.ts +24966 -73
- package/dist/lib/components/mediasfu-components/mediasfu-chat.component.d.ts +276 -73
- package/dist/lib/components/mediasfu-components/mediasfu-conference.component.d.ts +18132 -93
- package/dist/lib/components/mediasfu-components/mediasfu-generic.component.d.ts +56344 -93
- package/dist/lib/components/mediasfu-components/mediasfu-webinar.component.d.ts +20849 -93
- package/dist/lib/components/menu-components/custom-buttons/custom-buttons.component.d.ts +23 -4
- package/dist/lib/components/menu-components/meeting-id-component/meeting-id-component.component.d.ts +78 -1
- package/dist/lib/components/menu-components/meeting-passcode-component/meeting-passcode-component.component.d.ts +37 -1
- package/dist/lib/components/menu-components/menu-modal/menu-modal.component.d.ts +689 -9
- package/dist/lib/components/menu-components/share-buttons-component/share-buttons-component.component.d.ts +46 -2
- package/dist/lib/components/message-components/messages-modal/messages-modal.component.d.ts +76 -13
- package/dist/lib/components/misc-components/confirm-here-modal/confirm-here-modal.component.d.ts +1113 -17
- package/dist/lib/components/misc-components/share-event-modal/share-event-modal.component.d.ts +1114 -29
- package/dist/lib/components/participants-components/participants-modal/participants-modal.component.d.ts +1084 -6
- package/dist/lib/components/polls-components/poll-modal/poll-modal.component.d.ts +1060 -21
- package/dist/lib/components/recording-components/recording-modal/recording-modal.component.d.ts +1054 -35
- package/dist/lib/components/requests-components/requests-modal/requests-modal.component.d.ts +1117 -45
- package/dist/lib/components/screenboard-components/screenboard-modal/screenboard-modal.component.d.ts +1059 -47
- package/dist/lib/components/waiting-components/waiting-room-modal.component.d.ts +1119 -46
- package/dist/lib/components/whiteboard-components/configure-whiteboard-modal/configure-whiteboard-modal.component.d.ts +1049 -16
- package/dist/lib/consumers/add-videos-grid.service.d.ts +3 -0
- package/dist/lib/consumers/connect-ips.service.d.ts +3 -1
- package/dist/lib/consumers/connect-recv-transport.service.d.ts +4 -1
- package/dist/lib/consumers/connect-send-transport-audio.service.d.ts +5 -1
- package/dist/lib/consumers/connect-send-transport-screen.service.d.ts +6 -1
- package/dist/lib/consumers/connect-send-transport-video.service.d.ts +6 -1
- package/dist/lib/consumers/connect-send-transport.service.d.ts +3 -1
- package/dist/lib/consumers/consumer-resume.service.d.ts +2 -1
- package/dist/lib/consumers/create-send-transport.service.d.ts +4 -1
- package/dist/lib/consumers/disconnect-send-transport-audio.service.d.ts +3 -1
- package/dist/lib/consumers/disconnect-send-transport-screen.service.d.ts +3 -1
- package/dist/lib/consumers/disconnect-send-transport-video.service.d.ts +3 -1
- package/dist/lib/consumers/prepopulate-user-media.service.d.ts +3 -0
- package/dist/lib/consumers/resume-send-transport-audio.service.d.ts +3 -1
- package/dist/lib/consumers/signal-new-consumer-transport.service.d.ts +3 -1
- package/dist/lib/consumers/socket-receive-methods/join-consume-room.service.d.ts +3 -1
- package/dist/lib/consumers/socket-receive-methods/new-pipe-producer.service.d.ts +3 -1
- package/dist/lib/consumers/stream-success-audio-switch.service.d.ts +5 -1
- package/dist/lib/consumers/stream-success-audio.service.d.ts +3 -1
- package/dist/lib/consumers/stream-success-video.service.d.ts +5 -1
- package/dist/lib/directives/with-override.directive.d.ts +76 -0
- package/dist/lib/methods/utils/initial-values.util.d.ts +7 -2
- package/dist/lib/methods/utils/mini-audio-player/mini-audio-player.component.d.ts +3 -1
- package/dist/lib/methods/utils/producer/a-params.service.d.ts +4 -1
- package/dist/lib/methods/utils/producer/h-params.service.d.ts +4 -1
- package/dist/lib/methods/utils/producer/screen-params.service.d.ts +4 -1
- package/dist/lib/methods/utils/producer/v-params.service.d.ts +4 -1
- package/dist/lib/methods/whiteboard-methods/capture-canvas-stream.service.d.ts +3 -1
- package/dist/lib/producer-client/producer-client-emits/create-device-client.service.d.ts +4 -1
- package/dist/lib/producer-client/producer-client-emits/update-room-parameters-client.service.d.ts +3 -1
- package/dist/lib/producers/producer-emits/join-con-room.service.d.ts +3 -1
- package/dist/lib/producers/socket-receive-methods/get-domains.service.d.ts +3 -1
- package/dist/lib/services/ui-override-resolver.service.d.ts +91 -0
- package/dist/public-api.d.ts +4 -0
- package/package.json +2 -2
package/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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,20 +74,359 @@ 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
|
+
- [Quick Reference: Component Props & UI Overrides](#quick-reference-component-props--ui-overrides)
|
|
66
84
|
- [Features](#features)
|
|
67
85
|
- [Getting Started](#getting-started)
|
|
68
|
-
- [
|
|
69
|
-
- [
|
|
70
|
-
- [
|
|
71
|
-
- [
|
|
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)
|
|
75
96
|
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Quick Reference: Component Props & UI Overrides
|
|
101
|
+
|
|
102
|
+
> **New:** UI override parity now extends across Webinar and Chat layouts, unifying customization for every MediaSFU interface.
|
|
103
|
+
|
|
104
|
+
Every primary MediaSFU UI export—`MediasfuGeneric`, `MediasfuBroadcast`, `MediasfuConference`, `MediasfuWebinar`, and `MediasfuChat`—now ships with a consistent prop surface and a powerful `uiOverrides` input, so you can bend the bundled experience to match your product without losing MediaSFU's hardened real-time logic.
|
|
105
|
+
|
|
106
|
+
### Shared component inputs (applies to every MediaSFU UI component)
|
|
107
|
+
|
|
108
|
+
| Input | Type | Default | What it does |
|
|
109
|
+
| --- | --- | --- | --- |
|
|
110
|
+
| `PrejoinPage` | `ComponentType` | `WelcomePage` | Swap in a custom pre-join experience. Receives unified pre-join options so you can add branding, legal copy, or warm-up flows. |
|
|
111
|
+
| `localLink` | `string` | `""` | Point the SDK at your self-hosted MediaSFU server. Leave empty when using MediaSFU Cloud. |
|
|
112
|
+
| `connectMediaSFU` | `boolean` | `true` | Toggle automatic socket/WebRTC connections. Set to `false` when you only need the UI shell. |
|
|
113
|
+
| `credentials` | `{ apiUserName: string; apiKey: string }` | `{ apiUserName: "", apiKey: "" }` | Supply cloud credentials without hard-coding them elsewhere. |
|
|
114
|
+
| `useLocalUIMode` | `boolean` | `false` | Run the interface in local/demo mode with no remote signaling. |
|
|
115
|
+
| `seedData`, `useSeed` | `SeedData`, `boolean` | `{}`, `false` | Pre-populate the UI for demos, snapshot tests, or onboarding tours. |
|
|
116
|
+
| `imgSrc` | `string` | `https://mediasfu.com/images/logo192.png` | Default artwork used across pre-join and modal flows. |
|
|
117
|
+
| `sourceParameters` | `Record<string, unknown>` | `undefined` | Shared helper bag (media devices, participant helpers, layout handlers). Pair with `updateSourceParameters` to mirror the SDK's internal utilities. |
|
|
118
|
+
| `updateSourceParameters` | `EventEmitter` | `undefined` | Receive the latest helper bundle so you can bridge MediaSFU logic into your own components. |
|
|
119
|
+
| `returnUI` | `boolean` | `true` | When `false`, mount the logic only—a perfect stepping stone to a fully bespoke interface. |
|
|
120
|
+
| `noUIPreJoinOptions` | `CreateJoinRoomParameters \| JoinLocalEventRoomParameters` | `undefined` | Feed pre-join data when `returnUI` is `false` and you want to bypass the on-screen wizard. |
|
|
121
|
+
| `joinRoom`, `createRoom` | `Function` | `undefined` | Inject your own networking layers for joining or creating rooms. |
|
|
122
|
+
| `customComponent` | `ComponentType` | `undefined` | Replace the entire UI while retaining transports, sockets, and helpers. |
|
|
123
|
+
| `customVideoCard`, `customAudioCard`, `customMiniCard` | `ComponentType` | `undefined` | Override participant card renders to add metadata, CTAs, or badges. |
|
|
124
|
+
| `[customStyles]` | `Record<string, any>` | `undefined` | Apply inline styles to the root wrapper (dashboards, split views, etc.). |
|
|
125
|
+
| `[uiOverrides]` | `MediasfuUICustomOverrides` | `undefined` | Targeted component/function overrides described below. |
|
|
126
|
+
|
|
127
|
+
> **Power combo:** Set `returnUI="false"` to run MediaSFU logic headless, capture helpers via `updateSourceParameters` output, and selectively bring UI pieces back with `uiOverrides`. That gives you progressive migration with minimal code churn.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import type { MediasfuUICustomOverrides } from 'mediasfu-angular';
|
|
131
|
+
|
|
132
|
+
const overrides: MediasfuUICustomOverrides = { /* ... */ };
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Bring the types into your project to unlock full IntelliSense for every override slot.
|
|
136
|
+
|
|
137
|
+
### Custom UI Playbook
|
|
138
|
+
|
|
139
|
+
Use a toggle-driven "playbook" component to experiment with MediaSFU's customization layers. Flip a couple of booleans and you can watch the SDK jump between prebuilt layouts, headless logic, or a fully bespoke workspace driven by `customComponent`.
|
|
140
|
+
|
|
141
|
+
#### What the playbook demonstrates
|
|
142
|
+
|
|
143
|
+
- **Connection presets**: toggle `connectionScenario` between `cloud`, `hybrid`, or `ce` to swap credentials, local links, and connection modes in one place.
|
|
144
|
+
- **Experience selector**: the `selectedExperience` switch renders `MediasfuGeneric`, `MediasfuBroadcast`, `MediasfuWebinar`, `MediasfuConference`, or `MediasfuChat` without touching the rest of your stack.
|
|
145
|
+
- **UI strategy flags**: booleans like `showPrebuiltUI`, `enableFullCustomUI`, and `enableNoUIPreJoin` demonstrate how to run the MediaSFU logic with or without the bundled UI.
|
|
146
|
+
- **Layered overrides**: toggles enable the custom video/audio/mini cards, drop-in `uiOverrides` for layout and modal surfaces, container styling, and backend proxy helpers.
|
|
147
|
+
- **Custom workspace demo**: a `customComponent` receives live MediaSFU helpers so you can build dashboards, CRM surfaces, or any bespoke host interface.
|
|
148
|
+
- **Debug panel & helpers**: optional JSON panel exposes the `updateSourceParameters` payload so you can see exactly what to wire into your own components.
|
|
149
|
+
|
|
150
|
+
#### Try it quickly
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
@Component({
|
|
154
|
+
selector: 'app-custom-ui-playbook',
|
|
155
|
+
template: `
|
|
156
|
+
<ng-container [ngSwitch]="selectedExperience">
|
|
157
|
+
<app-mediasfu-generic *ngSwitchCase="'generic'"
|
|
158
|
+
[credentials]="currentPreset.credentials"
|
|
159
|
+
[localLink]="currentPreset.localLink"
|
|
160
|
+
[connectMediaSFU]="currentPreset.connectMediaSFU"
|
|
161
|
+
[returnUI]="showPrebuiltUI"
|
|
162
|
+
[uiOverrides]="overrides"
|
|
163
|
+
[customStyles]="containerStyles">
|
|
164
|
+
</app-mediasfu-generic>
|
|
165
|
+
|
|
166
|
+
<app-mediasfu-broadcast *ngSwitchCase="'broadcast'"
|
|
167
|
+
[credentials]="currentPreset.credentials"
|
|
168
|
+
[localLink]="currentPreset.localLink"
|
|
169
|
+
[connectMediaSFU]="currentPreset.connectMediaSFU"
|
|
170
|
+
[returnUI]="showPrebuiltUI"
|
|
171
|
+
[uiOverrides]="overrides">
|
|
172
|
+
</app-mediasfu-broadcast>
|
|
173
|
+
|
|
174
|
+
<!-- Similar for webinar, conference, chat -->
|
|
175
|
+
</ng-container>
|
|
176
|
+
`
|
|
177
|
+
})
|
|
178
|
+
export class CustomUIPlaybookComponent {
|
|
179
|
+
connectionScenario: 'cloud' | 'hybrid' | 'ce' = 'cloud';
|
|
180
|
+
selectedExperience: 'generic' | 'broadcast' | 'webinar' | 'conference' | 'chat' = 'generic';
|
|
181
|
+
showPrebuiltUI = true;
|
|
182
|
+
enableFullCustomUI = false;
|
|
183
|
+
|
|
184
|
+
connectionPresets = {
|
|
185
|
+
cloud: {
|
|
186
|
+
credentials: { apiUserName: 'demo', apiKey: 'demo' },
|
|
187
|
+
localLink: '',
|
|
188
|
+
connectMediaSFU: true
|
|
189
|
+
},
|
|
190
|
+
hybrid: {
|
|
191
|
+
credentials: { apiUserName: 'demo', apiKey: 'demo' },
|
|
192
|
+
localLink: 'http://localhost:3000',
|
|
193
|
+
connectMediaSFU: true
|
|
194
|
+
},
|
|
195
|
+
ce: {
|
|
196
|
+
credentials: undefined,
|
|
197
|
+
localLink: 'http://localhost:3000',
|
|
198
|
+
connectMediaSFU: false
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
get currentPreset() {
|
|
203
|
+
return this.connectionPresets[this.connectionScenario];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
overrides: MediasfuUICustomOverrides = {
|
|
207
|
+
mainContainer: this.enableFullCustomUI ? {
|
|
208
|
+
component: CustomMainContainerComponent
|
|
209
|
+
} : undefined,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
containerStyles = {
|
|
213
|
+
background: 'linear-gradient(135deg, #0f172a, #1e3a8a)',
|
|
214
|
+
minHeight: '100vh'
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Toggle the configuration values at the top of the playbook and watch the UI reconfigure instantly. It's the fastest path to understand MediaSFU's override surface before you fold the patterns into your production entrypoint.
|
|
220
|
+
|
|
221
|
+
#### Passing custom props and UI overrides
|
|
222
|
+
|
|
223
|
+
Use the same playbook to validate bespoke cards, override bundles, and fully custom workspaces before you move them into production code:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
@Component({
|
|
227
|
+
selector: 'app-advanced-playbook',
|
|
228
|
+
template: `
|
|
229
|
+
<app-mediasfu-generic
|
|
230
|
+
[credentials]="credentials"
|
|
231
|
+
[customVideoCard]="videoCard"
|
|
232
|
+
[customAudioCard]="audioCard"
|
|
233
|
+
[customMiniCard]="miniCard"
|
|
234
|
+
[customComponent]="enableFullCustomUI ? customWorkspace : undefined"
|
|
235
|
+
[customStyles]="containerStyles"
|
|
236
|
+
[uiOverrides]="uiOverrides">
|
|
237
|
+
</app-mediasfu-generic>
|
|
238
|
+
`
|
|
239
|
+
})
|
|
240
|
+
export class AdvancedPlaybookComponent {
|
|
241
|
+
credentials = { apiUserName: 'demo', apiKey: 'demo' };
|
|
242
|
+
enableFullCustomUI = false;
|
|
243
|
+
|
|
244
|
+
// Custom card components with themed styling
|
|
245
|
+
videoCard = VideoCardComponent; // Pass component class
|
|
246
|
+
audioCard = AudioCardComponent;
|
|
247
|
+
miniCard = MiniCardComponent;
|
|
248
|
+
customWorkspace = CustomWorkspaceComponent;
|
|
249
|
+
|
|
250
|
+
containerStyles = {
|
|
251
|
+
background: '#0f172a',
|
|
252
|
+
borderRadius: '32px',
|
|
253
|
+
overflow: 'hidden'
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
uiOverrides: MediasfuUICustomOverrides = {
|
|
257
|
+
mainContainer: {
|
|
258
|
+
component: CustomMainContainerComponent
|
|
259
|
+
},
|
|
260
|
+
menuModal: {
|
|
261
|
+
component: CustomMenuModalComponent
|
|
262
|
+
},
|
|
263
|
+
consumerResume: {
|
|
264
|
+
wrap: (original) => async (params) => {
|
|
265
|
+
const startedAt = performance.now();
|
|
266
|
+
const result = await original(params);
|
|
267
|
+
console.log('consumer_resume', {
|
|
268
|
+
durationMs: performance.now() - startedAt,
|
|
269
|
+
consumerId: params?.consumer?.id,
|
|
270
|
+
});
|
|
271
|
+
return result;
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Because the playbook surfaces `updateSourceParameters`, you can also log or snapshot the helper bundle (`getParticipantMedia`, `toggleMenuModal`, `showAlert`, and more) to ensure your custom UI always receives the hooks it expects.
|
|
279
|
+
|
|
280
|
+
### `uiOverrides` input — override keys at a glance
|
|
281
|
+
|
|
282
|
+
Each key accepts a `CustomComponentOverride<Props>` or `CustomFunctionOverride<Fn>` object with optional `component` and `wrap` fields. You can fully replace the default implementation or wrap it while forwarding props.
|
|
283
|
+
|
|
284
|
+
#### Layout & control surfaces
|
|
285
|
+
|
|
286
|
+
| Key | Default component | Typical use |
|
|
287
|
+
| --- | --- | --- |
|
|
288
|
+
| `mainContainer` | `MainContainerComponent` | Inject theming providers or dashboard layouts. |
|
|
289
|
+
| `mainAspect` | `MainAspectComponent` | Tune how the main region splits space. |
|
|
290
|
+
| `mainScreen` | `MainScreenComponent` | Orchestrate hero video + gallery interplay. |
|
|
291
|
+
| `mainGrid` | `MainGridComponent` | Modify layout or layering of primary participants. |
|
|
292
|
+
| `subAspect` | `SubAspectComponent` | Restyle fixed control strips in webinar/conference modes. |
|
|
293
|
+
| `otherGrid` | `OtherGridComponent` | Change presentation of off-stage attendees. |
|
|
294
|
+
| `flexibleGrid`, `flexibleGridAlt` | `FlexibleGrid` | Implement AI-driven or branded array layouts. |
|
|
295
|
+
| `flexibleVideo` | `FlexibleVideo` | Add captions, watermarks, or overlays to highlighted speakers. |
|
|
296
|
+
| `audioGrid` | `AudioGrid` | Customise audio-only attendee presentation. |
|
|
297
|
+
| `pagination` | `Pagination` | Introduce infinite scroll or auto-cycling carousels. |
|
|
298
|
+
| `controlButtons` | `ControlButtonsComponent` | Rebrand the primary action bar. |
|
|
299
|
+
| `controlButtonsAlt` | `ControlButtonsAltComponent` | Control secondary button clusters. |
|
|
300
|
+
| `controlButtonsTouch` | `ControlButtonsComponentTouch` | Deliver mobile-first controls (used heavily by `MediasfuChat`). |
|
|
301
|
+
|
|
302
|
+
#### Participant cards & widgets
|
|
303
|
+
|
|
304
|
+
| Key | Default component | Typical use |
|
|
305
|
+
| --- | --- | --- |
|
|
306
|
+
| `videoCard` | `VideoCard` | Add host badges, reactions, or CRM overlays. |
|
|
307
|
+
| `audioCard` | `AudioCard` | Swap avatars or expose spoken-language info. |
|
|
308
|
+
| `miniCard` | `MiniCard` | Customize thumbnails in picture-in-picture modes. |
|
|
309
|
+
| `miniAudio` | `MiniAudio` | Re-style the audio-only mini indicators. |
|
|
310
|
+
| `meetingProgressTimer` | `MeetingProgressTimer` | Replace the elapsed-time widget with countdowns or milestones. |
|
|
311
|
+
| `miniAudioPlayer` | `MiniAudioPlayer` | Provide alternative UI for recorded clip playback. |
|
|
312
|
+
|
|
313
|
+
#### Modals, dialogs, and collaboration surfaces
|
|
314
|
+
|
|
315
|
+
| Key | Default component | Typical use |
|
|
316
|
+
| --- | --- | --- |
|
|
317
|
+
| `loadingModal` | `LoadingModal` | Show branded skeletons while connecting. |
|
|
318
|
+
| `alert` | `AlertComponent` | Route alerts through your notification system. |
|
|
319
|
+
| `menuModal` | `MenuModal` | Redesign quick-action trays. |
|
|
320
|
+
| `eventSettingsModal` | `EventSettingsModal` | Extend host tools with your own settings. |
|
|
321
|
+
| `requestsModal` | `RequestsModal` | Build moderation queues tailored to your workflows. |
|
|
322
|
+
| `waitingRoomModal` | `WaitingRoomModal` | Deliver custom waiting-room experiences. |
|
|
323
|
+
| `coHostModal` | `CoHostModal` | Manage co-hosts with bespoke UX. |
|
|
324
|
+
| `mediaSettingsModal` | `MediaSettingsModal` | Embed device tests or instructions. |
|
|
325
|
+
| `participantsModal` | `ParticipantsModal` | Introduce advanced filters, search, or notes. |
|
|
326
|
+
| `messagesModal` | `MessagesModal` | Drop in your full-featured chat module. |
|
|
327
|
+
| `displaySettingsModal` | `DisplaySettingsModal` | Let users pick layouts, themes, or captions. |
|
|
328
|
+
| `confirmExitModal` | `ConfirmExitModal` | Meet compliance wording requirements. |
|
|
329
|
+
| `confirmHereModal` | `ConfirmHereModal` | Customize attendance confirmations for webinars. |
|
|
330
|
+
| `shareEventModal` | `ShareEventModal` | Add referral codes or QR sharing. |
|
|
331
|
+
| `recordingModal` | `RecordingModal` | Tailor recording confirmation flows. |
|
|
332
|
+
| `pollModal` | `PollModal` | Integrate your polling/quiz engine. |
|
|
333
|
+
| `backgroundModal` | `BackgroundModal` | Hook AI background replacement or brand presets. |
|
|
334
|
+
| `breakoutRoomsModal` | `BreakoutRoomsModal` | Implement drag-and-drop or AI room suggestions. |
|
|
335
|
+
| `configureWhiteboardModal` | `ConfigureWhiteboardModal` | Adjust collaboration permissions before launch. |
|
|
336
|
+
| `whiteboard` | `Whiteboard` | Replace with your whiteboard provider. |
|
|
337
|
+
| `screenboard` | `Screenboard` | Modify shared-screen annotation layers. |
|
|
338
|
+
| `screenboardModal` | `ScreenboardModal` | Reimagine how users enable shared annotations. |
|
|
339
|
+
|
|
340
|
+
#### Entry flows & custom renderers
|
|
341
|
+
|
|
342
|
+
| Key | Default component | Typical use |
|
|
343
|
+
| --- | --- | --- |
|
|
344
|
+
| `welcomePage` | `WelcomePage` | Provide a fully branded welcome/marketing splash. |
|
|
345
|
+
| `preJoinPage` | `PrejoinPage` | Override the wizard used before joining live sessions. |
|
|
346
|
+
| `customMenuButtonsRenderer` | `ControlButtonsAltComponent` | Supply a bespoke renderer for menu button groups without overriding each button. |
|
|
347
|
+
|
|
348
|
+
#### Function overrides
|
|
349
|
+
|
|
350
|
+
| Key | Default function | Typical use |
|
|
351
|
+
| --- | --- | --- |
|
|
352
|
+
| `consumerResume` | `consumerResume` | Wrap errors, capture analytics, or rate-limit consumer resume behavior. |
|
|
353
|
+
| `addVideosGrid` | `addVideosGrid` | Replace participant ordering or layout heuristics on the fly. |
|
|
354
|
+
| `prepopulateUserMedia` | `prepopulateUserMedia` | Customize initial media setup with custom video/audio/mini cards. |
|
|
355
|
+
|
|
356
|
+
> Function overrides support `{ implementation, wrap }`. Provide `implementation` for a full replacement, or `wrap` to intercept the default behavior before/after it runs.
|
|
357
|
+
|
|
358
|
+
### Example: swap the chat modal and theme the controls
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import { Component } from '@angular/core';
|
|
362
|
+
import { MediasfuGeneric } from 'mediasfu-angular';
|
|
363
|
+
import { MyChatModalComponent } from './ui/my-chat-modal.component';
|
|
364
|
+
import { MyControlsComponent } from './ui/my-controls.component';
|
|
365
|
+
|
|
366
|
+
@Component({
|
|
367
|
+
selector: 'app-my-meeting',
|
|
368
|
+
template: `
|
|
369
|
+
<app-mediasfu-generic
|
|
370
|
+
[credentials]="credentials"
|
|
371
|
+
[uiOverrides]="uiOverrides">
|
|
372
|
+
</app-mediasfu-generic>
|
|
373
|
+
`
|
|
374
|
+
})
|
|
375
|
+
export class MyMeetingComponent {
|
|
376
|
+
credentials = { apiUserName: 'your-api-user', apiKey: 'your-api-key' };
|
|
377
|
+
|
|
378
|
+
uiOverrides = {
|
|
379
|
+
messagesModal: {
|
|
380
|
+
component: MyChatModalComponent,
|
|
381
|
+
},
|
|
382
|
+
controlButtons: {
|
|
383
|
+
component: MyControlsComponent,
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Example: wrap a MediaSFU helper instead of replacing it
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { Component } from '@angular/core';
|
|
393
|
+
import { MediasfuConference } from 'mediasfu-angular';
|
|
394
|
+
|
|
395
|
+
@Component({
|
|
396
|
+
selector: 'app-analytics-meeting',
|
|
397
|
+
template: `
|
|
398
|
+
<app-mediasfu-conference
|
|
399
|
+
[credentials]="credentials"
|
|
400
|
+
[uiOverrides]="uiOverrides">
|
|
401
|
+
</app-mediasfu-conference>
|
|
402
|
+
`
|
|
403
|
+
})
|
|
404
|
+
export class AnalyticsMeetingComponent {
|
|
405
|
+
credentials = { apiUserName: 'your-api-user', apiKey: 'your-api-key' };
|
|
406
|
+
|
|
407
|
+
uiOverrides = {
|
|
408
|
+
consumerResume: {
|
|
409
|
+
wrap: (original) => async (params) => {
|
|
410
|
+
const startedAt = performance.now();
|
|
411
|
+
const result = await original(params);
|
|
412
|
+
|
|
413
|
+
// Send analytics
|
|
414
|
+
this.analytics.track('consumer_resume', {
|
|
415
|
+
durationMs: performance.now() - startedAt,
|
|
416
|
+
consumerId: params?.consumer?.id,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
return result;
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
constructor(private analytics: AnalyticsService) {}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
76
430
|
# Features <a name="features"></a>
|
|
77
431
|
|
|
78
432
|
MediaSFU's Angular SDK comes with a host of powerful features out of the box:
|
|
@@ -88,6 +442,74 @@ MediaSFU's Angular SDK comes with a host of powerful features out of the box:
|
|
|
88
442
|
9. **Cloud Recording (track-based)**: Customize recordings with track-based options, including watermarks, name tags, background colors, and more.
|
|
89
443
|
10. **Managed Events**: Manage events with features to handle abandoned and inactive participants, as well as enforce time and capacity limits.
|
|
90
444
|
|
|
445
|
+
## 🆕 **New Advanced Media Access**
|
|
446
|
+
|
|
447
|
+
The Angular SDK now includes powerful utility methods for fine-grained control over media devices and participant streams:
|
|
448
|
+
|
|
449
|
+
### **`getMediaDevicesList`** - Device Enumeration
|
|
450
|
+
|
|
451
|
+
Enumerate available cameras and microphones with automatic permission handling:
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// Get all available cameras
|
|
455
|
+
const cameras = await sourceParameters.getMediaDevicesList('videoinput');
|
|
456
|
+
cameras.forEach(camera => {
|
|
457
|
+
console.log(`Camera: ${camera.label} (${camera.deviceId})`);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// Get all available microphones
|
|
461
|
+
const microphones = await sourceParameters.getMediaDevicesList('audioinput');
|
|
462
|
+
microphones.forEach(mic => {
|
|
463
|
+
console.log(`Microphone: ${mic.label} (${mic.deviceId})`);
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Use Cases:**
|
|
468
|
+
- Build custom device selection interfaces
|
|
469
|
+
- Detect available media hardware
|
|
470
|
+
- Switch between multiple cameras/microphones
|
|
471
|
+
- Pre-flight device checks before joining
|
|
472
|
+
|
|
473
|
+
[See full documentation and examples →](#media-device-and-stream-utility-methods)
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
### **`getParticipantMedia`** - Stream Access
|
|
478
|
+
|
|
479
|
+
Retrieve specific participant's video or audio streams by ID or name:
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
// Get participant video stream by producer ID
|
|
483
|
+
const videoStream = await sourceParameters.getParticipantMedia({
|
|
484
|
+
id: 'producer-123',
|
|
485
|
+
kind: 'video'
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Get participant audio stream by name
|
|
489
|
+
const audioStream = await sourceParameters.getParticipantMedia({
|
|
490
|
+
name: 'John Doe',
|
|
491
|
+
kind: 'audio'
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// Use the stream (e.g., attach to video element)
|
|
495
|
+
if (videoStream) {
|
|
496
|
+
videoElement.srcObject = videoStream;
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Use Cases:**
|
|
501
|
+
- Monitor specific participant streams
|
|
502
|
+
- Create custom video layouts with individual control
|
|
503
|
+
- Build stream recording features
|
|
504
|
+
- Implement advanced audio/video processing
|
|
505
|
+
- Create picture-in-picture views for specific users
|
|
506
|
+
|
|
507
|
+
[See full documentation and examples →](#media-device-and-stream-utility-methods)
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
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.
|
|
512
|
+
|
|
91
513
|
# Getting Started <a name="getting-started"></a>
|
|
92
514
|
|
|
93
515
|
This section will guide users through the initial setup and installation of the npm module.
|
|
@@ -180,153 +602,1475 @@ If you plan to self-host MediaSFU or use it without MediaSFU Cloud services, you
|
|
|
180
602
|
This setup allows full flexibility and customization while bypassing the need for cloud-dependent credentials.
|
|
181
603
|
|
|
182
604
|
|
|
183
|
-
#
|
|
605
|
+
# 📘 Angular SDK Guide <a name="angular-sdk-guide"></a>
|
|
606
|
+
|
|
607
|
+
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.
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## Quick Start (5 Minutes) <a name="quick-start-5-minutes"></a>
|
|
612
|
+
|
|
613
|
+
Get your first MediaSFU app running in just a few minutes.
|
|
614
|
+
|
|
615
|
+
### Step 1: Install the Package
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
npm install mediasfu-angular
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Step 2: Import and Use
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
// app.component.ts
|
|
625
|
+
import { Component } from '@angular/core';
|
|
626
|
+
import { MediasfuGeneric } from 'mediasfu-angular';
|
|
627
|
+
|
|
628
|
+
@Component({
|
|
629
|
+
selector: 'app-root',
|
|
630
|
+
standalone: true,
|
|
631
|
+
imports: [MediasfuGeneric],
|
|
632
|
+
template: `<app-mediasfu-generic></app-mediasfu-generic>`,
|
|
633
|
+
})
|
|
634
|
+
export class AppComponent { }
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
**Alternative with Credentials:**
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
import { Component } from '@angular/core';
|
|
641
|
+
import { MediasfuGeneric, PreJoinPage } from 'mediasfu-angular';
|
|
642
|
+
|
|
643
|
+
@Component({
|
|
644
|
+
selector: 'app-root',
|
|
645
|
+
standalone: true,
|
|
646
|
+
imports: [MediasfuGeneric],
|
|
647
|
+
template: `
|
|
648
|
+
<app-mediasfu-generic
|
|
649
|
+
[PrejoinPage]="PreJoinPage"
|
|
650
|
+
[credentials]="credentials">
|
|
651
|
+
</app-mediasfu-generic>
|
|
652
|
+
`,
|
|
653
|
+
})
|
|
654
|
+
export class AppComponent {
|
|
655
|
+
PreJoinPage = PreJoinPage;
|
|
656
|
+
credentials = {
|
|
657
|
+
apiUserName: 'your_username',
|
|
658
|
+
apiKey: 'your_api_key',
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Step 3: Run Your App
|
|
664
|
+
|
|
665
|
+
```bash
|
|
666
|
+
ng serve
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
**That's it!** You now have a fully functional video conferencing app with:
|
|
670
|
+
- ✅ Video and audio streaming
|
|
671
|
+
- ✅ Screen sharing
|
|
672
|
+
- ✅ Chat messaging
|
|
673
|
+
- ✅ Participant management
|
|
674
|
+
- ✅ Recording capabilities
|
|
675
|
+
- ✅ Breakout rooms
|
|
676
|
+
- ✅ Polls and whiteboards
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## Understanding MediaSFU Architecture <a name="understanding-mediasfu-architecture"></a>
|
|
681
|
+
|
|
682
|
+
Before diving deeper, let's understand how MediaSFU is structured.
|
|
683
|
+
|
|
684
|
+
### The Three-Layer Architecture
|
|
685
|
+
|
|
686
|
+
```
|
|
687
|
+
┌─────────────────────────────────────────────┐
|
|
688
|
+
│ Your Angular Application │
|
|
689
|
+
│ (components, services, business logic) │
|
|
690
|
+
└─────────────────────────────────────────────┘
|
|
691
|
+
↓
|
|
692
|
+
┌─────────────────────────────────────────────┐
|
|
693
|
+
│ MediaSFU Components Layer │
|
|
694
|
+
│ (MediasfuGeneric, MediasfuBroadcast, etc.) │
|
|
695
|
+
│ - Pre-built UI components │
|
|
696
|
+
│ - Event handling │
|
|
697
|
+
│ - State management (RxJS) │
|
|
698
|
+
└─────────────────────────────────────────────┘
|
|
699
|
+
↓
|
|
700
|
+
┌─────────────────────────────────────────────┐
|
|
701
|
+
│ MediaSFU Core Methods Layer │
|
|
702
|
+
│ (Stream control, room management, │
|
|
703
|
+
│ WebRTC handling, socket communication) │
|
|
704
|
+
└─────────────────────────────────────────────┘
|
|
705
|
+
↓
|
|
706
|
+
┌─────────────────────────────────────────────┐
|
|
707
|
+
│ MediaSFU Backend Services │
|
|
708
|
+
│ (MediaSFU Cloud or Community Edition) │
|
|
709
|
+
└─────────────────────────────────────────────┘
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### Key Concepts
|
|
713
|
+
|
|
714
|
+
#### 1. **Event Room Types**
|
|
715
|
+
|
|
716
|
+
MediaSFU provides 5 specialized room types, each optimized for specific use cases:
|
|
717
|
+
|
|
718
|
+
| Room Type | Best For | Key Features |
|
|
719
|
+
|-----------|----------|--------------|
|
|
720
|
+
| **MediasfuGeneric** | General purpose meetings | Flexible layout, all features enabled |
|
|
721
|
+
| **MediasfuBroadcast** | Live streaming events | Optimized for one-to-many communication |
|
|
722
|
+
| **MediasfuWebinar** | Educational sessions | Presenter focus, Q&A features |
|
|
723
|
+
| **MediasfuConference** | Business meetings | Equal participant layout, collaboration tools |
|
|
724
|
+
| **MediasfuChat** | Interactive discussions | Chat-first interface, quick connections |
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
// Choose the right room type for your use case
|
|
728
|
+
import {
|
|
729
|
+
MediasfuWebinar,
|
|
730
|
+
MediasfuBroadcast,
|
|
731
|
+
MediasfuConference
|
|
732
|
+
} from 'mediasfu-angular';
|
|
733
|
+
|
|
734
|
+
@Component({
|
|
735
|
+
// For a webinar
|
|
736
|
+
template: `<app-mediasfu-webinar [credentials]="credentials"></app-mediasfu-webinar>`,
|
|
737
|
+
// For a broadcast
|
|
738
|
+
// template: `<app-mediasfu-broadcast [credentials]="credentials"></app-mediasfu-broadcast>`,
|
|
739
|
+
// For a conference
|
|
740
|
+
// template: `<app-mediasfu-conference [credentials]="credentials"></app-mediasfu-conference>`,
|
|
741
|
+
})
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
#### 2. **The Three Usage Modes**
|
|
745
|
+
|
|
746
|
+
MediaSFU offers three progressive levels of customization:
|
|
747
|
+
|
|
748
|
+
##### Mode 1: Default UI (Simplest)
|
|
749
|
+
Use MediaSFU's complete pre-built interface - perfect for rapid development.
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
import { Component } from '@angular/core';
|
|
753
|
+
import { MediasfuGeneric } from 'mediasfu-angular';
|
|
754
|
+
|
|
755
|
+
@Component({
|
|
756
|
+
selector: 'app-root',
|
|
757
|
+
standalone: true,
|
|
758
|
+
imports: [MediasfuGeneric],
|
|
759
|
+
template: `<app-mediasfu-generic [credentials]="credentials"></app-mediasfu-generic>`,
|
|
760
|
+
})
|
|
761
|
+
export class AppComponent {
|
|
762
|
+
credentials = { apiUserName: 'username', apiKey: 'key' };
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
**When to use:**
|
|
767
|
+
- ✅ Prototyping or MVP development
|
|
768
|
+
- ✅ Need a production-ready UI quickly
|
|
769
|
+
- ✅ Standard video conferencing features are sufficient
|
|
770
|
+
|
|
771
|
+
##### Mode 2: Custom UI with MediaSFU Backend (Most Flexible)
|
|
772
|
+
Build your own UI while using MediaSFU's powerful backend infrastructure.
|
|
773
|
+
|
|
774
|
+
```typescript
|
|
775
|
+
import { Component, OnInit } from '@angular/core';
|
|
776
|
+
import { MediasfuGeneric } from 'mediasfu-angular';
|
|
777
|
+
|
|
778
|
+
@Component({
|
|
779
|
+
selector: 'app-root',
|
|
780
|
+
standalone: true,
|
|
781
|
+
imports: [MediasfuGeneric, CommonModule],
|
|
782
|
+
template: `
|
|
783
|
+
<app-mediasfu-generic
|
|
784
|
+
[returnUI]="false"
|
|
785
|
+
[sourceParameters]="sourceParameters"
|
|
786
|
+
[updateSourceParameters]="updateSourceParameters.bind(this)"
|
|
787
|
+
[credentials]="credentials"
|
|
788
|
+
[noUIPreJoinOptions]="preJoinOptions">
|
|
789
|
+
</app-mediasfu-generic>
|
|
790
|
+
|
|
791
|
+
<!-- Your custom UI -->
|
|
792
|
+
@if (sourceParameters) {
|
|
793
|
+
<div class="custom-controls">
|
|
794
|
+
<button (click)="toggleVideo()">
|
|
795
|
+
{{ sourceParameters.videoAlreadyOn ? 'Stop Video' : 'Start Video' }}
|
|
796
|
+
</button>
|
|
797
|
+
<button (click)="toggleAudio()">
|
|
798
|
+
{{ sourceParameters.audioAlreadyOn ? 'Mute' : 'Unmute' }}
|
|
799
|
+
</button>
|
|
800
|
+
<button (click)="toggleScreenShare()">
|
|
801
|
+
{{ sourceParameters.screenAlreadyOn ? 'Stop Sharing' : 'Share Screen' }}
|
|
802
|
+
</button>
|
|
803
|
+
</div>
|
|
804
|
+
}
|
|
805
|
+
`,
|
|
806
|
+
})
|
|
807
|
+
export class AppComponent implements OnInit {
|
|
808
|
+
sourceParameters: any = null;
|
|
809
|
+
credentials = { apiUserName: 'username', apiKey: 'key' };
|
|
810
|
+
preJoinOptions = {
|
|
811
|
+
action: 'create',
|
|
812
|
+
userName: 'Your Name',
|
|
813
|
+
capacity: 50,
|
|
814
|
+
duration: 30,
|
|
815
|
+
eventType: 'conference'
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
updateSourceParameters(params: any) {
|
|
819
|
+
this.sourceParameters = params;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
toggleVideo() {
|
|
823
|
+
this.sourceParameters?.clickVideo({ parameters: this.sourceParameters });
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
toggleAudio() {
|
|
827
|
+
this.sourceParameters?.clickAudio({ parameters: this.sourceParameters });
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
toggleScreenShare() {
|
|
831
|
+
this.sourceParameters?.clickScreenShare({ parameters: this.sourceParameters });
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
**When to use:**
|
|
837
|
+
- ✅ Need complete control over UI/UX
|
|
838
|
+
- ✅ Building a custom branded experience
|
|
839
|
+
- ✅ Integrating into existing app design
|
|
840
|
+
|
|
841
|
+
##### Mode 3: Component Replacement (Balanced)
|
|
842
|
+
Replace specific MediaSFU components while keeping the rest of the infrastructure.
|
|
843
|
+
|
|
844
|
+
```typescript
|
|
845
|
+
import { Component } from '@angular/core';
|
|
846
|
+
import {
|
|
847
|
+
MediasfuGeneric,
|
|
848
|
+
FlexibleVideo,
|
|
849
|
+
FlexibleGrid
|
|
850
|
+
} from 'mediasfu-angular';
|
|
851
|
+
|
|
852
|
+
@Component({
|
|
853
|
+
selector: 'app-custom-main',
|
|
854
|
+
standalone: true,
|
|
855
|
+
imports: [FlexibleVideo, FlexibleGrid, CommonModule],
|
|
856
|
+
template: `
|
|
857
|
+
<div class="custom-layout">
|
|
858
|
+
<!-- Custom header -->
|
|
859
|
+
<div class="custom-header">
|
|
860
|
+
<h1>{{ parameters.roomName }}</h1>
|
|
861
|
+
<span>{{ parameters.participants.length }} participants</span>
|
|
862
|
+
</div>
|
|
863
|
+
|
|
864
|
+
<!-- Use MediaSFU's components in your layout -->
|
|
865
|
+
<app-flexible-video
|
|
866
|
+
[customWidth]="windowWidth"
|
|
867
|
+
[customHeight]="600"
|
|
868
|
+
[parameters]="parameters">
|
|
869
|
+
</app-flexible-video>
|
|
870
|
+
|
|
871
|
+
<app-flexible-grid
|
|
872
|
+
[customWidth]="windowWidth"
|
|
873
|
+
[customHeight]="400"
|
|
874
|
+
[parameters]="parameters">
|
|
875
|
+
</app-flexible-grid>
|
|
876
|
+
|
|
877
|
+
<!-- Custom footer -->
|
|
878
|
+
<div class="custom-footer">
|
|
879
|
+
<button (click)="toggleVideo()">
|
|
880
|
+
{{ parameters.videoAlreadyOn ? 'Stop Video' : 'Start Video' }}
|
|
881
|
+
</button>
|
|
882
|
+
</div>
|
|
883
|
+
</div>
|
|
884
|
+
`,
|
|
885
|
+
styles: [`
|
|
886
|
+
.custom-layout {
|
|
887
|
+
display: flex;
|
|
888
|
+
flex-direction: column;
|
|
889
|
+
height: 100vh;
|
|
890
|
+
}
|
|
891
|
+
.custom-header {
|
|
892
|
+
padding: 20px;
|
|
893
|
+
background: #1976d2;
|
|
894
|
+
color: white;
|
|
895
|
+
}
|
|
896
|
+
`]
|
|
897
|
+
})
|
|
898
|
+
export class CustomMainComponent {
|
|
899
|
+
parameters: any;
|
|
900
|
+
windowWidth = window.innerWidth;
|
|
901
|
+
|
|
902
|
+
toggleVideo() {
|
|
903
|
+
this.parameters?.clickVideo({ parameters: this.parameters });
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
@Component({
|
|
908
|
+
selector: 'app-root',
|
|
909
|
+
standalone: true,
|
|
910
|
+
imports: [MediasfuGeneric],
|
|
911
|
+
template: `
|
|
912
|
+
<app-mediasfu-generic
|
|
913
|
+
[credentials]="credentials"
|
|
914
|
+
[PrejoinPage]="PreJoinPage"
|
|
915
|
+
[customComponent]="CustomMainComponent">
|
|
916
|
+
</app-mediasfu-generic>
|
|
917
|
+
`,
|
|
918
|
+
})
|
|
919
|
+
export class AppComponent {
|
|
920
|
+
PreJoinPage = PreJoinPage;
|
|
921
|
+
CustomMainComponent = CustomMainComponent;
|
|
922
|
+
credentials = { apiUserName: 'username', apiKey: 'key' };
|
|
923
|
+
}
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
**When to use:**
|
|
927
|
+
- ✅ Need custom main interface but want to keep MediaSFU's components
|
|
928
|
+
- ✅ Partial customization with minimal effort
|
|
929
|
+
- ✅ Want to maintain MediaSFU's functionality while customizing layout
|
|
930
|
+
|
|
931
|
+
#### 3. **Parameters: Your Control Center**
|
|
932
|
+
|
|
933
|
+
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:
|
|
934
|
+
|
|
935
|
+
```typescript
|
|
936
|
+
// Available in sourceParameters or parameters object
|
|
937
|
+
{
|
|
938
|
+
// Media Controls (Methods)
|
|
939
|
+
clickVideo: (options) => {},
|
|
940
|
+
clickAudio: (options) => {},
|
|
941
|
+
clickScreenShare: (options) => {},
|
|
942
|
+
|
|
943
|
+
// Room State (BehaviorSubject values)
|
|
944
|
+
roomName: 'meeting-123',
|
|
945
|
+
participants: [...],
|
|
946
|
+
allVideoStreams: [...],
|
|
947
|
+
allAudioStreams: [...],
|
|
948
|
+
|
|
949
|
+
// UI State (BehaviorSubject values)
|
|
950
|
+
videoAlreadyOn: false,
|
|
951
|
+
audioAlreadyOn: false,
|
|
952
|
+
screenAlreadyOn: false,
|
|
953
|
+
|
|
954
|
+
// Update Functions (BehaviorSubject next())
|
|
955
|
+
updateVideoAlreadyOn: (value) => {},
|
|
956
|
+
updateAudioAlreadyOn: (value) => {},
|
|
957
|
+
|
|
958
|
+
// And 200+ more properties and methods...
|
|
959
|
+
}
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
**Access patterns:**
|
|
963
|
+
|
|
964
|
+
```typescript
|
|
965
|
+
// In Mode 1 (Default UI): Parameters are managed internally
|
|
966
|
+
// You don't need to access them directly
|
|
967
|
+
|
|
968
|
+
// In Mode 2 (Custom UI): Access via sourceParameters
|
|
969
|
+
sourceParameters?.clickVideo({ parameters: sourceParameters });
|
|
970
|
+
|
|
971
|
+
// In Mode 3 (Component Replacement): Passed to your custom component
|
|
972
|
+
@Component({
|
|
973
|
+
template: `<button (click)="toggleVideo()">Toggle</button>`
|
|
974
|
+
})
|
|
975
|
+
export class CustomComponent {
|
|
976
|
+
@Input() parameters: any;
|
|
977
|
+
|
|
978
|
+
toggleVideo() {
|
|
979
|
+
this.parameters.clickVideo({ parameters: this.parameters });
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Subscribing to reactive state changes
|
|
984
|
+
sourceParameters.participants.subscribe((participants) => {
|
|
985
|
+
console.log('Participants updated:', participants);
|
|
986
|
+
});
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
---
|
|
990
|
+
|
|
991
|
+
## Core Concepts & Components <a name="core-concepts--components"></a>
|
|
992
|
+
|
|
993
|
+
Now that you understand the architecture, let's explore the building blocks.
|
|
994
|
+
|
|
995
|
+
### 1. Display Components: Building Your Video Layout
|
|
996
|
+
|
|
997
|
+
MediaSFU provides powerful components for organizing and displaying media streams.
|
|
998
|
+
|
|
999
|
+
#### Primary Layout Components
|
|
1000
|
+
|
|
1001
|
+
**FlexibleVideo** - Main video display area
|
|
1002
|
+
|
|
1003
|
+
```typescript
|
|
1004
|
+
import { FlexibleVideo } from 'mediasfu-angular';
|
|
1005
|
+
|
|
1006
|
+
@Component({
|
|
1007
|
+
template: `
|
|
1008
|
+
<app-flexible-video
|
|
1009
|
+
[customWidth]="windowWidth"
|
|
1010
|
+
[customHeight]="600"
|
|
1011
|
+
[parameters]="parameters">
|
|
1012
|
+
</app-flexible-video>
|
|
1013
|
+
`
|
|
1014
|
+
})
|
|
1015
|
+
```
|
|
1016
|
+
- Automatically handles main presenter or screen share
|
|
1017
|
+
- Smooth transitions between different video sources
|
|
1018
|
+
- Responsive sizing
|
|
1019
|
+
|
|
1020
|
+
**FlexibleGrid** - Participant grid layout
|
|
1021
|
+
|
|
1022
|
+
```typescript
|
|
1023
|
+
import { FlexibleGrid } from 'mediasfu-angular';
|
|
1024
|
+
|
|
1025
|
+
@Component({
|
|
1026
|
+
template: `
|
|
1027
|
+
<app-flexible-grid
|
|
1028
|
+
[customWidth]="windowWidth"
|
|
1029
|
+
[customHeight]="800"
|
|
1030
|
+
[parameters]="parameters">
|
|
1031
|
+
</app-flexible-grid>
|
|
1032
|
+
`
|
|
1033
|
+
})
|
|
1034
|
+
```
|
|
1035
|
+
- Intelligent grid sizing (2x2, 3x3, 4x4, etc.)
|
|
1036
|
+
- Pagination for large participant lists
|
|
1037
|
+
- Automatic reflow on window resize
|
|
1038
|
+
|
|
1039
|
+
**AudioGrid** - Audio-only participants
|
|
1040
|
+
|
|
1041
|
+
```typescript
|
|
1042
|
+
import { AudioGrid } from 'mediasfu-angular';
|
|
1043
|
+
|
|
1044
|
+
@Component({
|
|
1045
|
+
template: `<app-audio-grid [parameters]="parameters"></app-audio-grid>`
|
|
1046
|
+
})
|
|
1047
|
+
```
|
|
1048
|
+
- Displays participants without video
|
|
1049
|
+
- Audio level indicators
|
|
1050
|
+
- Compact layout for efficiency
|
|
1051
|
+
|
|
1052
|
+
#### Container Components
|
|
1053
|
+
|
|
1054
|
+
| Component | Purpose | Use Case |
|
|
1055
|
+
|-----------|---------|----------|
|
|
1056
|
+
| **MainContainerComponent** | Primary content wrapper | Wraps all main content areas |
|
|
1057
|
+
| **MainAspectComponent** | Aspect ratio container | Maintains proper video proportions |
|
|
1058
|
+
| **MainScreenComponent** | Screen layout manager | Organizes screen regions |
|
|
1059
|
+
| **SubAspectComponent** | Secondary content container | For picture-in-picture, sidebars |
|
|
1060
|
+
|
|
1061
|
+
**Example: Building a custom layout**
|
|
1062
|
+
|
|
1063
|
+
```typescript
|
|
1064
|
+
import { Component } from '@angular/core';
|
|
1065
|
+
import {
|
|
1066
|
+
MainContainerComponent,
|
|
1067
|
+
FlexibleVideo,
|
|
1068
|
+
FlexibleGrid,
|
|
1069
|
+
AudioGrid
|
|
1070
|
+
} from 'mediasfu-angular';
|
|
1071
|
+
|
|
1072
|
+
@Component({
|
|
1073
|
+
selector: 'app-custom-layout',
|
|
1074
|
+
standalone: true,
|
|
1075
|
+
imports: [
|
|
1076
|
+
MainContainerComponent,
|
|
1077
|
+
FlexibleVideo,
|
|
1078
|
+
FlexibleGrid,
|
|
1079
|
+
AudioGrid,
|
|
1080
|
+
CommonModule
|
|
1081
|
+
],
|
|
1082
|
+
template: `
|
|
1083
|
+
<app-main-container-component>
|
|
1084
|
+
<div class="layout-container">
|
|
1085
|
+
<!-- Main video area -->
|
|
1086
|
+
<div class="main-video">
|
|
1087
|
+
<app-flexible-video
|
|
1088
|
+
[customWidth]="windowWidth"
|
|
1089
|
+
[customHeight]="windowHeight * 0.6"
|
|
1090
|
+
[parameters]="parameters">
|
|
1091
|
+
</app-flexible-video>
|
|
1092
|
+
</div>
|
|
1093
|
+
|
|
1094
|
+
<!-- Participant grid -->
|
|
1095
|
+
<div class="participant-grid">
|
|
1096
|
+
<app-flexible-grid
|
|
1097
|
+
[customWidth]="windowWidth"
|
|
1098
|
+
[customHeight]="windowHeight * 0.3"
|
|
1099
|
+
[parameters]="parameters">
|
|
1100
|
+
</app-flexible-grid>
|
|
1101
|
+
</div>
|
|
1102
|
+
|
|
1103
|
+
<!-- Audio-only participants -->
|
|
1104
|
+
<div class="audio-participants">
|
|
1105
|
+
<app-audio-grid [parameters]="parameters"></app-audio-grid>
|
|
1106
|
+
</div>
|
|
1107
|
+
</div>
|
|
1108
|
+
</app-main-container-component>
|
|
1109
|
+
`,
|
|
1110
|
+
styles: [`
|
|
1111
|
+
.layout-container {
|
|
1112
|
+
display: flex;
|
|
1113
|
+
flex-direction: column;
|
|
1114
|
+
height: 100vh;
|
|
1115
|
+
}
|
|
1116
|
+
.main-video { flex: 3; }
|
|
1117
|
+
.participant-grid { flex: 2; }
|
|
1118
|
+
.audio-participants { height: 80px; }
|
|
1119
|
+
`]
|
|
1120
|
+
})
|
|
1121
|
+
export class CustomLayoutComponent {
|
|
1122
|
+
@Input() parameters: any;
|
|
1123
|
+
windowWidth = window.innerWidth;
|
|
1124
|
+
windowHeight = window.innerHeight;
|
|
1125
|
+
}
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
### 2. Control Components: User Interactions
|
|
1129
|
+
|
|
1130
|
+
**ControlButtonsComponent** - Standard control bar
|
|
1131
|
+
|
|
1132
|
+
```typescript
|
|
1133
|
+
import { ControlButtonsComponent } from 'mediasfu-angular';
|
|
1134
|
+
|
|
1135
|
+
@Component({
|
|
1136
|
+
template: `
|
|
1137
|
+
<app-control-buttons-component
|
|
1138
|
+
[parameters]="parameters"
|
|
1139
|
+
[position]="'bottom'">
|
|
1140
|
+
</app-control-buttons-component>
|
|
1141
|
+
`
|
|
1142
|
+
})
|
|
1143
|
+
```
|
|
1144
|
+
Includes: mute, video, screenshare, participants, chat, settings, etc.
|
|
1145
|
+
|
|
1146
|
+
**ControlButtonsAltComponent** - Alternative layout
|
|
1147
|
+
|
|
1148
|
+
```typescript
|
|
1149
|
+
import { ControlButtonsAltComponent } from 'mediasfu-angular';
|
|
1150
|
+
|
|
1151
|
+
@Component({
|
|
1152
|
+
template: `
|
|
1153
|
+
<app-control-buttons-alt-component
|
|
1154
|
+
[parameters]="parameters"
|
|
1155
|
+
[position]="'top'">
|
|
1156
|
+
</app-control-buttons-alt-component>
|
|
1157
|
+
`
|
|
1158
|
+
})
|
|
1159
|
+
```
|
|
1160
|
+
Different button arrangement optimized for specific layouts.
|
|
1161
|
+
|
|
1162
|
+
**ControlButtonsComponentTouch** - Touch-optimized controls
|
|
1163
|
+
|
|
1164
|
+
```typescript
|
|
1165
|
+
import { ControlButtonsComponentTouch } from 'mediasfu-angular';
|
|
1166
|
+
|
|
1167
|
+
@Component({
|
|
1168
|
+
template: `
|
|
1169
|
+
<app-control-buttons-component-touch
|
|
1170
|
+
[parameters]="parameters">
|
|
1171
|
+
</app-control-buttons-component-touch>
|
|
1172
|
+
`
|
|
1173
|
+
})
|
|
1174
|
+
```
|
|
1175
|
+
Floating action buttons optimized for mobile/tablet interfaces.
|
|
1176
|
+
|
|
1177
|
+
### 3. Modal Components: Feature Interfaces
|
|
1178
|
+
|
|
1179
|
+
MediaSFU includes modals for various features:
|
|
1180
|
+
|
|
1181
|
+
```typescript
|
|
1182
|
+
import {
|
|
1183
|
+
ParticipantsModal,
|
|
1184
|
+
MessagesModal,
|
|
1185
|
+
SettingsModal,
|
|
1186
|
+
DisplaySettingsModal,
|
|
1187
|
+
RecordingModal,
|
|
1188
|
+
PollModal,
|
|
1189
|
+
BreakoutRoomsModal
|
|
1190
|
+
} from 'mediasfu-angular';
|
|
1191
|
+
|
|
1192
|
+
// These are automatically rendered when enabled
|
|
1193
|
+
// Control their visibility via parameters
|
|
1194
|
+
parameters.updateIsParticipantsModalVisible.next(true);
|
|
1195
|
+
parameters.updateIsMessagesModalVisible.next(true);
|
|
1196
|
+
parameters.updateIsSettingsModalVisible.next(true);
|
|
1197
|
+
```
|
|
1198
|
+
|
|
1199
|
+
Available modals:
|
|
1200
|
+
- **ParticipantsModal** - Participant list management
|
|
1201
|
+
- **MessagesModal** - Chat interface
|
|
1202
|
+
- **SettingsModal** - Event and room settings
|
|
1203
|
+
- **DisplaySettingsModal** - Layout and display options
|
|
1204
|
+
- **RecordingModal** - Recording controls and settings
|
|
1205
|
+
- **PollModal** - Create and manage polls
|
|
1206
|
+
- **BreakoutRoomsModal** - Breakout room management
|
|
1207
|
+
- **MediaSettingsModal** - Camera/microphone selection
|
|
1208
|
+
- **BackgroundModal** - Virtual background settings
|
|
1209
|
+
- **ConfigureWhiteboardModal** - Whiteboard configuration
|
|
1210
|
+
|
|
1211
|
+
**Example: Programmatically showing modals**
|
|
1212
|
+
|
|
1213
|
+
```typescript
|
|
1214
|
+
@Component({
|
|
1215
|
+
selector: 'app-custom-toolbar',
|
|
1216
|
+
template: `
|
|
1217
|
+
<div class="custom-toolbar">
|
|
1218
|
+
<button (click)="showParticipants()">
|
|
1219
|
+
Show Participants ({{ participantCount }})
|
|
1220
|
+
</button>
|
|
1221
|
+
|
|
1222
|
+
<button (click)="openChat()">
|
|
1223
|
+
Open Chat
|
|
1224
|
+
</button>
|
|
1225
|
+
|
|
1226
|
+
<button (click)="createPoll()">
|
|
1227
|
+
Create Poll
|
|
1228
|
+
</button>
|
|
1229
|
+
</div>
|
|
1230
|
+
`
|
|
1231
|
+
})
|
|
1232
|
+
export class CustomToolbarComponent {
|
|
1233
|
+
@Input() parameters: any;
|
|
1234
|
+
|
|
1235
|
+
get participantCount() {
|
|
1236
|
+
return this.parameters?.participants?.length || 0;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
showParticipants() {
|
|
1240
|
+
this.parameters?.updateIsParticipantsModalVisible.next(true);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
openChat() {
|
|
1244
|
+
this.parameters?.updateIsMessagesModalVisible.next(true);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
createPoll() {
|
|
1248
|
+
this.parameters?.launchPoll?.launchPoll({ parameters: this.parameters });
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
### 4. Video Cards: Individual Participant Display
|
|
1254
|
+
|
|
1255
|
+
**VideoCard** - Individual participant video element
|
|
1256
|
+
|
|
1257
|
+
```typescript
|
|
1258
|
+
import { VideoCard } from 'mediasfu-angular';
|
|
1259
|
+
|
|
1260
|
+
@Component({
|
|
1261
|
+
template: `
|
|
1262
|
+
<app-video-card
|
|
1263
|
+
[videoStream]="participantStream"
|
|
1264
|
+
[remoteProducerId]="'producer-id'"
|
|
1265
|
+
[eventType]="'conference'"
|
|
1266
|
+
[forceFullDisplay]="false"
|
|
1267
|
+
[participant]="participantObject"
|
|
1268
|
+
[backgroundColor]="'#000000'"
|
|
1269
|
+
[showControls]="true"
|
|
1270
|
+
[showInfo]="true"
|
|
1271
|
+
[name]="'Participant Name'"
|
|
1272
|
+
[parameters]="parameters">
|
|
1273
|
+
</app-video-card>
|
|
1274
|
+
`
|
|
1275
|
+
})
|
|
1276
|
+
```
|
|
1277
|
+
|
|
1278
|
+
**AudioCard** - Individual audio-only participant
|
|
1279
|
+
|
|
1280
|
+
```typescript
|
|
1281
|
+
import { AudioCard } from 'mediasfu-angular';
|
|
1282
|
+
|
|
1283
|
+
@Component({
|
|
1284
|
+
template: `
|
|
1285
|
+
<app-audio-card
|
|
1286
|
+
[name]="'Participant Name'"
|
|
1287
|
+
[barColor]="'#4CAF50'"
|
|
1288
|
+
[textColor]="'#FFFFFF'"
|
|
1289
|
+
[customStyle]="{ borderRadius: '10px' }"
|
|
1290
|
+
[controlsPosition]="'topLeft'"
|
|
1291
|
+
[infoPosition]="'topRight'"
|
|
1292
|
+
[participant]="participantObject"
|
|
1293
|
+
[parameters]="parameters">
|
|
1294
|
+
</app-audio-card>
|
|
1295
|
+
`
|
|
1296
|
+
})
|
|
1297
|
+
```
|
|
1298
|
+
|
|
1299
|
+
**MiniCard** - Compact participant display (for grids)
|
|
1300
|
+
|
|
1301
|
+
```typescript
|
|
1302
|
+
import { MiniCard } from 'mediasfu-angular';
|
|
1303
|
+
|
|
1304
|
+
@Component({
|
|
1305
|
+
template: `
|
|
1306
|
+
<app-mini-card
|
|
1307
|
+
[participant]="participantObject"
|
|
1308
|
+
[showControls]="false"
|
|
1309
|
+
[parameters]="parameters">
|
|
1310
|
+
</app-mini-card>
|
|
1311
|
+
`
|
|
1312
|
+
})
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
**Example: Custom Video Card**
|
|
1316
|
+
|
|
1317
|
+
```typescript
|
|
1318
|
+
@Component({
|
|
1319
|
+
selector: 'app-my-custom-video-card',
|
|
1320
|
+
standalone: true,
|
|
1321
|
+
template: `
|
|
1322
|
+
<div class="custom-video-card">
|
|
1323
|
+
<video
|
|
1324
|
+
#videoElement
|
|
1325
|
+
[srcObject]="stream"
|
|
1326
|
+
autoplay
|
|
1327
|
+
[muted]="true"
|
|
1328
|
+
playsinline>
|
|
1329
|
+
</video>
|
|
1330
|
+
|
|
1331
|
+
<div class="participant-info">
|
|
1332
|
+
{{ participant.name }}
|
|
1333
|
+
@if (participant.muted) {
|
|
1334
|
+
<span>🔇</span>
|
|
1335
|
+
}
|
|
1336
|
+
</div>
|
|
1337
|
+
</div>
|
|
1338
|
+
`,
|
|
1339
|
+
styles: [`
|
|
1340
|
+
.custom-video-card {
|
|
1341
|
+
border: 3px solid #00ff88;
|
|
1342
|
+
border-radius: 15px;
|
|
1343
|
+
overflow: hidden;
|
|
1344
|
+
position: relative;
|
|
1345
|
+
}
|
|
1346
|
+
video {
|
|
1347
|
+
width: 100%;
|
|
1348
|
+
height: 100%;
|
|
1349
|
+
object-fit: cover;
|
|
1350
|
+
}
|
|
1351
|
+
.participant-info {
|
|
1352
|
+
position: absolute;
|
|
1353
|
+
bottom: 0;
|
|
1354
|
+
left: 0;
|
|
1355
|
+
right: 0;
|
|
1356
|
+
background: rgba(0, 255, 136, 0.8);
|
|
1357
|
+
color: black;
|
|
1358
|
+
padding: 8px;
|
|
1359
|
+
font-weight: bold;
|
|
1360
|
+
}
|
|
1361
|
+
`]
|
|
1362
|
+
})
|
|
1363
|
+
export class MyCustomVideoCardComponent {
|
|
1364
|
+
@Input() stream!: MediaStream;
|
|
1365
|
+
@Input() participant!: any;
|
|
1366
|
+
@Input() parameters!: any;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// Use it in MediasfuGeneric
|
|
1370
|
+
@Component({
|
|
1371
|
+
template: `
|
|
1372
|
+
<app-mediasfu-generic
|
|
1373
|
+
[credentials]="credentials"
|
|
1374
|
+
[customVideoCard]="CustomVideoCard">
|
|
1375
|
+
</app-mediasfu-generic>
|
|
1376
|
+
`
|
|
1377
|
+
})
|
|
1378
|
+
export class AppComponent {
|
|
1379
|
+
CustomVideoCard = MyCustomVideoCardComponent;
|
|
1380
|
+
credentials = { apiUserName: 'username', apiKey: 'key' };
|
|
1381
|
+
}
|
|
1382
|
+
```
|
|
1383
|
+
|
|
1384
|
+
---
|
|
1385
|
+
|
|
1386
|
+
## Working with Methods <a name="working-with-methods"></a>
|
|
1387
|
+
|
|
1388
|
+
MediaSFU provides 200+ methods for controlling every aspect of your real-time communication experience. Let's explore the most important categories.
|
|
1389
|
+
|
|
1390
|
+
### Media Control Methods
|
|
1391
|
+
|
|
1392
|
+
#### Video Control
|
|
1393
|
+
|
|
1394
|
+
```typescript
|
|
1395
|
+
// Toggle video on/off
|
|
1396
|
+
parameters.clickVideo({ parameters });
|
|
1397
|
+
|
|
1398
|
+
// Switch camera (front/back on mobile)
|
|
1399
|
+
parameters.switchVideoAlt({ parameters });
|
|
1400
|
+
|
|
1401
|
+
// Switch to specific camera by ID
|
|
1402
|
+
const cameras = await parameters.getMediaDevicesList('videoinput');
|
|
1403
|
+
parameters.switchUserVideo({
|
|
1404
|
+
videoPreference: cameras[1].deviceId,
|
|
1405
|
+
parameters
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
// Get current video state
|
|
1409
|
+
const isVideoOn = parameters.videoAlreadyOn;
|
|
1410
|
+
|
|
1411
|
+
// Subscribe to video state changes (RxJS)
|
|
1412
|
+
parameters.videoAlreadyOn.subscribe((isOn: boolean) => {
|
|
1413
|
+
console.log('Video is now:', isOn ? 'ON' : 'OFF');
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
// Update video state programmatically
|
|
1417
|
+
parameters.updateVideoAlreadyOn.next(true);
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
#### Audio Control
|
|
1421
|
+
|
|
1422
|
+
```typescript
|
|
1423
|
+
// Toggle audio on/off
|
|
1424
|
+
parameters.clickAudio({ parameters });
|
|
1425
|
+
|
|
1426
|
+
// Switch microphone
|
|
1427
|
+
const microphones = await parameters.getMediaDevicesList('audioinput');
|
|
1428
|
+
parameters.switchUserAudio({
|
|
1429
|
+
audioPreference: microphones[1].deviceId,
|
|
1430
|
+
parameters
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
// Get current audio state
|
|
1434
|
+
const isAudioOn = parameters.audioAlreadyOn;
|
|
1435
|
+
const hasHostPermission = parameters.micAction; // Host approval status
|
|
1436
|
+
|
|
1437
|
+
// Subscribe to audio state changes
|
|
1438
|
+
parameters.audioAlreadyOn.subscribe((isOn: boolean) => {
|
|
1439
|
+
console.log('Audio is now:', isOn ? 'ON' : 'OFF');
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
// Mute/unmute specific participant (host only)
|
|
1443
|
+
parameters.controlMedia({
|
|
1444
|
+
participantId: 'participant-id',
|
|
1445
|
+
participantName: 'John Doe',
|
|
1446
|
+
type: 'audio',
|
|
1447
|
+
socket: parameters.socket,
|
|
1448
|
+
roomName: parameters.roomName
|
|
1449
|
+
});
|
|
1450
|
+
```
|
|
1451
|
+
|
|
1452
|
+
#### Screen Sharing
|
|
1453
|
+
|
|
1454
|
+
```typescript
|
|
1455
|
+
// Start screen sharing
|
|
1456
|
+
parameters.clickScreenShare({ parameters });
|
|
1457
|
+
|
|
1458
|
+
// Stop screen sharing
|
|
1459
|
+
parameters.stopShareScreen({ parameters });
|
|
1460
|
+
|
|
1461
|
+
// Check if screen sharing is available
|
|
1462
|
+
const canShare = await parameters.checkScreenShare({ parameters });
|
|
1463
|
+
|
|
1464
|
+
// Get screen share state
|
|
1465
|
+
const isSharing = parameters.screenAlreadyOn;
|
|
1466
|
+
const shareAudio = parameters.shareScreenStarted; // Sharing with audio
|
|
1467
|
+
|
|
1468
|
+
// Subscribe to screen share state
|
|
1469
|
+
parameters.screenAlreadyOn.subscribe((isSharing: boolean) => {
|
|
1470
|
+
console.log('Screen sharing:', isSharing ? 'ACTIVE' : 'INACTIVE');
|
|
1471
|
+
});
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
### Media Device and Stream Utility Methods
|
|
1475
|
+
|
|
1476
|
+
#### Get Available Media Devices
|
|
1477
|
+
|
|
1478
|
+
```typescript
|
|
1479
|
+
// Get available cameras
|
|
1480
|
+
const cameras = await parameters.getMediaDevicesList('videoinput');
|
|
1481
|
+
cameras.forEach(camera => {
|
|
1482
|
+
console.log(`Camera: ${camera.label} (${camera.deviceId})`);
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
// Get available microphones
|
|
1486
|
+
const microphones = await parameters.getMediaDevicesList('audioinput');
|
|
1487
|
+
microphones.forEach(mic => {
|
|
1488
|
+
console.log(`Microphone: ${mic.label} (${mic.deviceId})`);
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
// Building a device selector UI
|
|
1492
|
+
@Component({
|
|
1493
|
+
selector: 'app-device-selector',
|
|
1494
|
+
template: `
|
|
1495
|
+
<div class="device-selector">
|
|
1496
|
+
<select (change)="onCameraChange($event)">
|
|
1497
|
+
<option value="">Select Camera</option>
|
|
1498
|
+
@for (camera of cameras; track camera.deviceId) {
|
|
1499
|
+
<option [value]="camera.deviceId">
|
|
1500
|
+
{{ camera.label }}
|
|
1501
|
+
</option>
|
|
1502
|
+
}
|
|
1503
|
+
</select>
|
|
1504
|
+
|
|
1505
|
+
<select (change)="onMicrophoneChange($event)">
|
|
1506
|
+
<option value="">Select Microphone</option>
|
|
1507
|
+
@for (mic of microphones; track mic.deviceId) {
|
|
1508
|
+
<option [value]="mic.deviceId">
|
|
1509
|
+
{{ mic.label }}
|
|
1510
|
+
</option>
|
|
1511
|
+
}
|
|
1512
|
+
</select>
|
|
1513
|
+
</div>
|
|
1514
|
+
`
|
|
1515
|
+
})
|
|
1516
|
+
export class DeviceSelectorComponent implements OnInit {
|
|
1517
|
+
@Input() parameters: any;
|
|
1518
|
+
cameras: MediaDeviceInfo[] = [];
|
|
1519
|
+
microphones: MediaDeviceInfo[] = [];
|
|
1520
|
+
|
|
1521
|
+
async ngOnInit() {
|
|
1522
|
+
await this.loadDevices();
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
async loadDevices() {
|
|
1526
|
+
this.cameras = await this.parameters.getMediaDevicesList('videoinput');
|
|
1527
|
+
this.microphones = await this.parameters.getMediaDevicesList('audioinput');
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
onCameraChange(event: any) {
|
|
1531
|
+
this.parameters.switchUserVideo({
|
|
1532
|
+
videoPreference: event.target.value,
|
|
1533
|
+
parameters: this.parameters
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
onMicrophoneChange(event: any) {
|
|
1538
|
+
this.parameters.switchUserAudio({
|
|
1539
|
+
audioPreference: event.target.value,
|
|
1540
|
+
parameters: this.parameters
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
```
|
|
1545
|
+
|
|
1546
|
+
#### Get Participant Media Streams
|
|
1547
|
+
|
|
1548
|
+
```typescript
|
|
1549
|
+
// Get participant video stream by ID
|
|
1550
|
+
const videoStream = await parameters.getParticipantMedia({
|
|
1551
|
+
id: 'producer-123',
|
|
1552
|
+
kind: 'video'
|
|
1553
|
+
});
|
|
1554
|
+
|
|
1555
|
+
// Get participant audio stream by name
|
|
1556
|
+
const audioStream = await parameters.getParticipantMedia({
|
|
1557
|
+
name: 'John Doe',
|
|
1558
|
+
kind: 'audio'
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
// Example: Custom participant stream monitor
|
|
1562
|
+
@Component({
|
|
1563
|
+
selector: 'app-stream-monitor',
|
|
1564
|
+
template: `
|
|
1565
|
+
<div class="stream-monitor">
|
|
1566
|
+
<h3>Participant Streams</h3>
|
|
1567
|
+
@for (participant of participants; track participant.id) {
|
|
1568
|
+
<div class="participant-item">
|
|
1569
|
+
<span>{{ participant.name }}</span>
|
|
1570
|
+
<button (click)="viewStream(participant)">View Stream</button>
|
|
1571
|
+
<button (click)="monitorAudio(participant)">Monitor Audio</button>
|
|
1572
|
+
</div>
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
@if (selectedStream) {
|
|
1576
|
+
<div class="stream-viewer">
|
|
1577
|
+
<video #streamVideo autoplay playsinline></video>
|
|
1578
|
+
</div>
|
|
1579
|
+
}
|
|
1580
|
+
</div>
|
|
1581
|
+
`,
|
|
1582
|
+
styles: [`
|
|
1583
|
+
.stream-monitor { padding: 20px; }
|
|
1584
|
+
.participant-item {
|
|
1585
|
+
margin: 10px 0;
|
|
1586
|
+
display: flex;
|
|
1587
|
+
gap: 10px;
|
|
1588
|
+
align-items: center;
|
|
1589
|
+
}
|
|
1590
|
+
.stream-viewer video {
|
|
1591
|
+
width: 100%;
|
|
1592
|
+
max-width: 640px;
|
|
1593
|
+
border: 2px solid #1976d2;
|
|
1594
|
+
}
|
|
1595
|
+
`]
|
|
1596
|
+
})
|
|
1597
|
+
export class StreamMonitorComponent {
|
|
1598
|
+
@Input() parameters: any;
|
|
1599
|
+
@ViewChild('streamVideo') videoElement!: ElementRef<HTMLVideoElement>;
|
|
1600
|
+
|
|
1601
|
+
selectedStream: MediaStream | null = null;
|
|
1602
|
+
|
|
1603
|
+
get participants() {
|
|
1604
|
+
return this.parameters?.participants || [];
|
|
1605
|
+
}
|
|
184
1606
|
|
|
185
|
-
|
|
1607
|
+
async viewStream(participant: any) {
|
|
1608
|
+
const stream = await this.parameters.getParticipantMedia({
|
|
1609
|
+
id: participant.videoID,
|
|
1610
|
+
name: participant.name,
|
|
1611
|
+
kind: 'video'
|
|
1612
|
+
});
|
|
186
1613
|
|
|
187
|
-
|
|
188
|
-
|
|
1614
|
+
if (stream && this.videoElement) {
|
|
1615
|
+
this.selectedStream = stream;
|
|
1616
|
+
this.videoElement.nativeElement.srcObject = stream;
|
|
1617
|
+
} else {
|
|
1618
|
+
console.log('No video stream found for participant');
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
189
1621
|
|
|
190
|
-
|
|
1622
|
+
async monitorAudio(participant: any) {
|
|
1623
|
+
const stream = await this.parameters.getParticipantMedia({
|
|
1624
|
+
id: participant.audioID,
|
|
1625
|
+
name: participant.name,
|
|
1626
|
+
kind: 'audio'
|
|
1627
|
+
});
|
|
191
1628
|
|
|
192
|
-
|
|
1629
|
+
if (stream) {
|
|
1630
|
+
// Create audio context for analysis
|
|
1631
|
+
const audioContext = new AudioContext();
|
|
1632
|
+
const analyser = audioContext.createAnalyser();
|
|
1633
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
1634
|
+
source.connect(analyser);
|
|
1635
|
+
|
|
1636
|
+
console.log('Monitoring audio for:', participant.name);
|
|
1637
|
+
// Add your audio analysis logic here
|
|
1638
|
+
} else {
|
|
1639
|
+
console.log('No audio stream found for participant');
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
```
|
|
193
1644
|
|
|
194
|
-
|
|
1645
|
+
### Participant Management Methods
|
|
195
1646
|
|
|
196
|
-
|
|
1647
|
+
```typescript
|
|
1648
|
+
// Get all participants
|
|
1649
|
+
const participants = parameters.participants;
|
|
1650
|
+
const participantCount = parameters.participantsCounter;
|
|
1651
|
+
|
|
1652
|
+
// Subscribe to participant changes
|
|
1653
|
+
parameters.participants.subscribe((participants: any[]) => {
|
|
1654
|
+
console.log('Participants updated:', participants);
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
// Filter participants
|
|
1658
|
+
const videoParticipants = participants.filter((p: any) => p.videoOn);
|
|
1659
|
+
const audioOnlyParticipants = participants.filter((p: any) => !p.videoOn);
|
|
1660
|
+
const mutedParticipants = participants.filter((p: any) => p.muted);
|
|
1661
|
+
|
|
1662
|
+
// Find specific participant
|
|
1663
|
+
const participant = participants.find((p: any) => p.name === 'John Doe');
|
|
1664
|
+
|
|
1665
|
+
// Remove participant from room (host only)
|
|
1666
|
+
parameters.disconnectUserInitiate({
|
|
1667
|
+
member: participantId,
|
|
1668
|
+
roomName: parameters.roomName,
|
|
1669
|
+
socket: parameters.socket
|
|
1670
|
+
});
|
|
1671
|
+
|
|
1672
|
+
// Change participant role (host only)
|
|
1673
|
+
parameters.updateParticipant({
|
|
1674
|
+
participantId: 'participant-id',
|
|
1675
|
+
islevel: '2', // '2' = host, '1' = co-host, '0' = participant
|
|
1676
|
+
parameters
|
|
1677
|
+
});
|
|
1678
|
+
|
|
1679
|
+
// Request to unmute participant (sends request)
|
|
1680
|
+
parameters.requestScreenShare({ parameters });
|
|
1681
|
+
```
|
|
197
1682
|
|
|
198
|
-
|
|
1683
|
+
### Chat & Messaging Methods
|
|
199
1684
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
1685
|
+
```typescript
|
|
1686
|
+
// Send a group message
|
|
1687
|
+
parameters.sendMessage({
|
|
1688
|
+
message: 'Hello everyone!',
|
|
1689
|
+
type: 'group',
|
|
1690
|
+
parameters
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
// Send direct message
|
|
1694
|
+
parameters.sendMessage({
|
|
1695
|
+
message: 'Private message',
|
|
1696
|
+
type: 'direct',
|
|
1697
|
+
receivers: ['participant-id'],
|
|
1698
|
+
parameters
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
// Access message history
|
|
1702
|
+
const messages = parameters.messages;
|
|
1703
|
+
|
|
1704
|
+
// Subscribe to new messages (RxJS)
|
|
1705
|
+
parameters.messages.subscribe((messages: any[]) => {
|
|
1706
|
+
console.log('Messages updated:', messages);
|
|
1707
|
+
});
|
|
1708
|
+
|
|
1709
|
+
// Example: Custom chat component
|
|
1710
|
+
@Component({
|
|
1711
|
+
selector: 'app-custom-chat',
|
|
1712
|
+
standalone: true,
|
|
1713
|
+
imports: [CommonModule, FormsModule],
|
|
1714
|
+
template: `
|
|
1715
|
+
<div class="chat-container">
|
|
1716
|
+
<div class="messages">
|
|
1717
|
+
@for (msg of messages; track msg.timestamp) {
|
|
1718
|
+
<div class="message">
|
|
1719
|
+
<strong>{{ msg.sender }}:</strong> {{ msg.message }}
|
|
1720
|
+
</div>
|
|
1721
|
+
}
|
|
1722
|
+
</div>
|
|
1723
|
+
<div class="input-area">
|
|
1724
|
+
<input
|
|
1725
|
+
[(ngModel)]="message"
|
|
1726
|
+
(keyup.enter)="sendMessage()"
|
|
1727
|
+
placeholder="Type a message..."
|
|
1728
|
+
/>
|
|
1729
|
+
<button (click)="sendMessage()">Send</button>
|
|
1730
|
+
</div>
|
|
1731
|
+
</div>
|
|
1732
|
+
`,
|
|
1733
|
+
styles: [`
|
|
1734
|
+
.chat-container {
|
|
1735
|
+
display: flex;
|
|
1736
|
+
flex-direction: column;
|
|
1737
|
+
height: 400px;
|
|
1738
|
+
}
|
|
1739
|
+
.messages {
|
|
1740
|
+
flex: 1;
|
|
1741
|
+
overflow-y: auto;
|
|
1742
|
+
padding: 10px;
|
|
1743
|
+
}
|
|
1744
|
+
.message {
|
|
1745
|
+
margin: 5px 0;
|
|
1746
|
+
padding: 8px;
|
|
1747
|
+
background: #f5f5f5;
|
|
1748
|
+
border-radius: 4px;
|
|
1749
|
+
}
|
|
1750
|
+
.input-area {
|
|
1751
|
+
display: flex;
|
|
1752
|
+
gap: 10px;
|
|
1753
|
+
padding: 10px;
|
|
1754
|
+
border-top: 1px solid #ddd;
|
|
1755
|
+
}
|
|
1756
|
+
input {
|
|
1757
|
+
flex: 1;
|
|
1758
|
+
padding: 8px;
|
|
1759
|
+
border: 1px solid #ddd;
|
|
1760
|
+
border-radius: 4px;
|
|
1761
|
+
}
|
|
1762
|
+
button {
|
|
1763
|
+
padding: 8px 16px;
|
|
1764
|
+
background: #1976d2;
|
|
1765
|
+
color: white;
|
|
1766
|
+
border: none;
|
|
1767
|
+
border-radius: 4px;
|
|
1768
|
+
cursor: pointer;
|
|
1769
|
+
}
|
|
1770
|
+
`]
|
|
1771
|
+
})
|
|
1772
|
+
export class CustomChatComponent implements OnInit {
|
|
1773
|
+
@Input() parameters: any;
|
|
1774
|
+
message = '';
|
|
1775
|
+
messages: any[] = [];
|
|
205
1776
|
|
|
206
|
-
|
|
1777
|
+
ngOnInit() {
|
|
1778
|
+
// Subscribe to messages
|
|
1779
|
+
this.parameters?.messages?.subscribe((msgs: any[]) => {
|
|
1780
|
+
this.messages = msgs;
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
207
1783
|
|
|
208
|
-
|
|
1784
|
+
sendMessage() {
|
|
1785
|
+
if (this.message.trim()) {
|
|
1786
|
+
this.parameters?.sendMessage({
|
|
1787
|
+
message: this.message,
|
|
1788
|
+
type: 'group',
|
|
1789
|
+
parameters: this.parameters
|
|
1790
|
+
});
|
|
1791
|
+
this.message = '';
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
```
|
|
209
1796
|
|
|
210
|
-
|
|
1797
|
+
### Recording Methods
|
|
211
1798
|
|
|
212
|
-
```
|
|
213
|
-
|
|
1799
|
+
```typescript
|
|
1800
|
+
// Start recording
|
|
1801
|
+
parameters.startRecording({ parameters });
|
|
1802
|
+
|
|
1803
|
+
// Stop recording
|
|
1804
|
+
parameters.stopRecording({ parameters });
|
|
1805
|
+
|
|
1806
|
+
// Pause recording
|
|
1807
|
+
parameters.pauseRecording({ parameters });
|
|
1808
|
+
|
|
1809
|
+
// Resume recording
|
|
1810
|
+
parameters.resumeRecording({ parameters });
|
|
1811
|
+
|
|
1812
|
+
// Configure recording settings
|
|
1813
|
+
parameters.updateRecording({
|
|
1814
|
+
recordingMediaOptions: 'video', // or 'audio'
|
|
1815
|
+
recordingAudioOptions: 'all', // or 'host'
|
|
1816
|
+
recordingVideoOptions: 'all', // or 'host'
|
|
1817
|
+
recordingVideoType: 'fullDisplay', // or 'bestDisplay', 'all'
|
|
1818
|
+
recordingDisplayType: 'video', // 'media', 'video', 'all'
|
|
1819
|
+
recordingBackgroundColor: '#000000',
|
|
1820
|
+
recordingNameTagsColor: '#ffffff',
|
|
1821
|
+
recordingOrientationVideo: 'landscape', // or 'portrait'
|
|
1822
|
+
recordingNameTags: true,
|
|
1823
|
+
recordingAddHLS: false,
|
|
1824
|
+
parameters
|
|
1825
|
+
});
|
|
1826
|
+
|
|
1827
|
+
// Check recording state
|
|
1828
|
+
const isRecording = parameters.recordStarted;
|
|
1829
|
+
const isPaused = parameters.recordPaused;
|
|
1830
|
+
const recordingTime = parameters.recordElapsedTime;
|
|
1831
|
+
|
|
1832
|
+
// Subscribe to recording state changes
|
|
1833
|
+
parameters.recordStarted.subscribe((isRecording: boolean) => {
|
|
1834
|
+
console.log('Recording:', isRecording ? 'ACTIVE' : 'STOPPED');
|
|
1835
|
+
});
|
|
1836
|
+
```
|
|
214
1837
|
|
|
215
|
-
|
|
1838
|
+
### Polls & Surveys Methods
|
|
216
1839
|
|
|
217
1840
|
```typescript
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
1841
|
+
// Create a poll
|
|
1842
|
+
parameters.handleCreatePoll({
|
|
1843
|
+
poll: {
|
|
1844
|
+
question: 'What time works best?',
|
|
1845
|
+
type: 'multiple', // or 'single'
|
|
1846
|
+
options: ['10 AM', '2 PM', '5 PM']
|
|
1847
|
+
},
|
|
1848
|
+
parameters
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
// Vote on a poll
|
|
1852
|
+
parameters.handleVotePoll({
|
|
1853
|
+
pollId: 'poll-id',
|
|
1854
|
+
optionIndex: 1,
|
|
1855
|
+
parameters
|
|
1856
|
+
});
|
|
1857
|
+
|
|
1858
|
+
// End a poll
|
|
1859
|
+
parameters.handleEndPoll({
|
|
1860
|
+
pollId: 'poll-id',
|
|
1861
|
+
parameters
|
|
1862
|
+
});
|
|
1863
|
+
|
|
1864
|
+
// Access poll data
|
|
1865
|
+
const polls = parameters.polls;
|
|
1866
|
+
|
|
1867
|
+
// Subscribe to polls changes
|
|
1868
|
+
parameters.polls.subscribe((polls: any[]) => {
|
|
1869
|
+
const activePoll = polls.find(p => p.status === 'active');
|
|
1870
|
+
console.log('Active poll:', activePoll);
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
// Example: Custom poll component
|
|
222
1874
|
@Component({
|
|
223
|
-
selector: 'app-
|
|
1875
|
+
selector: 'app-custom-poll',
|
|
224
1876
|
standalone: true,
|
|
225
|
-
imports: [
|
|
1877
|
+
imports: [CommonModule],
|
|
226
1878
|
template: `
|
|
227
|
-
|
|
1879
|
+
@if (activePoll) {
|
|
1880
|
+
<div class="poll-container">
|
|
1881
|
+
<h3>{{ activePoll.question }}</h3>
|
|
1882
|
+
<div class="poll-options">
|
|
1883
|
+
@for (option of activePoll.options; track $index) {
|
|
1884
|
+
<button
|
|
1885
|
+
class="poll-option"
|
|
1886
|
+
(click)="vote($index)">
|
|
1887
|
+
{{ option }}
|
|
1888
|
+
<span class="votes">({{ activePoll.votes?.[$index] || 0 }} votes)</span>
|
|
1889
|
+
</button>
|
|
1890
|
+
}
|
|
1891
|
+
</div>
|
|
1892
|
+
@if (isHost) {
|
|
1893
|
+
<button class="end-poll" (click)="endPoll()">
|
|
1894
|
+
End Poll
|
|
1895
|
+
</button>
|
|
1896
|
+
}
|
|
1897
|
+
</div>
|
|
1898
|
+
}
|
|
228
1899
|
`,
|
|
1900
|
+
styles: [`
|
|
1901
|
+
.poll-container {
|
|
1902
|
+
padding: 20px;
|
|
1903
|
+
background: white;
|
|
1904
|
+
border-radius: 8px;
|
|
1905
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
1906
|
+
}
|
|
1907
|
+
.poll-options {
|
|
1908
|
+
display: flex;
|
|
1909
|
+
flex-direction: column;
|
|
1910
|
+
gap: 10px;
|
|
1911
|
+
margin: 20px 0;
|
|
1912
|
+
}
|
|
1913
|
+
.poll-option {
|
|
1914
|
+
padding: 12px;
|
|
1915
|
+
border: 2px solid #1976d2;
|
|
1916
|
+
background: white;
|
|
1917
|
+
border-radius: 4px;
|
|
1918
|
+
cursor: pointer;
|
|
1919
|
+
transition: all 0.3s;
|
|
1920
|
+
display: flex;
|
|
1921
|
+
justify-content: space-between;
|
|
1922
|
+
}
|
|
1923
|
+
.poll-option:hover {
|
|
1924
|
+
background: #1976d2;
|
|
1925
|
+
color: white;
|
|
1926
|
+
}
|
|
1927
|
+
.votes {
|
|
1928
|
+
font-size: 0.9em;
|
|
1929
|
+
opacity: 0.7;
|
|
1930
|
+
}
|
|
1931
|
+
.end-poll {
|
|
1932
|
+
padding: 10px 20px;
|
|
1933
|
+
background: #d32f2f;
|
|
1934
|
+
color: white;
|
|
1935
|
+
border: none;
|
|
1936
|
+
border-radius: 4px;
|
|
1937
|
+
cursor: pointer;
|
|
1938
|
+
}
|
|
1939
|
+
`]
|
|
229
1940
|
})
|
|
230
|
-
export class
|
|
231
|
-
|
|
1941
|
+
export class CustomPollComponent implements OnInit {
|
|
1942
|
+
@Input() parameters: any;
|
|
1943
|
+
activePoll: any = null;
|
|
232
1944
|
|
|
233
|
-
|
|
1945
|
+
ngOnInit() {
|
|
1946
|
+
this.parameters?.polls?.subscribe((polls: any[]) => {
|
|
1947
|
+
this.activePoll = polls.find((p: any) => p.status === 'active');
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
234
1950
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
1951
|
+
get isHost() {
|
|
1952
|
+
return this.parameters?.islevel === '2';
|
|
1953
|
+
}
|
|
242
1954
|
|
|
243
|
-
|
|
1955
|
+
vote(optionIndex: number) {
|
|
1956
|
+
if (this.activePoll) {
|
|
1957
|
+
this.parameters?.handleVotePoll({
|
|
1958
|
+
pollId: this.activePoll.id,
|
|
1959
|
+
optionIndex,
|
|
1960
|
+
parameters: this.parameters
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
244
1964
|
|
|
245
|
-
|
|
246
|
-
|
|
1965
|
+
endPoll() {
|
|
1966
|
+
if (this.activePoll) {
|
|
1967
|
+
this.parameters?.handleEndPoll({
|
|
1968
|
+
pollId: this.activePoll.id,
|
|
1969
|
+
parameters: this.parameters
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
```
|
|
247
1975
|
|
|
248
|
-
###
|
|
249
|
-
Choose from pre-built, production-ready templates:
|
|
1976
|
+
### Breakout Rooms Methods
|
|
250
1977
|
|
|
251
1978
|
```typescript
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
1979
|
+
// Create breakout rooms
|
|
1980
|
+
parameters.createBreakoutRooms({
|
|
1981
|
+
numberOfRooms: 3,
|
|
1982
|
+
participants: parameters.participants,
|
|
1983
|
+
parameters
|
|
1984
|
+
});
|
|
1985
|
+
|
|
1986
|
+
// Assign participant to room
|
|
1987
|
+
parameters.assignParticipantToRoom({
|
|
1988
|
+
participantId: 'participant-id',
|
|
1989
|
+
roomIndex: 0,
|
|
1990
|
+
parameters
|
|
1991
|
+
});
|
|
1992
|
+
|
|
1993
|
+
// Start breakout rooms
|
|
1994
|
+
parameters.startBreakoutRooms({ parameters });
|
|
1995
|
+
|
|
1996
|
+
// Stop breakout rooms
|
|
1997
|
+
parameters.stopBreakoutRooms({ parameters });
|
|
1998
|
+
|
|
1999
|
+
// Access breakout room data
|
|
2000
|
+
const breakoutRooms = parameters.breakoutRooms;
|
|
2001
|
+
const currentRoom = parameters.currentBreakoutRoom;
|
|
2002
|
+
|
|
2003
|
+
// Subscribe to breakout room changes
|
|
2004
|
+
parameters.breakoutRooms.subscribe((rooms: any[]) => {
|
|
2005
|
+
console.log('Breakout rooms:', rooms);
|
|
2006
|
+
});
|
|
259
2007
|
```
|
|
260
2008
|
|
|
261
|
-
###
|
|
262
|
-
|
|
263
|
-
**Replace Any Component:** Don't like our video card? Swap it with yours:
|
|
2009
|
+
### Whiteboard Methods
|
|
264
2010
|
|
|
265
2011
|
```typescript
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
template: `<div class="my-custom-video">{{ participant.name }}</div>`
|
|
270
|
-
})
|
|
271
|
-
export class MyVideoCard { }
|
|
272
|
-
|
|
273
|
-
// Use it in MediaSFU
|
|
274
|
-
const customVideoCard = this.customComponentService.createComponentWithInjector(
|
|
275
|
-
MyVideoCard, { participant: participantData }
|
|
276
|
-
);
|
|
2012
|
+
// Show/hide whiteboard
|
|
2013
|
+
parameters.updateWhiteboardStarted.next(true);
|
|
2014
|
+
parameters.updateWhiteboardEnded.next(false);
|
|
277
2015
|
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
```
|
|
2016
|
+
// Configure whiteboard
|
|
2017
|
+
parameters.launchConfigureWhiteboard?.launchConfigureWhiteboard({ parameters });
|
|
281
2018
|
|
|
282
|
-
|
|
2019
|
+
// Access whiteboard state
|
|
2020
|
+
const isWhiteboardActive = parameters.whiteboardStarted;
|
|
283
2021
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
<div class="whatsapp-style-layout">
|
|
289
|
-
<header>My Custom Header</header>
|
|
290
|
-
<main>Your video layout here</main>
|
|
291
|
-
</div>
|
|
292
|
-
`
|
|
293
|
-
})
|
|
294
|
-
export class MyCustomLayout { }
|
|
2022
|
+
// Subscribe to whiteboard state
|
|
2023
|
+
parameters.whiteboardStarted.subscribe((isActive: boolean) => {
|
|
2024
|
+
console.log('Whiteboard:', isActive ? 'ACTIVE' : 'INACTIVE');
|
|
2025
|
+
});
|
|
295
2026
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
customMainComponent: MyCustomLayout,
|
|
299
|
-
returnUI: false // Disable default MediaSFU UI
|
|
300
|
-
};
|
|
2027
|
+
// Access whiteboard users
|
|
2028
|
+
const whiteboardUsers = parameters.whiteboardUsers;
|
|
301
2029
|
```
|
|
302
2030
|
|
|
303
|
-
###
|
|
304
|
-
|
|
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
|
|
312
|
-
|
|
313
|
-
### 🌍 **Flexible Deployment Options**
|
|
2031
|
+
### Utility Methods
|
|
314
2032
|
|
|
315
2033
|
```typescript
|
|
316
|
-
//
|
|
317
|
-
const
|
|
2034
|
+
// Check permissions
|
|
2035
|
+
const hasPermission = await parameters.checkPermission({
|
|
2036
|
+
permissionType: 'video', // or 'audio'
|
|
2037
|
+
parameters
|
|
2038
|
+
});
|
|
2039
|
+
|
|
2040
|
+
// Format large numbers
|
|
2041
|
+
const formatted = parameters.formatNumber(1250000); // Returns "1.25M"
|
|
2042
|
+
|
|
2043
|
+
// Sleep/delay
|
|
2044
|
+
await parameters.sleep({ ms: 1000 });
|
|
2045
|
+
|
|
2046
|
+
// Update display settings
|
|
2047
|
+
parameters.updateMainWindow.next(true); // Show/hide main window
|
|
2048
|
+
|
|
2049
|
+
// Trigger layout recalculation
|
|
2050
|
+
parameters.onScreenChanges({ changed: true, parameters });
|
|
2051
|
+
|
|
2052
|
+
// Get room information
|
|
2053
|
+
const roomInfo = {
|
|
2054
|
+
name: parameters.roomName,
|
|
2055
|
+
host: parameters.host,
|
|
2056
|
+
capacity: parameters.capacity,
|
|
2057
|
+
eventType: parameters.eventType,
|
|
2058
|
+
participants: parameters.participants,
|
|
2059
|
+
isRecording: parameters.recordStarted
|
|
2060
|
+
};
|
|
318
2061
|
|
|
319
|
-
//
|
|
320
|
-
|
|
2062
|
+
// Subscribe to room state changes
|
|
2063
|
+
parameters.roomName.subscribe((name: string) => {
|
|
2064
|
+
console.log('Room name:', name);
|
|
2065
|
+
});
|
|
321
2066
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
connectMediaSFU: true, // Use cloud for advanced features
|
|
326
|
-
credentials: credentials
|
|
327
|
-
};
|
|
2067
|
+
parameters.participantsCounter.subscribe((count: number) => {
|
|
2068
|
+
console.log('Participant count:', count);
|
|
2069
|
+
});
|
|
328
2070
|
```
|
|
329
2071
|
|
|
2072
|
+
---
|
|
2073
|
+
|
|
330
2074
|
## Programmatically Fetching Tokens
|
|
331
2075
|
|
|
332
2076
|
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 +4334,504 @@ Once your custom components are built, modify the imports of `prepopulateUserMed
|
|
|
2590
4334
|
|
|
2591
4335
|
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
4336
|
|
|
2593
|
-
## Custom Component Injection Service
|
|
2594
4337
|
|
|
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
4338
|
|
|
2597
|
-
|
|
4339
|
+
---
|
|
2598
4340
|
|
|
2599
|
-
|
|
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
|
|
4341
|
+
## UI Component Customization & Override System
|
|
2604
4342
|
|
|
2605
|
-
|
|
4343
|
+
MediaSFU provides comprehensive customization capabilities for all UI components through a powerful override system. You can customize styling, inject custom templates, or completely replace components with your own implementations.
|
|
2606
4344
|
|
|
2607
|
-
|
|
4345
|
+
### Overview
|
|
2608
4346
|
|
|
2609
|
-
|
|
2610
|
-
import {
|
|
2611
|
-
CustomComponentInjectionService,
|
|
2612
|
-
CustomComponent,
|
|
2613
|
-
CustomComponentType,
|
|
2614
|
-
CustomComponentParameters,
|
|
2615
|
-
CustomComponentContext
|
|
2616
|
-
} from 'mediasfu-angular';
|
|
2617
|
-
```
|
|
4347
|
+
The customization system operates at three levels:
|
|
2618
4348
|
|
|
2619
|
-
|
|
4349
|
+
1. **Style Overrides**: Apply custom CSS styles to containers, overlays, and content areas
|
|
4350
|
+
2. **Template Injection**: Replace default component templates with custom Angular templates
|
|
4351
|
+
3. **Component Replacement**: Completely replace MediaSFU components with your own using the `WithOverrideDirective`
|
|
2620
4352
|
|
|
2621
|
-
|
|
2622
|
-
|
|
4353
|
+
### 1. Style Customization
|
|
4354
|
+
|
|
4355
|
+
All display and modal components support style customization through Input properties:
|
|
4356
|
+
|
|
4357
|
+
#### Display Components (MainContainer, MainAspect, MainScreen, MainGrid, OtherGrid, SubAspect, FlexibleGrid, AudioGrid)
|
|
2623
4358
|
|
|
4359
|
+
```typescript
|
|
2624
4360
|
@Component({
|
|
2625
|
-
selector: 'app-my-
|
|
2626
|
-
template:
|
|
4361
|
+
selector: 'app-my-meeting',
|
|
4362
|
+
template: `
|
|
4363
|
+
<app-main-container
|
|
4364
|
+
[containerStyle]="{
|
|
4365
|
+
backgroundColor: '#1a1a1a',
|
|
4366
|
+
border: '2px solid #333',
|
|
4367
|
+
borderRadius: '12px',
|
|
4368
|
+
padding: '20px'
|
|
4369
|
+
}"
|
|
4370
|
+
[backgroundColor]="'transparent'">
|
|
4371
|
+
</app-main-container>
|
|
4372
|
+
|
|
4373
|
+
<app-flexible-grid
|
|
4374
|
+
[containerStyle]="{
|
|
4375
|
+
display: 'grid',
|
|
4376
|
+
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
4377
|
+
gap: '16px'
|
|
4378
|
+
}">
|
|
4379
|
+
</app-flexible-grid>
|
|
4380
|
+
`
|
|
2627
4381
|
})
|
|
2628
|
-
export class
|
|
2629
|
-
|
|
4382
|
+
export class MyMeetingComponent {
|
|
4383
|
+
// Custom styles can also be dynamic
|
|
4384
|
+
mainGridStyle: Partial<CSSStyleDeclaration> = {
|
|
4385
|
+
backgroundColor: '#2a2a2a',
|
|
4386
|
+
maxWidth: '1400px',
|
|
4387
|
+
margin: '0 auto'
|
|
4388
|
+
};
|
|
2630
4389
|
}
|
|
2631
4390
|
```
|
|
2632
4391
|
|
|
2633
|
-
####
|
|
4392
|
+
#### Modal Components (All modals support overlayStyle, contentStyle)
|
|
2634
4393
|
|
|
2635
4394
|
```typescript
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
4395
|
+
@Component({
|
|
4396
|
+
selector: 'app-custom-modal-example',
|
|
4397
|
+
template: `
|
|
4398
|
+
<!-- Participants Modal with custom styling -->
|
|
4399
|
+
<app-participants-modal
|
|
4400
|
+
[isParticipantsModalVisible]="true"
|
|
4401
|
+
[overlayStyle]="{
|
|
4402
|
+
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
|
4403
|
+
backdropFilter: 'blur(8px)'
|
|
4404
|
+
}"
|
|
4405
|
+
[contentStyle]="{
|
|
4406
|
+
backgroundColor: '#ffffff',
|
|
4407
|
+
borderRadius: '16px',
|
|
4408
|
+
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
|
|
4409
|
+
maxWidth: '600px',
|
|
4410
|
+
padding: '24px'
|
|
4411
|
+
}">
|
|
4412
|
+
</app-participants-modal>
|
|
4413
|
+
|
|
4414
|
+
<!-- Recording Modal with custom theme -->
|
|
4415
|
+
<app-recording-modal
|
|
4416
|
+
[isRecordingModalVisible]="true"
|
|
4417
|
+
[contentStyle]="{
|
|
4418
|
+
backgroundColor: '#f5f5f5',
|
|
4419
|
+
border: '3px solid #007bff',
|
|
4420
|
+
fontFamily: 'Inter, sans-serif'
|
|
4421
|
+
}">
|
|
4422
|
+
</app-recording-modal>
|
|
4423
|
+
`
|
|
4424
|
+
})
|
|
4425
|
+
export class CustomModalExample {
|
|
4426
|
+
// Styles can be computed or conditional
|
|
4427
|
+
get modalOverlay(): Partial<CSSStyleDeclaration> {
|
|
4428
|
+
return {
|
|
4429
|
+
backgroundColor: this.isDarkMode ? 'rgba(0,0,0,0.9)' : 'rgba(0,0,0,0.5)'
|
|
4430
|
+
};
|
|
2643
4431
|
}
|
|
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
4432
|
}
|
|
2667
4433
|
```
|
|
2668
4434
|
|
|
2669
|
-
|
|
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
|
-
```
|
|
4435
|
+
### 2. Template Injection
|
|
2679
4436
|
|
|
2680
|
-
|
|
4437
|
+
Replace default component templates with custom Angular templates using `TemplateRef`:
|
|
2681
4438
|
|
|
2682
4439
|
```typescript
|
|
2683
|
-
|
|
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
|
-
```
|
|
4440
|
+
import { Component, TemplateRef, ViewChild } from '@angular/core';
|
|
2694
4441
|
|
|
2695
|
-
### Advanced Usage Examples
|
|
2696
|
-
|
|
2697
|
-
#### 1. Dynamic Component Rendering
|
|
2698
|
-
|
|
2699
|
-
```typescript
|
|
2700
4442
|
@Component({
|
|
2701
|
-
selector: 'app-
|
|
4443
|
+
selector: 'app-custom-template-demo',
|
|
2702
4444
|
template: `
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
injector: this.injector,
|
|
2719
|
-
config: {
|
|
2720
|
-
enabled: true,
|
|
2721
|
-
fallbackToDefault: true
|
|
2722
|
-
}
|
|
2723
|
-
};
|
|
4445
|
+
<!-- Define custom template -->
|
|
4446
|
+
<ng-template #customMainGridTemplate let-components="components">
|
|
4447
|
+
<div class="my-custom-grid">
|
|
4448
|
+
<div class="grid-header">
|
|
4449
|
+
<h3>Active Participants</h3>
|
|
4450
|
+
<span class="count">{{ components?.length || 0 }}</span>
|
|
4451
|
+
</div>
|
|
4452
|
+
<div class="grid-content">
|
|
4453
|
+
<!-- Custom rendering logic -->
|
|
4454
|
+
<div *ngFor="let component of components" class="participant-tile">
|
|
4455
|
+
<ng-container *ngComponentOutlet="component"></ng-container>
|
|
4456
|
+
</div>
|
|
4457
|
+
</div>
|
|
4458
|
+
</div>
|
|
4459
|
+
</ng-template>
|
|
2724
4460
|
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
4461
|
+
<!-- Use custom template -->
|
|
4462
|
+
<app-main-grid
|
|
4463
|
+
[customTemplate]="customMainGridTemplate"
|
|
4464
|
+
[mainGridStream]="streams">
|
|
4465
|
+
</app-main-grid>
|
|
2730
4466
|
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
4467
|
+
<!-- Custom modal template -->
|
|
4468
|
+
<ng-template #customAlertTemplate let-message="message" let-type="type">
|
|
4469
|
+
<div class="custom-alert" [class.error]="type === 'error'">
|
|
4470
|
+
<i class="alert-icon"></i>
|
|
4471
|
+
<p>{{ message }}</p>
|
|
4472
|
+
<button (click)="closeAlert()">Dismiss</button>
|
|
4473
|
+
</div>
|
|
4474
|
+
</ng-template>
|
|
4475
|
+
|
|
4476
|
+
<app-alert-component
|
|
4477
|
+
[customTemplate]="customAlertTemplate"
|
|
4478
|
+
[visible]="showAlert"
|
|
4479
|
+
[message]="alertMessage">
|
|
4480
|
+
</app-alert-component>
|
|
4481
|
+
`,
|
|
4482
|
+
styles: [`
|
|
4483
|
+
.my-custom-grid {
|
|
4484
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
4485
|
+
border-radius: 12px;
|
|
4486
|
+
padding: 20px;
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
.grid-header {
|
|
4490
|
+
display: flex;
|
|
4491
|
+
justify-content: space-between;
|
|
4492
|
+
color: white;
|
|
4493
|
+
margin-bottom: 16px;
|
|
4494
|
+
}
|
|
4495
|
+
|
|
4496
|
+
.participant-tile {
|
|
4497
|
+
background: rgba(255, 255, 255, 0.1);
|
|
4498
|
+
border-radius: 8px;
|
|
4499
|
+
padding: 12px;
|
|
4500
|
+
margin: 8px 0;
|
|
4501
|
+
}
|
|
4502
|
+
`]
|
|
4503
|
+
})
|
|
4504
|
+
export class CustomTemplateDemo {
|
|
4505
|
+
@ViewChild('customMainGridTemplate') customMainGridTemplate!: TemplateRef<any>;
|
|
4506
|
+
@ViewChild('customAlertTemplate') customAlertTemplate!: TemplateRef<any>;
|
|
2734
4507
|
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
4508
|
+
streams: any[] = [];
|
|
4509
|
+
showAlert = false;
|
|
4510
|
+
alertMessage = '';
|
|
4511
|
+
|
|
4512
|
+
closeAlert() {
|
|
4513
|
+
this.showAlert = false;
|
|
2740
4514
|
}
|
|
2741
4515
|
}
|
|
2742
4516
|
```
|
|
2743
4517
|
|
|
2744
|
-
|
|
4518
|
+
### 3. Component Replacement with UI Overrides
|
|
4519
|
+
|
|
4520
|
+
Use the `uiOverrides` system to completely replace MediaSFU components with your own:
|
|
2745
4521
|
|
|
2746
4522
|
```typescript
|
|
2747
|
-
|
|
4523
|
+
import { Component, Type } from '@angular/core';
|
|
4524
|
+
import { MediasfuUICustomOverrides } from 'mediasfu-angular';
|
|
4525
|
+
|
|
4526
|
+
// Your custom replacement components
|
|
2748
4527
|
@Component({
|
|
2749
|
-
selector: 'app-custom-video-card',
|
|
4528
|
+
selector: 'app-my-custom-video-card',
|
|
2750
4529
|
template: `
|
|
2751
|
-
<div class="
|
|
2752
|
-
<video [srcObject]="videoStream" autoplay
|
|
2753
|
-
<div class="
|
|
2754
|
-
<
|
|
2755
|
-
<button (click)="
|
|
4530
|
+
<div class="premium-video-card">
|
|
4531
|
+
<video [srcObject]="videoStream" autoplay></video>
|
|
4532
|
+
<div class="overlay">
|
|
4533
|
+
<span class="name">{{ participant.name }}</span>
|
|
4534
|
+
<button class="pin-btn" (click)="togglePin()">📌</button>
|
|
2756
4535
|
</div>
|
|
2757
|
-
<div class="custom-info">{{ participantName }}</div>
|
|
2758
4536
|
</div>
|
|
2759
4537
|
`,
|
|
2760
|
-
|
|
4538
|
+
styles: [`
|
|
4539
|
+
.premium-video-card {
|
|
4540
|
+
position: relative;
|
|
4541
|
+
border-radius: 16px;
|
|
4542
|
+
overflow: hidden;
|
|
4543
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
4544
|
+
}
|
|
4545
|
+
.overlay {
|
|
4546
|
+
position: absolute;
|
|
4547
|
+
bottom: 0;
|
|
4548
|
+
left: 0;
|
|
4549
|
+
right: 0;
|
|
4550
|
+
background: linear-gradient(transparent, rgba(0,0,0,0.8));
|
|
4551
|
+
padding: 12px;
|
|
4552
|
+
display: flex;
|
|
4553
|
+
justify-content: space-between;
|
|
4554
|
+
align-items: center;
|
|
4555
|
+
}
|
|
4556
|
+
`]
|
|
2761
4557
|
})
|
|
2762
|
-
export class
|
|
2763
|
-
|
|
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
|
-
}
|
|
4558
|
+
export class MyCustomVideoCard {
|
|
4559
|
+
// Your custom implementation
|
|
2800
4560
|
}
|
|
2801
|
-
```
|
|
2802
4561
|
|
|
2803
|
-
#### 3. Custom Main Component with Template Restructuring
|
|
2804
|
-
|
|
2805
|
-
```typescript
|
|
2806
4562
|
@Component({
|
|
2807
|
-
selector: 'app-custom-
|
|
4563
|
+
selector: 'app-my-custom-control-buttons',
|
|
2808
4564
|
template: `
|
|
2809
|
-
<div class="
|
|
2810
|
-
<
|
|
2811
|
-
<
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
</
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
<!-- Custom participant list -->
|
|
2823
|
-
</div>
|
|
2824
|
-
</main>
|
|
2825
|
-
|
|
2826
|
-
<footer class="custom-footer">
|
|
2827
|
-
<!-- Custom footer content -->
|
|
2828
|
-
</footer>
|
|
4565
|
+
<div class="modern-controls">
|
|
4566
|
+
<button class="control-btn mic" [class.active]="!isMuted" (click)="toggleMic()">
|
|
4567
|
+
<i class="icon-mic"></i>
|
|
4568
|
+
</button>
|
|
4569
|
+
<button class="control-btn camera" [class.active]="!isVideoOff" (click)="toggleVideo()">
|
|
4570
|
+
<i class="icon-camera"></i>
|
|
4571
|
+
</button>
|
|
4572
|
+
<button class="control-btn screen" (click)="toggleScreen()">
|
|
4573
|
+
<i class="icon-screen"></i>
|
|
4574
|
+
</button>
|
|
4575
|
+
<button class="control-btn leave danger" (click)="leaveMeeting()">
|
|
4576
|
+
<i class="icon-leave"></i>
|
|
4577
|
+
</button>
|
|
2829
4578
|
</div>
|
|
2830
4579
|
`,
|
|
2831
|
-
|
|
4580
|
+
styles: [`
|
|
4581
|
+
.modern-controls {
|
|
4582
|
+
display: flex;
|
|
4583
|
+
gap: 12px;
|
|
4584
|
+
padding: 16px;
|
|
4585
|
+
background: rgba(0, 0, 0, 0.8);
|
|
4586
|
+
border-radius: 24px;
|
|
4587
|
+
}
|
|
4588
|
+
.control-btn {
|
|
4589
|
+
width: 48px;
|
|
4590
|
+
height: 48px;
|
|
4591
|
+
border-radius: 50%;
|
|
4592
|
+
border: none;
|
|
4593
|
+
cursor: pointer;
|
|
4594
|
+
transition: all 0.2s;
|
|
4595
|
+
}
|
|
4596
|
+
.control-btn.active {
|
|
4597
|
+
background: #4CAF50;
|
|
4598
|
+
color: white;
|
|
4599
|
+
}
|
|
4600
|
+
.control-btn.danger {
|
|
4601
|
+
background: #f44336;
|
|
4602
|
+
color: white;
|
|
4603
|
+
}
|
|
4604
|
+
`]
|
|
2832
4605
|
})
|
|
2833
|
-
export class
|
|
2834
|
-
|
|
2835
|
-
@Input() credentials!: any;
|
|
4606
|
+
export class MyCustomControlButtons {
|
|
4607
|
+
// Your custom implementation
|
|
2836
4608
|
}
|
|
2837
4609
|
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
4610
|
+
@Component({
|
|
4611
|
+
selector: 'app-meeting-room',
|
|
4612
|
+
template: `
|
|
4613
|
+
<app-mediasfu-generic
|
|
4614
|
+
[credentials]="credentials"
|
|
4615
|
+
[uiOverrides]="customUIOverrides"
|
|
4616
|
+
[returnUI]="false">
|
|
4617
|
+
</app-mediasfu-generic>
|
|
4618
|
+
`
|
|
4619
|
+
})
|
|
4620
|
+
export class MeetingRoomComponent {
|
|
4621
|
+
credentials = { apiUserName: 'user', apiKey: 'key' };
|
|
4622
|
+
|
|
4623
|
+
// Define your UI overrides
|
|
4624
|
+
customUIOverrides: Partial<MediasfuUICustomOverrides> = {
|
|
4625
|
+
// Replace video cards
|
|
4626
|
+
VideoCard: MyCustomVideoCard as Type<any>,
|
|
4627
|
+
|
|
4628
|
+
// Replace control buttons
|
|
4629
|
+
ControlButtonsComponent: MyCustomControlButtons as Type<any>,
|
|
4630
|
+
|
|
4631
|
+
// Replace modals
|
|
4632
|
+
ParticipantsModal: MyCustomParticipantsModal as Type<any>,
|
|
4633
|
+
|
|
4634
|
+
// Replace other display components
|
|
4635
|
+
MainGrid: MyCustomMainGrid as Type<any>,
|
|
4636
|
+
FlexibleGrid: MyCustomFlexibleGrid as Type<any>,
|
|
4637
|
+
|
|
4638
|
+
// ... any other components you want to replace
|
|
4639
|
+
};
|
|
2865
4640
|
}
|
|
2866
|
-
|
|
2867
|
-
// Merge options safely
|
|
2868
|
-
const mergedOptions = this.customComponentService.mergeComponentOptions(
|
|
2869
|
-
customOptions,
|
|
2870
|
-
defaultOptions
|
|
2871
|
-
);
|
|
2872
4641
|
```
|
|
2873
4642
|
|
|
2874
|
-
###
|
|
4643
|
+
### 4. Using WithOverrideDirective
|
|
2875
4644
|
|
|
2876
|
-
The
|
|
4645
|
+
The `*appWithOverride` directive enables conditional component replacement:
|
|
2877
4646
|
|
|
2878
4647
|
```typescript
|
|
2879
|
-
|
|
2880
|
-
|
|
4648
|
+
import { Component } from '@angular/core';
|
|
4649
|
+
import { WithOverrideDirective } from 'mediasfu-angular';
|
|
2881
4650
|
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
4651
|
+
@Component({
|
|
4652
|
+
selector: 'app-conditional-override',
|
|
4653
|
+
imports: [WithOverrideDirective, /* other imports */],
|
|
4654
|
+
template: `
|
|
4655
|
+
<!-- Use default or custom component based on condition -->
|
|
4656
|
+
<app-video-card
|
|
4657
|
+
*appWithOverride="useCustomCard ? MyCustomVideoCard : null"
|
|
4658
|
+
[participant]="currentParticipant">
|
|
4659
|
+
</app-video-card>
|
|
4660
|
+
|
|
4661
|
+
<!-- Override with custom component when premium feature enabled -->
|
|
4662
|
+
<app-control-buttons
|
|
4663
|
+
*appWithOverride="isPremiumUser ? PremiumControlButtons : null">
|
|
4664
|
+
</app-control-buttons>
|
|
4665
|
+
`
|
|
4666
|
+
})
|
|
4667
|
+
export class ConditionalOverrideComponent {
|
|
4668
|
+
useCustomCard = false;
|
|
4669
|
+
isPremiumUser = false;
|
|
4670
|
+
|
|
4671
|
+
MyCustomVideoCard = MyCustomVideoCard;
|
|
4672
|
+
PremiumControlButtons = PremiumControlButtons;
|
|
2886
4673
|
}
|
|
4674
|
+
```
|
|
2887
4675
|
|
|
2888
|
-
|
|
2889
|
-
type CustomComponentFunction = () => HTMLElement;
|
|
4676
|
+
### 5. Complete Example: Themed Meeting Room
|
|
2890
4677
|
|
|
2891
|
-
|
|
2892
|
-
interface CustomComponentParameters {
|
|
2893
|
-
[key: string]: any;
|
|
2894
|
-
}
|
|
4678
|
+
Here's a comprehensive example combining all customization approaches:
|
|
2895
4679
|
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
injector?: Injector;
|
|
2900
|
-
config?: ComponentInjectionConfig;
|
|
2901
|
-
}
|
|
4680
|
+
```typescript
|
|
4681
|
+
import { Component, TemplateRef, ViewChild, Type } from '@angular/core';
|
|
4682
|
+
import { MediasfuUICustomOverrides } from 'mediasfu-angular';
|
|
2902
4683
|
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
4684
|
+
@Component({
|
|
4685
|
+
selector: 'app-themed-meeting-room',
|
|
4686
|
+
template: `
|
|
4687
|
+
<!-- Custom alert template -->
|
|
4688
|
+
<ng-template #brandedAlertTemplate let-message="message">
|
|
4689
|
+
<div class="branded-alert">
|
|
4690
|
+
<img src="/assets/logo.png" alt="Logo" class="alert-logo">
|
|
4691
|
+
<p>{{ message }}</p>
|
|
4692
|
+
</div>
|
|
4693
|
+
</ng-template>
|
|
4694
|
+
|
|
4695
|
+
<app-mediasfu-conference
|
|
4696
|
+
[credentials]="credentials"
|
|
4697
|
+
[returnUI]="true"
|
|
4698
|
+
[uiOverrides]="themeOverrides"
|
|
4699
|
+
[containerStyle]="meetingContainerStyle">
|
|
4700
|
+
|
|
4701
|
+
<!-- Customize individual components -->
|
|
4702
|
+
<app-main-grid
|
|
4703
|
+
[containerStyle]="gridStyle"
|
|
4704
|
+
[customTemplate]="customGridTemplate">
|
|
4705
|
+
</app-main-grid>
|
|
4706
|
+
|
|
4707
|
+
<app-control-buttons-component
|
|
4708
|
+
[containerStyle]="controlsStyle">
|
|
4709
|
+
</app-control-buttons-component>
|
|
4710
|
+
|
|
4711
|
+
<app-participants-modal
|
|
4712
|
+
[overlayStyle]="modalOverlayStyle"
|
|
4713
|
+
[contentStyle]="modalContentStyle">
|
|
4714
|
+
</app-participants-modal>
|
|
4715
|
+
|
|
4716
|
+
<app-alert-component
|
|
4717
|
+
[customTemplate]="brandedAlertTemplate">
|
|
4718
|
+
</app-alert-component>
|
|
4719
|
+
</app-mediasfu-conference>
|
|
4720
|
+
`,
|
|
4721
|
+
styles: [`
|
|
4722
|
+
.branded-alert {
|
|
4723
|
+
display: flex;
|
|
4724
|
+
align-items: center;
|
|
4725
|
+
gap: 12px;
|
|
4726
|
+
padding: 16px;
|
|
4727
|
+
background: white;
|
|
4728
|
+
border-left: 4px solid #007bff;
|
|
4729
|
+
}
|
|
4730
|
+
`]
|
|
4731
|
+
})
|
|
4732
|
+
export class ThemedMeetingRoomComponent {
|
|
4733
|
+
@ViewChild('brandedAlertTemplate') brandedAlertTemplate!: TemplateRef<any>;
|
|
4734
|
+
|
|
4735
|
+
credentials = {
|
|
4736
|
+
apiUserName: 'your-api-username',
|
|
4737
|
+
apiKey: 'your-api-key'
|
|
4738
|
+
};
|
|
4739
|
+
|
|
4740
|
+
// Container styling
|
|
4741
|
+
meetingContainerStyle: Partial<CSSStyleDeclaration> = {
|
|
4742
|
+
backgroundColor: '#0f0f0f',
|
|
4743
|
+
fontFamily: 'Inter, system-ui, sans-serif'
|
|
4744
|
+
};
|
|
4745
|
+
|
|
4746
|
+
// Grid styling
|
|
4747
|
+
gridStyle: Partial<CSSStyleDeclaration> = {
|
|
4748
|
+
display: 'grid',
|
|
4749
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
|
|
4750
|
+
gap: '16px',
|
|
4751
|
+
padding: '20px'
|
|
4752
|
+
};
|
|
4753
|
+
|
|
4754
|
+
// Controls styling
|
|
4755
|
+
controlsStyle: Partial<CSSStyleDeclaration> = {
|
|
4756
|
+
position: 'fixed',
|
|
4757
|
+
bottom: '24px',
|
|
4758
|
+
left: '50%',
|
|
4759
|
+
transform: 'translateX(-50%)',
|
|
4760
|
+
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
|
4761
|
+
borderRadius: '32px',
|
|
4762
|
+
padding: '12px 24px'
|
|
4763
|
+
};
|
|
4764
|
+
|
|
4765
|
+
// Modal styling
|
|
4766
|
+
modalOverlayStyle: Partial<CSSStyleDeclaration> = {
|
|
4767
|
+
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
|
4768
|
+
backdropFilter: 'blur(10px)'
|
|
4769
|
+
};
|
|
4770
|
+
|
|
4771
|
+
modalContentStyle: Partial<CSSStyleDeclaration> = {
|
|
4772
|
+
backgroundColor: '#1a1a1a',
|
|
4773
|
+
color: '#ffffff',
|
|
4774
|
+
borderRadius: '16px',
|
|
4775
|
+
border: '1px solid #333'
|
|
4776
|
+
};
|
|
4777
|
+
|
|
4778
|
+
// Component overrides
|
|
4779
|
+
themeOverrides: Partial<MediasfuUICustomOverrides> = {
|
|
4780
|
+
VideoCard: BrandedVideoCard as Type<any>,
|
|
4781
|
+
ControlButtonsComponent: ModernControls as Type<any>
|
|
4782
|
+
};
|
|
2908
4783
|
}
|
|
2909
4784
|
```
|
|
2910
4785
|
|
|
2911
|
-
###
|
|
4786
|
+
### Customizable Components Reference
|
|
4787
|
+
|
|
4788
|
+
#### Display Components
|
|
4789
|
+
- `MainContainer` - Main viewport container
|
|
4790
|
+
- `MainAspect` - Aspect ratio container
|
|
4791
|
+
- `MainScreen` - Screen share display
|
|
4792
|
+
- `MainGrid` - Primary participant grid
|
|
4793
|
+
- `OtherGrid` - Secondary participant grid
|
|
4794
|
+
- `SubAspect` - Sub-aspect ratio container
|
|
4795
|
+
- `FlexibleGrid` - Flexible layout grid
|
|
4796
|
+
- `AudioGrid` - Audio-only participants grid
|
|
4797
|
+
|
|
4798
|
+
Each accepts: `containerStyle?: Partial<CSSStyleDeclaration>`, `customTemplate?: TemplateRef<any>`
|
|
4799
|
+
|
|
4800
|
+
#### Modal Components
|
|
4801
|
+
- `LoadingModal` - Loading indicator
|
|
4802
|
+
- `ConfirmExitModal` - Exit confirmation
|
|
4803
|
+
- `ConfirmHereModal` - Presence confirmation
|
|
4804
|
+
- `ShareEventModal` - Event sharing
|
|
4805
|
+
- `AlertComponent` - Alert notifications
|
|
4806
|
+
- `MenuModal` - Main menu
|
|
4807
|
+
- `ParticipantsModal` - Participants list
|
|
4808
|
+
- `RecordingModal` - Recording controls
|
|
4809
|
+
- `RequestsModal` - Media requests
|
|
4810
|
+
- `WaitingRoomModal` - Waiting room management
|
|
4811
|
+
- `CoHostModal` - Co-host management
|
|
4812
|
+
- `DisplaySettingsModal` - Display settings
|
|
4813
|
+
- `EventSettingsModal` - Event settings
|
|
4814
|
+
- `MediaSettingsModal` - Media device settings
|
|
4815
|
+
- `MessagesModal` - Chat messages
|
|
4816
|
+
- `PollModal` - Polls interface
|
|
4817
|
+
- `BackgroundModal` - Background effects
|
|
4818
|
+
- `BreakoutRoomsModal` - Breakout rooms
|
|
4819
|
+
- `ConfigureWhiteboardModal` - Whiteboard settings
|
|
4820
|
+
- `ScreenboardModal` - Screen annotation
|
|
4821
|
+
|
|
4822
|
+
Each accepts: `overlayStyle?: Partial<CSSStyleDeclaration>`, `contentStyle?: Partial<CSSStyleDeclaration>`, `customTemplate?: TemplateRef<any>`
|
|
2912
4823
|
|
|
2913
|
-
|
|
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
|
|
4824
|
+
### Best Practices
|
|
2919
4825
|
|
|
2920
|
-
|
|
4826
|
+
1. **Type Safety**: Always use `Partial<CSSStyleDeclaration>` for style properties
|
|
4827
|
+
2. **Template Context**: Ensure your custom templates accept the correct context variables
|
|
4828
|
+
3. **Component Lifecycle**: Custom replacement components should implement the same interface as the original
|
|
4829
|
+
4. **Performance**: Avoid inline style objects in templates; define them in the component class
|
|
4830
|
+
5. **Responsive Design**: Use CSS Grid and Flexbox for responsive layouts
|
|
4831
|
+
6. **Accessibility**: Maintain ARIA attributes and keyboard navigation in custom components
|
|
4832
|
+
7. **Testing**: Test custom components in isolation before integration
|
|
2921
4833
|
|
|
4834
|
+
---
|
|
2922
4835
|
|
|
2923
4836
|
# API Reference <a name="api-reference"></a>
|
|
2924
4837
|
|