@defcomm/meetingsdk 0.6.7 → 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +33 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +219 -61
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +219 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -32,13 +32,16 @@ var ActiveSpeakerDetector = class {
|
|
|
32
32
|
this.timer = null;
|
|
33
33
|
this.lastEmitted = "";
|
|
34
34
|
this.intervalMs = options.intervalMs ?? 100;
|
|
35
|
-
this.threshold = options.threshold ?? 0.
|
|
36
|
-
this.releaseThreshold = options.releaseThreshold ?? 0.
|
|
37
|
-
this.releaseMs = options.releaseMs ??
|
|
35
|
+
this.threshold = options.threshold ?? 0.02;
|
|
36
|
+
this.releaseThreshold = options.releaseThreshold ?? 0.01;
|
|
37
|
+
this.releaseMs = options.releaseMs ?? 650;
|
|
38
38
|
this.onActiveSpeakersChanged = options.onActiveSpeakersChanged ?? (() => {
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
ensureAudioContext() {
|
|
42
|
+
if (typeof window === "undefined") {
|
|
43
|
+
throw new Error("AudioContext is only available in the browser");
|
|
44
|
+
}
|
|
42
45
|
if (!this.audioContext) {
|
|
43
46
|
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
44
47
|
if (!AudioContextClass) {
|
|
@@ -48,28 +51,52 @@ var ActiveSpeakerDetector = class {
|
|
|
48
51
|
}
|
|
49
52
|
return this.audioContext;
|
|
50
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Create/wake the Web Audio context. Call this from a user gesture when
|
|
56
|
+
* possible. Returns true when the context is running after the call.
|
|
57
|
+
*/
|
|
51
58
|
async resume() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
try {
|
|
60
|
+
const context = this.ensureAudioContext();
|
|
61
|
+
if (context.state === "suspended") {
|
|
62
|
+
try {
|
|
63
|
+
await context.resume();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn("[ActiveSpeaker] AudioContext resume failed", error);
|
|
66
|
+
}
|
|
57
67
|
}
|
|
68
|
+
return context.state === "running";
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.warn("[ActiveSpeaker] AudioContext unavailable", error);
|
|
71
|
+
return false;
|
|
58
72
|
}
|
|
59
73
|
}
|
|
74
|
+
getAudioContextState() {
|
|
75
|
+
if (!this.audioContext) return "unavailable";
|
|
76
|
+
return this.audioContext.state;
|
|
77
|
+
}
|
|
60
78
|
addTrack(participantId, track) {
|
|
61
|
-
if (!participantId) return;
|
|
62
|
-
if (track.kind !== "audio") return;
|
|
79
|
+
if (!participantId || track.kind !== "audio") return;
|
|
63
80
|
this.removeParticipant(participantId);
|
|
64
|
-
|
|
81
|
+
let context;
|
|
82
|
+
try {
|
|
83
|
+
context = this.ensureAudioContext();
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.warn("[ActiveSpeaker] Could not create AudioContext", error);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
65
88
|
const stream = new MediaStream([track]);
|
|
66
89
|
const source = context.createMediaStreamSource(stream);
|
|
67
90
|
const analyser = context.createAnalyser();
|
|
68
91
|
analyser.fftSize = 512;
|
|
69
|
-
analyser.smoothingTimeConstant = 0.
|
|
92
|
+
analyser.smoothingTimeConstant = 0.35;
|
|
70
93
|
analyser.minDecibels = -90;
|
|
71
94
|
analyser.maxDecibels = -10;
|
|
72
95
|
source.connect(analyser);
|
|
96
|
+
const sink = context.createGain();
|
|
97
|
+
sink.gain.value = 0;
|
|
98
|
+
analyser.connect(sink);
|
|
99
|
+
sink.connect(context.destination);
|
|
73
100
|
const data = new Float32Array(analyser.fftSize);
|
|
74
101
|
const state = {
|
|
75
102
|
participantId,
|
|
@@ -77,15 +104,20 @@ var ActiveSpeakerDetector = class {
|
|
|
77
104
|
stream,
|
|
78
105
|
source,
|
|
79
106
|
analyser,
|
|
107
|
+
sink,
|
|
80
108
|
data,
|
|
81
109
|
active: false,
|
|
110
|
+
level: 0,
|
|
82
111
|
lastActiveAt: 0
|
|
83
112
|
};
|
|
84
113
|
this.speakers.set(participantId, state);
|
|
85
114
|
track.addEventListener(
|
|
86
115
|
"ended",
|
|
87
116
|
() => {
|
|
88
|
-
this.
|
|
117
|
+
const current = this.speakers.get(participantId);
|
|
118
|
+
if (current?.track === track) {
|
|
119
|
+
this.removeParticipant(participantId);
|
|
120
|
+
}
|
|
89
121
|
},
|
|
90
122
|
{ once: true }
|
|
91
123
|
);
|
|
@@ -96,15 +128,19 @@ var ActiveSpeakerDetector = class {
|
|
|
96
128
|
const state = this.speakers.get(participantId);
|
|
97
129
|
if (!state) return;
|
|
98
130
|
state.active = false;
|
|
131
|
+
state.level = 0;
|
|
99
132
|
try {
|
|
100
133
|
state.source.disconnect();
|
|
101
134
|
} catch {
|
|
102
135
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
136
|
+
try {
|
|
137
|
+
state.analyser.disconnect();
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
state.sink.disconnect();
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
108
144
|
this.speakers.delete(participantId);
|
|
109
145
|
this.emitIfChanged();
|
|
110
146
|
}
|
|
@@ -116,7 +152,7 @@ var ActiveSpeakerDetector = class {
|
|
|
116
152
|
}
|
|
117
153
|
}
|
|
118
154
|
clear() {
|
|
119
|
-
for (const participantId of this.speakers.keys()) {
|
|
155
|
+
for (const participantId of [...this.speakers.keys()]) {
|
|
120
156
|
this.removeParticipant(participantId);
|
|
121
157
|
}
|
|
122
158
|
this.speakers.clear();
|
|
@@ -136,6 +172,7 @@ var ActiveSpeakerDetector = class {
|
|
|
136
172
|
const now = performance.now();
|
|
137
173
|
for (const state of this.speakers.values()) {
|
|
138
174
|
if (state.track.readyState !== "live" || state.track.muted || !state.track.enabled) {
|
|
175
|
+
state.level = 0;
|
|
139
176
|
if (state.active) {
|
|
140
177
|
state.active = false;
|
|
141
178
|
}
|
|
@@ -148,30 +185,33 @@ var ActiveSpeakerDetector = class {
|
|
|
148
185
|
sumSquares += sample * sample;
|
|
149
186
|
}
|
|
150
187
|
const rms = Math.sqrt(sumSquares / state.data.length);
|
|
188
|
+
state.level = rms;
|
|
151
189
|
if (!state.active) {
|
|
152
190
|
if (rms >= this.threshold) {
|
|
153
191
|
state.active = true;
|
|
154
192
|
state.lastActiveAt = now;
|
|
155
193
|
}
|
|
156
|
-
} else {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
state.active = false;
|
|
161
|
-
}
|
|
194
|
+
} else if (rms >= this.releaseThreshold) {
|
|
195
|
+
state.lastActiveAt = now;
|
|
196
|
+
} else if (now - state.lastActiveAt >= this.releaseMs) {
|
|
197
|
+
state.active = false;
|
|
162
198
|
}
|
|
163
199
|
}
|
|
164
200
|
this.emitIfChanged();
|
|
165
201
|
}
|
|
202
|
+
getActiveSpeakers() {
|
|
203
|
+
return Array.from(this.speakers.values()).filter((speaker) => speaker.active).sort((a, b) => {
|
|
204
|
+
if (b.level !== a.level) return b.level - a.level;
|
|
205
|
+
return a.participantId.localeCompare(b.participantId);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
166
208
|
getActiveSpeakerIds() {
|
|
167
|
-
return
|
|
209
|
+
return this.getActiveSpeakers().map((speaker) => speaker.participantId);
|
|
168
210
|
}
|
|
169
211
|
emitIfChanged(force = false) {
|
|
170
|
-
const ids = this.getActiveSpeakerIds()
|
|
212
|
+
const ids = this.getActiveSpeakerIds();
|
|
171
213
|
const key = ids.join("|");
|
|
172
|
-
if (!force && key === this.lastEmitted)
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
214
|
+
if (!force && key === this.lastEmitted) return;
|
|
175
215
|
this.lastEmitted = key;
|
|
176
216
|
this.onActiveSpeakersChanged(ids);
|
|
177
217
|
}
|
|
@@ -248,6 +288,36 @@ var MeetingState = class {
|
|
|
248
288
|
this.notify("participants");
|
|
249
289
|
return true;
|
|
250
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Reconcile a participant received from a room snapshot without emitting a
|
|
293
|
+
* lifecycle/join event. Snapshot data is authoritative for current room
|
|
294
|
+
* membership, including after a reconnect/resume.
|
|
295
|
+
*/
|
|
296
|
+
upsertParticipantSnapshot(p) {
|
|
297
|
+
if (!p?.id) return;
|
|
298
|
+
const current = this.participants.get(p.id);
|
|
299
|
+
const updated = {
|
|
300
|
+
...current ?? {},
|
|
301
|
+
...p,
|
|
302
|
+
media: {
|
|
303
|
+
stream: current?.media?.stream ?? null,
|
|
304
|
+
screenStream: current?.media?.screenStream,
|
|
305
|
+
cameraTrack: current?.media?.cameraTrack,
|
|
306
|
+
screenTrack: current?.media?.screenTrack,
|
|
307
|
+
audioTrack: current?.media?.audioTrack,
|
|
308
|
+
micEnabled: current?.media?.micEnabled ?? !(p.media?.micEnabled === false),
|
|
309
|
+
camEnabled: current?.media?.camEnabled ?? !(p.media?.camEnabled === false),
|
|
310
|
+
isScreenSharing: current?.media?.isScreenSharing ?? false,
|
|
311
|
+
...current?.media,
|
|
312
|
+
...p.media
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const next = new Map(this.participants);
|
|
316
|
+
next.set(p.id, updated);
|
|
317
|
+
this.participants = next;
|
|
318
|
+
this.notify(`participant:${p.id}`);
|
|
319
|
+
this.notify("participants");
|
|
320
|
+
}
|
|
251
321
|
removeParticipant(id) {
|
|
252
322
|
const next = new Map(this.participants);
|
|
253
323
|
next.delete(id);
|
|
@@ -1212,7 +1282,6 @@ var SFUClient = class {
|
|
|
1212
1282
|
}
|
|
1213
1283
|
for (const participant of this.participants.values()) {
|
|
1214
1284
|
if (participant.participant_id !== this.currentParticipantId) {
|
|
1215
|
-
this.onParticipantJoined(participant);
|
|
1216
1285
|
this.onParticipantPresence(
|
|
1217
1286
|
participant.participant_id,
|
|
1218
1287
|
String(participant.presence || "connected")
|
|
@@ -2287,26 +2356,11 @@ var VideoSDKCore = class {
|
|
|
2287
2356
|
async initLocal(video, name) {
|
|
2288
2357
|
this.participantName = name;
|
|
2289
2358
|
try {
|
|
2290
|
-
this.localStream = await
|
|
2291
|
-
video: {
|
|
2292
|
-
width: { ideal: 1280 },
|
|
2293
|
-
height: { ideal: 720 }
|
|
2294
|
-
},
|
|
2295
|
-
audio: {
|
|
2296
|
-
echoCancellation: true,
|
|
2297
|
-
noiseSuppression: true,
|
|
2298
|
-
autoGainControl: true
|
|
2299
|
-
}
|
|
2300
|
-
});
|
|
2359
|
+
this.localStream = await this.acquireOptionalLocalMedia();
|
|
2301
2360
|
const localAudioTrack = this.localStream.getAudioTracks()[0];
|
|
2302
2361
|
if (localAudioTrack) {
|
|
2303
2362
|
this.activeSpeakerDetector.addTrack(this.myId, localAudioTrack);
|
|
2304
2363
|
}
|
|
2305
|
-
const hasVideo = this.localStream.getVideoTracks().some((t) => t.readyState === "live");
|
|
2306
|
-
const hasAudio = this.localStream.getAudioTracks().some((t) => t.readyState === "live");
|
|
2307
|
-
if (!hasVideo || !hasAudio) {
|
|
2308
|
-
throw new Error(`Missing tracks: video=${hasVideo}, audio=${hasAudio}`);
|
|
2309
|
-
}
|
|
2310
2364
|
video.srcObject = this.localStream;
|
|
2311
2365
|
this.updateLocalMedia();
|
|
2312
2366
|
} catch (err) {
|
|
@@ -2323,8 +2377,8 @@ var VideoSDKCore = class {
|
|
|
2323
2377
|
stream: this.localStream,
|
|
2324
2378
|
cameraTrack: this.localStream.getVideoTracks()[0],
|
|
2325
2379
|
audioTrack: this.localStream.getAudioTracks()[0],
|
|
2326
|
-
micEnabled: this.localStream.getAudioTracks()[0]?.enabled ??
|
|
2327
|
-
camEnabled: this.localStream.getVideoTracks()[0]?.enabled ??
|
|
2380
|
+
micEnabled: this.localStream.getAudioTracks()[0]?.enabled ?? false,
|
|
2381
|
+
camEnabled: this.localStream.getVideoTracks()[0]?.enabled ?? false,
|
|
2328
2382
|
isScreenSharing: this.isScreenSharing,
|
|
2329
2383
|
screenStream: this.screenStream,
|
|
2330
2384
|
screenTrack: this.screenStream?.getVideoTracks()[0]
|
|
@@ -2351,6 +2405,70 @@ var VideoSDKCore = class {
|
|
|
2351
2405
|
async getMeetingSummary(roomCode, sessionId) {
|
|
2352
2406
|
return this.sfuClient.getMeetingSummary(roomCode, sessionId);
|
|
2353
2407
|
}
|
|
2408
|
+
async acquireOptionalLocalMedia(cameraDeviceId, microphoneDeviceId) {
|
|
2409
|
+
const combined = new MediaStream();
|
|
2410
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
2411
|
+
console.warn(
|
|
2412
|
+
"[SDK] Camera/microphone access is not supported; joining without local media."
|
|
2413
|
+
);
|
|
2414
|
+
return combined;
|
|
2415
|
+
}
|
|
2416
|
+
const cameraPromise = navigator.mediaDevices.getUserMedia({
|
|
2417
|
+
video: cameraDeviceId ? { deviceId: { exact: cameraDeviceId } } : {
|
|
2418
|
+
width: { ideal: 1280 },
|
|
2419
|
+
height: { ideal: 720 }
|
|
2420
|
+
},
|
|
2421
|
+
audio: false
|
|
2422
|
+
});
|
|
2423
|
+
const microphonePromise = navigator.mediaDevices.getUserMedia({
|
|
2424
|
+
video: false,
|
|
2425
|
+
audio: microphoneDeviceId ? {
|
|
2426
|
+
deviceId: { exact: microphoneDeviceId },
|
|
2427
|
+
echoCancellation: true,
|
|
2428
|
+
noiseSuppression: true,
|
|
2429
|
+
autoGainControl: true
|
|
2430
|
+
} : {
|
|
2431
|
+
echoCancellation: true,
|
|
2432
|
+
noiseSuppression: true,
|
|
2433
|
+
autoGainControl: true
|
|
2434
|
+
}
|
|
2435
|
+
});
|
|
2436
|
+
const [cameraResult, microphoneResult] = await Promise.allSettled([
|
|
2437
|
+
cameraPromise,
|
|
2438
|
+
microphonePromise
|
|
2439
|
+
]);
|
|
2440
|
+
if (cameraResult.status === "fulfilled") {
|
|
2441
|
+
for (const track of cameraResult.value.getVideoTracks()) {
|
|
2442
|
+
combined.addTrack(track);
|
|
2443
|
+
}
|
|
2444
|
+
} else {
|
|
2445
|
+
console.warn("[SDK] Camera unavailable; continuing without video.", {
|
|
2446
|
+
name: cameraResult.reason?.name,
|
|
2447
|
+
message: cameraResult.reason?.message
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
if (microphoneResult.status === "fulfilled") {
|
|
2451
|
+
for (const track of microphoneResult.value.getAudioTracks()) {
|
|
2452
|
+
combined.addTrack(track);
|
|
2453
|
+
}
|
|
2454
|
+
} else {
|
|
2455
|
+
console.warn(
|
|
2456
|
+
"[SDK] Microphone unavailable; continuing without audio.",
|
|
2457
|
+
{
|
|
2458
|
+
name: microphoneResult.reason?.name,
|
|
2459
|
+
message: microphoneResult.reason?.message
|
|
2460
|
+
}
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
return combined;
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Wakes the active-speaker Web Audio pipeline. Call this from a user gesture
|
|
2467
|
+
* whenever possible (Join, first click inside the meeting, etc.).
|
|
2468
|
+
*/
|
|
2469
|
+
async resumeActiveSpeaker() {
|
|
2470
|
+
return this.activeSpeakerDetector.resume();
|
|
2471
|
+
}
|
|
2354
2472
|
// JOIN
|
|
2355
2473
|
async joinMeeting(config) {
|
|
2356
2474
|
const {
|
|
@@ -2394,15 +2512,10 @@ var VideoSDKCore = class {
|
|
|
2394
2512
|
this.room.id = roomId;
|
|
2395
2513
|
this.room.name = name;
|
|
2396
2514
|
if (!this.localStream) {
|
|
2397
|
-
this.localStream = await
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
echoCancellation: true,
|
|
2402
|
-
noiseSuppression: true,
|
|
2403
|
-
autoGainControl: true
|
|
2404
|
-
} : true
|
|
2405
|
-
});
|
|
2515
|
+
this.localStream = await this.acquireOptionalLocalMedia(
|
|
2516
|
+
cameraDeviceId,
|
|
2517
|
+
microphoneDeviceId
|
|
2518
|
+
);
|
|
2406
2519
|
const localAudioTrack = this.localStream.getAudioTracks()[0];
|
|
2407
2520
|
if (localAudioTrack) {
|
|
2408
2521
|
this.activeSpeakerDetector.addTrack(this.myId, localAudioTrack);
|
|
@@ -2447,8 +2560,8 @@ var VideoSDKCore = class {
|
|
|
2447
2560
|
await this.sfuClient.getToken(roomId, {
|
|
2448
2561
|
displayName: name,
|
|
2449
2562
|
participantId: this.myId,
|
|
2450
|
-
audioMuted: !(this.localStream?.getAudioTracks()[0]?.enabled ??
|
|
2451
|
-
videoMuted: !(this.localStream?.getVideoTracks()[0]?.enabled ??
|
|
2563
|
+
audioMuted: !(this.localStream?.getAudioTracks()[0]?.enabled ?? false),
|
|
2564
|
+
videoMuted: !(this.localStream?.getVideoTracks()[0]?.enabled ?? false)
|
|
2452
2565
|
});
|
|
2453
2566
|
}
|
|
2454
2567
|
createSFUClient(token) {
|
|
@@ -2497,6 +2610,36 @@ var VideoSDKCore = class {
|
|
|
2497
2610
|
},
|
|
2498
2611
|
onRoomUpdate: (payload) => {
|
|
2499
2612
|
const participants = payload?.participants || [];
|
|
2613
|
+
const snapshotParticipantIds = /* @__PURE__ */ new Set();
|
|
2614
|
+
for (const participant of participants) {
|
|
2615
|
+
const pid = participant?.participant_id;
|
|
2616
|
+
if (!pid || pid === this.myId) continue;
|
|
2617
|
+
snapshotParticipantIds.add(pid);
|
|
2618
|
+
const current = this.state.getParticipant(pid);
|
|
2619
|
+
const name = participant?.user_metadata?.name || participant?.display_name || current?.name || "Participant";
|
|
2620
|
+
this.state.upsertParticipantSnapshot({
|
|
2621
|
+
id: pid,
|
|
2622
|
+
name,
|
|
2623
|
+
isModerator: Boolean(participant?.permissions?.moderate),
|
|
2624
|
+
isHost: Boolean(
|
|
2625
|
+
participant?.is_host ?? participant?.isHost ?? participant?.permissions?.host
|
|
2626
|
+
),
|
|
2627
|
+
handRaised: Boolean(participant?.hand_raised),
|
|
2628
|
+
permissions: participant?.permissions,
|
|
2629
|
+
connectionState: participant?.presence === "reconnecting" ? "reconnecting" : participant?.presence === "disconnected" ? "disconnected" : "connected",
|
|
2630
|
+
media: {
|
|
2631
|
+
...current?.media || {},
|
|
2632
|
+
micEnabled: !Boolean(participant?.audio_muted),
|
|
2633
|
+
camEnabled: !Boolean(participant?.video_muted),
|
|
2634
|
+
isScreenSharing: Boolean(participant?.screen_sharing)
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
for (const [pid] of this.state.participants) {
|
|
2639
|
+
if (pid === this.myId || snapshotParticipantIds.has(pid)) continue;
|
|
2640
|
+
this.cleanupRemoteParticipant(pid);
|
|
2641
|
+
this.state.removeParticipant(pid);
|
|
2642
|
+
}
|
|
2500
2643
|
const local = participants.find(
|
|
2501
2644
|
(p) => p?.participant_id === this.myId
|
|
2502
2645
|
);
|
|
@@ -3310,6 +3453,9 @@ var MeetingProvider = ({
|
|
|
3310
3453
|
const participantJoinedListeners = useRef(
|
|
3311
3454
|
/* @__PURE__ */ new Set()
|
|
3312
3455
|
);
|
|
3456
|
+
const participantPresenceListeners = useRef(
|
|
3457
|
+
/* @__PURE__ */ new Set()
|
|
3458
|
+
);
|
|
3313
3459
|
const participantLeftListeners = useRef(
|
|
3314
3460
|
/* @__PURE__ */ new Set()
|
|
3315
3461
|
);
|
|
@@ -3398,7 +3544,9 @@ var MeetingProvider = ({
|
|
|
3398
3544
|
pubSubListeners.current.get("REACTION")?.forEach((fn) => fn(message));
|
|
3399
3545
|
},
|
|
3400
3546
|
onHandRaised: (participantId, raised) => {
|
|
3401
|
-
handRaisedListeners.current.forEach(
|
|
3547
|
+
handRaisedListeners.current.forEach(
|
|
3548
|
+
(fn) => fn(participantId, raised)
|
|
3549
|
+
);
|
|
3402
3550
|
},
|
|
3403
3551
|
onNetworkQuality: (quality) => {
|
|
3404
3552
|
networkQualityListeners.current.forEach((fn) => fn(quality));
|
|
@@ -3411,6 +3559,11 @@ var MeetingProvider = ({
|
|
|
3411
3559
|
onParticipantJoined: (participant) => {
|
|
3412
3560
|
participantJoinedListeners.current.forEach((fn) => fn(participant));
|
|
3413
3561
|
},
|
|
3562
|
+
onParticipantPresence: (participantId, presence) => {
|
|
3563
|
+
participantPresenceListeners.current.forEach(
|
|
3564
|
+
(fn) => fn(participantId, presence)
|
|
3565
|
+
);
|
|
3566
|
+
},
|
|
3414
3567
|
onParticipantLeft: (participant) => {
|
|
3415
3568
|
participantLeftListeners.current.forEach((fn) => fn(participant));
|
|
3416
3569
|
}
|
|
@@ -3468,6 +3621,7 @@ var MeetingProvider = ({
|
|
|
3468
3621
|
getMeetingSummary: sdk.getMeetingSummary.bind(sdk),
|
|
3469
3622
|
toggleMic: sdk.toggleMic.bind(sdk),
|
|
3470
3623
|
toggleCam: sdk.toggleCam.bind(sdk),
|
|
3624
|
+
resumeActiveSpeaker: sdk.resumeActiveSpeaker.bind(sdk),
|
|
3471
3625
|
startScreenShare: sdk.startScreenShare.bind(sdk),
|
|
3472
3626
|
stopScreenShare: sdk.stopScreenShare.bind(sdk),
|
|
3473
3627
|
sendMessage: sdk.sendChatMessage.bind(sdk),
|
|
@@ -3623,6 +3777,10 @@ var MeetingProvider = ({
|
|
|
3623
3777
|
participantJoinedListeners.current.add(cb);
|
|
3624
3778
|
return () => participantJoinedListeners.current.delete(cb);
|
|
3625
3779
|
},
|
|
3780
|
+
onParticipantPresence: (cb) => {
|
|
3781
|
+
participantPresenceListeners.current.add(cb);
|
|
3782
|
+
return () => participantPresenceListeners.current.delete(cb);
|
|
3783
|
+
},
|
|
3626
3784
|
onChatMessage: (cb) => {
|
|
3627
3785
|
chatMessageListeners.current.add(cb);
|
|
3628
3786
|
return () => chatMessageListeners.current.delete(cb);
|