UNPKG

@soniox/speech-to-text-web

Version:

Javascript client library for Soniox Speech-to-Text websocket API

231 lines (230 loc) 10.5 kB
var p = Object.defineProperty; var b = (n, e, t) => e in n ? p(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t; var s = (n, e, t) => b(n, typeof e != "symbol" ? e + "" : e, t); const k = ["Init", "Finished", "Error", "Canceled"], g = [ "RequestingMedia", "OpeningWebSocket", "Running", "FinishingProcessing" ], f = ["OpeningWebSocket", "Running", "FinishingProcessing"]; function S(n) { return k.includes(n); } function v(n) { return g.includes(n); } function R(n) { return f.includes(n); } const m = "wss://stt-rt.soniox.com/transcribe-websocket", d = 1e3, w = 120, u = '{ "type": "finalize" }', E = '{ "type": "keepalive" }', M = () => ({ apiKey: "", bufferQueueSize: d }), O = { echoCancellation: !1, noiseSuppression: !1, autoGainControl: !1, channelCount: 1, sampleRate: 44100 }, _ = class _ { /** * SonioxClient connects to the Soniox Speech-to-Text API for real-time speech-to-text transcription and translation. * It provides a simple API for starting and stopping the transcription, as well as handling the transcription results. * * @example * const sonioxClient = new SonioxClient({ * apiKey: '<SONIOX_API_KEY>', * onPartialResult: (result) => { * console.log('partial result', result.text); * }, * }); * sonioxClient.start(); */ constructor(e) { s(this, "_state", "Init"); s(this, "_options"); s(this, "_audioOptions"); s(this, "_websocket"); s(this, "_mediaRecorder"); s(this, "_queuedMessages", []); // Queued data (before websocket is opened) s(this, "_keepAliveInterval", null); s(this, "_hasCallback", (e) => { var t; return this._options[e] != null || ((t = this._audioOptions) == null ? void 0 : t[e]) != null; }); s(this, "_callback", (e, ...t) => { var i, c, o, a; (c = (i = this._options)[e]) == null || c.call(i, ...t), (a = (o = this._audioOptions) == null ? void 0 : o[e]) == null || a.call(o, ...t); }); /** * Start transcription. You can pass options to configure the transcription settings, source and callbacks. */ s(this, "start", async (e) => { if (v(this._state)) throw new Error("SonioxClient is already active"); this._audioOptions = { ...e }; let t; if (e.stream != null) t = e.stream.clone(); else { this._setState("RequestingMedia"); try { t = await navigator.mediaDevices.getUserMedia({ audio: this._audioOptions.audioConstraints ? this._audioOptions.audioConstraints : O }); } catch (i) { this._onError("get_user_media_failed", i == null ? void 0 : i.toString()); } } if (t == null) throw new Error("Failed to create stream"); this._mediaRecorder = new MediaRecorder( t, e.mediaRecorderOptions ? e.mediaRecorderOptions : {} ), this._queuedMessages = [], this._mediaRecorder.addEventListener("dataavailable", this._onMediaRecorderData), this._mediaRecorder.addEventListener("error", this._onMediaRecorderError), this._mediaRecorder.addEventListener("pause", this._onMediaRecorderPause), this._mediaRecorder.addEventListener("stop", this._onMediaRecorderStop), this._mediaRecorder.start(w), this._setState("OpeningWebSocket"), this._websocket = new WebSocket(this._options.webSocketUri ?? m), this._websocket.addEventListener("open", this._onWebSocketOpen), this._websocket.addEventListener("error", this._onWebSocketError), this._websocket.addEventListener("message", this._onWebSocketMessage); }); /** * Stop transcription. Stopping transcription will send stop signal to the API and wait for the final results to be received. * Only after the final results are received, the transcription will be finished. If you want to cancel the transcription immediately, * (for example, on component unmount), you should probably use the `cancel()` method instead. */ s(this, "stop", () => { var e; this._state == "RequestingMedia" || this._state == "OpeningWebSocket" ? (this._closeResources(), this._handleFinished()) : this._state == "Running" && (this._setState("FinishingProcessing"), this._closeSource(), (e = this._websocket) == null || e.send("")); }); /** * Cancel transcription. Cancelling transcription will stop the transcription immediately and close the resources. * For user initiated cancellation, you should probably use the `stop()` method instead. */ s(this, "cancel", () => { S(this._state) || (this._closeResources(), this._setState("Canceled")); }); /** * Trigger finalize. This will finalize all non-final tokens. */ s(this, "finalize", () => { var e; this._state == "RequestingMedia" || this._state == "OpeningWebSocket" ? this._queuedMessages.length < (this._options.bufferQueueSize ?? d) ? this._queuedMessages.push(u) : this._onError("queue_limit_exceeded", "Queue size exceeded before websocket connection was established.") : (this._state == "Running" || this._state == "FinishingProcessing") && ((e = this._websocket) == null || e.send(u)); }); // Media recorder events s(this, "_onMediaRecorderData", async (e) => { var t; if (this._state === "OpeningWebSocket") this._queuedMessages.length < (this._options.bufferQueueSize ?? d) ? this._queuedMessages.push(e.data) : this._onError("queue_limit_exceeded", "Queue size exceeded before websocket connection was established."); else if (this._state === "Running") { const i = await e.data.arrayBuffer(); (t = this._websocket) == null || t.send(i); } }); s(this, "_onMediaRecorderError", (e) => { this._onError("media_recorder_error", e.error ?? "Unknown error"); }); s(this, "_onMediaRecorderPause", (e) => { this.stop(); }); s(this, "_onMediaRecorderStop", (e) => { this.stop(); }); // Websocket events s(this, "_onWebSocketOpen", (e) => { this._onWebSocketOpenAsync(e); }); s(this, "_onWebSocketOpenAsync", async (e) => { var o, a; if (this._state !== "OpeningWebSocket" || this._audioOptions == null) return; const t = this._audioOptions; let i; if (typeof this._options.apiKey == "function") try { i = await this._options.apiKey(); } catch (r) { this._onError("api_key_fetch_failed", r == null ? void 0 : r.toString()); return; } else i = this._options.apiKey; if (this._state !== "OpeningWebSocket") return; const c = { api_key: i, model: t.model, audio_format: t.audioFormat ? t.audioFormat : "auto", sample_rate: t.sampleRate, num_channels: t.numChannels, language_hints: t.languageHints, context: t.context, enable_speaker_diarization: t.enableSpeakerDiarization, enable_language_identification: t.enableLanguageIdentification, enable_endpoint_detection: t.enableEndpointDetection, translation: t.translation, client_reference_id: t.clientReferenceId }; (o = this._websocket) == null || o.send(JSON.stringify(c)); for (const r of this._queuedMessages) (a = this._websocket) == null || a.send(r); if (this._queuedMessages = [], this._setState("Running"), this._callback("onStarted"), this._options.keepAlive) { const r = this._options.keepAliveInterval ?? 5e3; this._keepAliveInterval = setInterval(() => { var l; (this._state === "Running" || this._state === "FinishingProcessing") && ((l = this._websocket) == null || l.send(E)); }, r); } }); s(this, "_onWebSocketError", (e) => { R(this._state) && this._onError("websocket_error", "WebSocket error occurred."); }); // eslint-disable-next-line @typescript-eslint/no-explicit-any s(this, "_onWebSocketMessage", (e) => { if (this._state != "Running" && this._state != "FinishingProcessing" || this._audioOptions == null) return; const t = JSON.parse(e.data); if (t.error_code != null || t.error_message != null) { this._onError("api_error", t.error_message, t.error_code); return; } this._callback("onPartialResult", t), t.finished && this._handleFinished(); }); s(this, "_onError", (e, t, i = void 0) => { if (this._setState("Error"), this._closeResources(), this._hasCallback("onError")) this._callback("onError", e, t ?? "Unknown error", i); else throw new Error(`SonioxClient error: ${e}: ${t ?? "Unknown error"}`); }); s(this, "_closeSource", () => { this._mediaRecorder != null && (this._mediaRecorder.removeEventListener("dataavailable", this._onMediaRecorderData), this._mediaRecorder.removeEventListener("error", this._onMediaRecorderError), this._mediaRecorder.removeEventListener("pause", this._onMediaRecorderPause), this._mediaRecorder.removeEventListener("stop", this._onMediaRecorderStop), this._mediaRecorder.stop(), this._mediaRecorder.stream.getTracks().forEach((e) => e.stop()), this._mediaRecorder = null); }); s(this, "_closeResources", () => { this._queuedMessages = [], this._keepAliveInterval != null && (clearInterval(this._keepAliveInterval), this._keepAliveInterval = null), this._websocket != null && (this._websocket.removeEventListener("open", this._onWebSocketOpen), this._websocket.removeEventListener("error", this._onWebSocketError), this._websocket.removeEventListener("message", this._onWebSocketMessage), this._websocket.close(), this._websocket = null), this._closeSource(); }); if (!_.isSupported) throw "Soniox Speech-to-Text is not supported on this browser."; this._options = { ...M(), ...e }, this._audioOptions = null, this._websocket = null, this._mediaRecorder = null; } _setState(e) { const t = this._state; this._state = e, this._callback("onStateChange", { oldState: t, newState: e }); } get state() { return this._state; } _handleFinished() { this._closeResources(), this._setState("Finished"), this._callback("onFinished"); } }; s(_, "isSupported", !!("WebSocket" in window && navigator.mediaDevices && navigator.mediaDevices.getUserMedia)); let h = _; const y = h; export { y as RecordTranscribe, h as SonioxClient, v as isActiveState, S as isInactiveState, R as isWebSocketState };