@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 CHANGED
@@ -421,10 +421,26 @@ Toggle the local camera:
421
421
  ```tsx
422
422
  const { toggleCam } = useMeeting();
423
423
 
424
- <button onClick={toggleCam}>Toggle camera</button>;
425
- ```
426
-
427
- The local participant state exposes `media.micEnabled` and `media.camEnabled`, so the button can reflect the current state.
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?.cameraTrack ?? prevMedia.cameraTrack,
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
- updateLocalMedia(audioMuted = false, videoMuted = false) {
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
- cameraTrack: this.localStream.getVideoTracks()[0],
2410
- audioTrack: this.localStream.getAudioTracks()[0],
2411
- micEnabled: this.localStream.getAudioTracks()[0]?.enabled ?? false,
2412
- camEnabled: this.localStream.getVideoTracks()[0]?.enabled ?? false,
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
- this.localStream.getVideoTracks().forEach((track) => {
2559
- track.enabled = !videoMuted;
2560
- });
2561
- this.updateLocalMedia(audioMuted, videoMuted);
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
- const mediaState = this.state.localParticipant?.media;
3232
- if (!mediaState || !this.localStream) {
3233
- return;
3234
- }
3235
- const nextEnabled = !mediaState.camEnabled;
3236
- this.localStream.getVideoTracks().forEach((track) => {
3237
- track.enabled = nextEnabled;
3238
- });
3239
- this.state.updateLocalParticipant({
3240
- id: this.myId,
3241
- name: this.participantName,
3242
- media: {
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
- this.sfuClient?.muteVideo(!nextEnabled);
3247
- this.events.onCamToggled?.(this.myId, nextEnabled);
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;