@defcomm/meetingsdk 0.2.3 → 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.mjs CHANGED
@@ -349,6 +349,9 @@ var SFUClient = class {
349
349
  this.localStream = null;
350
350
  this.screenStream = null;
351
351
  this.videoSender = null;
352
+ this.audioMuted = false;
353
+ this.videoMuted = false;
354
+ this.activeRecording = null;
352
355
  this.audioSender = null;
353
356
  this.screenSender = null;
354
357
  this.audioTransceiver = null;
@@ -369,7 +372,7 @@ var SFUClient = class {
369
372
  this.joiningWithResume = false;
370
373
  this.reconnectTimer = null;
371
374
  this.reconnectAttempt = 0;
372
- this.maxReconnectAttempts = 8;
375
+ this.maxReconnectAttempts = 6;
373
376
  this.reconnectBaseMs = 750;
374
377
  this.joinWaiter = null;
375
378
  this.transportRecoveryInProgress = false;
@@ -432,6 +435,8 @@ var SFUClient = class {
432
435
  });
433
436
  this.onTrackStateChanged = options.onTrackStateChanged || (() => {
434
437
  });
438
+ this.onParticipantMediaState = options.onParticipantMediaState || (() => {
439
+ });
435
440
  this.onRecordingStarted = options.onRecordingStarted || (() => {
436
441
  });
437
442
  this.onRecordingStopped = options.onRecordingStopped || (() => {
@@ -579,9 +584,13 @@ var SFUClient = class {
579
584
  this.resumeToken = null;
580
585
  }
581
586
  this.room = nextRoom;
587
+ this.audioMuted = Boolean(options.audioMuted);
588
+ this.videoMuted = Boolean(options.videoMuted);
582
589
  this.joinOptions = {
583
590
  displayName: options.displayName,
584
- avatarUrl: options.avatarUrl
591
+ avatarUrl: options.avatarUrl,
592
+ audioMuted: this.audioMuted,
593
+ videoMuted: this.videoMuted
585
594
  };
586
595
  if (options.participantId) this.participantId = options.participantId;
587
596
  if (!this.resumeToken) {
@@ -650,7 +659,9 @@ var SFUClient = class {
650
659
  room_code: this.room,
651
660
  resume_token: this.resumeToken,
652
661
  display_name: options.displayName,
653
- avatar_url: options.avatarUrl
662
+ avatar_url: options.avatarUrl,
663
+ audio_muted: this.audioMuted,
664
+ video_muted: this.videoMuted
654
665
  };
655
666
  if (this.pendingJoinRequestId) {
656
667
  this.send({
@@ -691,8 +702,20 @@ var SFUClient = class {
691
702
  scheduleReconnect() {
692
703
  if (this.reconnectTimer || this.intentionalClose) return;
693
704
  if (this.reconnectAttempt >= this.maxReconnectAttempts) {
705
+ const error = new Error("SFU reconnect attempts exhausted");
706
+ console.error("[SFU] Reconnect attempts exhausted; leaving meeting", error);
707
+ this.intentionalClose = true;
708
+ this.stopHeartbeat();
709
+ this.stopRoomStateSync();
710
+ this.cleanupAll();
694
711
  this.setState("disconnected");
695
- this.failJoin(new Error("SFU reconnect attempts exhausted"));
712
+ this.onError({
713
+ code: "RECONNECT_TIMEOUT",
714
+ message: error.message,
715
+ recoverable: false
716
+ });
717
+ this.onDisconnected();
718
+ this.failJoin(error);
696
719
  return;
697
720
  }
698
721
  this.reconnectAttempt += 1;
@@ -764,7 +787,9 @@ var SFUClient = class {
764
787
  room_code: this.room,
765
788
  resume_token: this.resumeToken,
766
789
  display_name: this.joinOptions.displayName,
767
- avatar_url: this.joinOptions.avatarUrl
790
+ avatar_url: this.joinOptions.avatarUrl,
791
+ audio_muted: this.audioMuted,
792
+ video_muted: this.videoMuted
768
793
  }
769
794
  });
770
795
  }
@@ -839,22 +864,8 @@ var SFUClient = class {
839
864
  if (!track?.track_id) break;
840
865
  this.rememberTrack(track);
841
866
  if (track.participant_id === this.currentParticipantId) {
842
- if (track.source === "audio") {
843
- this.localTrackIds.set("audio", track.track_id);
844
- const muted = !(this.localStream?.getAudioTracks()[0]?.enabled ?? true);
845
- this.send({
846
- type: "track_state",
847
- payload: { track_id: track.track_id, muted }
848
- });
849
- }
850
- if (track.source === "camera") {
851
- this.localTrackIds.set("camera", track.track_id);
852
- const muted = !(this.localStream?.getVideoTracks()[0]?.enabled ?? true);
853
- this.send({
854
- type: "track_state",
855
- payload: { track_id: track.track_id, muted }
856
- });
857
- }
867
+ if (track.source === "audio") this.localTrackIds.set("audio", track.track_id);
868
+ if (track.source === "camera") this.localTrackIds.set("camera", track.track_id);
858
869
  if (track.source === "screen")
859
870
  this.localTrackIds.set("screen", track.track_id);
860
871
  break;
@@ -883,6 +894,27 @@ var SFUClient = class {
883
894
  this.onTrackUnpublished(track);
884
895
  break;
885
896
  }
897
+ case "participant_media_state": {
898
+ const participantId = payload.participant_id;
899
+ if (!participantId) break;
900
+ const participant = this.participants.get(participantId);
901
+ if (participant) {
902
+ participant.audio_muted = Boolean(payload.audio_muted);
903
+ participant.video_muted = Boolean(payload.video_muted);
904
+ this.participants.set(participantId, participant);
905
+ }
906
+ for (const track of this.trackMetadata.values()) {
907
+ if (track.participant_id !== participantId) continue;
908
+ if (track.source === "audio") track.muted = Boolean(payload.audio_muted);
909
+ if (track.source === "camera") track.muted = Boolean(payload.video_muted);
910
+ }
911
+ this.onParticipantMediaState(
912
+ participantId,
913
+ Boolean(payload.audio_muted),
914
+ Boolean(payload.video_muted)
915
+ );
916
+ break;
917
+ }
886
918
  case "track_state_changed": {
887
919
  const trackId = payload.track_id || payload.track?.track_id;
888
920
  if (!trackId) break;
@@ -909,13 +941,15 @@ var SFUClient = class {
909
941
  break;
910
942
  }
911
943
  case "recording_started": {
912
- this.onRecordingStarted({
944
+ this.activeRecording = {
913
945
  recording_id: payload.recording_id,
914
946
  started_at: payload.started_at
915
- });
947
+ };
948
+ this.onRecordingStarted(this.activeRecording);
916
949
  break;
917
950
  }
918
951
  case "recording_stopped": {
952
+ this.activeRecording = null;
919
953
  this.onRecordingStopped({
920
954
  recording_id: payload.recording_id,
921
955
  stopped_at: payload.stopped_at
@@ -974,68 +1008,63 @@ var SFUClient = class {
974
1008
  }
975
1009
  }
976
1010
  async applyRoomState(payload) {
977
- const nextParticipants = /* @__PURE__ */ new Map();
978
- for (const participant of payload.participants || []) {
979
- const id = participant?.participant_id;
980
- if (!id) continue;
981
- nextParticipants.set(id, participant);
1011
+ const authoritative = /* @__PURE__ */ new Map();
1012
+ for (const participant of payload?.participants || []) {
1013
+ if (participant?.participant_id) authoritative.set(participant.participant_id, participant);
982
1014
  }
983
- for (const [id] of this.participants) {
984
- if (id === this.currentParticipantId) continue;
985
- if (!nextParticipants.has(id)) {
1015
+ for (const id of [...this.participants.keys()]) {
1016
+ if (!authoritative.has(id) && id !== this.currentParticipantId) {
986
1017
  this.participants.delete(id);
987
1018
  this.removeRemoteParticipant(id);
988
1019
  this.onParticipantLeft(id);
989
1020
  }
990
1021
  }
991
- for (const [id, participant] of nextParticipants) {
1022
+ for (const [id, participant] of authoritative) {
992
1023
  if (id === this.currentParticipantId) continue;
993
- const existed = this.participants.has(id);
994
- this.participants.set(id, participant);
995
- if (!existed) {
996
- this.onParticipantJoined(participant);
997
- }
998
- const sharing = Boolean(participant.screen_sharing);
999
- const screenTrackId = participant.screen_track_id ? String(participant.screen_track_id) : void 0;
1000
- if (sharing) {
1001
- const track = screenTrackId ? (payload.tracks || []).find(
1002
- (candidate) => String(candidate.track_id) === screenTrackId
1003
- ) : void 0;
1004
- this.onScreenShareStarted(id, track);
1005
- } else if (this.remoteScreenStreams.has(id)) {
1006
- this.remoteScreenStreams.get(id)?.getTracks().forEach((track) => track.stop());
1007
- this.remoteScreenStreams.delete(id);
1008
- this.onScreenShareStopped(id, screenTrackId);
1009
- }
1024
+ const previous = this.participants.get(id);
1025
+ this.participants.set(id, {
1026
+ ...previous,
1027
+ ...participant,
1028
+ audio_muted: Boolean(participant.audio_muted),
1029
+ video_muted: Boolean(participant.video_muted)
1030
+ });
1031
+ if (!previous) this.onParticipantJoined(participant);
1032
+ if (participant.presence) this.onParticipantPresence(id, String(participant.presence));
1010
1033
  }
1011
1034
  const nextTracks = /* @__PURE__ */ new Map();
1012
- for (const track of payload.tracks || []) {
1013
- if (track?.track_id) nextTracks.set(String(track.track_id), track);
1035
+ for (const track of payload?.tracks || []) {
1036
+ if (!track?.track_id) continue;
1037
+ nextTracks.set(track.track_id, track);
1038
+ this.rememberTrack(track);
1014
1039
  }
1015
- for (const [trackId, existing] of this.trackMetadata) {
1016
- if (!nextTracks.has(trackId)) {
1017
- this.trackMetadata.delete(trackId);
1018
- this.trackToParticipant.delete(trackId);
1019
- this.trackToSource.delete(trackId);
1020
- this.subscribedTracks.delete(trackId);
1021
- for (const [source, localId] of this.localTrackIds) {
1022
- if (localId === trackId) this.localTrackIds.delete(source);
1023
- }
1024
- if (existing.participant_id !== this.currentParticipantId) {
1025
- this.onTrackUnpublished(existing);
1026
- }
1040
+ for (const id of [...this.trackMetadata.keys()]) {
1041
+ if (!nextTracks.has(id)) {
1042
+ this.trackMetadata.delete(id);
1043
+ this.subscribedTracks.delete(id);
1044
+ this.trackToParticipant.delete(id);
1045
+ this.trackToSource.delete(id);
1027
1046
  }
1028
1047
  }
1029
- for (const track of nextTracks.values()) {
1030
- this.rememberTrack(track);
1048
+ if (payload?.recording) {
1049
+ const current = {
1050
+ recording_id: payload.recording.recording_id,
1051
+ started_at: payload.recording.started_at
1052
+ };
1053
+ if (!this.activeRecording || this.activeRecording.recording_id !== current.recording_id) {
1054
+ this.activeRecording = current;
1055
+ this.onRecordingStarted(current);
1056
+ }
1057
+ } else if (this.activeRecording) {
1058
+ const previous = this.activeRecording;
1059
+ this.activeRecording = null;
1060
+ this.onRecordingStopped({
1061
+ recording_id: previous.recording_id,
1062
+ stopped_at: (/* @__PURE__ */ new Date()).toISOString()
1063
+ });
1031
1064
  }
1032
1065
  if (this.autoSubscribe) {
1033
- const missing = [...nextTracks.values()].filter(
1034
- (track) => track.participant_id !== this.currentParticipantId && !this.subscribedTracks.has(track.track_id)
1035
- ).map((track) => track.track_id);
1036
- if (missing.length) {
1037
- await this.subscribeMultiple(missing);
1038
- }
1066
+ const missing = [...nextTracks.values()].filter((t) => t.participant_id !== this.currentParticipantId && !this.subscribedTracks.has(t.track_id)).map((t) => t.track_id);
1067
+ if (missing.length) await this.subscribeMultiple(missing);
1039
1068
  }
1040
1069
  this.onRoomState(payload);
1041
1070
  }
@@ -1069,9 +1098,17 @@ var SFUClient = class {
1069
1098
  if (track?.track_id) this.rememberTrack(track);
1070
1099
  }
1071
1100
  if (payload.recording) {
1072
- this.onRecordingStarted({
1101
+ this.activeRecording = {
1073
1102
  recording_id: payload.recording.recording_id,
1074
1103
  started_at: payload.recording.started_at
1104
+ };
1105
+ this.onRecordingStarted(this.activeRecording);
1106
+ } else if (this.activeRecording) {
1107
+ const previous = this.activeRecording;
1108
+ this.activeRecording = null;
1109
+ this.onRecordingStopped({
1110
+ recording_id: previous.recording_id,
1111
+ stopped_at: (/* @__PURE__ */ new Date()).toISOString()
1075
1112
  });
1076
1113
  }
1077
1114
  await this.initializePeerConnections();
@@ -1999,9 +2036,13 @@ var VideoSDKCore = class {
1999
2036
  );
2000
2037
  return;
2001
2038
  }
2002
- throw new Error(
2003
- `SFU client already exists in state "${state}". Disconnect explicitly before creating another connection.`
2004
- );
2039
+ if (state === "disconnected" || state === "idle") {
2040
+ this.sfuClient = null;
2041
+ } else {
2042
+ throw new Error(
2043
+ `SFU client already exists in state "${state}". Disconnect explicitly before creating another connection.`
2044
+ );
2045
+ }
2005
2046
  }
2006
2047
  const client = new SFUClient({
2007
2048
  apiBase: this.apiBase,
@@ -2020,12 +2061,26 @@ var VideoSDKCore = class {
2020
2061
  });
2021
2062
  },
2022
2063
  onRoomState: (roomState) => {
2064
+ for (const participant of roomState?.participants || []) {
2065
+ const pid = participant?.participant_id;
2066
+ if (!pid || pid === this.myId) continue;
2067
+ const current = this.state.getParticipant(pid);
2068
+ if (!current) continue;
2069
+ this.state.updateParticipantMedia(pid, {
2070
+ stream: current.media?.stream ?? null,
2071
+ micEnabled: !Boolean(participant.audio_muted),
2072
+ camEnabled: !Boolean(participant.video_muted),
2073
+ isScreenSharing: Boolean(participant.screen_sharing)
2074
+ });
2075
+ }
2076
+ const presenters = /* @__PURE__ */ new Set();
2023
2077
  for (const participant of roomState?.participants || []) {
2024
2078
  const pid = participant?.participant_id;
2025
2079
  if (!pid || pid === this.myId) continue;
2026
2080
  const current = this.state.getParticipant(pid);
2027
2081
  if (!current) continue;
2028
2082
  if (participant.screen_sharing) {
2083
+ presenters.add(pid);
2029
2084
  this.state.updateParticipantMedia(pid, {
2030
2085
  isScreenSharing: true,
2031
2086
  screenStream: current.media?.screenStream ?? null
@@ -2041,6 +2096,10 @@ var VideoSDKCore = class {
2041
2096
  });
2042
2097
  }
2043
2098
  }
2099
+ const presenter = [...presenters][0] ?? null;
2100
+ if (this.state.presenterId !== presenter) {
2101
+ this.state.setPresenterId(presenter);
2102
+ }
2044
2103
  this.events.onRoomUpdate?.(roomState);
2045
2104
  },
2046
2105
  onDisconnected: () => {
@@ -2114,8 +2173,8 @@ var VideoSDKCore = class {
2114
2173
  name: pname,
2115
2174
  connectionState: "connected",
2116
2175
  media: {
2117
- micEnabled: true,
2118
- camEnabled: true,
2176
+ micEnabled: !Boolean(participant.audio_muted),
2177
+ camEnabled: !Boolean(participant.video_muted),
2119
2178
  isScreenSharing: false,
2120
2179
  stream: null
2121
2180
  }
@@ -2188,6 +2247,18 @@ var VideoSDKCore = class {
2188
2247
  camEnabled: track.kind === "video" ? false : current.media?.camEnabled ?? true
2189
2248
  });
2190
2249
  },
2250
+ onParticipantMediaState: (participantId, audioMuted, videoMuted) => {
2251
+ if (participantId === this.myId) return;
2252
+ const current = this.state.getParticipant(participantId);
2253
+ if (!current) return;
2254
+ this.state.updateParticipantMedia(participantId, {
2255
+ stream: current.media?.stream ?? null,
2256
+ micEnabled: !audioMuted,
2257
+ camEnabled: !videoMuted
2258
+ });
2259
+ this.events.onMuteStateChanged?.(participantId, "audio", audioMuted);
2260
+ this.events.onMuteStateChanged?.(participantId, "video", videoMuted);
2261
+ },
2191
2262
  onTrackStateChanged: (track) => {
2192
2263
  const pid = track.participant_id;
2193
2264
  if (!pid || pid === this.myId) {
@@ -2288,7 +2359,9 @@ var VideoSDKCore = class {
2288
2359
  client.setLocalStream(this.localStream);
2289
2360
  await client.getToken(roomId, {
2290
2361
  displayName: name,
2291
- participantId: this.myId
2362
+ participantId: this.myId,
2363
+ audioMuted: this.state.localParticipant?.media?.micEnabled,
2364
+ videoMuted: this.state.localParticipant?.media?.camEnabled
2292
2365
  });
2293
2366
  }
2294
2367
  // SFU STATE