@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.js
CHANGED
|
@@ -63,13 +63,16 @@ var ActiveSpeakerDetector = class {
|
|
|
63
63
|
this.timer = null;
|
|
64
64
|
this.lastEmitted = "";
|
|
65
65
|
this.intervalMs = options.intervalMs ?? 100;
|
|
66
|
-
this.threshold = options.threshold ?? 0.
|
|
67
|
-
this.releaseThreshold = options.releaseThreshold ?? 0.
|
|
68
|
-
this.releaseMs = options.releaseMs ??
|
|
66
|
+
this.threshold = options.threshold ?? 0.02;
|
|
67
|
+
this.releaseThreshold = options.releaseThreshold ?? 0.01;
|
|
68
|
+
this.releaseMs = options.releaseMs ?? 650;
|
|
69
69
|
this.onActiveSpeakersChanged = options.onActiveSpeakersChanged ?? (() => {
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
ensureAudioContext() {
|
|
73
|
+
if (typeof window === "undefined") {
|
|
74
|
+
throw new Error("AudioContext is only available in the browser");
|
|
75
|
+
}
|
|
73
76
|
if (!this.audioContext) {
|
|
74
77
|
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
75
78
|
if (!AudioContextClass) {
|
|
@@ -79,28 +82,52 @@ var ActiveSpeakerDetector = class {
|
|
|
79
82
|
}
|
|
80
83
|
return this.audioContext;
|
|
81
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Create/wake the Web Audio context. Call this from a user gesture when
|
|
87
|
+
* possible. Returns true when the context is running after the call.
|
|
88
|
+
*/
|
|
82
89
|
async resume() {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
try {
|
|
91
|
+
const context = this.ensureAudioContext();
|
|
92
|
+
if (context.state === "suspended") {
|
|
93
|
+
try {
|
|
94
|
+
await context.resume();
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.warn("[ActiveSpeaker] AudioContext resume failed", error);
|
|
97
|
+
}
|
|
88
98
|
}
|
|
99
|
+
return context.state === "running";
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.warn("[ActiveSpeaker] AudioContext unavailable", error);
|
|
102
|
+
return false;
|
|
89
103
|
}
|
|
90
104
|
}
|
|
105
|
+
getAudioContextState() {
|
|
106
|
+
if (!this.audioContext) return "unavailable";
|
|
107
|
+
return this.audioContext.state;
|
|
108
|
+
}
|
|
91
109
|
addTrack(participantId, track) {
|
|
92
|
-
if (!participantId) return;
|
|
93
|
-
if (track.kind !== "audio") return;
|
|
110
|
+
if (!participantId || track.kind !== "audio") return;
|
|
94
111
|
this.removeParticipant(participantId);
|
|
95
|
-
|
|
112
|
+
let context;
|
|
113
|
+
try {
|
|
114
|
+
context = this.ensureAudioContext();
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.warn("[ActiveSpeaker] Could not create AudioContext", error);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
96
119
|
const stream = new MediaStream([track]);
|
|
97
120
|
const source = context.createMediaStreamSource(stream);
|
|
98
121
|
const analyser = context.createAnalyser();
|
|
99
122
|
analyser.fftSize = 512;
|
|
100
|
-
analyser.smoothingTimeConstant = 0.
|
|
123
|
+
analyser.smoothingTimeConstant = 0.35;
|
|
101
124
|
analyser.minDecibels = -90;
|
|
102
125
|
analyser.maxDecibels = -10;
|
|
103
126
|
source.connect(analyser);
|
|
127
|
+
const sink = context.createGain();
|
|
128
|
+
sink.gain.value = 0;
|
|
129
|
+
analyser.connect(sink);
|
|
130
|
+
sink.connect(context.destination);
|
|
104
131
|
const data = new Float32Array(analyser.fftSize);
|
|
105
132
|
const state = {
|
|
106
133
|
participantId,
|
|
@@ -108,15 +135,20 @@ var ActiveSpeakerDetector = class {
|
|
|
108
135
|
stream,
|
|
109
136
|
source,
|
|
110
137
|
analyser,
|
|
138
|
+
sink,
|
|
111
139
|
data,
|
|
112
140
|
active: false,
|
|
141
|
+
level: 0,
|
|
113
142
|
lastActiveAt: 0
|
|
114
143
|
};
|
|
115
144
|
this.speakers.set(participantId, state);
|
|
116
145
|
track.addEventListener(
|
|
117
146
|
"ended",
|
|
118
147
|
() => {
|
|
119
|
-
this.
|
|
148
|
+
const current = this.speakers.get(participantId);
|
|
149
|
+
if (current?.track === track) {
|
|
150
|
+
this.removeParticipant(participantId);
|
|
151
|
+
}
|
|
120
152
|
},
|
|
121
153
|
{ once: true }
|
|
122
154
|
);
|
|
@@ -127,15 +159,19 @@ var ActiveSpeakerDetector = class {
|
|
|
127
159
|
const state = this.speakers.get(participantId);
|
|
128
160
|
if (!state) return;
|
|
129
161
|
state.active = false;
|
|
162
|
+
state.level = 0;
|
|
130
163
|
try {
|
|
131
164
|
state.source.disconnect();
|
|
132
165
|
} catch {
|
|
133
166
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
167
|
+
try {
|
|
168
|
+
state.analyser.disconnect();
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
state.sink.disconnect();
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
139
175
|
this.speakers.delete(participantId);
|
|
140
176
|
this.emitIfChanged();
|
|
141
177
|
}
|
|
@@ -147,7 +183,7 @@ var ActiveSpeakerDetector = class {
|
|
|
147
183
|
}
|
|
148
184
|
}
|
|
149
185
|
clear() {
|
|
150
|
-
for (const participantId of this.speakers.keys()) {
|
|
186
|
+
for (const participantId of [...this.speakers.keys()]) {
|
|
151
187
|
this.removeParticipant(participantId);
|
|
152
188
|
}
|
|
153
189
|
this.speakers.clear();
|
|
@@ -167,6 +203,7 @@ var ActiveSpeakerDetector = class {
|
|
|
167
203
|
const now = performance.now();
|
|
168
204
|
for (const state of this.speakers.values()) {
|
|
169
205
|
if (state.track.readyState !== "live" || state.track.muted || !state.track.enabled) {
|
|
206
|
+
state.level = 0;
|
|
170
207
|
if (state.active) {
|
|
171
208
|
state.active = false;
|
|
172
209
|
}
|
|
@@ -179,30 +216,33 @@ var ActiveSpeakerDetector = class {
|
|
|
179
216
|
sumSquares += sample * sample;
|
|
180
217
|
}
|
|
181
218
|
const rms = Math.sqrt(sumSquares / state.data.length);
|
|
219
|
+
state.level = rms;
|
|
182
220
|
if (!state.active) {
|
|
183
221
|
if (rms >= this.threshold) {
|
|
184
222
|
state.active = true;
|
|
185
223
|
state.lastActiveAt = now;
|
|
186
224
|
}
|
|
187
|
-
} else {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
state.active = false;
|
|
192
|
-
}
|
|
225
|
+
} else if (rms >= this.releaseThreshold) {
|
|
226
|
+
state.lastActiveAt = now;
|
|
227
|
+
} else if (now - state.lastActiveAt >= this.releaseMs) {
|
|
228
|
+
state.active = false;
|
|
193
229
|
}
|
|
194
230
|
}
|
|
195
231
|
this.emitIfChanged();
|
|
196
232
|
}
|
|
233
|
+
getActiveSpeakers() {
|
|
234
|
+
return Array.from(this.speakers.values()).filter((speaker) => speaker.active).sort((a, b) => {
|
|
235
|
+
if (b.level !== a.level) return b.level - a.level;
|
|
236
|
+
return a.participantId.localeCompare(b.participantId);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
197
239
|
getActiveSpeakerIds() {
|
|
198
|
-
return
|
|
240
|
+
return this.getActiveSpeakers().map((speaker) => speaker.participantId);
|
|
199
241
|
}
|
|
200
242
|
emitIfChanged(force = false) {
|
|
201
|
-
const ids = this.getActiveSpeakerIds()
|
|
243
|
+
const ids = this.getActiveSpeakerIds();
|
|
202
244
|
const key = ids.join("|");
|
|
203
|
-
if (!force && key === this.lastEmitted)
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
245
|
+
if (!force && key === this.lastEmitted) return;
|
|
206
246
|
this.lastEmitted = key;
|
|
207
247
|
this.onActiveSpeakersChanged(ids);
|
|
208
248
|
}
|
|
@@ -377,7 +417,7 @@ var MeetingState = class {
|
|
|
377
417
|
const nextMedia = {
|
|
378
418
|
stream: patch.media?.stream ?? prevMedia.stream,
|
|
379
419
|
screenStream: patch.media?.screenStream ?? prevMedia.screenStream,
|
|
380
|
-
cameraTrack: patch.media
|
|
420
|
+
cameraTrack: patch.media && Object.prototype.hasOwnProperty.call(patch.media, "cameraTrack") ? patch.media.cameraTrack ?? void 0 : prevMedia.cameraTrack,
|
|
381
421
|
screenTrack: patch.media?.screenTrack ?? prevMedia.screenTrack,
|
|
382
422
|
audioTrack: patch.media?.audioTrack ?? prevMedia.audioTrack,
|
|
383
423
|
micEnabled: patch.media?.micEnabled ?? prevMedia.micEnabled,
|
|
@@ -638,6 +678,71 @@ var SFUClient = class {
|
|
|
638
678
|
setLocalStream(stream) {
|
|
639
679
|
this.localStream = stream;
|
|
640
680
|
}
|
|
681
|
+
/**
|
|
682
|
+
* Attach or detach the local camera track without replacing the publisher
|
|
683
|
+
* PeerConnection. Detaching with replaceTrack(null) stops media transmission;
|
|
684
|
+
* VideoSDKCore separately calls MediaStreamTrack.stop() to release hardware.
|
|
685
|
+
*/
|
|
686
|
+
async setCameraTrack(track) {
|
|
687
|
+
if (!this.localStream) this.localStream = new MediaStream();
|
|
688
|
+
const currentTrack = this.localStream.getVideoTracks().find((item) => item !== track);
|
|
689
|
+
if (currentTrack) this.localStream.removeTrack(currentTrack);
|
|
690
|
+
if (track && !this.localStream.getTracks().includes(track)) {
|
|
691
|
+
this.localStream.addTrack(track);
|
|
692
|
+
}
|
|
693
|
+
if (!this.videoSender || !this.publisher || !this.cameraTransceiver) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const needsCameraNegotiation = Boolean(
|
|
697
|
+
track && this.cameraTransceiver.direction === "inactive"
|
|
698
|
+
);
|
|
699
|
+
if (needsCameraNegotiation && this.ws?.readyState !== WebSocket.OPEN) {
|
|
700
|
+
throw new Error("Cannot enable the camera while signaling is disconnected");
|
|
701
|
+
}
|
|
702
|
+
await this.videoSender.replaceTrack(track);
|
|
703
|
+
if (needsCameraNegotiation) {
|
|
704
|
+
await this.waitForPublisherStable();
|
|
705
|
+
this.cameraTransceiver.direction = "sendonly";
|
|
706
|
+
await this.sendPublisherOffer();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
async waitForPublisherStable(timeoutMs = 5e3) {
|
|
710
|
+
const publisher = this.publisher;
|
|
711
|
+
if (!publisher) throw new Error("Publisher PeerConnection is not initialized");
|
|
712
|
+
const startedAt = Date.now();
|
|
713
|
+
while (publisher.signalingState !== "stable") {
|
|
714
|
+
if (publisher.signalingState === "closed") {
|
|
715
|
+
throw new Error("Publisher PeerConnection is closed");
|
|
716
|
+
}
|
|
717
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
718
|
+
throw new Error("Publisher negotiation did not become stable in time");
|
|
719
|
+
}
|
|
720
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
async sendPublisherOffer() {
|
|
724
|
+
if (!this.publisher) throw new Error("Publisher PeerConnection is not initialized");
|
|
725
|
+
const offer = await this.publisher.createOffer();
|
|
726
|
+
await this.publisher.setLocalDescription(offer);
|
|
727
|
+
const sent = this.send({
|
|
728
|
+
type: "publish_offer",
|
|
729
|
+
payload: {
|
|
730
|
+
revision: ++this.publisherRevision,
|
|
731
|
+
sdp: offer.sdp
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
if (!sent) {
|
|
735
|
+
try {
|
|
736
|
+
await this.publisher.setLocalDescription({ type: "rollback" });
|
|
737
|
+
} catch {
|
|
738
|
+
}
|
|
739
|
+
if (this.cameraTransceiver) {
|
|
740
|
+
this.cameraTransceiver.direction = "inactive";
|
|
741
|
+
}
|
|
742
|
+
await this.videoSender?.replaceTrack(null);
|
|
743
|
+
throw new Error("Unable to negotiate camera publishing: signaling is disconnected");
|
|
744
|
+
}
|
|
745
|
+
}
|
|
641
746
|
nextRequestId() {
|
|
642
747
|
return (++this.requestId).toString();
|
|
643
748
|
}
|
|
@@ -2315,6 +2420,8 @@ var VideoSDKCore = class {
|
|
|
2315
2420
|
this.screenStream = null;
|
|
2316
2421
|
this.isScreenSharing = false;
|
|
2317
2422
|
this.participantName = "";
|
|
2423
|
+
this.cameraEnabled = false;
|
|
2424
|
+
this.cameraToggleInProgress = false;
|
|
2318
2425
|
this.intentionalDisconnect = false;
|
|
2319
2426
|
this.remoteStreams = /* @__PURE__ */ new Map();
|
|
2320
2427
|
this.remoteScreenStreams = /* @__PURE__ */ new Map();
|
|
@@ -2347,26 +2454,12 @@ var VideoSDKCore = class {
|
|
|
2347
2454
|
async initLocal(video, name) {
|
|
2348
2455
|
this.participantName = name;
|
|
2349
2456
|
try {
|
|
2350
|
-
this.localStream = await
|
|
2351
|
-
|
|
2352
|
-
width: { ideal: 1280 },
|
|
2353
|
-
height: { ideal: 720 }
|
|
2354
|
-
},
|
|
2355
|
-
audio: {
|
|
2356
|
-
echoCancellation: true,
|
|
2357
|
-
noiseSuppression: true,
|
|
2358
|
-
autoGainControl: true
|
|
2359
|
-
}
|
|
2360
|
-
});
|
|
2457
|
+
this.localStream = await this.acquireOptionalLocalMedia();
|
|
2458
|
+
this.cameraEnabled = this.hasLiveCameraTrack();
|
|
2361
2459
|
const localAudioTrack = this.localStream.getAudioTracks()[0];
|
|
2362
2460
|
if (localAudioTrack) {
|
|
2363
2461
|
this.activeSpeakerDetector.addTrack(this.myId, localAudioTrack);
|
|
2364
2462
|
}
|
|
2365
|
-
const hasVideo = this.localStream.getVideoTracks().some((t) => t.readyState === "live");
|
|
2366
|
-
const hasAudio = this.localStream.getAudioTracks().some((t) => t.readyState === "live");
|
|
2367
|
-
if (!hasVideo || !hasAudio) {
|
|
2368
|
-
throw new Error(`Missing tracks: video=${hasVideo}, audio=${hasAudio}`);
|
|
2369
|
-
}
|
|
2370
2463
|
video.srcObject = this.localStream;
|
|
2371
2464
|
this.updateLocalMedia();
|
|
2372
2465
|
} catch (err) {
|
|
@@ -2374,17 +2467,47 @@ var VideoSDKCore = class {
|
|
|
2374
2467
|
throw err;
|
|
2375
2468
|
}
|
|
2376
2469
|
}
|
|
2377
|
-
|
|
2470
|
+
hasLiveCameraTrack() {
|
|
2471
|
+
return Boolean(
|
|
2472
|
+
this.localStream?.getVideoTracks().some((track) => track.readyState === "live")
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
getLiveCameraTrack() {
|
|
2476
|
+
return this.localStream?.getVideoTracks().find((track) => track.readyState === "live") ?? null;
|
|
2477
|
+
}
|
|
2478
|
+
/**
|
|
2479
|
+
* Stop every locally captured camera track and remove it from the stream.
|
|
2480
|
+
* Track.enabled=false only emits black frames; stop() releases camera capture.
|
|
2481
|
+
*/
|
|
2482
|
+
stopCameraCapture() {
|
|
2483
|
+
if (!this.localStream) {
|
|
2484
|
+
this.cameraEnabled = false;
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
for (const track of this.localStream.getVideoTracks()) {
|
|
2488
|
+
this.localStream.removeTrack(track);
|
|
2489
|
+
try {
|
|
2490
|
+
track.stop();
|
|
2491
|
+
} catch {
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
this.cameraEnabled = false;
|
|
2495
|
+
}
|
|
2496
|
+
updateLocalMedia() {
|
|
2378
2497
|
if (!this.localStream) return;
|
|
2498
|
+
const cameraTrack = this.getLiveCameraTrack();
|
|
2499
|
+
const audioTrack = this.localStream.getAudioTracks().find((track) => track.readyState === "live");
|
|
2500
|
+
this.cameraEnabled = Boolean(cameraTrack && cameraTrack.enabled);
|
|
2379
2501
|
this.state.updateLocalParticipant({
|
|
2380
2502
|
id: this.myId,
|
|
2381
2503
|
name: this.participantName,
|
|
2382
2504
|
media: {
|
|
2383
2505
|
stream: this.localStream,
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2506
|
+
// Explicit null clears a previously published track from React state.
|
|
2507
|
+
cameraTrack,
|
|
2508
|
+
audioTrack: audioTrack ?? void 0,
|
|
2509
|
+
micEnabled: audioTrack?.enabled ?? false,
|
|
2510
|
+
camEnabled: this.cameraEnabled,
|
|
2388
2511
|
isScreenSharing: this.isScreenSharing,
|
|
2389
2512
|
screenStream: this.screenStream,
|
|
2390
2513
|
screenTrack: this.screenStream?.getVideoTracks()[0]
|
|
@@ -2411,6 +2534,89 @@ var VideoSDKCore = class {
|
|
|
2411
2534
|
async getMeetingSummary(roomCode, sessionId) {
|
|
2412
2535
|
return this.sfuClient.getMeetingSummary(roomCode, sessionId);
|
|
2413
2536
|
}
|
|
2537
|
+
async acquireOptionalLocalMedia(cameraDeviceId, microphoneDeviceId, includeVideo = true) {
|
|
2538
|
+
const combined = new MediaStream();
|
|
2539
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
2540
|
+
console.warn(
|
|
2541
|
+
"[SDK] Camera/microphone access is not supported; joining without local media."
|
|
2542
|
+
);
|
|
2543
|
+
return combined;
|
|
2544
|
+
}
|
|
2545
|
+
const cameraPromise = includeVideo ? navigator.mediaDevices.getUserMedia({
|
|
2546
|
+
video: cameraDeviceId ? { deviceId: { exact: cameraDeviceId } } : {
|
|
2547
|
+
width: { ideal: 1280 },
|
|
2548
|
+
height: { ideal: 720 }
|
|
2549
|
+
},
|
|
2550
|
+
audio: false
|
|
2551
|
+
}) : Promise.resolve(null);
|
|
2552
|
+
const microphonePromise = navigator.mediaDevices.getUserMedia({
|
|
2553
|
+
video: false,
|
|
2554
|
+
audio: microphoneDeviceId ? {
|
|
2555
|
+
deviceId: { exact: microphoneDeviceId },
|
|
2556
|
+
echoCancellation: true,
|
|
2557
|
+
noiseSuppression: true,
|
|
2558
|
+
autoGainControl: true
|
|
2559
|
+
} : {
|
|
2560
|
+
echoCancellation: true,
|
|
2561
|
+
noiseSuppression: true,
|
|
2562
|
+
autoGainControl: true
|
|
2563
|
+
}
|
|
2564
|
+
});
|
|
2565
|
+
const [cameraResult, microphoneResult] = await Promise.allSettled([
|
|
2566
|
+
cameraPromise,
|
|
2567
|
+
microphonePromise
|
|
2568
|
+
]);
|
|
2569
|
+
if (cameraResult.status === "fulfilled" && cameraResult.value) {
|
|
2570
|
+
for (const track of cameraResult.value.getVideoTracks()) {
|
|
2571
|
+
combined.addTrack(track);
|
|
2572
|
+
}
|
|
2573
|
+
} else if (cameraResult.status === "rejected") {
|
|
2574
|
+
console.warn("[SDK] Camera unavailable; continuing without video.", {
|
|
2575
|
+
name: cameraResult.reason?.name,
|
|
2576
|
+
message: cameraResult.reason?.message
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
if (microphoneResult.status === "fulfilled") {
|
|
2580
|
+
for (const track of microphoneResult.value.getAudioTracks()) {
|
|
2581
|
+
combined.addTrack(track);
|
|
2582
|
+
}
|
|
2583
|
+
} else {
|
|
2584
|
+
console.warn(
|
|
2585
|
+
"[SDK] Microphone unavailable; continuing without audio.",
|
|
2586
|
+
{
|
|
2587
|
+
name: microphoneResult.reason?.name,
|
|
2588
|
+
message: microphoneResult.reason?.message
|
|
2589
|
+
}
|
|
2590
|
+
);
|
|
2591
|
+
}
|
|
2592
|
+
return combined;
|
|
2593
|
+
}
|
|
2594
|
+
async acquireCameraTrack() {
|
|
2595
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
2596
|
+
throw new Error("Camera access is not supported on this device");
|
|
2597
|
+
}
|
|
2598
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
2599
|
+
video: this.cameraDeviceId ? { deviceId: { exact: this.cameraDeviceId } } : {
|
|
2600
|
+
width: { ideal: 1280 },
|
|
2601
|
+
height: { ideal: 720 }
|
|
2602
|
+
},
|
|
2603
|
+
audio: false
|
|
2604
|
+
});
|
|
2605
|
+
const track = stream.getVideoTracks()[0];
|
|
2606
|
+
if (!track) {
|
|
2607
|
+
stream.getTracks().forEach((item) => item.stop());
|
|
2608
|
+
throw new Error("The selected camera did not provide a video track");
|
|
2609
|
+
}
|
|
2610
|
+
this.cameraDeviceId = track.getSettings().deviceId || this.cameraDeviceId;
|
|
2611
|
+
return track;
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Wakes the active-speaker Web Audio pipeline. Call this from a user gesture
|
|
2615
|
+
* whenever possible (Join, first click inside the meeting, etc.).
|
|
2616
|
+
*/
|
|
2617
|
+
async resumeActiveSpeaker() {
|
|
2618
|
+
return this.activeSpeakerDetector.resume();
|
|
2619
|
+
}
|
|
2414
2620
|
// JOIN
|
|
2415
2621
|
async joinMeeting(config) {
|
|
2416
2622
|
const {
|
|
@@ -2453,16 +2659,13 @@ var VideoSDKCore = class {
|
|
|
2453
2659
|
this.participantName = name;
|
|
2454
2660
|
this.room.id = roomId;
|
|
2455
2661
|
this.room.name = name;
|
|
2662
|
+
this.cameraDeviceId = cameraDeviceId ?? this.cameraDeviceId;
|
|
2456
2663
|
if (!this.localStream) {
|
|
2457
|
-
this.localStream = await
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
noiseSuppression: true,
|
|
2463
|
-
autoGainControl: true
|
|
2464
|
-
} : true
|
|
2465
|
-
});
|
|
2664
|
+
this.localStream = await this.acquireOptionalLocalMedia(
|
|
2665
|
+
this.cameraDeviceId,
|
|
2666
|
+
microphoneDeviceId,
|
|
2667
|
+
!videoMuted
|
|
2668
|
+
);
|
|
2466
2669
|
const localAudioTrack = this.localStream.getAudioTracks()[0];
|
|
2467
2670
|
if (localAudioTrack) {
|
|
2468
2671
|
this.activeSpeakerDetector.addTrack(this.myId, localAudioTrack);
|
|
@@ -2471,10 +2674,17 @@ var VideoSDKCore = class {
|
|
|
2471
2674
|
this.localStream.getAudioTracks().forEach((track) => {
|
|
2472
2675
|
track.enabled = !audioMuted;
|
|
2473
2676
|
});
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2677
|
+
if (videoMuted) {
|
|
2678
|
+
this.stopCameraCapture();
|
|
2679
|
+
} else {
|
|
2680
|
+
const initialCameraTrack = this.getLiveCameraTrack();
|
|
2681
|
+
this.cameraEnabled = Boolean(initialCameraTrack);
|
|
2682
|
+
if (initialCameraTrack) {
|
|
2683
|
+
this.cameraDeviceId = initialCameraTrack.getSettings().deviceId || this.cameraDeviceId;
|
|
2684
|
+
initialCameraTrack.enabled = true;
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
this.updateLocalMedia();
|
|
2478
2688
|
const promise = this.connect(roomId, name);
|
|
2479
2689
|
this.joinPromise = promise;
|
|
2480
2690
|
try {
|
|
@@ -2507,8 +2717,8 @@ var VideoSDKCore = class {
|
|
|
2507
2717
|
await this.sfuClient.getToken(roomId, {
|
|
2508
2718
|
displayName: name,
|
|
2509
2719
|
participantId: this.myId,
|
|
2510
|
-
audioMuted: !(this.localStream?.getAudioTracks()[0]?.enabled ??
|
|
2511
|
-
videoMuted: !(this.localStream?.getVideoTracks()[0]?.enabled ??
|
|
2720
|
+
audioMuted: !(this.localStream?.getAudioTracks()[0]?.enabled ?? false),
|
|
2721
|
+
videoMuted: !(this.localStream?.getVideoTracks()[0]?.enabled ?? false)
|
|
2512
2722
|
});
|
|
2513
2723
|
}
|
|
2514
2724
|
createSFUClient(token) {
|
|
@@ -3101,7 +3311,12 @@ var VideoSDKCore = class {
|
|
|
3101
3311
|
if (!this.sfuClient) {
|
|
3102
3312
|
throw new Error("SFU client is not initialized");
|
|
3103
3313
|
}
|
|
3314
|
+
if (!this.cameraEnabled) {
|
|
3315
|
+
throw new Error("Your camera is off. Turn it on before switching devices.");
|
|
3316
|
+
}
|
|
3104
3317
|
const track = await this.sfuClient.selectCamera(deviceId);
|
|
3318
|
+
this.cameraDeviceId = deviceId;
|
|
3319
|
+
this.cameraEnabled = true;
|
|
3105
3320
|
if (this.localStream) {
|
|
3106
3321
|
this.state.localStream = this.localStream;
|
|
3107
3322
|
this.updateLocalMedia();
|
|
@@ -3143,24 +3358,52 @@ var VideoSDKCore = class {
|
|
|
3143
3358
|
this.sfuClient?.muteAudio(!nextEnabled);
|
|
3144
3359
|
this.events.onMicToggled?.(this.myId, nextEnabled);
|
|
3145
3360
|
}
|
|
3146
|
-
toggleCam() {
|
|
3147
|
-
|
|
3148
|
-
if (
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
camEnabled: nextEnabled
|
|
3361
|
+
async toggleCam() {
|
|
3362
|
+
if (!this.state.localParticipant?.media || !this.localStream) return;
|
|
3363
|
+
if (this.cameraToggleInProgress) return;
|
|
3364
|
+
this.cameraToggleInProgress = true;
|
|
3365
|
+
const nextEnabled = !this.cameraEnabled;
|
|
3366
|
+
try {
|
|
3367
|
+
if (!nextEnabled) {
|
|
3368
|
+
this.sfuClient?.muteVideo(true);
|
|
3369
|
+
this.stopCameraCapture();
|
|
3370
|
+
await this.sfuClient?.setCameraTrack(null);
|
|
3371
|
+
this.updateLocalMedia();
|
|
3372
|
+
this.events.onCamToggled?.(this.myId, false);
|
|
3373
|
+
return;
|
|
3160
3374
|
}
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3375
|
+
const track = await this.acquireCameraTrack();
|
|
3376
|
+
if (!this.localStream) this.localStream = new MediaStream();
|
|
3377
|
+
this.localStream.addTrack(track);
|
|
3378
|
+
try {
|
|
3379
|
+
await this.sfuClient?.setCameraTrack(track);
|
|
3380
|
+
this.cameraEnabled = true;
|
|
3381
|
+
this.sfuClient?.muteVideo(false);
|
|
3382
|
+
this.updateLocalMedia();
|
|
3383
|
+
this.events.onCamToggled?.(this.myId, true);
|
|
3384
|
+
} catch (error) {
|
|
3385
|
+
this.localStream.removeTrack(track);
|
|
3386
|
+
try {
|
|
3387
|
+
track.stop();
|
|
3388
|
+
} catch {
|
|
3389
|
+
}
|
|
3390
|
+
this.cameraEnabled = false;
|
|
3391
|
+
this.updateLocalMedia();
|
|
3392
|
+
throw error;
|
|
3393
|
+
}
|
|
3394
|
+
} catch (error) {
|
|
3395
|
+
this.cameraEnabled = false;
|
|
3396
|
+
this.updateLocalMedia();
|
|
3397
|
+
this.emitError(
|
|
3398
|
+
"CAMERA_TOGGLE_FAILED",
|
|
3399
|
+
error?.message || "Unable to change camera state",
|
|
3400
|
+
error,
|
|
3401
|
+
true
|
|
3402
|
+
);
|
|
3403
|
+
throw error;
|
|
3404
|
+
} finally {
|
|
3405
|
+
this.cameraToggleInProgress = false;
|
|
3406
|
+
}
|
|
3164
3407
|
}
|
|
3165
3408
|
// SCREEN SHARING
|
|
3166
3409
|
async startScreenShare() {
|
|
@@ -3318,6 +3561,8 @@ var VideoSDKCore = class {
|
|
|
3318
3561
|
this.localStream.getTracks().forEach((track) => track.stop());
|
|
3319
3562
|
this.localStream = null;
|
|
3320
3563
|
}
|
|
3564
|
+
this.cameraEnabled = false;
|
|
3565
|
+
this.cameraToggleInProgress = false;
|
|
3321
3566
|
if (this.screenStream) {
|
|
3322
3567
|
this.screenStream.getTracks().forEach((track) => track.stop());
|
|
3323
3568
|
this.screenStream = null;
|
|
@@ -3568,6 +3813,7 @@ var MeetingProvider = ({
|
|
|
3568
3813
|
getMeetingSummary: sdk.getMeetingSummary.bind(sdk),
|
|
3569
3814
|
toggleMic: sdk.toggleMic.bind(sdk),
|
|
3570
3815
|
toggleCam: sdk.toggleCam.bind(sdk),
|
|
3816
|
+
resumeActiveSpeaker: sdk.resumeActiveSpeaker.bind(sdk),
|
|
3571
3817
|
startScreenShare: sdk.startScreenShare.bind(sdk),
|
|
3572
3818
|
stopScreenShare: sdk.stopScreenShare.bind(sdk),
|
|
3573
3819
|
sendMessage: sdk.sendChatMessage.bind(sdk),
|