@defcomm/meetingsdk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +984 -637
- package/dist/index.d.mts +50 -4
- package/dist/index.d.ts +50 -4
- package/dist/index.js +231 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +230 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,281 +1,355 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DEFCOMM Meet SDK
|
|
2
2
|
|
|
3
|
-
React/TypeScript SDK
|
|
3
|
+
`@afosecure/meetingsdk` is the React/TypeScript client SDK used by **DEFCOMM Meet**.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It provides the application layer for:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import { updateSDKConfig } from "@afosecure/meetingsdk";
|
|
19
|
-
|
|
20
|
-
updateSDKConfig({
|
|
21
|
-
apiBase: "https://your-sfu.example.com/api",
|
|
22
|
-
wsBase: "wss://your-sfu.example.com/ws",
|
|
23
|
-
});
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Then put your meeting UI inside `MeetingProvider`:
|
|
27
|
-
|
|
28
|
-
```tsx
|
|
29
|
-
import {
|
|
30
|
-
MeetingProvider,
|
|
31
|
-
useMeeting,
|
|
32
|
-
useParticipants,
|
|
33
|
-
} from "@afosecure/meetingsdk";
|
|
34
|
-
|
|
35
|
-
function MeetingRoom() {
|
|
36
|
-
const {
|
|
37
|
-
join,
|
|
38
|
-
leave,
|
|
39
|
-
toggleMic,
|
|
40
|
-
toggleCam,
|
|
41
|
-
startScreenShare,
|
|
42
|
-
stopScreenShare,
|
|
43
|
-
startRecording,
|
|
44
|
-
stopRecording,
|
|
45
|
-
} = useMeeting();
|
|
7
|
+
- joining and leaving meetings
|
|
8
|
+
- microphone and camera control
|
|
9
|
+
- remote participant media
|
|
10
|
+
- screen sharing
|
|
11
|
+
- active-speaker detection
|
|
12
|
+
- participant presence and reconnection state
|
|
13
|
+
- private-room admission
|
|
14
|
+
- server-side recording
|
|
15
|
+
- room management
|
|
16
|
+
- chat/PubSub integration
|
|
17
|
+
- WebRTC and SFU signaling orchestration
|
|
46
18
|
|
|
47
|
-
|
|
19
|
+
The SDK is designed so that a DEFCOMM Meet consumer should **not need to manage WebRTC peer connections, SDP, ICE candidates, RTP tracks, reconnect logic, or browser media plumbing directly**.
|
|
48
20
|
|
|
49
|
-
|
|
50
|
-
<div>
|
|
51
|
-
<button onClick={join}>Join</button>
|
|
52
|
-
<button onClick={toggleMic}>Mic</button>
|
|
53
|
-
<button onClick={toggleCam}>Camera</button>
|
|
54
|
-
<button onClick={startScreenShare}>Share screen</button>
|
|
55
|
-
<button onClick={stopScreenShare}>Stop sharing</button>
|
|
56
|
-
<button onClick={startRecording}>Record</button>
|
|
57
|
-
<button onClick={stopRecording}>Stop recording</button>
|
|
58
|
-
<button onClick={leave}>Leave</button>
|
|
21
|
+
---
|
|
59
22
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
23
|
+
## Table of contents
|
|
24
|
+
|
|
25
|
+
1. [What the SDK does](#what-the-sdk-does)
|
|
26
|
+
2. [Architecture](#architecture)
|
|
27
|
+
3. [Installation](#installation)
|
|
28
|
+
4. [SDK configuration](#sdk-configuration)
|
|
29
|
+
5. [Creating a meeting](#creating-a-meeting)
|
|
30
|
+
6. [Joining a meeting](#joining-a-meeting)
|
|
31
|
+
7. [MeetingProvider](#meetingprovider)
|
|
32
|
+
8. [The `useMeeting` hook](#the-usemeeting-hook)
|
|
33
|
+
9. [Participants](#participants)
|
|
34
|
+
10. [Local participant](#local-participant)
|
|
35
|
+
11. [Remote media](#remote-media)
|
|
36
|
+
12. [Microphone and camera](#microphone-and-camera)
|
|
37
|
+
13. [Active speaker detection](#active-speaker-detection)
|
|
38
|
+
14. [Screen sharing](#screen-sharing)
|
|
39
|
+
15. [Recording](#recording)
|
|
40
|
+
16. [Private rooms and join approval](#private-rooms-and-join-approval)
|
|
41
|
+
17. [Reconnection and participant presence](#reconnection-and-participant-presence)
|
|
42
|
+
18. [Leaving a meeting](#leaving-a-meeting)
|
|
43
|
+
19. [Room management](#room-management)
|
|
44
|
+
20. [Meeting preview](#meeting-preview)
|
|
45
|
+
21. [Chat](#chat)
|
|
46
|
+
22. [Error handling](#error-handling)
|
|
47
|
+
23. [Recommended DEFCOMM Meet structure](#recommended-defcomm-meet-structure)
|
|
48
|
+
24. [Important implementation rules](#important-implementation-rules)
|
|
49
|
+
25. [Build and publish](#build-and-publish)
|
|
66
50
|
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<MeetingProvider roomCode={roomCode} displayName="Afolabi">
|
|
70
|
-
<MeetingRoom />
|
|
71
|
-
</MeetingProvider>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
```
|
|
51
|
+
---
|
|
75
52
|
|
|
76
|
-
|
|
53
|
+
# What the SDK does
|
|
77
54
|
|
|
78
|
-
|
|
55
|
+
DEFCOMM Meet is built around an **SFU architecture**.
|
|
79
56
|
|
|
80
|
-
|
|
57
|
+
The browser does not establish a separate peer connection with every participant.
|
|
81
58
|
|
|
82
|
-
|
|
59
|
+
Instead:
|
|
83
60
|
|
|
84
61
|
```text
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
└── Subscriber RTCPeerConnection <─ subscribe_offer ── Rust SFU
|
|
89
|
-
│
|
|
90
|
-
MediaRouter
|
|
91
|
-
│
|
|
92
|
-
other participants
|
|
62
|
+
Participant A ──┐
|
|
63
|
+
Participant B ──┼──► DEFCOMM Rust SFU ──► Participants
|
|
64
|
+
Participant C ──┘
|
|
93
65
|
```
|
|
94
66
|
|
|
95
|
-
The
|
|
96
|
-
|
|
97
|
-
Think of the system as:
|
|
67
|
+
The SDK sits between the React application and the SFU:
|
|
98
68
|
|
|
99
69
|
```text
|
|
100
|
-
|
|
101
|
-
│
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
├── WebSocket signaling
|
|
106
|
-
└── WebRTC media
|
|
70
|
+
┌───────────────────────────────┐
|
|
71
|
+
│ DEFCOMM Meet App │
|
|
72
|
+
│ │
|
|
73
|
+
│ Meeting UI / Participant UI │
|
|
74
|
+
└───────────────┬───────────────┘
|
|
107
75
|
│
|
|
108
76
|
▼
|
|
109
|
-
|
|
77
|
+
┌───────────────────────────────┐
|
|
78
|
+
│ @afosecure/meetingsdk │
|
|
79
|
+
│ │
|
|
80
|
+
│ React hooks │
|
|
81
|
+
│ Meeting state │
|
|
82
|
+
│ WebRTC │
|
|
83
|
+
│ WebSocket signaling │
|
|
84
|
+
│ Reconnection │
|
|
85
|
+
│ Active speaker detection │
|
|
86
|
+
│ Media attachment │
|
|
87
|
+
│ Recording controls │
|
|
88
|
+
└───────────────┬───────────────┘
|
|
110
89
|
│
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
90
|
+
▼
|
|
91
|
+
┌───────────────────────────────┐
|
|
92
|
+
│ DEFCOMM Rust SFU │
|
|
93
|
+
│ │
|
|
94
|
+
│ Rooms │
|
|
95
|
+
│ Participants │
|
|
96
|
+
│ Admission │
|
|
97
|
+
│ SDP / ICE │
|
|
98
|
+
│ RTP forwarding │
|
|
99
|
+
│ Track state │
|
|
100
|
+
│ Recording │
|
|
101
|
+
└───────────────────────────────┘
|
|
116
102
|
```
|
|
117
103
|
|
|
118
|
-
|
|
104
|
+
### The responsibility split
|
|
105
|
+
|
|
106
|
+
**Your application owns:**
|
|
107
|
+
|
|
108
|
+
- layout
|
|
109
|
+
- buttons
|
|
110
|
+
- participant tiles
|
|
111
|
+
- navbar
|
|
112
|
+
- chat UI
|
|
113
|
+
- recording indicator
|
|
114
|
+
- active-speaker styling
|
|
115
|
+
- loading/error screens
|
|
116
|
+
|
|
117
|
+
**The SDK owns:**
|
|
118
|
+
|
|
119
|
+
- browser media access
|
|
120
|
+
- publisher PeerConnection
|
|
121
|
+
- subscriber PeerConnection
|
|
122
|
+
- WebSocket signaling
|
|
123
|
+
- media track mapping
|
|
124
|
+
- reconnect/resume behavior
|
|
125
|
+
- active-speaker detection
|
|
126
|
+
- SDK meeting state
|
|
127
|
+
|
|
128
|
+
**The SFU owns:**
|
|
129
|
+
|
|
130
|
+
- room state
|
|
131
|
+
- participant membership
|
|
132
|
+
- admission/approval
|
|
133
|
+
- signaling authority
|
|
134
|
+
- media forwarding
|
|
135
|
+
- track publication/subscription
|
|
136
|
+
- server-side recording
|
|
119
137
|
|
|
120
138
|
---
|
|
121
139
|
|
|
122
|
-
#
|
|
140
|
+
# Installation
|
|
123
141
|
|
|
124
142
|
```bash
|
|
125
143
|
npm install @afosecure/meetingsdk
|
|
126
144
|
```
|
|
127
145
|
|
|
128
|
-
The
|
|
146
|
+
The SDK is intended for browser-based React + TypeScript applications.
|
|
129
147
|
|
|
130
148
|
---
|
|
131
149
|
|
|
132
|
-
#
|
|
150
|
+
# Creating a meeting
|
|
133
151
|
|
|
134
|
-
|
|
135
|
-
import { updateSDKConfig } from "@afosecure/meetingsdk";
|
|
152
|
+
The SDK exposes room management through `VideoSDKCore` and `useMeeting()`.
|
|
136
153
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
154
|
+
```tsx
|
|
155
|
+
const { createRoom } = useMeeting();
|
|
156
|
+
|
|
157
|
+
const room = await createRoom({
|
|
158
|
+
name: "Engineering Standup",
|
|
159
|
+
description: "Daily engineering meeting",
|
|
160
|
+
capacity: 50,
|
|
161
|
+
is_private: true,
|
|
140
162
|
});
|
|
141
163
|
```
|
|
142
164
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
```text
|
|
146
|
-
apiBase → REST API base
|
|
147
|
-
wsBase → WebSocket signaling endpoint
|
|
148
|
-
```
|
|
165
|
+
A room can also be retrieved:
|
|
149
166
|
|
|
150
|
-
|
|
167
|
+
```tsx
|
|
168
|
+
const { getRoom } = useMeeting();
|
|
151
169
|
|
|
152
|
-
|
|
153
|
-
updateSDKConfig({
|
|
154
|
-
apiBase: "http://localhost:8080/api",
|
|
155
|
-
wsBase: "ws://localhost:8080/ws",
|
|
156
|
-
});
|
|
170
|
+
const room = await getRoom(roomCode);
|
|
157
171
|
```
|
|
158
172
|
|
|
159
|
-
|
|
173
|
+
And deleted when appropriate:
|
|
160
174
|
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
175
|
+
```tsx
|
|
176
|
+
const { deleteRoom } = useMeeting();
|
|
177
|
+
|
|
178
|
+
await deleteRoom(roomCode);
|
|
166
179
|
```
|
|
167
180
|
|
|
168
|
-
|
|
181
|
+
Room creation and deletion should normally be restricted to the appropriate application role.
|
|
169
182
|
|
|
170
183
|
---
|
|
171
184
|
|
|
172
|
-
#
|
|
185
|
+
# Joining a meeting
|
|
186
|
+
|
|
187
|
+
The normal React integration starts with `MeetingProvider`.
|
|
173
188
|
|
|
174
|
-
|
|
189
|
+
```tsx
|
|
190
|
+
import { MeetingProvider, useMeeting } from "@afosecure/meetingsdk";
|
|
191
|
+
|
|
192
|
+
function MeetingRoom() {
|
|
193
|
+
const { join, leave } = useMeeting();
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<div>
|
|
197
|
+
<button onClick={() => join()}>Join meeting</button>
|
|
198
|
+
|
|
199
|
+
<button onClick={() => leave()}>Leave</button>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function MeetingPage() {
|
|
205
|
+
return (
|
|
206
|
+
<MeetingProvider
|
|
207
|
+
config={{
|
|
208
|
+
roomId: "room-code",
|
|
209
|
+
name: "Afolabi",
|
|
210
|
+
audioMuted: false,
|
|
211
|
+
videoMuted: false,
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
<MeetingRoom />
|
|
215
|
+
</MeetingProvider>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`MeetingProvider` accepts:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
type MeetingConfig = {
|
|
224
|
+
roomId: string;
|
|
225
|
+
name: string;
|
|
226
|
+
audioMuted?: boolean;
|
|
227
|
+
videoMuted?: boolean;
|
|
228
|
+
token?: string;
|
|
229
|
+
};
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Recommended join flow
|
|
175
233
|
|
|
176
234
|
```text
|
|
177
|
-
|
|
235
|
+
Meeting page
|
|
178
236
|
↓
|
|
179
237
|
MeetingProvider
|
|
180
238
|
↓
|
|
181
239
|
User clicks Join
|
|
182
240
|
↓
|
|
183
|
-
|
|
184
|
-
↓
|
|
185
|
-
Open WebSocket
|
|
241
|
+
join()
|
|
186
242
|
↓
|
|
187
|
-
|
|
243
|
+
Browser requests camera/microphone
|
|
188
244
|
↓
|
|
189
|
-
|
|
245
|
+
SDK connects to SFU
|
|
190
246
|
↓
|
|
191
|
-
|
|
247
|
+
Publisher PeerConnection
|
|
192
248
|
↓
|
|
193
|
-
|
|
249
|
+
Subscriber PeerConnection
|
|
194
250
|
↓
|
|
195
|
-
|
|
251
|
+
Remote tracks become available
|
|
196
252
|
```
|
|
197
253
|
|
|
198
|
-
|
|
254
|
+
Call `join()` from a user action whenever possible. This gives the browser a normal user-initiated context for requesting media permissions.
|
|
199
255
|
|
|
200
256
|
---
|
|
201
257
|
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
The SDK creates a browser participant ID only when one does not already exist.
|
|
205
|
-
|
|
206
|
-
Storage key:
|
|
207
|
-
|
|
208
|
-
```text
|
|
209
|
-
defcomm:participant_id
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
Therefore:
|
|
213
|
-
|
|
214
|
-
```text
|
|
215
|
-
First load → generate ID
|
|
216
|
-
Reload → reuse ID
|
|
217
|
-
Temporary outage → reuse ID
|
|
218
|
-
Reconnect → reuse ID
|
|
219
|
-
```
|
|
258
|
+
# The `useMeeting` hook
|
|
220
259
|
|
|
221
|
-
|
|
260
|
+
`useMeeting()` is the main application API.
|
|
222
261
|
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
A transient network failure does not clear the resume token. An intentional `leave()` does. fileciteturn9file0L116-L132
|
|
262
|
+
```tsx
|
|
263
|
+
const {
|
|
264
|
+
join,
|
|
265
|
+
leave,
|
|
228
266
|
|
|
229
|
-
|
|
267
|
+
toggleMic,
|
|
268
|
+
toggleCam,
|
|
230
269
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
→ reconnect/resume
|
|
270
|
+
startScreenShare,
|
|
271
|
+
stopScreenShare,
|
|
234
272
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
273
|
+
startRecording,
|
|
274
|
+
stopRecording,
|
|
275
|
+
getRecordingStatus,
|
|
238
276
|
|
|
239
|
-
|
|
277
|
+
participants,
|
|
278
|
+
localParticipant,
|
|
279
|
+
activeSpeakerIds,
|
|
280
|
+
presenterId,
|
|
281
|
+
room,
|
|
240
282
|
|
|
241
|
-
|
|
283
|
+
approveJoinRequest,
|
|
284
|
+
rejectJoinRequest,
|
|
242
285
|
|
|
243
|
-
|
|
244
|
-
const {
|
|
245
|
-
join,
|
|
246
|
-
leave,
|
|
286
|
+
sendMessage,
|
|
247
287
|
} = useMeeting();
|
|
248
288
|
```
|
|
249
289
|
|
|
250
|
-
|
|
290
|
+
The hook also supports event handlers:
|
|
251
291
|
|
|
252
292
|
```tsx
|
|
253
|
-
|
|
293
|
+
const meeting = useMeeting({
|
|
294
|
+
onError: (error) => {
|
|
295
|
+
console.error(error);
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
onActiveSpeakersChanged: (participantIds) => {
|
|
299
|
+
console.log("Active speakers:", participantIds);
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
onRecordingStarted: (recording) => {
|
|
303
|
+
console.log("Recording started:", recording);
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
onRecordingStopped: (recording) => {
|
|
307
|
+
console.log("Recording stopped:", recording);
|
|
308
|
+
},
|
|
309
|
+
});
|
|
254
310
|
```
|
|
255
311
|
|
|
256
|
-
|
|
312
|
+
For normal React rendering, prefer the state exposed by the hook:
|
|
257
313
|
|
|
258
314
|
```tsx
|
|
259
|
-
|
|
315
|
+
const { activeSpeakerIds } = useMeeting();
|
|
260
316
|
```
|
|
261
317
|
|
|
262
|
-
|
|
318
|
+
Use event callbacks when the application needs to perform an additional side effect.
|
|
263
319
|
|
|
264
320
|
---
|
|
265
321
|
|
|
266
|
-
#
|
|
322
|
+
# Participants
|
|
267
323
|
|
|
268
|
-
|
|
324
|
+
Use `useParticipants()` when the component only needs the participant list.
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
import { useParticipants } from "@afosecure/meetingsdk";
|
|
328
|
+
|
|
329
|
+
function ParticipantList() {
|
|
330
|
+
const participants = useParticipants();
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<div>
|
|
334
|
+
{participants.map((participant) => (
|
|
335
|
+
<div key={participant.id}>{participant.name}</div>
|
|
336
|
+
))}
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
A participant contains:
|
|
269
343
|
|
|
270
344
|
```ts
|
|
271
345
|
type Participant = {
|
|
272
346
|
id: string;
|
|
273
347
|
name?: string;
|
|
348
|
+
isHost?: boolean;
|
|
349
|
+
isLocal?: boolean;
|
|
350
|
+
isPresenter?: boolean;
|
|
274
351
|
|
|
275
|
-
connectionState?:
|
|
276
|
-
| "connected"
|
|
277
|
-
| "reconnecting"
|
|
278
|
-
| "disconnected";
|
|
352
|
+
connectionState?: "connected" | "reconnecting" | "disconnected";
|
|
279
353
|
|
|
280
354
|
media?: {
|
|
281
355
|
stream?: MediaStream | null;
|
|
@@ -288,713 +362,986 @@ type Participant = {
|
|
|
288
362
|
micEnabled: boolean;
|
|
289
363
|
camEnabled: boolean;
|
|
290
364
|
isScreenSharing: boolean;
|
|
365
|
+
|
|
366
|
+
remoteScreenStreamId?: string;
|
|
367
|
+
cameraStreamId?: string;
|
|
291
368
|
};
|
|
292
369
|
};
|
|
293
370
|
```
|
|
294
371
|
|
|
295
|
-
The SDK exposes these connection and media states for meeting UI. fileciteturn9file0L367-L385
|
|
296
|
-
|
|
297
372
|
---
|
|
298
373
|
|
|
299
|
-
#
|
|
374
|
+
# Local participant
|
|
300
375
|
|
|
301
|
-
Use `
|
|
376
|
+
Use `useLocalParticipant()` when you need the local participant and local video.
|
|
302
377
|
|
|
303
378
|
```tsx
|
|
304
|
-
import {
|
|
379
|
+
import { useLocalParticipant } from "@afosecure/meetingsdk";
|
|
305
380
|
|
|
306
|
-
function
|
|
307
|
-
const {
|
|
308
|
-
videoRef,
|
|
309
|
-
audioRef,
|
|
310
|
-
isCamActive,
|
|
311
|
-
isMicEnabled,
|
|
312
|
-
} = useRemoteMedia(participantId);
|
|
381
|
+
function LocalVideo() {
|
|
382
|
+
const { participant, videoRef } = useLocalParticipant();
|
|
313
383
|
|
|
314
|
-
return
|
|
315
|
-
<>
|
|
316
|
-
<video
|
|
317
|
-
ref={videoRef}
|
|
318
|
-
autoPlay
|
|
319
|
-
playsInline
|
|
320
|
-
muted
|
|
321
|
-
/>
|
|
322
|
-
|
|
323
|
-
<audio
|
|
324
|
-
ref={audioRef}
|
|
325
|
-
autoPlay
|
|
326
|
-
/>
|
|
327
|
-
</>
|
|
328
|
-
);
|
|
384
|
+
return <video ref={videoRef} autoPlay muted playsInline />;
|
|
329
385
|
}
|
|
330
386
|
```
|
|
331
387
|
|
|
332
|
-
The
|
|
388
|
+
The local participant exposes:
|
|
389
|
+
|
|
390
|
+
```tsx
|
|
391
|
+
participant?.media?.micEnabled;
|
|
392
|
+
participant?.media?.camEnabled;
|
|
393
|
+
participant?.media?.isScreenSharing;
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Use these values to render local microphone/camera state.
|
|
333
397
|
|
|
334
398
|
---
|
|
335
399
|
|
|
336
|
-
#
|
|
400
|
+
# Remote media
|
|
337
401
|
|
|
338
|
-
|
|
402
|
+
Use `useRemoteMedia(participantId)` for a remote participant.
|
|
339
403
|
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
↓
|
|
343
|
-
browser track.id ("track-" + id)
|
|
344
|
-
↓
|
|
345
|
-
normalize ID
|
|
346
|
-
↓
|
|
347
|
-
TrackInfo.participant_id
|
|
348
|
-
↓
|
|
349
|
-
Participant
|
|
350
|
-
↓
|
|
351
|
-
MediaStream
|
|
352
|
-
```
|
|
404
|
+
```tsx
|
|
405
|
+
import { useRemoteMedia } from "@afosecure/meetingsdk";
|
|
353
406
|
|
|
354
|
-
|
|
407
|
+
function RemoteParticipant({ participantId }: { participantId: string }) {
|
|
408
|
+
const { videoRef, audioRef, isCamActive, isMicEnabled } =
|
|
409
|
+
useRemoteMedia(participantId);
|
|
355
410
|
|
|
356
|
-
|
|
357
|
-
|
|
411
|
+
return (
|
|
412
|
+
<div>
|
|
413
|
+
<video ref={videoRef} autoPlay playsInline muted />
|
|
414
|
+
|
|
415
|
+
<audio ref={audioRef} autoPlay />
|
|
416
|
+
|
|
417
|
+
{!isMicEnabled && <span>Muted</span>}
|
|
418
|
+
{!isCamActive && <span>Camera off</span>}
|
|
419
|
+
</div>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
358
422
|
```
|
|
359
423
|
|
|
360
|
-
|
|
424
|
+
### Why are video and audio separate?
|
|
425
|
+
|
|
426
|
+
The SDK attaches the remote `MediaStream` to both elements:
|
|
427
|
+
|
|
428
|
+
- `<video>` is muted
|
|
429
|
+
- `<audio>` handles remote audio
|
|
430
|
+
|
|
431
|
+
This makes media rendering explicit and avoids accidentally routing remote audio through the video element.
|
|
361
432
|
|
|
362
433
|
---
|
|
363
434
|
|
|
364
|
-
#
|
|
435
|
+
# Microphone and camera
|
|
436
|
+
|
|
437
|
+
The SDK exposes:
|
|
365
438
|
|
|
366
439
|
```tsx
|
|
367
|
-
const {
|
|
368
|
-
toggleMic,
|
|
369
|
-
toggleCam,
|
|
370
|
-
} = useMeeting();
|
|
440
|
+
const { toggleMic, toggleCam } = useMeeting();
|
|
371
441
|
```
|
|
372
442
|
|
|
373
443
|
Use:
|
|
374
444
|
|
|
375
445
|
```tsx
|
|
376
|
-
<button onClick={toggleMic}>
|
|
377
|
-
|
|
378
|
-
|
|
446
|
+
<button onClick={toggleMic}>
|
|
447
|
+
Microphone
|
|
448
|
+
</button>
|
|
379
449
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
toggleMic()
|
|
384
|
-
↓
|
|
385
|
-
MediaStreamTrack.enabled
|
|
386
|
-
↓
|
|
387
|
-
track_state
|
|
388
|
-
↓
|
|
389
|
-
Rust SFU
|
|
390
|
-
↓
|
|
391
|
-
track_state_changed
|
|
392
|
-
↓
|
|
393
|
-
remote UI
|
|
450
|
+
<button onClick={toggleCam}>
|
|
451
|
+
Camera
|
|
452
|
+
</button>
|
|
394
453
|
```
|
|
395
454
|
|
|
396
|
-
The
|
|
455
|
+
The SDK updates the local media track and sends the corresponding media state to the SFU.
|
|
397
456
|
|
|
398
|
-
|
|
457
|
+
Remote participants receive the authoritative track state through the meeting state.
|
|
399
458
|
|
|
400
|
-
|
|
459
|
+
For participant UI, use:
|
|
401
460
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
remoteTrack.muted
|
|
461
|
+
```tsx
|
|
462
|
+
participant.media?.micEnabled;
|
|
463
|
+
participant.media?.camEnabled;
|
|
406
464
|
```
|
|
407
465
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
Use:
|
|
466
|
+
rather than trying to infer another participant's mute state from RTP or `MediaStreamTrack.muted`.
|
|
411
467
|
|
|
412
|
-
|
|
413
|
-
participant.media?.micEnabled
|
|
414
|
-
participant.media?.camEnabled
|
|
415
|
-
```
|
|
468
|
+
---
|
|
416
469
|
|
|
417
|
-
|
|
470
|
+
# Active speaker detection
|
|
418
471
|
|
|
419
|
-
|
|
472
|
+
DEFCOMM Meet performs active-speaker detection **inside the SDK**.
|
|
420
473
|
|
|
421
|
-
|
|
474
|
+
The application does not need to use Web Audio APIs, `AnalyserNode`, audio RMS calculations, timers, or WebRTC statistics.
|
|
422
475
|
|
|
423
|
-
|
|
476
|
+
The SDK exposes:
|
|
424
477
|
|
|
425
478
|
```tsx
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
participant.connectionState === "reconnecting";
|
|
479
|
+
const { activeSpeakerIds } = useMeeting();
|
|
480
|
+
```
|
|
429
481
|
|
|
430
|
-
|
|
431
|
-
participant.media?.micEnabled ?? false;
|
|
482
|
+
`activeSpeakerIds` is:
|
|
432
483
|
|
|
433
|
-
|
|
434
|
-
|
|
484
|
+
```ts
|
|
485
|
+
string[]
|
|
486
|
+
```
|
|
435
487
|
|
|
436
|
-
|
|
437
|
-
participant.media?.isScreenSharing ?? false;
|
|
488
|
+
containing participant IDs currently detected as speaking.
|
|
438
489
|
|
|
439
|
-
|
|
440
|
-
<div className="participant-tile">
|
|
441
|
-
{reconnecting && (
|
|
442
|
-
<span className="reconnecting">
|
|
443
|
-
Reconnecting...
|
|
444
|
-
</span>
|
|
445
|
-
)}
|
|
490
|
+
### Participant tile
|
|
446
491
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
492
|
+
```tsx
|
|
493
|
+
function ParticipantTile({ participant }: { participant: Participant }) {
|
|
494
|
+
const { activeSpeakerIds } = useMeeting();
|
|
450
495
|
|
|
451
|
-
|
|
452
|
-
<CameraIcon off={!camEnabled} />
|
|
496
|
+
const isActiveSpeaker = activeSpeakerIds.includes(participant.id);
|
|
453
497
|
|
|
454
|
-
|
|
498
|
+
return (
|
|
499
|
+
<div className={isActiveSpeaker ? "ring-2 ring-green-400" : ""}>
|
|
500
|
+
{participant.name}
|
|
455
501
|
</div>
|
|
456
502
|
);
|
|
457
503
|
}
|
|
458
504
|
```
|
|
459
505
|
|
|
460
|
-
|
|
506
|
+
### Better pattern
|
|
461
507
|
|
|
462
|
-
|
|
463
|
-
connectionState
|
|
464
|
-
↓
|
|
465
|
-
reconnecting badge
|
|
508
|
+
For a participant list:
|
|
466
509
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
510
|
+
```tsx
|
|
511
|
+
const { activeSpeakerIds } = useMeeting();
|
|
512
|
+
|
|
513
|
+
{
|
|
514
|
+
participants.map((participant) => (
|
|
515
|
+
<ParticipantTile
|
|
516
|
+
key={participant.id}
|
|
517
|
+
participant={participant}
|
|
518
|
+
isActiveSpeaker={activeSpeakerIds.includes(participant.id)}
|
|
519
|
+
/>
|
|
520
|
+
));
|
|
521
|
+
}
|
|
522
|
+
```
|
|
470
523
|
|
|
471
|
-
|
|
472
|
-
↓
|
|
473
|
-
camera icon
|
|
524
|
+
The tile should only render the state. It should not implement speaker detection itself.
|
|
474
525
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
526
|
+
### Active speaker event
|
|
527
|
+
|
|
528
|
+
The SDK also exposes:
|
|
529
|
+
|
|
530
|
+
```tsx
|
|
531
|
+
useMeeting({
|
|
532
|
+
onActiveSpeakersChanged: (participantIds) => {
|
|
533
|
+
console.log("Active speakers:", participantIds);
|
|
534
|
+
},
|
|
535
|
+
});
|
|
478
536
|
```
|
|
479
537
|
|
|
480
|
-
|
|
538
|
+
The event is useful for analytics or application-level side effects. For normal UI rendering, `activeSpeakerIds` is preferred.
|
|
481
539
|
|
|
482
|
-
|
|
540
|
+
### Multiple speakers
|
|
483
541
|
|
|
484
|
-
|
|
542
|
+
The API intentionally supports multiple active speakers:
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
activeSpeakerIds: string[]
|
|
546
|
+
```
|
|
485
547
|
|
|
486
|
-
|
|
548
|
+
rather than:
|
|
487
549
|
|
|
488
|
-
```
|
|
489
|
-
|
|
550
|
+
```ts
|
|
551
|
+
activeSpeakerId: string | null;
|
|
490
552
|
```
|
|
491
553
|
|
|
492
|
-
|
|
554
|
+
This allows the UI to handle overlapping speech naturally.
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
# Screen sharing
|
|
493
559
|
|
|
494
560
|
Start:
|
|
495
561
|
|
|
496
562
|
```tsx
|
|
563
|
+
const { startScreenShare } = useMeeting();
|
|
564
|
+
|
|
497
565
|
await startScreenShare();
|
|
498
566
|
```
|
|
499
567
|
|
|
500
568
|
Stop:
|
|
501
569
|
|
|
502
570
|
```tsx
|
|
503
|
-
|
|
571
|
+
const { stopScreenShare } = useMeeting();
|
|
572
|
+
|
|
573
|
+
stopScreenShare();
|
|
504
574
|
```
|
|
505
575
|
|
|
506
|
-
|
|
576
|
+
A participant's screen share is represented separately from their camera:
|
|
507
577
|
|
|
508
578
|
```ts
|
|
509
|
-
participant.media
|
|
510
|
-
participant.media
|
|
511
|
-
participant.media
|
|
579
|
+
participant.media?.screenStream;
|
|
580
|
+
participant.media?.screenTrack;
|
|
581
|
+
participant.media?.isScreenSharing;
|
|
512
582
|
```
|
|
513
583
|
|
|
514
|
-
|
|
584
|
+
### Recommended UI
|
|
515
585
|
|
|
516
|
-
|
|
586
|
+
When someone shares their screen:
|
|
517
587
|
|
|
518
588
|
```text
|
|
519
|
-
|
|
520
|
-
│
|
|
521
|
-
│
|
|
522
|
-
│
|
|
523
|
-
│
|
|
524
|
-
|
|
589
|
+
┌──────────────────────────────────────────┐
|
|
590
|
+
│ │
|
|
591
|
+
│ SCREEN SHARE │
|
|
592
|
+
│ │
|
|
593
|
+
│ │
|
|
594
|
+
└──────────────────────────────────────────┘
|
|
595
|
+
|
|
596
|
+
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
597
|
+
│ Alice │ │ Bob │ │ Charlie │
|
|
598
|
+
└─────────┘ └─────────┘ └─────────┘
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Do not treat the screen track as the participant's camera track.
|
|
602
|
+
|
|
603
|
+
---
|
|
525
604
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
605
|
+
# Recording
|
|
606
|
+
|
|
607
|
+
DEFCOMM Meet recording is **server-side**.
|
|
608
|
+
|
|
609
|
+
The browser does not use `MediaRecorder` to create the meeting recording.
|
|
610
|
+
|
|
611
|
+
Start recording:
|
|
612
|
+
|
|
613
|
+
```tsx
|
|
614
|
+
const { startRecording } = useMeeting();
|
|
615
|
+
|
|
616
|
+
await startRecording();
|
|
529
617
|
```
|
|
530
618
|
|
|
531
|
-
|
|
619
|
+
Stop recording:
|
|
532
620
|
|
|
533
|
-
|
|
621
|
+
```tsx
|
|
622
|
+
const { stopRecording } = useMeeting();
|
|
534
623
|
|
|
535
|
-
|
|
624
|
+
await stopRecording();
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Get current recording status:
|
|
536
628
|
|
|
537
|
-
|
|
629
|
+
```tsx
|
|
630
|
+
const { getRecordingStatus } = useMeeting();
|
|
538
631
|
|
|
539
|
-
|
|
632
|
+
const result = await getRecordingStatus();
|
|
540
633
|
|
|
541
|
-
|
|
542
|
-
Network interruption
|
|
543
|
-
↓
|
|
544
|
-
WebSocket closes
|
|
545
|
-
↓
|
|
546
|
-
participant = reconnecting
|
|
547
|
-
↓
|
|
548
|
-
refresh access token
|
|
549
|
-
↓
|
|
550
|
-
JOIN + resume_token
|
|
551
|
-
↓
|
|
552
|
-
same logical participant
|
|
553
|
-
↓
|
|
554
|
-
new publisher/subscriber PCs
|
|
555
|
-
↓
|
|
556
|
-
republish local tracks
|
|
557
|
-
↓
|
|
558
|
-
resubscribe remote tracks
|
|
559
|
-
↓
|
|
560
|
-
fresh ICE/SDP
|
|
561
|
-
↓
|
|
562
|
-
connected
|
|
634
|
+
console.log(result.recording);
|
|
563
635
|
```
|
|
564
636
|
|
|
565
|
-
|
|
637
|
+
A recording has:
|
|
566
638
|
|
|
567
|
-
|
|
639
|
+
```ts
|
|
640
|
+
type RecordingInfo = {
|
|
641
|
+
recording_id: string;
|
|
642
|
+
started_at?: string;
|
|
643
|
+
stopped_at?: string;
|
|
644
|
+
active?: boolean;
|
|
645
|
+
};
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
## Recording events
|
|
568
649
|
|
|
569
|
-
|
|
650
|
+
The SDK exposes:
|
|
651
|
+
|
|
652
|
+
```tsx
|
|
653
|
+
useMeeting({
|
|
654
|
+
onRecordingStarted: (recording) => {
|
|
655
|
+
console.log("Recording started", recording);
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
onRecordingStopped: (recording) => {
|
|
659
|
+
console.log("Recording stopped", recording);
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### Recommended DEFCOMM Meet UI
|
|
665
|
+
|
|
666
|
+
The meeting application can place the recording control in the meeting navbar:
|
|
570
667
|
|
|
571
668
|
```text
|
|
572
|
-
|
|
669
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
670
|
+
│ Engineering 🔴 REC Chat People │
|
|
671
|
+
└───────────────────────────────────────────────────────────────┘
|
|
573
672
|
```
|
|
574
673
|
|
|
575
|
-
|
|
674
|
+
Host before recording:
|
|
576
675
|
|
|
577
|
-
|
|
676
|
+
```text
|
|
677
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
678
|
+
│ Engineering [ Start Recording ] Chat People │
|
|
679
|
+
└───────────────────────────────────────────────────────────────┘
|
|
680
|
+
```
|
|
578
681
|
|
|
579
|
-
|
|
682
|
+
Host while recording:
|
|
580
683
|
|
|
581
|
-
|
|
684
|
+
```text
|
|
685
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
686
|
+
│ Engineering 🔴 Recording [ Stop ] Chat People │
|
|
687
|
+
└───────────────────────────────────────────────────────────────┘
|
|
688
|
+
```
|
|
582
689
|
|
|
583
|
-
|
|
690
|
+
Other participants should see the recording status but should not automatically receive the host's recording controls.
|
|
584
691
|
|
|
585
|
-
|
|
586
|
-
|
|
692
|
+
### Important recording rule
|
|
693
|
+
|
|
694
|
+
Do not assume that clicking the button means recording has started.
|
|
695
|
+
|
|
696
|
+
The UI should use:
|
|
697
|
+
|
|
698
|
+
```ts
|
|
699
|
+
onRecordingStarted;
|
|
587
700
|
```
|
|
588
701
|
|
|
589
|
-
|
|
702
|
+
as the authoritative start event and:
|
|
590
703
|
|
|
591
704
|
```ts
|
|
592
|
-
|
|
705
|
+
onRecordingStopped;
|
|
593
706
|
```
|
|
594
707
|
|
|
595
|
-
|
|
708
|
+
as the authoritative stop event.
|
|
596
709
|
|
|
597
|
-
|
|
710
|
+
Recommended state:
|
|
598
711
|
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
712
|
+
```tsx
|
|
713
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
714
|
+
|
|
715
|
+
useMeeting({
|
|
716
|
+
onRecordingStarted: () => {
|
|
717
|
+
setIsRecording(true);
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
onRecordingStopped: () => {
|
|
721
|
+
setIsRecording(false);
|
|
722
|
+
},
|
|
723
|
+
});
|
|
611
724
|
```
|
|
612
725
|
|
|
613
|
-
|
|
726
|
+
This prevents the UI from claiming that a recording is active when the SFU has not confirmed it.
|
|
614
727
|
|
|
615
728
|
---
|
|
616
729
|
|
|
617
|
-
#
|
|
730
|
+
# Private rooms and join approval
|
|
618
731
|
|
|
619
|
-
|
|
732
|
+
Private rooms can require host approval.
|
|
733
|
+
|
|
734
|
+
A host can listen for entry requests:
|
|
620
735
|
|
|
621
736
|
```tsx
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
}
|
|
737
|
+
useMeeting({
|
|
738
|
+
onEntryRequested: (request) => {
|
|
739
|
+
console.log("Join request:", request);
|
|
740
|
+
},
|
|
741
|
+
});
|
|
626
742
|
```
|
|
627
743
|
|
|
628
744
|
Approve:
|
|
629
745
|
|
|
630
746
|
```tsx
|
|
631
|
-
|
|
747
|
+
const { approveJoinRequest } = useMeeting();
|
|
748
|
+
|
|
749
|
+
approveJoinRequest(requestId);
|
|
632
750
|
```
|
|
633
751
|
|
|
634
752
|
Reject:
|
|
635
753
|
|
|
636
754
|
```tsx
|
|
637
|
-
|
|
755
|
+
const { rejectJoinRequest } = useMeeting();
|
|
756
|
+
|
|
757
|
+
rejectJoinRequest(requestId);
|
|
638
758
|
```
|
|
639
759
|
|
|
640
|
-
The SFU remains authoritative
|
|
760
|
+
The SDK exposes the approval flow, while the SFU remains authoritative for whether a participant is admitted.
|
|
641
761
|
|
|
642
762
|
---
|
|
643
763
|
|
|
644
|
-
#
|
|
764
|
+
# Reconnection and participant presence
|
|
645
765
|
|
|
646
|
-
|
|
766
|
+
The SDK distinguishes a temporary connection problem from an intentional leave.
|
|
647
767
|
|
|
648
|
-
|
|
768
|
+
A participant can be:
|
|
649
769
|
|
|
650
|
-
```
|
|
651
|
-
|
|
770
|
+
```ts
|
|
771
|
+
"connected";
|
|
772
|
+
"reconnecting";
|
|
773
|
+
"disconnected";
|
|
652
774
|
```
|
|
653
775
|
|
|
654
|
-
|
|
776
|
+
Recommended UI:
|
|
655
777
|
|
|
656
778
|
```tsx
|
|
657
|
-
|
|
779
|
+
if (participant.connectionState === "reconnecting") {
|
|
780
|
+
return <ReconnectingBadge />;
|
|
781
|
+
}
|
|
658
782
|
```
|
|
659
783
|
|
|
660
|
-
|
|
784
|
+
A temporary network interruption should not immediately remove a participant from the UI.
|
|
661
785
|
|
|
662
|
-
|
|
663
|
-
|
|
786
|
+
The SDK attempts to reconnect and resume the same logical participant session.
|
|
787
|
+
|
|
788
|
+
Conceptually:
|
|
789
|
+
|
|
790
|
+
```text
|
|
791
|
+
Network interruption
|
|
792
|
+
↓
|
|
793
|
+
WebSocket disconnect
|
|
794
|
+
↓
|
|
795
|
+
reconnecting
|
|
796
|
+
↓
|
|
797
|
+
refresh/reconnect
|
|
798
|
+
↓
|
|
799
|
+
resume
|
|
800
|
+
↓
|
|
801
|
+
publisher/subscriber recreated
|
|
802
|
+
↓
|
|
803
|
+
media subscriptions restored
|
|
804
|
+
↓
|
|
805
|
+
connected
|
|
664
806
|
```
|
|
665
807
|
|
|
666
|
-
|
|
808
|
+
This is why applications should render `reconnecting` as a state rather than treating every WebSocket close as a participant leaving.
|
|
809
|
+
|
|
810
|
+
---
|
|
811
|
+
|
|
812
|
+
# Stable participant identity
|
|
813
|
+
|
|
814
|
+
The SDK stores a stable browser participant ID using:
|
|
667
815
|
|
|
668
816
|
```text
|
|
669
|
-
|
|
670
|
-
↓
|
|
671
|
-
start_recording
|
|
672
|
-
↓
|
|
673
|
-
Rust SFU RecordingManager
|
|
674
|
-
↓
|
|
675
|
-
recording_started
|
|
817
|
+
defcomm:participant_id
|
|
676
818
|
```
|
|
677
819
|
|
|
678
|
-
|
|
820
|
+
This prevents a browser reload or temporary reconnect from unnecessarily generating a completely new participant identity.
|
|
821
|
+
|
|
822
|
+
Room resume state uses:
|
|
679
823
|
|
|
680
824
|
```text
|
|
681
|
-
|
|
682
|
-
↓
|
|
683
|
-
stop_recording
|
|
684
|
-
↓
|
|
685
|
-
RecordingManager finalizes
|
|
686
|
-
↓
|
|
687
|
-
recording_stopped
|
|
825
|
+
defcomm:sfu:resume:<roomCode>
|
|
688
826
|
```
|
|
689
827
|
|
|
690
|
-
The
|
|
828
|
+
The SDK treats temporary network loss differently from an intentional `leave()`.
|
|
829
|
+
|
|
830
|
+
Applications should use:
|
|
831
|
+
|
|
832
|
+
```tsx
|
|
833
|
+
await leave();
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
for an intentional departure instead of manually closing the WebSocket.
|
|
691
837
|
|
|
692
838
|
---
|
|
693
839
|
|
|
694
|
-
#
|
|
840
|
+
# Leaving a meeting
|
|
695
841
|
|
|
696
|
-
|
|
842
|
+
Use:
|
|
697
843
|
|
|
698
844
|
```tsx
|
|
699
|
-
const
|
|
700
|
-
|
|
845
|
+
const { leave } = useMeeting();
|
|
846
|
+
|
|
847
|
+
await leave();
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
Do not implement the Leave button by doing:
|
|
851
|
+
|
|
852
|
+
```ts
|
|
853
|
+
websocket.close();
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
or by directly destroying the SDK's PeerConnections.
|
|
857
|
+
|
|
858
|
+
The SDK owns cleanup and the SFU leave lifecycle.
|
|
859
|
+
|
|
860
|
+
---
|
|
861
|
+
|
|
862
|
+
# Room management
|
|
863
|
+
|
|
864
|
+
Room management is available through `useMeeting()`:
|
|
865
|
+
|
|
866
|
+
```tsx
|
|
867
|
+
const { createRoom, getRoom, deleteRoom } = useMeeting();
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
### Create
|
|
871
|
+
|
|
872
|
+
```tsx
|
|
873
|
+
await createRoom({
|
|
874
|
+
name: "Product Review",
|
|
875
|
+
description: "Weekly product review",
|
|
701
876
|
capacity: 100,
|
|
702
|
-
is_private:
|
|
877
|
+
is_private: false,
|
|
703
878
|
});
|
|
704
879
|
```
|
|
705
880
|
|
|
706
|
-
Get
|
|
881
|
+
### Get
|
|
707
882
|
|
|
708
883
|
```tsx
|
|
709
|
-
|
|
884
|
+
await getRoom(roomCode);
|
|
710
885
|
```
|
|
711
886
|
|
|
712
|
-
Delete
|
|
887
|
+
### Delete
|
|
713
888
|
|
|
714
889
|
```tsx
|
|
715
890
|
await deleteRoom(roomCode);
|
|
716
891
|
```
|
|
717
892
|
|
|
718
|
-
|
|
893
|
+
---
|
|
894
|
+
|
|
895
|
+
# Meeting preview
|
|
896
|
+
|
|
897
|
+
The SDK exports:
|
|
898
|
+
|
|
899
|
+
```tsx
|
|
900
|
+
import { useMeetingPreview } from "@afosecure/meetingsdk";
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
Use preview when the application needs a pre-join screen for room information or local setup.
|
|
904
|
+
|
|
905
|
+
The actual live meeting begins when `join()` connects the SDK to the SFU.
|
|
719
906
|
|
|
720
907
|
---
|
|
721
908
|
|
|
722
|
-
#
|
|
909
|
+
# Chat
|
|
723
910
|
|
|
724
|
-
The
|
|
911
|
+
The SDK exposes the current chat interface through:
|
|
725
912
|
|
|
726
|
-
```
|
|
727
|
-
|
|
913
|
+
```tsx
|
|
914
|
+
const { sendMessage } = useMeeting();
|
|
728
915
|
```
|
|
729
916
|
|
|
730
|
-
|
|
917
|
+
Send:
|
|
731
918
|
|
|
732
|
-
|
|
919
|
+
```tsx
|
|
920
|
+
sendMessage({
|
|
921
|
+
message: "Hello everyone",
|
|
922
|
+
});
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
The input type is:
|
|
926
|
+
|
|
927
|
+
```ts
|
|
928
|
+
type ChatInput = {
|
|
929
|
+
message: string;
|
|
930
|
+
reply_to?: {
|
|
931
|
+
id: string;
|
|
932
|
+
name: string;
|
|
933
|
+
} | null;
|
|
934
|
+
target?: string | null;
|
|
935
|
+
};
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
The SDK also exposes:
|
|
939
|
+
|
|
940
|
+
```tsx
|
|
941
|
+
usePubSub("SECURE_CHAT");
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
when an application wants the Pub/Sub style interface.
|
|
945
|
+
|
|
946
|
+
Example:
|
|
947
|
+
|
|
948
|
+
```tsx
|
|
949
|
+
const chat = usePubSub("SECURE_CHAT");
|
|
950
|
+
|
|
951
|
+
chat.publish({
|
|
952
|
+
message: "Hello",
|
|
953
|
+
});
|
|
954
|
+
```
|
|
733
955
|
|
|
734
956
|
---
|
|
735
957
|
|
|
736
|
-
#
|
|
958
|
+
# Error handling
|
|
737
959
|
|
|
738
|
-
|
|
960
|
+
Use the SDK error event:
|
|
739
961
|
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
962
|
+
```tsx
|
|
963
|
+
useMeeting({
|
|
964
|
+
onError: (error) => {
|
|
965
|
+
console.error(error);
|
|
966
|
+
},
|
|
967
|
+
});
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
Errors contain:
|
|
971
|
+
|
|
972
|
+
```ts
|
|
973
|
+
type SDKError = {
|
|
974
|
+
code: string;
|
|
975
|
+
message: string;
|
|
976
|
+
roomId?: string | null;
|
|
977
|
+
userId?: string;
|
|
978
|
+
raw?: any;
|
|
979
|
+
recoverable?: boolean;
|
|
980
|
+
};
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
A useful UI rule is:
|
|
759
984
|
|
|
760
985
|
```text
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
---
|
|
788
|
-
|
|
789
|
-
# 20. Recommended React structure
|
|
986
|
+
recoverable = true
|
|
987
|
+
↓
|
|
988
|
+
show retry/reconnecting UI
|
|
989
|
+
|
|
990
|
+
recoverable = false
|
|
991
|
+
↓
|
|
992
|
+
show a blocking error or return to the appropriate screen
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
Always wrap user-triggered asynchronous operations when appropriate:
|
|
996
|
+
|
|
997
|
+
```tsx
|
|
998
|
+
async function handleJoin() {
|
|
999
|
+
try {
|
|
1000
|
+
await join();
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
console.error("Unable to join meeting", error);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
# Recommended DEFCOMM Meet structure
|
|
1010
|
+
|
|
1011
|
+
A production DEFCOMM Meet page can be structured like this:
|
|
790
1012
|
|
|
791
1013
|
```text
|
|
792
1014
|
MeetingPage
|
|
793
1015
|
│
|
|
794
1016
|
└── MeetingProvider
|
|
795
1017
|
│
|
|
796
|
-
├──
|
|
797
|
-
│ ├──
|
|
798
|
-
│ ├──
|
|
799
|
-
│
|
|
1018
|
+
├── MeetingNav
|
|
1019
|
+
│ ├── Meeting name
|
|
1020
|
+
│ ├── Recording state/control
|
|
1021
|
+
│ ├── Chat
|
|
1022
|
+
│ └── Participants
|
|
800
1023
|
│
|
|
801
1024
|
├── MeetingStage
|
|
1025
|
+
│ │
|
|
802
1026
|
│ ├── ScreenShareStage
|
|
803
|
-
│
|
|
804
|
-
│
|
|
805
|
-
|
|
806
|
-
│
|
|
807
|
-
│
|
|
808
|
-
│
|
|
809
|
-
│
|
|
810
|
-
│
|
|
811
|
-
│
|
|
1027
|
+
│ │
|
|
1028
|
+
│ └── ParticipantGrid
|
|
1029
|
+
│ └── ParticipantTile[]
|
|
1030
|
+
│ ├── Video
|
|
1031
|
+
│ ├── Name
|
|
1032
|
+
│ ├── Mic state
|
|
1033
|
+
│ ├── Camera state
|
|
1034
|
+
│ ├── Active speaker state
|
|
1035
|
+
│ └── Reconnecting state
|
|
812
1036
|
│
|
|
813
1037
|
└── MeetingControls
|
|
814
|
-
├──
|
|
1038
|
+
├── Microphone
|
|
815
1039
|
├── Camera
|
|
816
1040
|
├── Screen share
|
|
817
|
-
├── Record
|
|
818
1041
|
└── Leave
|
|
819
1042
|
```
|
|
820
1043
|
|
|
821
|
-
The
|
|
1044
|
+
The application should consume SDK state rather than recreating WebRTC state locally.
|
|
822
1045
|
|
|
823
1046
|
---
|
|
824
1047
|
|
|
825
|
-
#
|
|
1048
|
+
# Recommended participant tile
|
|
826
1049
|
|
|
827
|
-
|
|
1050
|
+
A participant tile should combine the SDK's states:
|
|
828
1051
|
|
|
829
1052
|
```tsx
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1053
|
+
function ParticipantTile({ participant }: { participant: Participant }) {
|
|
1054
|
+
const { activeSpeakerIds } = useMeeting();
|
|
1055
|
+
|
|
1056
|
+
const isSpeaking = activeSpeakerIds.includes(participant.id);
|
|
1057
|
+
|
|
1058
|
+
const isReconnecting = participant.connectionState === "reconnecting";
|
|
1059
|
+
|
|
1060
|
+
const micEnabled = participant.media?.micEnabled ?? false;
|
|
1061
|
+
|
|
1062
|
+
const camEnabled = participant.media?.camEnabled ?? false;
|
|
1063
|
+
|
|
1064
|
+
const isScreenSharing = participant.media?.isScreenSharing ?? false;
|
|
1065
|
+
|
|
1066
|
+
return (
|
|
1067
|
+
<div
|
|
1068
|
+
className={
|
|
1069
|
+
isSpeaking ? "participant-tile active-speaker" : "participant-tile"
|
|
1070
|
+
}
|
|
1071
|
+
>
|
|
1072
|
+
{isReconnecting && <div className="reconnecting">Reconnecting...</div>}
|
|
1073
|
+
|
|
1074
|
+
{/* Remote/local media */}
|
|
1075
|
+
|
|
1076
|
+
<div className="participant-name">{participant.name}</div>
|
|
1077
|
+
|
|
1078
|
+
<div className="participant-status">
|
|
1079
|
+
{!micEnabled && <span>Muted</span>}
|
|
1080
|
+
{!camEnabled && <span>Camera off</span>}
|
|
1081
|
+
{isScreenSharing && <span>Sharing</span>}
|
|
1082
|
+
</div>
|
|
1083
|
+
</div>
|
|
1084
|
+
);
|
|
834
1085
|
}
|
|
835
1086
|
```
|
|
836
1087
|
|
|
837
|
-
|
|
1088
|
+
The important design principle is:
|
|
838
1089
|
|
|
839
1090
|
```text
|
|
840
|
-
|
|
841
|
-
Reconnecting
|
|
842
|
-
Connected
|
|
843
|
-
Disconnected
|
|
844
|
-
Join rejected
|
|
1091
|
+
SDK state → UI
|
|
845
1092
|
```
|
|
846
1093
|
|
|
847
|
-
|
|
1094
|
+
not:
|
|
1095
|
+
|
|
1096
|
+
```text
|
|
1097
|
+
UI → WebRTC internals
|
|
1098
|
+
```
|
|
848
1099
|
|
|
849
1100
|
---
|
|
850
1101
|
|
|
851
|
-
#
|
|
1102
|
+
# Public exports
|
|
1103
|
+
|
|
1104
|
+
The SDK currently exports:
|
|
1105
|
+
|
|
1106
|
+
```ts
|
|
1107
|
+
// Core
|
|
1108
|
+
VideoSDKCore;
|
|
1109
|
+
SFUClient;
|
|
1110
|
+
MeetingState;
|
|
1111
|
+
ActiveSpeakerDetector;
|
|
1112
|
+
|
|
1113
|
+
// React
|
|
1114
|
+
MeetingProvider;
|
|
1115
|
+
useMeetingContext;
|
|
1116
|
+
useMeeting;
|
|
1117
|
+
useParticipants;
|
|
1118
|
+
useLocalParticipant;
|
|
1119
|
+
useRemoteMedia;
|
|
1120
|
+
useMeetingPreview;
|
|
1121
|
+
|
|
1122
|
+
// Types
|
|
1123
|
+
Participant;
|
|
1124
|
+
ChatInput;
|
|
1125
|
+
RecordingInfo;
|
|
1126
|
+
MeetingConfig;
|
|
1127
|
+
SFUConnectionState;
|
|
1128
|
+
TrackInfo;
|
|
1129
|
+
ParticipantInfo;
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
Most DEFCOMM Meet application developers should only need the React API:
|
|
1133
|
+
|
|
1134
|
+
```tsx
|
|
1135
|
+
import {
|
|
1136
|
+
MeetingProvider,
|
|
1137
|
+
useMeeting,
|
|
1138
|
+
useParticipants,
|
|
1139
|
+
useLocalParticipant,
|
|
1140
|
+
useRemoteMedia,
|
|
1141
|
+
useMeetingPreview,
|
|
1142
|
+
} from "@afosecure/meetingsdk";
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
Direct use of `VideoSDKCore` or `SFUClient` is intended for advanced integrations.
|
|
852
1146
|
|
|
853
|
-
|
|
1147
|
+
---
|
|
854
1148
|
|
|
855
|
-
|
|
1149
|
+
# Important implementation rules
|
|
1150
|
+
|
|
1151
|
+
## 1. Do not build a mesh
|
|
1152
|
+
|
|
1153
|
+
Do not create:
|
|
856
1154
|
|
|
857
1155
|
```text
|
|
858
|
-
A ↔ B
|
|
859
|
-
A ↔ C
|
|
860
|
-
B ↔ C
|
|
1156
|
+
Participant A ↔ Participant B
|
|
1157
|
+
Participant A ↔ Participant C
|
|
1158
|
+
Participant B ↔ Participant C
|
|
861
1159
|
```
|
|
862
1160
|
|
|
863
|
-
|
|
1161
|
+
The SDK is designed for:
|
|
864
1162
|
|
|
865
1163
|
```text
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1164
|
+
Participants
|
|
1165
|
+
│
|
|
1166
|
+
▼
|
|
1167
|
+
DEFCOMM SFU
|
|
1168
|
+
│
|
|
1169
|
+
▼
|
|
1170
|
+
Participants
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
---
|
|
1174
|
+
|
|
1175
|
+
## 2. Do not manipulate the SDK's PeerConnections
|
|
1176
|
+
|
|
1177
|
+
Applications should not directly access:
|
|
1178
|
+
|
|
1179
|
+
```ts
|
|
1180
|
+
RTCPeerConnection;
|
|
1181
|
+
RTCSessionDescription;
|
|
1182
|
+
RTCIceCandidate;
|
|
1183
|
+
RTCRtpSender;
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
for normal meeting functionality.
|
|
1187
|
+
|
|
1188
|
+
The SDK owns those objects.
|
|
1189
|
+
|
|
1190
|
+
---
|
|
1191
|
+
|
|
1192
|
+
## 3. Do not infer remote mute state from media silence
|
|
1193
|
+
|
|
1194
|
+
Use:
|
|
1195
|
+
|
|
1196
|
+
```ts
|
|
1197
|
+
participant.media?.micEnabled;
|
|
1198
|
+
participant.media?.camEnabled;
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
The SFU signaling state is authoritative.
|
|
1202
|
+
|
|
1203
|
+
---
|
|
1204
|
+
|
|
1205
|
+
## 4. Do not implement speaker detection in the application
|
|
1206
|
+
|
|
1207
|
+
Use:
|
|
1208
|
+
|
|
1209
|
+
```tsx
|
|
1210
|
+
const { activeSpeakerIds } = useMeeting();
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
The SDK owns audio analysis and active-speaker state.
|
|
885
1214
|
|
|
886
|
-
|
|
1215
|
+
---
|
|
1216
|
+
|
|
1217
|
+
## 5. Do not use browser recording for meeting recording
|
|
1218
|
+
|
|
1219
|
+
Use:
|
|
1220
|
+
|
|
1221
|
+
```tsx
|
|
1222
|
+
await startRecording();
|
|
1223
|
+
await stopRecording();
|
|
1224
|
+
```
|
|
1225
|
+
|
|
1226
|
+
Meeting recording is server-side.
|
|
1227
|
+
|
|
1228
|
+
---
|
|
1229
|
+
|
|
1230
|
+
## 6. Do not manually close the WebSocket to leave
|
|
1231
|
+
|
|
1232
|
+
Use:
|
|
1233
|
+
|
|
1234
|
+
```tsx
|
|
1235
|
+
await leave();
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
This allows the SDK to perform the intended cleanup and leave lifecycle.
|
|
1239
|
+
|
|
1240
|
+
---
|
|
1241
|
+
|
|
1242
|
+
## 7. Do not immediately remove reconnecting participants
|
|
1243
|
+
|
|
1244
|
+
Use:
|
|
1245
|
+
|
|
1246
|
+
```ts
|
|
1247
|
+
participant.connectionState;
|
|
1248
|
+
```
|
|
887
1249
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
- reconnect/resume orchestration
|
|
894
|
-
- React bindings
|
|
1250
|
+
and show:
|
|
1251
|
+
|
|
1252
|
+
```text
|
|
1253
|
+
Reconnecting...
|
|
1254
|
+
```
|
|
895
1255
|
|
|
896
|
-
|
|
1256
|
+
during transient failures.
|
|
897
1257
|
|
|
898
1258
|
---
|
|
899
1259
|
|
|
900
|
-
|
|
1260
|
+
## 8. Keep screen share separate from camera
|
|
1261
|
+
|
|
1262
|
+
Use:
|
|
1263
|
+
|
|
1264
|
+
```ts
|
|
1265
|
+
participant.media.screenStream;
|
|
1266
|
+
participant.media.screenTrack;
|
|
1267
|
+
participant.media.isScreenSharing;
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
instead of replacing the camera stream.
|
|
1271
|
+
|
|
1272
|
+
---
|
|
1273
|
+
|
|
1274
|
+
# Build and publish
|
|
1275
|
+
|
|
1276
|
+
Install dependencies:
|
|
901
1277
|
|
|
902
1278
|
```bash
|
|
903
1279
|
npm install
|
|
1280
|
+
```
|
|
1281
|
+
|
|
1282
|
+
Build:
|
|
1283
|
+
|
|
1284
|
+
```bash
|
|
904
1285
|
npm run build
|
|
905
1286
|
```
|
|
906
1287
|
|
|
907
|
-
|
|
1288
|
+
Before publishing a new SDK version, verify:
|
|
908
1289
|
|
|
909
1290
|
```text
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1291
|
+
✓ TypeScript build succeeds
|
|
1292
|
+
✓ React integration works
|
|
1293
|
+
✓ Join/leave works
|
|
1294
|
+
✓ Camera works
|
|
1295
|
+
✓ Microphone works
|
|
1296
|
+
✓ Remote audio/video works
|
|
1297
|
+
✓ Screen sharing works
|
|
1298
|
+
✓ Active speaker detection works
|
|
1299
|
+
✓ Reconnection works
|
|
1300
|
+
✓ Private-room admission works
|
|
1301
|
+
✓ Recording start/stop works
|
|
1302
|
+
✓ Recording callbacks fire
|
|
1303
|
+
✓ Production WSS/API configuration works
|
|
915
1304
|
```
|
|
916
1305
|
|
|
917
|
-
|
|
1306
|
+
---
|
|
918
1307
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
---
|
|
942
|
-
|
|
943
|
-
# 24. Production checklist
|
|
944
|
-
|
|
945
|
-
Before shipping:
|
|
946
|
-
|
|
947
|
-
- [ ] Configure `apiBase` and `wsBase`.
|
|
948
|
-
- [ ] Use HTTPS/WSS in production.
|
|
949
|
-
- [ ] Render hooks inside `MeetingProvider`.
|
|
950
|
-
- [ ] Call `join()` from a user action.
|
|
951
|
-
- [ ] Handle camera/microphone permissions.
|
|
952
|
-
- [ ] Render remote video and audio separately.
|
|
953
|
-
- [ ] Keep video elements muted when remote audio is attached separately.
|
|
954
|
-
- [ ] Use SFU signaling for remote mic/camera icons.
|
|
955
|
-
- [ ] Render screen sharing separately from camera.
|
|
956
|
-
- [ ] Show `Reconnecting...` during transient network loss.
|
|
957
|
-
- [ ] Do not remove a participant immediately on temporary connection loss.
|
|
958
|
-
- [ ] Use `leave()` for intentional departure.
|
|
959
|
-
- [ ] Do not create mesh PeerConnections.
|
|
960
|
-
- [ ] Restrict recording controls to authorized users.
|
|
961
|
-
- [ ] Treat recording as server-side.
|
|
962
|
-
- [ ] Test reload, reconnect, leave, and rejoin.
|
|
963
|
-
- [ ] Run `npm run build` before publishing.
|
|
964
|
-
- [ ] Keep the SDK and SFU signaling protocol versions compatible.
|
|
965
|
-
|
|
966
|
-
---
|
|
967
|
-
|
|
968
|
-
# 25. Developer mental model
|
|
969
|
-
|
|
970
|
-
The simplest mental model is:
|
|
1308
|
+
# DEFCOMM Meet integration principle
|
|
1309
|
+
|
|
1310
|
+
The purpose of this SDK is to make the DEFCOMM meeting experience feel like a single integrated platform.
|
|
1311
|
+
|
|
1312
|
+
A consumer should be able to think in terms of:
|
|
1313
|
+
|
|
1314
|
+
```tsx
|
|
1315
|
+
const {
|
|
1316
|
+
join,
|
|
1317
|
+
leave,
|
|
1318
|
+
toggleMic,
|
|
1319
|
+
toggleCam,
|
|
1320
|
+
startScreenShare,
|
|
1321
|
+
stopScreenShare,
|
|
1322
|
+
startRecording,
|
|
1323
|
+
stopRecording,
|
|
1324
|
+
participants,
|
|
1325
|
+
activeSpeakerIds,
|
|
1326
|
+
} = useMeeting();
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
rather than:
|
|
971
1330
|
|
|
972
1331
|
```text
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
├── Rooms
|
|
990
|
-
├── Participants
|
|
991
|
-
├── Admission
|
|
992
|
-
├── SDP / ICE
|
|
993
|
-
├── Track registry
|
|
994
|
-
├── MediaRouter
|
|
995
|
-
├── RTCP feedback
|
|
996
|
-
├── Screen-share state
|
|
997
|
-
└── Recording
|
|
998
|
-
```
|
|
999
|
-
|
|
1000
|
-
**Key rule:** the application owns presentation, the SDK owns browser WebRTC, and the SFU owns authoritative meeting/media state.
|
|
1332
|
+
WebSocket messages
|
|
1333
|
+
SDP offers/answers
|
|
1334
|
+
ICE candidates
|
|
1335
|
+
PeerConnections
|
|
1336
|
+
RTP tracks
|
|
1337
|
+
SSRCs
|
|
1338
|
+
audio analysers
|
|
1339
|
+
reconnect timers
|
|
1340
|
+
resume tokens
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
Those implementation details belong inside the SDK/SFU layer.
|
|
1344
|
+
|
|
1345
|
+
**DEFCOMM Meet owns the meeting experience.
|
|
1346
|
+
The SDK owns the browser media experience.
|
|
1347
|
+
The SFU owns the authoritative meeting/media infrastructure.**
|