@defcomm/meetingsdk 0.6.0 → 0.6.2

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
@@ -197,7 +197,6 @@ var MeetingState = class {
197
197
  this.presenterId = null;
198
198
  this.activeSpeakerIds = /* @__PURE__ */ new Set();
199
199
  this.recording = null;
200
- this.liveTranscripts = [];
201
200
  this.networkQuality = null;
202
201
  this.listeners = /* @__PURE__ */ new Map();
203
202
  }
@@ -236,17 +235,6 @@ var MeetingState = class {
236
235
  this.recording = null;
237
236
  this.notify("recording");
238
237
  }
239
- addLiveTranscript(transcript) {
240
- this.liveTranscripts = [...this.liveTranscripts.slice(-499), transcript];
241
- this.notify("transcript");
242
- }
243
- getLiveTranscripts() {
244
- return this.liveTranscripts;
245
- }
246
- clearLiveTranscripts() {
247
- this.liveTranscripts = [];
248
- this.notify("transcript");
249
- }
250
238
  setNetworkQuality(quality) {
251
239
  this.networkQuality = quality;
252
240
  this.notify("networkQuality");
@@ -371,13 +359,11 @@ var MeetingState = class {
371
359
  this.chatMessages.clear();
372
360
  this.presenterId = null;
373
361
  this.activeSpeakerIds = /* @__PURE__ */ new Set();
374
- this.liveTranscripts = [];
375
362
  this.networkQuality = null;
376
363
  this.notify("participants");
377
364
  this.notify("chat");
378
365
  this.notify("presenter");
379
366
  this.notify("activeSpeakers");
380
- this.notify("transcript");
381
367
  this.notify("networkQuality");
382
368
  }
383
369
  };
@@ -440,7 +426,6 @@ var SFUClient = class {
440
426
  this.trackToSource = /* @__PURE__ */ new Map();
441
427
  this.localTrackIds = /* @__PURE__ */ new Map();
442
428
  this.subscriberStatsTimer = null;
443
- this.liveTranscriptionSeen = false;
444
429
  this.networkProfile = null;
445
430
  this.heartbeatTimer = null;
446
431
  this.apiBase = options.apiBase || "https://api.example.com/api";
@@ -498,8 +483,6 @@ var SFUClient = class {
498
483
  });
499
484
  this.onRoomUpdate = options.onRoomUpdate || (() => {
500
485
  });
501
- this.onLiveTranscript = options.onLiveTranscript || (() => {
502
- });
503
486
  this.onNetworkQuality = options.onNetworkQuality || (() => {
504
487
  });
505
488
  this.onParticipantMediaState = options.onParticipantMediaState || (() => {
@@ -1096,24 +1079,6 @@ var SFUClient = class {
1096
1079
  if (reaction?.id && reaction?.emoji) this.onReaction(reaction);
1097
1080
  break;
1098
1081
  }
1099
- case "live_transcript": {
1100
- const transcript = {
1101
- transcript_id: String(payload.transcript_id),
1102
- participant_id: String(payload.participant_id),
1103
- speaker_name: payload.speaker_name ?? null,
1104
- sequence: Number(payload.sequence ?? 0),
1105
- text: String(payload.text ?? ""),
1106
- offset_ms: payload.offset_ms == null ? null : Number(payload.offset_ms),
1107
- start_ms: payload.start_ms == null ? null : Number(payload.start_ms),
1108
- end_ms: payload.end_ms == null ? null : Number(payload.end_ms),
1109
- timestamp: String(payload.timestamp ?? (/* @__PURE__ */ new Date()).toISOString())
1110
- };
1111
- if (transcript.text.trim()) {
1112
- this.liveTranscriptionSeen = true;
1113
- this.onLiveTranscript(transcript);
1114
- }
1115
- break;
1116
- }
1117
1082
  case "network_quality": {
1118
1083
  const raw = payload.quality || payload;
1119
1084
  const quality = {
@@ -1571,6 +1536,78 @@ var SFUClient = class {
1571
1536
  this.subscriptionRunning = false;
1572
1537
  }
1573
1538
  }
1539
+ /**
1540
+ * Enumerate media inputs and outputs exposed by the browser/OS.
1541
+ * Labels may be empty until the user has granted media permission.
1542
+ */
1543
+ async getMediaDevices() {
1544
+ if (!navigator.mediaDevices?.enumerateDevices) {
1545
+ throw new Error("Media device enumeration is not supported on this device");
1546
+ }
1547
+ return navigator.mediaDevices.enumerateDevices();
1548
+ }
1549
+ /**
1550
+ * Replace the published camera without leaving the meeting.
1551
+ * The browser/OS must expose the target as a videoinput device.
1552
+ */
1553
+ async selectCamera(deviceId) {
1554
+ if (!deviceId) throw new Error("Camera deviceId is required");
1555
+ if (!navigator.mediaDevices?.getUserMedia) {
1556
+ throw new Error("Camera access is not supported on this device");
1557
+ }
1558
+ const stream = await navigator.mediaDevices.getUserMedia({
1559
+ video: { deviceId: { exact: deviceId } },
1560
+ audio: false
1561
+ });
1562
+ const nextTrack = stream.getVideoTracks()[0];
1563
+ if (!nextTrack) {
1564
+ stream.getTracks().forEach((track) => track.stop());
1565
+ throw new Error("Selected camera returned no video track");
1566
+ }
1567
+ const currentTrack = this.localStream?.getVideoTracks()[0] ?? null;
1568
+ nextTrack.enabled = currentTrack?.enabled ?? true;
1569
+ if (!this.localStream) this.localStream = new MediaStream();
1570
+ if (currentTrack) this.localStream.removeTrack(currentTrack);
1571
+ this.localStream.addTrack(nextTrack);
1572
+ if (this.videoSender) {
1573
+ await this.videoSender.replaceTrack(nextTrack);
1574
+ }
1575
+ currentTrack?.stop();
1576
+ return nextTrack;
1577
+ }
1578
+ /**
1579
+ * Replace the published microphone without leaving the meeting.
1580
+ */
1581
+ async selectMicrophone(deviceId) {
1582
+ if (!deviceId) throw new Error("Microphone deviceId is required");
1583
+ if (!navigator.mediaDevices?.getUserMedia) {
1584
+ throw new Error("Microphone access is not supported on this device");
1585
+ }
1586
+ const stream = await navigator.mediaDevices.getUserMedia({
1587
+ video: false,
1588
+ audio: {
1589
+ deviceId: { exact: deviceId },
1590
+ echoCancellation: true,
1591
+ noiseSuppression: true,
1592
+ autoGainControl: true
1593
+ }
1594
+ });
1595
+ const nextTrack = stream.getAudioTracks()[0];
1596
+ if (!nextTrack) {
1597
+ stream.getTracks().forEach((track) => track.stop());
1598
+ throw new Error("Selected microphone returned no audio track");
1599
+ }
1600
+ const currentTrack = this.localStream?.getAudioTracks()[0] ?? null;
1601
+ nextTrack.enabled = currentTrack?.enabled ?? true;
1602
+ if (!this.localStream) this.localStream = new MediaStream();
1603
+ if (currentTrack) this.localStream.removeTrack(currentTrack);
1604
+ this.localStream.addTrack(nextTrack);
1605
+ if (this.audioSender) {
1606
+ await this.audioSender.replaceTrack(nextTrack);
1607
+ }
1608
+ currentTrack?.stop();
1609
+ return nextTrack;
1610
+ }
1574
1611
  async publish({ audio = true, video = true } = {}) {
1575
1612
  if (!this.publisher)
1576
1613
  throw new Error("Publisher PeerConnection is not initialized");
@@ -1792,27 +1829,6 @@ var SFUClient = class {
1792
1829
  }
1793
1830
  });
1794
1831
  }
1795
- /**
1796
- * Transcription is server-authoritative. The SFU reads the already-published
1797
- * microphone RTP stream, sends audio to the configured transcription service,
1798
- * persists final segments, and broadcasts LiveTranscript events.
1799
- *
1800
- * These methods remain as compatibility shims for applications that used the
1801
- * previous browser MediaRecorder-based API. They intentionally do not capture
1802
- * or upload microphone audio from the browser.
1803
- */
1804
- async startLiveTranscription() {
1805
- if (!this.room) {
1806
- throw new Error("Cannot start live transcription without a room");
1807
- }
1808
- console.info("[Transcript] server-authoritative live transcription is managed by the SFU");
1809
- }
1810
- stopLiveTranscription() {
1811
- console.info("[Transcript] server-authoritative live transcription remains managed by the SFU");
1812
- }
1813
- isLiveTranscriptionActive() {
1814
- return this.liveTranscriptionSeen;
1815
- }
1816
1832
  async getTranscript(roomCode, sessionId) {
1817
1833
  if (!this.authToken) throw new Error("Authentication token is required");
1818
1834
  const query = sessionId ? `?session_id=${encodeURIComponent(sessionId)}` : "";
@@ -2009,7 +2025,6 @@ var SFUClient = class {
2009
2025
  }
2010
2026
  }
2011
2027
  async leave() {
2012
- this.stopLiveTranscription();
2013
2028
  this.stopHeartbeat();
2014
2029
  this.intentionalClose = true;
2015
2030
  this.setState("leaving");
@@ -2299,7 +2314,14 @@ var VideoSDKCore = class {
2299
2314
  }
2300
2315
  // JOIN
2301
2316
  async joinMeeting(config) {
2302
- const { roomId, name, audioMuted = false, videoMuted = false } = config;
2317
+ const {
2318
+ roomId,
2319
+ name,
2320
+ audioMuted = false,
2321
+ videoMuted = false,
2322
+ cameraDeviceId,
2323
+ microphoneDeviceId
2324
+ } = config;
2303
2325
  if (!roomId || !name) {
2304
2326
  throw new Error("roomId and name are required to join meeting");
2305
2327
  }
@@ -2334,8 +2356,13 @@ var VideoSDKCore = class {
2334
2356
  this.room.name = name;
2335
2357
  if (!this.localStream) {
2336
2358
  this.localStream = await navigator.mediaDevices.getUserMedia({
2337
- video: true,
2338
- audio: true
2359
+ video: cameraDeviceId ? { deviceId: { exact: cameraDeviceId } } : true,
2360
+ audio: microphoneDeviceId ? {
2361
+ deviceId: { exact: microphoneDeviceId },
2362
+ echoCancellation: true,
2363
+ noiseSuppression: true,
2364
+ autoGainControl: true
2365
+ } : true
2339
2366
  });
2340
2367
  const localAudioTrack = this.localStream.getAudioTracks()[0];
2341
2368
  if (localAudioTrack) {
@@ -2424,10 +2451,6 @@ var VideoSDKCore = class {
2424
2451
  console.warn(`[SFU] Reconnecting, attempt ${attempt}`);
2425
2452
  },
2426
2453
  // ADMISSION CONTROL
2427
- onLiveTranscript: (transcript) => {
2428
- this.state.addLiveTranscript(transcript);
2429
- this.events.onLiveTranscript?.(transcript);
2430
- },
2431
2454
  onNetworkQuality: (quality) => {
2432
2455
  this.state.setNetworkQuality(quality);
2433
2456
  this.events.onNetworkQuality?.(quality);
@@ -2583,10 +2606,12 @@ var VideoSDKCore = class {
2583
2606
  stream: null
2584
2607
  }
2585
2608
  });
2586
- this.events.onUserJoined?.({
2609
+ const joinedParticipant = this.state.getParticipant(pid) ?? {
2587
2610
  id: pid,
2588
2611
  name: pname
2589
- });
2612
+ };
2613
+ this.events.onUserJoined?.(joinedParticipant);
2614
+ this.events.onParticipantJoined?.(joinedParticipant);
2590
2615
  },
2591
2616
  onParticipantPresence: (participantId, presence) => {
2592
2617
  if (participantId === this.myId) {
@@ -2615,9 +2640,11 @@ var VideoSDKCore = class {
2615
2640
  return;
2616
2641
  }
2617
2642
  console.log("[SFU] Participant permanently left", pid);
2643
+ const leavingParticipant = this.state.getParticipant(pid) ?? { id: pid };
2618
2644
  this.cleanupRemoteParticipant(pid);
2619
2645
  this.state.removeParticipant(pid);
2620
2646
  this.events.onUserLeft?.(pid);
2647
+ this.events.onParticipantLeft?.(leavingParticipant);
2621
2648
  },
2622
2649
  // TRACKS
2623
2650
  onParticipantMediaState: (participantId, audioMuted, videoMuted) => {
@@ -2927,6 +2954,39 @@ var VideoSDKCore = class {
2927
2954
  return this.sfuClient?.removeModerator(roomCode, userId);
2928
2955
  }
2929
2956
  // MEDIA CONTROL
2957
+ async getMediaDevices() {
2958
+ if (!this.sfuClient) {
2959
+ throw new Error("SFU client is not initialized");
2960
+ }
2961
+ return this.sfuClient.getMediaDevices();
2962
+ }
2963
+ async selectCamera(deviceId) {
2964
+ if (!this.sfuClient) {
2965
+ throw new Error("SFU client is not initialized");
2966
+ }
2967
+ const track = await this.sfuClient.selectCamera(deviceId);
2968
+ if (this.localStream) {
2969
+ this.state.localStream = this.localStream;
2970
+ this.updateLocalMedia();
2971
+ }
2972
+ return track;
2973
+ }
2974
+ async selectMicrophone(deviceId) {
2975
+ if (!this.sfuClient) {
2976
+ throw new Error("SFU client is not initialized");
2977
+ }
2978
+ const track = await this.sfuClient.selectMicrophone(deviceId);
2979
+ if (this.localStream) {
2980
+ const mic = this.localStream.getAudioTracks()[0];
2981
+ if (mic) {
2982
+ this.activeSpeakerDetector.removeParticipant(this.myId);
2983
+ this.activeSpeakerDetector.addTrack(this.myId, mic);
2984
+ }
2985
+ this.state.localStream = this.localStream;
2986
+ this.updateLocalMedia();
2987
+ }
2988
+ return track;
2989
+ }
2930
2990
  toggleMic() {
2931
2991
  const mediaState = this.state.localParticipant?.media;
2932
2992
  if (!mediaState || !this.localStream) {
@@ -3076,22 +3136,6 @@ var VideoSDKCore = class {
3076
3136
  }
3077
3137
  }
3078
3138
  }
3079
- async startLiveTranscription() {
3080
- if (!this.sfuClient) {
3081
- throw new Error("SFU client is not connected");
3082
- }
3083
- const audioTrack = this.localStream?.getAudioTracks()[0];
3084
- if (!audioTrack || audioTrack.readyState !== "live") {
3085
- throw new Error("Microphone audio track is not available");
3086
- }
3087
- await this.sfuClient.startLiveTranscription();
3088
- }
3089
- stopLiveTranscription() {
3090
- this.sfuClient?.stopLiveTranscription();
3091
- }
3092
- isLiveTranscriptionActive() {
3093
- return this.sfuClient?.isLiveTranscriptionActive() ?? false;
3094
- }
3095
3139
  getNetworkQuality() {
3096
3140
  return this.state.networkQuality;
3097
3141
  }
@@ -3143,7 +3187,6 @@ var VideoSDKCore = class {
3143
3187
  this.room.id = null;
3144
3188
  this.room.name = null;
3145
3189
  this.state.localParticipant = null;
3146
- this.state.clearLiveTranscripts();
3147
3190
  this.state.setNetworkQuality(null);
3148
3191
  this.state.notify("localParticipant");
3149
3192
  this.state.resetRemoteState();
@@ -3198,15 +3241,21 @@ var MeetingProvider = ({
3198
3241
  const activeSpeakerListeners = useRef(
3199
3242
  /* @__PURE__ */ new Set()
3200
3243
  );
3201
- const liveTranscriptListeners = useRef(
3202
- /* @__PURE__ */ new Set()
3203
- );
3204
3244
  const networkQualityListeners = useRef(
3205
3245
  /* @__PURE__ */ new Set()
3206
3246
  );
3207
3247
  const connectionStateListeners = useRef(
3208
3248
  /* @__PURE__ */ new Set()
3209
3249
  );
3250
+ const participantJoinedListeners = useRef(
3251
+ /* @__PURE__ */ new Set()
3252
+ );
3253
+ const participantLeftListeners = useRef(
3254
+ /* @__PURE__ */ new Set()
3255
+ );
3256
+ const chatMessageListeners = useRef(
3257
+ /* @__PURE__ */ new Set()
3258
+ );
3210
3259
  if (!sdkRef.current) {
3211
3260
  sdkRef.current = new VideoSDKCore(
3212
3261
  {
@@ -3267,6 +3316,7 @@ var MeetingProvider = ({
3267
3316
  data: msg
3268
3317
  };
3269
3318
  pubSubListeners.current.get("SECURE_CHAT")?.forEach((fn) => fn(pubSubMessage));
3319
+ chatMessageListeners.current.forEach((fn) => fn(msg));
3270
3320
  },
3271
3321
  onReaction: (reaction) => {
3272
3322
  reactionListeners.current.forEach((fn) => fn(reaction));
@@ -3287,9 +3337,6 @@ var MeetingProvider = ({
3287
3337
  );
3288
3338
  pubSubListeners.current.get("REACTION")?.forEach((fn) => fn(message));
3289
3339
  },
3290
- onLiveTranscript: (transcript) => {
3291
- liveTranscriptListeners.current.forEach((fn) => fn(transcript));
3292
- },
3293
3340
  onNetworkQuality: (quality) => {
3294
3341
  networkQualityListeners.current.forEach((fn) => fn(quality));
3295
3342
  },
@@ -3297,6 +3344,12 @@ var MeetingProvider = ({
3297
3344
  connectionStateListeners.current.forEach(
3298
3345
  (fn) => fn(state)
3299
3346
  );
3347
+ },
3348
+ onParticipantJoined: (participant) => {
3349
+ participantJoinedListeners.current.forEach((fn) => fn(participant));
3350
+ },
3351
+ onParticipantLeft: (participant) => {
3352
+ participantLeftListeners.current.forEach((fn) => fn(participant));
3300
3353
  }
3301
3354
  }
3302
3355
  );
@@ -3328,11 +3381,6 @@ var MeetingProvider = ({
3328
3381
  "chat",
3329
3382
  (s) => s.getChatMessages()
3330
3383
  );
3331
- const liveTranscripts = useMeetingStore(
3332
- sdk.state,
3333
- "transcript",
3334
- (s) => s.getLiveTranscripts()
3335
- );
3336
3384
  const networkQuality = useMeetingStore(
3337
3385
  sdk.state,
3338
3386
  "networkQuality",
@@ -3350,6 +3398,9 @@ var MeetingProvider = ({
3350
3398
  createRoom: sdk.createRoom.bind(sdk),
3351
3399
  getRoom: sdk.getRoom.bind(sdk),
3352
3400
  deleteRoom: sdk.deleteRoom.bind(sdk),
3401
+ getMediaDevices: sdk.getMediaDevices.bind(sdk),
3402
+ selectCamera: sdk.selectCamera.bind(sdk),
3403
+ selectMicrophone: sdk.selectMicrophone.bind(sdk),
3353
3404
  getTranscript: sdk.getTranscript.bind(sdk),
3354
3405
  getMeetingSummary: sdk.getMeetingSummary.bind(sdk),
3355
3406
  toggleMic: sdk.toggleMic.bind(sdk),
@@ -3364,11 +3415,7 @@ var MeetingProvider = ({
3364
3415
  messages,
3365
3416
  presenterId,
3366
3417
  activeSpeakerIds,
3367
- liveTranscripts,
3368
3418
  networkQuality,
3369
- startLiveTranscription: sdk.startLiveTranscription.bind(sdk),
3370
- stopLiveTranscription: sdk.stopLiveTranscription.bind(sdk),
3371
- isLiveTranscriptionActive: sdk.isLiveTranscriptionActive.bind(sdk),
3372
3419
  recording,
3373
3420
  usePubSub: (topic) => {
3374
3421
  if (topic === "SECURE_CHAT") {
@@ -3487,10 +3534,6 @@ var MeetingProvider = ({
3487
3534
  reactionListeners.current.add(cb);
3488
3535
  return () => reactionListeners.current.delete(cb);
3489
3536
  },
3490
- onLiveTranscript: (cb) => {
3491
- liveTranscriptListeners.current.add(cb);
3492
- return () => liveTranscriptListeners.current.delete(cb);
3493
- },
3494
3537
  onNetworkQuality: (cb) => {
3495
3538
  networkQualityListeners.current.add(cb);
3496
3539
  return () => networkQualityListeners.current.delete(cb);
@@ -3506,6 +3549,18 @@ var MeetingProvider = ({
3506
3549
  return () => {
3507
3550
  activeSpeakerListeners.current.delete(cb);
3508
3551
  };
3552
+ },
3553
+ onParticipantJoined: (cb) => {
3554
+ participantJoinedListeners.current.add(cb);
3555
+ return () => participantJoinedListeners.current.delete(cb);
3556
+ },
3557
+ onChatMessage: (cb) => {
3558
+ chatMessageListeners.current.add(cb);
3559
+ return () => chatMessageListeners.current.delete(cb);
3560
+ },
3561
+ onParticipantLeft: (cb) => {
3562
+ participantLeftListeners.current.add(cb);
3563
+ return () => participantLeftListeners.current.delete(cb);
3509
3564
  }
3510
3565
  };
3511
3566
  }, [
@@ -3517,7 +3572,6 @@ var MeetingProvider = ({
3517
3572
  presenterId,
3518
3573
  recording,
3519
3574
  activeSpeakerIds,
3520
- liveTranscripts,
3521
3575
  networkQuality,
3522
3576
  reactionPubSubMessages
3523
3577
  ]);
@@ -3627,14 +3681,22 @@ var useMeeting = (handlers) => {
3627
3681
  if (!handlers?.onConnectionStateChanged) return;
3628
3682
  return ctx.onConnectionStateChanged(handlers.onConnectionStateChanged);
3629
3683
  }, [handlers?.onConnectionStateChanged]);
3630
- useEffect3(() => {
3631
- if (!handlers?.onLiveTranscript) return;
3632
- return ctx.onLiveTranscript(handlers.onLiveTranscript);
3633
- }, [handlers?.onLiveTranscript]);
3634
3684
  useEffect3(() => {
3635
3685
  if (!handlers?.onNetworkQuality) return;
3636
3686
  return ctx.onNetworkQuality(handlers.onNetworkQuality);
3637
3687
  }, [handlers?.onNetworkQuality]);
3688
+ useEffect3(() => {
3689
+ if (!handlers?.onParticipantJoined) return;
3690
+ return ctx.onParticipantJoined(handlers.onParticipantJoined);
3691
+ }, [handlers?.onParticipantJoined]);
3692
+ useEffect3(() => {
3693
+ if (!handlers?.onParticipantLeft) return;
3694
+ return ctx.onParticipantLeft(handlers.onParticipantLeft);
3695
+ }, [handlers?.onParticipantLeft]);
3696
+ useEffect3(() => {
3697
+ if (!handlers?.onChatMessage) return;
3698
+ return ctx.onChatMessage(handlers.onChatMessage);
3699
+ }, [handlers?.onChatMessage]);
3638
3700
  useEffect3(() => {
3639
3701
  if (!handlers?.onReaction) return;
3640
3702
  return ctx.onReaction(handlers.onReaction);
@@ -3772,16 +3834,6 @@ function useMeetingPreview(roomId, participantId) {
3772
3834
  };
3773
3835
  }
3774
3836
 
3775
- // src/react/useLiveTranscript.ts
3776
- var useLiveTranscript = () => {
3777
- const { sdk } = useMeetingContext();
3778
- return useMeetingStore(
3779
- sdk.state,
3780
- "transcript",
3781
- (state) => state.getLiveTranscripts()
3782
- );
3783
- };
3784
-
3785
3837
  // src/react/useNetworkQuality.ts
3786
3838
  var useNetworkQuality = () => {
3787
3839
  const { sdk } = useMeetingContext();
@@ -3810,7 +3862,6 @@ export {
3810
3862
  MeetingProvider,
3811
3863
  MeetingState,
3812
3864
  VideoSDKCore,
3813
- useLiveTranscript,
3814
3865
  useLocalParticipant,
3815
3866
  useMeeting,
3816
3867
  useMeetingContext,