@defcomm/meetingsdk 0.6.1 → 0.6.3
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 +742 -899
- package/dist/index.d.mts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +112 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +112 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,1347 +1,1190 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @defcomm/meetingsdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A React and TypeScript SDK for building real-time audio/video meetings with DefComm Meet infrastructure.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The SDK provides the meeting connection, participant state, media controls, chat, reactions, admission control, moderation, screen sharing, recording, transcript/summary access, network-quality state, and participant lifecycle events needed to build a complete meeting experience without implementing the SFU/WebSocket protocol yourself.
|
|
6
6
|
|
|
7
|
-
|
|
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
|
|
7
|
+
## Live sample
|
|
18
8
|
|
|
19
|
-
|
|
9
|
+
A complete example application is available at:
|
|
20
10
|
|
|
21
|
-
|
|
11
|
+
**https://meet.defcomm.ng**
|
|
22
12
|
|
|
23
|
-
|
|
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)
|
|
13
|
+
Use the sample as a reference for a full meeting UI, including pre-join device selection, waiting-room/admission behavior, participant lists, moderation, chat, reactions, screen sharing, and meeting controls.
|
|
50
14
|
|
|
51
15
|
---
|
|
52
16
|
|
|
53
|
-
|
|
17
|
+
## Installation
|
|
54
18
|
|
|
55
|
-
|
|
19
|
+
Install the package from npm:
|
|
56
20
|
|
|
57
|
-
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @defcomm/meetingsdk
|
|
23
|
+
```
|
|
58
24
|
|
|
59
|
-
|
|
25
|
+
or:
|
|
60
26
|
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
27
|
+
```bash
|
|
28
|
+
npm install @defcomm/meetingsdk
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The SDK is intended for browser/client-side React applications because it uses browser media APIs such as `navigator.mediaDevices`, `MediaStream`, and WebRTC.
|
|
32
|
+
|
|
33
|
+
### React / Next.js
|
|
34
|
+
|
|
35
|
+
For Next.js App Router, components that use the SDK should run on the client:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
"use client";
|
|
65
39
|
```
|
|
66
40
|
|
|
67
|
-
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## What the SDK does
|
|
44
|
+
|
|
45
|
+
At a high level, your application supplies a participant identity and an access token, creates a `MeetingProvider`, and uses the exported hooks to control the meeting.
|
|
68
46
|
|
|
69
47
|
```text
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
│
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
│
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
└───────────────────────────────┘
|
|
102
|
-
```
|
|
103
|
-
|
|
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
|
|
48
|
+
Your application
|
|
49
|
+
│
|
|
50
|
+
▼
|
|
51
|
+
MeetingProvider
|
|
52
|
+
│
|
|
53
|
+
├── useMeeting()
|
|
54
|
+
├── useParticipants()
|
|
55
|
+
├── useLocalParticipant()
|
|
56
|
+
├── useMeetingPreview()
|
|
57
|
+
├── useRemoteMedia()
|
|
58
|
+
├── useNetworkQuality()
|
|
59
|
+
└── usePubSub()
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
DefComm Meeting SDK
|
|
63
|
+
│
|
|
64
|
+
├── REST API
|
|
65
|
+
└── SFU / WebSocket
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The SDK owns the media/session state and translates SFU events into React-friendly state and callbacks.
|
|
137
69
|
|
|
138
70
|
---
|
|
139
71
|
|
|
140
|
-
#
|
|
72
|
+
# 1. Authentication and meeting tokens
|
|
141
73
|
|
|
142
|
-
|
|
143
|
-
|
|
74
|
+
The meeting client needs a token that authorizes the participant to connect to a room.
|
|
75
|
+
|
|
76
|
+
The recommended architecture is:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
Your backend
|
|
80
|
+
│
|
|
81
|
+
├── authenticates your application user
|
|
82
|
+
├── decides which room the user may access
|
|
83
|
+
└── obtains/creates a DefComm meeting token
|
|
84
|
+
│
|
|
85
|
+
▼
|
|
86
|
+
Your React app
|
|
87
|
+
│
|
|
88
|
+
▼
|
|
89
|
+
<MeetingProvider />
|
|
144
90
|
```
|
|
145
91
|
|
|
146
|
-
|
|
92
|
+
Do **not** put a server-only API secret in browser code. Your backend should handle server credentials and issue the short-lived/access-scoped token that the browser uses for the meeting.
|
|
93
|
+
|
|
94
|
+
Your application is responsible for deciding who the participant is and for obtaining the token securely.
|
|
147
95
|
|
|
148
96
|
---
|
|
149
97
|
|
|
150
|
-
#
|
|
98
|
+
# 2. Basic integration
|
|
99
|
+
|
|
100
|
+
The smallest useful integration looks like this.
|
|
151
101
|
|
|
152
|
-
|
|
102
|
+
## Step 1: Create the provider
|
|
153
103
|
|
|
154
104
|
```tsx
|
|
155
|
-
|
|
105
|
+
"use client";
|
|
156
106
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
107
|
+
import { MeetingProvider } from "@defcomm/meetingsdk";
|
|
108
|
+
|
|
109
|
+
export default function MeetingShell() {
|
|
110
|
+
return (
|
|
111
|
+
<MeetingProvider
|
|
112
|
+
config={{
|
|
113
|
+
roomId: "ROOM_ID",
|
|
114
|
+
participantId: "USER_ID",
|
|
115
|
+
name: "Cornel",
|
|
116
|
+
token: "MEETING_ACCESS_TOKEN",
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
<Meeting />
|
|
120
|
+
</MeetingProvider>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
163
123
|
```
|
|
164
124
|
|
|
165
|
-
|
|
125
|
+
`MeetingProvider` supplies the meeting SDK to all child components.
|
|
166
126
|
|
|
167
|
-
|
|
168
|
-
const { getRoom } = useMeeting();
|
|
127
|
+
### `config`
|
|
169
128
|
|
|
170
|
-
|
|
171
|
-
|
|
129
|
+
| Property | Required | Description |
|
|
130
|
+
|---|---:|---|
|
|
131
|
+
| `roomId` | Yes | DefComm room ID/code used for the meeting. |
|
|
132
|
+
| `participantId` | Yes | Your application's stable participant/user identifier. |
|
|
133
|
+
| `name` | Yes | Display name shown to other participants. |
|
|
134
|
+
| `token` | Yes | Access token generated/issued for the participant. |
|
|
135
|
+
| `audioMuted` | No | Start with microphone disabled. |
|
|
136
|
+
| `videoMuted` | No | Start with camera disabled. |
|
|
137
|
+
| `cameraDeviceId` | No | Camera to use when creating the initial local media stream. |
|
|
138
|
+
| `microphoneDeviceId` | No | Microphone to use when creating the initial local media stream. |
|
|
139
|
+
|
|
140
|
+
---
|
|
172
141
|
|
|
173
|
-
|
|
142
|
+
## Step 2: Join the meeting
|
|
174
143
|
|
|
175
144
|
```tsx
|
|
176
|
-
|
|
145
|
+
"use client";
|
|
146
|
+
|
|
147
|
+
import { useMeeting } from "@defcomm/meetingsdk";
|
|
177
148
|
|
|
178
|
-
|
|
149
|
+
export function JoinButton() {
|
|
150
|
+
const { join } = useMeeting();
|
|
151
|
+
|
|
152
|
+
const handleJoin = async () => {
|
|
153
|
+
await join();
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
return <button onClick={handleJoin}>Join meeting</button>;
|
|
157
|
+
}
|
|
179
158
|
```
|
|
180
159
|
|
|
181
|
-
|
|
160
|
+
The provider's original `config` supplies the participant ID and token. `join()` can also receive per-join overrides for supported meeting configuration fields.
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
await join({
|
|
164
|
+
roomId: "ROOM_ID",
|
|
165
|
+
name: "Cornel",
|
|
166
|
+
cameraDeviceId: selectedCameraId,
|
|
167
|
+
microphoneDeviceId: selectedMicrophoneId,
|
|
168
|
+
audioMuted: false,
|
|
169
|
+
videoMuted: false,
|
|
170
|
+
});
|
|
171
|
+
```
|
|
182
172
|
|
|
183
173
|
---
|
|
184
174
|
|
|
185
|
-
#
|
|
175
|
+
# 3. Participant lifecycle events
|
|
186
176
|
|
|
187
|
-
|
|
177
|
+
Participant lifecycle events are first-class SDK events. Developers do not need to compare the participant list before and after every update.
|
|
188
178
|
|
|
189
|
-
|
|
190
|
-
import { MeetingProvider, useMeeting } from "@afosecure/meetingsdk";
|
|
179
|
+
The most useful events are:
|
|
191
180
|
|
|
192
|
-
|
|
193
|
-
|
|
181
|
+
- `onParticipantJoined`
|
|
182
|
+
- `onParticipantLeft`
|
|
183
|
+
- `onChatMessage`
|
|
194
184
|
|
|
195
|
-
|
|
196
|
-
<div>
|
|
197
|
-
<button onClick={() => join()}>Join meeting</button>
|
|
185
|
+
Example:
|
|
198
186
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
)
|
|
202
|
-
|
|
187
|
+
```tsx
|
|
188
|
+
useMeeting({
|
|
189
|
+
onParticipantJoined: (participant) => {
|
|
190
|
+
console.log("Participant joined:", participant);
|
|
191
|
+
},
|
|
203
192
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}}
|
|
213
|
-
>
|
|
214
|
-
<MeetingRoom />
|
|
215
|
-
</MeetingProvider>
|
|
216
|
-
);
|
|
217
|
-
}
|
|
193
|
+
onParticipantLeft: (participant) => {
|
|
194
|
+
console.log("Participant left:", participant);
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
onChatMessage: (message) => {
|
|
198
|
+
console.log("Chat message:", message);
|
|
199
|
+
},
|
|
200
|
+
});
|
|
218
201
|
```
|
|
219
202
|
|
|
220
|
-
|
|
203
|
+
These callbacks are especially useful for application-level notifications, sounds, analytics, toasts, logging, and custom UI.
|
|
221
204
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
205
|
+
## Join sound example
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
const playSound = (src: string) => {
|
|
209
|
+
const audio = new Audio(src);
|
|
210
|
+
audio.play().catch(() => {
|
|
211
|
+
// Browsers can block autoplay until the user interacts with the page.
|
|
212
|
+
});
|
|
229
213
|
};
|
|
214
|
+
|
|
215
|
+
useMeeting({
|
|
216
|
+
onParticipantJoined: (participant) => {
|
|
217
|
+
console.log(`${participant.name ?? "A participant"} joined`);
|
|
218
|
+
playSound("/audio/join.mp3");
|
|
219
|
+
},
|
|
220
|
+
});
|
|
230
221
|
```
|
|
231
222
|
|
|
232
|
-
|
|
223
|
+
## Message notification example
|
|
233
224
|
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
↓
|
|
241
|
-
join()
|
|
242
|
-
↓
|
|
243
|
-
Browser requests camera/microphone
|
|
244
|
-
↓
|
|
245
|
-
SDK connects to SFU
|
|
246
|
-
↓
|
|
247
|
-
Publisher PeerConnection
|
|
248
|
-
↓
|
|
249
|
-
Subscriber PeerConnection
|
|
250
|
-
↓
|
|
251
|
-
Remote tracks become available
|
|
225
|
+
```tsx
|
|
226
|
+
useMeeting({
|
|
227
|
+
onChatMessage: (message) => {
|
|
228
|
+
playSound("/audio/message.mp3");
|
|
229
|
+
},
|
|
230
|
+
});
|
|
252
231
|
```
|
|
253
232
|
|
|
254
|
-
|
|
233
|
+
Because the callback represents an incoming SDK chat event, the application can decide how it wants to present the notification instead of the SDK forcing a particular sound or UI.
|
|
255
234
|
|
|
256
235
|
---
|
|
257
236
|
|
|
258
|
-
#
|
|
237
|
+
# 4. React hooks
|
|
238
|
+
|
|
239
|
+
## `useMeeting()`
|
|
259
240
|
|
|
260
|
-
|
|
241
|
+
The primary hook for meeting actions, state, and events.
|
|
261
242
|
|
|
262
243
|
```tsx
|
|
263
244
|
const {
|
|
264
245
|
join,
|
|
265
246
|
leave,
|
|
247
|
+
room,
|
|
248
|
+
localParticipant,
|
|
249
|
+
participants,
|
|
250
|
+
messages,
|
|
251
|
+
presenterId,
|
|
252
|
+
activeSpeakerIds,
|
|
253
|
+
networkQuality,
|
|
254
|
+
recording,
|
|
266
255
|
|
|
267
256
|
toggleMic,
|
|
268
257
|
toggleCam,
|
|
269
|
-
|
|
270
258
|
startScreenShare,
|
|
271
259
|
stopScreenShare,
|
|
272
260
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
getRecordingStatus,
|
|
261
|
+
sendMessage,
|
|
262
|
+
sendReaction,
|
|
276
263
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
presenterId,
|
|
281
|
-
room,
|
|
264
|
+
getMediaDevices,
|
|
265
|
+
selectCamera,
|
|
266
|
+
selectMicrophone,
|
|
282
267
|
|
|
283
268
|
approveJoinRequest,
|
|
284
269
|
rejectJoinRequest,
|
|
270
|
+
cancelJoinRequest,
|
|
285
271
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
The hook also supports event handlers:
|
|
291
|
-
|
|
292
|
-
```tsx
|
|
293
|
-
const meeting = useMeeting({
|
|
294
|
-
onError: (error) => {
|
|
295
|
-
console.error(error);
|
|
296
|
-
},
|
|
297
|
-
|
|
298
|
-
onActiveSpeakersChanged: (participantIds) => {
|
|
299
|
-
console.log("Active speakers:", participantIds);
|
|
300
|
-
},
|
|
272
|
+
muteAllAudio,
|
|
273
|
+
muteParticipant,
|
|
274
|
+
removeParticipant,
|
|
301
275
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
276
|
+
startRecording,
|
|
277
|
+
stopRecording,
|
|
278
|
+
getRecordingStatus,
|
|
305
279
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
});
|
|
280
|
+
getJoinState,
|
|
281
|
+
getTranscript,
|
|
282
|
+
getMeetingSummary,
|
|
283
|
+
} = useMeeting();
|
|
310
284
|
```
|
|
311
285
|
|
|
312
|
-
|
|
286
|
+
### Event callbacks available on `useMeeting()`
|
|
313
287
|
|
|
314
288
|
```tsx
|
|
315
|
-
|
|
289
|
+
useMeeting({
|
|
290
|
+
onError,
|
|
291
|
+
onEntryRequested,
|
|
292
|
+
onEntryResponded,
|
|
293
|
+
onJoinRequestCanceled,
|
|
294
|
+
onParticipantRemoved,
|
|
295
|
+
onParticipantPermissionsChanged,
|
|
296
|
+
onMeetingLeft,
|
|
297
|
+
onTrackStateChanged,
|
|
298
|
+
onRecordingStarted,
|
|
299
|
+
onRecordingStopped,
|
|
300
|
+
onReaction,
|
|
301
|
+
onActiveSpeakersChanged,
|
|
302
|
+
onConnectionStateChanged,
|
|
303
|
+
onNetworkQuality,
|
|
304
|
+
onParticipantJoined,
|
|
305
|
+
onParticipantLeft,
|
|
306
|
+
onChatMessage,
|
|
307
|
+
});
|
|
316
308
|
```
|
|
317
309
|
|
|
318
|
-
Use event callbacks when the application needs to perform an additional side effect.
|
|
319
|
-
|
|
320
310
|
---
|
|
321
311
|
|
|
322
|
-
|
|
312
|
+
## `useParticipants()`
|
|
323
313
|
|
|
324
|
-
|
|
314
|
+
Returns the current participants as an array.
|
|
325
315
|
|
|
326
316
|
```tsx
|
|
327
|
-
|
|
317
|
+
const participants = useParticipants();
|
|
328
318
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
);
|
|
339
|
-
}
|
|
319
|
+
return (
|
|
320
|
+
<ul>
|
|
321
|
+
{participants.map((participant) => (
|
|
322
|
+
<li key={participant.id}>
|
|
323
|
+
{participant.name}
|
|
324
|
+
</li>
|
|
325
|
+
))}
|
|
326
|
+
</ul>
|
|
327
|
+
);
|
|
340
328
|
```
|
|
341
329
|
|
|
342
|
-
A participant contains:
|
|
330
|
+
A participant contains information such as:
|
|
343
331
|
|
|
344
332
|
```ts
|
|
345
|
-
|
|
333
|
+
{
|
|
346
334
|
id: string;
|
|
347
335
|
name?: string;
|
|
348
336
|
isHost?: boolean;
|
|
337
|
+
isModerator?: boolean;
|
|
338
|
+
permissions?: any;
|
|
349
339
|
isLocal?: boolean;
|
|
350
340
|
isPresenter?: boolean;
|
|
351
|
-
|
|
352
341
|
connectionState?: "connected" | "reconnecting" | "disconnected";
|
|
353
|
-
|
|
354
342
|
media?: {
|
|
355
343
|
stream?: MediaStream | null;
|
|
356
344
|
screenStream?: MediaStream | null;
|
|
357
|
-
|
|
358
345
|
cameraTrack?: MediaStreamTrack;
|
|
359
346
|
screenTrack?: MediaStreamTrack;
|
|
360
347
|
audioTrack?: MediaStreamTrack;
|
|
361
|
-
|
|
362
348
|
micEnabled: boolean;
|
|
363
349
|
camEnabled: boolean;
|
|
364
350
|
isScreenSharing: boolean;
|
|
365
|
-
|
|
366
|
-
remoteScreenStreamId?: string;
|
|
367
|
-
cameraStreamId?: string;
|
|
368
351
|
};
|
|
369
|
-
}
|
|
352
|
+
}
|
|
370
353
|
```
|
|
371
354
|
|
|
372
355
|
---
|
|
373
356
|
|
|
374
|
-
|
|
357
|
+
## `useLocalParticipant()`
|
|
375
358
|
|
|
376
|
-
|
|
359
|
+
Returns the local participant and a convenient callback ref for rendering the local media stream.
|
|
377
360
|
|
|
378
361
|
```tsx
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
function LocalVideo() {
|
|
382
|
-
const { participant, videoRef } = useLocalParticipant();
|
|
362
|
+
const { participant, videoRef } = useLocalParticipant();
|
|
383
363
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
participant?.media?.camEnabled;
|
|
393
|
-
participant?.media?.isScreenSharing;
|
|
364
|
+
return (
|
|
365
|
+
<video
|
|
366
|
+
ref={videoRef}
|
|
367
|
+
autoPlay
|
|
368
|
+
muted
|
|
369
|
+
playsInline
|
|
370
|
+
/>
|
|
371
|
+
);
|
|
394
372
|
```
|
|
395
373
|
|
|
396
|
-
Use these values to render local microphone/camera state.
|
|
397
|
-
|
|
398
374
|
---
|
|
399
375
|
|
|
400
|
-
#
|
|
376
|
+
# 5. Pre-join camera and microphone selection
|
|
401
377
|
|
|
402
|
-
Use `
|
|
378
|
+
Use `getMediaDevices()` before entering the meeting.
|
|
403
379
|
|
|
404
380
|
```tsx
|
|
405
|
-
|
|
381
|
+
const { getMediaDevices, selectCamera, selectMicrophone } = useMeeting();
|
|
406
382
|
|
|
407
|
-
|
|
408
|
-
const { videoRef, audioRef, isCamActive, isMicEnabled } =
|
|
409
|
-
useRemoteMedia(participantId);
|
|
383
|
+
const devices = await getMediaDevices();
|
|
410
384
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
385
|
+
const cameras = devices.filter((device) => device.kind === "videoinput");
|
|
386
|
+
const microphones = devices.filter(
|
|
387
|
+
(device) => device.kind === "audioinput",
|
|
388
|
+
);
|
|
389
|
+
```
|
|
414
390
|
|
|
415
|
-
|
|
391
|
+
Select a camera:
|
|
416
392
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
</div>
|
|
420
|
-
);
|
|
421
|
-
}
|
|
393
|
+
```tsx
|
|
394
|
+
await selectCamera(cameraDeviceId);
|
|
422
395
|
```
|
|
423
396
|
|
|
424
|
-
|
|
397
|
+
Select a microphone:
|
|
425
398
|
|
|
426
|
-
|
|
399
|
+
```tsx
|
|
400
|
+
await selectMicrophone(microphoneDeviceId);
|
|
401
|
+
```
|
|
427
402
|
|
|
428
|
-
|
|
429
|
-
- `<audio>` handles remote audio
|
|
403
|
+
The SDK replaces the active media track when the meeting is already connected. The same camera-selection API can also be used by a pre-join UI so the application preview can follow the selected device.
|
|
430
404
|
|
|
431
|
-
|
|
405
|
+
For browsers where device labels are initially empty, request media permission before calling `enumerateDevices()` so the browser can expose useful labels.
|
|
432
406
|
|
|
433
407
|
---
|
|
434
408
|
|
|
435
|
-
# Microphone and camera
|
|
409
|
+
# 6. Microphone and camera controls
|
|
436
410
|
|
|
437
|
-
|
|
411
|
+
Toggle the local microphone:
|
|
438
412
|
|
|
439
413
|
```tsx
|
|
440
|
-
const { toggleMic
|
|
414
|
+
const { toggleMic } = useMeeting();
|
|
415
|
+
|
|
416
|
+
<button onClick={toggleMic}>Toggle microphone</button>;
|
|
441
417
|
```
|
|
442
418
|
|
|
443
|
-
|
|
419
|
+
Toggle the local camera:
|
|
444
420
|
|
|
445
421
|
```tsx
|
|
446
|
-
|
|
447
|
-
Microphone
|
|
448
|
-
</button>
|
|
422
|
+
const { toggleCam } = useMeeting();
|
|
449
423
|
|
|
450
|
-
<button onClick={toggleCam}>
|
|
451
|
-
Camera
|
|
452
|
-
</button>
|
|
424
|
+
<button onClick={toggleCam}>Toggle camera</button>;
|
|
453
425
|
```
|
|
454
426
|
|
|
455
|
-
The
|
|
427
|
+
The local participant state exposes `media.micEnabled` and `media.camEnabled`, so the button can reflect the current state.
|
|
428
|
+
|
|
429
|
+
---
|
|
456
430
|
|
|
457
|
-
|
|
431
|
+
# 7. Screen sharing
|
|
458
432
|
|
|
459
|
-
|
|
433
|
+
Start sharing:
|
|
460
434
|
|
|
461
435
|
```tsx
|
|
462
|
-
|
|
463
|
-
|
|
436
|
+
const { startScreenShare } = useMeeting();
|
|
437
|
+
|
|
438
|
+
const stream = await startScreenShare();
|
|
464
439
|
```
|
|
465
440
|
|
|
466
|
-
|
|
441
|
+
Stop sharing:
|
|
442
|
+
|
|
443
|
+
```tsx
|
|
444
|
+
const { stopScreenShare } = useMeeting();
|
|
445
|
+
|
|
446
|
+
stopScreenShare();
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
The browser controls the permission prompt for screen capture.
|
|
467
450
|
|
|
468
451
|
---
|
|
469
452
|
|
|
470
|
-
#
|
|
453
|
+
# 8. Chat
|
|
471
454
|
|
|
472
|
-
|
|
455
|
+
Send a message:
|
|
456
|
+
|
|
457
|
+
```tsx
|
|
458
|
+
const { sendMessage } = useMeeting();
|
|
473
459
|
|
|
474
|
-
|
|
460
|
+
sendMessage({
|
|
461
|
+
message: "Hello everyone!",
|
|
462
|
+
});
|
|
463
|
+
```
|
|
475
464
|
|
|
476
|
-
|
|
465
|
+
Reply to a message:
|
|
477
466
|
|
|
478
467
|
```tsx
|
|
479
|
-
|
|
468
|
+
sendMessage({
|
|
469
|
+
message: "Thanks!",
|
|
470
|
+
reply_to: {
|
|
471
|
+
id: message.id,
|
|
472
|
+
name: message.sender_name ?? "Participant",
|
|
473
|
+
},
|
|
474
|
+
});
|
|
480
475
|
```
|
|
481
476
|
|
|
482
|
-
`
|
|
477
|
+
`ChatMessage` includes:
|
|
483
478
|
|
|
484
479
|
```ts
|
|
485
|
-
|
|
480
|
+
{
|
|
481
|
+
id: string;
|
|
482
|
+
text: string;
|
|
483
|
+
sender_id: string;
|
|
484
|
+
sender_name?: string;
|
|
485
|
+
timestamp: number;
|
|
486
|
+
reply_to?: { id: string; name: string } | null;
|
|
487
|
+
target?: string | null;
|
|
488
|
+
}
|
|
486
489
|
```
|
|
487
490
|
|
|
488
|
-
|
|
491
|
+
For real-time notifications, prefer `onChatMessage` rather than watching the `messages` array manually.
|
|
492
|
+
|
|
493
|
+
---
|
|
489
494
|
|
|
490
|
-
|
|
495
|
+
# 9. Reactions
|
|
496
|
+
|
|
497
|
+
Send a reaction:
|
|
491
498
|
|
|
492
499
|
```tsx
|
|
493
|
-
|
|
494
|
-
const { activeSpeakerIds } = useMeeting();
|
|
500
|
+
const { sendReaction } = useMeeting();
|
|
495
501
|
|
|
496
|
-
|
|
502
|
+
sendReaction("👍");
|
|
503
|
+
```
|
|
497
504
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
)
|
|
503
|
-
|
|
505
|
+
Listen for reactions:
|
|
506
|
+
|
|
507
|
+
```tsx
|
|
508
|
+
useMeeting({
|
|
509
|
+
onReaction: (reaction) => {
|
|
510
|
+
console.log(reaction.emoji);
|
|
511
|
+
},
|
|
512
|
+
});
|
|
504
513
|
```
|
|
505
514
|
|
|
506
|
-
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
# 10. Private meetings and waiting-room admission
|
|
507
518
|
|
|
508
|
-
|
|
519
|
+
The SDK supports rooms where a participant must be approved before entering.
|
|
520
|
+
|
|
521
|
+
Check a participant's current join state:
|
|
509
522
|
|
|
510
523
|
```tsx
|
|
511
|
-
const {
|
|
524
|
+
const { getJoinState } = useMeeting();
|
|
525
|
+
|
|
526
|
+
const state = await getJoinState(roomCode);
|
|
527
|
+
|
|
528
|
+
if (state.status === "ready") {
|
|
529
|
+
// The participant can proceed into the meeting.
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (state.status === "approval_required") {
|
|
533
|
+
// Show your waiting-room UI.
|
|
534
|
+
}
|
|
535
|
+
```
|
|
512
536
|
|
|
537
|
+
Possible states include:
|
|
538
|
+
|
|
539
|
+
```ts
|
|
513
540
|
{
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
key={participant.id}
|
|
517
|
-
participant={participant}
|
|
518
|
-
isActiveSpeaker={activeSpeakerIds.includes(participant.id)}
|
|
519
|
-
/>
|
|
520
|
-
));
|
|
541
|
+
status: "ready";
|
|
542
|
+
reason: "admitted" | "open" | "moderator";
|
|
521
543
|
}
|
|
522
544
|
```
|
|
523
545
|
|
|
524
|
-
|
|
546
|
+
or:
|
|
525
547
|
|
|
526
|
-
|
|
548
|
+
```ts
|
|
549
|
+
{
|
|
550
|
+
status: "approval_required";
|
|
551
|
+
}
|
|
552
|
+
```
|
|
527
553
|
|
|
528
|
-
|
|
554
|
+
### Moderator admission events
|
|
529
555
|
|
|
530
556
|
```tsx
|
|
531
557
|
useMeeting({
|
|
532
|
-
|
|
533
|
-
console.log("
|
|
558
|
+
onEntryRequested: (request) => {
|
|
559
|
+
console.log("Join request:", request);
|
|
560
|
+
},
|
|
561
|
+
onEntryResponded: (payload, decision) => {
|
|
562
|
+
console.log("Admission decision:", decision);
|
|
534
563
|
},
|
|
535
564
|
});
|
|
536
565
|
```
|
|
537
566
|
|
|
538
|
-
|
|
567
|
+
Approve:
|
|
539
568
|
|
|
540
|
-
|
|
569
|
+
```tsx
|
|
570
|
+
await approveJoinRequest(requestId);
|
|
571
|
+
```
|
|
541
572
|
|
|
542
|
-
|
|
573
|
+
Reject:
|
|
543
574
|
|
|
544
|
-
```
|
|
545
|
-
|
|
575
|
+
```tsx
|
|
576
|
+
await rejectJoinRequest(requestId);
|
|
546
577
|
```
|
|
547
578
|
|
|
548
|
-
|
|
579
|
+
Cancel the current participant's request:
|
|
549
580
|
|
|
550
|
-
```
|
|
551
|
-
|
|
581
|
+
```tsx
|
|
582
|
+
await cancelJoinRequest();
|
|
552
583
|
```
|
|
553
584
|
|
|
554
|
-
This allows the UI to handle overlapping speech naturally.
|
|
555
|
-
|
|
556
585
|
---
|
|
557
586
|
|
|
558
|
-
#
|
|
587
|
+
# 11. Moderation
|
|
559
588
|
|
|
560
|
-
|
|
589
|
+
Moderator/host applications can use the SDK's moderation methods where the connected participant has permission.
|
|
561
590
|
|
|
562
|
-
|
|
563
|
-
const { startScreenShare } = useMeeting();
|
|
591
|
+
Mute everyone:
|
|
564
592
|
|
|
565
|
-
|
|
593
|
+
```tsx
|
|
594
|
+
await muteAllAudio();
|
|
566
595
|
```
|
|
567
596
|
|
|
568
|
-
|
|
597
|
+
Mute one participant:
|
|
569
598
|
|
|
570
599
|
```tsx
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
stopScreenShare();
|
|
600
|
+
await muteParticipant(participantId);
|
|
574
601
|
```
|
|
575
602
|
|
|
576
|
-
|
|
603
|
+
Remove a participant:
|
|
577
604
|
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
participant.media?.screenTrack;
|
|
581
|
-
participant.media?.isScreenSharing;
|
|
605
|
+
```tsx
|
|
606
|
+
await removeParticipant(participantId);
|
|
582
607
|
```
|
|
583
608
|
|
|
584
|
-
|
|
609
|
+
Listen for permission changes:
|
|
585
610
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
│ │
|
|
593
|
-
│ │
|
|
594
|
-
└──────────────────────────────────────────┘
|
|
595
|
-
|
|
596
|
-
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
597
|
-
│ Alice │ │ Bob │ │ Charlie │
|
|
598
|
-
└─────────┘ └─────────┘ └─────────┘
|
|
611
|
+
```tsx
|
|
612
|
+
useMeeting({
|
|
613
|
+
onParticipantPermissionsChanged: (participantId, permissions) => {
|
|
614
|
+
console.log(participantId, permissions);
|
|
615
|
+
},
|
|
616
|
+
});
|
|
599
617
|
```
|
|
600
618
|
|
|
601
|
-
|
|
619
|
+
The server remains the authority for moderation permissions. The SDK should not be treated as the security boundary by itself.
|
|
602
620
|
|
|
603
621
|
---
|
|
604
622
|
|
|
605
|
-
#
|
|
623
|
+
# 12. Persistent moderators
|
|
606
624
|
|
|
607
|
-
|
|
625
|
+
The SDK can retrieve and manage moderator assignments for a room.
|
|
608
626
|
|
|
609
|
-
|
|
627
|
+
```tsx
|
|
628
|
+
const { getModerators, addModerator, removeModerator } = useMeeting();
|
|
610
629
|
|
|
611
|
-
|
|
630
|
+
const moderators = await getModerators(roomCode);
|
|
612
631
|
|
|
613
|
-
|
|
614
|
-
const { startRecording } = useMeeting();
|
|
632
|
+
await addModerator(roomCode, userId);
|
|
615
633
|
|
|
616
|
-
await
|
|
634
|
+
await removeModerator(roomCode, userId);
|
|
617
635
|
```
|
|
618
636
|
|
|
619
|
-
|
|
637
|
+
Your backend/SFU should enforce which participant is allowed to perform each operation.
|
|
620
638
|
|
|
621
|
-
|
|
622
|
-
const { stopRecording } = useMeeting();
|
|
639
|
+
---
|
|
623
640
|
|
|
624
|
-
|
|
625
|
-
```
|
|
641
|
+
# 13. Recording
|
|
626
642
|
|
|
627
|
-
|
|
643
|
+
Start:
|
|
628
644
|
|
|
629
645
|
```tsx
|
|
630
|
-
|
|
646
|
+
await startRecording();
|
|
647
|
+
```
|
|
631
648
|
|
|
632
|
-
|
|
649
|
+
Stop:
|
|
633
650
|
|
|
634
|
-
|
|
651
|
+
```tsx
|
|
652
|
+
await stopRecording();
|
|
635
653
|
```
|
|
636
654
|
|
|
637
|
-
|
|
655
|
+
Check status:
|
|
638
656
|
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
recording_id: string;
|
|
642
|
-
started_at?: string;
|
|
643
|
-
stopped_at?: string;
|
|
644
|
-
active?: boolean;
|
|
645
|
-
};
|
|
657
|
+
```tsx
|
|
658
|
+
const status = await getRecordingStatus();
|
|
646
659
|
```
|
|
647
660
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
The SDK exposes:
|
|
661
|
+
Listen for recording events:
|
|
651
662
|
|
|
652
663
|
```tsx
|
|
653
664
|
useMeeting({
|
|
654
665
|
onRecordingStarted: (recording) => {
|
|
655
666
|
console.log("Recording started", recording);
|
|
656
667
|
},
|
|
657
|
-
|
|
658
668
|
onRecordingStopped: (recording) => {
|
|
659
669
|
console.log("Recording stopped", recording);
|
|
660
670
|
},
|
|
661
671
|
});
|
|
662
672
|
```
|
|
663
673
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
The meeting application can place the recording control in the meeting navbar:
|
|
674
|
+
---
|
|
667
675
|
|
|
668
|
-
|
|
669
|
-
┌───────────────────────────────────────────────────────────────┐
|
|
670
|
-
│ Engineering 🔴 REC Chat People │
|
|
671
|
-
└───────────────────────────────────────────────────────────────┘
|
|
672
|
-
```
|
|
676
|
+
# 14. Transcripts and meeting summaries
|
|
673
677
|
|
|
674
|
-
|
|
678
|
+
Retrieve transcript data:
|
|
675
679
|
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
│ Engineering [ Start Recording ] Chat People │
|
|
679
|
-
└───────────────────────────────────────────────────────────────┘
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
Host while recording:
|
|
680
|
+
```tsx
|
|
681
|
+
const response = await getTranscript(roomCode, sessionId);
|
|
683
682
|
|
|
684
|
-
|
|
685
|
-
┌───────────────────────────────────────────────────────────────┐
|
|
686
|
-
│ Engineering 🔴 Recording [ Stop ] Chat People │
|
|
687
|
-
└───────────────────────────────────────────────────────────────┘
|
|
683
|
+
console.log(response.segments);
|
|
688
684
|
```
|
|
689
685
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
### Important recording rule
|
|
686
|
+
Retrieve a meeting summary:
|
|
693
687
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
The UI should use:
|
|
688
|
+
```tsx
|
|
689
|
+
const response = await getMeetingSummary(roomCode, sessionId);
|
|
697
690
|
|
|
698
|
-
|
|
699
|
-
onRecordingStarted;
|
|
691
|
+
console.log(response.summary);
|
|
700
692
|
```
|
|
701
693
|
|
|
702
|
-
|
|
694
|
+
The SDK exposes transcript segments, transcript sessions, and meeting summaries as typed objects.
|
|
703
695
|
|
|
704
|
-
|
|
705
|
-
onRecordingStopped;
|
|
706
|
-
```
|
|
696
|
+
---
|
|
707
697
|
|
|
708
|
-
|
|
698
|
+
# 15. Network quality
|
|
709
699
|
|
|
710
|
-
|
|
700
|
+
Read network quality through the hook or subscribe to updates.
|
|
711
701
|
|
|
712
702
|
```tsx
|
|
713
|
-
const [isRecording, setIsRecording] = useState(false);
|
|
714
|
-
|
|
715
703
|
useMeeting({
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
onRecordingStopped: () => {
|
|
721
|
-
setIsRecording(false);
|
|
704
|
+
onNetworkQuality: (quality) => {
|
|
705
|
+
console.log("Network quality:", quality.quality);
|
|
706
|
+
console.log("Packet loss:", quality.packet_loss_percent);
|
|
722
707
|
},
|
|
723
708
|
});
|
|
724
709
|
```
|
|
725
710
|
|
|
726
|
-
|
|
711
|
+
Available quality values are normalized into values such as `excellent`, `good`, `fair`, and `poor`.
|
|
727
712
|
|
|
728
713
|
---
|
|
729
714
|
|
|
730
|
-
#
|
|
731
|
-
|
|
732
|
-
Private rooms can require host approval.
|
|
715
|
+
# 16. Connection state
|
|
733
716
|
|
|
734
|
-
|
|
717
|
+
Listen for the meeting connection lifecycle:
|
|
735
718
|
|
|
736
719
|
```tsx
|
|
737
720
|
useMeeting({
|
|
738
|
-
|
|
739
|
-
console.log("
|
|
721
|
+
onConnectionStateChanged: (state) => {
|
|
722
|
+
console.log("Connection:", state);
|
|
740
723
|
},
|
|
741
724
|
});
|
|
742
725
|
```
|
|
743
726
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
```tsx
|
|
747
|
-
const { approveJoinRequest } = useMeeting();
|
|
748
|
-
|
|
749
|
-
approveJoinRequest(requestId);
|
|
750
|
-
```
|
|
751
|
-
|
|
752
|
-
Reject:
|
|
727
|
+
The SDK exposes states including:
|
|
753
728
|
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
729
|
+
```text
|
|
730
|
+
idle
|
|
731
|
+
connecting
|
|
732
|
+
joining
|
|
733
|
+
waiting_approval
|
|
734
|
+
connected
|
|
735
|
+
reconnecting
|
|
736
|
+
leaving
|
|
737
|
+
disconnected
|
|
758
738
|
```
|
|
759
739
|
|
|
760
|
-
|
|
740
|
+
This is useful for displaying connecting/reconnecting UI and for deciding when meeting controls should be enabled.
|
|
761
741
|
|
|
762
742
|
---
|
|
763
743
|
|
|
764
|
-
#
|
|
765
|
-
|
|
766
|
-
The SDK distinguishes a temporary connection problem from an intentional leave.
|
|
767
|
-
|
|
768
|
-
A participant can be:
|
|
744
|
+
# 17. Pub/Sub
|
|
769
745
|
|
|
770
|
-
|
|
771
|
-
"connected";
|
|
772
|
-
"reconnecting";
|
|
773
|
-
"disconnected";
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
Recommended UI:
|
|
746
|
+
The SDK includes typed Pub/Sub helpers for supported real-time topics.
|
|
777
747
|
|
|
778
748
|
```tsx
|
|
779
|
-
|
|
780
|
-
return <ReconnectingBadge />;
|
|
781
|
-
}
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
A temporary network interruption should not immediately remove a participant from the UI.
|
|
785
|
-
|
|
786
|
-
The SDK attempts to reconnect and resume the same logical participant session.
|
|
787
|
-
|
|
788
|
-
Conceptually:
|
|
749
|
+
const reactions = usePubSub("REACTION");
|
|
789
750
|
|
|
790
|
-
|
|
791
|
-
|
|
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
|
|
751
|
+
reactions.publish({
|
|
752
|
+
emoji: "🎉",
|
|
753
|
+
});
|
|
806
754
|
```
|
|
807
755
|
|
|
808
|
-
|
|
756
|
+
The currently exposed typed topics include:
|
|
809
757
|
|
|
810
|
-
|
|
758
|
+
- `SECURE_CHAT`
|
|
759
|
+
- `REACTION`
|
|
811
760
|
|
|
812
|
-
|
|
761
|
+
You can also listen at the provider level:
|
|
813
762
|
|
|
814
|
-
|
|
763
|
+
```tsx
|
|
764
|
+
const { onPubSubMessage } = useMeeting();
|
|
815
765
|
|
|
816
|
-
|
|
817
|
-
|
|
766
|
+
useEffect(() => {
|
|
767
|
+
return onPubSubMessage("REACTION", (message) => {
|
|
768
|
+
console.log(message.data.emoji);
|
|
769
|
+
});
|
|
770
|
+
}, [onPubSubMessage]);
|
|
818
771
|
```
|
|
819
772
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
Room resume state uses:
|
|
823
|
-
|
|
824
|
-
```text
|
|
825
|
-
defcomm:sfu:resume:<roomCode>
|
|
826
|
-
```
|
|
773
|
+
---
|
|
827
774
|
|
|
828
|
-
|
|
775
|
+
# 18. A complete minimal meeting component
|
|
829
776
|
|
|
830
|
-
|
|
777
|
+
This is a useful starting point for an application developer.
|
|
831
778
|
|
|
832
779
|
```tsx
|
|
833
|
-
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
for an intentional departure instead of manually closing the WebSocket.
|
|
837
|
-
|
|
838
|
-
---
|
|
839
|
-
|
|
840
|
-
# Leaving a meeting
|
|
780
|
+
"use client";
|
|
841
781
|
|
|
842
|
-
|
|
782
|
+
import { useMeeting, useParticipants } from "@defcomm/meetingsdk";
|
|
843
783
|
|
|
844
|
-
|
|
845
|
-
const
|
|
784
|
+
export function Meeting() {
|
|
785
|
+
const participants = useParticipants();
|
|
846
786
|
|
|
847
|
-
|
|
848
|
-
|
|
787
|
+
const {
|
|
788
|
+
join,
|
|
789
|
+
leave,
|
|
790
|
+
toggleMic,
|
|
791
|
+
toggleCam,
|
|
792
|
+
sendMessage,
|
|
793
|
+
startScreenShare,
|
|
794
|
+
stopScreenShare,
|
|
795
|
+
} = useMeeting({
|
|
796
|
+
onParticipantJoined: (participant) => {
|
|
797
|
+
console.log("Joined:", participant.name);
|
|
798
|
+
},
|
|
799
|
+
onParticipantLeft: (participant) => {
|
|
800
|
+
console.log("Left:", participant.name);
|
|
801
|
+
},
|
|
802
|
+
onChatMessage: (message) => {
|
|
803
|
+
console.log("Message:", message.text);
|
|
804
|
+
},
|
|
805
|
+
onConnectionStateChanged: (state) => {
|
|
806
|
+
console.log("State:", state);
|
|
807
|
+
},
|
|
808
|
+
});
|
|
849
809
|
|
|
850
|
-
|
|
810
|
+
return (
|
|
811
|
+
<div>
|
|
812
|
+
<button onClick={() => void join()}>Join</button>
|
|
813
|
+
<button onClick={() => toggleMic()}>Mic</button>
|
|
814
|
+
<button onClick={() => toggleCam()}>Camera</button>
|
|
815
|
+
<button onClick={() => void startScreenShare()}>
|
|
816
|
+
Share screen
|
|
817
|
+
</button>
|
|
818
|
+
<button onClick={() => stopScreenShare()}>Stop sharing</button>
|
|
819
|
+
<button
|
|
820
|
+
onClick={() =>
|
|
821
|
+
sendMessage({ message: "Hello from the meeting" })
|
|
822
|
+
}
|
|
823
|
+
>
|
|
824
|
+
Send message
|
|
825
|
+
</button>
|
|
826
|
+
<button onClick={() => leave()}>Leave</button>
|
|
851
827
|
|
|
852
|
-
|
|
853
|
-
|
|
828
|
+
<p>Participants: {participants.length}</p>
|
|
829
|
+
</div>
|
|
830
|
+
);
|
|
831
|
+
}
|
|
854
832
|
```
|
|
855
833
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
The SDK owns cleanup and the SFU leave lifecycle.
|
|
834
|
+
For a production application, add your own pre-join screen, participant tiles, remote media rendering, chat panel, moderation UI, waiting room, error states, and responsive/mobile controls.
|
|
859
835
|
|
|
860
836
|
---
|
|
861
837
|
|
|
862
|
-
#
|
|
863
|
-
|
|
864
|
-
Room management is available through `useMeeting()`:
|
|
838
|
+
# 19. Recommended application structure
|
|
865
839
|
|
|
866
|
-
|
|
867
|
-
const { createRoom, getRoom, deleteRoom } = useMeeting();
|
|
868
|
-
```
|
|
840
|
+
A maintainable application can be organized like this:
|
|
869
841
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
842
|
+
```text
|
|
843
|
+
app/
|
|
844
|
+
├── meeting/
|
|
845
|
+
│ ├── page.tsx
|
|
846
|
+
│ ├── MeetingShell.tsx
|
|
847
|
+
│ ├── PreJoin.tsx
|
|
848
|
+
│ ├── WaitingRoom.tsx
|
|
849
|
+
│ ├── MeetingRoom.tsx
|
|
850
|
+
│ ├── ParticipantGrid.tsx
|
|
851
|
+
│ ├── ParticipantItem.tsx
|
|
852
|
+
│ ├── ChatPanel.tsx
|
|
853
|
+
│ ├── ControlPanel.tsx
|
|
854
|
+
│ └── ModeratorPanel.tsx
|
|
855
|
+
│
|
|
856
|
+
├── api/
|
|
857
|
+
│ └── meeting-token/
|
|
858
|
+
│ └── route.ts
|
|
859
|
+
│
|
|
860
|
+
└── ...
|
|
879
861
|
```
|
|
880
862
|
|
|
881
|
-
|
|
863
|
+
A useful separation is:
|
|
882
864
|
|
|
883
|
-
```
|
|
884
|
-
|
|
865
|
+
```text
|
|
866
|
+
Authentication/token creation → your backend
|
|
867
|
+
Room/meeting UI → your application
|
|
868
|
+
Media/session transport → @defcomm/meetingsdk
|
|
869
|
+
SFU protocol → DefComm infrastructure
|
|
885
870
|
```
|
|
886
871
|
|
|
887
|
-
|
|
872
|
+
---
|
|
888
873
|
|
|
889
|
-
|
|
890
|
-
await deleteRoom(roomCode);
|
|
891
|
-
```
|
|
874
|
+
# 20. Browser permissions
|
|
892
875
|
|
|
893
|
-
|
|
876
|
+
The SDK uses standard browser media APIs. Your application must run in a secure context such as HTTPS for production camera/microphone access.
|
|
894
877
|
|
|
895
|
-
|
|
878
|
+
Users may deny access to the camera or microphone. Always handle rejected media permissions and display a useful recovery message.
|
|
896
879
|
|
|
897
|
-
|
|
880
|
+
Example:
|
|
898
881
|
|
|
899
882
|
```tsx
|
|
900
|
-
|
|
883
|
+
try {
|
|
884
|
+
await join();
|
|
885
|
+
} catch (error) {
|
|
886
|
+
console.error("Unable to join:", error);
|
|
887
|
+
}
|
|
901
888
|
```
|
|
902
889
|
|
|
903
|
-
|
|
890
|
+
Common browser requirements include:
|
|
904
891
|
|
|
905
|
-
|
|
892
|
+
- HTTPS in production
|
|
893
|
+
- Camera permission for video
|
|
894
|
+
- Microphone permission for audio
|
|
895
|
+
- User interaction may be required before playing notification sounds
|
|
896
|
+
- Screen sharing requires browser support and user permission
|
|
906
897
|
|
|
907
898
|
---
|
|
908
899
|
|
|
909
|
-
#
|
|
910
|
-
|
|
911
|
-
The SDK exposes the current chat interface through:
|
|
900
|
+
# 21. Event design guidance
|
|
912
901
|
|
|
913
|
-
|
|
914
|
-
const { sendMessage } = useMeeting();
|
|
915
|
-
```
|
|
902
|
+
Use SDK events for one-time application reactions and hooks/state for rendering.
|
|
916
903
|
|
|
917
|
-
|
|
904
|
+
### Good use of events
|
|
918
905
|
|
|
919
906
|
```tsx
|
|
920
|
-
|
|
921
|
-
|
|
907
|
+
useMeeting({
|
|
908
|
+
onParticipantJoined: () => playSound("/audio/join.mp3"),
|
|
909
|
+
onParticipantLeft: () => showToast("Participant left"),
|
|
910
|
+
onChatMessage: () => playSound("/audio/message.mp3"),
|
|
922
911
|
});
|
|
923
912
|
```
|
|
924
913
|
|
|
925
|
-
|
|
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:
|
|
914
|
+
### Good use of state
|
|
939
915
|
|
|
940
916
|
```tsx
|
|
941
|
-
|
|
917
|
+
const participants = useParticipants();
|
|
918
|
+
const { messages, localParticipant } = useMeeting();
|
|
942
919
|
```
|
|
943
920
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
Example:
|
|
947
|
-
|
|
948
|
-
```tsx
|
|
949
|
-
const chat = usePubSub("SECURE_CHAT");
|
|
950
|
-
|
|
951
|
-
chat.publish({
|
|
952
|
-
message: "Hello",
|
|
953
|
-
});
|
|
954
|
-
```
|
|
921
|
+
This distinction keeps UI rendering predictable and avoids implementing your own event-detection logic in every application.
|
|
955
922
|
|
|
956
923
|
---
|
|
957
924
|
|
|
958
|
-
# Error handling
|
|
925
|
+
# 22. Error handling
|
|
959
926
|
|
|
960
|
-
|
|
927
|
+
Subscribe to the normalized SDK error callback:
|
|
961
928
|
|
|
962
929
|
```tsx
|
|
963
930
|
useMeeting({
|
|
964
931
|
onError: (error) => {
|
|
965
|
-
console.error(error);
|
|
932
|
+
console.error(error.code, error.message);
|
|
966
933
|
},
|
|
967
934
|
});
|
|
968
935
|
```
|
|
969
936
|
|
|
970
|
-
|
|
937
|
+
SDK errors include a stable `code`, a human-readable `message`, and additional context where available.
|
|
938
|
+
|
|
939
|
+
Example shape:
|
|
971
940
|
|
|
972
941
|
```ts
|
|
973
|
-
|
|
942
|
+
{
|
|
974
943
|
code: string;
|
|
975
944
|
message: string;
|
|
976
945
|
roomId?: string | null;
|
|
977
946
|
participantId?: string;
|
|
978
|
-
raw?:
|
|
947
|
+
raw?: unknown;
|
|
979
948
|
recoverable?: boolean;
|
|
980
|
-
};
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
A useful UI rule is:
|
|
984
|
-
|
|
985
|
-
```text
|
|
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
949
|
}
|
|
1005
950
|
```
|
|
1006
951
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
# Recommended DEFCOMM Meet structure
|
|
1010
|
-
|
|
1011
|
-
A production DEFCOMM Meet page can be structured like this:
|
|
1012
|
-
|
|
1013
|
-
```text
|
|
1014
|
-
MeetingPage
|
|
1015
|
-
│
|
|
1016
|
-
└── MeetingProvider
|
|
1017
|
-
│
|
|
1018
|
-
├── MeetingNav
|
|
1019
|
-
│ ├── Meeting name
|
|
1020
|
-
│ ├── Recording state/control
|
|
1021
|
-
│ ├── Chat
|
|
1022
|
-
│ └── Participants
|
|
1023
|
-
│
|
|
1024
|
-
├── MeetingStage
|
|
1025
|
-
│ │
|
|
1026
|
-
│ ├── ScreenShareStage
|
|
1027
|
-
│ │
|
|
1028
|
-
│ └── ParticipantGrid
|
|
1029
|
-
│ └── ParticipantTile[]
|
|
1030
|
-
│ ├── Video
|
|
1031
|
-
│ ├── Name
|
|
1032
|
-
│ ├── Mic state
|
|
1033
|
-
│ ├── Camera state
|
|
1034
|
-
│ ├── Active speaker state
|
|
1035
|
-
│ └── Reconnecting state
|
|
1036
|
-
│
|
|
1037
|
-
└── MeetingControls
|
|
1038
|
-
├── Microphone
|
|
1039
|
-
├── Camera
|
|
1040
|
-
├── Screen share
|
|
1041
|
-
└── Leave
|
|
1042
|
-
```
|
|
1043
|
-
|
|
1044
|
-
The application should consume SDK state rather than recreating WebRTC state locally.
|
|
952
|
+
Applications should show user-friendly UI and log the technical error details for debugging.
|
|
1045
953
|
|
|
1046
954
|
---
|
|
1047
955
|
|
|
1048
|
-
#
|
|
1049
|
-
|
|
1050
|
-
A participant tile should combine the SDK's states:
|
|
956
|
+
# 23. Leaving a meeting
|
|
1051
957
|
|
|
1052
958
|
```tsx
|
|
1053
|
-
|
|
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>}
|
|
959
|
+
const { leave } = useMeeting();
|
|
1073
960
|
|
|
1074
|
-
|
|
961
|
+
leave();
|
|
962
|
+
```
|
|
1075
963
|
|
|
1076
|
-
|
|
964
|
+
You can also listen for the lifecycle event:
|
|
1077
965
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
);
|
|
1085
|
-
}
|
|
966
|
+
```tsx
|
|
967
|
+
useMeeting({
|
|
968
|
+
onMeetingLeft: () => {
|
|
969
|
+
console.log("Meeting ended for this client");
|
|
970
|
+
},
|
|
971
|
+
});
|
|
1086
972
|
```
|
|
1087
973
|
|
|
1088
|
-
|
|
974
|
+
---
|
|
1089
975
|
|
|
1090
|
-
|
|
1091
|
-
SDK state → UI
|
|
1092
|
-
```
|
|
976
|
+
# 24. Security recommendations
|
|
1093
977
|
|
|
1094
|
-
|
|
978
|
+
The SDK is a client library. Security-sensitive authorization must remain enforced by the backend/SFU.
|
|
1095
979
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
980
|
+
Recommended production practices:
|
|
981
|
+
|
|
982
|
+
1. Never expose server API secrets in frontend JavaScript.
|
|
983
|
+
2. Issue room-scoped participant credentials from your backend.
|
|
984
|
+
3. Enforce room membership and moderation permissions on the server.
|
|
985
|
+
4. Do not trust a client-provided `isHost` or `isModerator` value as authorization.
|
|
986
|
+
5. Keep meeting tokens short-lived or otherwise appropriately scoped.
|
|
987
|
+
6. Validate all room and participant operations server-side.
|
|
988
|
+
7. Rate-limit sensitive endpoints such as token creation, join requests, moderation, and recording operations.
|
|
989
|
+
8. Use HTTPS/WSS in production.
|
|
1099
990
|
|
|
1100
991
|
---
|
|
1101
992
|
|
|
1102
|
-
#
|
|
993
|
+
# 25. TypeScript
|
|
1103
994
|
|
|
1104
|
-
The SDK
|
|
995
|
+
The SDK exports the primary public types used by applications, including:
|
|
1105
996
|
|
|
1106
997
|
```ts
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
RecordingInfo;
|
|
1126
|
-
MeetingConfig;
|
|
1127
|
-
SFUConnectionState;
|
|
1128
|
-
TrackInfo;
|
|
1129
|
-
ParticipantInfo;
|
|
1130
|
-
```
|
|
1131
|
-
|
|
1132
|
-
Most DEFCOMM Meet application developers should only need the React API:
|
|
998
|
+
Participant
|
|
999
|
+
ChatInput
|
|
1000
|
+
ChatMessage
|
|
1001
|
+
MeetingConfig
|
|
1002
|
+
Reaction
|
|
1003
|
+
RecordingInfo
|
|
1004
|
+
SFUConnectionState
|
|
1005
|
+
JoinState
|
|
1006
|
+
NetworkQuality
|
|
1007
|
+
TranscriptSegment
|
|
1008
|
+
TranscriptSession
|
|
1009
|
+
TranscriptResponse
|
|
1010
|
+
MeetingSummary
|
|
1011
|
+
PubSubTopic
|
|
1012
|
+
PubSubMessage
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
For example:
|
|
1133
1016
|
|
|
1134
|
-
```
|
|
1135
|
-
import {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
useRemoteMedia,
|
|
1141
|
-
useMeetingPreview,
|
|
1142
|
-
} from "@afosecure/meetingsdk";
|
|
1017
|
+
```ts
|
|
1018
|
+
import type {
|
|
1019
|
+
Participant,
|
|
1020
|
+
ChatMessage,
|
|
1021
|
+
MeetingConfig,
|
|
1022
|
+
} from "@defcomm/meetingsdk";
|
|
1143
1023
|
```
|
|
1144
1024
|
|
|
1145
|
-
Direct use of `VideoSDKCore` or `SFUClient` is intended for advanced integrations.
|
|
1146
|
-
|
|
1147
1025
|
---
|
|
1148
1026
|
|
|
1149
|
-
#
|
|
1027
|
+
# 26. Full sample application
|
|
1150
1028
|
|
|
1151
|
-
|
|
1029
|
+
The recommended way to understand the SDK end-to-end is to compare your integration with the DefComm Meet reference application:
|
|
1152
1030
|
|
|
1153
|
-
|
|
1031
|
+
**https://meet.defcomm.ng**
|
|
1154
1032
|
|
|
1155
|
-
|
|
1156
|
-
Participant A ↔ Participant B
|
|
1157
|
-
Participant A ↔ Participant C
|
|
1158
|
-
Participant B ↔ Participant C
|
|
1159
|
-
```
|
|
1033
|
+
The sample demonstrates how the SDK can be composed into a complete product rather than exposing a single monolithic meeting component.
|
|
1160
1034
|
|
|
1161
|
-
|
|
1035
|
+
Typical flow:
|
|
1162
1036
|
|
|
1163
1037
|
```text
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
RTCPeerConnection;
|
|
1181
|
-
RTCSessionDescription;
|
|
1182
|
-
RTCIceCandidate;
|
|
1183
|
-
RTCRtpSender;
|
|
1038
|
+
Landing / authentication
|
|
1039
|
+
↓
|
|
1040
|
+
Fetch or create room
|
|
1041
|
+
↓
|
|
1042
|
+
Fetch participant meeting token
|
|
1043
|
+
↓
|
|
1044
|
+
Pre-join camera + microphone selection
|
|
1045
|
+
↓
|
|
1046
|
+
Join-state check
|
|
1047
|
+
├── ready ───────────────→ Meeting room
|
|
1048
|
+
│
|
|
1049
|
+
└── approval_required ───→ Waiting room
|
|
1050
|
+
↓
|
|
1051
|
+
approved
|
|
1052
|
+
↓
|
|
1053
|
+
Meeting room
|
|
1184
1054
|
```
|
|
1185
1055
|
|
|
1186
|
-
for normal meeting functionality.
|
|
1187
|
-
|
|
1188
|
-
The SDK owns those objects.
|
|
1189
|
-
|
|
1190
1056
|
---
|
|
1191
1057
|
|
|
1192
|
-
|
|
1058
|
+
# 27. Troubleshooting
|
|
1193
1059
|
|
|
1194
|
-
|
|
1060
|
+
## Camera or microphone list is empty
|
|
1195
1061
|
|
|
1196
|
-
|
|
1197
|
-
participant.media?.micEnabled;
|
|
1198
|
-
participant.media?.camEnabled;
|
|
1199
|
-
```
|
|
1200
|
-
|
|
1201
|
-
The SFU signaling state is authoritative.
|
|
1062
|
+
Call the media permission flow first, then enumerate devices. Browser privacy rules can hide device labels until permission has been granted.
|
|
1202
1063
|
|
|
1203
|
-
|
|
1064
|
+
## Camera changes but the preview does not update
|
|
1204
1065
|
|
|
1205
|
-
|
|
1066
|
+
The application should render the current local `MediaStream`/track rather than caching the first stream forever. Use the SDK's local participant stream and re-render/rebind the `<video>` element when the stream changes.
|
|
1206
1067
|
|
|
1207
|
-
|
|
1068
|
+
## Notification sound does not play
|
|
1208
1069
|
|
|
1209
|
-
|
|
1210
|
-
const { activeSpeakerIds } = useMeeting();
|
|
1211
|
-
```
|
|
1070
|
+
Browsers can block autoplay. Trigger sounds after a user interaction, or catch the rejected `audio.play()` promise.
|
|
1212
1071
|
|
|
1213
|
-
|
|
1072
|
+
## Participant joined callback fires unexpectedly during reconnect
|
|
1214
1073
|
|
|
1215
|
-
|
|
1074
|
+
Treat the lifecycle event as a participant lifecycle event, not as a low-level WebSocket reconnect notification. Use `onConnectionStateChanged` for connection/reconnection UI.
|
|
1216
1075
|
|
|
1217
|
-
##
|
|
1076
|
+
## Private-room join returns `approval_required`
|
|
1218
1077
|
|
|
1219
|
-
|
|
1078
|
+
Show the application's waiting-room UI and wait for the admission decision before treating the participant as fully admitted.
|
|
1220
1079
|
|
|
1221
|
-
|
|
1222
|
-
await startRecording();
|
|
1223
|
-
await stopRecording();
|
|
1224
|
-
```
|
|
1080
|
+
## `useMeeting()` throws an error about context
|
|
1225
1081
|
|
|
1226
|
-
|
|
1082
|
+
Make sure the component using `useMeeting()` is rendered inside `MeetingProvider`.
|
|
1227
1083
|
|
|
1228
1084
|
---
|
|
1229
1085
|
|
|
1230
|
-
|
|
1086
|
+
# 28. API summary
|
|
1231
1087
|
|
|
1232
|
-
|
|
1088
|
+
### Provider
|
|
1233
1089
|
|
|
1234
1090
|
```tsx
|
|
1235
|
-
|
|
1091
|
+
<MeetingProvider config={...}>
|
|
1092
|
+
{children}
|
|
1093
|
+
</MeetingProvider>
|
|
1236
1094
|
```
|
|
1237
1095
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
---
|
|
1241
|
-
|
|
1242
|
-
## 7. Do not immediately remove reconnecting participants
|
|
1243
|
-
|
|
1244
|
-
Use:
|
|
1096
|
+
### Hooks
|
|
1245
1097
|
|
|
1246
1098
|
```ts
|
|
1247
|
-
|
|
1099
|
+
useMeeting()
|
|
1100
|
+
useParticipants()
|
|
1101
|
+
useLocalParticipant()
|
|
1102
|
+
useRemoteMedia()
|
|
1103
|
+
useMeetingPreview()
|
|
1104
|
+
useNetworkQuality()
|
|
1105
|
+
usePubSub()
|
|
1248
1106
|
```
|
|
1249
1107
|
|
|
1250
|
-
|
|
1108
|
+
### Core meeting actions
|
|
1251
1109
|
|
|
1252
|
-
```
|
|
1253
|
-
|
|
1110
|
+
```ts
|
|
1111
|
+
join()
|
|
1112
|
+
leave()
|
|
1113
|
+
toggleMic()
|
|
1114
|
+
toggleCam()
|
|
1115
|
+
startScreenShare()
|
|
1116
|
+
stopScreenShare()
|
|
1117
|
+
sendMessage()
|
|
1118
|
+
sendReaction()
|
|
1254
1119
|
```
|
|
1255
1120
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
---
|
|
1259
|
-
|
|
1260
|
-
## 8. Keep screen share separate from camera
|
|
1261
|
-
|
|
1262
|
-
Use:
|
|
1121
|
+
### Devices
|
|
1263
1122
|
|
|
1264
1123
|
```ts
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1124
|
+
getMediaDevices()
|
|
1125
|
+
selectCamera()
|
|
1126
|
+
selectMicrophone()
|
|
1268
1127
|
```
|
|
1269
1128
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
---
|
|
1273
|
-
|
|
1274
|
-
# Build and publish
|
|
1129
|
+
### Admission and moderation
|
|
1275
1130
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1131
|
+
```ts
|
|
1132
|
+
getJoinState()
|
|
1133
|
+
approveJoinRequest()
|
|
1134
|
+
rejectJoinRequest()
|
|
1135
|
+
cancelJoinRequest()
|
|
1136
|
+
muteAllAudio()
|
|
1137
|
+
muteParticipant()
|
|
1138
|
+
removeParticipant()
|
|
1139
|
+
getModerators()
|
|
1140
|
+
addModerator()
|
|
1141
|
+
removeModerator()
|
|
1280
1142
|
```
|
|
1281
1143
|
|
|
1282
|
-
|
|
1144
|
+
### Recording and meeting data
|
|
1283
1145
|
|
|
1284
|
-
```
|
|
1285
|
-
|
|
1146
|
+
```ts
|
|
1147
|
+
startRecording()
|
|
1148
|
+
stopRecording()
|
|
1149
|
+
getRecordingStatus()
|
|
1150
|
+
getTranscript()
|
|
1151
|
+
getMeetingSummary()
|
|
1286
1152
|
```
|
|
1287
1153
|
|
|
1288
|
-
|
|
1154
|
+
### Lifecycle events
|
|
1289
1155
|
|
|
1290
|
-
```
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1156
|
+
```ts
|
|
1157
|
+
onParticipantJoined
|
|
1158
|
+
onParticipantLeft
|
|
1159
|
+
onChatMessage
|
|
1160
|
+
onConnectionStateChanged
|
|
1161
|
+
onMeetingLeft
|
|
1162
|
+
onError
|
|
1163
|
+
onEntryRequested
|
|
1164
|
+
onEntryResponded
|
|
1165
|
+
onJoinRequestCanceled
|
|
1166
|
+
onParticipantRemoved
|
|
1167
|
+
onParticipantPermissionsChanged
|
|
1168
|
+
onTrackStateChanged
|
|
1169
|
+
onRecordingStarted
|
|
1170
|
+
onRecordingStopped
|
|
1171
|
+
onReaction
|
|
1172
|
+
onActiveSpeakersChanged
|
|
1173
|
+
onNetworkQuality
|
|
1304
1174
|
```
|
|
1305
1175
|
|
|
1306
1176
|
---
|
|
1307
1177
|
|
|
1308
|
-
#
|
|
1178
|
+
# 29. Support and examples
|
|
1309
1179
|
|
|
1310
|
-
|
|
1180
|
+
For the complete working example, visit:
|
|
1311
1181
|
|
|
1312
|
-
|
|
1182
|
+
**https://meet.defcomm.ng**
|
|
1313
1183
|
|
|
1314
|
-
|
|
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
|
-
```
|
|
1184
|
+
The reference application is the recommended starting point for seeing how the SDK APIs fit together in a production-style meeting interface.
|
|
1328
1185
|
|
|
1329
|
-
|
|
1186
|
+
---
|
|
1330
1187
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
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.**
|
|
1188
|
+
## License
|
|
1189
|
+
|
|
1190
|
+
See the package/repository license for the applicable terms.
|