@defcomm/meetingsdk 0.5.5 → 0.5.7

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,6 +197,8 @@ var MeetingState = class {
197
197
  this.presenterId = null;
198
198
  this.activeSpeakerIds = /* @__PURE__ */ new Set();
199
199
  this.recording = null;
200
+ this.liveTranscripts = [];
201
+ this.networkQuality = null;
200
202
  this.listeners = /* @__PURE__ */ new Map();
201
203
  }
202
204
  // ---- reactive system ----
@@ -234,6 +236,21 @@ var MeetingState = class {
234
236
  this.recording = null;
235
237
  this.notify("recording");
236
238
  }
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
+ setNetworkQuality(quality) {
251
+ this.networkQuality = quality;
252
+ this.notify("networkQuality");
253
+ }
237
254
  // ---- participants ----
238
255
  addParticipant(p) {
239
256
  if (this.participants.has(p.id)) return false;
@@ -354,10 +371,14 @@ var MeetingState = class {
354
371
  this.chatMessages.clear();
355
372
  this.presenterId = null;
356
373
  this.activeSpeakerIds = /* @__PURE__ */ new Set();
374
+ this.liveTranscripts = [];
375
+ this.networkQuality = null;
357
376
  this.notify("participants");
358
377
  this.notify("chat");
359
378
  this.notify("presenter");
360
379
  this.notify("activeSpeakers");
380
+ this.notify("transcript");
381
+ this.notify("networkQuality");
361
382
  }
362
383
  };
363
384
 
@@ -419,6 +440,12 @@ var SFUClient = class {
419
440
  this.trackToSource = /* @__PURE__ */ new Map();
420
441
  this.localTrackIds = /* @__PURE__ */ new Map();
421
442
  this.subscriberStatsTimer = null;
443
+ this.liveTranscriptionActive = false;
444
+ this.liveTranscriptionSequence = 0;
445
+ this.liveTranscriptionStartedAt = 0;
446
+ this.liveTranscriptionRecorder = null;
447
+ this.liveTranscriptionLoopPromise = null;
448
+ this.networkProfile = null;
422
449
  this.heartbeatTimer = null;
423
450
  this.apiBase = options.apiBase || "https://api.example.com/api";
424
451
  this.wsBase = options.wsBase || "wss://api.example.com/ws";
@@ -475,6 +502,10 @@ var SFUClient = class {
475
502
  });
476
503
  this.onRoomUpdate = options.onRoomUpdate || (() => {
477
504
  });
505
+ this.onLiveTranscript = options.onLiveTranscript || (() => {
506
+ });
507
+ this.onNetworkQuality = options.onNetworkQuality || (() => {
508
+ });
478
509
  this.onParticipantMediaState = options.onParticipantMediaState || (() => {
479
510
  });
480
511
  this.autoSubscribe = options.autoSubscribe !== false;
@@ -1069,6 +1100,40 @@ var SFUClient = class {
1069
1100
  if (reaction?.id && reaction?.emoji) this.onReaction(reaction);
1070
1101
  break;
1071
1102
  }
1103
+ case "live_transcript": {
1104
+ const transcript = {
1105
+ transcript_id: String(payload.transcript_id),
1106
+ participant_id: String(payload.participant_id),
1107
+ speaker_name: payload.speaker_name ?? null,
1108
+ sequence: Number(payload.sequence ?? 0),
1109
+ text: String(payload.text ?? ""),
1110
+ offset_ms: payload.offset_ms == null ? null : Number(payload.offset_ms),
1111
+ start_ms: payload.start_ms == null ? null : Number(payload.start_ms),
1112
+ end_ms: payload.end_ms == null ? null : Number(payload.end_ms),
1113
+ timestamp: String(payload.timestamp ?? (/* @__PURE__ */ new Date()).toISOString())
1114
+ };
1115
+ if (transcript.text.trim()) this.onLiveTranscript(transcript);
1116
+ break;
1117
+ }
1118
+ case "network_quality": {
1119
+ const raw = payload.quality || payload;
1120
+ const quality = {
1121
+ participant_id: String(raw.participant_id ?? this.currentParticipantId ?? this.participantId),
1122
+ quality: String(raw.quality ?? "unknown"),
1123
+ score: Number(raw.score ?? 0),
1124
+ recommended_action: String(raw.recommended_action ?? "maintain"),
1125
+ packet_loss_percent: Number(raw.packet_loss_percent ?? 0),
1126
+ upload_kbps: Number(raw.upload_kbps ?? 0),
1127
+ forwarded_drop_percent: Number(raw.forwarded_drop_percent ?? 0),
1128
+ queued_packets: Number(raw.queued_packets ?? 0),
1129
+ packets_received: Number(raw.packets_received ?? 0),
1130
+ packets_lost: Number(raw.packets_lost ?? 0),
1131
+ observed_at: String(raw.observed_at ?? (/* @__PURE__ */ new Date()).toISOString())
1132
+ };
1133
+ this.onNetworkQuality(quality);
1134
+ void this.applyNetworkRecommendation(quality.recommended_action);
1135
+ break;
1136
+ }
1072
1137
  case "error": {
1073
1138
  const message2 = payload.message || "SFU signaling error";
1074
1139
  if (this.state === "reconnecting" && message2.includes("participant session is stale")) {
@@ -1728,6 +1793,208 @@ var SFUClient = class {
1728
1793
  }
1729
1794
  });
1730
1795
  }
1796
+ getLiveTranscriptionMimeType() {
1797
+ if (typeof MediaRecorder === "undefined") {
1798
+ throw new Error("MediaRecorder is not supported by this browser");
1799
+ }
1800
+ const candidates = [
1801
+ "audio/webm;codecs=opus",
1802
+ "audio/webm",
1803
+ "audio/ogg;codecs=opus"
1804
+ ];
1805
+ return candidates.find(
1806
+ (type) => MediaRecorder.isTypeSupported(type)
1807
+ ) || "";
1808
+ }
1809
+ async startLiveTranscription() {
1810
+ if (this.liveTranscriptionActive) {
1811
+ return;
1812
+ }
1813
+ if (!this.room) {
1814
+ throw new Error("Cannot start live transcription without a room");
1815
+ }
1816
+ if (!this.authToken) {
1817
+ throw new Error(
1818
+ "Cannot start live transcription without an authenticated token"
1819
+ );
1820
+ }
1821
+ const audioTrack = this.localStream?.getAudioTracks()[0];
1822
+ if (!audioTrack || audioTrack.readyState !== "live") {
1823
+ throw new Error(
1824
+ "Cannot start live transcription without a live microphone track"
1825
+ );
1826
+ }
1827
+ const mimeType = this.getLiveTranscriptionMimeType();
1828
+ const stream = new MediaStream([audioTrack]);
1829
+ this.liveTranscriptionActive = true;
1830
+ this.liveTranscriptionSequence = 0;
1831
+ this.liveTranscriptionStartedAt = Date.now();
1832
+ console.log("[Transcript] starting", {
1833
+ room: this.room,
1834
+ trackId: audioTrack.id,
1835
+ readyState: audioTrack.readyState,
1836
+ enabled: audioTrack.enabled,
1837
+ mimeType
1838
+ });
1839
+ this.liveTranscriptionLoopPromise = this.runLiveTranscriptionLoop(stream, mimeType).finally(() => {
1840
+ this.liveTranscriptionRecorder = null;
1841
+ this.liveTranscriptionLoopPromise = null;
1842
+ });
1843
+ await Promise.resolve();
1844
+ }
1845
+ async captureLiveChunk(stream, mimeType) {
1846
+ return new Promise((resolve, reject) => {
1847
+ const chunks = [];
1848
+ const recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
1849
+ this.liveTranscriptionRecorder = recorder;
1850
+ const timeout = window.setTimeout(() => {
1851
+ if (recorder.state !== "inactive") {
1852
+ recorder.stop();
1853
+ }
1854
+ }, 1500);
1855
+ recorder.onstart = () => {
1856
+ console.log("[Transcript] MediaRecorder started", {
1857
+ mimeType: recorder.mimeType
1858
+ });
1859
+ };
1860
+ recorder.ondataavailable = (event) => {
1861
+ if (event.data.size > 0) {
1862
+ chunks.push(event.data);
1863
+ }
1864
+ };
1865
+ recorder.onerror = () => {
1866
+ window.clearTimeout(timeout);
1867
+ reject(new Error("Live transcription recorder failed"));
1868
+ };
1869
+ recorder.onstop = () => {
1870
+ window.clearTimeout(timeout);
1871
+ this.liveTranscriptionRecorder = null;
1872
+ resolve(
1873
+ chunks.length ? new Blob(chunks, {
1874
+ type: recorder.mimeType || mimeType || "audio/webm"
1875
+ }) : null
1876
+ );
1877
+ };
1878
+ recorder.start();
1879
+ });
1880
+ }
1881
+ async runLiveTranscriptionLoop(stream, mimeType) {
1882
+ while (this.liveTranscriptionActive && stream.getAudioTracks()[0]?.readyState === "live") {
1883
+ try {
1884
+ const blob = await this.captureLiveChunk(stream, mimeType);
1885
+ if (!this.liveTranscriptionActive) {
1886
+ break;
1887
+ }
1888
+ if (!blob || blob.size === 0) {
1889
+ continue;
1890
+ }
1891
+ const sequence = this.liveTranscriptionSequence++;
1892
+ const offsetMs = Math.max(
1893
+ 0,
1894
+ Date.now() - this.liveTranscriptionStartedAt
1895
+ );
1896
+ await this.uploadLiveTranscriptionChunk(
1897
+ blob,
1898
+ sequence,
1899
+ offsetMs,
1900
+ blob.type || mimeType
1901
+ );
1902
+ } catch (error) {
1903
+ console.error(
1904
+ "[Transcript] chunk processing failed",
1905
+ error
1906
+ );
1907
+ this.onError(error);
1908
+ await new Promise(
1909
+ (resolve) => setTimeout(resolve, 750)
1910
+ );
1911
+ }
1912
+ }
1913
+ }
1914
+ async uploadLiveTranscriptionChunk(blob, sequence, offsetMs, mimeType) {
1915
+ if (!this.room || !this.authToken) {
1916
+ return;
1917
+ }
1918
+ const form = new FormData();
1919
+ form.append("sequence", String(sequence));
1920
+ form.append("offset_ms", String(offsetMs));
1921
+ const extension = mimeType.includes("ogg") || blob.type.includes("ogg") ? "ogg" : "webm";
1922
+ form.append(
1923
+ "audio",
1924
+ blob,
1925
+ `live-${sequence}.${extension}`
1926
+ );
1927
+ const url = `${this.apiBase}/rooms/${encodeURIComponent(this.room)}/live-transcription`;
1928
+ console.log("[Transcript] POST", {
1929
+ url,
1930
+ sequence,
1931
+ size: blob.size,
1932
+ mimeType
1933
+ });
1934
+ const response = await fetch(url, {
1935
+ method: "POST",
1936
+ headers: {
1937
+ Authorization: `Bearer ${this.authToken}`,
1938
+ Accept: "application/json"
1939
+ },
1940
+ body: form
1941
+ });
1942
+ const responseText = await response.text();
1943
+ console.log("[Transcript] response", {
1944
+ status: response.status,
1945
+ body: responseText
1946
+ });
1947
+ if (!response.ok) {
1948
+ throw new Error(
1949
+ `Live transcription request failed: ${response.status} ${responseText}`
1950
+ );
1951
+ }
1952
+ }
1953
+ stopLiveTranscription() {
1954
+ console.log("[Transcript] stopping");
1955
+ this.liveTranscriptionActive = false;
1956
+ try {
1957
+ if (this.liveTranscriptionRecorder && this.liveTranscriptionRecorder.state !== "inactive") {
1958
+ this.liveTranscriptionRecorder.stop();
1959
+ }
1960
+ } catch (error) {
1961
+ console.warn(
1962
+ "[Transcript] recorder shutdown failed",
1963
+ error
1964
+ );
1965
+ }
1966
+ this.liveTranscriptionRecorder = null;
1967
+ }
1968
+ isLiveTranscriptionActive() {
1969
+ return this.liveTranscriptionActive;
1970
+ }
1971
+ async applyNetworkRecommendation(action) {
1972
+ if (!this.videoSender || this.networkProfile === action) return;
1973
+ const params = this.videoSender.getParameters();
1974
+ const encodings = params.encodings?.length ? params.encodings : [{}];
1975
+ const encoding = encodings[0];
1976
+ switch (action) {
1977
+ case "reduce_video":
1978
+ encoding.maxBitrate = 7e5;
1979
+ encoding.maxFramerate = 20;
1980
+ break;
1981
+ case "audio_priority":
1982
+ encoding.maxBitrate = 35e4;
1983
+ encoding.maxFramerate = 10;
1984
+ break;
1985
+ default:
1986
+ encoding.maxBitrate = 15e5;
1987
+ encoding.maxFramerate = 30;
1988
+ break;
1989
+ }
1990
+ params.encodings = encodings;
1991
+ try {
1992
+ await this.videoSender.setParameters(params);
1993
+ this.networkProfile = action;
1994
+ } catch (error) {
1995
+ this.onError(error);
1996
+ }
1997
+ }
1731
1998
  async startRecording() {
1732
1999
  if (!this.send({ type: "start_recording" })) {
1733
2000
  throw new Error("SFU signaling is not connected");
@@ -1860,6 +2127,7 @@ var SFUClient = class {
1860
2127
  }
1861
2128
  }
1862
2129
  async leave() {
2130
+ this.stopLiveTranscription();
1863
2131
  this.stopHeartbeat();
1864
2132
  this.intentionalClose = true;
1865
2133
  this.setState("leaving");
@@ -2268,6 +2536,14 @@ var VideoSDKCore = class {
2268
2536
  console.warn(`[SFU] Reconnecting, attempt ${attempt}`);
2269
2537
  },
2270
2538
  // ADMISSION CONTROL
2539
+ onLiveTranscript: (transcript) => {
2540
+ this.state.addLiveTranscript(transcript);
2541
+ this.events.onLiveTranscript?.(transcript);
2542
+ },
2543
+ onNetworkQuality: (quality) => {
2544
+ this.state.setNetworkQuality(quality);
2545
+ this.events.onNetworkQuality?.(quality);
2546
+ },
2271
2547
  onRoomUpdate: (payload) => {
2272
2548
  const participants = payload?.participants || [];
2273
2549
  const local = participants.find(
@@ -2912,6 +3188,25 @@ var VideoSDKCore = class {
2912
3188
  }
2913
3189
  }
2914
3190
  }
3191
+ async startLiveTranscription() {
3192
+ if (!this.sfuClient) {
3193
+ throw new Error("SFU client is not connected");
3194
+ }
3195
+ const audioTrack = this.localStream?.getAudioTracks()[0];
3196
+ if (!audioTrack || audioTrack.readyState !== "live") {
3197
+ throw new Error("Microphone audio track is not available");
3198
+ }
3199
+ await this.sfuClient.startLiveTranscription();
3200
+ }
3201
+ stopLiveTranscription() {
3202
+ this.sfuClient?.stopLiveTranscription();
3203
+ }
3204
+ isLiveTranscriptionActive() {
3205
+ return this.sfuClient?.isLiveTranscriptionActive() ?? false;
3206
+ }
3207
+ getNetworkQuality() {
3208
+ return this.state.networkQuality;
3209
+ }
2915
3210
  // CLEANUP
2916
3211
  cleanupRemoteParticipant(participantId) {
2917
3212
  this.activeSpeakerDetector.removeParticipant(participantId);
@@ -2960,6 +3255,8 @@ var VideoSDKCore = class {
2960
3255
  this.room.id = null;
2961
3256
  this.room.name = null;
2962
3257
  this.state.localParticipant = null;
3258
+ this.state.clearLiveTranscripts();
3259
+ this.state.setNetworkQuality(null);
2963
3260
  this.state.notify("localParticipant");
2964
3261
  this.state.resetRemoteState();
2965
3262
  this.events.onMeetingLeft?.();
@@ -2992,9 +3289,15 @@ var MeetingProvider = ({
2992
3289
  const entryResponseListeners = useRef(
2993
3290
  /* @__PURE__ */ new Set()
2994
3291
  );
2995
- const joinRequestCanceledListeners = useRef(/* @__PURE__ */ new Set());
2996
- const participantRemovedListeners = useRef(/* @__PURE__ */ new Set());
2997
- const participantPermissionsChangedListeners = useRef(/* @__PURE__ */ new Set());
3292
+ const joinRequestCanceledListeners = useRef(
3293
+ /* @__PURE__ */ new Set()
3294
+ );
3295
+ const participantRemovedListeners = useRef(
3296
+ /* @__PURE__ */ new Set()
3297
+ );
3298
+ const participantPermissionsChangedListeners = useRef(
3299
+ /* @__PURE__ */ new Set()
3300
+ );
2998
3301
  const meetingLeftListeners = useRef(/* @__PURE__ */ new Set());
2999
3302
  const trackStateListeners = useRef(/* @__PURE__ */ new Set());
3000
3303
  const recordingStartedListeners = useRef(/* @__PURE__ */ new Set());
@@ -3007,6 +3310,12 @@ var MeetingProvider = ({
3007
3310
  const activeSpeakerListeners = useRef(
3008
3311
  /* @__PURE__ */ new Set()
3009
3312
  );
3313
+ const liveTranscriptListeners = useRef(
3314
+ /* @__PURE__ */ new Set()
3315
+ );
3316
+ const networkQualityListeners = useRef(
3317
+ /* @__PURE__ */ new Set()
3318
+ );
3010
3319
  const connectionStateListeners = useRef(
3011
3320
  /* @__PURE__ */ new Set()
3012
3321
  );
@@ -3030,10 +3339,14 @@ var MeetingProvider = ({
3030
3339
  joinRequestCanceledListeners.current.forEach((fn) => fn(requestId));
3031
3340
  },
3032
3341
  onParticipantRemoved: (participantId, removedBy) => {
3033
- participantRemovedListeners.current.forEach((fn) => fn(participantId, removedBy));
3342
+ participantRemovedListeners.current.forEach(
3343
+ (fn) => fn(participantId, removedBy)
3344
+ );
3034
3345
  },
3035
3346
  onParticipantPermissionsChanged: (participantId, permissions) => {
3036
- participantPermissionsChangedListeners.current.forEach((fn) => fn(participantId, permissions));
3347
+ participantPermissionsChangedListeners.current.forEach(
3348
+ (fn) => fn(participantId, permissions)
3349
+ );
3037
3350
  },
3038
3351
  onMeetingLeft: () => {
3039
3352
  meetingLeftListeners.current.forEach((fn) => fn());
@@ -3086,6 +3399,12 @@ var MeetingProvider = ({
3086
3399
  );
3087
3400
  pubSubListeners.current.get("REACTION")?.forEach((fn) => fn(message));
3088
3401
  },
3402
+ onLiveTranscript: (transcript) => {
3403
+ liveTranscriptListeners.current.forEach((fn) => fn(transcript));
3404
+ },
3405
+ onNetworkQuality: (quality) => {
3406
+ networkQualityListeners.current.forEach((fn) => fn(quality));
3407
+ },
3089
3408
  onConnectionStateChanged: (state) => {
3090
3409
  connectionStateListeners.current.forEach(
3091
3410
  (fn) => fn(state)
@@ -3121,6 +3440,16 @@ var MeetingProvider = ({
3121
3440
  "chat",
3122
3441
  (s) => s.getChatMessages()
3123
3442
  );
3443
+ const liveTranscripts = useMeetingStore(
3444
+ sdk.state,
3445
+ "transcript",
3446
+ (s) => s.getLiveTranscripts()
3447
+ );
3448
+ const networkQuality = useMeetingStore(
3449
+ sdk.state,
3450
+ "networkQuality",
3451
+ (s) => s.networkQuality
3452
+ );
3124
3453
  const value = useMemo(() => {
3125
3454
  return {
3126
3455
  sdk,
@@ -3145,6 +3474,11 @@ var MeetingProvider = ({
3145
3474
  messages,
3146
3475
  presenterId,
3147
3476
  activeSpeakerIds,
3477
+ liveTranscripts,
3478
+ networkQuality,
3479
+ startLiveTranscription: sdk.startLiveTranscription.bind(sdk),
3480
+ stopLiveTranscription: sdk.stopLiveTranscription.bind(sdk),
3481
+ isLiveTranscriptionActive: sdk.isLiveTranscriptionActive.bind(sdk),
3148
3482
  recording,
3149
3483
  usePubSub: (topic) => {
3150
3484
  if (topic === "SECURE_CHAT") {
@@ -3263,6 +3597,14 @@ var MeetingProvider = ({
3263
3597
  reactionListeners.current.add(cb);
3264
3598
  return () => reactionListeners.current.delete(cb);
3265
3599
  },
3600
+ onLiveTranscript: (cb) => {
3601
+ liveTranscriptListeners.current.add(cb);
3602
+ return () => liveTranscriptListeners.current.delete(cb);
3603
+ },
3604
+ onNetworkQuality: (cb) => {
3605
+ networkQualityListeners.current.add(cb);
3606
+ return () => networkQualityListeners.current.delete(cb);
3607
+ },
3266
3608
  onConnectionStateChanged: (cb) => {
3267
3609
  connectionStateListeners.current.add(cb);
3268
3610
  return () => {
@@ -3285,6 +3627,8 @@ var MeetingProvider = ({
3285
3627
  presenterId,
3286
3628
  recording,
3287
3629
  activeSpeakerIds,
3630
+ liveTranscripts,
3631
+ networkQuality,
3288
3632
  reactionPubSubMessages
3289
3633
  ]);
3290
3634
  return /* @__PURE__ */ jsx(MeetingContext.Provider, { value, children });
@@ -3369,7 +3713,9 @@ var useMeeting = (handlers) => {
3369
3713
  }, [handlers?.onParticipantRemoved]);
3370
3714
  useEffect3(() => {
3371
3715
  if (!handlers?.onParticipantPermissionsChanged) return;
3372
- return ctx.onParticipantPermissionsChanged(handlers.onParticipantPermissionsChanged);
3716
+ return ctx.onParticipantPermissionsChanged(
3717
+ handlers.onParticipantPermissionsChanged
3718
+ );
3373
3719
  }, [handlers?.onParticipantPermissionsChanged]);
3374
3720
  useEffect3(() => {
3375
3721
  if (!handlers?.onMeetingLeft) return;
@@ -3391,6 +3737,14 @@ var useMeeting = (handlers) => {
3391
3737
  if (!handlers?.onConnectionStateChanged) return;
3392
3738
  return ctx.onConnectionStateChanged(handlers.onConnectionStateChanged);
3393
3739
  }, [handlers?.onConnectionStateChanged]);
3740
+ useEffect3(() => {
3741
+ if (!handlers?.onLiveTranscript) return;
3742
+ return ctx.onLiveTranscript(handlers.onLiveTranscript);
3743
+ }, [handlers?.onLiveTranscript]);
3744
+ useEffect3(() => {
3745
+ if (!handlers?.onNetworkQuality) return;
3746
+ return ctx.onNetworkQuality(handlers.onNetworkQuality);
3747
+ }, [handlers?.onNetworkQuality]);
3394
3748
  useEffect3(() => {
3395
3749
  if (!handlers?.onReaction) return;
3396
3750
  return ctx.onReaction(handlers.onReaction);
@@ -3528,6 +3882,26 @@ function useMeetingPreview(roomId, participantId) {
3528
3882
  };
3529
3883
  }
3530
3884
 
3885
+ // src/react/useLiveTranscript.ts
3886
+ var useLiveTranscript = () => {
3887
+ const { sdk } = useMeetingContext();
3888
+ return useMeetingStore(
3889
+ sdk.state,
3890
+ "transcript",
3891
+ (state) => state.getLiveTranscripts()
3892
+ );
3893
+ };
3894
+
3895
+ // src/react/useNetworkQuality.ts
3896
+ var useNetworkQuality = () => {
3897
+ const { sdk } = useMeetingContext();
3898
+ return useMeetingStore(
3899
+ sdk.state,
3900
+ "networkQuality",
3901
+ (state) => state.networkQuality
3902
+ );
3903
+ };
3904
+
3531
3905
  // src/react/usePubSub.ts
3532
3906
  import { useEffect as useEffect7 } from "react";
3533
3907
  function usePubSub(topic, options) {
@@ -3546,10 +3920,12 @@ export {
3546
3920
  MeetingProvider,
3547
3921
  MeetingState,
3548
3922
  VideoSDKCore,
3923
+ useLiveTranscript,
3549
3924
  useLocalParticipant,
3550
3925
  useMeeting,
3551
3926
  useMeetingContext,
3552
3927
  useMeetingPreview,
3928
+ useNetworkQuality,
3553
3929
  useParticipants,
3554
3930
  usePubSub,
3555
3931
  useRemoteMedia