@defcomm/meetingsdk 0.6.9 → 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 +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +195 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +195 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -421,10 +421,26 @@ Toggle the local camera:
|
|
|
421
421
|
```tsx
|
|
422
422
|
const { toggleCam } = useMeeting();
|
|
423
423
|
|
|
424
|
-
<button
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
424
|
+
<button
|
|
425
|
+
onClick={() =>
|
|
426
|
+
void toggleCam().catch((error) => {
|
|
427
|
+
console.error("Unable to toggle camera", error);
|
|
428
|
+
})
|
|
429
|
+
}
|
|
430
|
+
>
|
|
431
|
+
Toggle camera
|
|
432
|
+
</button>;
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Camera-off is a privacy action: the SDK detaches and stops the active camera
|
|
436
|
+
`MediaStreamTrack`, releasing the browser's camera capture. It does not merely
|
|
437
|
+
set `track.enabled = false`. Turning the camera back on explicitly requests a
|
|
438
|
+
new camera track and may trigger the browser's permission prompt if access was
|
|
439
|
+
revoked. Joining with `videoMuted: true` does not request camera access at all.
|
|
440
|
+
|
|
441
|
+
The local participant state exposes `media.micEnabled` and `media.camEnabled`,
|
|
442
|
+
so the button can reflect the current state. `toggleCam()` returns a Promise;
|
|
443
|
+
handle errors if camera permission is denied or the device is unavailable.
|
|
428
444
|
|
|
429
445
|
---
|
|
430
446
|
|
|
@@ -811,7 +827,7 @@ export function Meeting() {
|
|
|
811
827
|
<div>
|
|
812
828
|
<button onClick={() => void join()}>Join</button>
|
|
813
829
|
<button onClick={() => toggleMic()}>Mic</button>
|
|
814
|
-
<button onClick={() => toggleCam()}>Camera</button>
|
|
830
|
+
<button onClick={() => void toggleCam()}>Camera</button>
|
|
815
831
|
<button onClick={() => void startScreenShare()}>
|
|
816
832
|
Share screen
|
|
817
833
|
</button>
|
package/dist/index.d.mts
CHANGED
|
@@ -112,7 +112,7 @@ type Participant = {
|
|
|
112
112
|
type ParticipantMedia = {
|
|
113
113
|
stream?: MediaStream | null;
|
|
114
114
|
screenStream?: MediaStream | null;
|
|
115
|
-
cameraTrack?: MediaStreamTrack;
|
|
115
|
+
cameraTrack?: MediaStreamTrack | null;
|
|
116
116
|
screenTrack?: MediaStreamTrack;
|
|
117
117
|
audioTrack?: MediaStreamTrack;
|
|
118
118
|
micEnabled: boolean;
|
|
@@ -315,6 +315,9 @@ declare class VideoSDKCore {
|
|
|
315
315
|
private screenStream;
|
|
316
316
|
private isScreenSharing;
|
|
317
317
|
private participantName;
|
|
318
|
+
private cameraDeviceId?;
|
|
319
|
+
private cameraEnabled;
|
|
320
|
+
private cameraToggleInProgress;
|
|
318
321
|
readonly state: MeetingState;
|
|
319
322
|
private intentionalDisconnect;
|
|
320
323
|
private remoteStreams;
|
|
@@ -325,6 +328,13 @@ declare class VideoSDKCore {
|
|
|
325
328
|
private emitError;
|
|
326
329
|
constructor(options: VideoSDKCoreOptions, events?: Events, apiBase?: string, wsBase?: string);
|
|
327
330
|
initLocal(video: HTMLVideoElement, name: string): Promise<void>;
|
|
331
|
+
private hasLiveCameraTrack;
|
|
332
|
+
private getLiveCameraTrack;
|
|
333
|
+
/**
|
|
334
|
+
* Stop every locally captured camera track and remove it from the stream.
|
|
335
|
+
* Track.enabled=false only emits black frames; stop() releases camera capture.
|
|
336
|
+
*/
|
|
337
|
+
private stopCameraCapture;
|
|
328
338
|
private updateLocalMedia;
|
|
329
339
|
createRoom(input: {
|
|
330
340
|
name: string;
|
|
@@ -340,6 +350,7 @@ declare class VideoSDKCore {
|
|
|
340
350
|
summary: MeetingSummary | null;
|
|
341
351
|
}>;
|
|
342
352
|
private acquireOptionalLocalMedia;
|
|
353
|
+
private acquireCameraTrack;
|
|
343
354
|
/**
|
|
344
355
|
* Wakes the active-speaker Web Audio pipeline. Call this from a user gesture
|
|
345
356
|
* whenever possible (Join, first click inside the meeting, etc.).
|
|
@@ -367,7 +378,7 @@ declare class VideoSDKCore {
|
|
|
367
378
|
selectCamera(deviceId: string): Promise<MediaStreamTrack>;
|
|
368
379
|
selectMicrophone(deviceId: string): Promise<MediaStreamTrack>;
|
|
369
380
|
toggleMic(): void;
|
|
370
|
-
toggleCam(): void
|
|
381
|
+
toggleCam(): Promise<void>;
|
|
371
382
|
startScreenShare(): Promise<MediaStream | null>;
|
|
372
383
|
stopScreenShare(): Promise<void>;
|
|
373
384
|
startRecording(): Promise<void>;
|
|
@@ -432,7 +443,7 @@ type MeetingContextValue = {
|
|
|
432
443
|
summary: MeetingSummary | null;
|
|
433
444
|
}>;
|
|
434
445
|
toggleMic: () => void;
|
|
435
|
-
toggleCam: () => void
|
|
446
|
+
toggleCam: () => Promise<void>;
|
|
436
447
|
resumeActiveSpeaker: () => Promise<boolean>;
|
|
437
448
|
startScreenShare: () => Promise<MediaStream | null>;
|
|
438
449
|
stopScreenShare: () => void;
|
|
@@ -530,7 +541,7 @@ declare const useMeeting: (handlers?: {
|
|
|
530
541
|
summary: MeetingSummary | null;
|
|
531
542
|
}>;
|
|
532
543
|
toggleMic: () => void;
|
|
533
|
-
toggleCam: () => void
|
|
544
|
+
toggleCam: () => Promise<void>;
|
|
534
545
|
resumeActiveSpeaker: () => Promise<boolean>;
|
|
535
546
|
startScreenShare: () => Promise<MediaStream | null>;
|
|
536
547
|
stopScreenShare: () => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -112,7 +112,7 @@ type Participant = {
|
|
|
112
112
|
type ParticipantMedia = {
|
|
113
113
|
stream?: MediaStream | null;
|
|
114
114
|
screenStream?: MediaStream | null;
|
|
115
|
-
cameraTrack?: MediaStreamTrack;
|
|
115
|
+
cameraTrack?: MediaStreamTrack | null;
|
|
116
116
|
screenTrack?: MediaStreamTrack;
|
|
117
117
|
audioTrack?: MediaStreamTrack;
|
|
118
118
|
micEnabled: boolean;
|
|
@@ -315,6 +315,9 @@ declare class VideoSDKCore {
|
|
|
315
315
|
private screenStream;
|
|
316
316
|
private isScreenSharing;
|
|
317
317
|
private participantName;
|
|
318
|
+
private cameraDeviceId?;
|
|
319
|
+
private cameraEnabled;
|
|
320
|
+
private cameraToggleInProgress;
|
|
318
321
|
readonly state: MeetingState;
|
|
319
322
|
private intentionalDisconnect;
|
|
320
323
|
private remoteStreams;
|
|
@@ -325,6 +328,13 @@ declare class VideoSDKCore {
|
|
|
325
328
|
private emitError;
|
|
326
329
|
constructor(options: VideoSDKCoreOptions, events?: Events, apiBase?: string, wsBase?: string);
|
|
327
330
|
initLocal(video: HTMLVideoElement, name: string): Promise<void>;
|
|
331
|
+
private hasLiveCameraTrack;
|
|
332
|
+
private getLiveCameraTrack;
|
|
333
|
+
/**
|
|
334
|
+
* Stop every locally captured camera track and remove it from the stream.
|
|
335
|
+
* Track.enabled=false only emits black frames; stop() releases camera capture.
|
|
336
|
+
*/
|
|
337
|
+
private stopCameraCapture;
|
|
328
338
|
private updateLocalMedia;
|
|
329
339
|
createRoom(input: {
|
|
330
340
|
name: string;
|
|
@@ -340,6 +350,7 @@ declare class VideoSDKCore {
|
|
|
340
350
|
summary: MeetingSummary | null;
|
|
341
351
|
}>;
|
|
342
352
|
private acquireOptionalLocalMedia;
|
|
353
|
+
private acquireCameraTrack;
|
|
343
354
|
/**
|
|
344
355
|
* Wakes the active-speaker Web Audio pipeline. Call this from a user gesture
|
|
345
356
|
* whenever possible (Join, first click inside the meeting, etc.).
|
|
@@ -367,7 +378,7 @@ declare class VideoSDKCore {
|
|
|
367
378
|
selectCamera(deviceId: string): Promise<MediaStreamTrack>;
|
|
368
379
|
selectMicrophone(deviceId: string): Promise<MediaStreamTrack>;
|
|
369
380
|
toggleMic(): void;
|
|
370
|
-
toggleCam(): void
|
|
381
|
+
toggleCam(): Promise<void>;
|
|
371
382
|
startScreenShare(): Promise<MediaStream | null>;
|
|
372
383
|
stopScreenShare(): Promise<void>;
|
|
373
384
|
startRecording(): Promise<void>;
|
|
@@ -432,7 +443,7 @@ type MeetingContextValue = {
|
|
|
432
443
|
summary: MeetingSummary | null;
|
|
433
444
|
}>;
|
|
434
445
|
toggleMic: () => void;
|
|
435
|
-
toggleCam: () => void
|
|
446
|
+
toggleCam: () => Promise<void>;
|
|
436
447
|
resumeActiveSpeaker: () => Promise<boolean>;
|
|
437
448
|
startScreenShare: () => Promise<MediaStream | null>;
|
|
438
449
|
stopScreenShare: () => void;
|
|
@@ -530,7 +541,7 @@ declare const useMeeting: (handlers?: {
|
|
|
530
541
|
summary: MeetingSummary | null;
|
|
531
542
|
}>;
|
|
532
543
|
toggleMic: () => void;
|
|
533
|
-
toggleCam: () => void
|
|
544
|
+
toggleCam: () => Promise<void>;
|
|
534
545
|
resumeActiveSpeaker: () => Promise<boolean>;
|
|
535
546
|
startScreenShare: () => Promise<MediaStream | null>;
|
|
536
547
|
stopScreenShare: () => void;
|
package/dist/index.js
CHANGED
|
@@ -417,7 +417,7 @@ var MeetingState = class {
|
|
|
417
417
|
const nextMedia = {
|
|
418
418
|
stream: patch.media?.stream ?? prevMedia.stream,
|
|
419
419
|
screenStream: patch.media?.screenStream ?? prevMedia.screenStream,
|
|
420
|
-
cameraTrack: patch.media
|
|
420
|
+
cameraTrack: patch.media && Object.prototype.hasOwnProperty.call(patch.media, "cameraTrack") ? patch.media.cameraTrack ?? void 0 : prevMedia.cameraTrack,
|
|
421
421
|
screenTrack: patch.media?.screenTrack ?? prevMedia.screenTrack,
|
|
422
422
|
audioTrack: patch.media?.audioTrack ?? prevMedia.audioTrack,
|
|
423
423
|
micEnabled: patch.media?.micEnabled ?? prevMedia.micEnabled,
|
|
@@ -678,6 +678,71 @@ var SFUClient = class {
|
|
|
678
678
|
setLocalStream(stream) {
|
|
679
679
|
this.localStream = stream;
|
|
680
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
|
+
}
|
|
681
746
|
nextRequestId() {
|
|
682
747
|
return (++this.requestId).toString();
|
|
683
748
|
}
|
|
@@ -2355,6 +2420,8 @@ var VideoSDKCore = class {
|
|
|
2355
2420
|
this.screenStream = null;
|
|
2356
2421
|
this.isScreenSharing = false;
|
|
2357
2422
|
this.participantName = "";
|
|
2423
|
+
this.cameraEnabled = false;
|
|
2424
|
+
this.cameraToggleInProgress = false;
|
|
2358
2425
|
this.intentionalDisconnect = false;
|
|
2359
2426
|
this.remoteStreams = /* @__PURE__ */ new Map();
|
|
2360
2427
|
this.remoteScreenStreams = /* @__PURE__ */ new Map();
|
|
@@ -2388,6 +2455,7 @@ var VideoSDKCore = class {
|
|
|
2388
2455
|
this.participantName = name;
|
|
2389
2456
|
try {
|
|
2390
2457
|
this.localStream = await this.acquireOptionalLocalMedia();
|
|
2458
|
+
this.cameraEnabled = this.hasLiveCameraTrack();
|
|
2391
2459
|
const localAudioTrack = this.localStream.getAudioTracks()[0];
|
|
2392
2460
|
if (localAudioTrack) {
|
|
2393
2461
|
this.activeSpeakerDetector.addTrack(this.myId, localAudioTrack);
|
|
@@ -2399,17 +2467,47 @@ var VideoSDKCore = class {
|
|
|
2399
2467
|
throw err;
|
|
2400
2468
|
}
|
|
2401
2469
|
}
|
|
2402
|
-
|
|
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() {
|
|
2403
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);
|
|
2404
2501
|
this.state.updateLocalParticipant({
|
|
2405
2502
|
id: this.myId,
|
|
2406
2503
|
name: this.participantName,
|
|
2407
2504
|
media: {
|
|
2408
2505
|
stream: this.localStream,
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
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,
|
|
2413
2511
|
isScreenSharing: this.isScreenSharing,
|
|
2414
2512
|
screenStream: this.screenStream,
|
|
2415
2513
|
screenTrack: this.screenStream?.getVideoTracks()[0]
|
|
@@ -2436,7 +2534,7 @@ var VideoSDKCore = class {
|
|
|
2436
2534
|
async getMeetingSummary(roomCode, sessionId) {
|
|
2437
2535
|
return this.sfuClient.getMeetingSummary(roomCode, sessionId);
|
|
2438
2536
|
}
|
|
2439
|
-
async acquireOptionalLocalMedia(cameraDeviceId, microphoneDeviceId) {
|
|
2537
|
+
async acquireOptionalLocalMedia(cameraDeviceId, microphoneDeviceId, includeVideo = true) {
|
|
2440
2538
|
const combined = new MediaStream();
|
|
2441
2539
|
if (!navigator.mediaDevices?.getUserMedia) {
|
|
2442
2540
|
console.warn(
|
|
@@ -2444,13 +2542,13 @@ var VideoSDKCore = class {
|
|
|
2444
2542
|
);
|
|
2445
2543
|
return combined;
|
|
2446
2544
|
}
|
|
2447
|
-
const cameraPromise = navigator.mediaDevices.getUserMedia({
|
|
2545
|
+
const cameraPromise = includeVideo ? navigator.mediaDevices.getUserMedia({
|
|
2448
2546
|
video: cameraDeviceId ? { deviceId: { exact: cameraDeviceId } } : {
|
|
2449
2547
|
width: { ideal: 1280 },
|
|
2450
2548
|
height: { ideal: 720 }
|
|
2451
2549
|
},
|
|
2452
2550
|
audio: false
|
|
2453
|
-
});
|
|
2551
|
+
}) : Promise.resolve(null);
|
|
2454
2552
|
const microphonePromise = navigator.mediaDevices.getUserMedia({
|
|
2455
2553
|
video: false,
|
|
2456
2554
|
audio: microphoneDeviceId ? {
|
|
@@ -2468,11 +2566,11 @@ var VideoSDKCore = class {
|
|
|
2468
2566
|
cameraPromise,
|
|
2469
2567
|
microphonePromise
|
|
2470
2568
|
]);
|
|
2471
|
-
if (cameraResult.status === "fulfilled") {
|
|
2569
|
+
if (cameraResult.status === "fulfilled" && cameraResult.value) {
|
|
2472
2570
|
for (const track of cameraResult.value.getVideoTracks()) {
|
|
2473
2571
|
combined.addTrack(track);
|
|
2474
2572
|
}
|
|
2475
|
-
} else {
|
|
2573
|
+
} else if (cameraResult.status === "rejected") {
|
|
2476
2574
|
console.warn("[SDK] Camera unavailable; continuing without video.", {
|
|
2477
2575
|
name: cameraResult.reason?.name,
|
|
2478
2576
|
message: cameraResult.reason?.message
|
|
@@ -2493,6 +2591,25 @@ var VideoSDKCore = class {
|
|
|
2493
2591
|
}
|
|
2494
2592
|
return combined;
|
|
2495
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
|
+
}
|
|
2496
2613
|
/**
|
|
2497
2614
|
* Wakes the active-speaker Web Audio pipeline. Call this from a user gesture
|
|
2498
2615
|
* whenever possible (Join, first click inside the meeting, etc.).
|
|
@@ -2542,10 +2659,12 @@ var VideoSDKCore = class {
|
|
|
2542
2659
|
this.participantName = name;
|
|
2543
2660
|
this.room.id = roomId;
|
|
2544
2661
|
this.room.name = name;
|
|
2662
|
+
this.cameraDeviceId = cameraDeviceId ?? this.cameraDeviceId;
|
|
2545
2663
|
if (!this.localStream) {
|
|
2546
2664
|
this.localStream = await this.acquireOptionalLocalMedia(
|
|
2547
|
-
cameraDeviceId,
|
|
2548
|
-
microphoneDeviceId
|
|
2665
|
+
this.cameraDeviceId,
|
|
2666
|
+
microphoneDeviceId,
|
|
2667
|
+
!videoMuted
|
|
2549
2668
|
);
|
|
2550
2669
|
const localAudioTrack = this.localStream.getAudioTracks()[0];
|
|
2551
2670
|
if (localAudioTrack) {
|
|
@@ -2555,10 +2674,17 @@ var VideoSDKCore = class {
|
|
|
2555
2674
|
this.localStream.getAudioTracks().forEach((track) => {
|
|
2556
2675
|
track.enabled = !audioMuted;
|
|
2557
2676
|
});
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
}
|
|
2561
|
-
|
|
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();
|
|
2562
2688
|
const promise = this.connect(roomId, name);
|
|
2563
2689
|
this.joinPromise = promise;
|
|
2564
2690
|
try {
|
|
@@ -3185,7 +3311,12 @@ var VideoSDKCore = class {
|
|
|
3185
3311
|
if (!this.sfuClient) {
|
|
3186
3312
|
throw new Error("SFU client is not initialized");
|
|
3187
3313
|
}
|
|
3314
|
+
if (!this.cameraEnabled) {
|
|
3315
|
+
throw new Error("Your camera is off. Turn it on before switching devices.");
|
|
3316
|
+
}
|
|
3188
3317
|
const track = await this.sfuClient.selectCamera(deviceId);
|
|
3318
|
+
this.cameraDeviceId = deviceId;
|
|
3319
|
+
this.cameraEnabled = true;
|
|
3189
3320
|
if (this.localStream) {
|
|
3190
3321
|
this.state.localStream = this.localStream;
|
|
3191
3322
|
this.updateLocalMedia();
|
|
@@ -3227,24 +3358,52 @@ var VideoSDKCore = class {
|
|
|
3227
3358
|
this.sfuClient?.muteAudio(!nextEnabled);
|
|
3228
3359
|
this.events.onMicToggled?.(this.myId, nextEnabled);
|
|
3229
3360
|
}
|
|
3230
|
-
toggleCam() {
|
|
3231
|
-
|
|
3232
|
-
if (
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
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;
|
|
3244
3374
|
}
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
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
|
+
}
|
|
3248
3407
|
}
|
|
3249
3408
|
// SCREEN SHARING
|
|
3250
3409
|
async startScreenShare() {
|
|
@@ -3402,6 +3561,8 @@ var VideoSDKCore = class {
|
|
|
3402
3561
|
this.localStream.getTracks().forEach((track) => track.stop());
|
|
3403
3562
|
this.localStream = null;
|
|
3404
3563
|
}
|
|
3564
|
+
this.cameraEnabled = false;
|
|
3565
|
+
this.cameraToggleInProgress = false;
|
|
3405
3566
|
if (this.screenStream) {
|
|
3406
3567
|
this.screenStream.getTracks().forEach((track) => track.stop());
|
|
3407
3568
|
this.screenStream = null;
|