Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@speechly/browser-client

Package Overview
Dependencies
Maintainers
6
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@speechly/browser-client - npm Package Compare versions

Comparing version 2.0.1 to 2.1.0-beta.0

core/types/audioprocessing/AudioProcessor.d.ts

4

core/speechly.umd.min.js

@@ -1,4 +0,4 @@

!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Speechly={})}(this,(function(t){"use strict";const e=new Error("Current device does not support microphone API"),n=new Error("AppId changed without project login"),i=16e3;class s{constructor(t,e){this.isFinalized=!1,this.words=[],this.entities=new Map,this.intent={intent:"",isFinal:!1},this.contextId=t,this.id=e}toSegment(){let t=0;const e=new Array(this.entities.size);return this.entities.forEach((n=>{e[t]=n,t++})),{id:this.id,contextId:this.contextId,isFinal:this.isFinalized,words:this.words,entities:e,intent:this.intent}}toString(){const t=this.toSegment(),e=t.words.filter((t=>t.value)).map((t=>({value:t.value,index:t.index}))),n=Object.assign(Object.assign({},t),{words:e});return JSON.stringify(n,null,2)}updateTranscript(t){return t.forEach((t=>{this.isFinalized&&!t.isFinal||(this.words[t.index]=t)})),this}updateEntities(t){return t.forEach((t=>{this.isFinalized&&!t.isFinal||this.entities.set(function(t){return`${t.startPosition.toString()}:${t.endPosition.toString()}`}(t),t)})),this}updateIntent(t){return this.isFinalized&&!t.isFinal||(this.intent=t),this}finalize(){return this.entities.forEach(((t,e)=>{t.isFinal||this.entities.delete(e)})),this.words=this.words.filter((t=>t.isFinal)),this.intent.isFinal||(this.intent.intent="",this.intent.isFinal=!0),this.isFinalized=!0,this}}function o(t,e,n,i){return new(n||(n=Promise))((function(s,o){function r(t){try{c(i.next(t))}catch(t){o(t)}}function a(t){try{c(i.throw(t))}catch(t){o(t)}}function c(t){var e;t.done?s(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(r,a)}c((i=i.apply(t,e||[])).next())}))}const r=new Error("Microphone is not initialized"),a=new Error("Microphone is already initialized"),c=new Error("Microphone consent is not given");var d;t.WebsocketResponseType=void 0,(d=t.WebsocketResponseType||(t.WebsocketResponseType={})).Opened="WEBSOCKET_OPEN",d.Closed="WEBSOCKET_CLOSED",d.SourceSampleRateSetSuccess="SOURCE_SAMPLE_RATE_SET_SUCCESS",d.Started="started",d.Stopped="stopped",d.SegmentEnd="segment_end",d.Transcript="transcript",d.Entity="entity",d.Intent="intent",d.TentativeTranscript="tentative_transcript",d.TentativeEntities="tentative_entities",d.TentativeIntent="tentative_intent";const l=new Error("Current device does not support storage API"),h=new Error("Requested key was not present in storage");var u;t.DecoderState=void 0,(u=t.DecoderState||(t.DecoderState={}))[u.Failed=0]="Failed",u[u.Disconnected=1]="Disconnected",u[u.Connected=2]="Connected",u[u.Active=3]="Active";class p{constructor(){this.stateChangeCbs=[],this.transcriptCbs=[],this.entityCbs=[],this.intentCbs=[],this.segmentChangeCbs=[],this.tentativeTranscriptCbs=[],this.tentativeEntityCbs=[],this.tentativeIntentCbs=[],this.contextStartedCbs=[],this.contextStoppedCbs=[]}}function f(t){var e;return null!==(e=S.get(t))&&void 0!==e?e:"unknown"}const S=new Map([[t.DecoderState.Failed,"Failed"],[t.DecoderState.Disconnected,"Disconnected"],[t.DecoderState.Connected,"Connected"],[t.DecoderState.Active,"Active"]]);var v,b=new Uint8Array(16);function C(){if(!v&&!(v="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return v(b)}var g=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;function m(t){return"string"==typeof t&&g.test(t)}for(var w=[],y=0;y<256;++y)w.push((y+256).toString(16).substr(1));function k(t,e,n){var i=(t=t||{}).random||(t.rng||C)();if(i[6]=15&i[6]|64,i[8]=63&i[8]|128,e){n=n||0;for(var s=0;s<16;++s)e[n+s]=i[s];return e}return function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=(w[t[e+0]]+w[t[e+1]]+w[t[e+2]]+w[t[e+3]]+"-"+w[t[e+4]]+w[t[e+5]]+"-"+w[t[e+6]]+w[t[e+7]]+"-"+w[t[e+8]]+w[t[e+9]]+"-"+w[t[e+10]]+w[t[e+11]]+w[t[e+12]]+w[t[e+13]]+w[t[e+14]]+w[t[e+15]]).toLowerCase();if(!m(n))throw TypeError("Stringified UUID is invalid");return n}(i)}var E="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},A={exports:{}};
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Speechly={})}(this,(function(t){"use strict";const e=new Error("Current device does not support microphone API"),i=new Error("AppId changed without project login"),n=16e3;class s{constructor(t,e){this.isFinalized=!1,this.words=[],this.entities=new Map,this.intent={intent:"",isFinal:!1},this.contextId=t,this.id=e}toSegment(){let t=0;const e=new Array(this.entities.size);return this.entities.forEach((i=>{e[t]=i,t++})),{id:this.id,contextId:this.contextId,isFinal:this.isFinalized,words:this.words,entities:e,intent:this.intent}}toString(){const t=this.toSegment(),e=t.words.filter((t=>t.value)).map((t=>({value:t.value,index:t.index}))),i=Object.assign(Object.assign({},t),{words:e});return JSON.stringify(i,null,2)}updateTranscript(t){return t.forEach((t=>{this.isFinalized&&!t.isFinal||(this.words[t.index]=t)})),this}updateEntities(t){return t.forEach((t=>{this.isFinalized&&!t.isFinal||this.entities.set(function(t){return`${t.startPosition.toString()}:${t.endPosition.toString()}`}(t),t)})),this}updateIntent(t){return this.isFinalized&&!t.isFinal||(this.intent=t),this}finalize(){return this.entities.forEach(((t,e)=>{t.isFinal||this.entities.delete(e)})),this.words=this.words.filter((t=>t.isFinal)),this.intent.isFinal||(this.intent.intent="",this.intent.isFinal=!0),this.isFinalized=!0,this}}function o(t,e,i,n){return new(i||(i=Promise))((function(s,o){function a(t){try{c(n.next(t))}catch(t){o(t)}}function d(t){try{c(n.throw(t))}catch(t){o(t)}}function c(t){var e;t.done?s(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(a,d)}c((n=n.apply(t,e||[])).next())}))}const a=new Error("Microphone is not initialized"),d=new Error("Microphone is already initialized"),c=new Error("Microphone consent is not given");var l,r,h;t.WebsocketResponseType=void 0,(l=t.WebsocketResponseType||(t.WebsocketResponseType={})).Started="started",l.Stopped="stopped",l.SegmentEnd="segment_end",l.Transcript="transcript",l.Entity="entity",l.Intent="intent",l.TentativeTranscript="tentative_transcript",l.TentativeEntities="tentative_entities",l.TentativeIntent="tentative_intent",t.WorkerSignal=void 0,(r=t.WorkerSignal||(t.WorkerSignal={})).Opened="WEBSOCKET_OPEN",r.Closed="WEBSOCKET_CLOSED",r.AudioProcessorReady="SOURCE_SAMPLE_RATE_SET_SUCCESS",r.VadSignalHigh="VadSignalHigh",r.VadSignalLow="VadSignalLow",t.ControllerSignal=void 0,(h=t.ControllerSignal||(t.ControllerSignal={})).connect="connect",h.initAudioProcessor="initAudioProcessor",h.adjustAudioProcessor="adjustAudioProcessor",h.SET_SHARED_ARRAY_BUFFERS="SET_SHARED_ARRAY_BUFFERS",h.CLOSE="CLOSE",h.START_CONTEXT="START_CONTEXT",h.SWITCH_CONTEXT="SWITCH_CONTEXT",h.STOP_CONTEXT="STOP_CONTEXT",h.AUDIO="AUDIO",h.startStream="startStream",h.stopStream="stopStream";const u=new Error("Current device does not support storage API"),p=new Error("Requested key was not present in storage"),b={enabled:!1,controlListening:!0,signalToNoiseDb:3,noiseGateDb:-24,noiseLearnHalftimeMillis:400,signalSearchFrames:5,signalActivation:.7,signalRelease:.2,signalSustainMillis:3e3};var m;t.DecoderState=void 0,(m=t.DecoderState||(t.DecoderState={}))[m.Failed=0]="Failed",m[m.Disconnected=1]="Disconnected",m[m.Connected=2]="Connected",m[m.Active=3]="Active";class Z{constructor(){this.stateChangeCbs=[],this.transcriptCbs=[],this.entityCbs=[],this.intentCbs=[],this.segmentChangeCbs=[],this.tentativeTranscriptCbs=[],this.tentativeEntityCbs=[],this.tentativeIntentCbs=[],this.contextStartedCbs=[],this.contextStoppedCbs=[],this.onVadStateChange=[]}}function y(t){var e;return null!==(e=G.get(t))&&void 0!==e?e:"unknown"}const G=new Map([[t.DecoderState.Failed,"Failed"],[t.DecoderState.Disconnected,"Disconnected"],[t.DecoderState.Connected,"Connected"],[t.DecoderState.Active,"Active"]]);var v,W=new Uint8Array(16);function X(){if(!v&&!(v="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return v(W)}var R=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;function V(t){return"string"==typeof t&&R.test(t)}for(var S=[],C=0;C<256;++C)S.push((C+256).toString(16).substr(1));function g(t,e,i){var n=(t=t||{}).random||(t.rng||X)();if(n[6]=15&n[6]|64,n[8]=63&n[8]|128,e){i=i||0;for(var s=0;s<16;++s)e[i+s]=n[s];return e}return function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,i=(S[t[e+0]]+S[t[e+1]]+S[t[e+2]]+S[t[e+3]]+"-"+S[t[e+4]]+S[t[e+5]]+"-"+S[t[e+6]]+S[t[e+7]]+"-"+S[t[e+8]]+S[t[e+9]]+"-"+S[t[e+10]]+S[t[e+11]]+S[t[e+12]]+S[t[e+13]]+S[t[e+14]]+S[t[e+15]]).toLowerCase();if(!V(i))throw TypeError("Stringified UUID is invalid");return i}(n)}var N="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},Y={exports:{}};
/*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */
!function(t,e){!function(n){var i=e,s=t&&t.exports==i&&t,o="object"==typeof E&&E;o.global!==o&&o.window!==o||(n=o);var r=function(t){this.message=t};(r.prototype=new Error).name="InvalidCharacterError";var a=function(t){throw new r(t)},c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",d=/[\t\n\f\r ]/g,l={encode:function(t){t=String(t),/[^\0-\xFF]/.test(t)&&a("The string to be encoded contains characters outside of the Latin1 range.");for(var e,n,i,s,o=t.length%3,r="",d=-1,l=t.length-o;++d<l;)e=t.charCodeAt(d)<<16,n=t.charCodeAt(++d)<<8,i=t.charCodeAt(++d),r+=c.charAt((s=e+n+i)>>18&63)+c.charAt(s>>12&63)+c.charAt(s>>6&63)+c.charAt(63&s);return 2==o?(e=t.charCodeAt(d)<<8,n=t.charCodeAt(++d),r+=c.charAt((s=e+n)>>10)+c.charAt(s>>4&63)+c.charAt(s<<2&63)+"="):1==o&&(s=t.charCodeAt(d),r+=c.charAt(s>>2)+c.charAt(s<<4&63)+"=="),r},decode:function(t){var e=(t=String(t).replace(d,"")).length;e%4==0&&(e=(t=t.replace(/==?$/,"")).length),(e%4==1||/[^+a-zA-Z0-9/]/.test(t))&&a("Invalid character: the string to be decoded is not correctly encoded.");for(var n,i,s=0,o="",r=-1;++r<e;)i=c.indexOf(t.charAt(r)),n=s%4?64*n+i:i,s++%4&&(o+=String.fromCharCode(255&n>>(-2*s&6)));return o},version:"0.1.0"};if(i&&!i.nodeType)if(s)s.exports=l;else for(var h in l)l.hasOwnProperty(h)&&(i[h]=l[h]);else n.base64=l}(E)}(A,A.exports);function R(t,e,n,i,s=Date.now){const o=function(t){const e=t.split(".")[1];let n;try{n=JSON.parse(A.exports.decode(e))}catch(t){throw new Error("Error decoding Speechly token!")}return{appId:n.appId,projectId:n.projectId,deviceId:n.deviceId,configId:n.configId,scopes:n.scope.split(" "),issuer:n.iss,audience:n.aud,expiresAtMs:1e3*n.exp}}(t);return!(o.expiresAtMs-s()<36e5)&&(o.appId===n&&o.projectId===e&&o.deviceId===i)}class x{constructor(){this.startCbs=[],this.stopCbs=[],this.onResponseCb=()=>{},this.onCloseCb=()=>{},this.onWebsocketMessage=e=>{const n=e.data;switch(n.type){case t.WebsocketResponseType.Opened:null!=this.resolveInitialization&&this.resolveInitialization();break;case t.WebsocketResponseType.Closed:this.onCloseCb({code:e.data.code,reason:e.data.reason,wasClean:e.data.wasClean});break;case t.WebsocketResponseType.SourceSampleRateSetSuccess:null!=this.resolveSourceSampleRateSet&&this.resolveSourceSampleRateSet();break;case t.WebsocketResponseType.Started:this.startCbs.forEach((t=>{try{t(void 0,n.audio_context)}catch(t){console.error('[SpeechlyClient] Error while invoking "onStart" callback:',t)}})),this.startCbs.length=0;break;case t.WebsocketResponseType.Stopped:this.stopCbs.forEach((t=>{try{t(void 0,n.audio_context)}catch(t){console.error('[SpeechlyClient] Error while invoking "onStop" callback:',t)}})),this.stopCbs.length=0;break;default:this.onResponseCb(n)}};const e=new Blob(["/**\n * Known WebSocket response types.\n * @public\n */\nvar WebsocketResponseType;\n(function (WebsocketResponseType) {\n WebsocketResponseType[\"Opened\"] = \"WEBSOCKET_OPEN\";\n WebsocketResponseType[\"SourceSampleRateSetSuccess\"] = \"SOURCE_SAMPLE_RATE_SET_SUCCESS\";\n WebsocketResponseType[\"Started\"] = \"started\";\n WebsocketResponseType[\"Stopped\"] = \"stopped\";\n})(WebsocketResponseType || (WebsocketResponseType = {}));\nvar CONTROL = {\n WRITE_INDEX: 0,\n FRAMES_AVAILABLE: 1,\n LOCK: 2\n};\nvar WebsocketClient = /** @class */ (function () {\n function WebsocketClient(ctx) {\n var _this = this;\n this.isContextStarted = false;\n this.isStartContextConfirmed = false;\n this.shouldResendLastFramesSent = false;\n this.buffer = new Float32Array(0);\n this.lastFramesSent = new Int16Array(0); // to re-send after switch context\n this.debug = false;\n this.initialized = false;\n // WebSocket's close handler, called e.g. when\n // - normal close (code 1000)\n // - network unreachable or unable to (re)connect (code 1006)\n // List of CloseEvent.code values: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code\n this.onWebsocketClose = function (event) {\n if (_this.debug) {\n console.log('[WebSocketClient]', 'onWebsocketClose');\n }\n _this.websocket.removeEventListener('open', _this.onWebsocketOpen);\n _this.websocket.removeEventListener('message', _this.onWebsocketMessage);\n _this.websocket.removeEventListener('error', _this.onWebsocketError);\n _this.websocket.removeEventListener('close', _this.onWebsocketClose);\n _this.websocket = undefined;\n _this.workerCtx.postMessage({\n type: 'WEBSOCKET_CLOSED',\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean\n });\n };\n this.onWebsocketOpen = function (_event) {\n if (_this.debug) {\n console.log('[WebSocketClient]', 'websocket opened');\n }\n if (_this.isContextStarted && !_this.isStartContextConfirmed) {\n _this.send(_this.outbox);\n }\n _this.workerCtx.postMessage({ type: 'WEBSOCKET_OPEN' });\n };\n this.onWebsocketError = function (_event) {\n if (_this.debug) {\n console.log('[WebSocketClient]', 'websocket error');\n }\n };\n this.onWebsocketMessage = function (event) {\n var response;\n try {\n response = JSON.parse(event.data);\n }\n catch (e) {\n console.error('[WebSocketClient]', 'error parsing response from the server:', e);\n return;\n }\n if (response.type === WebsocketResponseType.Started) {\n _this.isStartContextConfirmed = true;\n if (_this.shouldResendLastFramesSent) {\n _this.resendLastFrames();\n _this.shouldResendLastFramesSent = false;\n }\n }\n _this.workerCtx.postMessage(response);\n };\n this.workerCtx = ctx;\n }\n WebsocketClient.prototype.init = function (apiUrl, authToken, targetSampleRate, debug) {\n this.debug = debug;\n if (this.debug) {\n console.log('[WebSocketClient]', 'initialize worker');\n }\n this.apiUrl = apiUrl;\n this.authToken = authToken;\n this.targetSampleRate = targetSampleRate;\n this.initialized = true;\n this.isContextStarted = false;\n this.connect(0);\n };\n WebsocketClient.prototype.setSourceSampleRate = function (sourceSampleRate) {\n this.sourceSampleRate = sourceSampleRate;\n this.resampleRatio = this.sourceSampleRate / this.targetSampleRate;\n if (this.debug) {\n console.log('[WebSocketClient]', 'resampleRatio', this.resampleRatio);\n }\n if (this.resampleRatio > 1) {\n this.filter = generateFilter(this.sourceSampleRate, this.targetSampleRate, 127);\n }\n this.workerCtx.postMessage({ type: 'SOURCE_SAMPLE_RATE_SET_SUCCESS' });\n if (isNaN(this.resampleRatio)) {\n throw Error(\"resampleRatio is NaN source rate is \".concat(this.sourceSampleRate, \" and target rate is \").concat(this.targetSampleRate));\n }\n };\n WebsocketClient.prototype.setSharedArrayBuffers = function (controlSAB, dataSAB) {\n this.controlSAB = new Int32Array(controlSAB);\n this.dataSAB = new Float32Array(dataSAB);\n var audioHandleInterval = this.dataSAB.length / 32; // ms\n if (this.debug) {\n console.log('[WebSocketClient]', 'Audio handle interval', audioHandleInterval, 'ms');\n }\n setInterval(this.sendAudioFromSAB.bind(this), audioHandleInterval);\n };\n WebsocketClient.prototype.connect = function (timeout) {\n if (timeout === void 0) { timeout = 1000; }\n if (this.debug) {\n console.log('[WebSocketClient]', 'connect in ', timeout / 1000, 'sec');\n }\n setTimeout(this.initializeWebsocket.bind(this), timeout);\n };\n WebsocketClient.prototype.initializeWebsocket = function () {\n if (this.debug) {\n console.log('[WebSocketClient]', 'connecting to ', this.apiUrl);\n }\n this.websocket = new WebSocket(this.apiUrl, this.authToken);\n this.websocket.addEventListener('open', this.onWebsocketOpen);\n this.websocket.addEventListener('message', this.onWebsocketMessage);\n this.websocket.addEventListener('error', this.onWebsocketError);\n this.websocket.addEventListener('close', this.onWebsocketClose);\n };\n WebsocketClient.prototype.isOpen = function () {\n return this.websocket !== undefined && this.websocket.readyState === this.websocket.OPEN;\n };\n WebsocketClient.prototype.resendLastFrames = function () {\n if (this.lastFramesSent.length > 0) {\n this.send(this.lastFramesSent);\n this.lastFramesSent = new Int16Array(0);\n }\n };\n WebsocketClient.prototype.sendAudio = function (audioChunk) {\n if (!this.isContextStarted) {\n return;\n }\n if (audioChunk.length > 0) {\n if (this.resampleRatio > 1) {\n // Downsampling\n this.send(this.downsample(audioChunk));\n }\n else {\n this.send(float32ToInt16(audioChunk));\n }\n }\n };\n WebsocketClient.prototype.sendAudioFromSAB = function () {\n if (!this.isContextStarted) {\n this.controlSAB[CONTROL.FRAMES_AVAILABLE] = 0;\n this.controlSAB[CONTROL.WRITE_INDEX] = 0;\n return;\n }\n if (this.controlSAB == undefined) {\n return;\n }\n var framesAvailable = this.controlSAB[CONTROL.FRAMES_AVAILABLE];\n var lock = this.controlSAB[CONTROL.LOCK];\n if (lock == 0 && framesAvailable > 0) {\n var data = this.dataSAB.subarray(0, framesAvailable);\n this.controlSAB[CONTROL.FRAMES_AVAILABLE] = 0;\n this.controlSAB[CONTROL.WRITE_INDEX] = 0;\n if (data.length > 0) {\n var frames_1;\n if (this.resampleRatio > 1) {\n frames_1 = this.downsample(data);\n }\n else {\n frames_1 = float32ToInt16(data);\n }\n this.send(frames_1);\n // 16000 per second, 1000 in 100 ms\n // save last 250 ms\n if (this.lastFramesSent.length > 1024 * 4) {\n this.lastFramesSent = frames_1;\n }\n else {\n var concat = new Int16Array(this.lastFramesSent.length + frames_1.length);\n concat.set(this.lastFramesSent);\n concat.set(frames_1, this.lastFramesSent.length);\n this.lastFramesSent = concat;\n }\n }\n }\n };\n WebsocketClient.prototype.startContext = function (appId) {\n if (this.isContextStarted) {\n console.error('[WebSocketClient]', \"can't start context: active context exists\");\n return;\n }\n this.isContextStarted = true;\n this.isStartContextConfirmed = false;\n if (appId !== undefined) {\n this.outbox = JSON.stringify({ event: 'start', appId: appId });\n }\n else {\n this.outbox = JSON.stringify({ event: 'start' });\n }\n this.send(this.outbox);\n };\n WebsocketClient.prototype.stopContext = function () {\n if (!this.websocket) {\n throw Error('WebSocket is undefined');\n }\n if (!this.isContextStarted) {\n console.error('[WebSocketClient]', \"can't stop context: no active context\");\n return;\n }\n this.isContextStarted = false;\n this.isStartContextConfirmed = false;\n var StopEventJSON = JSON.stringify({ event: 'stop' });\n this.send(StopEventJSON);\n };\n WebsocketClient.prototype.switchContext = function (newAppId) {\n if (!this.websocket) {\n throw Error('WebSocket is undefined');\n }\n if (!this.isContextStarted) {\n console.error('[WebSocketClient]', \"can't switch context: no active context\");\n return;\n }\n if (newAppId == undefined) {\n console.error('[WebSocketClient]', \"can't switch context: new app id is undefined\");\n return;\n }\n this.isStartContextConfirmed = false;\n var StopEventJSON = JSON.stringify({ event: 'stop' });\n this.send(StopEventJSON);\n this.shouldResendLastFramesSent = true;\n this.send(JSON.stringify({ event: 'start', appId: newAppId }));\n };\n WebsocketClient.prototype.closeWebsocket = function (websocketCode, reason) {\n if (websocketCode === void 0) { websocketCode = 1005; }\n if (reason === void 0) { reason = 'No Status Received'; }\n if (this.debug) {\n console.log('[WebSocketClient]', 'Websocket closing');\n }\n if (!this.websocket) {\n throw Error('WebSocket is undefined');\n }\n this.websocket.close(websocketCode, reason);\n };\n WebsocketClient.prototype.downsample = function (input) {\n var inputBuffer = new Float32Array(this.buffer.length + input.length);\n inputBuffer.set(this.buffer, 0);\n inputBuffer.set(input, this.buffer.length);\n var outputLength = Math.ceil((inputBuffer.length - this.filter.length) / this.resampleRatio);\n var outputBuffer = new Int16Array(outputLength);\n for (var i = 0; i < outputLength; i++) {\n var offset = Math.round(this.resampleRatio * i);\n var val = 0.0;\n for (var j = 0; j < this.filter.length; j++) {\n val += inputBuffer[offset + j] * this.filter[j];\n }\n outputBuffer[i] = val * (val < 0 ? 0x8000 : 0x7fff);\n }\n var remainingOffset = Math.round(this.resampleRatio * outputLength);\n if (remainingOffset < inputBuffer.length) {\n this.buffer = inputBuffer.subarray(remainingOffset);\n }\n else {\n this.buffer = new Float32Array(0);\n }\n return outputBuffer;\n };\n WebsocketClient.prototype.send = function (data) {\n if (this.isOpen()) {\n try {\n this.websocket.send(data);\n }\n catch (error) {\n console.log('[WebSocketClient]', 'server connection error', error);\n }\n }\n };\n return WebsocketClient;\n}());\nvar ctx = self;\nvar websocketClient = new WebsocketClient(ctx);\nctx.onmessage = function (e) {\n switch (e.data.type) {\n case 'INIT':\n websocketClient.init(e.data.apiUrl, e.data.authToken, e.data.targetSampleRate, e.data.debug);\n break;\n case 'SET_SOURCE_SAMPLE_RATE':\n websocketClient.setSourceSampleRate(e.data.sourceSampleRate);\n break;\n case 'SET_SHARED_ARRAY_BUFFERS':\n websocketClient.setSharedArrayBuffers(e.data.controlSAB, e.data.dataSAB);\n break;\n case 'CLOSE':\n websocketClient.closeWebsocket(1000, 'Close requested by client');\n break;\n case 'START_CONTEXT':\n websocketClient.startContext(e.data.appId);\n break;\n case 'SWITCH_CONTEXT':\n websocketClient.switchContext(e.data.appId);\n break;\n case 'STOP_CONTEXT':\n websocketClient.stopContext();\n break;\n case 'AUDIO':\n websocketClient.sendAudio(e.data.payload);\n break;\n default:\n console.log('WORKER', e);\n }\n};\nfunction float32ToInt16(buffer) {\n var buf = new Int16Array(buffer.length);\n for (var l = 0; l < buffer.length; l++) {\n buf[l] = buffer[l] * (buffer[l] < 0 ? 0x8000 : 0x7fff);\n }\n return buf;\n}\nfunction generateFilter(sourceSampleRate, targetSampleRate, length) {\n if (length % 2 === 0) {\n throw Error('Filter length must be odd');\n }\n var cutoff = targetSampleRate / 2;\n var filter = new Float32Array(length);\n var sum = 0;\n for (var i = 0; i < length; i++) {\n var x = sinc(((2 * cutoff) / sourceSampleRate) * (i - (length - 1) / 2));\n sum += x;\n filter[i] = x;\n }\n for (var i = 0; i < length; i++) {\n filter[i] = filter[i] / sum;\n }\n return filter;\n}\nfunction sinc(x) {\n if (x === 0.0) {\n return 1.0;\n }\n var piX = Math.PI * x;\n return Math.sin(piX) / piX;\n}\n"],{type:"text/javascript"}),n=window.URL.createObjectURL(e);this.worker=new Worker(n),this.worker.addEventListener("message",this.onWebsocketMessage)}onResponse(t){this.onResponseCb=t}onClose(t){this.onCloseCb=t}initialize(t,e,n,i){return o(this,void 0,void 0,(function*(){return this.worker.postMessage({type:"INIT",apiUrl:t,authToken:e,targetSampleRate:n,debug:i}),this.startCbs=[],this.stopCbs=[],new Promise((t=>{this.resolveInitialization=t}))}))}setSourceSampleRate(t){return o(this,void 0,void 0,(function*(){return this.worker.postMessage({type:"SET_SOURCE_SAMPLE_RATE",sourceSampleRate:t}),new Promise((t=>{this.resolveSourceSampleRateSet=t}))}))}close(){return o(this,void 0,void 0,(function*(){return new Promise(((t,e)=>{this.worker.postMessage({type:"CLOSE",code:1e3,message:"Client has ended the session"}),t()}))}))}startContext(t){return o(this,void 0,void 0,(function*(){return new Promise(((e,n)=>{this.startCbs.push(((t,i)=>{void 0!==t?n(t):e(i)})),null!=t?this.worker.postMessage({type:"START_CONTEXT",appId:t}):this.worker.postMessage({type:"START_CONTEXT"})}))}))}stopContext(){return o(this,void 0,void 0,(function*(){return new Promise(((t,e)=>{this.stopCbs.push(((n,i)=>{void 0!==n?e(n):t(i)})),this.worker.postMessage({type:"STOP_CONTEXT"})}))}))}switchContext(t){return o(this,void 0,void 0,(function*(){return new Promise(((e,n)=>{this.startCbs.push(((t,i)=>{void 0!==t?n(t):e(i)})),this.worker.postMessage({type:"SWITCH_CONTEXT",appId:t})}))}))}postMessage(t){this.worker.postMessage(t)}sendAudio(t){this.worker.postMessage({type:"AUDIO",payload:t})}}class T{constructor(){this.storage=window.localStorage}get(t){return this.storage.getItem(t)}set(t,e){this.storage.setItem(t,e)}getOrSet(t,e){let n=this.storage.getItem(t);return null===n&&(n=e(),this.storage.setItem(t,n)),n}}function I(t,e){return{intent:t.intent,isFinal:e}}const W="speechly-auth-token";class _{constructor(e){var n,o,r,a,c,d,l,h;if(this.activeContexts=new Map,this.maxReconnectAttemptCount=10,this.contextStopDelay=250,this.connectAttempt=0,this.connectPromise=null,this.listeningPromise=null,this.cbs=[],this.state=t.DecoderState.Disconnected,this.handleWebsocketResponse=e=>{var n;this.debug&&console.log("[Decoder]","Received response",e);const{audio_context:i,segment_id:o,type:r}=e;let{data:a}=e;const c=this.activeContexts.get(i);if(void 0===c)return void console.warn("[Decoder]","Received response for non-existent context",i);let d=null!==(n=c.get(o))&&void 0!==n?n:new s(i,o);switch(r){case t.WebsocketResponseType.TentativeTranscript:const e=function(t){return t.words.map((({word:t,index:e,start_timestamp:n,end_timestamp:i})=>({value:t,index:e,startTimestamp:n,endTimestamp:i,isFinal:!1})))}(a),n=a.transcript;this.cbs.forEach((t=>t.tentativeTranscriptCbs.forEach((t=>t(i,o,e,n))))),d=d.updateTranscript(e);break;case t.WebsocketResponseType.Transcript:const s=function(t){return{value:t.word,index:t.index,startTimestamp:t.start_timestamp,endTimestamp:t.end_timestamp,isFinal:!0}}(a);this.cbs.forEach((t=>t.transcriptCbs.forEach((t=>t(i,o,s))))),d=d.updateTranscript([s]);break;case t.WebsocketResponseType.TentativeEntities:const r=function(t){return t.entities.map((({entity:t,value:e,start_position:n,end_position:i})=>({type:t,value:e,startPosition:n,endPosition:i,isFinal:!1})))}(a);this.cbs.forEach((t=>t.tentativeEntityCbs.forEach((t=>t(i,o,r))))),d=d.updateEntities(r);break;case t.WebsocketResponseType.Entity:const c=function(t){return{type:t.entity,value:t.value,startPosition:t.start_position,endPosition:t.end_position,isFinal:!0}}(a);this.cbs.forEach((t=>t.entityCbs.forEach((t=>t(i,o,c))))),d=d.updateEntities([c]);break;case t.WebsocketResponseType.TentativeIntent:const l=I(a,!1);this.cbs.forEach((t=>t.tentativeIntentCbs.forEach((t=>t(i,o,l))))),d=d.updateIntent(l);break;case t.WebsocketResponseType.Intent:const h=I(a,!0);this.cbs.forEach((t=>t.intentCbs.forEach((t=>t(i,o,h))))),d=d.updateIntent(h);break;case t.WebsocketResponseType.SegmentEnd:d=d.finalize()}c.set(o,d),this.activeContexts.set(i,c),this.logSegments&&console.info(d.toString()),this.cbs.forEach((t=>t.segmentChangeCbs.forEach((t=>t(d.toSegment())))))},this.handleWebsocketClosure=e=>{if(1e3===e.code)this.debug&&console.log("[Decoder]","Websocket closed",e);else{if(console.error("[Decoder]","Websocket closed due to error",e),void 0===this.deviceId)return void this.setState(t.DecoderState.Failed);this.listeningPromise=null,this.setState(t.DecoderState.Disconnected),this.reconnect()}},this.logSegments=null!==(n=e.logSegments)&&void 0!==n&&n,this.appId=null!==(o=e.appId)&&void 0!==o?o:void 0,this.projectId=null!==(r=e.projectId)&&void 0!==r?r:void 0,this.sampleRate=null!==(a=e.sampleRate)&&void 0!==a?a:i,this.debug=null!==(c=e.debug)&&void 0!==c&&c,void 0!==this.appId&&void 0!==this.projectId)throw Error("[Decoder] You cannot use both appId and projectId at the same time");if(void 0===this.appId&&void 0===this.projectId)throw Error("[Decoder] Either an appId or a projectId is required");const u=null!==(d=e.apiUrl)&&void 0!==d?d:"https://api.speechly.com";this.apiUrl=function(t,e){const n=new URLSearchParams;return n.append("sampleRate",e.toString()),`${t}?${n.toString()}`}(u.replace("http","ws")+"/ws/v1",this.sampleRate),this.loginUrl=`${u}/login`,this.storage=null!==(l=e.storage)&&void 0!==l?l:new T,this.deviceId=this.storage.getOrSet("speechly-device-id",k),this.apiClient=new x,this.apiClient.onResponse(this.handleWebsocketResponse),this.apiClient.onClose(this.handleWebsocketClosure),(null===(h=e.connect)||void 0===h||h)&&this.connect()}getReconnectDelayMs(t){return 100*Math.pow(2,t)}sleep(t){return o(this,void 0,void 0,(function*(){return new Promise((e=>setTimeout(e,t)))}))}connect(){return o(this,void 0,void 0,(function*(){null===this.connectPromise&&(this.connectPromise=(()=>o(this,void 0,void 0,(function*(){const e=this.storage.get(W);if(null!=e&&R(e,this.projectId,this.appId,this.deviceId))this.authToken=e;else try{this.authToken=yield function(t,e,n,i,s=fetch,r=Date.now){var a;return o(this,void 0,void 0,(function*(){let o;o=void 0!==e?{projectId:e,deviceId:i}:{appId:n,deviceId:i};const c=yield s(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}),d=yield c.json();if(200!==c.status)throw Error(null!==(a=d.error)&&void 0!==a?a:`Speechly API login request failed with ${c.status}`);if(void 0===d.access_token)throw Error("Invalid login response from Speechly API");if(!R(d.access_token,e,n,i,r))throw Error("Invalid token received from Speechly API");return d.access_token}))}(this.loginUrl,this.projectId,this.appId,this.deviceId),this.storage.set(W,this.authToken)}catch(e){throw this.setState(t.DecoderState.Failed),e}yield this.apiClient.initialize(this.apiUrl,this.authToken,this.sampleRate,this.debug),this.advanceState(t.DecoderState.Connected)})))()),yield this.connectPromise}))}close(){return o(this,void 0,void 0,(function*(){let e;try{yield this.apiClient.close()}catch(t){e=t.message}if(this.activeContexts.clear(),this.connectPromise=null,this.setState(t.DecoderState.Disconnected),void 0!==e)throw Error(e)}))}startContext(e){return o(this,void 0,void 0,(function*(){if(this.state===t.DecoderState.Failed)throw Error("[Decoder] startContext cannot be run in Failed state.");if(this.state<t.DecoderState.Connected)yield this.connect();else if(this.state>t.DecoderState.Connected)throw Error("[Decoder] Unable to complete startContext: Expected Connected state, but was in "+f(this.state)+".");let i;if(this.setState(t.DecoderState.Active),null!=this.projectId)i=yield this.apiClient.startContext(null==e?void 0:e.appId);else{if(null!=(null==e?void 0:e.appId)&&this.appId!==(null==e?void 0:e.appId))throw this.setState(t.DecoderState.Failed),n;i=yield this.apiClient.startContext()}if(this.state<t.DecoderState.Active)throw Error("[Decoder] Unable to complete startContext: Problem acquiring contextId");return this.activeContexts.set(i,new Map),this.cbs.forEach((t=>t.contextStartedCbs.forEach((t=>t(i))))),i}))}sendAudio(e){if(this.state!==t.DecoderState.Active)throw Error("[Decoder] Unable to complete startContext: Expected Active state, but was in "+f(this.state)+".");this.apiClient.sendAudio(e)}stopContext(){return o(this,void 0,void 0,(function*(){if(this.state===t.DecoderState.Failed)throw Error("[Decoder] stopContext cannot be run in unrecovable error state.");if(this.state!==t.DecoderState.Active)throw Error("[Decoder] Unable to complete stopContext: Expected Active state, but was in "+f(this.state)+".");this.setState(t.DecoderState.Connected),yield this.sleep(this.contextStopDelay);try{const t=yield this.apiClient.stopContext();return this.activeContexts.delete(t),this.cbs.forEach((e=>e.contextStoppedCbs.forEach((e=>e(t))))),t}catch(e){throw this.setState(t.DecoderState.Failed),e}}))}switchContext(e){return o(this,void 0,void 0,(function*(){if(this.state!==t.DecoderState.Active)throw Error("[Decoder] Unable to complete switchContext: Expected Active state, but was in "+f(this.state)+".");const n=yield this.apiClient.switchContext(e);this.activeContexts.set(n,new Map)}))}registerListener(t){this.cbs.push(t)}setSampleRate(t){return o(this,void 0,void 0,(function*(){this.sampleRate=t,yield this.apiClient.setSourceSampleRate(t)}))}useSharedArrayBuffers(t,e){this.apiClient.postMessage({type:"SET_SHARED_ARRAY_BUFFERS",controlSAB:t,dataSAB:e})}reconnect(){return o(this,void 0,void 0,(function*(){this.debug&&console.log("[Decoder]","Reconnecting...",this.connectAttempt),this.connectPromise=null,this.connectAttempt<this.maxReconnectAttemptCount?(yield this.sleep(this.getReconnectDelayMs(this.connectAttempt++)),yield this.connect()):console.error("[Decoder] Maximum reconnect count reached, giving up automatic reconnect.")}))}advanceState(t){this.state>=t||this.setState(t)}setState(t){this.state!==t&&(this.debug&&console.log("[Decoder]",f(this.state),"->",f(t)),this.state=t,this.cbs.forEach((e=>{var n;return null===(n=e.stateChangeCbs)||void 0===n?void 0:n.forEach((e=>e(t)))})))}}t.BrowserClient=class{constructor(t){var e,n;this.debug=!1,this.initialized=!1,this.active=!1,this.stats={maxSignalEnergy:0,sentSamples:0};const i=window.navigator.mediaDevices.getSupportedConstraints();this.nativeResamplingSupported=!0===i.sampleRate,this.isMobileSafari=["iPad Simulator","iPhone Simulator","iPod Simulator","iPad","iPhone","iPod"].indexOf(navigator.platform)>=0||navigator.userAgent.includes("Mac")&&"ontouchend"in document,this.isSafari=this.isMobileSafari||void 0!==window.safari,this.useSAB=!this.isSafari,this.debug=null===(e=t.debug)||void 0===e||e,this.callbacks=new p,this.decoder=null!==(n=t.decoder)&&void 0!==n?n:new _(t),this.decoder.registerListener(this.callbacks)}initialize(t){var n,s;return o(this,void 0,void 0,(function*(){if(!this.initialized){this.initialized=!0,this.debug&&console.log("[BrowserClient]","initializing"),yield this.decoder.connect();try{const t={};if(this.nativeResamplingSupported&&(t.sampleRate=i),void 0!==window.webkitAudioContext)try{this.audioContext=new window.webkitAudioContext(t)}catch(t){this.debug&&console.log("[BrowserClient]","creating audioContext without samplerate conversion",t),this.audioContext=new window.webkitAudioContext}else this.audioContext=new window.AudioContext(t),void 0!==window.webkitAudioContext&&(yield this.audioContext.resume())}catch(t){throw e}if(this.isSafari||void 0===window.AudioWorkletNode){if(this.debug&&console.log("[BrowserClient]","using ScriptProcessorNode"),void 0!==window.webkitAudioContext){const t=this.audioContext.sampleRate/i,e=4096*Math.pow(2,Math.ceil(Math.log(t)/Math.log(2)));this.audioProcessor=this.audioContext.createScriptProcessor(e,1,1)}else this.audioProcessor=this.audioContext.createScriptProcessor(void 0,1,1);this.audioProcessor.connect(this.audioContext.destination),this.audioProcessor.addEventListener("audioprocess",(t=>{this.handleAudio(t.inputBuffer.getChannelData(0))}))}else{this.debug&&console.log("[BrowserClient]","using AudioWorkletNode");const t=new Blob(["\n// Indices for the Control SAB.\nconst CONTROL = {\n 'WRITE_INDEX': 0,\n 'FRAMES_AVAILABLE': 1,\n 'LOCK': 2,\n};\n\nclass SpeechlyProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n\n this._initialized = false;\n this.debug = false;\n this.port.onmessage = this._initialize.bind(this);\n }\n\n _initialize(event) {\n this.controlSAB = new Int32Array(event.data.controlSAB);\n this.dataSAB = new Float32Array(event.data.dataSAB);\n this.debug = event.data.debug;\n if (this.debug) {\n console.log('[BrowserClient AudioWorkletNode]', 'initializing audioworklet');\n }\n this.sharedBufferSize = this.dataSAB.length;\n this.buffer = new Float32Array(0);\n this._initialized = true;\n }\n\n _transferDataToSharedBuffer(data) {\n this.controlSAB[CONTROL.LOCK] = 1;\n let inputWriteIndex = this.controlSAB[CONTROL.WRITE_INDEX];\n if (this.controlSAB[CONTROL.FRAMES_AVAILABLE] > 0) {\n if (inputWriteIndex + data.length > this.sharedBufferSize) {\n // console.log('buffer overflow')\n inputWriteIndex = 0;\n }\n }\n this.dataSAB.set(data, inputWriteIndex);\n this.controlSAB[CONTROL.WRITE_INDEX] = inputWriteIndex + data.length;\n this.controlSAB[CONTROL.FRAMES_AVAILABLE] = inputWriteIndex + data.length;\n this.controlSAB[CONTROL.LOCK] = 0;\n }\n\n _pushData(data) {\n if (this.debug) {\n const signalEnergy = getStandardDeviation(data)\n this.port.postMessage({\n type: 'STATS',\n signalEnergy: signalEnergy,\n samples: data.length,\n });\n }\n\n if (this.buffer.length > this.sharedBufferSize) {\n const dataToTransfer = this.buffer.subarray(0, this.sharedBufferSize);\n this._transferDataToSharedBuffer(dataToTransfer);\n this.buffer = this.buffer.subarray(this.sharedBufferSize);\n }\n let concat = new Float32Array(this.buffer.length + data.length);\n concat.set(this.buffer);\n concat.set(data, this.buffer.length);\n this.buffer = concat;\n }\n\n process(inputs, outputs, parameters) {\n const inputChannelData = inputs[0][0];\n if (inputChannelData !== undefined) {\n if (this.controlSAB && this.dataSAB) {\n this._pushData(inputChannelData);\n } else {\n this.port.postMessage({\n type: 'DATA',\n frames: inputChannelData\n });\n }\n }\n\n return true;\n }\n}\n\nfunction getStandardDeviation(array) {\n const n = array.length\n const mean = array.reduce((a, b) => a + b) / n\n return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n)\n}\n\nregisterProcessor('speechly-worklet', SpeechlyProcessor);\n"],{type:"text/javascript"}),e=window.URL.createObjectURL(t);if(yield this.audioContext.audioWorklet.addModule(e),this.speechlyNode=new AudioWorkletNode(this.audioContext,"speechly-worklet"),this.speechlyNode.connect(this.audioContext.destination),this.useSAB&&void 0!==window.SharedArrayBuffer){this.debug&&console.log("[BrowserClient]","using SharedArrayBuffer");const t=new window.SharedArrayBuffer(4*Int32Array.BYTES_PER_ELEMENT),e=new window.SharedArrayBuffer(1024*Float32Array.BYTES_PER_ELEMENT);this.decoder.useSharedArrayBuffers(t,e),this.speechlyNode.port.postMessage({type:"SET_SHARED_ARRAY_BUFFERS",controlSAB:t,dataSAB:e,debug:this.debug})}else this.debug&&console.log("[BrowserClient]","can not use SharedArrayBuffer");this.speechlyNode.port.onmessage=t=>{switch(t.data.type){case"STATS":t.data.signalEnergy>this.stats.maxSignalEnergy&&(this.stats.maxSignalEnergy=t.data.signalEnergy),this.stats.sentSamples+=parseInt(t.data.samples);break;case"DATA":this.handleAudio(t.data.frames)}}}this.debug&&console.log("[BrowserClient]","audioContext sampleRate is",null===(n=this.audioContext)||void 0===n?void 0:n.sampleRate),yield this.decoder.setSampleRate(null===(s=this.audioContext)||void 0===s?void 0:s.sampleRate),(null==t?void 0:t.mediaStream)&&(yield this.attach(null==t?void 0:t.mediaStream))}}))}close(){var t,e,n;return o(this,void 0,void 0,(function*(){yield this.detach(),null!==this.speechlyNode&&(null===(t=this.speechlyNode)||void 0===t||t.port.close(),null===(e=this.speechlyNode)||void 0===e||e.disconnect()),void 0!==this.audioProcessor&&(null===(n=this.audioProcessor)||void 0===n||n.disconnect()),yield this.decoder.close(),this.initialized=!1}))}attach(t){var e,n,i,s,r,a;return o(this,void 0,void 0,(function*(){if(yield this.initialize(),yield this.detach(),this.stream=null===(e=this.audioContext)||void 0===e?void 0:e.createMediaStreamSource(t),"running"!==(null===(n=this.audioContext)||void 0===n?void 0:n.state)&&(this.debug&&console.log("[BrowserClient]","audioContext resume required, state is",null===(i=this.audioContext)||void 0===i?void 0:i.state),yield null===(s=this.audioContext)||void 0===s?void 0:s.resume()),this.speechlyNode)null===(r=this.stream)||void 0===r||r.connect(this.speechlyNode);else{if(!this.audioProcessor)throw Error("[BrowserClient] cannot attach to mediaStream, not initialized");null===(a=this.stream)||void 0===a||a.connect(this.audioProcessor)}}))}detach(){return o(this,void 0,void 0,(function*(){this.active&&(yield this.stop()),this.stream&&(this.stream.disconnect(),this.stream=void 0)}))}uploadAudioData(t,e){var n;return o(this,void 0,void 0,(function*(){yield this.initialize();const i=yield null===(n=this.audioContext)||void 0===n?void 0:n.decodeAudioData(t);if(void 0===i)throw Error("Could not decode audioData");const s=i.getChannelData(0);if(i.numberOfChannels>1){const t=i.getChannelData(1);for(let e=0;e<s.length;e++)s[e]=(s[e]+t[e])/2}const o=yield this.start(e);let r;for(let t=0;t<s.length;t+=16e3){const e=t+16e3;r=e>s.length?s.slice(t):s.slice(t,e),this.handleAudio(r)}return yield this.stop(),o}))}start(t){return o(this,void 0,void 0,(function*(){yield this.initialize();const e=this.decoder.startContext(t);return this.active=!0,e}))}stop(){return o(this,void 0,void 0,(function*(){let t=null;try{t=yield this.decoder.stopContext(),0===this.stats.sentSamples&&console.warn("[BrowserClient]","audioContext contained no audio data")}catch(t){console.warn("[BrowserClient]","stop() failed",t)}finally{this.active=!1,this.stats.sentSamples=0}return t}))}handleAudio(t){this.active&&t.length>0&&(this.stats.sentSamples+=t.length,this.decoder.sendAudio(t))}isActive(){return this.active}onSegmentChange(t){this.callbacks.segmentChangeCbs.push(t)}onTranscript(t){this.callbacks.transcriptCbs.push(t)}onEntity(t){this.callbacks.entityCbs.push(t)}onIntent(t){this.callbacks.intentCbs.push(t)}onTentativeTranscript(t){this.callbacks.tentativeTranscriptCbs.push(t)}onTentativeEntities(t){this.callbacks.tentativeEntityCbs.push(t)}onTentativeIntent(t){this.callbacks.tentativeIntentCbs.push(t)}onStateChange(t){this.callbacks.stateChangeCbs.push(t)}},t.BrowserMicrophone=class{constructor(){this.muted=!1,this.initialized=!1;try{const t=window.navigator.mediaDevices.getSupportedConstraints();this.nativeResamplingSupported=!0===t.sampleRate,this.autoGainControl=!0===t.autoGainControl}catch(t){this.nativeResamplingSupported=!1,this.autoGainControl=!1}}initialize(){var t;return o(this,void 0,void 0,(function*(){if(this.initialized)return;if(void 0===(null===(t=window.navigator)||void 0===t?void 0:t.mediaDevices))throw e;const n={video:!1};this.nativeResamplingSupported||this.autoGainControl?n.audio={sampleRate:i,autoGainControl:this.autoGainControl}:n.audio=!0;try{this.mediaStream=yield window.navigator.mediaDevices.getUserMedia(n)}catch(t){throw console.error(t),c}this.initialized=!0,this.muted=!0}))}close(){return o(this,void 0,void 0,(function*(){if(!this.initialized)throw r;this.muted=!0;this.mediaStream.getTracks().forEach((t=>t.stop())),this.mediaStream=void 0,this.initialized=!1}))}isRecording(){return!this.muted}},t.CloudDecoder=_,t.DefaultSampleRate=i,t.ErrAlreadyInitialized=a,t.ErrAppIdChangeWithoutProjectLogin=n,t.ErrDeviceNotSupported=e,t.ErrKeyNotFound=h,t.ErrNoAudioConsent=c,t.ErrNoStorageSupport=l,t.ErrNotInitialized=r,t.EventCallbacks=p,t.SegmentState=s,t.stateToString=f,Object.defineProperty(t,"__esModule",{value:!0})}));
!function(t,e){!function(i){var n=e,s=t&&t.exports==n&&t,o="object"==typeof N&&N;o.global!==o&&o.window!==o||(i=o);var a=function(t){this.message=t};(a.prototype=new Error).name="InvalidCharacterError";var d=function(t){throw new a(t)},c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",l=/[\t\n\f\r ]/g,r={encode:function(t){t=String(t),/[^\0-\xFF]/.test(t)&&d("The string to be encoded contains characters outside of the Latin1 range.");for(var e,i,n,s,o=t.length%3,a="",l=-1,r=t.length-o;++l<r;)e=t.charCodeAt(l)<<16,i=t.charCodeAt(++l)<<8,n=t.charCodeAt(++l),a+=c.charAt((s=e+i+n)>>18&63)+c.charAt(s>>12&63)+c.charAt(s>>6&63)+c.charAt(63&s);return 2==o?(e=t.charCodeAt(l)<<8,i=t.charCodeAt(++l),a+=c.charAt((s=e+i)>>10)+c.charAt(s>>4&63)+c.charAt(s<<2&63)+"="):1==o&&(s=t.charCodeAt(l),a+=c.charAt(s>>2)+c.charAt(s<<4&63)+"=="),a},decode:function(t){var e=(t=String(t).replace(l,"")).length;e%4==0&&(e=(t=t.replace(/==?$/,"")).length),(e%4==1||/[^+a-zA-Z0-9/]/.test(t))&&d("Invalid character: the string to be decoded is not correctly encoded.");for(var i,n,s=0,o="",a=-1;++a<e;)n=c.indexOf(t.charAt(a)),i=s%4?64*i+n:n,s++%4&&(o+=String.fromCharCode(255&i>>(-2*s&6)));return o},version:"0.1.0"};if(n&&!n.nodeType)if(s)s.exports=r;else for(var h in r)r.hasOwnProperty(h)&&(n[h]=r[h]);else i.base64=r}(N)}(Y,Y.exports);function T(t,e,i,n,s=Date.now){const o=function(t){const e=t.split(".")[1];let i;try{i=JSON.parse(Y.exports.decode(e))}catch(t){throw new Error("Error decoding Speechly token!")}return{appId:i.appId,projectId:i.projectId,deviceId:i.deviceId,configId:i.configId,scopes:i.scope.split(" "),issuer:i.iss,audience:i.aud,expiresAtMs:1e3*i.exp}}(t);return!(o.expiresAtMs-s()<36e5)&&(o.appId===i&&o.projectId===e&&o.deviceId===n)}class z{constructor(){this.startCbs=[],this.stopCbs=[],this.onResponseCb=()=>{},this.onCloseCb=()=>{},this.onWebsocketMessage=e=>{const i=e.data;switch(i.type){case t.WorkerSignal.Opened:null!=this.resolveInitialization&&this.resolveInitialization();break;case t.WorkerSignal.Closed:this.onCloseCb({code:e.data.code,reason:e.data.reason,wasClean:e.data.wasClean});break;case t.WorkerSignal.AudioProcessorReady:null!=this.resolveSourceSampleRateSet&&this.resolveSourceSampleRateSet();break;case t.WebsocketResponseType.Started:this.onResponseCb(i),this.startCbs.forEach((t=>{try{t(void 0,i.audio_context)}catch(t){console.error('[SpeechlyClient] Error while invoking "onStart" callback:',t)}})),this.startCbs.length=0;break;case t.WebsocketResponseType.Stopped:this.onResponseCb(i),this.stopCbs.forEach((t=>{try{t(void 0,i.audio_context)}catch(t){console.error('[SpeechlyClient] Error while invoking "onStop" callback:',t)}})),this.stopCbs.length=0;break;default:this.onResponseCb(i)}},this.worker=new"Lyogcm9sbHVwLXBsdWdpbi13ZWItd29ya2VyLWxvYWRlciAqLwp2YXIgd29ya2VyX2NvZGU9ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7Y2xhc3MgdHtzdGF0aWMgZG93bnNhbXBsZSh0LGUscz0wLGk9LTEsbz0wLHI9LTEpe2lmKGk8MCYmKGk9dC5sZW5ndGgtcykscjwwJiYocj1lLmxlbmd0aC1vKSxyPmkpdGhyb3cgbmV3IEVycm9yKGBDYW4ndCBkb3duc2FtcGxlOiBzb3VyY2UgYXJyYXkgbGVuZ3RoICgke2l9KSBpcyBzaG9ydGVyIHRoYW4gZGVzdGluYXRpb24gKCR7cn0pYCk7aWYoMD09PXIpdGhyb3cgbmV3IEVycm9yKGBDYW4ndCBkb3duc2FtcGxlOiBzb3VyY2UgYXJyYXkgbGVuZ3RoICgke2l9KSBjYW4ndCBiZSBkb3duc2FtcGxlZCB0byB6ZXJvLWxlbmd0aCBkZXN0aW5hdGlvbi5gKTtpZigwPT09aSl0aHJvdyBuZXcgRXJyb3IoIkNhbid0IGRvd25zYW1wbGU6IHNvdXJjZSByYW5nZSBjYW4ndCBiZSB6ZXJvIGxlbmd0aC4iKTtpZigxPT09aSlyZXR1cm4gdm9pZChlWzBdPXRbMF0pO2xldCBhPTA7Y29uc3Qgbj0oci0xKS8oaS0xKTtsZXQgaD0wLGw9MDtjb25zdCBkPXMraTtmb3IoO3M8ZDtzKyspe2NvbnN0IGk9LjUtTWF0aC5hYnMoYSk7aCs9dFtzXSppLGwrPWksYSs9bixhPj0uNSYmKGEtPTEsZVtvKytdPWgvbCxoPTAsbD0wKX1sPjAmJihlW28rK109aC9sKX1zdGF0aWMgZ2V0RW5lcmd5KHQsZT0wLHM9LTEpe2lmKHM8MCYmKHM9dC5sZW5ndGgtZSksczw9MClyZXR1cm4gMDtjb25zdCBpPWUrcztsZXQgbz0wO2Zvcig7ZTxpO2UrKylvKz10W2VdKnRbZV07cmV0dXJuIE1hdGguc3FydChvL3MpfXN0YXRpYyBnZXRBdWRpb1BlYWsodCxlPTAscz0tMSl7aWYoczwwJiYocz10Lmxlbmd0aC1lKSxzPD0wKXJldHVybiAwO2NvbnN0IGk9ZStzO2xldCBvPTA7Zm9yKDtlPGk7ZSsrKXRbZV0+byYmKG89dFtlXSk7cmV0dXJuIG99c3RhdGljIGNvbnZlcnRJbnQxNlRvRmxvYXQodCxlLHM9MCxpPS0xLG89MCl7aTwwJiYoaT10Lmxlbmd0aC8yLXMpO2NvbnN0IHI9TWF0aC5taW4odC5sZW5ndGgvMi1zLGUubGVuZ3RoLW8pO2lmKChpPU1hdGgubWluKGkscikpPD0wKXJldHVybiAwO2xldCBhPTIqcztjb25zdCBuPWErMippO2Zvcig7YTxuOyllW28rK109KHRbYSsrXSsodFthKytdPDw4KSkvMzI3Njc7cmV0dXJuIGl9c3RhdGljIGNvbnZlcnRGbG9hdFRvSW50MTYodCxlLHM9MCxpPS0xLG89MCl7aTwwJiYoaT10Lmxlbmd0aC1zKTtjb25zdCByPXMraTtmb3IoO3M8cjspZVtvKytdPX5+KDMyNzY3KnRbcysrXSl9c3RhdGljIGVuZXJneVRvRGIoZSl7cmV0dXJuIDEwKk1hdGgubG9nKGUpL3QuTE9HXzJfUExVU19MT0dfNX1zdGF0aWMgZGJUb0VuZXJneSh0KXtyZXR1cm4gTWF0aC5wb3coMTAsdC8xMCl9fXQuTE9HXzJfUExVU19MT0dfNT1NYXRoLmxvZygyKStNYXRoLmxvZyg1KTtjbGFzcyBle2NvbnN0cnVjdG9yKHQsZSxzKXt0aGlzLmlzQWN0aXZlPSExLHRoaXMuc3RyZWFtU2FtcGxlUG9zPTAsdGhpcy5zYW1wbGVzU2VudD0wLHRoaXMudXR0ZXJhbmNlU2VyaWFsPS0xLHRoaXMuc2VuZEF1ZGlvPSh0LGUscyk9Pnt9LHRoaXMub25WYWRTaWduYWxMb3c9KCk9Pnt9LHRoaXMub25WYWRTaWduYWxIaWdoPSgpPT57fSx0aGlzLmlucHV0U2FtcGxlUmF0ZT0xNmUzLHRoaXMuaW50ZXJuYWxTYW1wbGVSYXRlPTE2ZTMsdGhpcy5oaXN0b3J5RnJhbWVzPTUsdGhpcy5mcmFtZU1pbGxpcz0zMCx0aGlzLmN1cnJlbnRGcmFtZU51bWJlcj0wLHRoaXMuZnJhbWVTYW1wbGVQb3M9MCx0aGlzLnN0cmVhbUZyYW1lUG9zPTAsdGhpcy5pc1NpZ25hbERldGVjdGVkPSExLHRoaXMuaW5wdXRTYW1wbGVSYXRlPXQsdGhpcy5pbnRlcm5hbFNhbXBsZVJhdGU9ZSx0aGlzLmhpc3RvcnlGcmFtZXM9cyx0aGlzLmZyYW1lU2FtcGxlcz1+fih0aGlzLmludGVybmFsU2FtcGxlUmF0ZSp0aGlzLmZyYW1lTWlsbGlzLzFlMyksdGhpcy5zYW1wbGVSaW5nQnVmZmVyPW5ldyBGbG9hdDMyQXJyYXkodGhpcy5mcmFtZVNhbXBsZXMqdGhpcy5oaXN0b3J5RnJhbWVzKX1zdGFydENvbnRleHQoKXt0aGlzLmlzQWN0aXZlPSEwLHRoaXMuc2FtcGxlc1NlbnQ9MCx0aGlzLnV0dGVyYW5jZVNlcmlhbCsrfXN0b3BDb250ZXh0KCl7dGhpcy5mbHVzaCgpLHRoaXMuaXNBY3RpdmU9ITF9cmVzZXRTdHJlYW0oKXt2YXIgdDt0aGlzLnN0cmVhbUZyYW1lUG9zPTAsdGhpcy5zdHJlYW1TYW1wbGVQb3M9MCx0aGlzLmZyYW1lU2FtcGxlUG9zPTAsdGhpcy5jdXJyZW50RnJhbWVOdW1iZXI9MCx0aGlzLnV0dGVyYW5jZVNlcmlhbD0tMSxudWxsPT09KHQ9dGhpcy52YWQpfHx2b2lkIDA9PT10fHx0LnJlc2V0VkFEKCl9Zmx1c2goKXt0aGlzLnByb2Nlc3NBdWRpbyh0aGlzLnNhbXBsZVJpbmdCdWZmZXIsMCx0aGlzLmZyYW1lU2FtcGxlUG9zLCEwKX1wcm9jZXNzQXVkaW8oZSxzPTAsaT0tMSxvPSExKXtpZihpPDAmJihpPWUubGVuZ3RoKSwwPT09aSlyZXR1cm47bGV0IHI9cztjb25zdCBhPXMraTtmb3IoO3I8YTspe2NvbnN0IHM9dGhpcy5jdXJyZW50RnJhbWVOdW1iZXIqdGhpcy5mcmFtZVNhbXBsZXM7aWYodGhpcy5pbnB1dFNhbXBsZVJhdGU9PT10aGlzLmludGVybmFsU2FtcGxlUmF0ZSl7Y29uc3QgdD1NYXRoLm1pbihhLXIsdGhpcy5mcmFtZVNhbXBsZXMtdGhpcy5mcmFtZVNhbXBsZVBvcyksaT10aGlzLmZyYW1lU2FtcGxlUG9zK3Q7Zm9yKDt0aGlzLmZyYW1lU2FtcGxlUG9zPGk7KXRoaXMuc2FtcGxlUmluZ0J1ZmZlcltzK3RoaXMuZnJhbWVTYW1wbGVQb3MrK109ZVtyKytdfWVsc2V7Y29uc3QgaT0xKnRoaXMuaW5wdXRTYW1wbGVSYXRlL3RoaXMuaW50ZXJuYWxTYW1wbGVSYXRlLG89TWF0aC5taW4oYS1yLE1hdGgucm91bmQoaSoodGhpcy5mcmFtZVNhbXBsZXMtdGhpcy5mcmFtZVNhbXBsZVBvcykpKSxuPU1hdGgubWluKE1hdGgucm91bmQoKGEtcikvaSksdGhpcy5mcmFtZVNhbXBsZXMtdGhpcy5mcmFtZVNhbXBsZVBvcyk7bj4wJiZ0LmRvd25zYW1wbGUoZSx0aGlzLnNhbXBsZVJpbmdCdWZmZXIscixvLHMrdGhpcy5mcmFtZVNhbXBsZVBvcyxuKSxyKz1vLHRoaXMuZnJhbWVTYW1wbGVQb3MrPW59aWYodGhpcy5mcmFtZVNhbXBsZVBvcz50aGlzLmZyYW1lU2FtcGxlcyl0aHJvdyBuZXcgRXJyb3IoYHRoaXMuZnJhbWVTYW1wbGVQb3MgKCR7dGhpcy5mcmFtZVNhbXBsZVBvc30pID4gdGhpcy5mcmFtZVNhbXBsZXMgKCR7dGhpcy5mcmFtZVNhbXBsZXN9KWApO2lmKHRoaXMuZnJhbWVTYW1wbGVQb3M9PT10aGlzLmZyYW1lU2FtcGxlc3x8byl7Y29uc3QgdD1vP3RoaXMuZnJhbWVTYW1wbGVQb3M6dGhpcy5mcmFtZVNhbXBsZXM7aWYob3x8dGhpcy5wcm9jZXNzRnJhbWUodGhpcy5zYW1wbGVSaW5nQnVmZmVyLHMsdCksdGhpcy5pc0FjdGl2ZSl7aWYoMD09PXRoaXMuc2FtcGxlc1NlbnQpe2NvbnN0IHQ9TWF0aC5taW4odGhpcy5zdHJlYW1GcmFtZVBvcyx0aGlzLmhpc3RvcnlGcmFtZXMtMSk7bGV0IGU9KHRoaXMuY3VycmVudEZyYW1lTnVtYmVyK3RoaXMuaGlzdG9yeUZyYW1lcy10KSV0aGlzLmhpc3RvcnlGcmFtZXM7Zm9yKDtlIT09dGhpcy5jdXJyZW50RnJhbWVOdW1iZXI7KXRoaXMuc2VuZEF1ZGlvKHRoaXMuc2FtcGxlUmluZ0J1ZmZlcixlKnRoaXMuZnJhbWVTYW1wbGVzLHRoaXMuZnJhbWVTYW1wbGVzKSx0aGlzLnNhbXBsZXNTZW50Kz10aGlzLmZyYW1lU2FtcGxlcyxlPShlKzEpJXRoaXMuaGlzdG9yeUZyYW1lc310aGlzLnNlbmRBdWRpbyh0aGlzLnNhbXBsZVJpbmdCdWZmZXIscyx0KSx0aGlzLnNhbXBsZXNTZW50Kz10fXRoaXMuZnJhbWVTYW1wbGVQb3M9PT10aGlzLmZyYW1lU2FtcGxlcyYmKHRoaXMuZnJhbWVTYW1wbGVQb3M9MCx0aGlzLnN0cmVhbUZyYW1lUG9zKz0xLHRoaXMuc3RyZWFtU2FtcGxlUG9zKz10LHRoaXMuY3VycmVudEZyYW1lTnVtYmVyPSh0aGlzLmN1cnJlbnRGcmFtZU51bWJlcisxKSV0aGlzLmhpc3RvcnlGcmFtZXMpfX19cHJvY2Vzc0ZyYW1lKHQsZT0wLHM9LTEpe3RoaXMuYW5hbHl6ZUF1ZGlvRnJhbWUodCxlLHMpLHRoaXMuYXV0b0NvbnRyb2xMaXN0ZW5pbmcoKX1hbmFseXplQXVkaW9GcmFtZSh0LGUscyl7dmFyIGk7KG51bGw9PT0oaT10aGlzLnZhZCl8fHZvaWQgMD09PWk/dm9pZCAwOmkudmFkT3B0aW9ucy5lbmFibGVkKSYmdGhpcy52YWQucHJvY2Vzc0ZyYW1lKHQsZSxzKX1hdXRvQ29udHJvbExpc3RlbmluZygpe3ZhciB0OyhudWxsPT09KHQ9dGhpcy52YWQpfHx2b2lkIDA9PT10P3ZvaWQgMDp0LnZhZE9wdGlvbnMuZW5hYmxlZCkmJighdGhpcy5pc1NpZ25hbERldGVjdGVkJiZ0aGlzLnZhZC5pc1NpZ25hbERldGVjdGVkJiYodGhpcy5vblZhZFNpZ25hbEhpZ2goKSx0aGlzLmlzU2lnbmFsRGV0ZWN0ZWQ9ITApLHRoaXMuaXNTaWduYWxEZXRlY3RlZCYmIXRoaXMudmFkLmlzU2lnbmFsRGV0ZWN0ZWQmJih0aGlzLm9uVmFkU2lnbmFsTG93KCksdGhpcy5pc1NpZ25hbERldGVjdGVkPSExKSl9fWNsYXNzIHN7Y29uc3RydWN0b3IodCl7dGhpcy5pc1NpZ25hbERldGVjdGVkPSExLHRoaXMuc2lnbmFsRGI9LTkwLHRoaXMubm9pc2VMZXZlbERiPS05MCx0aGlzLmZyYW1lTWlsbGlzPTMwLHRoaXMuZW5lcmd5PTAsdGhpcy5iYXNlbGluZUVuZXJneT0tMSx0aGlzLmxvdWRGcmFtZUJpdHM9MCx0aGlzLnZhZFN1c3RhaW5NaWxsaXNMZWZ0PTAsdGhpcy52YWRPcHRpb25zPXR9YWRqdXN0VmFkT3B0aW9ucyh0KXt0aGlzLnZhZE9wdGlvbnM9T2JqZWN0LmFzc2lnbihPYmplY3QuYXNzaWduKHt9LHRoaXMudmFkT3B0aW9ucyksdCl9cmVzZXRWQUQoKXt0aGlzLmlzU2lnbmFsRGV0ZWN0ZWQ9ITEsdGhpcy5sb3VkRnJhbWVCaXRzPTAsdGhpcy5lbmVyZ3k9MCx0aGlzLmJhc2VsaW5lRW5lcmd5PS0xfXByb2Nlc3NGcmFtZShlLHM9MCxpPS0xKXtpZighdGhpcy52YWRPcHRpb25zLmVuYWJsZWQpcmV0dXJuIHZvaWQgdGhpcy5yZXNldFZBRCgpO3RoaXMuZW5lcmd5PXQuZ2V0RW5lcmd5KGUscyxpKSx0aGlzLmJhc2VsaW5lRW5lcmd5PDAmJih0aGlzLmJhc2VsaW5lRW5lcmd5PXRoaXMuZW5lcmd5KTtjb25zdCBvPXRoaXMuZW5lcmd5Pk1hdGgubWF4KHQuZGJUb0VuZXJneSh0aGlzLnZhZE9wdGlvbnMubm9pc2VHYXRlRGIpLHRoaXMuYmFzZWxpbmVFbmVyZ3kqdC5kYlRvRW5lcmd5KHRoaXMudmFkT3B0aW9ucy5zaWduYWxUb05vaXNlRGIpKTt0aGlzLnB1c2hGcmFtZUhpc3RvcnkobyksdGhpcy5pc1NpZ25hbERldGVjdGVkPXRoaXMuZGV0ZXJtaW5lTmV3U2lnbmFsU3RhdGUodGhpcy5pc1NpZ25hbERldGVjdGVkKSx0aGlzLmFkYXB0QmFja2dyb3VuZE5vaXNlKCksdGhpcy5zaWduYWxEYj10LmVuZXJneVRvRGIodGhpcy5lbmVyZ3kvdGhpcy5iYXNlbGluZUVuZXJneSksdGhpcy5ub2lzZUxldmVsRGI9dC5lbmVyZ3lUb0RiKHRoaXMuYmFzZWxpbmVFbmVyZ3kpfWRldGVybWluZU5ld1NpZ25hbFN0YXRlKHQpe3RoaXMudmFkU3VzdGFpbk1pbGxpc0xlZnQ9TWF0aC5tYXgodGhpcy52YWRTdXN0YWluTWlsbGlzTGVmdC10aGlzLmZyYW1lTWlsbGlzLDApO2NvbnN0IGU9dGhpcy5jb3VudExvdWRGcmFtZXModGhpcy52YWRPcHRpb25zLnNpZ25hbFNlYXJjaEZyYW1lcykscz1NYXRoLnJvdW5kKHRoaXMudmFkT3B0aW9ucy5zaWduYWxBY3RpdmF0aW9uKnRoaXMudmFkT3B0aW9ucy5zaWduYWxTZWFyY2hGcmFtZXMpLGk9TWF0aC5yb3VuZCh0aGlzLnZhZE9wdGlvbnMuc2lnbmFsUmVsZWFzZSp0aGlzLnZhZE9wdGlvbnMuc2lnbmFsU2VhcmNoRnJhbWVzKTtyZXR1cm4gZT49cz8odGhpcy52YWRTdXN0YWluTWlsbGlzTGVmdD10aGlzLnZhZE9wdGlvbnMuc2lnbmFsU3VzdGFpbk1pbGxpcywhMCk6IShlPD1pJiYwPT09dGhpcy52YWRTdXN0YWluTWlsbGlzTGVmdCkmJnR9YWRhcHRCYWNrZ3JvdW5kTm9pc2UoKXtpZighdGhpcy5pc1NpZ25hbERldGVjdGVkJiZ0aGlzLnZhZE9wdGlvbnMubm9pc2VMZWFybkhhbGZ0aW1lTWlsbGlzPjApe3ZhciB0PU1hdGgucG93KDIsLXRoaXMuZnJhbWVNaWxsaXMvdGhpcy52YWRPcHRpb25zLm5vaXNlTGVhcm5IYWxmdGltZU1pbGxpcyk7dGhpcy5iYXNlbGluZUVuZXJneT10aGlzLmJhc2VsaW5lRW5lcmd5KnQrdGhpcy5lbmVyZ3kqKDEtdCl9fXB1c2hGcmFtZUhpc3RvcnkodCl7dGhpcy5sb3VkRnJhbWVCaXRzPSh0PzE6MCl8dGhpcy5sb3VkRnJhbWVCaXRzPDwxfWNvdW50TG91ZEZyYW1lcyh0KXtsZXQgZT0wLHM9dGhpcy5sb3VkRnJhbWVCaXRzO2Zvcig7dD4wOykxPT0oMSZzKSYmZSsrLHM+Pj0xLHQtLTtyZXR1cm4gZX19dmFyIGksbyxyOyFmdW5jdGlvbih0KXt0LlN0YXJ0ZWQ9InN0YXJ0ZWQiLHQuU3RvcHBlZD0ic3RvcHBlZCIsdC5TZWdtZW50RW5kPSJzZWdtZW50X2VuZCIsdC5UcmFuc2NyaXB0PSJ0cmFuc2NyaXB0Iix0LkVudGl0eT0iZW50aXR5Iix0LkludGVudD0iaW50ZW50Iix0LlRlbnRhdGl2ZVRyYW5zY3JpcHQ9InRlbnRhdGl2ZV90cmFuc2NyaXB0Iix0LlRlbnRhdGl2ZUVudGl0aWVzPSJ0ZW50YXRpdmVfZW50aXRpZXMiLHQuVGVudGF0aXZlSW50ZW50PSJ0ZW50YXRpdmVfaW50ZW50In0oaXx8KGk9e30pKSxmdW5jdGlvbih0KXt0Lk9wZW5lZD0iV0VCU09DS0VUX09QRU4iLHQuQ2xvc2VkPSJXRUJTT0NLRVRfQ0xPU0VEIix0LkF1ZGlvUHJvY2Vzc29yUmVhZHk9IlNPVVJDRV9TQU1QTEVfUkFURV9TRVRfU1VDQ0VTUyIsdC5WYWRTaWduYWxIaWdoPSJWYWRTaWduYWxIaWdoIix0LlZhZFNpZ25hbExvdz0iVmFkU2lnbmFsTG93In0ob3x8KG89e30pKSxmdW5jdGlvbih0KXt0LmNvbm5lY3Q9ImNvbm5lY3QiLHQuaW5pdEF1ZGlvUHJvY2Vzc29yPSJpbml0QXVkaW9Qcm9jZXNzb3IiLHQuYWRqdXN0QXVkaW9Qcm9jZXNzb3I9ImFkanVzdEF1ZGlvUHJvY2Vzc29yIix0LlNFVF9TSEFSRURfQVJSQVlfQlVGRkVSUz0iU0VUX1NIQVJFRF9BUlJBWV9CVUZGRVJTIix0LkNMT1NFPSJDTE9TRSIsdC5TVEFSVF9DT05URVhUPSJTVEFSVF9DT05URVhUIix0LlNXSVRDSF9DT05URVhUPSJTV0lUQ0hfQ09OVEVYVCIsdC5TVE9QX0NPTlRFWFQ9IlNUT1BfQ09OVEVYVCIsdC5BVURJTz0iQVVESU8iLHQuc3RhcnRTdHJlYW09InN0YXJ0U3RyZWFtIix0LnN0b3BTdHJlYW09InN0b3BTdHJlYW0ifShyfHwocj17fSkpO2NvbnN0IGE9MCxuPTEsaD0yO2NsYXNzIGx7Y29uc3RydWN0b3IodCl7dGhpcy50YXJnZXRTYW1wbGVSYXRlPTE2ZTMsdGhpcy5pc0NvbnRleHRTdGFydGVkPSExLHRoaXMuZnJhbWVNaWxsaXM9MzAsdGhpcy5vdXRwdXRBdWRpb0ZyYW1lPW5ldyBJbnQxNkFycmF5KHRoaXMuZnJhbWVNaWxsaXMqdGhpcy50YXJnZXRTYW1wbGVSYXRlLzFlMyksdGhpcy5kZWJ1Zz0hMSx0aGlzLm9uV2Vic29ja2V0Q2xvc2U9dD0+e2lmKCF0aGlzLndlYnNvY2tldCl0aHJvdyBFcnJvcigiV2ViU29ja2V0IGlzIHVuZGVmaW5lZCIpO3RoaXMuZGVidWcmJmNvbnNvbGUubG9nKCJbV2ViU29ja2V0Q2xpZW50XSIsIm9uV2Vic29ja2V0Q2xvc2UiKSx0aGlzLndlYnNvY2tldC5yZW1vdmVFdmVudExpc3RlbmVyKCJvcGVuIix0aGlzLm9uV2Vic29ja2V0T3BlbiksdGhpcy53ZWJzb2NrZXQucmVtb3ZlRXZlbnRMaXN0ZW5lcigibWVzc2FnZSIsdGhpcy5vbldlYnNvY2tldE1lc3NhZ2UpLHRoaXMud2Vic29ja2V0LnJlbW92ZUV2ZW50TGlzdGVuZXIoImVycm9yIix0aGlzLm9uV2Vic29ja2V0RXJyb3IpLHRoaXMud2Vic29ja2V0LnJlbW92ZUV2ZW50TGlzdGVuZXIoImNsb3NlIix0aGlzLm9uV2Vic29ja2V0Q2xvc2UpLHRoaXMud2Vic29ja2V0PXZvaWQgMCx0aGlzLndvcmtlckN0eC5wb3N0TWVzc2FnZSh7dHlwZTpvLkNsb3NlZCxjb2RlOnQuY29kZSxyZWFzb246dC5yZWFzb24sd2FzQ2xlYW46dC53YXNDbGVhbn0pfSx0aGlzLm9uV2Vic29ja2V0T3Blbj10PT57dGhpcy5kZWJ1ZyYmY29uc29sZS5sb2coIltXZWJTb2NrZXRDbGllbnRdIiwid2Vic29ja2V0IG9wZW5lZCIpLHRoaXMud29ya2VyQ3R4LnBvc3RNZXNzYWdlKHt0eXBlOm8uT3BlbmVkfSl9LHRoaXMub25XZWJzb2NrZXRFcnJvcj10PT57dGhpcy5kZWJ1ZyYmY29uc29sZS5sb2coIltXZWJTb2NrZXRDbGllbnRdIiwid2Vic29ja2V0IGVycm9yIil9LHRoaXMub25XZWJzb2NrZXRNZXNzYWdlPXQ9PntsZXQgZTt0cnl7ZT1KU09OLnBhcnNlKHQuZGF0YSl9Y2F0Y2godCl7cmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiW1dlYlNvY2tldENsaWVudF0iLCJlcnJvciBwYXJzaW5nIHJlc3BvbnNlIGZyb20gdGhlIHNlcnZlcjoiLHQpfXRoaXMud29ya2VyQ3R4LnBvc3RNZXNzYWdlKGUpfSx0aGlzLndvcmtlckN0eD10fWNvbm5lY3QodCxlLHMsaSl7dGhpcy5kZWJ1Zz1pLHRoaXMuZGVidWcmJmNvbnNvbGUubG9nKCJbV2ViU29ja2V0Q2xpZW50XSIsImNvbm5lY3RpbmcgdG8gIix0KSx0aGlzLnRhcmdldFNhbXBsZVJhdGU9cyx0aGlzLmlzQ29udGV4dFN0YXJ0ZWQ9ITEsdGhpcy53ZWJzb2NrZXQ9bmV3IFdlYlNvY2tldCh0LGUpLHRoaXMud2Vic29ja2V0LmFkZEV2ZW50TGlzdGVuZXIoIm9wZW4iLHRoaXMub25XZWJzb2NrZXRPcGVuKSx0aGlzLndlYnNvY2tldC5hZGRFdmVudExpc3RlbmVyKCJtZXNzYWdlIix0aGlzLm9uV2Vic29ja2V0TWVzc2FnZSksdGhpcy53ZWJzb2NrZXQuYWRkRXZlbnRMaXN0ZW5lcigiZXJyb3IiLHRoaXMub25XZWJzb2NrZXRFcnJvciksdGhpcy53ZWJzb2NrZXQuYWRkRXZlbnRMaXN0ZW5lcigiY2xvc2UiLHRoaXMub25XZWJzb2NrZXRDbG9zZSl9aW5pdEF1ZGlvUHJvY2Vzc29yKGkscil7dGhpcy5hdWRpb1Byb2Nlc3Nvcj1uZXcgZShpLHRoaXMudGFyZ2V0U2FtcGxlUmF0ZSw1KSxyJiYodGhpcy5hdWRpb1Byb2Nlc3Nvci52YWQ9bmV3IHMociksdGhpcy5hdWRpb1Byb2Nlc3Nvci5vblZhZFNpZ25hbEhpZ2g9KCk9Pnt2YXIgdCxlLHMsaSxyO2NvbnN0IGE9bnVsbD09PShlPW51bGw9PT0odD10aGlzLmF1ZGlvUHJvY2Vzc29yKXx8dm9pZCAwPT09dD92b2lkIDA6dC52YWQpfHx2b2lkIDA9PT1lP3ZvaWQgMDplLnZhZE9wdGlvbnM7YSYmKChudWxsPT09KHM9dGhpcy5kZWZhdWx0Q29udGV4dE9wdGlvbnMpfHx2b2lkIDA9PT1zP3ZvaWQgMDpzLmltbWVkaWF0ZSk/YS5lbmFibGVkJiZhLmNvbnRyb2xMaXN0ZW5pbmcmJnRoaXMuc3RhcnRDb250ZXh0KCk6KG51bGw9PT0ocj1udWxsPT09KGk9dGhpcy5hdWRpb1Byb2Nlc3Nvcil8fHZvaWQgMD09PWk/dm9pZCAwOmkudmFkKXx8dm9pZCAwPT09cj92b2lkIDA6ci52YWRPcHRpb25zLmVuYWJsZWQpJiZhLmVuYWJsZWQmJmEuY29udHJvbExpc3RlbmluZyYmdGhpcy53b3JrZXJDdHgucG9zdE1lc3NhZ2Uoe3R5cGU6by5WYWRTaWduYWxIaWdofSkpfSx0aGlzLmF1ZGlvUHJvY2Vzc29yLm9uVmFkU2lnbmFsTG93PSgpPT57dmFyIHQsZSxzO2NvbnN0IGk9bnVsbD09PShlPW51bGw9PT0odD10aGlzLmF1ZGlvUHJvY2Vzc29yKXx8dm9pZCAwPT09dD92b2lkIDA6dC52YWQpfHx2b2lkIDA9PT1lP3ZvaWQgMDplLnZhZE9wdGlvbnM7aSYmKChudWxsPT09KHM9dGhpcy5kZWZhdWx0Q29udGV4dE9wdGlvbnMpfHx2b2lkIDA9PT1zP3ZvaWQgMDpzLmltbWVkaWF0ZSk/aS5lbmFibGVkJiZpLmNvbnRyb2xMaXN0ZW5pbmcmJnRoaXMuc3RvcENvbnRleHQoKTppLmVuYWJsZWQmJmkuY29udHJvbExpc3RlbmluZyYmdGhpcy53b3JrZXJDdHgucG9zdE1lc3NhZ2Uoe3R5cGU6by5WYWRTaWduYWxMb3d9KSl9KSx0aGlzLmF1ZGlvUHJvY2Vzc29yLnNlbmRBdWRpbz0oZSxzLGkpPT57dC5jb252ZXJ0RmxvYXRUb0ludDE2KGUsdGhpcy5vdXRwdXRBdWRpb0ZyYW1lLHMsaSksdGhpcy5zZW5kKHRoaXMub3V0cHV0QXVkaW9GcmFtZSl9LHRoaXMud29ya2VyQ3R4LnBvc3RNZXNzYWdlKHt0eXBlOm8uQXVkaW9Qcm9jZXNzb3JSZWFkeX0pfWFkanVzdEF1ZGlvUHJvY2Vzc29yKHQpe2lmKCF0aGlzLmF1ZGlvUHJvY2Vzc29yKXRocm93IG5ldyBFcnJvcigiTm8gQXVkaW9Qcm9jZXNzb3IiKTtpZih0LnZhZCl7aWYoIXRoaXMuYXVkaW9Qcm9jZXNzb3IudmFkKXRocm93IG5ldyBFcnJvcigiTm8gVkFEIGluIEF1ZGlvUHJvY2Vzc29yLiBEaWQgeW91IGRlZmluZSBgdmFkYCBpbiBCcm93c2VyQ2xpZW50IGNvbnN0cnVjdG9yIHBhcmFtZXRlcnM/Iik7dGhpcy5hdWRpb1Byb2Nlc3Nvci52YWQuYWRqdXN0VmFkT3B0aW9ucyh0LnZhZCl9fXNldFNoYXJlZEFycmF5QnVmZmVycyh0LGUpe3RoaXMuY29udHJvbFNBQj1uZXcgSW50MzJBcnJheSh0KSx0aGlzLmRhdGFTQUI9bmV3IEZsb2F0MzJBcnJheShlKTtjb25zdCBzPXRoaXMuZGF0YVNBQi5sZW5ndGgvMzI7dGhpcy5kZWJ1ZyYmY29uc29sZS5sb2coIltXZWJTb2NrZXRDbGllbnRdIiwiQXVkaW8gaGFuZGxlIGludGVydmFsIixzLCJtcyIpLHNldEludGVydmFsKHRoaXMucHJvY2Vzc0F1ZGlvU0FCLmJpbmQodGhpcykscyl9c3RhcnRTdHJlYW0odCl7aWYoIXRoaXMuYXVkaW9Qcm9jZXNzb3IpdGhyb3cgbmV3IEVycm9yKCJObyBBdWRpb1Byb2Nlc3NvciIpO3RoaXMuZGVmYXVsdENvbnRleHRPcHRpb25zPXQsdGhpcy5hdWRpb1Byb2Nlc3Nvci5yZXNldFN0cmVhbSgpfXN0b3BTdHJlYW0oKXtpZighdGhpcy5hdWRpb1Byb2Nlc3Nvcil0aHJvdyBuZXcgRXJyb3IoIk5vIEF1ZGlvUHJvY2Vzc29yIik7dGhpcy5pc0NvbnRleHRTdGFydGVkJiZ0aGlzLnN0b3BDb250ZXh0KCksdGhpcy5kZWZhdWx0Q29udGV4dE9wdGlvbnM9dm9pZCAwfXByb2Nlc3NBdWRpbyh0KXtpZighdGhpcy5hdWRpb1Byb2Nlc3Nvcil0aHJvdyBuZXcgRXJyb3IoIk5vIEF1ZGlvUHJvY2Vzc29yIik7dGhpcy5hdWRpb1Byb2Nlc3Nvci5wcm9jZXNzQXVkaW8odCl9cHJvY2Vzc0F1ZGlvU0FCKCl7aWYoIXRoaXMuY29udHJvbFNBQnx8IXRoaXMuZGF0YVNBQil0aHJvdyBuZXcgRXJyb3IoIk5vIFNoYXJlZEFycmF5QnVmZmVycyIpO2NvbnN0IHQ9dGhpcy5jb250cm9sU0FCW25dO2lmKDA9PT10aGlzLmNvbnRyb2xTQUJbaF0mJnQ+MCl7Y29uc3QgZT10aGlzLmRhdGFTQUIuc3ViYXJyYXkoMCx0KTt0aGlzLmNvbnRyb2xTQUJbbl09MCx0aGlzLmNvbnRyb2xTQUJbYV09MCxlLmxlbmd0aD4wJiZ0aGlzLnByb2Nlc3NBdWRpbyhlKX19c3RhcnRDb250ZXh0KHQpe2lmKCF0aGlzLmF1ZGlvUHJvY2Vzc29yKXRocm93IEVycm9yKCJObyBBdWRpb1Byb2Nlc3NvciIpO3RoaXMuaXNDb250ZXh0U3RhcnRlZD9jb25zb2xlLmVycm9yKCJbV2ViU29ja2V0Q2xpZW50XSIsImNhbid0IHN0YXJ0IGNvbnRleHQ6IGFjdGl2ZSBjb250ZXh0IGV4aXN0cyIpOih0aGlzLmF1ZGlvUHJvY2Vzc29yLnN0YXJ0Q29udGV4dCgpLHRoaXMuaXNDb250ZXh0U3RhcnRlZD0hMCx2b2lkIDAhPT10P3RoaXMuc2VuZChKU09OLnN0cmluZ2lmeSh7ZXZlbnQ6InN0YXJ0IixhcHBJZDp0fSkpOnRoaXMuc2VuZChKU09OLnN0cmluZ2lmeSh7ZXZlbnQ6InN0YXJ0In0pKSl9c3RvcENvbnRleHQoKXtpZighdGhpcy5hdWRpb1Byb2Nlc3Nvcil0aHJvdyBFcnJvcigiTm8gQXVkaW9Qcm9jZXNzb3IiKTtpZighdGhpcy5pc0NvbnRleHRTdGFydGVkKXJldHVybiB2b2lkIGNvbnNvbGUuZXJyb3IoIltXZWJTb2NrZXRDbGllbnRdIiwiY2FuJ3Qgc3RvcCBjb250ZXh0OiBubyBhY3RpdmUgY29udGV4dCIpO3RoaXMuYXVkaW9Qcm9jZXNzb3Iuc3RvcENvbnRleHQoKSx0aGlzLmlzQ29udGV4dFN0YXJ0ZWQ9ITE7Y29uc3QgdD1KU09OLnN0cmluZ2lmeSh7ZXZlbnQ6InN0b3AifSk7dGhpcy5zZW5kKHQpfXN3aXRjaENvbnRleHQodCl7aWYoIXRoaXMud2Vic29ja2V0KXRocm93IEVycm9yKCJXZWJTb2NrZXQgaXMgdW5kZWZpbmVkIik7aWYoIXRoaXMuaXNDb250ZXh0U3RhcnRlZClyZXR1cm4gdm9pZCBjb25zb2xlLmVycm9yKCJbV2ViU29ja2V0Q2xpZW50XSIsImNhbid0IHN3aXRjaCBjb250ZXh0OiBubyBhY3RpdmUgY29udGV4dCIpO2lmKHZvaWQgMD09PXQpcmV0dXJuIHZvaWQgY29uc29sZS5lcnJvcigiW1dlYlNvY2tldENsaWVudF0iLCJjYW4ndCBzd2l0Y2ggY29udGV4dDogbmV3IGFwcCBpZCBpcyB1bmRlZmluZWQiKTtjb25zdCBlPUpTT04uc3RyaW5naWZ5KHtldmVudDoic3RvcCJ9KTt0aGlzLnNlbmQoZSksdGhpcy5zZW5kKEpTT04uc3RyaW5naWZ5KHtldmVudDoic3RhcnQiLGFwcElkOnR9KSl9Y2xvc2VXZWJzb2NrZXQodD0xMDA1LGU9Ik5vIFN0YXR1cyBSZWNlaXZlZCIpe2lmKHRoaXMuZGVidWcmJmNvbnNvbGUubG9nKCJbV2ViU29ja2V0Q2xpZW50XSIsIldlYnNvY2tldCBjbG9zaW5nIiksIXRoaXMud2Vic29ja2V0KXRocm93IEVycm9yKCJXZWJTb2NrZXQgaXMgdW5kZWZpbmVkIik7dGhpcy53ZWJzb2NrZXQuY2xvc2UodCxlKX1zZW5kKHQpe2lmKCF0aGlzLndlYnNvY2tldCl0aHJvdyBuZXcgRXJyb3IoIk5vIFdlYnNvY2tldCIpO2lmKHRoaXMud2Vic29ja2V0LnJlYWR5U3RhdGUhPT10aGlzLndlYnNvY2tldC5PUEVOKXRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgT1BFTiBXZWJzb2NrZXQgc3RhdGUsIGJ1dCBnb3QgJHt0aGlzLndlYnNvY2tldC5yZWFkeVN0YXRlfWApO3RyeXt0aGlzLndlYnNvY2tldC5zZW5kKHQpfWNhdGNoKHQpe2NvbnNvbGUubG9nKCJbV2ViU29ja2V0Q2xpZW50XSIsInNlcnZlciBjb25uZWN0aW9uIGVycm9yIix0KX19fWNvbnN0IGQ9c2VsZixjPW5ldyBsKGQpO3JldHVybiBkLm9ubWVzc2FnZT1mdW5jdGlvbih0KXtzd2l0Y2godC5kYXRhLnR5cGUpe2Nhc2Ugci5jb25uZWN0OmMuY29ubmVjdCh0LmRhdGEuYXBpVXJsLHQuZGF0YS5hdXRoVG9rZW4sdC5kYXRhLnRhcmdldFNhbXBsZVJhdGUsdC5kYXRhLmRlYnVnKTticmVhaztjYXNlIHIuaW5pdEF1ZGlvUHJvY2Vzc29yOmMuaW5pdEF1ZGlvUHJvY2Vzc29yKHQuZGF0YS5zb3VyY2VTYW1wbGVSYXRlLHQuZGF0YS52YWRPcHRpb25zKTticmVhaztjYXNlIHIuYWRqdXN0QXVkaW9Qcm9jZXNzb3I6Yy5hZGp1c3RBdWRpb1Byb2Nlc3Nvcih0LmRhdGEucGFyYW1zKTticmVhaztjYXNlIHIuU0VUX1NIQVJFRF9BUlJBWV9CVUZGRVJTOmMuc2V0U2hhcmVkQXJyYXlCdWZmZXJzKHQuZGF0YS5jb250cm9sU0FCLHQuZGF0YS5kYXRhU0FCKTticmVhaztjYXNlIHIuQ0xPU0U6Yy5jbG9zZVdlYnNvY2tldCgxZTMsIkNsb3NlIHJlcXVlc3RlZCBieSBjbGllbnQiKTticmVhaztjYXNlIHIuc3RhcnRTdHJlYW06Yy5zdGFydFN0cmVhbSh0LmRhdGEub3B0aW9ucyk7YnJlYWs7Y2FzZSByLnN0b3BTdHJlYW06Yy5zdG9wU3RyZWFtKCk7YnJlYWs7Y2FzZSByLlNUQVJUX0NPTlRFWFQ6Yy5zdGFydENvbnRleHQodC5kYXRhLmFwcElkKTticmVhaztjYXNlIHIuU1dJVENIX0NPTlRFWFQ6Yy5zd2l0Y2hDb250ZXh0KHQuZGF0YS5hcHBJZCk7YnJlYWs7Y2FzZSByLlNUT1BfQ09OVEVYVDpjLnN0b3BDb250ZXh0KCk7YnJlYWs7Y2FzZSByLkFVRElPOmMucHJvY2Vzc0F1ZGlvKHQuZGF0YS5wYXlsb2FkKTticmVhaztkZWZhdWx0OmNvbnNvbGUubG9nKCJXT1JLRVIiLHQpfX0sbH0oKTsKCg==",this.worker.addEventListener("message",this.onWebsocketMessage)}onResponse(t){this.onResponseCb=t}onClose(t){this.onCloseCb=t}initialize(e,i,n,s){return o(this,void 0,void 0,(function*(){return this.worker.postMessage({type:t.ControllerSignal.connect,apiUrl:e,authToken:i,targetSampleRate:n,debug:s}),this.startCbs=[],this.stopCbs=[],new Promise((t=>{this.resolveInitialization=t}))}))}initAudioProcessor(e,i){return o(this,void 0,void 0,(function*(){return this.worker.postMessage({type:t.ControllerSignal.initAudioProcessor,sourceSampleRate:e,vadOptions:i}),new Promise((t=>{this.resolveSourceSampleRateSet=t}))}))}adjustAudioProcessor(e){this.worker.postMessage({type:t.ControllerSignal.adjustAudioProcessor,params:e})}close(){return o(this,void 0,void 0,(function*(){return new Promise(((e,i)=>{this.worker.postMessage({type:t.ControllerSignal.CLOSE,code:1e3,message:"Client has ended the session"}),e()}))}))}startStream(e){return o(this,void 0,void 0,(function*(){this.worker.postMessage({type:t.ControllerSignal.startStream,options:e})}))}stopStream(){return o(this,void 0,void 0,(function*(){this.worker.postMessage({type:t.ControllerSignal.stopStream})}))}startContext(e){return o(this,void 0,void 0,(function*(){return new Promise(((i,n)=>{this.startCbs.push(((t,e)=>{void 0!==t?n(t):i(e)})),null!=e?this.worker.postMessage({type:t.ControllerSignal.START_CONTEXT,appId:e}):this.worker.postMessage({type:t.ControllerSignal.START_CONTEXT})}))}))}stopContext(){return o(this,void 0,void 0,(function*(){return new Promise(((e,i)=>{this.stopCbs.push(((t,n)=>{void 0!==t?i(t):e(n)})),this.worker.postMessage({type:t.ControllerSignal.STOP_CONTEXT})}))}))}switchContext(e){return o(this,void 0,void 0,(function*(){return new Promise(((i,n)=>{this.startCbs.push(((t,e)=>{void 0!==t?n(t):i(e)})),this.worker.postMessage({type:t.ControllerSignal.SWITCH_CONTEXT,appId:e})}))}))}postMessage(t){this.worker.postMessage(t)}sendAudio(e){this.worker.postMessage({type:t.ControllerSignal.AUDIO,payload:e})}}class I{constructor(){this.storage=window.localStorage}get(t){return this.storage.getItem(t)}set(t,e){this.storage.setItem(t,e)}getOrSet(t,e){let i=this.storage.getItem(t);return null===i&&(i=e(),this.storage.setItem(t,i)),i}}function x(t,e){return{intent:t.intent,isFinal:e}}const f="speechly-auth-token";class k{constructor(e){var i,o,a,d,c,l,r,h;if(this.activeContexts=new Map,this.maxReconnectAttemptCount=10,this.contextStopDelay=250,this.connectAttempt=0,this.connectPromise=null,this.cbs=[],this.state=t.DecoderState.Disconnected,this.handleWebsocketResponse=e=>{switch(this.debug&&console.log("[Decoder]","Received response",e),e.type){case t.WorkerSignal.VadSignalHigh:this.cbs.forEach((t=>t.onVadStateChange.forEach((t=>t(!0)))));break;case t.WorkerSignal.VadSignalLow:this.cbs.forEach((t=>t.onVadStateChange.forEach((t=>t(!1)))));break;case t.WebsocketResponseType.Started:this.activeContexts.set(e.audio_context,new Map),this.cbs.forEach((t=>t.contextStartedCbs.forEach((t=>t(e.audio_context)))));break;case t.WebsocketResponseType.Stopped:this.activeContexts.delete(e.audio_context),this.cbs.forEach((t=>t.contextStoppedCbs.forEach((t=>t(e.audio_context)))));break;default:this.handleSegmentUpdate(e)}},this.handleSegmentUpdate=e=>{var i;const{audio_context:n,segment_id:o,type:a}=e;let{data:d}=e;const c=this.activeContexts.get(n);if(void 0===c)return void console.warn("[Decoder]","Received response for non-existent context",n);let l=null!==(i=c.get(o))&&void 0!==i?i:new s(n,o);switch(a){case t.WebsocketResponseType.TentativeTranscript:const e=function(t){return t.words.map((({word:t,index:e,start_timestamp:i,end_timestamp:n})=>({value:t,index:e,startTimestamp:i,endTimestamp:n,isFinal:!1})))}(d),i=d.transcript;this.cbs.forEach((t=>t.tentativeTranscriptCbs.forEach((t=>t(n,o,e,i))))),l=l.updateTranscript(e);break;case t.WebsocketResponseType.Transcript:const s=function(t){return{value:t.word,index:t.index,startTimestamp:t.start_timestamp,endTimestamp:t.end_timestamp,isFinal:!0}}(d);this.cbs.forEach((t=>t.transcriptCbs.forEach((t=>t(n,o,s))))),l=l.updateTranscript([s]);break;case t.WebsocketResponseType.TentativeEntities:const a=function(t){return t.entities.map((({entity:t,value:e,start_position:i,end_position:n})=>({type:t,value:e,startPosition:i,endPosition:n,isFinal:!1})))}(d);this.cbs.forEach((t=>t.tentativeEntityCbs.forEach((t=>t(n,o,a))))),l=l.updateEntities(a);break;case t.WebsocketResponseType.Entity:const c=function(t){return{type:t.entity,value:t.value,startPosition:t.start_position,endPosition:t.end_position,isFinal:!0}}(d);this.cbs.forEach((t=>t.entityCbs.forEach((t=>t(n,o,c))))),l=l.updateEntities([c]);break;case t.WebsocketResponseType.TentativeIntent:const r=x(d,!1);this.cbs.forEach((t=>t.tentativeIntentCbs.forEach((t=>t(n,o,r))))),l=l.updateIntent(r);break;case t.WebsocketResponseType.Intent:const h=x(d,!0);this.cbs.forEach((t=>t.intentCbs.forEach((t=>t(n,o,h))))),l=l.updateIntent(h);break;case t.WebsocketResponseType.SegmentEnd:l=l.finalize()}c.set(o,l),this.activeContexts.set(n,c),this.logSegments&&console.info(l.toString()),this.cbs.forEach((t=>t.segmentChangeCbs.forEach((t=>t(l.toSegment())))))},this.handleWebsocketClosure=e=>{if(1e3===e.code)this.debug&&console.log("[Decoder]","Websocket closed",e);else{if(console.error("[Decoder]","Websocket closed due to error",e),void 0===this.deviceId)return void this.setState(t.DecoderState.Failed);this.setState(t.DecoderState.Disconnected),this.reconnect()}},this.logSegments=null!==(i=e.logSegments)&&void 0!==i&&i,this.appId=null!==(o=e.appId)&&void 0!==o?o:void 0,this.projectId=null!==(a=e.projectId)&&void 0!==a?a:void 0,this.sampleRate=null!==(d=e.sampleRate)&&void 0!==d?d:n,this.debug=null!==(c=e.debug)&&void 0!==c&&c,void 0!==this.appId&&void 0!==this.projectId)throw Error("[Decoder] You cannot use both appId and projectId at the same time");if(void 0===this.appId&&void 0===this.projectId)throw Error("[Decoder] Either an appId or a projectId is required");const u=null!==(l=e.apiUrl)&&void 0!==l?l:"https://api.speechly.com";this.apiUrl=function(t,e){const i=new URLSearchParams;return i.append("sampleRate",e.toString()),`${t}?${i.toString()}`}(u.replace("http","ws")+"/ws/v1",this.sampleRate),this.loginUrl=`${u}/login`,this.storage=null!==(r=e.storage)&&void 0!==r?r:new I,this.deviceId=this.storage.getOrSet("speechly-device-id",g),this.apiClient=new z,this.apiClient.onResponse(this.handleWebsocketResponse),this.apiClient.onClose(this.handleWebsocketClosure),(null===(h=e.connect)||void 0===h||h)&&this.connect()}getReconnectDelayMs(t){return 100*Math.pow(2,t)}sleep(t){return o(this,void 0,void 0,(function*(){return new Promise((e=>setTimeout(e,t)))}))}connect(){return o(this,void 0,void 0,(function*(){null===this.connectPromise&&(this.connectPromise=(()=>o(this,void 0,void 0,(function*(){const e=this.storage.get(f);if(null!=e&&T(e,this.projectId,this.appId,this.deviceId))this.authToken=e;else try{this.authToken=yield function(t,e,i,n,s=fetch,a=Date.now){var d;return o(this,void 0,void 0,(function*(){let o;o=void 0!==e?{projectId:e,deviceId:n}:{appId:i,deviceId:n};const c=yield s(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}),l=yield c.json();if(200!==c.status)throw Error(null!==(d=l.error)&&void 0!==d?d:`Speechly API login request failed with ${c.status}`);if(void 0===l.access_token)throw Error("Invalid login response from Speechly API");if(!T(l.access_token,e,i,n,a))throw Error("Invalid token received from Speechly API");return l.access_token}))}(this.loginUrl,this.projectId,this.appId,this.deviceId),this.storage.set(f,this.authToken)}catch(e){throw this.setState(t.DecoderState.Failed),e}yield this.apiClient.initialize(this.apiUrl,this.authToken,this.sampleRate,this.debug),this.advanceState(t.DecoderState.Connected)})))()),yield this.connectPromise}))}adjustAudioProcessor(t){this.apiClient.adjustAudioProcessor(t)}close(){return o(this,void 0,void 0,(function*(){let e;try{yield this.apiClient.close()}catch(t){e=t.message}if(this.activeContexts.clear(),this.connectPromise=null,this.setState(t.DecoderState.Disconnected),void 0!==e)throw Error(e)}))}startStream(t){return o(this,void 0,void 0,(function*(){yield this.apiClient.startStream(t)}))}stopStream(){return o(this,void 0,void 0,(function*(){this.state===t.DecoderState.Active&&(yield this.stopContext()),yield this.apiClient.stopStream()}))}startContext(e){return o(this,void 0,void 0,(function*(){if(this.state===t.DecoderState.Failed)throw Error("[Decoder] startContext cannot be run in Failed state.");if(this.state<t.DecoderState.Connected)yield this.connect();else if(this.state>t.DecoderState.Connected)throw Error("[Decoder] Unable to complete startContext: Expected Connected state, but was in "+y(this.state)+".");let n;if(this.setState(t.DecoderState.Active),null!=this.projectId)n=yield this.apiClient.startContext(null==e?void 0:e.appId);else{if(null!=(null==e?void 0:e.appId)&&this.appId!==(null==e?void 0:e.appId))throw this.setState(t.DecoderState.Failed),i;n=yield this.apiClient.startContext()}if(this.state<t.DecoderState.Active)throw Error("[Decoder] Unable to complete startContext: Problem acquiring contextId");return n}))}sendAudio(t){this.apiClient.sendAudio(t)}stopContext(){return o(this,void 0,void 0,(function*(){if(this.state===t.DecoderState.Failed)throw Error("[Decoder] stopContext cannot be run in unrecovable error state.");if(this.state!==t.DecoderState.Active)throw Error("[Decoder] Unable to complete stopContext: Expected Active state, but was in "+y(this.state)+".");this.setState(t.DecoderState.Connected),yield this.sleep(this.contextStopDelay);try{return yield this.apiClient.stopContext()}catch(e){throw this.setState(t.DecoderState.Failed),e}}))}switchContext(e){return o(this,void 0,void 0,(function*(){if(this.state!==t.DecoderState.Active)throw Error("[Decoder] Unable to complete switchContext: Expected Active state, but was in "+y(this.state)+".");const i=yield this.apiClient.switchContext(e);this.activeContexts.set(i,new Map)}))}registerListener(t){this.cbs.push(t)}initAudioProcessor(t,e){return o(this,void 0,void 0,(function*(){this.sampleRate=t,yield this.apiClient.initAudioProcessor(t,e)}))}useSharedArrayBuffers(t,e){this.apiClient.postMessage({type:"SET_SHARED_ARRAY_BUFFERS",controlSAB:t,dataSAB:e})}reconnect(){return o(this,void 0,void 0,(function*(){this.debug&&console.log("[Decoder]","Reconnecting...",this.connectAttempt),this.connectPromise=null,this.connectAttempt<this.maxReconnectAttemptCount?(yield this.sleep(this.getReconnectDelayMs(this.connectAttempt++)),yield this.connect()):console.error("[Decoder] Maximum reconnect count reached, giving up automatic reconnect.")}))}advanceState(t){this.state>=t||this.setState(t)}setState(t){this.state!==t&&(this.debug&&console.log("[Decoder]",y(this.state),"->",y(t)),this.state=t,this.cbs.forEach((e=>{var i;return null===(i=e.stateChangeCbs)||void 0===i?void 0:i.forEach((e=>e(t)))})))}}t.BrowserClient=class{constructor(t){var e,i;this.debug=!1,this.initialized=!1,this.isStreaming=!1,this.isStreamAutoStarted=!1,this.active=!1,this.listeningPromise=null,this.stats={maxSignalEnergy:0,sentSamples:0};const n=window.navigator.mediaDevices.getSupportedConstraints();this.nativeResamplingSupported=!0===n.sampleRate,this.isMobileSafari=["iPad Simulator","iPhone Simulator","iPod Simulator","iPad","iPhone","iPod"].indexOf(navigator.platform)>=0||navigator.userAgent.includes("Mac")&&"ontouchend"in document,this.isSafari=this.isMobileSafari||void 0!==window.safari,this.useSAB=!this.isSafari,this.vadOptions=Object.assign(Object.assign({},b),t.vad),this.debug=null===(e=t.debug)||void 0===e||e,this.callbacks=new Z,this.callbacks.onVadStateChange.push(this.onVadStateChange.bind(this)),this.decoder=null!==(i=t.decoder)&&void 0!==i?i:new k(t),this.decoder.registerListener(this.callbacks)}onVadStateChange(t){var e;this.debug&&console.log("[BrowserClient]","onVadStateChange",t),(null===(e=this.vadOptions)||void 0===e?void 0:e.controlListening)&&(t?this.active||this.start():this.active&&this.stop())}initialize(t){var i,s;return o(this,void 0,void 0,(function*(){if(!this.initialized){this.initialized=!0,this.debug&&console.log("[BrowserClient]","initializing"),yield this.decoder.connect();try{const t={};if(this.nativeResamplingSupported&&(t.sampleRate=n),void 0!==window.webkitAudioContext)try{this.audioContext=new window.webkitAudioContext(t)}catch(t){this.debug&&console.log("[BrowserClient]","creating audioContext without samplerate conversion",t),this.audioContext=new window.webkitAudioContext}else this.audioContext=new window.AudioContext(t),void 0!==window.webkitAudioContext&&(yield this.audioContext.resume())}catch(t){throw e}if(this.isSafari||void 0===window.AudioWorkletNode){if(this.debug&&console.log("[BrowserClient]","using ScriptProcessorNode"),void 0!==window.webkitAudioContext){const t=this.audioContext.sampleRate/n,e=4096*Math.pow(2,Math.ceil(Math.log(t)/Math.log(2)));this.audioProcessor=this.audioContext.createScriptProcessor(e,1,1)}else this.audioProcessor=this.audioContext.createScriptProcessor(void 0,1,1);this.audioProcessor.connect(this.audioContext.destination),this.audioProcessor.addEventListener("audioprocess",(t=>{this.handleAudio(t.inputBuffer.getChannelData(0))}))}else{this.debug&&console.log("[BrowserClient]","using AudioWorkletNode");const t=new Blob(["\n// Indices for the Control SAB.\nconst CONTROL = {\n 'WRITE_INDEX': 0,\n 'FRAMES_AVAILABLE': 1,\n 'LOCK': 2,\n};\n\nclass SpeechlyProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n\n this._initialized = false;\n this.debug = false;\n this.port.onmessage = this._initialize.bind(this);\n }\n\n _initialize(event) {\n this.controlSAB = new Int32Array(event.data.controlSAB);\n this.dataSAB = new Float32Array(event.data.dataSAB);\n this.debug = event.data.debug;\n if (this.debug) {\n console.log('[BrowserClient AudioWorkletNode]', 'initializing audioworklet');\n }\n this.sharedBufferSize = this.dataSAB.length;\n this.buffer = new Float32Array(0);\n this._initialized = true;\n }\n\n _transferDataToSharedBuffer(data) {\n this.controlSAB[CONTROL.LOCK] = 1;\n let inputWriteIndex = this.controlSAB[CONTROL.WRITE_INDEX];\n if (this.controlSAB[CONTROL.FRAMES_AVAILABLE] > 0) {\n if (inputWriteIndex + data.length > this.sharedBufferSize) {\n // console.log('buffer overflow')\n inputWriteIndex = 0;\n }\n }\n this.dataSAB.set(data, inputWriteIndex);\n this.controlSAB[CONTROL.WRITE_INDEX] = inputWriteIndex + data.length;\n this.controlSAB[CONTROL.FRAMES_AVAILABLE] = inputWriteIndex + data.length;\n this.controlSAB[CONTROL.LOCK] = 0;\n }\n\n _pushData(data) {\n if (this.debug) {\n const signalEnergy = getStandardDeviation(data)\n this.port.postMessage({\n type: 'STATS',\n signalEnergy: signalEnergy,\n samples: data.length,\n });\n }\n\n if (this.buffer.length > this.sharedBufferSize) {\n const dataToTransfer = this.buffer.subarray(0, this.sharedBufferSize);\n this._transferDataToSharedBuffer(dataToTransfer);\n this.buffer = this.buffer.subarray(this.sharedBufferSize);\n }\n let concat = new Float32Array(this.buffer.length + data.length);\n concat.set(this.buffer);\n concat.set(data, this.buffer.length);\n this.buffer = concat;\n }\n\n process(inputs, outputs, parameters) {\n const inputChannelData = inputs[0][0];\n if (inputChannelData !== undefined) {\n if (this.controlSAB && this.dataSAB) {\n this._pushData(inputChannelData);\n } else {\n this.port.postMessage({\n type: 'DATA',\n frames: inputChannelData\n });\n }\n }\n\n return true;\n }\n}\n\nfunction getStandardDeviation(array) {\n const n = array.length\n const mean = array.reduce((a, b) => a + b) / n\n return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n)\n}\n\nregisterProcessor('speechly-worklet', SpeechlyProcessor);\n"],{type:"text/javascript"}),e=window.URL.createObjectURL(t);if(yield this.audioContext.audioWorklet.addModule(e),this.speechlyNode=new AudioWorkletNode(this.audioContext,"speechly-worklet"),this.speechlyNode.connect(this.audioContext.destination),this.useSAB&&void 0!==window.SharedArrayBuffer){this.debug&&console.log("[BrowserClient]","using SharedArrayBuffer");const t=new window.SharedArrayBuffer(4*Int32Array.BYTES_PER_ELEMENT),e=new window.SharedArrayBuffer(1024*Float32Array.BYTES_PER_ELEMENT);this.decoder.useSharedArrayBuffers(t,e),this.speechlyNode.port.postMessage({type:"SET_SHARED_ARRAY_BUFFERS",controlSAB:t,dataSAB:e,debug:this.debug})}else this.debug&&console.log("[BrowserClient]","can not use SharedArrayBuffer");this.speechlyNode.port.onmessage=t=>{switch(t.data.type){case"STATS":t.data.signalEnergy>this.stats.maxSignalEnergy&&(this.stats.maxSignalEnergy=t.data.signalEnergy),this.stats.sentSamples+=parseInt(t.data.samples);break;case"DATA":this.handleAudio(t.data.frames)}}}this.debug&&console.log("[BrowserClient]","audioContext sampleRate is",null===(i=this.audioContext)||void 0===i?void 0:i.sampleRate),yield this.decoder.initAudioProcessor(null===(s=this.audioContext)||void 0===s?void 0:s.sampleRate,this.vadOptions),this.vadOptions&&(yield this.startStream()),(null==t?void 0:t.mediaStream)&&(yield this.attach(null==t?void 0:t.mediaStream))}}))}adjustAudioProcessor(t){this.decoder.adjustAudioProcessor(t)}close(){var t,e,i;return o(this,void 0,void 0,(function*(){yield this.detach(),null!==this.speechlyNode&&(null===(t=this.speechlyNode)||void 0===t||t.port.close(),null===(e=this.speechlyNode)||void 0===e||e.disconnect()),void 0!==this.audioProcessor&&(null===(i=this.audioProcessor)||void 0===i||i.disconnect()),yield this.decoder.close(),this.initialized=!1}))}attach(t){var e,i,n,s,a,d;return o(this,void 0,void 0,(function*(){if(yield this.initialize(),yield this.detach(),this.stream=null===(e=this.audioContext)||void 0===e?void 0:e.createMediaStreamSource(t),"running"!==(null===(i=this.audioContext)||void 0===i?void 0:i.state)&&(this.debug&&console.log("[BrowserClient]","audioContext resume required, state is",null===(n=this.audioContext)||void 0===n?void 0:n.state),yield null===(s=this.audioContext)||void 0===s?void 0:s.resume()),this.speechlyNode)null===(a=this.stream)||void 0===a||a.connect(this.speechlyNode);else{if(!this.audioProcessor)throw Error("[BrowserClient] cannot attach to mediaStream, not initialized");null===(d=this.stream)||void 0===d||d.connect(this.audioProcessor)}}))}detach(){return o(this,void 0,void 0,(function*(){this.active&&(yield this.stop()),this.stream&&(this.stream.disconnect(),this.stream=void 0)}))}uploadAudioData(t,e){var i,n,s;return o(this,void 0,void 0,(function*(){yield this.initialize();const o=yield null===(i=this.audioContext)||void 0===i?void 0:i.decodeAudioData(t);if(void 0===o)throw Error("Could not decode audioData");const a=o.getChannelData(0);if(o.numberOfChannels>1){const t=o.getChannelData(1);for(let e=0;e<a.length;e++)a[e]=(a[e]+t[e])/2}let d;yield this.startStream({immediate:!0});const c=(null===(n=this.vadOptions)||void 0===n?void 0:n.enabled)&&(null===(s=this.vadOptions)||void 0===s?void 0:s.controlListening);let l;d=c?"multiple context ids":yield this.start(e);for(let t=0;t<a.length;t+=16e3){const e=t+16e3;l=e>a.length?a.slice(t):a.slice(t,e),this.handleAudio(l)}return c||(yield this.stop()),yield this.stopStream(),d}))}startStream(t){return o(this,void 0,void 0,(function*(){yield this.decoder.startStream(t),this.isStreaming=!0}))}stopStream(){return o(this,void 0,void 0,(function*(){yield this.decoder.stopStream(),this.isStreaming=!1,this.isStreamAutoStarted=!1}))}queueTask(t){return o(this,void 0,void 0,(function*(){const e=this.listeningPromise;return this.listeningPromise=(()=>o(this,void 0,void 0,(function*(){return yield e,t()})))(),this.listeningPromise}))}start(t){return o(this,void 0,void 0,(function*(){return yield this.queueTask((()=>o(this,void 0,void 0,(function*(){yield this.initialize(),this.isStreaming||(yield this.startStream(),this.isStreamAutoStarted=!0);const e=this.decoder.startContext(t);return this.active=!0,e}))))}))}stop(){return o(this,void 0,void 0,(function*(){return yield this.queueTask((()=>o(this,void 0,void 0,(function*(){let t=null;try{t=yield this.decoder.stopContext(),this.isStreaming&&this.isStreamAutoStarted&&(yield this.stopStream()),0===this.stats.sentSamples&&console.warn("[BrowserClient]","audioContext contained no audio data")}catch(t){console.warn("[BrowserClient]","stop() failed",t)}finally{this.active=!1,this.stats.sentSamples=0}return t}))))}))}handleAudio(t){this.isStreaming&&(this.stats.sentSamples+=t.length,this.decoder.sendAudio(t),console.log("handleAudio"))}isActive(){return this.active}onSegmentChange(t){this.callbacks.segmentChangeCbs.push(t)}onTranscript(t){this.callbacks.transcriptCbs.push(t)}onEntity(t){this.callbacks.entityCbs.push(t)}onIntent(t){this.callbacks.intentCbs.push(t)}onTentativeTranscript(t){this.callbacks.tentativeTranscriptCbs.push(t)}onTentativeEntities(t){this.callbacks.tentativeEntityCbs.push(t)}onTentativeIntent(t){this.callbacks.tentativeIntentCbs.push(t)}onStateChange(t){this.callbacks.stateChangeCbs.push(t)}},t.BrowserMicrophone=class{constructor(){this.muted=!1,this.initialized=!1;try{const t=window.navigator.mediaDevices.getSupportedConstraints();this.nativeResamplingSupported=!0===t.sampleRate,this.autoGainControlSupported=!0===t.autoGainControl}catch(t){this.nativeResamplingSupported=!1,this.autoGainControlSupported=!1}}initialize(){var t;return o(this,void 0,void 0,(function*(){if(this.initialized)return;if(void 0===(null===(t=window.navigator)||void 0===t?void 0:t.mediaDevices))throw e;const i={video:!1};this.nativeResamplingSupported||this.autoGainControlSupported?i.audio={sampleRate:n,autoGainControl:this.autoGainControlSupported}:i.audio=!0;try{this.mediaStream=yield window.navigator.mediaDevices.getUserMedia(i)}catch(t){throw console.error(t),c}this.initialized=!0,this.muted=!0}))}close(){return o(this,void 0,void 0,(function*(){if(!this.initialized)throw a;this.muted=!0;this.mediaStream.getTracks().forEach((t=>t.stop())),this.mediaStream=void 0,this.initialized=!1}))}isRecording(){return!this.muted}},t.CloudDecoder=k,t.DefaultSampleRate=n,t.ErrAlreadyInitialized=d,t.ErrAppIdChangeWithoutProjectLogin=i,t.ErrDeviceNotSupported=e,t.ErrKeyNotFound=p,t.ErrNoAudioConsent=c,t.ErrNoStorageSupport=u,t.ErrNotInitialized=a,t.EventCallbacks=Z,t.SegmentState=s,t.VadDefaultOptions=b,t.stateToString=y,Object.defineProperty(t,"__esModule",{value:!0})}));
//# sourceMappingURL=speechly.umd.min.js.map

@@ -1,2 +0,2 @@

import { DecoderState, DecoderOptions, ContextOptions } from './types';
import { DecoderState, DecoderOptions, ContextOptions, AudioProcessorParameters } from './types';
import { Segment, Word, Entity, Intent } from '../speechly';

@@ -17,3 +17,6 @@ /**

private readonly callbacks;
private readonly vadOptions?;
private initialized;
private isStreaming;
private isStreamAutoStarted;
private active;

@@ -23,2 +26,3 @@ private speechlyNode?;

private stream?;
private listeningPromise;
private stats;

@@ -31,6 +35,5 @@ /**

constructor(options: DecoderOptions);
onVadStateChange(active: boolean): void;
/**
* Create an AudioContext for resampling audio.
*
* @param options - shorthand for attaching to existing mediaStream
*/

@@ -41,2 +44,7 @@ initialize(options?: {

/**
* Control audio processor parameters
* @param ap - Audio processor parameters to adjust
*/
adjustAudioProcessor(ap: AudioProcessorParameters): void;
/**
* Closes the client, detaching from any audio source and disconnecting any audio

@@ -64,2 +72,14 @@ * processors.

/**
* If the application starts and resumes the flow of audio, `startStream` should be called at start of a continuous audio stream.
* If you're using VAD that controls starting and stopping audio contexts automatically, you can pass optional inference time options.
* It resets the stream sample counters and history.
*/
startStream(defaultContextOptions?: ContextOptions): Promise<void>;
/**
* If the application starts and resumes the flow of audio, `stopStream` should be called at the end of a continuous audio stream.
* It ensures that all of the internal audio buffers are flushed for processing.
*/
stopStream(): Promise<void>;
private queueTask;
/**
* Starts a new audio context, returning it's id to use for matching received responses.

@@ -66,0 +86,0 @@ * If an active context already exists, an error is thrown.

@@ -1,2 +0,2 @@

import { DecoderOptions, DecoderState, EventCallbacks, ContextOptions } from './types';
import { DecoderOptions, DecoderState, EventCallbacks, ContextOptions, VadOptions, AudioProcessorParameters } from './types';
/**

@@ -23,3 +23,2 @@ * A client for Speechly Spoken Language Understanding (SLU) API. The client handles initializing the websocket

private connectPromise;
private listeningPromise;
private authToken?;

@@ -40,5 +39,12 @@ private readonly cbs;

/**
* Control audio processor parameters
* @param ap - Audio processor parameters to adjust
*/
adjustAudioProcessor(ap: AudioProcessorParameters): void;
/**
* Closes the client by closing the API connection and disabling the microphone.
*/
close(): Promise<void>;
startStream(defaultContextOptions?: ContextOptions): Promise<void>;
stopStream(): Promise<void>;
/**

@@ -64,5 +70,6 @@ * Starts a new SLU context by sending a start context event to the API.

registerListener(listener: EventCallbacks): void;
setSampleRate(sr: number): Promise<void>;
initAudioProcessor(sampleRate: number, vadOptions?: VadOptions): Promise<void>;
useSharedArrayBuffers(controlSAB: any, dataSAB: any): void;
private readonly handleWebsocketResponse;
private readonly handleSegmentUpdate;
private readonly handleWebsocketClosure;

@@ -69,0 +76,0 @@ private reconnect;

@@ -51,4 +51,66 @@ import { Segment, Word, Entity, Intent } from '../speechly';

storage?: Storage;
/**
* Enable voice activity detection (VAD) configuration overrides
*/
vad?: Partial<VadOptions>;
}
/**
* Options for voice activity detection (VAD)
* @public
*/
export interface VadOptions {
/**
* Run energy analysis
*/
enabled: boolean;
/**
* Signal-to-noise energy ratio needed for frame to be 'loud'.
* Default: 3.0 [dB].
*/
signalToNoiseDb: number;
/**
* Energy threshold - below this won't trigger activation.
* Range: -90.0f to 0.0f [dB]. Default: -24 [dB].
*/
noiseGateDb: number;
/**
* Rate of background noise learn. Defined as duration in which background noise energy is moved halfway towards current frame's energy.
* Range: 0, 5000 [ms]. Default: 400 [ms].
*/
noiseLearnHalftimeMillis: number;
/**
* Number of past frames analyzed for energy threshold VAD. Should be less or equal than HistoryFrames.
* Range: 1 to 32 [frames]. Default: 5 [frames].
*/
signalSearchFrames: number;
/**
* Minimum 'signal' to 'silent' frame ratio in history to activate 'IsSignalDetected'
* Range: 0.0 to 1.0. Default: 0.7.
*/
signalActivation: number;
/**
* Maximum 'signal' to 'silent' frame ratio in history to inactivate 'IsSignalDetected'. Only evaluated when the sustain period is over.
* Range: 0.0 to 1.0. Default: 0.2.
*/
signalRelease: number;
/**
* Duration to keep 'IsSignalDetected' active. Renewed as long as VADActivation is holds true.
* Range: 0 to 8000 [ms]. Default: 3000 [ms].
*/
signalSustainMillis: number;
/**
* Enable listening control if you want to use IsSignalDetected to control SLU start / stop.
* Default: true.
*/
controlListening: boolean;
}
export interface AudioProcessorParameters {
vad?: Partial<VadOptions>;
}
/**
* Default options for voice activity detection (VAD)
* @public
*/
export declare const VadDefaultOptions: VadOptions;
/**
* All possible states of a Speechly API client. Failed state is non-recoverable.

@@ -80,2 +142,3 @@ * It is also possible to use arithmetics for state comparison, e.g. `if (state < speechly.ClientState.Disconnected)`,

contextStoppedCbs: Array<(contextId: string) => void>;
onVadStateChange: Array<(active: boolean) => void>;
}

@@ -88,2 +151,7 @@ /**

appId?: string;
/**
* BrowserClient.uploadAudioData internally uses this to set audio worker
* to ‘immediate audio processor’ mode where it can control start/stop context at its own pace.
*/
immediate?: boolean;
}

@@ -10,3 +10,3 @@ /**

private readonly nativeResamplingSupported;
private readonly autoGainControl;
private readonly autoGainControlSupported;
mediaStream?: MediaStream;

@@ -13,0 +13,0 @@ constructor();

@@ -0,1 +1,2 @@

import { AudioProcessorParameters, ContextOptions, VadOptions } from '../client';
/**

@@ -9,3 +10,3 @@ * The interface for response returned by WebSocket client.

*/
type: WebsocketResponseType;
type: WebsocketResponseType | WorkerSignal;
/**

@@ -33,5 +34,2 @@ * Audio context ID.

export declare enum WebsocketResponseType {
Opened = "WEBSOCKET_OPEN",
Closed = "WEBSOCKET_CLOSED",
SourceSampleRateSetSuccess = "SOURCE_SAMPLE_RATE_SET_SUCCESS",
Started = "started",

@@ -48,2 +46,30 @@ Stopped = "stopped",

/**
* Messages from worker to controller
* @public
*/
export declare enum WorkerSignal {
Opened = "WEBSOCKET_OPEN",
Closed = "WEBSOCKET_CLOSED",
AudioProcessorReady = "SOURCE_SAMPLE_RATE_SET_SUCCESS",
VadSignalHigh = "VadSignalHigh",
VadSignalLow = "VadSignalLow"
}
/**
* Messages from controller to worker
* @public
*/
export declare enum ControllerSignal {
connect = "connect",
initAudioProcessor = "initAudioProcessor",
adjustAudioProcessor = "adjustAudioProcessor",
SET_SHARED_ARRAY_BUFFERS = "SET_SHARED_ARRAY_BUFFERS",
CLOSE = "CLOSE",
START_CONTEXT = "START_CONTEXT",
SWITCH_CONTEXT = "SWITCH_CONTEXT",
STOP_CONTEXT = "STOP_CONTEXT",
AUDIO = "AUDIO",
startStream = "startStream",
stopStream = "stopStream"
}
/**
* Transcript response payload.

@@ -178,4 +204,9 @@ * @public

*/
setSourceSampleRate(sourceSampleRate: number): Promise<void>;
initAudioProcessor(sourceSampleRate: number, vadOptions?: VadOptions): Promise<void>;
/**
* Control audio processor parameters
* @param ap - Audio processor parameters to adjust
*/
adjustAudioProcessor(ap: AudioProcessorParameters): void;
/**
* Closes the client.

@@ -215,2 +246,4 @@ *

postMessage(message: Object): void;
startStream(defaultContextOptions?: ContextOptions): Promise<void>;
stopStream(): Promise<void>;
}
import { APIClient, ResponseCallback, CloseCallback } from './types';
import { AudioProcessorParameters, ContextOptions, VadOptions } from '../client';
export declare class WebWorkerController implements APIClient {

@@ -14,4 +15,11 @@ private readonly worker;

initialize(apiUrl: string, authToken: string, targetSampleRate: number, debug: boolean): Promise<void>;
setSourceSampleRate(sourceSampleRate: number): Promise<void>;
initAudioProcessor(sourceSampleRate: number, vadOptions?: VadOptions): Promise<void>;
/**
* Control audio processor parameters
* @param ap - Audio processor parameters to adjust
*/
adjustAudioProcessor(ap: AudioProcessorParameters): void;
close(): Promise<void>;
startStream(defaultContextOptions?: ContextOptions): Promise<void>;
stopStream(): Promise<void>;
startContext(appId?: string): Promise<string>;

@@ -18,0 +26,0 @@ stopContext(): Promise<string>;

@@ -1,2 +0,41 @@

declare const _default: "/**\n * Known WebSocket response types.\n * @public\n */\nvar WebsocketResponseType;\n(function (WebsocketResponseType) {\n WebsocketResponseType[\"Opened\"] = \"WEBSOCKET_OPEN\";\n WebsocketResponseType[\"SourceSampleRateSetSuccess\"] = \"SOURCE_SAMPLE_RATE_SET_SUCCESS\";\n WebsocketResponseType[\"Started\"] = \"started\";\n WebsocketResponseType[\"Stopped\"] = \"stopped\";\n})(WebsocketResponseType || (WebsocketResponseType = {}));\nvar CONTROL = {\n WRITE_INDEX: 0,\n FRAMES_AVAILABLE: 1,\n LOCK: 2\n};\nvar WebsocketClient = /** @class */ (function () {\n function WebsocketClient(ctx) {\n var _this = this;\n this.isContextStarted = false;\n this.isStartContextConfirmed = false;\n this.shouldResendLastFramesSent = false;\n this.buffer = new Float32Array(0);\n this.lastFramesSent = new Int16Array(0); // to re-send after switch context\n this.debug = false;\n this.initialized = false;\n // WebSocket's close handler, called e.g. when\n // - normal close (code 1000)\n // - network unreachable or unable to (re)connect (code 1006)\n // List of CloseEvent.code values: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code\n this.onWebsocketClose = function (event) {\n if (_this.debug) {\n console.log('[WebSocketClient]', 'onWebsocketClose');\n }\n _this.websocket.removeEventListener('open', _this.onWebsocketOpen);\n _this.websocket.removeEventListener('message', _this.onWebsocketMessage);\n _this.websocket.removeEventListener('error', _this.onWebsocketError);\n _this.websocket.removeEventListener('close', _this.onWebsocketClose);\n _this.websocket = undefined;\n _this.workerCtx.postMessage({\n type: 'WEBSOCKET_CLOSED',\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean\n });\n };\n this.onWebsocketOpen = function (_event) {\n if (_this.debug) {\n console.log('[WebSocketClient]', 'websocket opened');\n }\n if (_this.isContextStarted && !_this.isStartContextConfirmed) {\n _this.send(_this.outbox);\n }\n _this.workerCtx.postMessage({ type: 'WEBSOCKET_OPEN' });\n };\n this.onWebsocketError = function (_event) {\n if (_this.debug) {\n console.log('[WebSocketClient]', 'websocket error');\n }\n };\n this.onWebsocketMessage = function (event) {\n var response;\n try {\n response = JSON.parse(event.data);\n }\n catch (e) {\n console.error('[WebSocketClient]', 'error parsing response from the server:', e);\n return;\n }\n if (response.type === WebsocketResponseType.Started) {\n _this.isStartContextConfirmed = true;\n if (_this.shouldResendLastFramesSent) {\n _this.resendLastFrames();\n _this.shouldResendLastFramesSent = false;\n }\n }\n _this.workerCtx.postMessage(response);\n };\n this.workerCtx = ctx;\n }\n WebsocketClient.prototype.init = function (apiUrl, authToken, targetSampleRate, debug) {\n this.debug = debug;\n if (this.debug) {\n console.log('[WebSocketClient]', 'initialize worker');\n }\n this.apiUrl = apiUrl;\n this.authToken = authToken;\n this.targetSampleRate = targetSampleRate;\n this.initialized = true;\n this.isContextStarted = false;\n this.connect(0);\n };\n WebsocketClient.prototype.setSourceSampleRate = function (sourceSampleRate) {\n this.sourceSampleRate = sourceSampleRate;\n this.resampleRatio = this.sourceSampleRate / this.targetSampleRate;\n if (this.debug) {\n console.log('[WebSocketClient]', 'resampleRatio', this.resampleRatio);\n }\n if (this.resampleRatio > 1) {\n this.filter = generateFilter(this.sourceSampleRate, this.targetSampleRate, 127);\n }\n this.workerCtx.postMessage({ type: 'SOURCE_SAMPLE_RATE_SET_SUCCESS' });\n if (isNaN(this.resampleRatio)) {\n throw Error(\"resampleRatio is NaN source rate is \".concat(this.sourceSampleRate, \" and target rate is \").concat(this.targetSampleRate));\n }\n };\n WebsocketClient.prototype.setSharedArrayBuffers = function (controlSAB, dataSAB) {\n this.controlSAB = new Int32Array(controlSAB);\n this.dataSAB = new Float32Array(dataSAB);\n var audioHandleInterval = this.dataSAB.length / 32; // ms\n if (this.debug) {\n console.log('[WebSocketClient]', 'Audio handle interval', audioHandleInterval, 'ms');\n }\n setInterval(this.sendAudioFromSAB.bind(this), audioHandleInterval);\n };\n WebsocketClient.prototype.connect = function (timeout) {\n if (timeout === void 0) { timeout = 1000; }\n if (this.debug) {\n console.log('[WebSocketClient]', 'connect in ', timeout / 1000, 'sec');\n }\n setTimeout(this.initializeWebsocket.bind(this), timeout);\n };\n WebsocketClient.prototype.initializeWebsocket = function () {\n if (this.debug) {\n console.log('[WebSocketClient]', 'connecting to ', this.apiUrl);\n }\n this.websocket = new WebSocket(this.apiUrl, this.authToken);\n this.websocket.addEventListener('open', this.onWebsocketOpen);\n this.websocket.addEventListener('message', this.onWebsocketMessage);\n this.websocket.addEventListener('error', this.onWebsocketError);\n this.websocket.addEventListener('close', this.onWebsocketClose);\n };\n WebsocketClient.prototype.isOpen = function () {\n return this.websocket !== undefined && this.websocket.readyState === this.websocket.OPEN;\n };\n WebsocketClient.prototype.resendLastFrames = function () {\n if (this.lastFramesSent.length > 0) {\n this.send(this.lastFramesSent);\n this.lastFramesSent = new Int16Array(0);\n }\n };\n WebsocketClient.prototype.sendAudio = function (audioChunk) {\n if (!this.isContextStarted) {\n return;\n }\n if (audioChunk.length > 0) {\n if (this.resampleRatio > 1) {\n // Downsampling\n this.send(this.downsample(audioChunk));\n }\n else {\n this.send(float32ToInt16(audioChunk));\n }\n }\n };\n WebsocketClient.prototype.sendAudioFromSAB = function () {\n if (!this.isContextStarted) {\n this.controlSAB[CONTROL.FRAMES_AVAILABLE] = 0;\n this.controlSAB[CONTROL.WRITE_INDEX] = 0;\n return;\n }\n if (this.controlSAB == undefined) {\n return;\n }\n var framesAvailable = this.controlSAB[CONTROL.FRAMES_AVAILABLE];\n var lock = this.controlSAB[CONTROL.LOCK];\n if (lock == 0 && framesAvailable > 0) {\n var data = this.dataSAB.subarray(0, framesAvailable);\n this.controlSAB[CONTROL.FRAMES_AVAILABLE] = 0;\n this.controlSAB[CONTROL.WRITE_INDEX] = 0;\n if (data.length > 0) {\n var frames_1;\n if (this.resampleRatio > 1) {\n frames_1 = this.downsample(data);\n }\n else {\n frames_1 = float32ToInt16(data);\n }\n this.send(frames_1);\n // 16000 per second, 1000 in 100 ms\n // save last 250 ms\n if (this.lastFramesSent.length > 1024 * 4) {\n this.lastFramesSent = frames_1;\n }\n else {\n var concat = new Int16Array(this.lastFramesSent.length + frames_1.length);\n concat.set(this.lastFramesSent);\n concat.set(frames_1, this.lastFramesSent.length);\n this.lastFramesSent = concat;\n }\n }\n }\n };\n WebsocketClient.prototype.startContext = function (appId) {\n if (this.isContextStarted) {\n console.error('[WebSocketClient]', \"can't start context: active context exists\");\n return;\n }\n this.isContextStarted = true;\n this.isStartContextConfirmed = false;\n if (appId !== undefined) {\n this.outbox = JSON.stringify({ event: 'start', appId: appId });\n }\n else {\n this.outbox = JSON.stringify({ event: 'start' });\n }\n this.send(this.outbox);\n };\n WebsocketClient.prototype.stopContext = function () {\n if (!this.websocket) {\n throw Error('WebSocket is undefined');\n }\n if (!this.isContextStarted) {\n console.error('[WebSocketClient]', \"can't stop context: no active context\");\n return;\n }\n this.isContextStarted = false;\n this.isStartContextConfirmed = false;\n var StopEventJSON = JSON.stringify({ event: 'stop' });\n this.send(StopEventJSON);\n };\n WebsocketClient.prototype.switchContext = function (newAppId) {\n if (!this.websocket) {\n throw Error('WebSocket is undefined');\n }\n if (!this.isContextStarted) {\n console.error('[WebSocketClient]', \"can't switch context: no active context\");\n return;\n }\n if (newAppId == undefined) {\n console.error('[WebSocketClient]', \"can't switch context: new app id is undefined\");\n return;\n }\n this.isStartContextConfirmed = false;\n var StopEventJSON = JSON.stringify({ event: 'stop' });\n this.send(StopEventJSON);\n this.shouldResendLastFramesSent = true;\n this.send(JSON.stringify({ event: 'start', appId: newAppId }));\n };\n WebsocketClient.prototype.closeWebsocket = function (websocketCode, reason) {\n if (websocketCode === void 0) { websocketCode = 1005; }\n if (reason === void 0) { reason = 'No Status Received'; }\n if (this.debug) {\n console.log('[WebSocketClient]', 'Websocket closing');\n }\n if (!this.websocket) {\n throw Error('WebSocket is undefined');\n }\n this.websocket.close(websocketCode, reason);\n };\n WebsocketClient.prototype.downsample = function (input) {\n var inputBuffer = new Float32Array(this.buffer.length + input.length);\n inputBuffer.set(this.buffer, 0);\n inputBuffer.set(input, this.buffer.length);\n var outputLength = Math.ceil((inputBuffer.length - this.filter.length) / this.resampleRatio);\n var outputBuffer = new Int16Array(outputLength);\n for (var i = 0; i < outputLength; i++) {\n var offset = Math.round(this.resampleRatio * i);\n var val = 0.0;\n for (var j = 0; j < this.filter.length; j++) {\n val += inputBuffer[offset + j] * this.filter[j];\n }\n outputBuffer[i] = val * (val < 0 ? 0x8000 : 0x7fff);\n }\n var remainingOffset = Math.round(this.resampleRatio * outputLength);\n if (remainingOffset < inputBuffer.length) {\n this.buffer = inputBuffer.subarray(remainingOffset);\n }\n else {\n this.buffer = new Float32Array(0);\n }\n return outputBuffer;\n };\n WebsocketClient.prototype.send = function (data) {\n if (this.isOpen()) {\n try {\n this.websocket.send(data);\n }\n catch (error) {\n console.log('[WebSocketClient]', 'server connection error', error);\n }\n }\n };\n return WebsocketClient;\n}());\nvar ctx = self;\nvar websocketClient = new WebsocketClient(ctx);\nctx.onmessage = function (e) {\n switch (e.data.type) {\n case 'INIT':\n websocketClient.init(e.data.apiUrl, e.data.authToken, e.data.targetSampleRate, e.data.debug);\n break;\n case 'SET_SOURCE_SAMPLE_RATE':\n websocketClient.setSourceSampleRate(e.data.sourceSampleRate);\n break;\n case 'SET_SHARED_ARRAY_BUFFERS':\n websocketClient.setSharedArrayBuffers(e.data.controlSAB, e.data.dataSAB);\n break;\n case 'CLOSE':\n websocketClient.closeWebsocket(1000, 'Close requested by client');\n break;\n case 'START_CONTEXT':\n websocketClient.startContext(e.data.appId);\n break;\n case 'SWITCH_CONTEXT':\n websocketClient.switchContext(e.data.appId);\n break;\n case 'STOP_CONTEXT':\n websocketClient.stopContext();\n break;\n case 'AUDIO':\n websocketClient.sendAudio(e.data.payload);\n break;\n default:\n console.log('WORKER', e);\n }\n};\nfunction float32ToInt16(buffer) {\n var buf = new Int16Array(buffer.length);\n for (var l = 0; l < buffer.length; l++) {\n buf[l] = buffer[l] * (buffer[l] < 0 ? 0x8000 : 0x7fff);\n }\n return buf;\n}\nfunction generateFilter(sourceSampleRate, targetSampleRate, length) {\n if (length % 2 === 0) {\n throw Error('Filter length must be odd');\n }\n var cutoff = targetSampleRate / 2;\n var filter = new Float32Array(length);\n var sum = 0;\n for (var i = 0; i < length; i++) {\n var x = sinc(((2 * cutoff) / sourceSampleRate) * (i - (length - 1) / 2));\n sum += x;\n filter[i] = x;\n }\n for (var i = 0; i < length; i++) {\n filter[i] = filter[i] / sum;\n }\n return filter;\n}\nfunction sinc(x) {\n if (x === 0.0) {\n return 1.0;\n }\n var piX = Math.PI * x;\n return Math.sin(piX) / piX;\n}\n";
export default _default;
import { AudioProcessorParameters, ContextOptions, VadOptions } from '../client';
declare class WebsocketClient {
private readonly workerCtx;
private targetSampleRate;
private isContextStarted;
private websocket?;
private audioProcessor?;
private controlSAB?;
private dataSAB?;
private readonly frameMillis;
private readonly outputAudioFrame;
private debug;
private defaultContextOptions?;
constructor(ctx: Worker);
connect(apiUrl: string, authToken: string, targetSampleRate: number, debug: boolean): void;
initAudioProcessor(sourceSampleRate: number, vadOptions?: VadOptions): void;
/**
* Control audio processor parameters
* @param ap - Audio processor parameters to adjust
*/
adjustAudioProcessor(ap: AudioProcessorParameters): void;
setSharedArrayBuffers(controlSAB: number, dataSAB: number): void;
startStream(defaultContextOptions?: ContextOptions): void;
stopStream(): void;
/**
* Processes and sends audio
* @param audioChunk - audio data to process
*/
processAudio(audioChunk: Float32Array): void;
processAudioSAB(): void;
startContext(appId?: string): void;
stopContext(): void;
switchContext(newAppId: string): void;
closeWebsocket(websocketCode?: number, reason?: string): void;
private readonly onWebsocketClose;
private readonly onWebsocketOpen;
private readonly onWebsocketError;
private readonly onWebsocketMessage;
send(data: string | Int16Array): void;
}
export default WebsocketClient;
{
"name": "@speechly/browser-client",
"version": "2.0.1",
"version": "2.1.0-beta.0",
"description": "Browser client for Speechly API",

@@ -24,13 +24,2 @@ "keywords": [

],
"scripts": {
"build": "pnpm run lint && pnpm run test 2>&1 && rm -rf ./dist/ && pnpm run buildworker && pnpx rollup -c --silent",
"build:watch": "rm -rf ./dist/ && pnpm run buildworker && pnpx rollup -c --silent",
"buildworker": "pnpx tsc ./worker/worker.ts && cat ./worker/templateOpen > ./src/websocket/worker.ts && cat ./worker/worker.js >> ./src/websocket/worker.ts && cat ./worker/templateEnd >> ./src/websocket/worker.ts",
"check": "pnpm run build && pnpx api-extractor run --verbose",
"docs": "rimraf docs && pnpx typedoc --readme none --excludeExternals --excludePrivate --excludeProtected --out ./docs/ --entryPointStrategy expand --sort required-first --disableSources ./src/",
"getdeps": "pnpm install --force --frozen-lockfile",
"lint": "pnpx eslint --cache --max-warnings 0 'src/**/*.{ts,tsx}'",
"precommit": "pnpx prettier --write src/**/*.ts && pnpm run build && pnpx api-extractor run --local && pnpm run docs",
"test": "pnpx jest --config ./config/jest.config.js"
},
"repository": {

@@ -44,3 +33,6 @@ "type": "git",

},
"files": ["core/**/*", "src/**/*"],
"files": [
"core/**/*",
"src/**/*"
],
"main": "./core/speechly.umd.min.js",

@@ -85,3 +77,4 @@ "module": "./core/speechly.es.js",

"typedoc-plugin-markdown": "^3.11.3",
"@rollup/plugin-typescript": "~8.3.2"
"@rollup/plugin-typescript": "~8.3.2",
"rollup-plugin-web-worker-loader": "~1.6.1"
},

@@ -93,3 +86,13 @@ "publishConfig": {

"**/optimist/minimist": "0.2.1"
},
"scripts": {
"build": "pnpm run lint && pnpm run test 2>&1 && rm -rf ./dist/ && pnpx rollup -c --silent",
"build:watch": "rm -rf ./dist/ && pnpx rollup -c --silent",
"check": "pnpm run build && pnpx api-extractor run --verbose",
"docs": "rimraf docs && pnpx typedoc --readme none --excludeExternals --excludePrivate --excludeProtected --out ./docs/ --entryPointStrategy expand --sort required-first --disableSources ./src/",
"getdeps": "pnpm install --force --frozen-lockfile",
"lint": "pnpx eslint --cache --max-warnings 0 'src/**/*.{ts,tsx}'",
"precommit": "pnpx prettier --write src/**/*.ts && pnpm run build && pnpx api-extractor run --local && pnpm run docs",
"test": "pnpx jest --config ./config/jest.config.js"
}
}
}

@@ -1,2 +0,2 @@

import { DecoderState, EventCallbacks, DecoderOptions, ContextOptions } from './types'
import { DecoderState, EventCallbacks, DecoderOptions, ContextOptions, VadOptions, VadDefaultOptions, AudioProcessorParameters } from './types'
import { CloudDecoder } from './decoder'

@@ -22,3 +22,6 @@ import { ErrDeviceNotSupported, DefaultSampleRate, Segment, Word, Entity, Intent } from '../speechly'

private readonly vadOptions?: VadOptions
private initialized: boolean = false
private isStreaming: boolean = false
private isStreamAutoStarted: boolean = false
private active: boolean = false

@@ -28,2 +31,3 @@ private speechlyNode?: AudioWorkletNode

private stream?: MediaStreamAudioSourceNode
private listeningPromise: Promise<any> | null = null

@@ -48,5 +52,7 @@ private stats = {

this.useSAB = !this.isSafari
this.vadOptions = { ...VadDefaultOptions, ...options.vad }
this.debug = options.debug ?? true
this.callbacks = new EventCallbacks()
this.callbacks.onVadStateChange.push(this.onVadStateChange.bind(this))
this.decoder = options.decoder ?? new CloudDecoder(options)

@@ -56,6 +62,19 @@ this.decoder.registerListener(this.callbacks)

onVadStateChange(active: boolean): void {
if (this.debug) {
console.log('[BrowserClient]', 'onVadStateChange', active)
}
if (this.vadOptions?.controlListening) {
if (active) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
if (!this.active) this.start()
} else {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
if (this.active) this.stop()
}
}
}
/**
* Create an AudioContext for resampling audio.
*
* @param options - shorthand for attaching to existing mediaStream
*/

@@ -177,3 +196,9 @@ async initialize(options?: { mediaStream?: MediaStream }): Promise<void> {

}
await this.decoder.setSampleRate(this.audioContext?.sampleRate)
await this.decoder.initAudioProcessor(this.audioContext?.sampleRate, this.vadOptions)
// Auto-start stream if VAD is defined
if (this.vadOptions) {
await this.startStream()
}
if (options?.mediaStream) {

@@ -185,2 +210,10 @@ await this.attach(options?.mediaStream)

/**
* Control audio processor parameters
* @param ap - Audio processor parameters to adjust
*/
adjustAudioProcessor(ap: AudioProcessorParameters): void {
this.decoder.adjustAudioProcessor(ap)
}
/**
* Closes the client, detaching from any audio source and disconnecting any audio

@@ -263,4 +296,12 @@ * processors.

const contextId = await this.start(options)
await this.startStream({ immediate: true })
let contextId: string
const vadActive = this.vadOptions?.enabled && this.vadOptions?.controlListening
if (!vadActive) {
contextId = await this.start(options)
} else {
contextId = 'multiple context ids'
}
let sendBuffer: Float32Array

@@ -277,3 +318,8 @@ for (let b = 0; b < samples.length; b += 16000) {

await this.stop()
if (!vadActive) {
await this.stop()
}
await this.stopStream()
return contextId

@@ -283,2 +329,31 @@ }

/**
* If the application starts and resumes the flow of audio, `startStream` should be called at start of a continuous audio stream.
* If you're using VAD that controls starting and stopping audio contexts automatically, you can pass optional inference time options.
* It resets the stream sample counters and history.
*/
async startStream(defaultContextOptions?: ContextOptions): Promise<void> {
await this.decoder.startStream(defaultContextOptions)
this.isStreaming = true
}
/**
* If the application starts and resumes the flow of audio, `stopStream` should be called at the end of a continuous audio stream.
* It ensures that all of the internal audio buffers are flushed for processing.
*/
async stopStream(): Promise<void> {
await this.decoder.stopStream()
this.isStreaming = false
this.isStreamAutoStarted = false
}
private async queueTask(task: () => Promise<any>): Promise<any> {
const prevTask = this.listeningPromise
this.listeningPromise = (async () => {
await prevTask
return task()
})()
return this.listeningPromise
}
/**
* Starts a new audio context, returning it's id to use for matching received responses.

@@ -291,6 +366,14 @@ * If an active context already exists, an error is thrown.

async start(options?: ContextOptions): Promise<string> {
await this.initialize()
const startPromise = this.decoder.startContext(options)
this.active = true
return startPromise
const promise = await this.queueTask(async () => {
await this.initialize()
if (!this.isStreaming) {
// Automatically control streaming for backwards compability
await this.startStream()
this.isStreamAutoStarted = true
}
const startPromise = this.decoder.startContext(options)
this.active = true
return startPromise
})
return promise
}

@@ -305,14 +388,22 @@

async stop(): Promise<string | null> {
let contextId = null
try {
contextId = await this.decoder.stopContext()
if (this.stats.sentSamples === 0) {
console.warn('[BrowserClient]', 'audioContext contained no audio data')
const contextId = await this.queueTask(async () => {
let contextId = null
try {
contextId = await this.decoder.stopContext()
if (this.isStreaming && this.isStreamAutoStarted) {
// Automatically control streaming for backwards compability
await this.stopStream()
}
if (this.stats.sentSamples === 0) {
console.warn('[BrowserClient]', 'audioContext contained no audio data')
}
} catch (err) {
console.warn('[BrowserClient]', 'stop() failed', err)
} finally {
this.active = false
this.stats.sentSamples = 0
}
} catch (err) {
console.warn('[BrowserClient]', 'stop() failed', err)
} finally {
this.active = false
this.stats.sentSamples = 0
}
return contextId
})
return contextId

@@ -322,8 +413,6 @@ }

private handleAudio(array: Float32Array): void {
if (!this.active) {
return
}
if (array.length > 0) {
if (this.isStreaming) {
this.stats.sentSamples += array.length
this.decoder.sendAudio(array)
console.log('handleAudio')
}

@@ -330,0 +419,0 @@ }

@@ -17,2 +17,3 @@ import { v4 as uuidv4 } from 'uuid'

IntentResponse,
WorkerSignal,
} from '../websocket'

@@ -22,3 +23,3 @@

import { DecoderOptions, DecoderState, EventCallbacks, ContextOptions } from './types'
import { DecoderOptions, DecoderState, EventCallbacks, ContextOptions, VadOptions, AudioProcessorParameters } from './types'
import { stateToString } from './state'

@@ -55,3 +56,2 @@

private connectPromise: Promise<void> | null = null
private listeningPromise: Promise<any> | null = null

@@ -135,2 +135,10 @@ private authToken?: string

/**
* Control audio processor parameters
* @param ap - Audio processor parameters to adjust
*/
adjustAudioProcessor(ap: AudioProcessorParameters): void {
this.apiClient.adjustAudioProcessor(ap)
}
/**
* Closes the client by closing the API connection and disabling the microphone.

@@ -157,2 +165,13 @@ */

async startStream(defaultContextOptions?: ContextOptions): Promise<void> {
await this.apiClient.startStream(defaultContextOptions)
}
async stopStream(): Promise<void> {
if (this.state === DecoderState.Active) {
await this.stopContext()
}
await this.apiClient.stopStream()
}
/**

@@ -192,4 +211,2 @@ * Starts a new SLU context by sending a start context event to the API.

this.activeContexts.set(contextId, new Map<number, SegmentState>())
this.cbs.forEach(cb => cb.contextStartedCbs.forEach(f => f(contextId)))
return contextId

@@ -202,9 +219,2 @@ }

sendAudio(audio: Float32Array): void {
if (this.state !== DecoderState.Active) {
throw Error(
'[Decoder] Unable to complete startContext: Expected Active state, but was in ' +
stateToString(this.state) +
'.',
)
}
this.apiClient.sendAudio(audio)

@@ -232,4 +242,2 @@ }

const contextId = await this.apiClient.stopContext()
this.activeContexts.delete(contextId)
this.cbs.forEach(cb => cb.contextStoppedCbs.forEach(f => f(contextId)))
return contextId

@@ -263,5 +271,5 @@ } catch (err) {

async setSampleRate(sr: number): Promise<void> {
this.sampleRate = sr
await this.apiClient.setSourceSampleRate(sr)
async initAudioProcessor(sampleRate: number, vadOptions?: VadOptions): Promise<void> {
this.sampleRate = sampleRate
await this.apiClient.initAudioProcessor(sampleRate, vadOptions)
}

@@ -282,2 +290,26 @@

switch (response.type) {
case WorkerSignal.VadSignalHigh:
this.cbs.forEach(cb => cb.onVadStateChange.forEach(f => f(true)))
break
case WorkerSignal.VadSignalLow:
this.cbs.forEach(cb => cb.onVadStateChange.forEach(f => f(false)))
break
case WebsocketResponseType.Started: {
this.activeContexts.set(response.audio_context, new Map<number, SegmentState>())
this.cbs.forEach(cb => cb.contextStartedCbs.forEach(f => f(response.audio_context)))
break
}
case WebsocketResponseType.Stopped: {
this.activeContexts.delete(response.audio_context)
this.cbs.forEach(cb => cb.contextStoppedCbs.forEach(f => f(response.audio_context)))
break
}
default:
this.handleSegmentUpdate(response)
break
}
}
private readonly handleSegmentUpdate = (response: WebsocketResponse): void => {
const { audio_context, segment_id, type } = response

@@ -369,5 +401,2 @@ let { data } = response

// Reset
this.listeningPromise = null
this.setState(DecoderState.Disconnected)

@@ -374,0 +403,0 @@ // eslint-disable-next-line @typescript-eslint/no-floating-promises

@@ -61,5 +61,89 @@ import { Segment, Word, Entity, Intent } from '../speechly'

storage?: Storage
/**
* Enable voice activity detection (VAD) configuration overrides
*/
vad?: Partial<VadOptions>
}
/**
* Options for voice activity detection (VAD)
* @public
*/
export interface VadOptions {
/**
* Run energy analysis
*/
enabled: boolean
/**
* Signal-to-noise energy ratio needed for frame to be 'loud'.
* Default: 3.0 [dB].
*/
signalToNoiseDb: number
/**
* Energy threshold - below this won't trigger activation.
* Range: -90.0f to 0.0f [dB]. Default: -24 [dB].
*/
noiseGateDb: number
/**
* Rate of background noise learn. Defined as duration in which background noise energy is moved halfway towards current frame's energy.
* Range: 0, 5000 [ms]. Default: 400 [ms].
*/
noiseLearnHalftimeMillis: number
/**
* Number of past frames analyzed for energy threshold VAD. Should be less or equal than HistoryFrames.
* Range: 1 to 32 [frames]. Default: 5 [frames].
*/
signalSearchFrames: number
/**
* Minimum 'signal' to 'silent' frame ratio in history to activate 'IsSignalDetected'
* Range: 0.0 to 1.0. Default: 0.7.
*/
signalActivation: number
/**
* Maximum 'signal' to 'silent' frame ratio in history to inactivate 'IsSignalDetected'. Only evaluated when the sustain period is over.
* Range: 0.0 to 1.0. Default: 0.2.
*/
signalRelease: number
/**
* Duration to keep 'IsSignalDetected' active. Renewed as long as VADActivation is holds true.
* Range: 0 to 8000 [ms]. Default: 3000 [ms].
*/
signalSustainMillis: number
/**
* Enable listening control if you want to use IsSignalDetected to control SLU start / stop.
* Default: true.
*/
controlListening: boolean
}
export interface AudioProcessorParameters {
vad?: Partial<VadOptions>
}
/**
* Default options for voice activity detection (VAD)
* @public
*/
export const VadDefaultOptions: VadOptions = {
enabled: false,
controlListening: true,
signalToNoiseDb: 3.0,
noiseGateDb: -24.0,
noiseLearnHalftimeMillis: 400,
signalSearchFrames: 5,
signalActivation: 0.7,
signalRelease: 0.2,
signalSustainMillis: 3000,
}
/**
* All possible states of a Speechly API client. Failed state is non-recoverable.

@@ -93,2 +177,3 @@ * It is also possible to use arithmetics for state comparison, e.g. `if (state < speechly.ClientState.Disconnected)`,

contextStoppedCbs: Array<(contextId: string) => void> = []
onVadStateChange: Array<(active: boolean) => void> = []
}

@@ -102,2 +187,8 @@

appId?: string
/**
* BrowserClient.uploadAudioData internally uses this to set audio worker
* to ‘immediate audio processor’ mode where it can control start/stop context at its own pace.
*/
immediate?: boolean
}
interface Window {
webkitAudioContext: typeof AudioContext
}
declare module 'web-worker:*' {
const WorkerFactory: new () => Worker
export default WorkerFactory
}

@@ -14,3 +14,3 @@ import { ErrDeviceNotSupported, DefaultSampleRate } from '../speechly'

private readonly nativeResamplingSupported: boolean
private readonly autoGainControl: boolean
private readonly autoGainControlSupported: boolean

@@ -24,6 +24,6 @@ // The media stream and audio track are initialized during `initialize()` call.

this.nativeResamplingSupported = constraints.sampleRate === true
this.autoGainControl = constraints.autoGainControl === true
this.autoGainControlSupported = constraints.autoGainControl === true
} catch {
this.nativeResamplingSupported = false
this.autoGainControl = false
this.autoGainControlSupported = false
}

@@ -51,7 +51,7 @@ }

if (this.nativeResamplingSupported || this.autoGainControl) {
if (this.nativeResamplingSupported || this.autoGainControlSupported) {
mediaStreamConstraints.audio = {
sampleRate: DefaultSampleRate,
// @ts-ignore
autoGainControl: this.autoGainControl,
autoGainControl: this.autoGainControlSupported,
}

@@ -58,0 +58,0 @@ } else {

@@ -0,1 +1,3 @@

import { AudioProcessorParameters, ContextOptions, VadOptions } from '../client'
/**

@@ -9,3 +11,3 @@ * The interface for response returned by WebSocket client.

*/
type: WebsocketResponseType
type: WebsocketResponseType | WorkerSignal

@@ -37,5 +39,2 @@ /**

export enum WebsocketResponseType {
Opened = 'WEBSOCKET_OPEN',
Closed = 'WEBSOCKET_CLOSED',
SourceSampleRateSetSuccess = 'SOURCE_SAMPLE_RATE_SET_SUCCESS',
Started = 'started',

@@ -53,2 +52,32 @@ Stopped = 'stopped',

/**
* Messages from worker to controller
* @public
*/
export enum WorkerSignal {
Opened = 'WEBSOCKET_OPEN',
Closed = 'WEBSOCKET_CLOSED',
AudioProcessorReady = 'SOURCE_SAMPLE_RATE_SET_SUCCESS',
VadSignalHigh = 'VadSignalHigh',
VadSignalLow = 'VadSignalLow',
}
/**
* Messages from controller to worker
* @public
*/
export enum ControllerSignal {
connect = 'connect',
initAudioProcessor = 'initAudioProcessor',
adjustAudioProcessor = 'adjustAudioProcessor',
SET_SHARED_ARRAY_BUFFERS = 'SET_SHARED_ARRAY_BUFFERS',
CLOSE = 'CLOSE',
START_CONTEXT = 'START_CONTEXT',
SWITCH_CONTEXT = 'SWITCH_CONTEXT',
STOP_CONTEXT = 'STOP_CONTEXT',
AUDIO = 'AUDIO',
startStream = 'startStream',
stopStream = 'stopStream',
}
/**
* Transcript response payload.

@@ -197,5 +226,11 @@ * @public

*/
setSourceSampleRate(sourceSampleRate: number): Promise<void>
initAudioProcessor(sourceSampleRate: number, vadOptions?: VadOptions): Promise<void>
/**
* Control audio processor parameters
* @param ap - Audio processor parameters to adjust
*/
adjustAudioProcessor(ap: AudioProcessorParameters): void
/**
* Closes the client.

@@ -240,2 +275,7 @@ *

postMessage(message: Object): void
startStream(defaultContextOptions?: ContextOptions): Promise<void>
stopStream(): Promise<void>
}

@@ -1,3 +0,4 @@

import { APIClient, ResponseCallback, CloseCallback, WebsocketResponse, WebsocketResponseType } from './types'
import worker from './worker'
import { APIClient, ResponseCallback, CloseCallback, WebsocketResponse, WebsocketResponseType, WorkerSignal, ControllerSignal } from './types'
import WebsocketClient from 'web-worker:./worker'
import { AudioProcessorParameters, ContextOptions, VadOptions } from '../client'

@@ -25,5 +26,5 @@ type ContextCallback = (err?: Error, contextId?: string) => void

constructor() {
const blob = new Blob([worker], { type: 'text/javascript' })
const blobURL = window.URL.createObjectURL(blob)
this.worker = new Worker(blobURL)
// const blob = new Blob([worker], { type: 'text/javascript' })
// const blobURL = window.URL.createObjectURL(blob)
this.worker = new WebsocketClient()
this.worker.addEventListener('message', this.onWebsocketMessage)

@@ -34,3 +35,3 @@ }

this.worker.postMessage({
type: 'INIT',
type: ControllerSignal.connect,
apiUrl,

@@ -51,6 +52,7 @@ authToken,

async setSourceSampleRate(sourceSampleRate: number): Promise<void> {
async initAudioProcessor(sourceSampleRate: number, vadOptions?: VadOptions): Promise<void> {
this.worker.postMessage({
type: 'SET_SOURCE_SAMPLE_RATE',
sourceSampleRate,
type: ControllerSignal.initAudioProcessor,
sourceSampleRate: sourceSampleRate,
vadOptions: vadOptions,
})

@@ -63,6 +65,17 @@

/**
* Control audio processor parameters
* @param ap - Audio processor parameters to adjust
*/
adjustAudioProcessor(ap: AudioProcessorParameters): void {
this.worker.postMessage({
type: ControllerSignal.adjustAudioProcessor,
params: ap,
})
}
async close(): Promise<void> {
return new Promise((resolve, reject) => {
this.worker.postMessage({
type: 'CLOSE',
type: ControllerSignal.CLOSE,
code: 1000,

@@ -75,2 +88,10 @@ message: 'Client has ended the session',

async startStream(defaultContextOptions?: ContextOptions): Promise<void> {
this.worker.postMessage({ type: ControllerSignal.startStream, options: defaultContextOptions })
}
async stopStream(): Promise<void> {
this.worker.postMessage({ type: ControllerSignal.stopStream })
}
async startContext(appId?: string): Promise<string> {

@@ -86,5 +107,5 @@ return new Promise((resolve, reject) => {

if (appId != null) {
this.worker.postMessage({ type: 'START_CONTEXT', appId })
this.worker.postMessage({ type: ControllerSignal.START_CONTEXT, appId })
} else {
this.worker.postMessage({ type: 'START_CONTEXT' })
this.worker.postMessage({ type: ControllerSignal.START_CONTEXT })
}

@@ -104,3 +125,3 @@ })

this.worker.postMessage({ type: 'STOP_CONTEXT' })
this.worker.postMessage({ type: ControllerSignal.STOP_CONTEXT })
})

@@ -118,3 +139,3 @@ }

})
this.worker.postMessage({ type: 'SWITCH_CONTEXT', appId })
this.worker.postMessage({ type: ControllerSignal.SWITCH_CONTEXT, appId })
})

@@ -128,3 +149,3 @@ }

sendAudio(audioChunk: Float32Array): void {
this.worker.postMessage({ type: 'AUDIO', payload: audioChunk })
this.worker.postMessage({ type: ControllerSignal.AUDIO, payload: audioChunk })
}

@@ -135,3 +156,3 @@

switch (response.type) {
case WebsocketResponseType.Opened:
case WorkerSignal.Opened:
if (this.resolveInitialization != null) {

@@ -141,3 +162,3 @@ this.resolveInitialization()

break
case WebsocketResponseType.Closed:
case WorkerSignal.Closed:
this.onCloseCb({

@@ -149,3 +170,3 @@ code: event.data.code,

break
case WebsocketResponseType.SourceSampleRateSetSuccess:
case WorkerSignal.AudioProcessorReady:
if (this.resolveSourceSampleRateSet != null) {

@@ -156,2 +177,3 @@ this.resolveSourceSampleRateSet()

case WebsocketResponseType.Started:
this.onResponseCb(response)
this.startCbs.forEach(cb => {

@@ -167,2 +189,3 @@ try {

case WebsocketResponseType.Stopped:
this.onResponseCb(response)
this.stopCbs.forEach(cb => {

@@ -169,0 +192,0 @@ try {

@@ -1,349 +0,383 @@

export default `/**
* Known WebSocket response types.
import AudioProcessor from '../audioprocessing/AudioProcessor'
import EnergyTresholdVAD from '../audioprocessing/EnergyTresholdVAD'
import AudioTools from '../audioprocessing/AudioTools'
import { ControllerSignal, WebsocketResponseType, WorkerSignal } from './types'
import { AudioProcessorParameters, ContextOptions, VadOptions } from '../client'
/**
* The interface for response returned by WebSocket client.
* @public
*/
var WebsocketResponseType;
(function (WebsocketResponseType) {
WebsocketResponseType["Opened"] = "WEBSOCKET_OPEN";
WebsocketResponseType["SourceSampleRateSetSuccess"] = "SOURCE_SAMPLE_RATE_SET_SUCCESS";
WebsocketResponseType["Started"] = "started";
WebsocketResponseType["Stopped"] = "stopped";
})(WebsocketResponseType || (WebsocketResponseType = {}));
var CONTROL = {
WRITE_INDEX: 0,
FRAMES_AVAILABLE: 1,
LOCK: 2
};
var WebsocketClient = /** @class */ (function () {
function WebsocketClient(ctx) {
var _this = this;
this.isContextStarted = false;
this.isStartContextConfirmed = false;
this.shouldResendLastFramesSent = false;
this.buffer = new Float32Array(0);
this.lastFramesSent = new Int16Array(0); // to re-send after switch context
this.debug = false;
this.initialized = false;
// WebSocket's close handler, called e.g. when
// - normal close (code 1000)
// - network unreachable or unable to (re)connect (code 1006)
// List of CloseEvent.code values: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
this.onWebsocketClose = function (event) {
if (_this.debug) {
console.log('[WebSocketClient]', 'onWebsocketClose');
}
_this.websocket.removeEventListener('open', _this.onWebsocketOpen);
_this.websocket.removeEventListener('message', _this.onWebsocketMessage);
_this.websocket.removeEventListener('error', _this.onWebsocketError);
_this.websocket.removeEventListener('close', _this.onWebsocketClose);
_this.websocket = undefined;
_this.workerCtx.postMessage({
type: 'WEBSOCKET_CLOSED',
code: event.code,
reason: event.reason,
wasClean: event.wasClean
});
};
this.onWebsocketOpen = function (_event) {
if (_this.debug) {
console.log('[WebSocketClient]', 'websocket opened');
}
if (_this.isContextStarted && !_this.isStartContextConfirmed) {
_this.send(_this.outbox);
}
_this.workerCtx.postMessage({ type: 'WEBSOCKET_OPEN' });
};
this.onWebsocketError = function (_event) {
if (_this.debug) {
console.log('[WebSocketClient]', 'websocket error');
}
};
this.onWebsocketMessage = function (event) {
var response;
try {
response = JSON.parse(event.data);
}
catch (e) {
console.error('[WebSocketClient]', 'error parsing response from the server:', e);
return;
}
if (response.type === WebsocketResponseType.Started) {
_this.isStartContextConfirmed = true;
if (_this.shouldResendLastFramesSent) {
_this.resendLastFrames();
_this.shouldResendLastFramesSent = false;
}
}
_this.workerCtx.postMessage(response);
};
this.workerCtx = ctx;
interface WebsocketResponse {
/**
* Response type.
*/
type: WebsocketResponseType
/**
* Audio context ID.
*/
audio_context: string
/**
* Segment ID.
*/
segment_id: number
/**
* Response payload.
*
* The payload value should match the response type (i.e. TranscriptResponse should have Transcript type).
* Not all response types have payloads - Started, Stopped and SegmentEnd don't have payloads.
* TentativeIntent and Intent share the same payload interface (IntentResponse).
*/
data: any
}
const CONTROL = {
WRITE_INDEX: 0,
FRAMES_AVAILABLE: 1,
LOCK: 2,
}
class WebsocketClient {
private readonly workerCtx: Worker
private targetSampleRate: number = 16000
private isContextStarted: boolean = false
private websocket?: WebSocket
private audioProcessor?: AudioProcessor
private controlSAB?: Int32Array
private dataSAB?: Float32Array
private readonly frameMillis = 30
private readonly outputAudioFrame: Int16Array = new Int16Array(this.frameMillis * this.targetSampleRate / 1000)
private debug: boolean = false
private defaultContextOptions?: ContextOptions
constructor(ctx: Worker) {
this.workerCtx = ctx
}
connect(apiUrl: string, authToken: string, targetSampleRate: number, debug: boolean): void {
this.debug = debug
if (this.debug) {
console.log('[WebSocketClient]', 'connecting to ', apiUrl)
}
WebsocketClient.prototype.init = function (apiUrl, authToken, targetSampleRate, debug) {
this.debug = debug;
if (this.debug) {
console.log('[WebSocketClient]', 'initialize worker');
}
this.apiUrl = apiUrl;
this.authToken = authToken;
this.targetSampleRate = targetSampleRate;
this.initialized = true;
this.isContextStarted = false;
this.connect(0);
};
WebsocketClient.prototype.setSourceSampleRate = function (sourceSampleRate) {
this.sourceSampleRate = sourceSampleRate;
this.resampleRatio = this.sourceSampleRate / this.targetSampleRate;
if (this.debug) {
console.log('[WebSocketClient]', 'resampleRatio', this.resampleRatio);
}
if (this.resampleRatio > 1) {
this.filter = generateFilter(this.sourceSampleRate, this.targetSampleRate, 127);
}
this.workerCtx.postMessage({ type: 'SOURCE_SAMPLE_RATE_SET_SUCCESS' });
if (isNaN(this.resampleRatio)) {
throw Error("resampleRatio is NaN source rate is ".concat(this.sourceSampleRate, " and target rate is ").concat(this.targetSampleRate));
}
};
WebsocketClient.prototype.setSharedArrayBuffers = function (controlSAB, dataSAB) {
this.controlSAB = new Int32Array(controlSAB);
this.dataSAB = new Float32Array(dataSAB);
var audioHandleInterval = this.dataSAB.length / 32; // ms
if (this.debug) {
console.log('[WebSocketClient]', 'Audio handle interval', audioHandleInterval, 'ms');
}
setInterval(this.sendAudioFromSAB.bind(this), audioHandleInterval);
};
WebsocketClient.prototype.connect = function (timeout) {
if (timeout === void 0) { timeout = 1000; }
if (this.debug) {
console.log('[WebSocketClient]', 'connect in ', timeout / 1000, 'sec');
}
setTimeout(this.initializeWebsocket.bind(this), timeout);
};
WebsocketClient.prototype.initializeWebsocket = function () {
if (this.debug) {
console.log('[WebSocketClient]', 'connecting to ', this.apiUrl);
}
this.websocket = new WebSocket(this.apiUrl, this.authToken);
this.websocket.addEventListener('open', this.onWebsocketOpen);
this.websocket.addEventListener('message', this.onWebsocketMessage);
this.websocket.addEventListener('error', this.onWebsocketError);
this.websocket.addEventListener('close', this.onWebsocketClose);
};
WebsocketClient.prototype.isOpen = function () {
return this.websocket !== undefined && this.websocket.readyState === this.websocket.OPEN;
};
WebsocketClient.prototype.resendLastFrames = function () {
if (this.lastFramesSent.length > 0) {
this.send(this.lastFramesSent);
this.lastFramesSent = new Int16Array(0);
}
};
WebsocketClient.prototype.sendAudio = function (audioChunk) {
if (!this.isContextStarted) {
return;
}
if (audioChunk.length > 0) {
if (this.resampleRatio > 1) {
// Downsampling
this.send(this.downsample(audioChunk));
this.targetSampleRate = targetSampleRate
this.isContextStarted = false
this.websocket = new WebSocket(apiUrl, authToken)
this.websocket.addEventListener('open', this.onWebsocketOpen)
this.websocket.addEventListener('message', this.onWebsocketMessage)
this.websocket.addEventListener('error', this.onWebsocketError)
this.websocket.addEventListener('close', this.onWebsocketClose)
}
initAudioProcessor(sourceSampleRate: number, vadOptions?: VadOptions): void {
this.audioProcessor = new AudioProcessor(sourceSampleRate, this.targetSampleRate, 5)
if (vadOptions) {
this.audioProcessor.vad = new EnergyTresholdVAD(vadOptions)
this.audioProcessor.onVadSignalHigh = () => {
const currentVadOptions = this.audioProcessor?.vad?.vadOptions
if (currentVadOptions) {
if (this.defaultContextOptions?.immediate) {
if (currentVadOptions.enabled && currentVadOptions.controlListening) {
this.startContext()
}
else {
this.send(float32ToInt16(audioChunk));
} else {
if (this.audioProcessor?.vad?.vadOptions.enabled) {
if (currentVadOptions.enabled && currentVadOptions.controlListening) {
this.workerCtx.postMessage({ type: WorkerSignal.VadSignalHigh })
}
}
}
}
};
WebsocketClient.prototype.sendAudioFromSAB = function () {
if (!this.isContextStarted) {
this.controlSAB[CONTROL.FRAMES_AVAILABLE] = 0;
this.controlSAB[CONTROL.WRITE_INDEX] = 0;
return;
}
if (this.controlSAB == undefined) {
return;
}
var framesAvailable = this.controlSAB[CONTROL.FRAMES_AVAILABLE];
var lock = this.controlSAB[CONTROL.LOCK];
if (lock == 0 && framesAvailable > 0) {
var data = this.dataSAB.subarray(0, framesAvailable);
this.controlSAB[CONTROL.FRAMES_AVAILABLE] = 0;
this.controlSAB[CONTROL.WRITE_INDEX] = 0;
if (data.length > 0) {
var frames_1;
if (this.resampleRatio > 1) {
frames_1 = this.downsample(data);
}
else {
frames_1 = float32ToInt16(data);
}
this.send(frames_1);
// 16000 per second, 1000 in 100 ms
// save last 250 ms
if (this.lastFramesSent.length > 1024 * 4) {
this.lastFramesSent = frames_1;
}
else {
var concat = new Int16Array(this.lastFramesSent.length + frames_1.length);
concat.set(this.lastFramesSent);
concat.set(frames_1, this.lastFramesSent.length);
this.lastFramesSent = concat;
}
}
this.audioProcessor.onVadSignalLow = () => {
const currentVadOptions = this.audioProcessor?.vad?.vadOptions
if (currentVadOptions) {
if (this.defaultContextOptions?.immediate) {
if (currentVadOptions.enabled && currentVadOptions.controlListening) {
this.stopContext()
}
}
};
WebsocketClient.prototype.startContext = function (appId) {
if (this.isContextStarted) {
console.error('[WebSocketClient]', "can't start context: active context exists");
return;
}
this.isContextStarted = true;
this.isStartContextConfirmed = false;
if (appId !== undefined) {
this.outbox = JSON.stringify({ event: 'start', appId: appId });
}
else {
this.outbox = JSON.stringify({ event: 'start' });
}
this.send(this.outbox);
};
WebsocketClient.prototype.stopContext = function () {
if (!this.websocket) {
throw Error('WebSocket is undefined');
}
if (!this.isContextStarted) {
console.error('[WebSocketClient]', "can't stop context: no active context");
return;
}
this.isContextStarted = false;
this.isStartContextConfirmed = false;
var StopEventJSON = JSON.stringify({ event: 'stop' });
this.send(StopEventJSON);
};
WebsocketClient.prototype.switchContext = function (newAppId) {
if (!this.websocket) {
throw Error('WebSocket is undefined');
}
if (!this.isContextStarted) {
console.error('[WebSocketClient]', "can't switch context: no active context");
return;
}
if (newAppId == undefined) {
console.error('[WebSocketClient]', "can't switch context: new app id is undefined");
return;
}
this.isStartContextConfirmed = false;
var StopEventJSON = JSON.stringify({ event: 'stop' });
this.send(StopEventJSON);
this.shouldResendLastFramesSent = true;
this.send(JSON.stringify({ event: 'start', appId: newAppId }));
};
WebsocketClient.prototype.closeWebsocket = function (websocketCode, reason) {
if (websocketCode === void 0) { websocketCode = 1005; }
if (reason === void 0) { reason = 'No Status Received'; }
if (this.debug) {
console.log('[WebSocketClient]', 'Websocket closing');
}
if (!this.websocket) {
throw Error('WebSocket is undefined');
}
this.websocket.close(websocketCode, reason);
};
WebsocketClient.prototype.downsample = function (input) {
var inputBuffer = new Float32Array(this.buffer.length + input.length);
inputBuffer.set(this.buffer, 0);
inputBuffer.set(input, this.buffer.length);
var outputLength = Math.ceil((inputBuffer.length - this.filter.length) / this.resampleRatio);
var outputBuffer = new Int16Array(outputLength);
for (var i = 0; i < outputLength; i++) {
var offset = Math.round(this.resampleRatio * i);
var val = 0.0;
for (var j = 0; j < this.filter.length; j++) {
val += inputBuffer[offset + j] * this.filter[j];
} else {
if (currentVadOptions.enabled && currentVadOptions.controlListening) {
this.workerCtx.postMessage({ type: WorkerSignal.VadSignalLow })
}
outputBuffer[i] = val * (val < 0 ? 0x8000 : 0x7fff);
}
}
var remainingOffset = Math.round(this.resampleRatio * outputLength);
if (remainingOffset < inputBuffer.length) {
this.buffer = inputBuffer.subarray(remainingOffset);
}
else {
this.buffer = new Float32Array(0);
}
return outputBuffer;
};
WebsocketClient.prototype.send = function (data) {
if (this.isOpen()) {
try {
this.websocket.send(data);
}
catch (error) {
console.log('[WebSocketClient]', 'server connection error', error);
}
}
};
return WebsocketClient;
}());
var ctx = self;
var websocketClient = new WebsocketClient(ctx);
ctx.onmessage = function (e) {
switch (e.data.type) {
case 'INIT':
websocketClient.init(e.data.apiUrl, e.data.authToken, e.data.targetSampleRate, e.data.debug);
break;
case 'SET_SOURCE_SAMPLE_RATE':
websocketClient.setSourceSampleRate(e.data.sourceSampleRate);
break;
case 'SET_SHARED_ARRAY_BUFFERS':
websocketClient.setSharedArrayBuffers(e.data.controlSAB, e.data.dataSAB);
break;
case 'CLOSE':
websocketClient.closeWebsocket(1000, 'Close requested by client');
break;
case 'START_CONTEXT':
websocketClient.startContext(e.data.appId);
break;
case 'SWITCH_CONTEXT':
websocketClient.switchContext(e.data.appId);
break;
case 'STOP_CONTEXT':
websocketClient.stopContext();
break;
case 'AUDIO':
websocketClient.sendAudio(e.data.payload);
break;
default:
console.log('WORKER', e);
}
}
};
function float32ToInt16(buffer) {
var buf = new Int16Array(buffer.length);
for (var l = 0; l < buffer.length; l++) {
buf[l] = buffer[l] * (buffer[l] < 0 ? 0x8000 : 0x7fff);
this.audioProcessor.sendAudio = (floats: Float32Array, startIndex: number, length: number) => {
AudioTools.convertFloatToInt16(floats, this.outputAudioFrame, startIndex, length)
this.send(this.outputAudioFrame)
}
return buf;
}
function generateFilter(sourceSampleRate, targetSampleRate, length) {
if (length % 2 === 0) {
throw Error('Filter length must be odd');
this.workerCtx.postMessage({ type: WorkerSignal.AudioProcessorReady })
}
/**
* Control audio processor parameters
* @param ap - Audio processor parameters to adjust
*/
adjustAudioProcessor(ap: AudioProcessorParameters): void {
if (!this.audioProcessor) {
throw new Error('No AudioProcessor')
}
var cutoff = targetSampleRate / 2;
var filter = new Float32Array(length);
var sum = 0;
for (var i = 0; i < length; i++) {
var x = sinc(((2 * cutoff) / sourceSampleRate) * (i - (length - 1) / 2));
sum += x;
filter[i] = x;
if (ap.vad) {
if (!this.audioProcessor.vad) {
throw new Error('No VAD in AudioProcessor. Did you define `vad` in BrowserClient constructor parameters?')
}
this.audioProcessor.vad.adjustVadOptions(ap.vad)
}
for (var i = 0; i < length; i++) {
filter[i] = filter[i] / sum;
}
setSharedArrayBuffers(controlSAB: number, dataSAB: number): void {
this.controlSAB = new Int32Array(controlSAB)
this.dataSAB = new Float32Array(dataSAB)
const audioHandleInterval = this.dataSAB.length / 32 // ms
if (this.debug) {
console.log('[WebSocketClient]', 'Audio handle interval', audioHandleInterval, 'ms')
}
return filter;
}
function sinc(x) {
if (x === 0.0) {
return 1.0;
setInterval(this.processAudioSAB.bind(this), audioHandleInterval)
}
startStream(defaultContextOptions?: ContextOptions): void {
if (!this.audioProcessor) {
throw new Error('No AudioProcessor')
}
var piX = Math.PI * x;
return Math.sin(piX) / piX;
this.defaultContextOptions = defaultContextOptions
this.audioProcessor.resetStream()
}
stopStream(): void {
if (!this.audioProcessor) {
throw new Error('No AudioProcessor')
}
if (this.isContextStarted) {
// Ensure stopContext is called in immediate mode
this.stopContext()
}
this.defaultContextOptions = undefined
}
/**
* Processes and sends audio
* @param audioChunk - audio data to process
*/
processAudio(audioChunk: Float32Array): void {
if (!this.audioProcessor) {
throw new Error('No AudioProcessor')
}
this.audioProcessor.processAudio(audioChunk)
}
processAudioSAB(): void {
if (!this.controlSAB || !this.dataSAB) {
throw new Error('No SharedArrayBuffers')
}
const framesAvailable = this.controlSAB[CONTROL.FRAMES_AVAILABLE]
const lock = this.controlSAB[CONTROL.LOCK]
if (lock === 0 && framesAvailable > 0) {
const data = this.dataSAB.subarray(0, framesAvailable)
this.controlSAB[CONTROL.FRAMES_AVAILABLE] = 0
this.controlSAB[CONTROL.WRITE_INDEX] = 0
if (data.length > 0) {
this.processAudio(data)
}
}
}
startContext(appId?: string): void {
if (!this.audioProcessor) {
throw Error('No AudioProcessor')
}
if (this.isContextStarted) {
console.error('[WebSocketClient]', "can't start context: active context exists")
return
}
this.audioProcessor.startContext()
this.isContextStarted = true
if (appId !== undefined) {
this.send(JSON.stringify({ event: 'start', appId }))
} else {
this.send(JSON.stringify({ event: 'start' }))
}
}
stopContext(): void {
if (!this.audioProcessor) {
throw Error('No AudioProcessor')
}
if (!this.isContextStarted) {
console.error('[WebSocketClient]', "can't stop context: no active context")
return
}
this.audioProcessor.stopContext()
this.isContextStarted = false
const StopEventJSON = JSON.stringify({ event: 'stop' })
this.send(StopEventJSON)
}
switchContext(newAppId: string): void {
if (!this.websocket) {
throw Error('WebSocket is undefined')
}
if (!this.isContextStarted) {
console.error('[WebSocketClient]', "can't switch context: no active context")
return
}
if (newAppId === undefined) {
console.error('[WebSocketClient]', "can't switch context: new app id is undefined")
return
}
const StopEventJSON = JSON.stringify({ event: 'stop' })
this.send(StopEventJSON)
this.send(JSON.stringify({ event: 'start', appId: newAppId }))
}
closeWebsocket(websocketCode: number = 1005, reason: string = 'No Status Received'): void {
if (this.debug) {
console.log('[WebSocketClient]', 'Websocket closing')
}
if (!this.websocket) {
throw Error('WebSocket is undefined')
}
this.websocket.close(websocketCode, reason)
}
// WebSocket's close handler, called e.g. when
// - normal close (code 1000)
// - network unreachable or unable to (re)connect (code 1006)
// List of CloseEvent.code values: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
private readonly onWebsocketClose = (event: CloseEvent): void => {
if (!this.websocket) {
throw Error('WebSocket is undefined')
}
if (this.debug) {
console.log('[WebSocketClient]', 'onWebsocketClose')
}
this.websocket.removeEventListener('open', this.onWebsocketOpen)
this.websocket.removeEventListener('message', this.onWebsocketMessage)
this.websocket.removeEventListener('error', this.onWebsocketError)
this.websocket.removeEventListener('close', this.onWebsocketClose)
this.websocket = undefined
this.workerCtx.postMessage({
type: WorkerSignal.Closed,
code: event.code,
reason: event.reason,
wasClean: event.wasClean,
})
}
private readonly onWebsocketOpen = (_event: Event): void => {
if (this.debug) {
console.log('[WebSocketClient]', 'websocket opened')
}
this.workerCtx.postMessage({ type: WorkerSignal.Opened })
}
private readonly onWebsocketError = (_event: Event): void => {
if (this.debug) {
console.log('[WebSocketClient]', 'websocket error')
}
}
private readonly onWebsocketMessage = (event: MessageEvent): void => {
let response: WebsocketResponse
try {
response = JSON.parse(event.data)
} catch (e) {
console.error('[WebSocketClient]', 'error parsing response from the server:', e)
return
}
this.workerCtx.postMessage(response)
}
send(data: string | Int16Array): void {
if (!this.websocket) {
throw new Error('No Websocket')
}
if (this.websocket.readyState !== this.websocket.OPEN) {
throw new Error(`Expected OPEN Websocket state, but got ${this.websocket.readyState}`)
}
try {
this.websocket.send(data)
} catch (error) {
console.log('[WebSocketClient]', 'server connection error', error)
}
}
}
`
const ctx: Worker = self as any
const websocketClient = new WebsocketClient(ctx)
ctx.onmessage = function (e) {
switch (e.data.type) {
case ControllerSignal.connect:
websocketClient.connect(e.data.apiUrl, e.data.authToken, e.data.targetSampleRate, e.data.debug)
break
case ControllerSignal.initAudioProcessor:
websocketClient.initAudioProcessor(e.data.sourceSampleRate, e.data.vadOptions)
break
case ControllerSignal.adjustAudioProcessor:
websocketClient.adjustAudioProcessor(e.data.params)
break
case ControllerSignal.SET_SHARED_ARRAY_BUFFERS:
websocketClient.setSharedArrayBuffers(e.data.controlSAB, e.data.dataSAB)
break
case ControllerSignal.CLOSE:
websocketClient.closeWebsocket(1000, 'Close requested by client')
break
case ControllerSignal.startStream:
websocketClient.startStream(e.data.options)
break
case ControllerSignal.stopStream:
websocketClient.stopStream()
break
case ControllerSignal.START_CONTEXT:
websocketClient.startContext(e.data.appId)
break
case ControllerSignal.SWITCH_CONTEXT:
websocketClient.switchContext(e.data.appId)
break
case ControllerSignal.STOP_CONTEXT:
websocketClient.stopContext()
break
case ControllerSignal.AUDIO:
websocketClient.processAudio(e.data.payload)
break
default:
console.log('WORKER', e)
}
}
export default WebsocketClient

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc