@defcomm/meetingsdk 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -272,6 +272,7 @@ interface SFUClientOptions {
272
272
  onScreenShareStarted?: (participantId: string, track?: TrackInfo) => void;
273
273
  onScreenShareStopped?: (participantId: string, trackId?: string) => void;
274
274
  onTrackStateChanged?: (track: TrackInfo) => void;
275
+ onParticipantMediaState?: (participantId: string, audioMuted: boolean, videoMuted: boolean) => void;
275
276
  onRecordingStarted?: (recording: {
276
277
  recording_id: string;
277
278
  started_at: string;
@@ -280,6 +281,8 @@ interface SFUClientOptions {
280
281
  recording_id: string;
281
282
  stopped_at: string;
282
283
  }) => void;
284
+ /** Authoritative room snapshot received after join/reconnect/resync. */
285
+ onRoomState?: (state: any) => void;
283
286
  onError?: (error: any) => void;
284
287
  autoSubscribe?: boolean;
285
288
  }
@@ -293,6 +296,9 @@ declare class SFUClient {
293
296
  private localStream;
294
297
  private screenStream;
295
298
  private videoSender;
299
+ private audioMuted;
300
+ private videoMuted;
301
+ private activeRecording;
296
302
  private audioSender;
297
303
  private screenSender;
298
304
  private audioTransceiver;
@@ -333,6 +339,8 @@ declare class SFUClient {
333
339
  private trackToSource;
334
340
  private localTrackIds;
335
341
  private subscriberStatsTimer;
342
+ private roomSyncTimer;
343
+ private roomSyncInFlight;
336
344
  private onStateChange;
337
345
  private onParticipantJoined;
338
346
  private onParticipantLeft;
@@ -350,14 +358,19 @@ declare class SFUClient {
350
358
  private onScreenShareStarted;
351
359
  private onScreenShareStopped;
352
360
  private onTrackStateChanged;
361
+ private onParticipantMediaState;
353
362
  private onRecordingStarted;
354
363
  private onRecordingStopped;
364
+ private onRoomState;
355
365
  private onError;
356
366
  private autoSubscribe;
357
367
  constructor(options?: SFUClientOptions);
358
368
  private heartbeatTimer;
359
369
  private startHeartbeat;
360
370
  private stopHeartbeat;
371
+ private startRoomStateSync;
372
+ private stopRoomStateSync;
373
+ private syncRoomState;
361
374
  private createId;
362
375
  private resumeStorageKey;
363
376
  private loadResumeToken;
@@ -384,6 +397,8 @@ declare class SFUClient {
384
397
  displayName?: string;
385
398
  avatarUrl?: string;
386
399
  participantId?: string;
400
+ audioMuted?: boolean;
401
+ videoMuted?: boolean;
387
402
  }): Promise<void>;
388
403
  private refreshAccessToken;
389
404
  private connect;
@@ -392,6 +407,7 @@ declare class SFUClient {
392
407
  private failJoin;
393
408
  private resolveJoin;
394
409
  private handleServerMessage;
410
+ private applyRoomState;
395
411
  private rememberTrack;
396
412
  private handleJoined;
397
413
  private initializePeerConnections;
@@ -469,6 +485,7 @@ type MeetingContextValue = {
469
485
  onEntryRequested: (cb: (req: any) => void) => () => void;
470
486
  onEntryResponded: (cb: (payload: any, decision?: any) => void) => () => void;
471
487
  onMeetingLeft: (cb: () => void) => () => void;
488
+ onRoomUpdate: (cb: (data: any) => void) => () => void;
472
489
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
473
490
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
474
491
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
@@ -527,6 +544,7 @@ declare const useMeeting: (handlers?: {
527
544
  onEntryRequested: (cb: (req: any) => void) => () => void;
528
545
  onEntryResponded: (cb: (payload: any, decision?: any) => void) => () => void;
529
546
  onMeetingLeft: (cb: () => void) => () => void;
547
+ onRoomUpdate: (cb: (data: any) => void) => () => void;
530
548
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
531
549
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
532
550
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
package/dist/index.d.ts CHANGED
@@ -272,6 +272,7 @@ interface SFUClientOptions {
272
272
  onScreenShareStarted?: (participantId: string, track?: TrackInfo) => void;
273
273
  onScreenShareStopped?: (participantId: string, trackId?: string) => void;
274
274
  onTrackStateChanged?: (track: TrackInfo) => void;
275
+ onParticipantMediaState?: (participantId: string, audioMuted: boolean, videoMuted: boolean) => void;
275
276
  onRecordingStarted?: (recording: {
276
277
  recording_id: string;
277
278
  started_at: string;
@@ -280,6 +281,8 @@ interface SFUClientOptions {
280
281
  recording_id: string;
281
282
  stopped_at: string;
282
283
  }) => void;
284
+ /** Authoritative room snapshot received after join/reconnect/resync. */
285
+ onRoomState?: (state: any) => void;
283
286
  onError?: (error: any) => void;
284
287
  autoSubscribe?: boolean;
285
288
  }
@@ -293,6 +296,9 @@ declare class SFUClient {
293
296
  private localStream;
294
297
  private screenStream;
295
298
  private videoSender;
299
+ private audioMuted;
300
+ private videoMuted;
301
+ private activeRecording;
296
302
  private audioSender;
297
303
  private screenSender;
298
304
  private audioTransceiver;
@@ -333,6 +339,8 @@ declare class SFUClient {
333
339
  private trackToSource;
334
340
  private localTrackIds;
335
341
  private subscriberStatsTimer;
342
+ private roomSyncTimer;
343
+ private roomSyncInFlight;
336
344
  private onStateChange;
337
345
  private onParticipantJoined;
338
346
  private onParticipantLeft;
@@ -350,14 +358,19 @@ declare class SFUClient {
350
358
  private onScreenShareStarted;
351
359
  private onScreenShareStopped;
352
360
  private onTrackStateChanged;
361
+ private onParticipantMediaState;
353
362
  private onRecordingStarted;
354
363
  private onRecordingStopped;
364
+ private onRoomState;
355
365
  private onError;
356
366
  private autoSubscribe;
357
367
  constructor(options?: SFUClientOptions);
358
368
  private heartbeatTimer;
359
369
  private startHeartbeat;
360
370
  private stopHeartbeat;
371
+ private startRoomStateSync;
372
+ private stopRoomStateSync;
373
+ private syncRoomState;
361
374
  private createId;
362
375
  private resumeStorageKey;
363
376
  private loadResumeToken;
@@ -384,6 +397,8 @@ declare class SFUClient {
384
397
  displayName?: string;
385
398
  avatarUrl?: string;
386
399
  participantId?: string;
400
+ audioMuted?: boolean;
401
+ videoMuted?: boolean;
387
402
  }): Promise<void>;
388
403
  private refreshAccessToken;
389
404
  private connect;
@@ -392,6 +407,7 @@ declare class SFUClient {
392
407
  private failJoin;
393
408
  private resolveJoin;
394
409
  private handleServerMessage;
410
+ private applyRoomState;
395
411
  private rememberTrack;
396
412
  private handleJoined;
397
413
  private initializePeerConnections;
@@ -469,6 +485,7 @@ type MeetingContextValue = {
469
485
  onEntryRequested: (cb: (req: any) => void) => () => void;
470
486
  onEntryResponded: (cb: (payload: any, decision?: any) => void) => () => void;
471
487
  onMeetingLeft: (cb: () => void) => () => void;
488
+ onRoomUpdate: (cb: (data: any) => void) => () => void;
472
489
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
473
490
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
474
491
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
@@ -527,6 +544,7 @@ declare const useMeeting: (handlers?: {
527
544
  onEntryRequested: (cb: (req: any) => void) => () => void;
528
545
  onEntryResponded: (cb: (payload: any, decision?: any) => void) => () => void;
529
546
  onMeetingLeft: (cb: () => void) => () => void;
547
+ onRoomUpdate: (cb: (data: any) => void) => () => void;
530
548
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
531
549
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
532
550
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
package/dist/index.js CHANGED
@@ -385,6 +385,9 @@ var SFUClient = class {
385
385
  this.localStream = null;
386
386
  this.screenStream = null;
387
387
  this.videoSender = null;
388
+ this.audioMuted = false;
389
+ this.videoMuted = false;
390
+ this.activeRecording = null;
388
391
  this.audioSender = null;
389
392
  this.screenSender = null;
390
393
  this.audioTransceiver = null;
@@ -405,7 +408,7 @@ var SFUClient = class {
405
408
  this.joiningWithResume = false;
406
409
  this.reconnectTimer = null;
407
410
  this.reconnectAttempt = 0;
408
- this.maxReconnectAttempts = 8;
411
+ this.maxReconnectAttempts = 6;
409
412
  this.reconnectBaseMs = 750;
410
413
  this.joinWaiter = null;
411
414
  this.transportRecoveryInProgress = false;
@@ -428,6 +431,8 @@ var SFUClient = class {
428
431
  this.trackToSource = /* @__PURE__ */ new Map();
429
432
  this.localTrackIds = /* @__PURE__ */ new Map();
430
433
  this.subscriberStatsTimer = null;
434
+ this.roomSyncTimer = null;
435
+ this.roomSyncInFlight = false;
431
436
  this.heartbeatTimer = null;
432
437
  this.apiBase = options.apiBase || "https://api.example.com/api";
433
438
  this.wsBase = options.wsBase || "wss://api.example.com/ws";
@@ -466,10 +471,14 @@ var SFUClient = class {
466
471
  });
467
472
  this.onTrackStateChanged = options.onTrackStateChanged || (() => {
468
473
  });
474
+ this.onParticipantMediaState = options.onParticipantMediaState || (() => {
475
+ });
469
476
  this.onRecordingStarted = options.onRecordingStarted || (() => {
470
477
  });
471
478
  this.onRecordingStopped = options.onRecordingStopped || (() => {
472
479
  });
480
+ this.onRoomState = options.onRoomState || (() => {
481
+ });
473
482
  this.onError = options.onError || console.error;
474
483
  this.autoSubscribe = options.autoSubscribe !== false;
475
484
  }
@@ -491,6 +500,29 @@ var SFUClient = class {
491
500
  this.heartbeatTimer = null;
492
501
  }
493
502
  }
503
+ startRoomStateSync() {
504
+ this.stopRoomStateSync();
505
+ this.roomSyncTimer = setInterval(() => {
506
+ if (this.state !== "connected") return;
507
+ void this.syncRoomState();
508
+ }, 5e3);
509
+ }
510
+ stopRoomStateSync() {
511
+ if (this.roomSyncTimer) {
512
+ clearInterval(this.roomSyncTimer);
513
+ this.roomSyncTimer = null;
514
+ }
515
+ this.roomSyncInFlight = false;
516
+ }
517
+ async syncRoomState() {
518
+ if (this.roomSyncInFlight || !this.isSocketOpen()) return;
519
+ this.roomSyncInFlight = true;
520
+ try {
521
+ this.send({ type: "sync_room_state" });
522
+ } finally {
523
+ this.roomSyncInFlight = false;
524
+ }
525
+ }
494
526
  createId() {
495
527
  return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `id_${Math.random().toString(36).slice(2)}_${Date.now()}`;
496
528
  }
@@ -588,9 +620,13 @@ var SFUClient = class {
588
620
  this.resumeToken = null;
589
621
  }
590
622
  this.room = nextRoom;
623
+ this.audioMuted = Boolean(options.audioMuted);
624
+ this.videoMuted = Boolean(options.videoMuted);
591
625
  this.joinOptions = {
592
626
  displayName: options.displayName,
593
- avatarUrl: options.avatarUrl
627
+ avatarUrl: options.avatarUrl,
628
+ audioMuted: this.audioMuted,
629
+ videoMuted: this.videoMuted
594
630
  };
595
631
  if (options.participantId) this.participantId = options.participantId;
596
632
  if (!this.resumeToken) {
@@ -659,7 +695,9 @@ var SFUClient = class {
659
695
  room_code: this.room,
660
696
  resume_token: this.resumeToken,
661
697
  display_name: options.displayName,
662
- avatar_url: options.avatarUrl
698
+ avatar_url: options.avatarUrl,
699
+ audio_muted: this.audioMuted,
700
+ video_muted: this.videoMuted
663
701
  };
664
702
  if (this.pendingJoinRequestId) {
665
703
  this.send({
@@ -700,8 +738,20 @@ var SFUClient = class {
700
738
  scheduleReconnect() {
701
739
  if (this.reconnectTimer || this.intentionalClose) return;
702
740
  if (this.reconnectAttempt >= this.maxReconnectAttempts) {
741
+ const error = new Error("SFU reconnect attempts exhausted");
742
+ console.error("[SFU] Reconnect attempts exhausted; leaving meeting", error);
743
+ this.intentionalClose = true;
744
+ this.stopHeartbeat();
745
+ this.stopRoomStateSync();
746
+ this.cleanupAll();
703
747
  this.setState("disconnected");
704
- this.failJoin(new Error("SFU reconnect attempts exhausted"));
748
+ this.onError({
749
+ code: "RECONNECT_TIMEOUT",
750
+ message: error.message,
751
+ recoverable: false
752
+ });
753
+ this.onDisconnected();
754
+ this.failJoin(error);
705
755
  return;
706
756
  }
707
757
  this.reconnectAttempt += 1;
@@ -773,7 +823,9 @@ var SFUClient = class {
773
823
  room_code: this.room,
774
824
  resume_token: this.resumeToken,
775
825
  display_name: this.joinOptions.displayName,
776
- avatar_url: this.joinOptions.avatarUrl
826
+ avatar_url: this.joinOptions.avatarUrl,
827
+ audio_muted: this.audioMuted,
828
+ video_muted: this.videoMuted
777
829
  }
778
830
  });
779
831
  }
@@ -848,22 +900,8 @@ var SFUClient = class {
848
900
  if (!track?.track_id) break;
849
901
  this.rememberTrack(track);
850
902
  if (track.participant_id === this.currentParticipantId) {
851
- if (track.source === "audio") {
852
- this.localTrackIds.set("audio", track.track_id);
853
- const muted = !(this.localStream?.getAudioTracks()[0]?.enabled ?? true);
854
- this.send({
855
- type: "track_state",
856
- payload: { track_id: track.track_id, muted }
857
- });
858
- }
859
- if (track.source === "camera") {
860
- this.localTrackIds.set("camera", track.track_id);
861
- const muted = !(this.localStream?.getVideoTracks()[0]?.enabled ?? true);
862
- this.send({
863
- type: "track_state",
864
- payload: { track_id: track.track_id, muted }
865
- });
866
- }
903
+ if (track.source === "audio") this.localTrackIds.set("audio", track.track_id);
904
+ if (track.source === "camera") this.localTrackIds.set("camera", track.track_id);
867
905
  if (track.source === "screen")
868
906
  this.localTrackIds.set("screen", track.track_id);
869
907
  break;
@@ -892,6 +930,27 @@ var SFUClient = class {
892
930
  this.onTrackUnpublished(track);
893
931
  break;
894
932
  }
933
+ case "participant_media_state": {
934
+ const participantId = payload.participant_id;
935
+ if (!participantId) break;
936
+ const participant = this.participants.get(participantId);
937
+ if (participant) {
938
+ participant.audio_muted = Boolean(payload.audio_muted);
939
+ participant.video_muted = Boolean(payload.video_muted);
940
+ this.participants.set(participantId, participant);
941
+ }
942
+ for (const track of this.trackMetadata.values()) {
943
+ if (track.participant_id !== participantId) continue;
944
+ if (track.source === "audio") track.muted = Boolean(payload.audio_muted);
945
+ if (track.source === "camera") track.muted = Boolean(payload.video_muted);
946
+ }
947
+ this.onParticipantMediaState(
948
+ participantId,
949
+ Boolean(payload.audio_muted),
950
+ Boolean(payload.video_muted)
951
+ );
952
+ break;
953
+ }
895
954
  case "track_state_changed": {
896
955
  const trackId = payload.track_id || payload.track?.track_id;
897
956
  if (!trackId) break;
@@ -913,14 +972,20 @@ var SFUClient = class {
913
972
  case "subscription_updated": {
914
973
  break;
915
974
  }
975
+ case "room_state": {
976
+ await this.applyRoomState(payload);
977
+ break;
978
+ }
916
979
  case "recording_started": {
917
- this.onRecordingStarted({
980
+ this.activeRecording = {
918
981
  recording_id: payload.recording_id,
919
982
  started_at: payload.started_at
920
- });
983
+ };
984
+ this.onRecordingStarted(this.activeRecording);
921
985
  break;
922
986
  }
923
987
  case "recording_stopped": {
988
+ this.activeRecording = null;
924
989
  this.onRecordingStopped({
925
990
  recording_id: payload.recording_id,
926
991
  stopped_at: payload.stopped_at
@@ -978,6 +1043,67 @@ var SFUClient = class {
978
1043
  console.debug("[SFU] Ignoring unknown server message", message);
979
1044
  }
980
1045
  }
1046
+ async applyRoomState(payload) {
1047
+ const authoritative = /* @__PURE__ */ new Map();
1048
+ for (const participant of payload?.participants || []) {
1049
+ if (participant?.participant_id) authoritative.set(participant.participant_id, participant);
1050
+ }
1051
+ for (const id of [...this.participants.keys()]) {
1052
+ if (!authoritative.has(id) && id !== this.currentParticipantId) {
1053
+ this.participants.delete(id);
1054
+ this.removeRemoteParticipant(id);
1055
+ this.onParticipantLeft(id);
1056
+ }
1057
+ }
1058
+ for (const [id, participant] of authoritative) {
1059
+ if (id === this.currentParticipantId) continue;
1060
+ const previous = this.participants.get(id);
1061
+ this.participants.set(id, {
1062
+ ...previous,
1063
+ ...participant,
1064
+ audio_muted: Boolean(participant.audio_muted),
1065
+ video_muted: Boolean(participant.video_muted)
1066
+ });
1067
+ if (!previous) this.onParticipantJoined(participant);
1068
+ if (participant.presence) this.onParticipantPresence(id, String(participant.presence));
1069
+ }
1070
+ const nextTracks = /* @__PURE__ */ new Map();
1071
+ for (const track of payload?.tracks || []) {
1072
+ if (!track?.track_id) continue;
1073
+ nextTracks.set(track.track_id, track);
1074
+ this.rememberTrack(track);
1075
+ }
1076
+ for (const id of [...this.trackMetadata.keys()]) {
1077
+ if (!nextTracks.has(id)) {
1078
+ this.trackMetadata.delete(id);
1079
+ this.subscribedTracks.delete(id);
1080
+ this.trackToParticipant.delete(id);
1081
+ this.trackToSource.delete(id);
1082
+ }
1083
+ }
1084
+ if (payload?.recording) {
1085
+ const current = {
1086
+ recording_id: payload.recording.recording_id,
1087
+ started_at: payload.recording.started_at
1088
+ };
1089
+ if (!this.activeRecording || this.activeRecording.recording_id !== current.recording_id) {
1090
+ this.activeRecording = current;
1091
+ this.onRecordingStarted(current);
1092
+ }
1093
+ } else if (this.activeRecording) {
1094
+ const previous = this.activeRecording;
1095
+ this.activeRecording = null;
1096
+ this.onRecordingStopped({
1097
+ recording_id: previous.recording_id,
1098
+ stopped_at: (/* @__PURE__ */ new Date()).toISOString()
1099
+ });
1100
+ }
1101
+ if (this.autoSubscribe) {
1102
+ const missing = [...nextTracks.values()].filter((t) => t.participant_id !== this.currentParticipantId && !this.subscribedTracks.has(t.track_id)).map((t) => t.track_id);
1103
+ if (missing.length) await this.subscribeMultiple(missing);
1104
+ }
1105
+ this.onRoomState(payload);
1106
+ }
981
1107
  rememberTrack(track) {
982
1108
  this.trackMetadata.set(track.track_id, track);
983
1109
  this.trackToParticipant.set(track.track_id, track.participant_id);
@@ -1007,8 +1133,23 @@ var SFUClient = class {
1007
1133
  for (const track of payload.tracks || []) {
1008
1134
  if (track?.track_id) this.rememberTrack(track);
1009
1135
  }
1136
+ if (payload.recording) {
1137
+ this.activeRecording = {
1138
+ recording_id: payload.recording.recording_id,
1139
+ started_at: payload.recording.started_at
1140
+ };
1141
+ this.onRecordingStarted(this.activeRecording);
1142
+ } else if (this.activeRecording) {
1143
+ const previous = this.activeRecording;
1144
+ this.activeRecording = null;
1145
+ this.onRecordingStopped({
1146
+ recording_id: previous.recording_id,
1147
+ stopped_at: (/* @__PURE__ */ new Date()).toISOString()
1148
+ });
1149
+ }
1010
1150
  await this.initializePeerConnections();
1011
1151
  this.setState("connected");
1152
+ this.startRoomStateSync();
1012
1153
  this.onConnected(payload, resumed);
1013
1154
  for (const participant of this.participants.values()) {
1014
1155
  if (participant.participant_id !== this.currentParticipantId) {
@@ -1563,6 +1704,7 @@ var SFUClient = class {
1563
1704
  }
1564
1705
  async leave() {
1565
1706
  this.stopHeartbeat();
1707
+ this.stopRoomStateSync();
1566
1708
  this.intentionalClose = true;
1567
1709
  this.setState("leaving");
1568
1710
  if (this.reconnectTimer) {
@@ -1930,9 +2072,13 @@ var VideoSDKCore = class {
1930
2072
  );
1931
2073
  return;
1932
2074
  }
1933
- throw new Error(
1934
- `SFU client already exists in state "${state}". Disconnect explicitly before creating another connection.`
1935
- );
2075
+ if (state === "disconnected" || state === "idle") {
2076
+ this.sfuClient = null;
2077
+ } else {
2078
+ throw new Error(
2079
+ `SFU client already exists in state "${state}". Disconnect explicitly before creating another connection.`
2080
+ );
2081
+ }
1936
2082
  }
1937
2083
  const client = new SFUClient({
1938
2084
  apiBase: this.apiBase,
@@ -1950,6 +2096,48 @@ var VideoSDKCore = class {
1950
2096
  name: this.participantName
1951
2097
  });
1952
2098
  },
2099
+ onRoomState: (roomState) => {
2100
+ for (const participant of roomState?.participants || []) {
2101
+ const pid = participant?.participant_id;
2102
+ if (!pid || pid === this.myId) continue;
2103
+ const current = this.state.getParticipant(pid);
2104
+ if (!current) continue;
2105
+ this.state.updateParticipantMedia(pid, {
2106
+ stream: current.media?.stream ?? null,
2107
+ micEnabled: !Boolean(participant.audio_muted),
2108
+ camEnabled: !Boolean(participant.video_muted),
2109
+ isScreenSharing: Boolean(participant.screen_sharing)
2110
+ });
2111
+ }
2112
+ const presenters = /* @__PURE__ */ new Set();
2113
+ for (const participant of roomState?.participants || []) {
2114
+ const pid = participant?.participant_id;
2115
+ if (!pid || pid === this.myId) continue;
2116
+ const current = this.state.getParticipant(pid);
2117
+ if (!current) continue;
2118
+ if (participant.screen_sharing) {
2119
+ presenters.add(pid);
2120
+ this.state.updateParticipantMedia(pid, {
2121
+ isScreenSharing: true,
2122
+ screenStream: current.media?.screenStream ?? null
2123
+ });
2124
+ } else if (current.media?.isScreenSharing) {
2125
+ const screen = this.remoteScreenStreams.get(pid);
2126
+ screen?.getTracks().forEach((track) => track.stop());
2127
+ this.remoteScreenStreams.delete(pid);
2128
+ this.state.updateParticipantMedia(pid, {
2129
+ isScreenSharing: false,
2130
+ screenStream: null,
2131
+ screenTrack: void 0
2132
+ });
2133
+ }
2134
+ }
2135
+ const presenter = [...presenters][0] ?? null;
2136
+ if (this.state.presenterId !== presenter) {
2137
+ this.state.setPresenterId(presenter);
2138
+ }
2139
+ this.events.onRoomUpdate?.(roomState);
2140
+ },
1953
2141
  onDisconnected: () => {
1954
2142
  console.log("[SFU] Signaling WebSocket disconnected; reconnecting");
1955
2143
  },
@@ -2021,8 +2209,8 @@ var VideoSDKCore = class {
2021
2209
  name: pname,
2022
2210
  connectionState: "connected",
2023
2211
  media: {
2024
- micEnabled: true,
2025
- camEnabled: true,
2212
+ micEnabled: !Boolean(participant.audio_muted),
2213
+ camEnabled: !Boolean(participant.video_muted),
2026
2214
  isScreenSharing: false,
2027
2215
  stream: null
2028
2216
  }
@@ -2095,6 +2283,18 @@ var VideoSDKCore = class {
2095
2283
  camEnabled: track.kind === "video" ? false : current.media?.camEnabled ?? true
2096
2284
  });
2097
2285
  },
2286
+ onParticipantMediaState: (participantId, audioMuted, videoMuted) => {
2287
+ if (participantId === this.myId) return;
2288
+ const current = this.state.getParticipant(participantId);
2289
+ if (!current) return;
2290
+ this.state.updateParticipantMedia(participantId, {
2291
+ stream: current.media?.stream ?? null,
2292
+ micEnabled: !audioMuted,
2293
+ camEnabled: !videoMuted
2294
+ });
2295
+ this.events.onMuteStateChanged?.(participantId, "audio", audioMuted);
2296
+ this.events.onMuteStateChanged?.(participantId, "video", videoMuted);
2297
+ },
2098
2298
  onTrackStateChanged: (track) => {
2099
2299
  const pid = track.participant_id;
2100
2300
  if (!pid || pid === this.myId) {
@@ -2195,7 +2395,9 @@ var VideoSDKCore = class {
2195
2395
  client.setLocalStream(this.localStream);
2196
2396
  await client.getToken(roomId, {
2197
2397
  displayName: name,
2198
- participantId: this.myId
2398
+ participantId: this.myId,
2399
+ audioMuted: this.state.localParticipant?.media?.micEnabled,
2400
+ videoMuted: this.state.localParticipant?.media?.camEnabled
2199
2401
  });
2200
2402
  }
2201
2403
  // SFU STATE
@@ -2509,6 +2711,7 @@ var MeetingProvider = ({
2509
2711
  /* @__PURE__ */ new Set()
2510
2712
  );
2511
2713
  const meetingLeftListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2714
+ const roomUpdateListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2512
2715
  const trackStateListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2513
2716
  const recordingStartedListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2514
2717
  const recordingStoppedListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
@@ -2533,6 +2736,9 @@ var MeetingProvider = ({
2533
2736
  onMeetingLeft: () => {
2534
2737
  meetingLeftListeners.current.forEach((fn) => fn());
2535
2738
  },
2739
+ onRoomUpdate: (data) => {
2740
+ roomUpdateListeners.current.forEach((fn) => fn(data));
2741
+ },
2536
2742
  onTrackStateChanged: (track) => {
2537
2743
  trackStateListeners.current.forEach((fn) => fn(track));
2538
2744
  },
@@ -2631,6 +2837,10 @@ var MeetingProvider = ({
2631
2837
  meetingLeftListeners.current.delete(cb);
2632
2838
  };
2633
2839
  },
2840
+ onRoomUpdate: (cb) => {
2841
+ roomUpdateListeners.current.add(cb);
2842
+ return () => roomUpdateListeners.current.delete(cb);
2843
+ },
2634
2844
  onTrackStateChanged: (cb) => {
2635
2845
  trackStateListeners.current.add(cb);
2636
2846
  return () => trackStateListeners.current.delete(cb);