@defcomm/meetingsdk 0.6.8 → 0.7.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 +21 -5
- package/dist/index.d.mts +39 -5
- package/dist/index.d.ts +39 -5
- package/dist/index.js +330 -84
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +330 -84
- 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
|
}
|
|
@@ -346,7 +386,7 @@ var MeetingState = class {
|
|
|
346
386
|
const nextMedia = {
|
|
347
387
|
stream: patch.media?.stream ?? prevMedia.stream,
|
|
348
388
|
screenStream: patch.media?.screenStream ?? prevMedia.screenStream,
|
|
349
|
-
cameraTrack: patch.media
|
|
389
|
+
cameraTrack: patch.media && Object.prototype.hasOwnProperty.call(patch.media, "cameraTrack") ? patch.media.cameraTrack ?? void 0 : prevMedia.cameraTrack,
|
|
350
390
|
screenTrack: patch.media?.screenTrack ?? prevMedia.screenTrack,
|
|
351
391
|
audioTrack: patch.media?.audioTrack ?? prevMedia.audioTrack,
|
|
352
392
|
micEnabled: patch.media?.micEnabled ?? prevMedia.micEnabled,
|
|
@@ -607,6 +647,71 @@ var SFUClient = class {
|
|
|
607
647
|
setLocalStream(stream) {
|
|
608
648
|
this.localStream = stream;
|
|
609
649
|
}
|
|
650
|
+
/**
|
|
651
|
+
* Attach or detach the local camera track without replacing the publisher
|
|
652
|
+
* PeerConnection. Detaching with replaceTrack(null) stops media transmission;
|
|
653
|
+
* VideoSDKCore separately calls MediaStreamTrack.stop() to release hardware.
|
|
654
|
+
*/
|
|
655
|
+
async setCameraTrack(track) {
|
|
656
|
+
if (!this.localStream) this.localStream = new MediaStream();
|
|
657
|
+
const currentTrack = this.localStream.getVideoTracks().find((item) => item !== track);
|
|
658
|
+
if (currentTrack) this.localStream.removeTrack(currentTrack);
|
|
659
|
+
if (track && !this.localStream.getTracks().includes(track)) {
|
|
660
|
+
this.localStream.addTrack(track);
|
|
661
|
+
}
|
|
662
|
+
if (!this.videoSender || !this.publisher || !this.cameraTransceiver) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const needsCameraNegotiation = Boolean(
|
|
666
|
+
track && this.cameraTransceiver.direction === "inactive"
|
|
667
|
+
);
|
|
668
|
+
if (needsCameraNegotiation && this.ws?.readyState !== WebSocket.OPEN) {
|
|
669
|
+
throw new Error("Cannot enable the camera while signaling is disconnected");
|
|
670
|
+
}
|
|
671
|
+
await this.videoSender.replaceTrack(track);
|
|
672
|
+
if (needsCameraNegotiation) {
|
|
673
|
+
await this.waitForPublisherStable();
|
|
674
|
+
this.cameraTransceiver.direction = "sendonly";
|
|
675
|
+
await this.sendPublisherOffer();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
async waitForPublisherStable(timeoutMs = 5e3) {
|
|
679
|
+
const publisher = this.publisher;
|
|
680
|
+
if (!publisher) throw new Error("Publisher PeerConnection is not initialized");
|
|
681
|
+
const startedAt = Date.now();
|
|
682
|
+
while (publisher.signalingState !== "stable") {
|
|
683
|
+
if (publisher.signalingState === "closed") {
|
|
684
|
+
throw new Error("Publisher PeerConnection is closed");
|
|
685
|
+
}
|
|
686
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
687
|
+
throw new Error("Publisher negotiation did not become stable in time");
|
|
688
|
+
}
|
|
689
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
async sendPublisherOffer() {
|
|
693
|
+
if (!this.publisher) throw new Error("Publisher PeerConnection is not initialized");
|
|
694
|
+
const offer = await this.publisher.createOffer();
|
|
695
|
+
await this.publisher.setLocalDescription(offer);
|
|
696
|
+
const sent = this.send({
|
|
697
|
+
type: "publish_offer",
|
|
698
|
+
payload: {
|
|
699
|
+
revision: ++this.publisherRevision,
|
|
700
|
+
sdp: offer.sdp
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
if (!sent) {
|
|
704
|
+
try {
|
|
705
|
+
await this.publisher.setLocalDescription({ type: "rollback" });
|
|
706
|
+
} catch {
|
|
707
|
+
}
|
|
708
|
+
if (this.cameraTransceiver) {
|
|
709
|
+
this.cameraTransceiver.direction = "inactive";
|
|
710
|
+
}
|
|
711
|
+
await this.videoSender?.replaceTrack(null);
|
|
712
|
+
throw new Error("Unable to negotiate camera publishing: signaling is disconnected");
|
|
713
|
+
}
|
|
714
|
+
}
|
|
610
715
|
nextRequestId() {
|
|
611
716
|
return (++this.requestId).toString();
|
|
612
717
|
}
|
|
@@ -2284,6 +2389,8 @@ var VideoSDKCore = class {
|
|
|
2284
2389
|
this.screenStream = null;
|
|
2285
2390
|
this.isScreenSharing = false;
|
|
2286
2391
|
this.participantName = "";
|
|
2392
|
+
this.cameraEnabled = false;
|
|
2393
|
+
this.cameraToggleInProgress = false;
|
|
2287
2394
|
this.intentionalDisconnect = false;
|
|
2288
2395
|
this.remoteStreams = /* @__PURE__ */ new Map();
|
|
2289
2396
|
this.remoteScreenStreams = /* @__PURE__ */ new Map();
|
|
@@ -2316,26 +2423,12 @@ var VideoSDKCore = class {
|
|
|
2316
2423
|
async initLocal(video, name) {
|
|
2317
2424
|
this.participantName = name;
|
|
2318
2425
|
try {
|
|
2319
|
-
this.localStream = await
|
|
2320
|
-
|
|
2321
|
-
width: { ideal: 1280 },
|
|
2322
|
-
height: { ideal: 720 }
|
|
2323
|
-
},
|
|
2324
|
-
audio: {
|
|
2325
|
-
echoCancellation: true,
|
|
2326
|
-
noiseSuppression: true,
|
|
2327
|
-
autoGainControl: true
|
|
2328
|
-
}
|
|
2329
|
-
});
|
|
2426
|
+
this.localStream = await this.acquireOptionalLocalMedia();
|
|
2427
|
+
this.cameraEnabled = this.hasLiveCameraTrack();
|
|
2330
2428
|
const localAudioTrack = this.localStream.getAudioTracks()[0];
|
|
2331
2429
|
if (localAudioTrack) {
|
|
2332
2430
|
this.activeSpeakerDetector.addTrack(this.myId, localAudioTrack);
|
|
2333
2431
|
}
|
|
2334
|
-
const hasVideo = this.localStream.getVideoTracks().some((t) => t.readyState === "live");
|
|
2335
|
-
const hasAudio = this.localStream.getAudioTracks().some((t) => t.readyState === "live");
|
|
2336
|
-
if (!hasVideo || !hasAudio) {
|
|
2337
|
-
throw new Error(`Missing tracks: video=${hasVideo}, audio=${hasAudio}`);
|
|
2338
|
-
}
|
|
2339
2432
|
video.srcObject = this.localStream;
|
|
2340
2433
|
this.updateLocalMedia();
|
|
2341
2434
|
} catch (err) {
|
|
@@ -2343,17 +2436,47 @@ var VideoSDKCore = class {
|
|
|
2343
2436
|
throw err;
|
|
2344
2437
|
}
|
|
2345
2438
|
}
|
|
2346
|
-
|
|
2439
|
+
hasLiveCameraTrack() {
|
|
2440
|
+
return Boolean(
|
|
2441
|
+
this.localStream?.getVideoTracks().some((track) => track.readyState === "live")
|
|
2442
|
+
);
|
|
2443
|
+
}
|
|
2444
|
+
getLiveCameraTrack() {
|
|
2445
|
+
return this.localStream?.getVideoTracks().find((track) => track.readyState === "live") ?? null;
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Stop every locally captured camera track and remove it from the stream.
|
|
2449
|
+
* Track.enabled=false only emits black frames; stop() releases camera capture.
|
|
2450
|
+
*/
|
|
2451
|
+
stopCameraCapture() {
|
|
2452
|
+
if (!this.localStream) {
|
|
2453
|
+
this.cameraEnabled = false;
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
for (const track of this.localStream.getVideoTracks()) {
|
|
2457
|
+
this.localStream.removeTrack(track);
|
|
2458
|
+
try {
|
|
2459
|
+
track.stop();
|
|
2460
|
+
} catch {
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
this.cameraEnabled = false;
|
|
2464
|
+
}
|
|
2465
|
+
updateLocalMedia() {
|
|
2347
2466
|
if (!this.localStream) return;
|
|
2467
|
+
const cameraTrack = this.getLiveCameraTrack();
|
|
2468
|
+
const audioTrack = this.localStream.getAudioTracks().find((track) => track.readyState === "live");
|
|
2469
|
+
this.cameraEnabled = Boolean(cameraTrack && cameraTrack.enabled);
|
|
2348
2470
|
this.state.updateLocalParticipant({
|
|
2349
2471
|
id: this.myId,
|
|
2350
2472
|
name: this.participantName,
|
|
2351
2473
|
media: {
|
|
2352
2474
|
stream: this.localStream,
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2475
|
+
// Explicit null clears a previously published track from React state.
|
|
2476
|
+
cameraTrack,
|
|
2477
|
+
audioTrack: audioTrack ?? void 0,
|
|
2478
|
+
micEnabled: audioTrack?.enabled ?? false,
|
|
2479
|
+
camEnabled: this.cameraEnabled,
|
|
2357
2480
|
isScreenSharing: this.isScreenSharing,
|
|
2358
2481
|
screenStream: this.screenStream,
|
|
2359
2482
|
screenTrack: this.screenStream?.getVideoTracks()[0]
|
|
@@ -2380,6 +2503,89 @@ var VideoSDKCore = class {
|
|
|
2380
2503
|
async getMeetingSummary(roomCode, sessionId) {
|
|
2381
2504
|
return this.sfuClient.getMeetingSummary(roomCode, sessionId);
|
|
2382
2505
|
}
|
|
2506
|
+
async acquireOptionalLocalMedia(cameraDeviceId, microphoneDeviceId, includeVideo = true) {
|
|
2507
|
+
const combined = new MediaStream();
|
|
2508
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
2509
|
+
console.warn(
|
|
2510
|
+
"[SDK] Camera/microphone access is not supported; joining without local media."
|
|
2511
|
+
);
|
|
2512
|
+
return combined;
|
|
2513
|
+
}
|
|
2514
|
+
const cameraPromise = includeVideo ? navigator.mediaDevices.getUserMedia({
|
|
2515
|
+
video: cameraDeviceId ? { deviceId: { exact: cameraDeviceId } } : {
|
|
2516
|
+
width: { ideal: 1280 },
|
|
2517
|
+
height: { ideal: 720 }
|
|
2518
|
+
},
|
|
2519
|
+
audio: false
|
|
2520
|
+
}) : Promise.resolve(null);
|
|
2521
|
+
const microphonePromise = navigator.mediaDevices.getUserMedia({
|
|
2522
|
+
video: false,
|
|
2523
|
+
audio: microphoneDeviceId ? {
|
|
2524
|
+
deviceId: { exact: microphoneDeviceId },
|
|
2525
|
+
echoCancellation: true,
|
|
2526
|
+
noiseSuppression: true,
|
|
2527
|
+
autoGainControl: true
|
|
2528
|
+
} : {
|
|
2529
|
+
echoCancellation: true,
|
|
2530
|
+
noiseSuppression: true,
|
|
2531
|
+
autoGainControl: true
|
|
2532
|
+
}
|
|
2533
|
+
});
|
|
2534
|
+
const [cameraResult, microphoneResult] = await Promise.allSettled([
|
|
2535
|
+
cameraPromise,
|
|
2536
|
+
microphonePromise
|
|
2537
|
+
]);
|
|
2538
|
+
if (cameraResult.status === "fulfilled" && cameraResult.value) {
|
|
2539
|
+
for (const track of cameraResult.value.getVideoTracks()) {
|
|
2540
|
+
combined.addTrack(track);
|
|
2541
|
+
}
|
|
2542
|
+
} else if (cameraResult.status === "rejected") {
|
|
2543
|
+
console.warn("[SDK] Camera unavailable; continuing without video.", {
|
|
2544
|
+
name: cameraResult.reason?.name,
|
|
2545
|
+
message: cameraResult.reason?.message
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
if (microphoneResult.status === "fulfilled") {
|
|
2549
|
+
for (const track of microphoneResult.value.getAudioTracks()) {
|
|
2550
|
+
combined.addTrack(track);
|
|
2551
|
+
}
|
|
2552
|
+
} else {
|
|
2553
|
+
console.warn(
|
|
2554
|
+
"[SDK] Microphone unavailable; continuing without audio.",
|
|
2555
|
+
{
|
|
2556
|
+
name: microphoneResult.reason?.name,
|
|
2557
|
+
message: microphoneResult.reason?.message
|
|
2558
|
+
}
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
return combined;
|
|
2562
|
+
}
|
|
2563
|
+
async acquireCameraTrack() {
|
|
2564
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
2565
|
+
throw new Error("Camera access is not supported on this device");
|
|
2566
|
+
}
|
|
2567
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
2568
|
+
video: this.cameraDeviceId ? { deviceId: { exact: this.cameraDeviceId } } : {
|
|
2569
|
+
width: { ideal: 1280 },
|
|
2570
|
+
height: { ideal: 720 }
|
|
2571
|
+
},
|
|
2572
|
+
audio: false
|
|
2573
|
+
});
|
|
2574
|
+
const track = stream.getVideoTracks()[0];
|
|
2575
|
+
if (!track) {
|
|
2576
|
+
stream.getTracks().forEach((item) => item.stop());
|
|
2577
|
+
throw new Error("The selected camera did not provide a video track");
|
|
2578
|
+
}
|
|
2579
|
+
this.cameraDeviceId = track.getSettings().deviceId || this.cameraDeviceId;
|
|
2580
|
+
return track;
|
|
2581
|
+
}
|
|
2582
|
+
/**
|
|
2583
|
+
* Wakes the active-speaker Web Audio pipeline. Call this from a user gesture
|
|
2584
|
+
* whenever possible (Join, first click inside the meeting, etc.).
|
|
2585
|
+
*/
|
|
2586
|
+
async resumeActiveSpeaker() {
|
|
2587
|
+
return this.activeSpeakerDetector.resume();
|
|
2588
|
+
}
|
|
2383
2589
|
// JOIN
|
|
2384
2590
|
async joinMeeting(config) {
|
|
2385
2591
|
const {
|
|
@@ -2422,16 +2628,13 @@ var VideoSDKCore = class {
|
|
|
2422
2628
|
this.participantName = name;
|
|
2423
2629
|
this.room.id = roomId;
|
|
2424
2630
|
this.room.name = name;
|
|
2631
|
+
this.cameraDeviceId = cameraDeviceId ?? this.cameraDeviceId;
|
|
2425
2632
|
if (!this.localStream) {
|
|
2426
|
-
this.localStream = await
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
noiseSuppression: true,
|
|
2432
|
-
autoGainControl: true
|
|
2433
|
-
} : true
|
|
2434
|
-
});
|
|
2633
|
+
this.localStream = await this.acquireOptionalLocalMedia(
|
|
2634
|
+
this.cameraDeviceId,
|
|
2635
|
+
microphoneDeviceId,
|
|
2636
|
+
!videoMuted
|
|
2637
|
+
);
|
|
2435
2638
|
const localAudioTrack = this.localStream.getAudioTracks()[0];
|
|
2436
2639
|
if (localAudioTrack) {
|
|
2437
2640
|
this.activeSpeakerDetector.addTrack(this.myId, localAudioTrack);
|
|
@@ -2440,10 +2643,17 @@ var VideoSDKCore = class {
|
|
|
2440
2643
|
this.localStream.getAudioTracks().forEach((track) => {
|
|
2441
2644
|
track.enabled = !audioMuted;
|
|
2442
2645
|
});
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
}
|
|
2446
|
-
|
|
2646
|
+
if (videoMuted) {
|
|
2647
|
+
this.stopCameraCapture();
|
|
2648
|
+
} else {
|
|
2649
|
+
const initialCameraTrack = this.getLiveCameraTrack();
|
|
2650
|
+
this.cameraEnabled = Boolean(initialCameraTrack);
|
|
2651
|
+
if (initialCameraTrack) {
|
|
2652
|
+
this.cameraDeviceId = initialCameraTrack.getSettings().deviceId || this.cameraDeviceId;
|
|
2653
|
+
initialCameraTrack.enabled = true;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
this.updateLocalMedia();
|
|
2447
2657
|
const promise = this.connect(roomId, name);
|
|
2448
2658
|
this.joinPromise = promise;
|
|
2449
2659
|
try {
|
|
@@ -2476,8 +2686,8 @@ var VideoSDKCore = class {
|
|
|
2476
2686
|
await this.sfuClient.getToken(roomId, {
|
|
2477
2687
|
displayName: name,
|
|
2478
2688
|
participantId: this.myId,
|
|
2479
|
-
audioMuted: !(this.localStream?.getAudioTracks()[0]?.enabled ??
|
|
2480
|
-
videoMuted: !(this.localStream?.getVideoTracks()[0]?.enabled ??
|
|
2689
|
+
audioMuted: !(this.localStream?.getAudioTracks()[0]?.enabled ?? false),
|
|
2690
|
+
videoMuted: !(this.localStream?.getVideoTracks()[0]?.enabled ?? false)
|
|
2481
2691
|
});
|
|
2482
2692
|
}
|
|
2483
2693
|
createSFUClient(token) {
|
|
@@ -3070,7 +3280,12 @@ var VideoSDKCore = class {
|
|
|
3070
3280
|
if (!this.sfuClient) {
|
|
3071
3281
|
throw new Error("SFU client is not initialized");
|
|
3072
3282
|
}
|
|
3283
|
+
if (!this.cameraEnabled) {
|
|
3284
|
+
throw new Error("Your camera is off. Turn it on before switching devices.");
|
|
3285
|
+
}
|
|
3073
3286
|
const track = await this.sfuClient.selectCamera(deviceId);
|
|
3287
|
+
this.cameraDeviceId = deviceId;
|
|
3288
|
+
this.cameraEnabled = true;
|
|
3074
3289
|
if (this.localStream) {
|
|
3075
3290
|
this.state.localStream = this.localStream;
|
|
3076
3291
|
this.updateLocalMedia();
|
|
@@ -3112,24 +3327,52 @@ var VideoSDKCore = class {
|
|
|
3112
3327
|
this.sfuClient?.muteAudio(!nextEnabled);
|
|
3113
3328
|
this.events.onMicToggled?.(this.myId, nextEnabled);
|
|
3114
3329
|
}
|
|
3115
|
-
toggleCam() {
|
|
3116
|
-
|
|
3117
|
-
if (
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
camEnabled: nextEnabled
|
|
3330
|
+
async toggleCam() {
|
|
3331
|
+
if (!this.state.localParticipant?.media || !this.localStream) return;
|
|
3332
|
+
if (this.cameraToggleInProgress) return;
|
|
3333
|
+
this.cameraToggleInProgress = true;
|
|
3334
|
+
const nextEnabled = !this.cameraEnabled;
|
|
3335
|
+
try {
|
|
3336
|
+
if (!nextEnabled) {
|
|
3337
|
+
this.sfuClient?.muteVideo(true);
|
|
3338
|
+
this.stopCameraCapture();
|
|
3339
|
+
await this.sfuClient?.setCameraTrack(null);
|
|
3340
|
+
this.updateLocalMedia();
|
|
3341
|
+
this.events.onCamToggled?.(this.myId, false);
|
|
3342
|
+
return;
|
|
3129
3343
|
}
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3344
|
+
const track = await this.acquireCameraTrack();
|
|
3345
|
+
if (!this.localStream) this.localStream = new MediaStream();
|
|
3346
|
+
this.localStream.addTrack(track);
|
|
3347
|
+
try {
|
|
3348
|
+
await this.sfuClient?.setCameraTrack(track);
|
|
3349
|
+
this.cameraEnabled = true;
|
|
3350
|
+
this.sfuClient?.muteVideo(false);
|
|
3351
|
+
this.updateLocalMedia();
|
|
3352
|
+
this.events.onCamToggled?.(this.myId, true);
|
|
3353
|
+
} catch (error) {
|
|
3354
|
+
this.localStream.removeTrack(track);
|
|
3355
|
+
try {
|
|
3356
|
+
track.stop();
|
|
3357
|
+
} catch {
|
|
3358
|
+
}
|
|
3359
|
+
this.cameraEnabled = false;
|
|
3360
|
+
this.updateLocalMedia();
|
|
3361
|
+
throw error;
|
|
3362
|
+
}
|
|
3363
|
+
} catch (error) {
|
|
3364
|
+
this.cameraEnabled = false;
|
|
3365
|
+
this.updateLocalMedia();
|
|
3366
|
+
this.emitError(
|
|
3367
|
+
"CAMERA_TOGGLE_FAILED",
|
|
3368
|
+
error?.message || "Unable to change camera state",
|
|
3369
|
+
error,
|
|
3370
|
+
true
|
|
3371
|
+
);
|
|
3372
|
+
throw error;
|
|
3373
|
+
} finally {
|
|
3374
|
+
this.cameraToggleInProgress = false;
|
|
3375
|
+
}
|
|
3133
3376
|
}
|
|
3134
3377
|
// SCREEN SHARING
|
|
3135
3378
|
async startScreenShare() {
|
|
@@ -3287,6 +3530,8 @@ var VideoSDKCore = class {
|
|
|
3287
3530
|
this.localStream.getTracks().forEach((track) => track.stop());
|
|
3288
3531
|
this.localStream = null;
|
|
3289
3532
|
}
|
|
3533
|
+
this.cameraEnabled = false;
|
|
3534
|
+
this.cameraToggleInProgress = false;
|
|
3290
3535
|
if (this.screenStream) {
|
|
3291
3536
|
this.screenStream.getTracks().forEach((track) => track.stop());
|
|
3292
3537
|
this.screenStream = null;
|
|
@@ -3537,6 +3782,7 @@ var MeetingProvider = ({
|
|
|
3537
3782
|
getMeetingSummary: sdk.getMeetingSummary.bind(sdk),
|
|
3538
3783
|
toggleMic: sdk.toggleMic.bind(sdk),
|
|
3539
3784
|
toggleCam: sdk.toggleCam.bind(sdk),
|
|
3785
|
+
resumeActiveSpeaker: sdk.resumeActiveSpeaker.bind(sdk),
|
|
3540
3786
|
startScreenShare: sdk.startScreenShare.bind(sdk),
|
|
3541
3787
|
stopScreenShare: sdk.stopScreenShare.bind(sdk),
|
|
3542
3788
|
sendMessage: sdk.sendChatMessage.bind(sdk),
|