@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.js CHANGED
@@ -24,10 +24,12 @@ __export(index_exports, {
24
24
  MeetingProvider: () => MeetingProvider,
25
25
  MeetingState: () => MeetingState,
26
26
  VideoSDKCore: () => VideoSDKCore,
27
+ useLiveTranscript: () => useLiveTranscript,
27
28
  useLocalParticipant: () => useLocalParticipant,
28
29
  useMeeting: () => useMeeting,
29
30
  useMeetingContext: () => useMeetingContext,
30
31
  useMeetingPreview: () => useMeetingPreview,
32
+ useNetworkQuality: () => useNetworkQuality,
31
33
  useParticipants: () => useParticipants,
32
34
  usePubSub: () => usePubSub,
33
35
  useRemoteMedia: () => useRemoteMedia
@@ -227,6 +229,8 @@ var MeetingState = class {
227
229
  this.presenterId = null;
228
230
  this.activeSpeakerIds = /* @__PURE__ */ new Set();
229
231
  this.recording = null;
232
+ this.liveTranscripts = [];
233
+ this.networkQuality = null;
230
234
  this.listeners = /* @__PURE__ */ new Map();
231
235
  }
232
236
  // ---- reactive system ----
@@ -264,6 +268,21 @@ var MeetingState = class {
264
268
  this.recording = null;
265
269
  this.notify("recording");
266
270
  }
271
+ addLiveTranscript(transcript) {
272
+ this.liveTranscripts = [...this.liveTranscripts.slice(-499), transcript];
273
+ this.notify("transcript");
274
+ }
275
+ getLiveTranscripts() {
276
+ return this.liveTranscripts;
277
+ }
278
+ clearLiveTranscripts() {
279
+ this.liveTranscripts = [];
280
+ this.notify("transcript");
281
+ }
282
+ setNetworkQuality(quality) {
283
+ this.networkQuality = quality;
284
+ this.notify("networkQuality");
285
+ }
267
286
  // ---- participants ----
268
287
  addParticipant(p) {
269
288
  if (this.participants.has(p.id)) return false;
@@ -384,10 +403,14 @@ var MeetingState = class {
384
403
  this.chatMessages.clear();
385
404
  this.presenterId = null;
386
405
  this.activeSpeakerIds = /* @__PURE__ */ new Set();
406
+ this.liveTranscripts = [];
407
+ this.networkQuality = null;
387
408
  this.notify("participants");
388
409
  this.notify("chat");
389
410
  this.notify("presenter");
390
411
  this.notify("activeSpeakers");
412
+ this.notify("transcript");
413
+ this.notify("networkQuality");
391
414
  }
392
415
  };
393
416
 
@@ -449,6 +472,12 @@ var SFUClient = class {
449
472
  this.trackToSource = /* @__PURE__ */ new Map();
450
473
  this.localTrackIds = /* @__PURE__ */ new Map();
451
474
  this.subscriberStatsTimer = null;
475
+ this.liveTranscriptionActive = false;
476
+ this.liveTranscriptionSequence = 0;
477
+ this.liveTranscriptionStartedAt = 0;
478
+ this.liveTranscriptionRecorder = null;
479
+ this.liveTranscriptionLoopPromise = null;
480
+ this.networkProfile = null;
452
481
  this.heartbeatTimer = null;
453
482
  this.apiBase = options.apiBase || "https://api.example.com/api";
454
483
  this.wsBase = options.wsBase || "wss://api.example.com/ws";
@@ -505,6 +534,10 @@ var SFUClient = class {
505
534
  });
506
535
  this.onRoomUpdate = options.onRoomUpdate || (() => {
507
536
  });
537
+ this.onLiveTranscript = options.onLiveTranscript || (() => {
538
+ });
539
+ this.onNetworkQuality = options.onNetworkQuality || (() => {
540
+ });
508
541
  this.onParticipantMediaState = options.onParticipantMediaState || (() => {
509
542
  });
510
543
  this.autoSubscribe = options.autoSubscribe !== false;
@@ -1099,6 +1132,40 @@ var SFUClient = class {
1099
1132
  if (reaction?.id && reaction?.emoji) this.onReaction(reaction);
1100
1133
  break;
1101
1134
  }
1135
+ case "live_transcript": {
1136
+ const transcript = {
1137
+ transcript_id: String(payload.transcript_id),
1138
+ participant_id: String(payload.participant_id),
1139
+ speaker_name: payload.speaker_name ?? null,
1140
+ sequence: Number(payload.sequence ?? 0),
1141
+ text: String(payload.text ?? ""),
1142
+ offset_ms: payload.offset_ms == null ? null : Number(payload.offset_ms),
1143
+ start_ms: payload.start_ms == null ? null : Number(payload.start_ms),
1144
+ end_ms: payload.end_ms == null ? null : Number(payload.end_ms),
1145
+ timestamp: String(payload.timestamp ?? (/* @__PURE__ */ new Date()).toISOString())
1146
+ };
1147
+ if (transcript.text.trim()) this.onLiveTranscript(transcript);
1148
+ break;
1149
+ }
1150
+ case "network_quality": {
1151
+ const raw = payload.quality || payload;
1152
+ const quality = {
1153
+ participant_id: String(raw.participant_id ?? this.currentParticipantId ?? this.participantId),
1154
+ quality: String(raw.quality ?? "unknown"),
1155
+ score: Number(raw.score ?? 0),
1156
+ recommended_action: String(raw.recommended_action ?? "maintain"),
1157
+ packet_loss_percent: Number(raw.packet_loss_percent ?? 0),
1158
+ upload_kbps: Number(raw.upload_kbps ?? 0),
1159
+ forwarded_drop_percent: Number(raw.forwarded_drop_percent ?? 0),
1160
+ queued_packets: Number(raw.queued_packets ?? 0),
1161
+ packets_received: Number(raw.packets_received ?? 0),
1162
+ packets_lost: Number(raw.packets_lost ?? 0),
1163
+ observed_at: String(raw.observed_at ?? (/* @__PURE__ */ new Date()).toISOString())
1164
+ };
1165
+ this.onNetworkQuality(quality);
1166
+ void this.applyNetworkRecommendation(quality.recommended_action);
1167
+ break;
1168
+ }
1102
1169
  case "error": {
1103
1170
  const message2 = payload.message || "SFU signaling error";
1104
1171
  if (this.state === "reconnecting" && message2.includes("participant session is stale")) {
@@ -1758,6 +1825,208 @@ var SFUClient = class {
1758
1825
  }
1759
1826
  });
1760
1827
  }
1828
+ getLiveTranscriptionMimeType() {
1829
+ if (typeof MediaRecorder === "undefined") {
1830
+ throw new Error("MediaRecorder is not supported by this browser");
1831
+ }
1832
+ const candidates = [
1833
+ "audio/webm;codecs=opus",
1834
+ "audio/webm",
1835
+ "audio/ogg;codecs=opus"
1836
+ ];
1837
+ return candidates.find(
1838
+ (type) => MediaRecorder.isTypeSupported(type)
1839
+ ) || "";
1840
+ }
1841
+ async startLiveTranscription() {
1842
+ if (this.liveTranscriptionActive) {
1843
+ return;
1844
+ }
1845
+ if (!this.room) {
1846
+ throw new Error("Cannot start live transcription without a room");
1847
+ }
1848
+ if (!this.authToken) {
1849
+ throw new Error(
1850
+ "Cannot start live transcription without an authenticated token"
1851
+ );
1852
+ }
1853
+ const audioTrack = this.localStream?.getAudioTracks()[0];
1854
+ if (!audioTrack || audioTrack.readyState !== "live") {
1855
+ throw new Error(
1856
+ "Cannot start live transcription without a live microphone track"
1857
+ );
1858
+ }
1859
+ const mimeType = this.getLiveTranscriptionMimeType();
1860
+ const stream = new MediaStream([audioTrack]);
1861
+ this.liveTranscriptionActive = true;
1862
+ this.liveTranscriptionSequence = 0;
1863
+ this.liveTranscriptionStartedAt = Date.now();
1864
+ console.log("[Transcript] starting", {
1865
+ room: this.room,
1866
+ trackId: audioTrack.id,
1867
+ readyState: audioTrack.readyState,
1868
+ enabled: audioTrack.enabled,
1869
+ mimeType
1870
+ });
1871
+ this.liveTranscriptionLoopPromise = this.runLiveTranscriptionLoop(stream, mimeType).finally(() => {
1872
+ this.liveTranscriptionRecorder = null;
1873
+ this.liveTranscriptionLoopPromise = null;
1874
+ });
1875
+ await Promise.resolve();
1876
+ }
1877
+ async captureLiveChunk(stream, mimeType) {
1878
+ return new Promise((resolve, reject) => {
1879
+ const chunks = [];
1880
+ const recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
1881
+ this.liveTranscriptionRecorder = recorder;
1882
+ const timeout = window.setTimeout(() => {
1883
+ if (recorder.state !== "inactive") {
1884
+ recorder.stop();
1885
+ }
1886
+ }, 1500);
1887
+ recorder.onstart = () => {
1888
+ console.log("[Transcript] MediaRecorder started", {
1889
+ mimeType: recorder.mimeType
1890
+ });
1891
+ };
1892
+ recorder.ondataavailable = (event) => {
1893
+ if (event.data.size > 0) {
1894
+ chunks.push(event.data);
1895
+ }
1896
+ };
1897
+ recorder.onerror = () => {
1898
+ window.clearTimeout(timeout);
1899
+ reject(new Error("Live transcription recorder failed"));
1900
+ };
1901
+ recorder.onstop = () => {
1902
+ window.clearTimeout(timeout);
1903
+ this.liveTranscriptionRecorder = null;
1904
+ resolve(
1905
+ chunks.length ? new Blob(chunks, {
1906
+ type: recorder.mimeType || mimeType || "audio/webm"
1907
+ }) : null
1908
+ );
1909
+ };
1910
+ recorder.start();
1911
+ });
1912
+ }
1913
+ async runLiveTranscriptionLoop(stream, mimeType) {
1914
+ while (this.liveTranscriptionActive && stream.getAudioTracks()[0]?.readyState === "live") {
1915
+ try {
1916
+ const blob = await this.captureLiveChunk(stream, mimeType);
1917
+ if (!this.liveTranscriptionActive) {
1918
+ break;
1919
+ }
1920
+ if (!blob || blob.size === 0) {
1921
+ continue;
1922
+ }
1923
+ const sequence = this.liveTranscriptionSequence++;
1924
+ const offsetMs = Math.max(
1925
+ 0,
1926
+ Date.now() - this.liveTranscriptionStartedAt
1927
+ );
1928
+ await this.uploadLiveTranscriptionChunk(
1929
+ blob,
1930
+ sequence,
1931
+ offsetMs,
1932
+ blob.type || mimeType
1933
+ );
1934
+ } catch (error) {
1935
+ console.error(
1936
+ "[Transcript] chunk processing failed",
1937
+ error
1938
+ );
1939
+ this.onError(error);
1940
+ await new Promise(
1941
+ (resolve) => setTimeout(resolve, 750)
1942
+ );
1943
+ }
1944
+ }
1945
+ }
1946
+ async uploadLiveTranscriptionChunk(blob, sequence, offsetMs, mimeType) {
1947
+ if (!this.room || !this.authToken) {
1948
+ return;
1949
+ }
1950
+ const form = new FormData();
1951
+ form.append("sequence", String(sequence));
1952
+ form.append("offset_ms", String(offsetMs));
1953
+ const extension = mimeType.includes("ogg") || blob.type.includes("ogg") ? "ogg" : "webm";
1954
+ form.append(
1955
+ "audio",
1956
+ blob,
1957
+ `live-${sequence}.${extension}`
1958
+ );
1959
+ const url = `${this.apiBase}/rooms/${encodeURIComponent(this.room)}/live-transcription`;
1960
+ console.log("[Transcript] POST", {
1961
+ url,
1962
+ sequence,
1963
+ size: blob.size,
1964
+ mimeType
1965
+ });
1966
+ const response = await fetch(url, {
1967
+ method: "POST",
1968
+ headers: {
1969
+ Authorization: `Bearer ${this.authToken}`,
1970
+ Accept: "application/json"
1971
+ },
1972
+ body: form
1973
+ });
1974
+ const responseText = await response.text();
1975
+ console.log("[Transcript] response", {
1976
+ status: response.status,
1977
+ body: responseText
1978
+ });
1979
+ if (!response.ok) {
1980
+ throw new Error(
1981
+ `Live transcription request failed: ${response.status} ${responseText}`
1982
+ );
1983
+ }
1984
+ }
1985
+ stopLiveTranscription() {
1986
+ console.log("[Transcript] stopping");
1987
+ this.liveTranscriptionActive = false;
1988
+ try {
1989
+ if (this.liveTranscriptionRecorder && this.liveTranscriptionRecorder.state !== "inactive") {
1990
+ this.liveTranscriptionRecorder.stop();
1991
+ }
1992
+ } catch (error) {
1993
+ console.warn(
1994
+ "[Transcript] recorder shutdown failed",
1995
+ error
1996
+ );
1997
+ }
1998
+ this.liveTranscriptionRecorder = null;
1999
+ }
2000
+ isLiveTranscriptionActive() {
2001
+ return this.liveTranscriptionActive;
2002
+ }
2003
+ async applyNetworkRecommendation(action) {
2004
+ if (!this.videoSender || this.networkProfile === action) return;
2005
+ const params = this.videoSender.getParameters();
2006
+ const encodings = params.encodings?.length ? params.encodings : [{}];
2007
+ const encoding = encodings[0];
2008
+ switch (action) {
2009
+ case "reduce_video":
2010
+ encoding.maxBitrate = 7e5;
2011
+ encoding.maxFramerate = 20;
2012
+ break;
2013
+ case "audio_priority":
2014
+ encoding.maxBitrate = 35e4;
2015
+ encoding.maxFramerate = 10;
2016
+ break;
2017
+ default:
2018
+ encoding.maxBitrate = 15e5;
2019
+ encoding.maxFramerate = 30;
2020
+ break;
2021
+ }
2022
+ params.encodings = encodings;
2023
+ try {
2024
+ await this.videoSender.setParameters(params);
2025
+ this.networkProfile = action;
2026
+ } catch (error) {
2027
+ this.onError(error);
2028
+ }
2029
+ }
1761
2030
  async startRecording() {
1762
2031
  if (!this.send({ type: "start_recording" })) {
1763
2032
  throw new Error("SFU signaling is not connected");
@@ -1890,6 +2159,7 @@ var SFUClient = class {
1890
2159
  }
1891
2160
  }
1892
2161
  async leave() {
2162
+ this.stopLiveTranscription();
1893
2163
  this.stopHeartbeat();
1894
2164
  this.intentionalClose = true;
1895
2165
  this.setState("leaving");
@@ -2298,6 +2568,14 @@ var VideoSDKCore = class {
2298
2568
  console.warn(`[SFU] Reconnecting, attempt ${attempt}`);
2299
2569
  },
2300
2570
  // ADMISSION CONTROL
2571
+ onLiveTranscript: (transcript) => {
2572
+ this.state.addLiveTranscript(transcript);
2573
+ this.events.onLiveTranscript?.(transcript);
2574
+ },
2575
+ onNetworkQuality: (quality) => {
2576
+ this.state.setNetworkQuality(quality);
2577
+ this.events.onNetworkQuality?.(quality);
2578
+ },
2301
2579
  onRoomUpdate: (payload) => {
2302
2580
  const participants = payload?.participants || [];
2303
2581
  const local = participants.find(
@@ -2942,6 +3220,25 @@ var VideoSDKCore = class {
2942
3220
  }
2943
3221
  }
2944
3222
  }
3223
+ async startLiveTranscription() {
3224
+ if (!this.sfuClient) {
3225
+ throw new Error("SFU client is not connected");
3226
+ }
3227
+ const audioTrack = this.localStream?.getAudioTracks()[0];
3228
+ if (!audioTrack || audioTrack.readyState !== "live") {
3229
+ throw new Error("Microphone audio track is not available");
3230
+ }
3231
+ await this.sfuClient.startLiveTranscription();
3232
+ }
3233
+ stopLiveTranscription() {
3234
+ this.sfuClient?.stopLiveTranscription();
3235
+ }
3236
+ isLiveTranscriptionActive() {
3237
+ return this.sfuClient?.isLiveTranscriptionActive() ?? false;
3238
+ }
3239
+ getNetworkQuality() {
3240
+ return this.state.networkQuality;
3241
+ }
2945
3242
  // CLEANUP
2946
3243
  cleanupRemoteParticipant(participantId) {
2947
3244
  this.activeSpeakerDetector.removeParticipant(participantId);
@@ -2990,6 +3287,8 @@ var VideoSDKCore = class {
2990
3287
  this.room.id = null;
2991
3288
  this.room.name = null;
2992
3289
  this.state.localParticipant = null;
3290
+ this.state.clearLiveTranscripts();
3291
+ this.state.setNetworkQuality(null);
2993
3292
  this.state.notify("localParticipant");
2994
3293
  this.state.resetRemoteState();
2995
3294
  this.events.onMeetingLeft?.();
@@ -3022,9 +3321,15 @@ var MeetingProvider = ({
3022
3321
  const entryResponseListeners = (0, import_react2.useRef)(
3023
3322
  /* @__PURE__ */ new Set()
3024
3323
  );
3025
- const joinRequestCanceledListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
3026
- const participantRemovedListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
3027
- const participantPermissionsChangedListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
3324
+ const joinRequestCanceledListeners = (0, import_react2.useRef)(
3325
+ /* @__PURE__ */ new Set()
3326
+ );
3327
+ const participantRemovedListeners = (0, import_react2.useRef)(
3328
+ /* @__PURE__ */ new Set()
3329
+ );
3330
+ const participantPermissionsChangedListeners = (0, import_react2.useRef)(
3331
+ /* @__PURE__ */ new Set()
3332
+ );
3028
3333
  const meetingLeftListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
3029
3334
  const trackStateListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
3030
3335
  const recordingStartedListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
@@ -3037,6 +3342,12 @@ var MeetingProvider = ({
3037
3342
  const activeSpeakerListeners = (0, import_react2.useRef)(
3038
3343
  /* @__PURE__ */ new Set()
3039
3344
  );
3345
+ const liveTranscriptListeners = (0, import_react2.useRef)(
3346
+ /* @__PURE__ */ new Set()
3347
+ );
3348
+ const networkQualityListeners = (0, import_react2.useRef)(
3349
+ /* @__PURE__ */ new Set()
3350
+ );
3040
3351
  const connectionStateListeners = (0, import_react2.useRef)(
3041
3352
  /* @__PURE__ */ new Set()
3042
3353
  );
@@ -3060,10 +3371,14 @@ var MeetingProvider = ({
3060
3371
  joinRequestCanceledListeners.current.forEach((fn) => fn(requestId));
3061
3372
  },
3062
3373
  onParticipantRemoved: (participantId, removedBy) => {
3063
- participantRemovedListeners.current.forEach((fn) => fn(participantId, removedBy));
3374
+ participantRemovedListeners.current.forEach(
3375
+ (fn) => fn(participantId, removedBy)
3376
+ );
3064
3377
  },
3065
3378
  onParticipantPermissionsChanged: (participantId, permissions) => {
3066
- participantPermissionsChangedListeners.current.forEach((fn) => fn(participantId, permissions));
3379
+ participantPermissionsChangedListeners.current.forEach(
3380
+ (fn) => fn(participantId, permissions)
3381
+ );
3067
3382
  },
3068
3383
  onMeetingLeft: () => {
3069
3384
  meetingLeftListeners.current.forEach((fn) => fn());
@@ -3116,6 +3431,12 @@ var MeetingProvider = ({
3116
3431
  );
3117
3432
  pubSubListeners.current.get("REACTION")?.forEach((fn) => fn(message));
3118
3433
  },
3434
+ onLiveTranscript: (transcript) => {
3435
+ liveTranscriptListeners.current.forEach((fn) => fn(transcript));
3436
+ },
3437
+ onNetworkQuality: (quality) => {
3438
+ networkQualityListeners.current.forEach((fn) => fn(quality));
3439
+ },
3119
3440
  onConnectionStateChanged: (state) => {
3120
3441
  connectionStateListeners.current.forEach(
3121
3442
  (fn) => fn(state)
@@ -3151,6 +3472,16 @@ var MeetingProvider = ({
3151
3472
  "chat",
3152
3473
  (s) => s.getChatMessages()
3153
3474
  );
3475
+ const liveTranscripts = useMeetingStore(
3476
+ sdk.state,
3477
+ "transcript",
3478
+ (s) => s.getLiveTranscripts()
3479
+ );
3480
+ const networkQuality = useMeetingStore(
3481
+ sdk.state,
3482
+ "networkQuality",
3483
+ (s) => s.networkQuality
3484
+ );
3154
3485
  const value = (0, import_react2.useMemo)(() => {
3155
3486
  return {
3156
3487
  sdk,
@@ -3175,6 +3506,11 @@ var MeetingProvider = ({
3175
3506
  messages,
3176
3507
  presenterId,
3177
3508
  activeSpeakerIds,
3509
+ liveTranscripts,
3510
+ networkQuality,
3511
+ startLiveTranscription: sdk.startLiveTranscription.bind(sdk),
3512
+ stopLiveTranscription: sdk.stopLiveTranscription.bind(sdk),
3513
+ isLiveTranscriptionActive: sdk.isLiveTranscriptionActive.bind(sdk),
3178
3514
  recording,
3179
3515
  usePubSub: (topic) => {
3180
3516
  if (topic === "SECURE_CHAT") {
@@ -3293,6 +3629,14 @@ var MeetingProvider = ({
3293
3629
  reactionListeners.current.add(cb);
3294
3630
  return () => reactionListeners.current.delete(cb);
3295
3631
  },
3632
+ onLiveTranscript: (cb) => {
3633
+ liveTranscriptListeners.current.add(cb);
3634
+ return () => liveTranscriptListeners.current.delete(cb);
3635
+ },
3636
+ onNetworkQuality: (cb) => {
3637
+ networkQualityListeners.current.add(cb);
3638
+ return () => networkQualityListeners.current.delete(cb);
3639
+ },
3296
3640
  onConnectionStateChanged: (cb) => {
3297
3641
  connectionStateListeners.current.add(cb);
3298
3642
  return () => {
@@ -3315,6 +3659,8 @@ var MeetingProvider = ({
3315
3659
  presenterId,
3316
3660
  recording,
3317
3661
  activeSpeakerIds,
3662
+ liveTranscripts,
3663
+ networkQuality,
3318
3664
  reactionPubSubMessages
3319
3665
  ]);
3320
3666
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MeetingContext.Provider, { value, children });
@@ -3399,7 +3745,9 @@ var useMeeting = (handlers) => {
3399
3745
  }, [handlers?.onParticipantRemoved]);
3400
3746
  (0, import_react4.useEffect)(() => {
3401
3747
  if (!handlers?.onParticipantPermissionsChanged) return;
3402
- return ctx.onParticipantPermissionsChanged(handlers.onParticipantPermissionsChanged);
3748
+ return ctx.onParticipantPermissionsChanged(
3749
+ handlers.onParticipantPermissionsChanged
3750
+ );
3403
3751
  }, [handlers?.onParticipantPermissionsChanged]);
3404
3752
  (0, import_react4.useEffect)(() => {
3405
3753
  if (!handlers?.onMeetingLeft) return;
@@ -3421,6 +3769,14 @@ var useMeeting = (handlers) => {
3421
3769
  if (!handlers?.onConnectionStateChanged) return;
3422
3770
  return ctx.onConnectionStateChanged(handlers.onConnectionStateChanged);
3423
3771
  }, [handlers?.onConnectionStateChanged]);
3772
+ (0, import_react4.useEffect)(() => {
3773
+ if (!handlers?.onLiveTranscript) return;
3774
+ return ctx.onLiveTranscript(handlers.onLiveTranscript);
3775
+ }, [handlers?.onLiveTranscript]);
3776
+ (0, import_react4.useEffect)(() => {
3777
+ if (!handlers?.onNetworkQuality) return;
3778
+ return ctx.onNetworkQuality(handlers.onNetworkQuality);
3779
+ }, [handlers?.onNetworkQuality]);
3424
3780
  (0, import_react4.useEffect)(() => {
3425
3781
  if (!handlers?.onReaction) return;
3426
3782
  return ctx.onReaction(handlers.onReaction);
@@ -3558,6 +3914,26 @@ function useMeetingPreview(roomId, participantId) {
3558
3914
  };
3559
3915
  }
3560
3916
 
3917
+ // src/react/useLiveTranscript.ts
3918
+ var useLiveTranscript = () => {
3919
+ const { sdk } = useMeetingContext();
3920
+ return useMeetingStore(
3921
+ sdk.state,
3922
+ "transcript",
3923
+ (state) => state.getLiveTranscripts()
3924
+ );
3925
+ };
3926
+
3927
+ // src/react/useNetworkQuality.ts
3928
+ var useNetworkQuality = () => {
3929
+ const { sdk } = useMeetingContext();
3930
+ return useMeetingStore(
3931
+ sdk.state,
3932
+ "networkQuality",
3933
+ (state) => state.networkQuality
3934
+ );
3935
+ };
3936
+
3561
3937
  // src/react/usePubSub.ts
3562
3938
  var import_react8 = require("react");
3563
3939
  function usePubSub(topic, options) {
@@ -3577,10 +3953,12 @@ function usePubSub(topic, options) {
3577
3953
  MeetingProvider,
3578
3954
  MeetingState,
3579
3955
  VideoSDKCore,
3956
+ useLiveTranscript,
3580
3957
  useLocalParticipant,
3581
3958
  useMeeting,
3582
3959
  useMeetingContext,
3583
3960
  useMeetingPreview,
3961
+ useNetworkQuality,
3584
3962
  useParticipants,
3585
3963
  usePubSub,
3586
3964
  useRemoteMedia