UNPKG

@speechmatics/real-time-client

Version:
243 lines (239 loc) 7.36 kB
import { TypedEventTarget } from 'typescript-event-target'; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); class SocketStateChangeEvent extends Event { constructor(socketState) { super("socketStateChange"); this.socketState = socketState; } } class ReceiveMessageEvent extends Event { constructor(data) { super("receiveMessage"); this.data = data; } } class SendMessageEvent extends Event { constructor(data) { super("sendMessage"); this.data = data; } } class RealtimeClient extends TypedEventTarget { constructor(config = {}) { super(); __publicField(this, "url"); __publicField(this, "appId"); __publicField(this, "enableLegacy"); __publicField(this, "timeout"); __publicField(this, "socket"); // Track the last AudioAdded sequence number, used when stopping transcription to avoid missing audio // https://docs.speechmatics.com/rt-api-ref#audioadded __publicField(this, "lastAudioAddedSeqNo", 0); this.url = config.url ?? "wss://eu2.rt.speechmatics.com/v2"; this.appId = config.appId; this.enableLegacy = config.enableLegacy ?? false; this.timeout = config.connectionTimeout ?? 1e4; } get socketState() { if (!this.socket) return void 0; return { [WebSocket.CONNECTING]: "connecting", [WebSocket.OPEN]: "open", [WebSocket.CLOSING]: "closing", [WebSocket.CLOSED]: "closed" }[this.socket.readyState]; } async connect(jwt) { return new Promise((resolve, reject) => { const url = new URL(this.url); url.searchParams.append("jwt", jwt); if (this.appId) { url.searchParams.append("sm-app", this.appId); } if (this.enableLegacy) { url.searchParams.append("sm-enable-legacy-rt", "true"); } this.socket = new WebSocket(url.toString()); this.dispatchTypedEvent( "socketStateChange", new SocketStateChangeEvent(this.socketState) ); this.socket.addEventListener( "open", () => { resolve(); }, { once: true } ); this.socket.addEventListener("error", (error) => { this.dispatchTypedEvent( "socketStateChange", new SocketStateChangeEvent(this.socketState) ); reject(error); }); this.socket.addEventListener("close", () => { this.dispatchTypedEvent( "socketStateChange", new SocketStateChangeEvent(this.socketState) ); }); this.socket.addEventListener("message", (socketMessage) => { const data = JSON.parse(socketMessage.data); if (!dataIsRealtimeTranscriptionMessage(data)) { console.warn( "message does not look like a valid message: ", JSON.stringify(data) ); return; } if (data.message === "AudioAdded") { this.lastAudioAddedSeqNo = data.seq_no; } this.dispatchTypedEvent( "receiveMessage", new ReceiveMessageEvent(data) ); }); }); } sendMessage(message) { if (!this.socket) { throw new SpeechmaticsRealtimeError("Client socket not initialized"); } this.socket.send(JSON.stringify(message)); this.dispatchTypedEvent("sendMessage", new SendMessageEvent(message)); } sendAudio(data) { if (!this.socket || this.socket.readyState !== this.socket.OPEN) { throw new SpeechmaticsRealtimeError("Socket not ready to receive audio"); } this.socket.send(data); } async getSpeakers(options = {}) { this.sendMessage({ message: "GetSpeakers", final: options.final }); const waitForSpeakers = new Promise((resolve, reject) => { this.addEventListener("receiveMessage", ({ data }) => { if (data.message === "SpeakersResult") { resolve(data); } else if (data.message === "Error") { reject(new Error(data.type)); } }); this.addEventListener("socketStateChange", (state) => { state.socketState === "closed" && reject(new Error("Socket closed")); }); }); if (options.timeout) { return Promise.race([ waitForSpeakers, rejectAfter(options.timeout, "SpeakersResult") ]); } return waitForSpeakers; } async start(jwt, config) { await this.connect(jwt); const waitForRecognitionStarted = new Promise( (resolve, reject) => { this.addEventListener("receiveMessage", ({ data }) => { if (data.message === "RecognitionStarted") { resolve(data); } else if (data.message === "Error") { reject(new Error(data.type)); } }); const startRecognitionMessage = { audio_format: defaultAudioFormat, ...config, message: "StartRecognition" }; this.sendMessage(startRecognitionMessage); } ); return Promise.race([ waitForRecognitionStarted, rejectAfter(this.timeout, "RecognitionStarted") ]); } /** Sends an `"EndOfStream"` message, resolving if acknowledged by an `"EndOfTranscript"` from server, rejecting if not received */ async stopRecognition({ noTimeout } = {}) { const waitForEndOfTranscript = new Promise((resolve) => { this.addEventListener("receiveMessage", ({ data }) => { if (data.message === "EndOfTranscript") { this.socket?.close(); resolve(); } }); this.sendMessage({ message: "EndOfStream", last_seq_no: this.lastAudioAddedSeqNo }); }); if (noTimeout) { return; } return Promise.race([ waitForEndOfTranscript, rejectAfter(this.timeout, "EndOfTranscript") ]); } setRecognitionConfig(config) { this.sendMessage({ message: "SetRecognitionConfig", transcription_config: config }); } forceEndOfUtterance(channel) { this.sendMessage({ message: "ForceEndOfUtterance", channel }); } } function dataIsRealtimeTranscriptionMessage(data) { if (typeof data !== "object" || data === null) { return false; } if (!("message" in data)) { return false; } if (typeof data.message !== "string") { return false; } return true; } const defaultAudioFormat = { type: "file" }; class SpeechmaticsRealtimeError extends Error { constructor(message, options) { super(message, options); this.name = "SpeechmaticsRealtimeError"; } } function rejectAfter(timeoutMs, key) { return new Promise((_, reject) => { setTimeout( () => reject( new SpeechmaticsRealtimeError( `Timed out after ${timeoutMs}ms waiting for ${key}` ) ), timeoutMs ); }); } async function getFeatures(region = "eu2") { const resp = await fetch( `https://${region}.rt.speechmatics.com/v1/discovery/features` ); return resp.json(); } export { RealtimeClient, ReceiveMessageEvent, SendMessageEvent, SocketStateChangeEvent, SpeechmaticsRealtimeError, getFeatures }; //# sourceMappingURL=index.browser.js.map