@defcomm/meetingsdk 0.5.6 → 0.5.8

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
@@ -440,11 +440,7 @@ var SFUClient = class {
440
440
  this.trackToSource = /* @__PURE__ */ new Map();
441
441
  this.localTrackIds = /* @__PURE__ */ new Map();
442
442
  this.subscriberStatsTimer = null;
443
- this.liveTranscriptionActive = false;
444
- this.liveTranscriptionSequence = 0;
445
- this.liveTranscriptionStartedAt = 0;
446
- this.liveTranscriptionRecorder = null;
447
- this.liveTranscriptionLoopPromise = null;
443
+ this.liveTranscriptionSeen = false;
448
444
  this.networkProfile = null;
449
445
  this.heartbeatTimer = null;
450
446
  this.apiBase = options.apiBase || "https://api.example.com/api";
@@ -1112,7 +1108,10 @@ var SFUClient = class {
1112
1108
  end_ms: payload.end_ms == null ? null : Number(payload.end_ms),
1113
1109
  timestamp: String(payload.timestamp ?? (/* @__PURE__ */ new Date()).toISOString())
1114
1110
  };
1115
- if (transcript.text.trim()) this.onLiveTranscript(transcript);
1111
+ if (transcript.text.trim()) {
1112
+ this.liveTranscriptionSeen = true;
1113
+ this.onLiveTranscript(transcript);
1114
+ }
1116
1115
  break;
1117
1116
  }
1118
1117
  case "network_quality": {
@@ -1793,119 +1792,63 @@ var SFUClient = class {
1793
1792
  }
1794
1793
  });
1795
1794
  }
1796
- getLiveTranscriptionMimeType() {
1797
- if (typeof MediaRecorder === "undefined") {
1798
- throw new Error("MediaRecorder is not supported by this browser");
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");
1799
1807
  }
1800
- const candidates = [
1801
- "audio/webm;codecs=opus",
1802
- "audio/webm",
1803
- "audio/ogg;codecs=opus"
1804
- ];
1805
- return candidates.find((type) => MediaRecorder.isTypeSupported(type)) || "";
1806
- }
1807
- async startLiveTranscription(audioTrack, speakerName) {
1808
- if (this.liveTranscriptionActive) return;
1809
- if (!this.room) throw new Error("Cannot start live transcription without a room");
1810
- if (!this.authToken) throw new Error("Cannot start live transcription without an authenticated token");
1811
- if (!audioTrack || audioTrack.kind !== "audio") {
1812
- throw new Error("A live microphone audio track is required");
1813
- }
1814
- const mimeType = this.getLiveTranscriptionMimeType();
1815
- this.liveTranscriptionActive = true;
1816
- this.liveTranscriptionSequence = 0;
1817
- this.liveTranscriptionStartedAt = Date.now();
1818
- const stream = new MediaStream([audioTrack]);
1819
- this.liveTranscriptionLoopPromise = this.runLiveTranscriptionLoop(
1820
- stream,
1821
- mimeType
1822
- ).finally(() => {
1823
- this.liveTranscriptionRecorder = null;
1824
- this.liveTranscriptionLoopPromise = null;
1825
- });
1826
- void speakerName;
1827
- await Promise.resolve();
1808
+ console.info("[Transcript] server-authoritative live transcription is managed by the SFU");
1828
1809
  }
1829
- async captureLiveChunk(stream, mimeType) {
1830
- return new Promise((resolve, reject) => {
1831
- const chunks = [];
1832
- const recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
1833
- this.liveTranscriptionRecorder = recorder;
1834
- const timeout = setTimeout(() => {
1835
- if (recorder.state !== "inactive") recorder.stop();
1836
- }, 4500);
1837
- recorder.ondataavailable = (event) => {
1838
- if (event.data.size > 0) chunks.push(event.data);
1839
- };
1840
- recorder.onerror = () => {
1841
- clearTimeout(timeout);
1842
- reject(new Error("Live transcription recorder failed"));
1843
- };
1844
- recorder.onstop = () => {
1845
- clearTimeout(timeout);
1846
- this.liveTranscriptionRecorder = null;
1847
- resolve(
1848
- chunks.length ? new Blob(chunks, { type: recorder.mimeType || mimeType || "audio/webm" }) : null
1849
- );
1850
- };
1851
- recorder.start();
1852
- });
1810
+ stopLiveTranscription() {
1811
+ console.info("[Transcript] server-authoritative live transcription remains managed by the SFU");
1853
1812
  }
1854
- async runLiveTranscriptionLoop(stream, mimeType) {
1855
- while (this.liveTranscriptionActive && stream.getAudioTracks()[0]?.readyState === "live") {
1856
- try {
1857
- const blob = await this.captureLiveChunk(stream, mimeType);
1858
- if (!this.liveTranscriptionActive) break;
1859
- if (!blob || blob.size === 0) continue;
1860
- const sequence = this.liveTranscriptionSequence++;
1861
- const offsetMs = Math.max(0, Date.now() - this.liveTranscriptionStartedAt);
1862
- await this.uploadLiveTranscriptionChunk(blob, sequence, offsetMs, mimeType);
1863
- } catch (error) {
1864
- this.onError(error);
1865
- await new Promise((resolve) => setTimeout(resolve, 750));
1866
- }
1867
- }
1813
+ isLiveTranscriptionActive() {
1814
+ return this.liveTranscriptionSeen;
1868
1815
  }
1869
- async uploadLiveTranscriptionChunk(blob, sequence, offsetMs, mimeType) {
1870
- if (!this.room || !this.authToken) return;
1871
- const form = new FormData();
1872
- form.append("sequence", String(sequence));
1873
- form.append("offset_ms", String(offsetMs));
1874
- const extension = mimeType.includes("ogg") || blob.type.includes("ogg") ? "ogg" : "webm";
1875
- form.append("audio", blob, `live-${sequence}.${extension}`);
1816
+ async getTranscript(roomCode, sessionId) {
1817
+ if (!this.authToken) throw new Error("Authentication token is required");
1818
+ const query = sessionId ? `?session_id=${encodeURIComponent(sessionId)}` : "";
1876
1819
  const response = await fetch(
1877
- `${this.apiBase}/rooms/${encodeURIComponent(this.room)}/live-transcription`,
1820
+ `${this.apiBase}/rooms/${encodeURIComponent(roomCode)}/transcript${query}`,
1878
1821
  {
1879
- method: "POST",
1822
+ method: "GET",
1880
1823
  headers: {
1881
1824
  Authorization: `Bearer ${this.authToken}`,
1882
1825
  Accept: "application/json"
1883
- },
1884
- body: form
1826
+ }
1885
1827
  }
1886
1828
  );
1829
+ const text = await response.text();
1887
1830
  if (!response.ok) {
1888
- let detail = `Live transcription request failed: ${response.status}`;
1889
- try {
1890
- const body = await response.json();
1891
- detail = body?.message || body?.detail || detail;
1892
- } catch {
1893
- }
1894
- throw new Error(detail);
1831
+ throw new Error(`Transcript request failed: ${response.status} ${text}`);
1895
1832
  }
1833
+ return JSON.parse(text);
1896
1834
  }
1897
- stopLiveTranscription() {
1898
- this.liveTranscriptionActive = false;
1899
- try {
1900
- if (this.liveTranscriptionRecorder?.state !== "inactive") {
1901
- this.liveTranscriptionRecorder?.stop();
1835
+ async getMeetingSummary(roomCode, sessionId) {
1836
+ if (!this.authToken) throw new Error("Authentication token is required");
1837
+ const response = await fetch(
1838
+ `${this.apiBase}/rooms/${encodeURIComponent(roomCode)}/transcript/${encodeURIComponent(sessionId)}/summary`,
1839
+ {
1840
+ method: "GET",
1841
+ headers: {
1842
+ Authorization: `Bearer ${this.authToken}`,
1843
+ Accept: "application/json"
1844
+ }
1902
1845
  }
1903
- } catch {
1846
+ );
1847
+ const text = await response.text();
1848
+ if (!response.ok) {
1849
+ throw new Error(`Meeting summary request failed: ${response.status} ${text}`);
1904
1850
  }
1905
- this.liveTranscriptionRecorder = null;
1906
- }
1907
- isLiveTranscriptionActive() {
1908
- return this.liveTranscriptionActive;
1851
+ return JSON.parse(text);
1909
1852
  }
1910
1853
  async applyNetworkRecommendation(action) {
1911
1854
  if (!this.videoSender || this.networkProfile === action) return;
@@ -2348,6 +2291,12 @@ var VideoSDKCore = class {
2348
2291
  async deleteRoom(roomCode) {
2349
2292
  return this.sfuClient.deleteRoom(roomCode);
2350
2293
  }
2294
+ async getTranscript(roomCode, sessionId) {
2295
+ return this.sfuClient.getTranscript(roomCode, sessionId);
2296
+ }
2297
+ async getMeetingSummary(roomCode, sessionId) {
2298
+ return this.sfuClient.getMeetingSummary(roomCode, sessionId);
2299
+ }
2351
2300
  // JOIN
2352
2301
  async joinMeeting(config) {
2353
2302
  const { roomId, name, audioMuted = false, videoMuted = false } = config;
@@ -3132,13 +3081,10 @@ var VideoSDKCore = class {
3132
3081
  throw new Error("SFU client is not connected");
3133
3082
  }
3134
3083
  const audioTrack = this.localStream?.getAudioTracks()[0];
3135
- if (!audioTrack) {
3084
+ if (!audioTrack || audioTrack.readyState !== "live") {
3136
3085
  throw new Error("Microphone audio track is not available");
3137
3086
  }
3138
- await this.sfuClient.startLiveTranscription(
3139
- audioTrack,
3140
- this.participantName
3141
- );
3087
+ await this.sfuClient.startLiveTranscription();
3142
3088
  }
3143
3089
  stopLiveTranscription() {
3144
3090
  this.sfuClient?.stopLiveTranscription();
@@ -3404,6 +3350,8 @@ var MeetingProvider = ({
3404
3350
  createRoom: sdk.createRoom.bind(sdk),
3405
3351
  getRoom: sdk.getRoom.bind(sdk),
3406
3352
  deleteRoom: sdk.deleteRoom.bind(sdk),
3353
+ getTranscript: sdk.getTranscript.bind(sdk),
3354
+ getMeetingSummary: sdk.getMeetingSummary.bind(sdk),
3407
3355
  toggleMic: sdk.toggleMic.bind(sdk),
3408
3356
  toggleCam: sdk.toggleCam.bind(sdk),
3409
3357
  startScreenShare: sdk.startScreenShare.bind(sdk),