uneeq-js 3.13.3 → 3.13.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/948.index.js +1 -1
- package/dist/948.index.js.map +1 -1
- package/dist/esm/chunks/{google-stt-P7QLHE5H.js → google-stt-BWTBTWJR.js} +5 -5
- package/dist/esm/index.js +10 -10
- package/dist/google-stt.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/session.d.ts +1 -0
- package/dist/uneeq.d.ts +1 -0
- package/dist/webrtc-data-channel/DataChannelAction.d.ts +2 -1
- package/dist/webrtc-data-channel/DataChannelActionType.d.ts +1 -0
- package/dist/webrtc-data-channel/messages/LoadBackground.d.ts +12 -0
- package/package.json +1 -1
- package/dist/esm/chunks/chunk-557JQDB4.js +0 -1
- package/dist/esm/chunks/chunk-DORXZBMU.js +0 -2
- package/dist/esm/chunks/chunk-QNPFEGCG.js +0 -1
- package/dist/esm/chunks/chunk-V5GQSIWD.js +0 -2
- package/dist/esm/chunks/deepgram-flux-stt-MHRGSAVD.js +0 -27
- package/dist/esm/chunks/deepgram-flux-stt-UPRNCBXC.js +0 -27
- package/dist/esm/chunks/deepgram-stt-BJWZFBHV.js +0 -1
- package/dist/esm/chunks/deepgram-stt-EO3Y4T3A.js +0 -1
- package/dist/esm/chunks/deepgram-stt-WX4KHFSO.js +0 -1
- package/dist/esm/chunks/google-stt-45HG42BE.js +0 -2486
package/dist/948.index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";(Object("undefined"!=typeof self?self:this).webpackChunkUneeq=Object("undefined"!=typeof self?self:this).webpackChunkUneeq||[]).push([[948],{948(e,t,i){i.d(t,{GoogleSTT:()=>T});var s,n=i(955),a=i(603),o=i(514);!function(e){e.AudioFrame="AUDIO_FRAME",e.SpeechStart="SPEECH_START",e.VADMisfire="VAD_MISFIRE",e.SpeechEnd="SPEECH_END"}(s||(s={}));const r=[512,1024,1536];function h(e){r.includes(e.frameSamples)||o.A.warn("You are using an unusual frame size"),(e.positiveSpeechThreshold<0||e.negativeSpeechThreshold>1)&&o.A.error("postiveSpeechThreshold should be a number between 0 and 1"),(e.negativeSpeechThreshold<0||e.negativeSpeechThreshold>e.positiveSpeechThreshold)&&o.A.error("negativeSpeechThreshold should be between 0 and positiveSpeechThreshold"),e.preSpeechPadFrames<0&&o.A.error("preSpeechPadFrames should be positive"),e.redemptionFrames<0&&o.A.error("preSpeechPadFrames should be positive")}const c=e=>{const t=e.reduce((e,t)=>(e.push(e[e.length-1]+t.length),e),[0]),i=new Float32Array(t[t.length-1]);return e.forEach((e,s)=>{const n=t[s];i.set(e,n)}),i};class d{modelProcessFunc;modelResetFunc;options;speaking=!1;audioBuffer;redemptionCounter=0;active=!1;constructor(e,t,i){this.modelProcessFunc=e,this.modelResetFunc=t,this.options=i,this.audioBuffer=[],this.reset()}reset=()=>{this.speaking=!1,this.audioBuffer=[],this.modelResetFunc(),this.redemptionCounter=0};pause=()=>{this.active=!1,this.reset()};resume=()=>{this.active=!0};endSegment=()=>{const e=this.audioBuffer;this.audioBuffer=[];const t=this.speaking;this.reset();const i=e.reduce((e,t)=>e+ +t.isSpeech,0);if(t){if(i>=this.options.minSpeechFrames){const t=c(e.map(e=>e.frame));return{msg:s.SpeechEnd,audio:t}}return{msg:s.VADMisfire}}return{}};process=async e=>{if(!this.active)return{};const t=await this.modelProcessFunc(e);if(this.audioBuffer.push({frame:e,isSpeech:t.isSpeech>=this.options.positiveSpeechThreshold}),t.isSpeech>=this.options.positiveSpeechThreshold&&this.redemptionCounter&&(this.redemptionCounter=0),t.isSpeech>=this.options.positiveSpeechThreshold&&!this.speaking)return this.speaking=!0,{probs:t,msg:s.SpeechStart};if(t.isSpeech<this.options.negativeSpeechThreshold&&this.speaking&&++this.redemptionCounter>=this.options.redemptionFrames){this.redemptionCounter=0,this.speaking=!1;const e=this.audioBuffer;if(this.audioBuffer=[],e.reduce((e,t)=>e+ +t.isSpeech,0)>=this.options.minSpeechFrames){const i=c(e.map(e=>e.frame));return{probs:t,msg:s.SpeechEnd,audio:i}}return{probs:t,msg:s.VADMisfire}}if(!this.speaking)for(;this.audioBuffer.length>this.options.preSpeechPadFrames;)this.audioBuffer.shift();return{probs:t}}}class p{ort;modelFetcher;static new=async(e,t)=>{const i=new p(e,t);return await i.init(),i};_session;_h;_c;_sr;constructor(e,t){this.ort=e,this.modelFetcher=t}init=async()=>{o.A.debug("[VAD] initializing");const e=await this.modelFetcher();this._session=await this.ort.InferenceSession.create(e),this._sr=new this.ort.Tensor("int64",[BigInt(16e3)]),this.reset_state(),o.A.debug("[VAD] is initialized")};reset_state=()=>{const e=Array(128).fill(0);this._h=new this.ort.Tensor("float32",e,[2,1,64]),this._c=new this.ort.Tensor("float32",e,[2,1,64])};process=async e=>{const t={input:new this.ort.Tensor("float32",e,[1,e.length]),h:this._h,c:this._c,sr:this._sr},i=await this._session.run(t);this._h=i.hn,this._c=i.cn;const s=i.output.data[0];return{notSpeech:1-s,isSpeech:s}}}const l={positiveSpeechThreshold:.5,negativeSpeechThreshold:.35,preSpeechPadFrames:1,redemptionFrames:8,frameSamples:1536,minSpeechFrames:3,onFrameProcessed:e=>{},onVADMisfire:()=>{o.A.debug("VAD misfire")},onSpeechStart:()=>{o.A.debug("Detected speech start")},onSpeechEnd:()=>{o.A.debug("Detected speech end")},stream:new MediaStream,assetBasePath:""};class u{options;static async new(e={}){const t=new u({...l,...e});return await t.init(),t}audioContext;stream;audioNodeVAD;listening=!1;constructor(e){this.options=e,h(e)}init=async()=>{if(o.A.info("Initializing real time vad."),void 0===this.options.stream){if(!navigator.mediaDevices?.getUserMedia)throw new Error("Microphone access is not available in this context");this.stream=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,autoGainControl:!0,noiseSuppression:!0}})}else this.stream=this.options.stream;this.audioContext=new AudioContext;const e=new MediaStreamAudioSourceNode(this.audioContext,{mediaStream:this.stream});this.audioNodeVAD=await g.new(this.audioContext,this.options),this.audioNodeVAD.receive(e)};pause=()=>{this.audioNodeVAD.pause(),this.listening=!1};start=()=>{this.audioNodeVAD.start(),this.listening=!0}}class g{ctx;options;static async new(e,t={}){const i=new g(e,{...l,...t});return await i.init(),i}frameProcessor;entryNode;constructor(e,t){this.ctx=e,this.options=t,h(t)}pause=()=>{this.frameProcessor.pause()};start=()=>{this.frameProcessor.resume()};receive=e=>{e.connect(this.entryNode)};processFrame=async e=>{const{probs:t,msg:i,audio:n}=await this.frameProcessor.process(e);switch(void 0!==t&&this.options.onFrameProcessed(t),i){case s.SpeechStart:this.options.onSpeechStart();break;case s.VADMisfire:this.options.onVADMisfire();break;case s.SpeechEnd:this.options.onSpeechEnd(n)}};init=async()=>{await this.ctx.audioWorklet.addModule(this.options.assetBasePath+"vad.worklet.bundle.min.js");const e=new AudioWorkletNode(this.ctx,"vad-helper-worklet",{processorOptions:{frameSamples:this.options.frameSamples}});this.entryNode=e,a.env.wasm.wasmPaths=this.options.assetBasePath;const t=await p.new(a,this.modelFetcher);this.frameProcessor=new d(t.process,t.reset_state,{frameSamples:this.options.frameSamples,positiveSpeechThreshold:this.options.positiveSpeechThreshold,negativeSpeechThreshold:this.options.negativeSpeechThreshold,redemptionFrames:this.options.redemptionFrames,preSpeechPadFrames:this.options.preSpeechPadFrames,minSpeechFrames:this.options.minSpeechFrames}),e.port.onmessage=async e=>{switch(e.data?.message){case s.AudioFrame:{const t=e.data.data,i=new Float32Array(t);await this.processFrame(i);break}}}};modelFetcher=async()=>{const e=this.options.assetBasePath+"silero_vad.onnx";return await fetch(e).then(e=>e.arrayBuffer())}}var m,S=i(838);!function(e){e.startTranscription="startTranscription",e.stopTranscription="stopTranscription"}(m||(m={}));var f=i(58),w=i(388),A=i(33),b=i(992),M=i(1);class v{transcript;stability;confidence;languageCode;isFinal;requestId;constructor(e,t,i,s,n,a=(0,M.g)()){this.transcript=e,this.stability=t,this.confidence=i,this.languageCode=s,this.isFinal=n,this.requestId=a}toJSON(){return{action:b.A.InterimTranscript,data:{requestId:this.requestId,transcript:this.transcript,stability:this.stability,confidence:this.confidence,languageCode:this.languageCode,isFinal:this.isFinal}}}}const k=48e3,V="[Speech Recognition]";class T{options;ws;mediaRecorder;speechBuffer=new n.RingBuffer(5);recordingLive=!1;headerBlob;scriptsLoadedPromise;reconnectWs=!0;wsReconnectMessageQueue=[];storedTranscription;awaitingFinalTranscript=!1;isMicrophoneMuted=!0;isTemporarilyPaused=!1;vadInitialising=!1;pausedDuringVadInit=!1;stream;micVad;digitalHumanSpeaking=!1;interruptionBlocked=!1;constructor(e){this.options=e,this.options.assetBasePath=this.options.assetBasePath??"https://cdn.uneeq.io/assets/platform/speech-recognition/",this.options.enableVad=this.options.enableVad??!0,this.options.enableInterrupt=this.options.enableInterrupt??!1,this.loadSavedAudioHeaders(),this.loadScripts(),this.handleAppMessages()}async startRecognition(){o.A.info(`${V} start recognition`),this.reconnectWs=!0,this.isMicrophoneMuted=!1,this.initWebsocket(this.options)}async stopRecognition(){o.A.info(`${V} stop recognition`),this.isMicrophoneMuted=!0,this.stopRecognitionInternal()}stopRecognitionInternal(){this.mediaRecorder&&"recording"===this.mediaRecorder.state&&this.mediaRecorder.stop(),this.stream&&(this.stream.getTracks().forEach(e=>{e.stop()}),this.stream=void 0,this.clientMsgSend(new S.WY(!1))),this.pauseMicVad()}pause(){return o.A.info(`${V} pausing - setting temporarily paused state`),this.isTemporarilyPaused=!0,this.vadInitialising&&(this.pausedDuringVadInit=!0,o.A.warn(`${V} pauseSpeechRecognition() called while VAD is still initialising. VAD will not auto-start when initialisation completes. Call resumeSpeechRecognition() after receiving EnableMicrophoneUpdated to start VAD.`)),this.stream&&this.micVad||o.A.warn(`${V} Problem pausing speech recognition, stream live: ${void 0!==this.stream} or microphone voice detection live: ${void 0!==this.micVad}.`),this.sendStoredTranscriptionIfReady(),this.stream?.getTracks().forEach(e=>{e.enabled=!1}),this.pauseMicVad(),this.onVadSpeechEnd(),o.A.info(`${V} paused`),!0}resume(){if(o.A.info(`${V} resuming - clearing temporarily paused state`),this.isTemporarilyPaused=!1,this.interruptionBlocked&&(this.digitalHumanSpeaking=!1,this.interruptionBlocked=!1,this.clientMsgSend(new S.E1(!0))),!this.options.enableVad&&this.digitalHumanSpeaking&&(this.dataChannelMsgSend(new f.f),this.digitalHumanSpeaking=!1),this.storedTranscription=void 0,this.isMicrophoneMuted)return o.A.debug(`${V} Cannot resume: microphone is globally muted`),!1;if(this.stream&&this.micVad){this.stream.getTracks().forEach(e=>{e.enabled=!0}),o.A.info("stream and micVad exist, starting VAD");const e=this.startMicVad();return o.A.info(`${V} resumed: `+e),e}return o.A.warn(`${V} Could not resume speech recognition, stream live: ${void 0!==this.stream} or microphone voice detection live: ${void 0!==this.micVad}.`),!1}setChatMetadata(e){this.options.promptMetadata=e}pauseVadIfInterruptNotAllowed(){this.micVad&&!this.options.enableInterrupt&&(this.micVad.pause(),this.clientMsgSend(new S.E1(!1))),this.options.enableInterrupt||(this.interruptionBlocked=!0)}async loadScript(e){return await new Promise(t=>{const i=document.createElement("script");i.src=e,i.onload=()=>{t()},document.body.appendChild(i)})}loadScripts(){this.scriptsLoadedPromise=new Promise(e=>{this.loadScript(this.options.assetBasePath+"OpusMediaRecorder.umd.js").then(()=>{this.loadScript(this.options.assetBasePath+"encoderWorker.umd.js").then(()=>{e()}).catch(e=>{o.A.error(`${V} Error loading encoderWorker.umd.js`,o.A.serialiseError(e))})}).catch(e=>{o.A.error(`${V} Error loading OpusMediaRecorder.umd.js`,o.A.serialiseError(e))})})}handleAppMessages(){this.options.messages.subscribe(e=>{switch(e.uneeqMessageType){case S.Yg.AvatarStartedSpeaking:this.digitalHumanSpeaking=!0,this.pauseVadIfInterruptNotAllowed();break;case S.Yg.PromptResult:e.promptResult.success||this.handleSpeakingEnd();break;case S.Yg.AvatarAnswer:""===e.answerSpeech.replace(/<[^>]*>/g,"")&&this.handleSpeakingEnd();break;case S.Yg.AvatarStoppedSpeaking:this.handleSpeakingEnd();break;case S.Yg.SessionEnded:this.reconnectWs=!1,this.endRecognition();break;case S.Yg.SessionReconnecting:case S.Yg.SoftSwitchStarting:this.handleSpeakingEnd(),this.reconnectWs=!1,this.endRecognition();break;case S.Yg.CustomMetadataUpdated:this.options.promptMetadata=e.chatMetadata;break;case S.Yg.SessionBackendError:this.handleSpeakingEnd()}})}endRecognition(){o.A.info(`${V} ending recognition`),this.stopRecognitionInternal(),this.ws&&this.ws.readyState===WebSocket.OPEN&&(this.ws.onclose=null,this.ws.close())}handleSpeakingEnd(){this.digitalHumanSpeaking=!1,this.interruptionBlocked=!1,this.isMicrophoneMuted||this.isTemporarilyPaused||(o.A.info("mic not muted and not paused, starting VAD after digital human speaking ended"),this.startMicVad()),this.options.enableInterrupt||this.clientMsgSend(new S.E1(!0))}initWebsocket(e){o.A.info(`${V} initWebsocket - stopping any existing recognition`),this.endRecognition();let t=e.apiUrl.replace("https://","wss://").replace("http://","ws://");t+=`/speech-recognition-service/ws/recognize?jwt=${e.jwtToken}&session_id=${e.sessionId}`,o.A.info(`${V} Initializing speech recognition websocket to `+t),this.ws=new WebSocket(t),this.ws.onopen=()=>{o.A.info(`${V} Speech recognition web socket opened`),this.sendWebsocketMessageQueue(),this.initMicrophone()},this.ws.onclose=e=>{this.handleWebsocketClose(e)},this.ws.onerror=e=>{o.A.error(`${V} WebSocket error`,o.A.serialiseError(e)),this.clientMsgSend(new S.Cj("Speech recognition service connection failed"))},this.ws.onmessage=e=>{this.handleWebsocketMessage(e)}}sendWebsocketMessageQueue(){o.A.info(`${V} Sending all queued websocket messages, queue length: `+this.wsReconnectMessageQueue.length),this.wsReconnectMessageQueue.forEach(e=>{this.wsSend(e,!1,!1)}),this.wsReconnectMessageQueue=[]}async loadSavedAudioHeaders(){const e=this.options.assetBasePath+"opus-media-recorder-header.webm",t=await fetch(e),i=await t.arrayBuffer();this.headerBlob=new Blob([i],{type:"audio/webm;codecs=opus"})}initVoiceActivityDetection(e){o.A.info("[VAD] Going to initialize VAD module, with enableVad = "+this.options.enableVad),this.vadInitialising=!0,this.pausedDuringVadInit=!1,u.new({onSpeechStart:()=>{this.onVadSpeechStart()},onSpeechEnd:()=>{this.onVadSpeechEnd()},minSpeechFrames:1,positiveSpeechThreshold:this.options.enableVad?.5:0,negativeSpeechThreshold:this.options.enableVad?.35:0,redemptionFrames:8,assetBasePath:this.options.assetBasePath,stream:e}).then(e=>{this.micVad=e,this.vadInitialising=!1,this.interruptionBlocked||this.isMicrophoneMuted||this.isTemporarilyPaused?(this.interruptionBlocked&&this.clientMsgSend(new S.E1(!1)),this.pausedDuringVadInit?o.A.warn("[VAD] VAD module created but not started because pauseSpeechRecognition() was called during async VAD initialisation. Call resumeSpeechRecognition() to start VAD."):o.A.info(`[VAD] VAD module created but not started: interruptionBlocked=${this.interruptionBlocked}, isMicrophoneMuted=${this.isMicrophoneMuted}, isTemporarilyPaused=${this.isTemporarilyPaused}`)):(o.A.info(`${V} interruption not blocked and mic not muted, starting VAD module`),this.startMicVad()),this.pausedDuringVadInit=!1,o.A.info("[VAD] module has started")}).catch(e=>{this.vadInitialising=!1,this.pausedDuringVadInit=!1,o.A.error("[VAD] could not start module",o.A.serialiseError(e))})}startMicVad(){return!this.micVad||!this.options.enableInterrupt&&this.digitalHumanSpeaking?(o.A.info(`[VAD] VAD has not been started, microphone voice detection live: ${void 0!==this.micVad}, enableInterrupt: ${this.options.enableInterrupt}, isDigitalHumanSpeaking: ${this.digitalHumanSpeaking}.`),!1):(this.micVad.start(),o.A.info("[VAD] VAD has been started / unpaused"),!0)}pauseMicVad(){return this.micVad?(this.micVad.pause(),o.A.info("[VAD] has been paused"),!0):(o.A.info("[VAD] has not been initialized so has not been paused "),!1)}onVadSpeechStart(){o.A.info("[VAD] User started speaking"),this.dataChannelMsgSend(new A.A(A.f.Start)),this.clientMsgSend(new S._4),this.sendStartMessage(),this.wsSend(this.headerBlob),o.A.info("[VAD] Sending buffered speech audio");const e=new Blob(this.speechBuffer.toArray(),{type:"audio/webm;codecs=opus"});this.wsSend(e),this.recordingLive=!0,this.speechBuffer.clear()}onVadSpeechEnd(){o.A.info("[VAD] User has stopped speaking or intentionally ending current VAD"),this.dataChannelMsgSend(new A.A(A.f.Stop)),this.clientMsgSend(new S.im),this.recordingLive=!1,this.sendStopMessage()}sendStoredTranscriptionIfReady(){this.options.enableVad||void 0===this.storedTranscription||this.awaitingFinalTranscript||(this.sendChatPrompt(this.storedTranscription.transcript),this.clientMsgSend(new S.Ux(this.storedTranscription)),this.storedTranscription=void 0)}handleWebsocketMessage(e){o.A.debug(`${V} Got a websocket message: `,e);try{if("string"==typeof e.data){const t=JSON.parse(e.data),i=t.results?t.results[0]:null;"response"===t.state&&i&&(o.A.info(`${V} Speech transcription result ${JSON.stringify(i)}`),this.options.enableVad?this.handleVadTranscriptionResult(i):this.handlePttTranscriptionResult(i))}}catch(e){o.A.error(`${V} Error processing message speech recognition socket message`,o.A.serialiseError(e))}}handleWebsocketClose(e){o.A.warn(`${V} WebSocket closed`,e),this.reconnectWs&&this.initWebsocket(this.options)}sendStartMessage(){const e={action:m.startTranscription,channels:1,sampleRate:k,interimResults:!0,lang:this.options.locales||"en-US",phrases:this.options.hintPhrases??"",phraseBoost:this.options.hintPhrasesBoost??0};this.wsSend(JSON.stringify(e))}sendStopMessage(){const e={action:m.stopTranscription};this.wsSend(JSON.stringify(e),!0)}wsSend(e,t=!1,i=!0){this.ws.readyState===WebSocket.OPEN&&this.ws.send(e),this.reconnectWs&&i&&(this.wsReconnectMessageQueue.push(e),t&&this.ws.readyState===WebSocket.OPEN&&(this.wsReconnectMessageQueue=[]),this.wsReconnectMessageQueue.length>3e3&&this.handleWsReconnectQueueOverflow())}handleWsReconnectQueueOverflow(){o.A.warn(`${V} The websocket reconnection queue has exceeded maximum length of 3000. The audio reconnection queue will be reset, speech transcription performance may be impacted in the event of a reconnection.`),this.wsReconnectMessageQueue=[]}initMicrophone(){if(this.stream)o.A.warn("[Microphone] Microphone already initialized, stream already exists, skipping initialization.");else{if(!navigator.mediaDevices?.getUserMedia)return o.A.error("[Microphone] navigator.mediaDevices.getUserMedia is not available in this context"),void this.clientMsgSend(new S.co(new Error("Microphone access is not available in this context")));navigator.mediaDevices.getUserMedia({audio:{deviceId:this.options.microphoneDeviceId?{exact:this.options.microphoneDeviceId}:void 0,echoCancellation:!0,sampleRate:k,channelCount:1}}).then(e=>{this.stream=e,this.initVoiceActivityDetection(e),this.initMediaRecorder(e),this.clientMsgSend(new S.WY(!0))}).catch(e=>{o.A.error("[Microphone] Error starting recording",o.A.serialiseError(e)),this.clientMsgSend(new S.co(new Error(JSON.stringify(e))))})}}getMediaRecorder(e,t,i){return new window.OpusMediaRecorder(e,t,i)}initMediaRecorder(e){const t={OggOpusEncoderWasmPath:this.options.assetBasePath+"OggOpusEncoder.wasm",WebMOpusEncoderWasmPath:this.options.assetBasePath+"WebMOpusEncoder.wasm"},i={mimeType:"audio/webm",audioBitsPerSecond:k};this.scriptsLoadedPromise.then(()=>{this.mediaRecorder=this.getMediaRecorder(e,i,t),this.mediaRecorder.ondataavailable=e=>{this.mediaRecorderOnData(e)},this.mediaRecorder.onstop=()=>{this.sendStopMessage()},this.mediaRecorder.start(100)}).catch(e=>{o.A.error("[Microphone] Error initializing media recorder",o.A.serialiseError(e))})}mediaRecorderOnData(e){e.data&&e.data.size>0&&(this.recordingLive?this.wsSend(e.data):this.speechBuffer.add(e.data))}handlePttTranscriptionResult(e){this.awaitingFinalTranscript=!e.final;try{this.recordingLive?e.final?void 0===this.storedTranscription?this.storedTranscription=e:this.storedTranscription.transcript+=e.transcript:this.digitalHumanSpeaking&&(this.dataChannelMsgSend(new f.f),this.clientMsgSend(new S.tc),this.digitalHumanSpeaking=!1):e.final&&(void 0!==this.storedTranscription&&(e.transcript=this.storedTranscription.transcript+e.transcript),this.options.promptMetadata.userSpokenLocale=e.language_code,this.sendChatPrompt(e.transcript),this.sendInterimTranscription(e),this.clientMsgSend(new S.Ux(e)),this.storedTranscription=void 0),(e.stability??1)>.5&&!e.final&&(void 0!==this.storedTranscription&&(e.transcript=this.storedTranscription.transcript+e.transcript),this.clientMsgSend(new S.Ux(e)),this.sendInterimTranscription(e))}catch{this.clientMsgSend(new S.Cj("Error processing speech transcription result")),this.storedTranscription=void 0}}handleVadTranscriptionResult(e){""!==e.transcript&&(e.final?(this.options.promptMetadata.userSpokenLocale=e.language_code,this.sendChatPrompt(e.transcript)):this.digitalHumanSpeaking&&(this.dataChannelMsgSend(new f.f),this.clientMsgSend(new S.tc),this.digitalHumanSpeaking=!1),this.sendInterimTranscription(e)),((e.stability??1)>.5||e.final)&&this.clientMsgSend(new S.Ux(e))}sendChatPrompt(e){e&&""!==e.trim()&&(this.pauseVadIfInterruptNotAllowed(),this.dataChannelMsgSend(new w.D(e,this.options.promptMetadata)))}sendInterimTranscription(e){if(e.transcript&&""!==e.transcript.trim()){const t=e.stability??1;this.dataChannelMsgSend(new v(e.transcript,t,e.confidence,e.language_code,e.final))}}dataChannelMsgSend(e){this.options.sendMessage(e)}clientMsgSend(e){this.options.messages.next(e)}}}}]);
|
|
1
|
+
"use strict";(Object("undefined"!=typeof self?self:this).webpackChunkUneeq=Object("undefined"!=typeof self?self:this).webpackChunkUneeq||[]).push([[948],{948(e,t,i){i.d(t,{GoogleSTT:()=>T});var s,n=i(955),a=i(603),o=i(514);!function(e){e.AudioFrame="AUDIO_FRAME",e.SpeechStart="SPEECH_START",e.VADMisfire="VAD_MISFIRE",e.SpeechEnd="SPEECH_END"}(s||(s={}));const r=[512,1024,1536];function h(e){r.includes(e.frameSamples)||o.A.warn("You are using an unusual frame size"),(e.positiveSpeechThreshold<0||e.negativeSpeechThreshold>1)&&o.A.error("postiveSpeechThreshold should be a number between 0 and 1"),(e.negativeSpeechThreshold<0||e.negativeSpeechThreshold>e.positiveSpeechThreshold)&&o.A.error("negativeSpeechThreshold should be between 0 and positiveSpeechThreshold"),e.preSpeechPadFrames<0&&o.A.error("preSpeechPadFrames should be positive"),e.redemptionFrames<0&&o.A.error("preSpeechPadFrames should be positive")}const c=e=>{const t=e.reduce((e,t)=>(e.push(e[e.length-1]+t.length),e),[0]),i=new Float32Array(t[t.length-1]);return e.forEach((e,s)=>{const n=t[s];i.set(e,n)}),i};class d{modelProcessFunc;modelResetFunc;options;speaking=!1;audioBuffer;redemptionCounter=0;active=!1;constructor(e,t,i){this.modelProcessFunc=e,this.modelResetFunc=t,this.options=i,this.audioBuffer=[],this.reset()}reset=()=>{this.speaking=!1,this.audioBuffer=[],this.modelResetFunc(),this.redemptionCounter=0};pause=()=>{this.active=!1,this.reset()};resume=()=>{this.active=!0};endSegment=()=>{const e=this.audioBuffer;this.audioBuffer=[];const t=this.speaking;this.reset();const i=e.reduce((e,t)=>e+ +t.isSpeech,0);if(t){if(i>=this.options.minSpeechFrames){const t=c(e.map(e=>e.frame));return{msg:s.SpeechEnd,audio:t}}return{msg:s.VADMisfire}}return{}};process=async e=>{if(!this.active)return{};const t=await this.modelProcessFunc(e);if(this.audioBuffer.push({frame:e,isSpeech:t.isSpeech>=this.options.positiveSpeechThreshold}),t.isSpeech>=this.options.positiveSpeechThreshold&&this.redemptionCounter&&(this.redemptionCounter=0),t.isSpeech>=this.options.positiveSpeechThreshold&&!this.speaking)return this.speaking=!0,{probs:t,msg:s.SpeechStart};if(t.isSpeech<this.options.negativeSpeechThreshold&&this.speaking&&++this.redemptionCounter>=this.options.redemptionFrames){this.redemptionCounter=0,this.speaking=!1;const e=this.audioBuffer;if(this.audioBuffer=[],e.reduce((e,t)=>e+ +t.isSpeech,0)>=this.options.minSpeechFrames){const i=c(e.map(e=>e.frame));return{probs:t,msg:s.SpeechEnd,audio:i}}return{probs:t,msg:s.VADMisfire}}if(!this.speaking)for(;this.audioBuffer.length>this.options.preSpeechPadFrames;)this.audioBuffer.shift();return{probs:t}}}class p{ort;modelFetcher;static new=async(e,t)=>{const i=new p(e,t);return await i.init(),i};_session;_h;_c;_sr;constructor(e,t){this.ort=e,this.modelFetcher=t}init=async()=>{o.A.debug("[VAD] initializing");const e=await this.modelFetcher();this._session=await this.ort.InferenceSession.create(e),this._sr=new this.ort.Tensor("int64",[BigInt(16e3)]),this.reset_state(),o.A.debug("[VAD] is initialized")};reset_state=()=>{const e=Array(128).fill(0);this._h=new this.ort.Tensor("float32",e,[2,1,64]),this._c=new this.ort.Tensor("float32",e,[2,1,64])};process=async e=>{const t={input:new this.ort.Tensor("float32",e,[1,e.length]),h:this._h,c:this._c,sr:this._sr},i=await this._session.run(t);this._h=i.hn,this._c=i.cn;const s=i.output.data[0];return{notSpeech:1-s,isSpeech:s}}}const l={positiveSpeechThreshold:.5,negativeSpeechThreshold:.35,preSpeechPadFrames:1,redemptionFrames:8,frameSamples:1536,minSpeechFrames:3,onFrameProcessed:e=>{},onVADMisfire:()=>{o.A.debug("VAD misfire")},onSpeechStart:()=>{o.A.debug("Detected speech start")},onSpeechEnd:()=>{o.A.debug("Detected speech end")},stream:new MediaStream,assetBasePath:""};class u{options;static async new(e={}){const t=new u({...l,...e});return await t.init(),t}audioContext;stream;audioNodeVAD;listening=!1;constructor(e){this.options=e,h(e)}init=async()=>{if(o.A.info("Initializing real time vad."),void 0===this.options.stream){if(!navigator.mediaDevices?.getUserMedia)throw new Error("Microphone access is not available in this context");this.stream=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,autoGainControl:!0,noiseSuppression:!0}})}else this.stream=this.options.stream;this.audioContext=new AudioContext;const e=new MediaStreamAudioSourceNode(this.audioContext,{mediaStream:this.stream});this.audioNodeVAD=await g.new(this.audioContext,this.options),this.audioNodeVAD.receive(e)};pause=()=>{this.audioNodeVAD.pause(),this.listening=!1};start=()=>{this.audioNodeVAD.start(),this.listening=!0}}class g{ctx;options;static async new(e,t={}){const i=new g(e,{...l,...t});return await i.init(),i}frameProcessor;entryNode;constructor(e,t){this.ctx=e,this.options=t,h(t)}pause=()=>{this.frameProcessor.pause()};start=()=>{this.frameProcessor.resume()};receive=e=>{e.connect(this.entryNode)};processFrame=async e=>{const{probs:t,msg:i,audio:n}=await this.frameProcessor.process(e);switch(void 0!==t&&this.options.onFrameProcessed(t),i){case s.SpeechStart:this.options.onSpeechStart();break;case s.VADMisfire:this.options.onVADMisfire();break;case s.SpeechEnd:this.options.onSpeechEnd(n)}};init=async()=>{await this.ctx.audioWorklet.addModule(this.options.assetBasePath+"vad.worklet.bundle.min.js");const e=new AudioWorkletNode(this.ctx,"vad-helper-worklet",{processorOptions:{frameSamples:this.options.frameSamples}});this.entryNode=e,a.env.wasm.wasmPaths=this.options.assetBasePath;const t=await p.new(a,this.modelFetcher);this.frameProcessor=new d(t.process,t.reset_state,{frameSamples:this.options.frameSamples,positiveSpeechThreshold:this.options.positiveSpeechThreshold,negativeSpeechThreshold:this.options.negativeSpeechThreshold,redemptionFrames:this.options.redemptionFrames,preSpeechPadFrames:this.options.preSpeechPadFrames,minSpeechFrames:this.options.minSpeechFrames}),e.port.onmessage=async e=>{switch(e.data?.message){case s.AudioFrame:{const t=e.data.data,i=new Float32Array(t);await this.processFrame(i);break}}}};modelFetcher=async()=>{const e=this.options.assetBasePath+"silero_vad.onnx";return await fetch(e).then(e=>e.arrayBuffer())}}var m,S=i(838);!function(e){e.startTranscription="startTranscription",e.stopTranscription="stopTranscription"}(m||(m={}));var f=i(58),w=i(388),A=i(33),b=i(992),M=i(1);class v{transcript;stability;confidence;languageCode;isFinal;requestId;constructor(e,t,i,s,n,a=(0,M.g)()){this.transcript=e,this.stability=t,this.confidence=i,this.languageCode=s,this.isFinal=n,this.requestId=a}toJSON(){return{action:b.A.InterimTranscript,data:{requestId:this.requestId,transcript:this.transcript,stability:this.stability,confidence:this.confidence,languageCode:this.languageCode,isFinal:this.isFinal}}}}const k=48e3,V="[Google STT]";class T{options;ws;mediaRecorder;speechBuffer=new n.RingBuffer(5);recordingLive=!1;headerBlob;scriptsLoadedPromise;reconnectWs=!0;wsReconnectMessageQueue=[];storedTranscription;awaitingFinalTranscript=!1;isMicrophoneMuted=!0;isTemporarilyPaused=!1;vadInitialising=!1;pausedDuringVadInit=!1;isSignalingReconnecting=!1;stream;micVad;digitalHumanSpeaking=!1;interruptionBlocked=!1;constructor(e){this.options=e,this.options.assetBasePath=this.options.assetBasePath??"https://cdn.uneeq.io/assets/platform/speech-recognition/",this.options.enableVad=this.options.enableVad??!0,this.options.enableInterrupt=this.options.enableInterrupt??!1,this.loadSavedAudioHeaders(),this.loadScripts(),this.handleAppMessages()}async startRecognition(){o.A.info(`${V} start recognition`),this.reconnectWs=!0,this.isMicrophoneMuted=!1,this.initWebsocket(this.options)}async stopRecognition(){o.A.info(`${V} stop recognition`),this.isMicrophoneMuted=!0,this.stopRecognitionInternal()}stopRecognitionInternal(){this.mediaRecorder&&"recording"===this.mediaRecorder.state&&this.mediaRecorder.stop(),this.stream&&(this.stream.getTracks().forEach(e=>{e.stop()}),this.stream=void 0,this.clientMsgSend(new S.WY(!1))),this.pauseMicVad()}pause(){return o.A.info(`${V} pausing - setting temporarily paused state`),this.isTemporarilyPaused=!0,this.vadInitialising&&(this.pausedDuringVadInit=!0,o.A.warn(`${V} pauseSpeechRecognition() called while VAD is still initialising. VAD will not auto-start when initialisation completes. Call resumeSpeechRecognition() after receiving EnableMicrophoneUpdated to start VAD.`)),this.stream&&this.micVad||o.A.warn(`${V} Problem pausing speech recognition, stream live: ${void 0!==this.stream} or microphone voice detection live: ${void 0!==this.micVad}.`),this.sendStoredTranscriptionIfReady(),this.stream?.getTracks().forEach(e=>{e.enabled=!1}),this.pauseMicVad(),this.onVadSpeechEnd(),o.A.info(`${V} paused`),!0}resume(){if(o.A.info(`${V} resuming - clearing temporarily paused state`),this.isTemporarilyPaused=!1,this.interruptionBlocked&&(this.digitalHumanSpeaking=!1,this.interruptionBlocked=!1,this.clientMsgSend(new S.E1(!0))),!this.options.enableVad&&this.digitalHumanSpeaking&&(this.dataChannelMsgSend(new f.f),this.digitalHumanSpeaking=!1),this.storedTranscription=void 0,this.isMicrophoneMuted)return o.A.debug(`${V} Cannot resume: microphone is globally muted`),!1;if(this.stream&&this.micVad){this.stream.getTracks().forEach(e=>{e.enabled=!0}),o.A.info("stream and micVad exist, starting VAD");const e=this.startMicVad();return o.A.info(`${V} resumed: `+e),e}return o.A.info(`${V} No stream available after reconnect — performing full STT restart`),this.reconnectWs=!0,this.isMicrophoneMuted=!1,this.initWebsocket(this.options),!0}setChatMetadata(e){this.options.promptMetadata=e}pauseVadIfInterruptNotAllowed(){this.micVad&&!this.options.enableInterrupt&&(this.micVad.pause(),this.clientMsgSend(new S.E1(!1))),this.options.enableInterrupt||(this.interruptionBlocked=!0)}async loadScript(e){return await new Promise(t=>{const i=document.createElement("script");i.src=e,i.onload=()=>{t()},document.body.appendChild(i)})}loadScripts(){this.scriptsLoadedPromise=new Promise(e=>{this.loadScript(this.options.assetBasePath+"OpusMediaRecorder.umd.js").then(()=>{this.loadScript(this.options.assetBasePath+"encoderWorker.umd.js").then(()=>{e()}).catch(e=>{o.A.error(`${V} Error loading encoderWorker.umd.js`,o.A.serialiseError(e))})}).catch(e=>{o.A.error(`${V} Error loading OpusMediaRecorder.umd.js`,o.A.serialiseError(e))})})}handleAppMessages(){this.options.messages.subscribe(e=>{switch(e.uneeqMessageType){case S.Yg.AvatarStartedSpeaking:this.digitalHumanSpeaking=!0,this.pauseVadIfInterruptNotAllowed();break;case S.Yg.PromptResult:e.promptResult.success||this.handleSpeakingEnd();break;case S.Yg.AvatarAnswer:""===e.answerSpeech.replace(/<[^>]*>/g,"")&&this.handleSpeakingEnd();break;case S.Yg.AvatarStoppedSpeaking:this.handleSpeakingEnd();break;case S.Yg.SessionEnded:this.isSignalingReconnecting=!1,this.reconnectWs=!1,this.endRecognition();break;case S.Yg.SessionReconnecting:this.isSignalingReconnecting=!0,this.handleSpeakingEnd(),this.reconnectWs=!1,this.endRecognition();break;case S.Yg.SessionReconnectingFinished:this.isSignalingReconnecting=!1;break;case S.Yg.SoftSwitchStarting:this.handleSpeakingEnd(),this.reconnectWs=!1,this.endRecognition();break;case S.Yg.CustomMetadataUpdated:this.options.promptMetadata=e.chatMetadata;break;case S.Yg.SessionBackendError:this.handleSpeakingEnd()}})}endRecognition(){o.A.info(`${V} ending recognition`),this.stopRecognitionInternal(),this.ws&&this.ws.readyState===WebSocket.OPEN&&(this.ws.onclose=null,this.ws.close())}handleSpeakingEnd(){this.digitalHumanSpeaking=!1,this.interruptionBlocked=!1,this.isMicrophoneMuted||this.isTemporarilyPaused||(o.A.info("mic not muted and not paused, starting VAD after digital human speaking ended"),this.startMicVad()),this.options.enableInterrupt||this.clientMsgSend(new S.E1(!0))}initWebsocket(e){o.A.info(`${V} initWebsocket - stopping any existing recognition`),this.endRecognition();let t=e.apiUrl.replace("https://","wss://").replace("http://","ws://");t+=`/speech-recognition-service/ws/recognize?jwt=${e.jwtToken}&session_id=${e.sessionId}`,o.A.info(`${V} Initializing speech recognition websocket to `+t),this.ws=new WebSocket(t),this.ws.onopen=()=>{o.A.info(`${V} Speech recognition web socket opened`),this.sendWebsocketMessageQueue(),this.initMicrophone()},this.ws.onclose=e=>{this.handleWebsocketClose(e)},this.ws.onerror=e=>{o.A.error(`${V} WebSocket error`,o.A.serialiseError(e)),this.isSignalingReconnecting?o.A.info(`${V} WebSocket error suppressed — signaling reconnection in progress`):this.clientMsgSend(new S.Cj("Speech recognition service connection failed"))},this.ws.onmessage=e=>{this.handleWebsocketMessage(e)}}sendWebsocketMessageQueue(){o.A.info(`${V} Sending all queued websocket messages, queue length: `+this.wsReconnectMessageQueue.length),this.wsReconnectMessageQueue.forEach(e=>{this.wsSend(e,!1,!1)}),this.wsReconnectMessageQueue=[]}async loadSavedAudioHeaders(){const e=this.options.assetBasePath+"opus-media-recorder-header.webm",t=await fetch(e),i=await t.arrayBuffer();this.headerBlob=new Blob([i],{type:"audio/webm;codecs=opus"})}initVoiceActivityDetection(e){o.A.info("[VAD] Going to initialize VAD module, with enableVad = "+this.options.enableVad),this.vadInitialising=!0,this.pausedDuringVadInit=!1,u.new({onSpeechStart:()=>{this.onVadSpeechStart()},onSpeechEnd:()=>{this.onVadSpeechEnd()},minSpeechFrames:1,positiveSpeechThreshold:this.options.enableVad?.5:0,negativeSpeechThreshold:this.options.enableVad?.35:0,redemptionFrames:8,assetBasePath:this.options.assetBasePath,stream:e}).then(e=>{this.micVad=e,this.vadInitialising=!1,this.interruptionBlocked||this.isMicrophoneMuted||this.isTemporarilyPaused?(this.interruptionBlocked&&this.clientMsgSend(new S.E1(!1)),this.pausedDuringVadInit?o.A.warn("[VAD] VAD module created but not started because pauseSpeechRecognition() was called during async VAD initialisation. Call resumeSpeechRecognition() to start VAD."):o.A.info(`[VAD] VAD module created but not started: interruptionBlocked=${this.interruptionBlocked}, isMicrophoneMuted=${this.isMicrophoneMuted}, isTemporarilyPaused=${this.isTemporarilyPaused}`)):(o.A.info(`${V} interruption not blocked and mic not muted, starting VAD module`),this.startMicVad()),this.pausedDuringVadInit=!1,o.A.info("[VAD] module has started")}).catch(e=>{this.vadInitialising=!1,this.pausedDuringVadInit=!1,o.A.error("[VAD] could not start module",o.A.serialiseError(e))})}startMicVad(){return!this.micVad||!this.options.enableInterrupt&&this.digitalHumanSpeaking?(o.A.info(`[VAD] VAD has not been started, microphone voice detection live: ${void 0!==this.micVad}, enableInterrupt: ${this.options.enableInterrupt}, isDigitalHumanSpeaking: ${this.digitalHumanSpeaking}.`),!1):(this.micVad.start(),o.A.info("[VAD] VAD has been started / unpaused"),!0)}pauseMicVad(){return this.micVad?(this.micVad.pause(),o.A.info("[VAD] has been paused"),!0):(o.A.info("[VAD] has not been initialized so has not been paused "),!1)}onVadSpeechStart(){o.A.info("[VAD] User started speaking"),this.dataChannelMsgSend(new A.A(A.f.Start)),this.clientMsgSend(new S._4),this.sendStartMessage(),this.wsSend(this.headerBlob),o.A.info("[VAD] Sending buffered speech audio");const e=new Blob(this.speechBuffer.toArray(),{type:"audio/webm;codecs=opus"});this.wsSend(e),this.recordingLive=!0,this.speechBuffer.clear()}onVadSpeechEnd(){o.A.info("[VAD] User has stopped speaking or intentionally ending current VAD"),this.dataChannelMsgSend(new A.A(A.f.Stop)),this.clientMsgSend(new S.im),this.recordingLive=!1,this.sendStopMessage()}sendStoredTranscriptionIfReady(){this.options.enableVad||void 0===this.storedTranscription||this.awaitingFinalTranscript||(this.sendChatPrompt(this.storedTranscription.transcript),this.clientMsgSend(new S.Ux(this.storedTranscription)),this.storedTranscription=void 0)}handleWebsocketMessage(e){o.A.debug(`${V} Got a websocket message: `,e);try{if("string"==typeof e.data){const t=JSON.parse(e.data),i=t.results?t.results[0]:null;"response"===t.state&&i&&(o.A.info(`${V} Speech transcription result ${JSON.stringify(i)}`),this.options.enableVad?this.handleVadTranscriptionResult(i):this.handlePttTranscriptionResult(i))}}catch(e){o.A.error(`${V} Error processing message speech recognition socket message`,o.A.serialiseError(e))}}handleWebsocketClose(e){o.A.warn(`${V} WebSocket closed`,e),this.reconnectWs&&this.initWebsocket(this.options)}sendStartMessage(){const e={action:m.startTranscription,channels:1,sampleRate:k,interimResults:!0,lang:this.options.locales||"en-US",phrases:this.options.hintPhrases??"",phraseBoost:this.options.hintPhrasesBoost??0};this.wsSend(JSON.stringify(e))}sendStopMessage(){const e={action:m.stopTranscription};this.wsSend(JSON.stringify(e),!0)}wsSend(e,t=!1,i=!0){this.ws.readyState===WebSocket.OPEN&&this.ws.send(e),this.reconnectWs&&i&&(this.wsReconnectMessageQueue.push(e),t&&this.ws.readyState===WebSocket.OPEN&&(this.wsReconnectMessageQueue=[]),this.wsReconnectMessageQueue.length>3e3&&this.handleWsReconnectQueueOverflow())}handleWsReconnectQueueOverflow(){o.A.warn(`${V} The websocket reconnection queue has exceeded maximum length of 3000. The audio reconnection queue will be reset, speech transcription performance may be impacted in the event of a reconnection.`),this.wsReconnectMessageQueue=[]}initMicrophone(){if(this.stream)o.A.warn("[Microphone] Microphone already initialized, stream already exists, skipping initialization.");else{if(!navigator.mediaDevices?.getUserMedia)return o.A.error("[Microphone] navigator.mediaDevices.getUserMedia is not available in this context"),void this.clientMsgSend(new S.co(new Error("Microphone access is not available in this context")));navigator.mediaDevices.getUserMedia({audio:{deviceId:this.options.microphoneDeviceId?{exact:this.options.microphoneDeviceId}:void 0,echoCancellation:!0,sampleRate:k,channelCount:1}}).then(e=>{this.stream=e,this.initVoiceActivityDetection(e),this.initMediaRecorder(e),this.clientMsgSend(new S.WY(!0))}).catch(e=>{o.A.error("[Microphone] Error starting recording",o.A.serialiseError(e)),this.clientMsgSend(new S.co(new Error(JSON.stringify(e))))})}}getMediaRecorder(e,t,i){return new window.OpusMediaRecorder(e,t,i)}initMediaRecorder(e){const t={OggOpusEncoderWasmPath:this.options.assetBasePath+"OggOpusEncoder.wasm",WebMOpusEncoderWasmPath:this.options.assetBasePath+"WebMOpusEncoder.wasm"},i={mimeType:"audio/webm",audioBitsPerSecond:k};this.scriptsLoadedPromise.then(()=>{this.mediaRecorder=this.getMediaRecorder(e,i,t),this.mediaRecorder.ondataavailable=e=>{this.mediaRecorderOnData(e)},this.mediaRecorder.onstop=()=>{this.sendStopMessage()},this.mediaRecorder.start(100)}).catch(e=>{o.A.error("[Microphone] Error initializing media recorder",o.A.serialiseError(e))})}mediaRecorderOnData(e){e.data&&e.data.size>0&&(this.recordingLive?this.wsSend(e.data):this.speechBuffer.add(e.data))}handlePttTranscriptionResult(e){this.awaitingFinalTranscript=!e.final;try{this.recordingLive?e.final?void 0===this.storedTranscription?this.storedTranscription=e:this.storedTranscription.transcript+=e.transcript:this.digitalHumanSpeaking&&(this.dataChannelMsgSend(new f.f),this.clientMsgSend(new S.tc),this.digitalHumanSpeaking=!1):e.final&&(void 0!==this.storedTranscription&&(e.transcript=this.storedTranscription.transcript+e.transcript),this.options.promptMetadata.userSpokenLocale=e.language_code,this.sendChatPrompt(e.transcript),this.sendInterimTranscription(e),this.clientMsgSend(new S.Ux(e)),this.storedTranscription=void 0),(e.stability??1)>.5&&!e.final&&(void 0!==this.storedTranscription&&(e.transcript=this.storedTranscription.transcript+e.transcript),this.clientMsgSend(new S.Ux(e)),this.sendInterimTranscription(e))}catch{this.clientMsgSend(new S.Cj("Error processing speech transcription result")),this.storedTranscription=void 0}}handleVadTranscriptionResult(e){""!==e.transcript&&(e.final?(this.options.promptMetadata.userSpokenLocale=e.language_code,this.sendChatPrompt(e.transcript)):this.digitalHumanSpeaking&&(this.dataChannelMsgSend(new f.f),this.clientMsgSend(new S.tc),this.digitalHumanSpeaking=!1),this.sendInterimTranscription(e)),((e.stability??1)>.5||e.final)&&this.clientMsgSend(new S.Ux(e))}sendChatPrompt(e){e&&""!==e.trim()&&(this.pauseVadIfInterruptNotAllowed(),this.dataChannelMsgSend(new w.D(e,this.options.promptMetadata)))}sendInterimTranscription(e){if(e.transcript&&""!==e.transcript.trim()){const t=e.stability??1;this.dataChannelMsgSend(new v(e.transcript,t,e.confidence,e.language_code,e.final))}}dataChannelMsgSend(e){this.options.sendMessage(e)}clientMsgSend(e){this.options.messages.next(e)}}}}]);
|
|
2
2
|
//# sourceMappingURL=948.index.js.map
|
package/dist/948.index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"948.index.js","mappings":"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","sources":["webpack://Uneeq/./src/lib/vad/messages.ts","webpack://Uneeq/./src/lib/vad/frame-processor.ts","webpack://Uneeq/./src/lib/vad/models.ts","webpack://Uneeq/./src/lib/vad/real-time-vad.ts","webpack://Uneeq/./src/types/SpeechRecognitionMessageAction.ts","webpack://Uneeq/./src/webrtc-data-channel/messages/InterimTranscript.ts","webpack://Uneeq/./src/google-stt.ts"],"sourcesContent":["export enum Message {\n AudioFrame = 'AUDIO_FRAME',\n SpeechStart = 'SPEECH_START',\n VADMisfire = 'VAD_MISFIRE',\n SpeechEnd = 'SPEECH_END',\n}\n","/*\nSome of this code, together with the default options found in index.ts,\nwere taken (or took inspiration) from https://github.com/snakers4/silero-vad\n*/\n\nimport Logger from '../logger';\nimport { Message } from './messages';\nimport { SpeechProbabilities } from './models';\n\nconst RECOMMENDED_FRAME_SAMPLES = [512, 1024, 1536];\n\nexport interface FrameProcessorOptions {\n /** Threshold over which values returned by the Silero VAD model will be considered as positively indicating speech.\n * The Silero VAD model is run on each frame. This number should be between 0 and 1.\n */\n positiveSpeechThreshold: number;\n\n /** Threshold under which values returned by the Silero VAD model will be considered as indicating an absence of speech.\n * Note that the creators of the Silero VAD have historically set this number at 0.15 less than `positiveSpeechThreshold`.\n */\n negativeSpeechThreshold: number;\n\n /** After a VAD value under the `negativeSpeechThreshold` is observed, the algorithm will wait `redemptionFrames` frames\n * before running `onSpeechEnd`. If the model returns a value over `positiveSpeechThreshold` during this grace period, then\n * the algorithm will consider the previously-detected \"speech end\" as having been a false negative.\n */\n redemptionFrames: number;\n\n /** Number of audio samples (under a sample rate of 16000) to comprise one \"frame\" to feed to the Silero VAD model.\n * The `frame` serves as a unit of measurement of lengths of audio segments and many other parameters are defined in terms of\n * frames. The authors of the Silero VAD model offer the following warning:\n * > WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000 sample rate and\n * 256, 512, 768 samples for 8000 sample rate.\n * > Values other than these may affect model performance!!\n * In this context, audio fed to the VAD model always has sample rate 16000. It is probably a good idea to leave this at 1536.\n */\n frameSamples: number;\n\n /** Number of frames to prepend to the audio segment that will be passed to `onSpeechEnd`. */\n preSpeechPadFrames: number;\n\n /** If an audio segment is detected as a speech segment according to initial algorithm but it has fewer than `minSpeechFrames`,\n * it will be discarded and `onVADMisfire` will be run instead of `onSpeechEnd`.\n */\n minSpeechFrames: number;\n}\n\nexport const defaultFrameProcessorOptions: FrameProcessorOptions = {\n positiveSpeechThreshold: 0.5,\n negativeSpeechThreshold: 0.5 - 0.15,\n preSpeechPadFrames: 1,\n redemptionFrames: 8,\n frameSamples: 1536,\n minSpeechFrames: 3,\n};\n\nexport function validateOptions(options: FrameProcessorOptions): void {\n if (!RECOMMENDED_FRAME_SAMPLES.includes(options.frameSamples)) {\n Logger.warn('You are using an unusual frame size');\n }\n if (\n options.positiveSpeechThreshold < 0 ||\n options.negativeSpeechThreshold > 1\n ) {\n Logger.error('postiveSpeechThreshold should be a number between 0 and 1');\n }\n if (\n options.negativeSpeechThreshold < 0 ||\n options.negativeSpeechThreshold > options.positiveSpeechThreshold\n ) {\n Logger.error(\n 'negativeSpeechThreshold should be between 0 and positiveSpeechThreshold'\n );\n }\n if (options.preSpeechPadFrames < 0) {\n Logger.error('preSpeechPadFrames should be positive');\n }\n if (options.redemptionFrames < 0) {\n Logger.error('preSpeechPadFrames should be positive');\n }\n}\n\nexport interface FrameProcessorInterface {\n resume: () => void;\n process: (arr: Float32Array) => Promise<{\n probs?: SpeechProbabilities\n msg?: Message\n audio?: Float32Array\n }>;\n endSegment: () => { msg?: Message; audio?: Float32Array };\n}\n\nconst concatArrays = (arrays: Float32Array[]): Float32Array => {\n const sizes = arrays.reduce(\n (out, next) => {\n out.push(out[out.length - 1] + next.length);\n return out;\n },\n [0]\n );\n const outArray = new Float32Array(sizes[sizes.length - 1]);\n arrays.forEach((arr, index) => {\n const place = sizes[index];\n outArray.set(arr, place);\n });\n return outArray;\n};\n\nexport class FrameProcessor implements FrameProcessorInterface {\n public speaking: boolean = false;\n public audioBuffer: Array<{ frame: Float32Array; isSpeech: boolean }>;\n public redemptionCounter: number = 0;\n public active: boolean = false;\n\n constructor(\n public modelProcessFunc: (\n frame: Float32Array\n ) => Promise<SpeechProbabilities>,\n public modelResetFunc: () => void,\n public options: FrameProcessorOptions\n ) {\n this.audioBuffer = [];\n this.reset();\n }\n\n public reset = () => {\n this.speaking = false;\n this.audioBuffer = [];\n this.modelResetFunc();\n this.redemptionCounter = 0;\n }\n\n public pause = () => {\n this.active = false;\n this.reset();\n }\n\n public resume = () => {\n this.active = true;\n }\n\n public endSegment = () => {\n const audioBuffer = this.audioBuffer;\n this.audioBuffer = [];\n const speaking = this.speaking;\n this.reset();\n\n const speechFrameCount = audioBuffer.reduce((acc, item) => {\n return acc + +item.isSpeech;\n }, 0);\n\n if (speaking) {\n if (speechFrameCount >= this.options.minSpeechFrames) {\n const audio = concatArrays(audioBuffer.map((item) => item.frame));\n return { msg: Message.SpeechEnd, audio };\n } else {\n return { msg: Message.VADMisfire };\n }\n }\n return {};\n }\n\n public process = async (frame: Float32Array) => {\n if (!this.active) {\n return {};\n }\n const probs = await this.modelProcessFunc(frame);\n this.audioBuffer.push({\n frame,\n isSpeech: probs.isSpeech >= this.options.positiveSpeechThreshold,\n });\n\n if (\n probs.isSpeech >= this.options.positiveSpeechThreshold &&\n this.redemptionCounter\n ) {\n this.redemptionCounter = 0;\n }\n\n if (\n probs.isSpeech >= this.options.positiveSpeechThreshold &&\n !this.speaking\n ) {\n this.speaking = true;\n return { probs, msg: Message.SpeechStart };\n }\n\n if (\n probs.isSpeech < this.options.negativeSpeechThreshold &&\n this.speaking &&\n ++this.redemptionCounter >= this.options.redemptionFrames\n ) {\n this.redemptionCounter = 0;\n this.speaking = false;\n\n const audioBuffer = this.audioBuffer;\n this.audioBuffer = [];\n\n const speechFrameCount = audioBuffer.reduce((acc, item) => {\n return acc + +item.isSpeech;\n }, 0);\n\n if (speechFrameCount >= this.options.minSpeechFrames) {\n const audio = concatArrays(audioBuffer.map((item) => item.frame));\n return { probs, msg: Message.SpeechEnd, audio };\n } else {\n return { probs, msg: Message.VADMisfire };\n }\n }\n\n if (!this.speaking) {\n while (this.audioBuffer.length > this.options.preSpeechPadFrames) {\n this.audioBuffer.shift();\n }\n }\n return { probs };\n }\n}\n","import Logger from \"../logger\";\n\n/** Minimal interface describing the ONNX Tensor fields this VAD module accesses. */\ninterface OrtTensor {\n readonly data: ArrayLike<number>\n}\n\n/** Minimal interface describing the ONNX InferenceSession output map. */\ninterface OrtSessionOutput {\n hn: OrtTensor\n cn: OrtTensor\n output: OrtTensor\n}\n\n/** Minimal ONNX Runtime API interface describing only what the VAD module uses. */\nexport interface ONNXRuntimeAPI {\n InferenceSession: {\n create(model: ArrayBuffer): Promise<{\n run(feeds: Record<string, OrtTensor>): Promise<OrtSessionOutput>\n }>\n }\n Tensor: new (type: string, data: unknown, dims?: number[]) => OrtTensor\n}\n\nexport type ModelFetcher = () => Promise<ArrayBuffer>;\n\nexport interface SpeechProbabilities {\n notSpeech: number;\n isSpeech: number;\n}\n\nexport interface Model {\n reset_state: () => void;\n process: (arr: Float32Array) => Promise<SpeechProbabilities>;\n}\n\nexport class Silero {\n\n public static new = async (ort: ONNXRuntimeAPI, modelFetcher: ModelFetcher) => {\n const model = new Silero(ort, modelFetcher);\n await model.init();\n return model;\n }\n public _session!: { run(feeds: Record<string, OrtTensor>): Promise<OrtSessionOutput> };\n public _h!: OrtTensor;\n public _c!: OrtTensor;\n public _sr!: OrtTensor;\n\n constructor(\n private ort: ONNXRuntimeAPI,\n private modelFetcher: ModelFetcher\n ) {}\n\n public init = async () => {\n Logger.debug('[VAD] initializing');\n const modelArrayBuffer = await this.modelFetcher();\n this._session = await this.ort.InferenceSession.create(modelArrayBuffer);\n this._sr = new this.ort.Tensor('int64', [BigInt(16000)]);\n this.reset_state();\n Logger.debug('[VAD] is initialized');\n }\n\n public reset_state = () => {\n const zeroes = Array(2 * 64).fill(0);\n this._h = new this.ort.Tensor('float32', zeroes, [2, 1, 64]);\n this._c = new this.ort.Tensor('float32', zeroes, [2, 1, 64]);\n }\n\n public process = async (audioFrame: Float32Array): Promise<SpeechProbabilities> => {\n const t = new this.ort.Tensor('float32', audioFrame, [1, audioFrame.length]);\n const inputs = {\n input: t,\n h: this._h,\n c: this._c,\n sr: this._sr,\n };\n const out = await this._session.run(inputs);\n this._h = out.hn;\n this._c = out.cn;\n const isSpeech = out.output.data[0];\n const notSpeech = 1 - isSpeech;\n return { notSpeech, isSpeech };\n }\n}\n","/**\n * This VAD module is a fork from the following project:\n * https://github.com/ricky0123/vad/tree/master/packages/web\n *\n * It has been modified to only include what is required for our\n * own VAD implementation.\n *\n * This module is a wrapper to bring the following ML Model into\n * JS: https://github.com/snakers4/silero-vad\n */\n\nimport * as ort from 'onnxruntime-web';\nimport { defaultFrameProcessorOptions, FrameProcessor, FrameProcessorOptions, validateOptions } from './frame-processor';\nimport { Message } from './messages';\nimport { Silero, SpeechProbabilities, type ONNXRuntimeAPI } from './models';\nimport Logger from '../logger';\n\ninterface RealTimeVADCallbacks {\n /** Callback to run after each frame. The size (number of samples) of a frame is given by `frameSamples`. */\n onFrameProcessed: (probabilities: SpeechProbabilities) => void;\n\n /** Callback to run if speech start was detected but `onSpeechEnd` will not be run because the\n * audio segment is smaller than `minSpeechFrames`.\n */\n onVADMisfire: () => void;\n\n /** Callback to run when speech start is detected */\n onSpeechStart: () => void;\n\n /**\n * Callback to run when speech end is detected.\n * Takes as arg a Float32Array of audio samples between -1 and 1, sample rate 16000.\n * This will not run if the audio segment is smaller than `minSpeechFrames`.\n */\n onSpeechEnd: (audio: Float32Array) => void;\n}\n\ninterface RealTimeVADOptionsWithStream\n extends FrameProcessorOptions,\n RealTimeVADCallbacks {\n stream: MediaStream;\n assetBasePath: string;\n}\n\nexport type RealTimeVADOptions = RealTimeVADOptionsWithStream;\n\nexport const defaultRealTimeVADOptions: RealTimeVADOptions = {\n ...defaultFrameProcessorOptions,\n onFrameProcessed: (_probabilities) => {},\n onVADMisfire: () => {\n Logger.debug('VAD misfire');\n },\n onSpeechStart: () => {\n Logger.debug('Detected speech start');\n },\n onSpeechEnd: () => {\n Logger.debug('Detected speech end');\n },\n stream: new MediaStream(),\n assetBasePath: ''\n};\n\nexport class MicVAD {\n\n public static async new(options: Partial<RealTimeVADOptions> = {}): Promise<MicVAD> {\n const vad = new MicVAD({ ...defaultRealTimeVADOptions, ...options });\n await vad.init();\n return vad;\n }\n // @ts-expect-error assigned in init()\n public audioContext: AudioContext;\n // @ts-expect-error assigned in init()\n public stream: MediaStream;\n // @ts-expect-error assigned in init()\n public audioNodeVAD: AudioNodeVAD;\n public listening: boolean = false;\n\n constructor(public options: RealTimeVADOptions) {\n validateOptions(options);\n }\n\n public init = async () => {\n Logger.info('Initializing real time vad.');\n if (this.options.stream === undefined) {\n if (!navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone access is not available in this context')\n }\n this.stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n channelCount: 1,\n echoCancellation: true,\n autoGainControl: true,\n noiseSuppression: true,\n },\n });\n } else { this.stream = this.options.stream; }\n\n this.audioContext = new AudioContext();\n const source = new MediaStreamAudioSourceNode(this.audioContext, {\n mediaStream: this.stream,\n });\n\n this.audioNodeVAD = await AudioNodeVAD.new(this.audioContext, this.options);\n this.audioNodeVAD.receive(source);\n }\n\n public pause = () => {\n this.audioNodeVAD.pause();\n this.listening = false;\n }\n\n public start = () => {\n this.audioNodeVAD.start();\n this.listening = true;\n }\n}\n\nexport class AudioNodeVAD {\n\n public static async new(\n ctx: AudioContext,\n options: Partial<RealTimeVADOptions> = {}\n ): Promise<AudioNodeVAD> {\n const vad = new AudioNodeVAD(ctx, {\n ...defaultRealTimeVADOptions,\n ...options\n });\n await vad.init();\n return vad;\n }\n // @ts-expect-error assigned in init()\n public frameProcessor: FrameProcessor;\n // @ts-expect-error assigned in init()\n public entryNode: AudioNode;\n\n constructor(public ctx: AudioContext, public options: RealTimeVADOptions) {\n validateOptions(options);\n }\n\n public pause = () => {\n this.frameProcessor.pause();\n }\n\n public start = () => {\n this.frameProcessor.resume();\n }\n\n public receive = (node: AudioNode) => {\n node.connect(this.entryNode);\n }\n\n public processFrame = async (frame: Float32Array) => {\n const { probs, msg, audio } = await this.frameProcessor.process(frame);\n if (probs !== undefined) {\n this.options.onFrameProcessed(probs);\n }\n switch (msg) {\n case Message.SpeechStart:\n this.options.onSpeechStart();\n break;\n\n case Message.VADMisfire:\n this.options.onVADMisfire();\n break;\n\n case Message.SpeechEnd:\n // @ts-expect-error audio param type mismatch\n this.options.onSpeechEnd(audio);\n break;\n\n default:\n break;\n }\n }\n\n public init = async () => {\n await this.ctx.audioWorklet.addModule(this.options.assetBasePath + 'vad.worklet.bundle.min.js');\n const vadNode = new AudioWorkletNode(this.ctx, 'vad-helper-worklet', {\n processorOptions: {\n frameSamples: this.options.frameSamples,\n },\n });\n this.entryNode = vadNode;\n\n // set the base path for loading models\n ort.env.wasm.wasmPaths = this.options.assetBasePath;\n\n const model = await Silero.new(ort as unknown as ONNXRuntimeAPI, this.modelFetcher);\n\n this.frameProcessor = new FrameProcessor(model.process, model.reset_state, {\n frameSamples: this.options.frameSamples,\n positiveSpeechThreshold: this.options.positiveSpeechThreshold,\n negativeSpeechThreshold: this.options.negativeSpeechThreshold,\n redemptionFrames: this.options.redemptionFrames,\n preSpeechPadFrames: this.options.preSpeechPadFrames,\n minSpeechFrames: this.options.minSpeechFrames,\n });\n\n vadNode.port.onmessage = async (ev: MessageEvent) => {\n switch (ev.data?.message) {\n case Message.AudioFrame: {\n const buffer: ArrayBuffer = ev.data.data;\n const frame = new Float32Array(buffer);\n await this.processFrame(frame);\n break;\n }\n\n default:\n break;\n }\n };\n }\n\n public modelFetcher = async () => {\n const modelURL = this.options.assetBasePath + 'silero_vad.onnx';\n return await fetch(modelURL).then((r) => r.arrayBuffer());\n }\n\n}\n","export enum SpeechRecognitionMessageAction {\n startTranscription = 'startTranscription',\n stopTranscription = 'stopTranscription'\n}\n","import { type DataChannelAction } from \"../DataChannelAction\"\nimport { type DataChannelMessage } from \"../DataChannelMessage\"\nimport { DataChannelActionType } from \"../DataChannelActionType\"\nimport { uuidv4 } from '../../lib/uuid'\n\nexport interface InterimTranscriptData {\n requestId: string\n\n confidence: number\n isFinal: boolean\n languageCode: string\n stability: number\n transcript: string \n}\n\nexport class InterimTranscript implements DataChannelMessage {\n constructor(private readonly transcript: string, private readonly stability: number, private readonly confidence: number, private readonly languageCode: string, private readonly isFinal: boolean, private readonly requestId = uuidv4()) {}\n public toJSON(): DataChannelAction {\n const action: DataChannelAction = {\n action: DataChannelActionType.InterimTranscript,\n data: {\n requestId: this.requestId,\n transcript: this.transcript,\n stability: this.stability,\n confidence: this.confidence,\n languageCode: this.languageCode,\n isFinal: this.isFinal\n }\n }\n return action\n }\n}\n","import { RingBuffer } from 'ring-buffer-ts'\nimport { MicVAD } from './lib/vad/real-time-vad'\nimport { type AvatarAnswerMessage, AvatarInterruptedMessage, CustomMetadataUpdated, DeviceErrorMessage, type PromptResultMessage, SessionErrorMessage, SpeechTranscriptionMessage, UneeqMessageType, UserStartedSpeakingMessage, UserStoppedSpeakingMessage, VadInterruptionAllowedMessage, EnableMicrophoneUpdatedMessage, type UneeqMessage } from './types/UneeqMessages'\nimport { type SpeechRecognitionOptions } from './types/SpeechHandlerOptions'\nimport { SpeechRecognitionMessageAction } from './types/SpeechRecognitionMessageAction'\nimport { type SpeechRecognitionStartMessage } from './types/SpeechRecognitionStartMessage'\nimport { type SpeechRecognitionStopMessage } from './types/SpeechRecognitionStopMessage'\nimport { type SpeechTranscriptionResult } from './types/SpeechTranscriptionResult'\nimport Logger from \"./lib/logger\"\nimport { type PromptMetadata } from './types/PromptMetadata'\nimport { StopSpeaking } from './webrtc-data-channel/messages/StopSpeaking'\nimport { ChatPrompt } from './webrtc-data-channel/messages/ChatPrompt'\nimport { UserSpeaking, UserSpeakingState } from './webrtc-data-channel/messages/UserSpeaking'\nimport { type DataChannelMessage } from './webrtc-data-channel/DataChannelMessage'\nimport { InterimTranscript } from './webrtc-data-channel/messages/InterimTranscript'\nimport { type SpeechRecognitionInterface } from './types/SpeechRecognitionInterface'\n\n// The amount of audio chunks to store at any moment before VAD activates.\n// Once VAD activates, send these stored audio chunks before the live speech.\nconst speechBufferLength: number = 5\n\n// The size in milliseconds of each audio chunk to send for transcription.\nconst audioChunkSizeMs: number = 100\n\n// The audio sample rate and channel count to use for the audio stream.\nconst audioSampleRate: number = 48000\nconst audioChannelCount: number = 1\n\n// The maximum number of audio chunks to store in the websocket reconnection queue.\n// The reconnection queue is stored in memory and is used to resend audio chunks if\n// the websocket connection is lost.\nconst websocketReconnectionQueueLimit: number = 3000\n\n// Default path to load assets from (e.g. WASM files, etc).\nconst defaultAssetPath: string = 'https://cdn.uneeq.io/assets/platform/speech-recognition/'\n\n// The minimum required transcription stability for an interim transcription message to be\n// sent to the implementer.\nconst transcriptionStabilityThreshold: number = 0.5\n\n// Logging prefix for speech recognition related logs\nconst LOG_PREFIX: string = '[Speech Recognition]'\n\ninterface WorkerOptions {\n OggOpusEncoderWasmPath: string\n WebMOpusEncoderWasmPath: string\n}\n\ninterface OpusMediaRecorderOptions {\n mimeType: string\n audioBitsPerSecond: number\n}\n\n/**\n * Google Speech-to-Text implementation\n * Uses WebRTC audio, VAD (Voice Activity Detection), and MediaRecorder\n * to send audio to UneeQ's speech recognition service via WebSocket, which is then passed to Google\n */\nexport class GoogleSTT implements SpeechRecognitionInterface {\n private ws!: WebSocket\n private mediaRecorder!: MediaRecorder\n private readonly speechBuffer = new RingBuffer<Blob>(speechBufferLength)\n private recordingLive: boolean = false\n private headerBlob!: Blob\n private scriptsLoadedPromise!: Promise<void>\n private reconnectWs: boolean = true\n private wsReconnectMessageQueue: Array<string | Blob> = []\n private storedTranscription?: SpeechTranscriptionResult\n private awaitingFinalTranscript: boolean = false\n private isMicrophoneMuted: boolean = true\n private isTemporarilyPaused: boolean = false\n private vadInitialising: boolean = false\n private pausedDuringVadInit: boolean = false\n\n public stream?: MediaStream\n public micVad!: MicVAD\n public digitalHumanSpeaking: boolean = false\n public interruptionBlocked: boolean = false\n\n constructor(public options: SpeechRecognitionOptions) {\n this.options.assetBasePath = this.options.assetBasePath ?? defaultAssetPath\n this.options.enableVad = this.options.enableVad ?? true\n this.options.enableInterrupt = this.options.enableInterrupt ?? false\n void this.loadSavedAudioHeaders()\n this.loadScripts()\n this.handleAppMessages()\n }\n\n public async startRecognition(): Promise<void> {\n Logger.info(`${LOG_PREFIX} start recognition`)\n this.reconnectWs = true\n this.isMicrophoneMuted = false\n this.initWebsocket(this.options)\n }\n\n public async stopRecognition(): Promise<void> {\n Logger.info(`${LOG_PREFIX} stop recognition`)\n this.isMicrophoneMuted = true\n this.stopRecognitionInternal()\n }\n\n private stopRecognitionInternal(): void {\n if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {\n this.mediaRecorder.stop()\n }\n if (this.stream) {\n this.stream.getTracks().forEach((track) => {\n track.stop()\n })\n this.stream = undefined\n this.clientMsgSend(new EnableMicrophoneUpdatedMessage(false))\n }\n this.pauseMicVad()\n }\n\n public pause(): boolean {\n Logger.info(`${LOG_PREFIX} pausing - setting temporarily paused state`)\n this.isTemporarilyPaused = true\n\n if (this.vadInitialising) {\n this.pausedDuringVadInit = true\n Logger.warn(`${LOG_PREFIX} pauseSpeechRecognition() called while VAD is still initialising. ` +\n `VAD will not auto-start when initialisation completes. ` +\n `Call resumeSpeechRecognition() after receiving EnableMicrophoneUpdated to start VAD.`)\n }\n\n if (!this.stream || !this.micVad) {\n Logger.warn(`${LOG_PREFIX} Problem pausing speech recognition, stream live: ${this.stream !== undefined} or microphone voice detection live: ${this.micVad !== undefined}.`)\n }\n\n this.sendStoredTranscriptionIfReady()\n this.stream?.getTracks().forEach((track) => { track.enabled = false })\n this.pauseMicVad()\n this.onVadSpeechEnd()\n\n Logger.info(`${LOG_PREFIX} paused`)\n\n return true\n }\n\n public resume(): boolean {\n Logger.info(`${LOG_PREFIX} resuming - clearing temporarily paused state`)\n this.isTemporarilyPaused = false\n\n // If interruption was blocked clear while resuming speech recognition.\n if (this.interruptionBlocked) {\n this.digitalHumanSpeaking = false\n this.interruptionBlocked = false\n this.clientMsgSend(new VadInterruptionAllowedMessage(true))\n }\n\n // If VAD is disabled and the digital human is speaking, stop the digital human from speaking when push to talk is engaged.\n if (!this.options.enableVad && this.digitalHumanSpeaking) {\n this.dataChannelMsgSend(new StopSpeaking())\n this.digitalHumanSpeaking = false\n }\n\n this.storedTranscription = undefined\n\n // Check if microphone is globally muted\n if (this.isMicrophoneMuted) {\n Logger.debug(`${LOG_PREFIX} Cannot resume: microphone is globally muted`)\n return false\n }\n\n if (this.stream && this.micVad) {\n this.stream.getTracks().forEach((track) => { track.enabled = true })\n\n Logger.info('stream and micVad exist, starting VAD')\n\n const result = this.startMicVad()\n Logger.info(`${LOG_PREFIX} resumed: ` + result)\n return result\n }\n Logger.warn(`${LOG_PREFIX} Could not resume speech recognition, stream live: ${this.stream !== undefined} or microphone voice detection live: ${this.micVad !== undefined}.`)\n return false\n }\n\n public setChatMetadata(chatMetadata: PromptMetadata): void {\n this.options.promptMetadata = chatMetadata\n }\n\n public pauseVadIfInterruptNotAllowed(): void {\n if (this.micVad && !this.options.enableInterrupt) {\n this.micVad.pause()\n this.clientMsgSend(new VadInterruptionAllowedMessage(false))\n }\n if (!this.options.enableInterrupt) {\n this.interruptionBlocked = true\n }\n }\n\n private async loadScript(url: string): Promise<void> {\n return await new Promise<void>((resolve) => {\n const script = document.createElement('script')\n script.src = url\n script.onload = () => {\n resolve()\n }\n document.body.appendChild(script)\n })\n }\n\n private loadScripts(): void {\n this.scriptsLoadedPromise = new Promise((resolve) => {\n // load the opus media recorder script first or there will be a race condition.\n this.loadScript(this.options.assetBasePath + 'OpusMediaRecorder.umd.js').then(() => {\n // once opus media recorder has finished loading, load the encoder worker.\n this.loadScript(this.options.assetBasePath + 'encoderWorker.umd.js').then(() => {\n resolve()\n }).catch((err) => {\n Logger.error(`${LOG_PREFIX} Error loading encoderWorker.umd.js`, Logger.serialiseError(err))\n })\n }).catch((err) => {\n Logger.error(`${LOG_PREFIX} Error loading OpusMediaRecorder.umd.js`, Logger.serialiseError(err))\n })\n })\n }\n\n private handleAppMessages(): void {\n this.options.messages.subscribe((msg) => {\n switch (msg.uneeqMessageType) {\n case UneeqMessageType.AvatarStartedSpeaking:\n this.digitalHumanSpeaking = true\n this.pauseVadIfInterruptNotAllowed()\n break\n\n case UneeqMessageType.PromptResult: {\n const promptResultMessage = msg as PromptResultMessage\n if (!promptResultMessage.promptResult.success) {\n // The prompt failed, the digital human is not speaking, release the mic.\n this.handleSpeakingEnd()\n }\n break\n }\n // TODO Need to handle PromptResponse here instead (p2)\n // If the avatar answer is blank (after removing XML), then we should release the mic.\n case UneeqMessageType.AvatarAnswer: {\n const answer = msg as AvatarAnswerMessage\n if (answer.answerSpeech.replace(/<[^>]*>/g, '') === '') {\n // The response contained nothing to speak, release the mic.\n this.handleSpeakingEnd()\n }\n break\n }\n\n case UneeqMessageType.AvatarStoppedSpeaking: {\n this.handleSpeakingEnd()\n break\n }\n\n case UneeqMessageType.SessionEnded: {\n this.reconnectWs = false\n this.endRecognition()\n break\n }\n\n // A session reconnection can occur at any point - when a user is speaking, when a digital human is speaking, or neither, so we need to reset the speech recognition state.\n case UneeqMessageType.SessionReconnecting: {\n this.handleSpeakingEnd()\n this.reconnectWs = false\n this.endRecognition()\n break\n }\n\n // A soft switch tears down the old signaling connection - stop reconnecting so we don't loop.\n // reconnectWs is reset to true in startRecognition() when the new connection is ready.\n case UneeqMessageType.SoftSwitchStarting: {\n this.handleSpeakingEnd()\n this.reconnectWs = false\n this.endRecognition()\n break\n }\n\n // TODO Should be updated to get meta data from PromptResponse message (p2)\n case UneeqMessageType.CustomMetadataUpdated: {\n this.options.promptMetadata = (msg as CustomMetadataUpdated).chatMetadata\n break\n }\n\n case UneeqMessageType.SessionBackendError: {\n this.handleSpeakingEnd()\n break\n }\n }\n })\n }\n\n private endRecognition(): void {\n Logger.info(`${LOG_PREFIX} ending recognition`)\n this.stopRecognitionInternal()\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.onclose = null // prevent handleWebsocketClose from triggering a reconnect\n this.ws.close()\n }\n }\n\n private handleSpeakingEnd(): void {\n this.digitalHumanSpeaking = false\n this.interruptionBlocked = false\n\n // Only resume VAD if microphone is not globally muted AND not temporarily paused\n if (!this.isMicrophoneMuted && !this.isTemporarilyPaused) {\n Logger.info('mic not muted and not paused, starting VAD after digital human speaking ended')\n this.startMicVad()\n }\n\n if (!this.options.enableInterrupt) {\n this.clientMsgSend(new VadInterruptionAllowedMessage(true))\n }\n }\n\n private initWebsocket(options: SpeechRecognitionOptions): void {\n Logger.info(`${LOG_PREFIX} initWebsocket - stopping any existing recognition`)\n this.endRecognition()\n\n let connectionUrl = options.apiUrl.replace('https://', 'wss://').replace('http://', 'ws://')\n connectionUrl += `/speech-recognition-service/ws/recognize?jwt=${options.jwtToken}&session_id=${options.sessionId}`\n\n Logger.info(`${LOG_PREFIX} Initializing speech recognition websocket to ` + connectionUrl)\n\n this.ws = new WebSocket(connectionUrl)\n\n this.ws.onopen = () => {\n Logger.info(`${LOG_PREFIX} Speech recognition web socket opened`)\n this.sendWebsocketMessageQueue()\n this.initMicrophone()\n }\n this.ws.onclose = (e: Event) => { this.handleWebsocketClose(e) }\n this.ws.onerror = (err: Event) => {\n Logger.error(`${LOG_PREFIX} WebSocket error`, Logger.serialiseError(err))\n // Surface connection errors as session errors so the UI can display them.\n // These are NOT microphone/device errors — they indicate the speech recognition\n // service is unreachable (e.g. service down, network issue, auth failure).\n this.clientMsgSend(new SessionErrorMessage('Speech recognition service connection failed'))\n }\n this.ws.onmessage = (msg) => { this.handleWebsocketMessage(msg) }\n }\n\n private sendWebsocketMessageQueue(): void {\n Logger.info(`${LOG_PREFIX} Sending all queued websocket messages, queue length: ` + this.wsReconnectMessageQueue.length)\n this.wsReconnectMessageQueue.forEach((msg) => {\n this.wsSend(msg, false, false)\n })\n this.wsReconnectMessageQueue = []\n }\n\n private async loadSavedAudioHeaders(): Promise<void> {\n // this will be replaced with opus media recorder lib that will give us the first packets with header\n const fileName = this.options.assetBasePath + 'opus-media-recorder-header.webm'\n\n // load the saved header and send that:\n const response = await fetch(fileName)\n const arrayBuffer = await response.arrayBuffer()\n\n // Set the correct MIME type\n const mimeType = 'audio/webm;codecs=opus'\n this.headerBlob = new Blob([arrayBuffer], { type: mimeType })\n }\n\n private initVoiceActivityDetection(stream: MediaStream): void {\n Logger.info('[VAD] Going to initialize VAD module, with enableVad = ' + this.options.enableVad)\n this.vadInitialising = true\n this.pausedDuringVadInit = false\n MicVAD.new({\n onSpeechStart: () => { this.onVadSpeechStart() },\n onSpeechEnd: () => { this.onVadSpeechEnd() },\n minSpeechFrames: 1,\n positiveSpeechThreshold: this.options.enableVad ? 0.5 : 0,\n negativeSpeechThreshold: this.options.enableVad ? 0.35 : 0,\n redemptionFrames: 8,\n assetBasePath: this.options.assetBasePath,\n stream\n }).then((vad) => {\n this.micVad = vad\n this.vadInitialising = false\n // Don't start VAD if interruption has been blocked OR microphone is muted OR temporarily paused\n if (!this.interruptionBlocked && !this.isMicrophoneMuted && !this.isTemporarilyPaused) {\n Logger.info(`${LOG_PREFIX} interruption not blocked and mic not muted, starting VAD module`)\n this.startMicVad()\n } else {\n if (this.interruptionBlocked) {\n this.clientMsgSend(new VadInterruptionAllowedMessage(false))\n }\n if (this.pausedDuringVadInit) {\n Logger.warn(`[VAD] VAD module created but not started because pauseSpeechRecognition() ` +\n `was called during async VAD initialisation. Call resumeSpeechRecognition() to start VAD.`)\n } else {\n Logger.info(`[VAD] VAD module created but not started: ` +\n `interruptionBlocked=${this.interruptionBlocked}, ` +\n `isMicrophoneMuted=${this.isMicrophoneMuted}, ` +\n `isTemporarilyPaused=${this.isTemporarilyPaused}`)\n }\n }\n this.pausedDuringVadInit = false\n Logger.info('[VAD] module has started')\n }).catch((err) => {\n this.vadInitialising = false\n this.pausedDuringVadInit = false\n Logger.error('[VAD] could not start module', Logger.serialiseError(err))\n })\n }\n\n private startMicVad(): boolean {\n if (this.micVad) {\n if (this.options.enableInterrupt || !this.digitalHumanSpeaking) {\n this.micVad.start()\n Logger.info('[VAD] VAD has been started / unpaused')\n return true\n }\n }\n Logger.info(`[VAD] VAD has not been started, microphone voice detection live: ${this.micVad !== undefined}, enableInterrupt: ${this.options.enableInterrupt}, isDigitalHumanSpeaking: ${this.digitalHumanSpeaking}.`)\n return false\n }\n\n private pauseMicVad(): boolean {\n if (this.micVad) {\n this.micVad.pause()\n Logger.info('[VAD] has been paused')\n return true\n } else {\n Logger.info('[VAD] has not been initialized so has not been paused ')\n return false\n }\n }\n\n private onVadSpeechStart(): void {\n Logger.info('[VAD] User started speaking')\n this.dataChannelMsgSend(new UserSpeaking(UserSpeakingState.Start))\n this.clientMsgSend(new UserStartedSpeakingMessage())\n\n // Send the start recording message via websocket to speech recognition service\n this.sendStartMessage()\n\n // Send the headers audio data\n this.wsSend(this.headerBlob)\n\n // Send what ever speech audio that was buffered before we got the VAD start trigger\n Logger.info('[VAD] Sending buffered speech audio')\n const bufferedAudio = new Blob(this.speechBuffer.toArray(), {\n type: 'audio/webm;codecs=opus'\n })\n this.wsSend(bufferedAudio)\n\n // Turn on the recordingLive flag, which causes live audio to be sent as it comes in\n this.recordingLive = true\n this.speechBuffer.clear()\n }\n\n private onVadSpeechEnd(): void {\n Logger.info('[VAD] User has stopped speaking or intentionally ending current VAD')\n this.dataChannelMsgSend(new UserSpeaking(UserSpeakingState.Stop))\n this.clientMsgSend(new UserStoppedSpeakingMessage())\n this.recordingLive = false\n this.sendStopMessage()\n }\n\n private sendStoredTranscriptionIfReady(): void {\n if (!this.options.enableVad && this.storedTranscription !== undefined && !this.awaitingFinalTranscript) {\n // send the transcript\n this.sendChatPrompt(this.storedTranscription.transcript)\n this.clientMsgSend(new SpeechTranscriptionMessage(this.storedTranscription))\n this.storedTranscription = undefined\n }\n }\n\n private handleWebsocketMessage(wsMsg: MessageEvent): void {\n Logger.debug(`${LOG_PREFIX} Got a websocket message: `, wsMsg)\n try {\n if (typeof wsMsg.data === 'string') {\n const msg = JSON.parse(wsMsg.data)\n const result = msg.results ? msg.results[0] : null\n if (msg.state === 'response' && result) {\n Logger.info(`${LOG_PREFIX} Speech transcription result ${JSON.stringify(result)}`)\n if (this.options.enableVad) {\n this.handleVadTranscriptionResult(result as SpeechTranscriptionResult)\n } else {\n this.handlePttTranscriptionResult(result as SpeechTranscriptionResult)\n }\n }\n }\n } catch (err) {\n Logger.error(`${LOG_PREFIX} Error processing message speech recognition socket message`, Logger.serialiseError(err))\n }\n }\n\n private handleWebsocketClose(e: Event): void {\n Logger.warn(`${LOG_PREFIX} WebSocket closed`, e)\n if (this.reconnectWs) {\n // TODO implement a retry count\n this.initWebsocket(this.options)\n }\n }\n\n private sendStartMessage(): void {\n // Tell the speech recognition service (remote application) that a speech transcription request is starting\n const startMsg: SpeechRecognitionStartMessage = {\n action: SpeechRecognitionMessageAction.startTranscription,\n channels: audioChannelCount,\n sampleRate: audioSampleRate,\n interimResults: true,\n lang: this.options.locales || 'en-US',\n phrases: this.options.hintPhrases ?? '',\n phraseBoost: this.options.hintPhrasesBoost ?? 0\n }\n this.wsSend(JSON.stringify(startMsg))\n }\n\n private sendStopMessage(): void {\n const stopMsg: SpeechRecognitionStopMessage = {\n action: SpeechRecognitionMessageAction.stopTranscription\n }\n this.wsSend(JSON.stringify(stopMsg), true)\n }\n\n private wsSend(msg: string | Blob, isStopRecognitionMsg: boolean = false, addToQueue: boolean = true): void {\n if (this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(msg)\n }\n\n if (this.reconnectWs && addToQueue) {\n this.wsReconnectMessageQueue.push(msg) // store messages into queue incase websocket is dropped.\n if (isStopRecognitionMsg && this.ws.readyState === WebSocket.OPEN) {\n this.wsReconnectMessageQueue = []\n }\n if (this.wsReconnectMessageQueue.length > websocketReconnectionQueueLimit) {\n this.handleWsReconnectQueueOverflow()\n }\n }\n }\n\n private handleWsReconnectQueueOverflow(): void {\n // The websocket reconnection queue has gotten very long, something might be wrong. Resetting the queue.\n // If the server websocket connection is lost (server restart, etc), the users utterance will be lost.\n Logger.warn(`${LOG_PREFIX} The websocket reconnection queue has exceeded maximum length of ` + websocketReconnectionQueueLimit +\n '. The audio reconnection queue will be reset, speech transcription performance may be impacted in the event of a reconnection.'\n )\n this.wsReconnectMessageQueue = []\n }\n\n private initMicrophone(): void {\n if (!this.stream) {\n if (!navigator.mediaDevices?.getUserMedia) {\n Logger.error('[Microphone] navigator.mediaDevices.getUserMedia is not available in this context')\n this.clientMsgSend(new DeviceErrorMessage(new Error('Microphone access is not available in this context')))\n return\n }\n navigator.mediaDevices.getUserMedia({\n audio: {\n deviceId: this.options.microphoneDeviceId ? { exact: this.options.microphoneDeviceId } : undefined,\n echoCancellation: true,\n sampleRate: audioSampleRate,\n channelCount: audioChannelCount\n }\n })\n .then((stream) => {\n this.stream = stream\n this.initVoiceActivityDetection(stream)\n this.initMediaRecorder(stream)\n this.clientMsgSend(new EnableMicrophoneUpdatedMessage(true))\n })\n .catch((err) => {\n Logger.error('[Microphone] Error starting recording', Logger.serialiseError(err))\n this.clientMsgSend(new DeviceErrorMessage(new Error(JSON.stringify(err))))\n })\n } else {\n Logger.warn('[Microphone] Microphone already initialized, stream already exists, skipping initialization.')\n }\n }\n\n private getMediaRecorder(stream: MediaStream, mediaOptions: OpusMediaRecorderOptions, workerOptions: WorkerOptions): MediaRecorder {\n return new (window as unknown as { OpusMediaRecorder: new (s: MediaStream, o: MediaRecorderOptions, w: object) => MediaRecorder }).OpusMediaRecorder(stream, mediaOptions, workerOptions)\n }\n\n private initMediaRecorder(stream: MediaStream): void {\n const workerOptions: WorkerOptions = {\n OggOpusEncoderWasmPath: this.options.assetBasePath + 'OggOpusEncoder.wasm',\n WebMOpusEncoderWasmPath: this.options.assetBasePath + 'WebMOpusEncoder.wasm'\n }\n const mediaOptions: OpusMediaRecorderOptions = {\n mimeType: 'audio/webm',\n audioBitsPerSecond: audioSampleRate\n }\n\n this.scriptsLoadedPromise.then(() => { // all scripts are loaded\n this.mediaRecorder = this.getMediaRecorder(stream, mediaOptions, workerOptions)\n this.mediaRecorder.ondataavailable = (e) => { this.mediaRecorderOnData(e) }\n this.mediaRecorder.onstop = () => { this.sendStopMessage() }\n this.mediaRecorder.start(audioChunkSizeMs)\n }).catch((err) => {\n Logger.error('[Microphone] Error initializing media recorder', Logger.serialiseError(err))\n })\n }\n\n private mediaRecorderOnData(event: BlobEvent): void {\n if (event.data && event.data.size > 0) {\n if (this.recordingLive) {\n this.wsSend(event.data)\n } else {\n this.speechBuffer.add(event.data)\n }\n }\n }\n\n /*\n * If the recording is live (VAD), append final STT results until recording is no longer live,\n then send it as a question.\n */\n private handlePttTranscriptionResult(result: SpeechTranscriptionResult): void {\n this.awaitingFinalTranscript = !result.final\n\n try {\n if (this.recordingLive) {\n if (result.final) {\n if (this.storedTranscription === undefined) {\n this.storedTranscription = result\n } else {\n this.storedTranscription.transcript += result.transcript\n }\n } else if (this.digitalHumanSpeaking) {\n this.dataChannelMsgSend(new StopSpeaking())\n this.clientMsgSend(new AvatarInterruptedMessage())\n this.digitalHumanSpeaking = false\n }\n } else {\n if (result.final) {\n if (this.storedTranscription !== undefined) {\n result.transcript = this.storedTranscription.transcript + result.transcript\n }\n\n this.options.promptMetadata.userSpokenLocale = result.language_code\n this.sendChatPrompt(result.transcript)\n this.sendInterimTranscription(result)\n this.clientMsgSend(new SpeechTranscriptionMessage(result))\n this.storedTranscription = undefined\n }\n }\n\n // Use stability if provided, default to 1.0 (stable) if not\n const stability = result.stability ?? 1.0\n if (stability > transcriptionStabilityThreshold && !result.final) {\n if (this.storedTranscription !== undefined) {\n result.transcript = this.storedTranscription.transcript + result.transcript\n }\n this.clientMsgSend(new SpeechTranscriptionMessage(result))\n this.sendInterimTranscription(result)\n }\n } catch {\n this.clientMsgSend(new SessionErrorMessage('Error processing speech transcription result'))\n this.storedTranscription = undefined\n }\n }\n\n private handleVadTranscriptionResult(result: SpeechTranscriptionResult): void {\n if (result.transcript !== '') {\n if (result.final) {\n this.options.promptMetadata.userSpokenLocale = result.language_code\n this.sendChatPrompt(result.transcript)\n } else if (this.digitalHumanSpeaking) {\n this.dataChannelMsgSend(new StopSpeaking())\n this.clientMsgSend(new AvatarInterruptedMessage())\n this.digitalHumanSpeaking = false\n }\n this.sendInterimTranscription(result)\n }\n // Use stability if provided, default to 1.0 (stable) if not\n const vadStability = result.stability ?? 1.0\n if (vadStability > transcriptionStabilityThreshold || result.final) {\n this.clientMsgSend(new SpeechTranscriptionMessage(result))\n }\n }\n\n private sendChatPrompt(transcript: string): void {\n if (transcript && transcript.trim() !== '') {\n this.pauseVadIfInterruptNotAllowed()\n this.dataChannelMsgSend(new ChatPrompt(transcript, this.options.promptMetadata))\n }\n }\n\n private sendInterimTranscription(result: SpeechTranscriptionResult): void {\n if (result.transcript && result.transcript.trim() !== '') {\n // Use stability value if provided, default to 1.0 (stable) if not\n const stability = result.stability ?? 1.0\n this.dataChannelMsgSend(new InterimTranscript(result.transcript, stability, result.confidence, result.language_code, result.final))\n }\n }\n\n // Send a message on the data channel to renderer\n private dataChannelMsgSend(msg: DataChannelMessage): void {\n this.options.sendMessage(msg)\n }\n\n // Send a message to the client implementation, i.e hosted experience\n private clientMsgSend(msg: UneeqMessage): void {\n this.options.messages.next(msg)\n }\n}\n"],"names":["Message","RECOMMENDED_FRAME_SAMPLES","validateOptions","options","includes","frameSamples","warn","positiveSpeechThreshold","negativeSpeechThreshold","error","preSpeechPadFrames","redemptionFrames","concatArrays","arrays","sizes","reduce","out","next","push","length","outArray","Float32Array","forEach","arr","index","place","set","FrameProcessor","modelProcessFunc","modelResetFunc","speaking","audioBuffer","redemptionCounter","active","constructor","this","reset","pause","resume","endSegment","speechFrameCount","acc","item","isSpeech","minSpeechFrames","audio","map","frame","msg","SpeechEnd","VADMisfire","process","async","probs","SpeechStart","shift","Silero","ort","modelFetcher","static","model","init","_session","_h","_c","_sr","debug","modelArrayBuffer","InferenceSession","create","Tensor","BigInt","reset_state","zeroes","Array","fill","audioFrame","inputs","input","h","c","sr","run","hn","cn","output","data","notSpeech","defaultRealTimeVADOptions","onFrameProcessed","_probabilities","onVADMisfire","onSpeechStart","onSpeechEnd","stream","MediaStream","assetBasePath","MicVAD","vad","audioContext","audioNodeVAD","listening","info","undefined","navigator","mediaDevices","getUserMedia","Error","channelCount","echoCancellation","autoGainControl","noiseSuppression","AudioContext","source","MediaStreamAudioSourceNode","mediaStream","AudioNodeVAD","new","receive","start","ctx","frameProcessor","entryNode","node","connect","processFrame","audioWorklet","addModule","vadNode","AudioWorkletNode","processorOptions","wasm","wasmPaths","port","onmessage","ev","message","AudioFrame","buffer","modelURL","fetch","then","r","arrayBuffer","SpeechRecognitionMessageAction","InterimTranscript","transcript","stability","confidence","languageCode","isFinal","requestId","uuid","toJSON","action","DataChannelActionType","audioSampleRate","LOG_PREFIX","GoogleSTT","ws","mediaRecorder","speechBuffer","RingBuffer","recordingLive","headerBlob","scriptsLoadedPromise","reconnectWs","wsReconnectMessageQueue","storedTranscription","awaitingFinalTranscript","isMicrophoneMuted","isTemporarilyPaused","vadInitialising","pausedDuringVadInit","micVad","digitalHumanSpeaking","interruptionBlocked","enableVad","enableInterrupt","loadSavedAudioHeaders","loadScripts","handleAppMessages","startRecognition","initWebsocket","stopRecognition","stopRecognitionInternal","state","stop","getTracks","track","clientMsgSend","pauseMicVad","sendStoredTranscriptionIfReady","enabled","onVadSpeechEnd","dataChannelMsgSend","StopSpeaking","result","startMicVad","setChatMetadata","chatMetadata","promptMetadata","pauseVadIfInterruptNotAllowed","loadScript","url","Promise","resolve","script","document","createElement","src","onload","body","appendChild","catch","err","serialiseError","messages","subscribe","uneeqMessageType","AvatarStartedSpeaking","PromptResult","promptResult","success","handleSpeakingEnd","AvatarAnswer","answerSpeech","replace","AvatarStoppedSpeaking","SessionEnded","endRecognition","SessionReconnecting","SoftSwitchStarting","CustomMetadataUpdated","SessionBackendError","readyState","WebSocket","OPEN","onclose","close","connectionUrl","apiUrl","jwtToken","sessionId","onopen","sendWebsocketMessageQueue","initMicrophone","e","handleWebsocketClose","onerror","handleWebsocketMessage","wsSend","fileName","response","Blob","type","initVoiceActivityDetection","onVadSpeechStart","UserSpeaking","Start","sendStartMessage","bufferedAudio","toArray","clear","Stop","sendStopMessage","sendChatPrompt","wsMsg","JSON","parse","results","stringify","handleVadTranscriptionResult","handlePttTranscriptionResult","startMsg","startTranscription","channels","sampleRate","interimResults","lang","locales","phrases","hintPhrases","phraseBoost","hintPhrasesBoost","stopMsg","stopTranscription","isStopRecognitionMsg","addToQueue","send","handleWsReconnectQueueOverflow","deviceId","microphoneDeviceId","exact","initMediaRecorder","getMediaRecorder","mediaOptions","workerOptions","window","OpusMediaRecorder","OggOpusEncoderWasmPath","WebMOpusEncoderWasmPath","mimeType","audioBitsPerSecond","ondataavailable","mediaRecorderOnData","onstop","event","size","add","final","userSpokenLocale","language_code","sendInterimTranscription","trim","ChatPrompt","sendMessage"],"sourceRoot":""}
|
|
1
|
+
{"version":3,"file":"948.index.js","mappings":"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","sources":["webpack://Uneeq/./src/lib/vad/messages.ts","webpack://Uneeq/./src/lib/vad/frame-processor.ts","webpack://Uneeq/./src/lib/vad/models.ts","webpack://Uneeq/./src/lib/vad/real-time-vad.ts","webpack://Uneeq/./src/types/SpeechRecognitionMessageAction.ts","webpack://Uneeq/./src/webrtc-data-channel/messages/InterimTranscript.ts","webpack://Uneeq/./src/google-stt.ts"],"sourcesContent":["export enum Message {\n AudioFrame = 'AUDIO_FRAME',\n SpeechStart = 'SPEECH_START',\n VADMisfire = 'VAD_MISFIRE',\n SpeechEnd = 'SPEECH_END',\n}\n","/*\nSome of this code, together with the default options found in index.ts,\nwere taken (or took inspiration) from https://github.com/snakers4/silero-vad\n*/\n\nimport Logger from '../logger';\nimport { Message } from './messages';\nimport { SpeechProbabilities } from './models';\n\nconst RECOMMENDED_FRAME_SAMPLES = [512, 1024, 1536];\n\nexport interface FrameProcessorOptions {\n /** Threshold over which values returned by the Silero VAD model will be considered as positively indicating speech.\n * The Silero VAD model is run on each frame. This number should be between 0 and 1.\n */\n positiveSpeechThreshold: number;\n\n /** Threshold under which values returned by the Silero VAD model will be considered as indicating an absence of speech.\n * Note that the creators of the Silero VAD have historically set this number at 0.15 less than `positiveSpeechThreshold`.\n */\n negativeSpeechThreshold: number;\n\n /** After a VAD value under the `negativeSpeechThreshold` is observed, the algorithm will wait `redemptionFrames` frames\n * before running `onSpeechEnd`. If the model returns a value over `positiveSpeechThreshold` during this grace period, then\n * the algorithm will consider the previously-detected \"speech end\" as having been a false negative.\n */\n redemptionFrames: number;\n\n /** Number of audio samples (under a sample rate of 16000) to comprise one \"frame\" to feed to the Silero VAD model.\n * The `frame` serves as a unit of measurement of lengths of audio segments and many other parameters are defined in terms of\n * frames. The authors of the Silero VAD model offer the following warning:\n * > WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000 sample rate and\n * 256, 512, 768 samples for 8000 sample rate.\n * > Values other than these may affect model performance!!\n * In this context, audio fed to the VAD model always has sample rate 16000. It is probably a good idea to leave this at 1536.\n */\n frameSamples: number;\n\n /** Number of frames to prepend to the audio segment that will be passed to `onSpeechEnd`. */\n preSpeechPadFrames: number;\n\n /** If an audio segment is detected as a speech segment according to initial algorithm but it has fewer than `minSpeechFrames`,\n * it will be discarded and `onVADMisfire` will be run instead of `onSpeechEnd`.\n */\n minSpeechFrames: number;\n}\n\nexport const defaultFrameProcessorOptions: FrameProcessorOptions = {\n positiveSpeechThreshold: 0.5,\n negativeSpeechThreshold: 0.5 - 0.15,\n preSpeechPadFrames: 1,\n redemptionFrames: 8,\n frameSamples: 1536,\n minSpeechFrames: 3,\n};\n\nexport function validateOptions(options: FrameProcessorOptions): void {\n if (!RECOMMENDED_FRAME_SAMPLES.includes(options.frameSamples)) {\n Logger.warn('You are using an unusual frame size');\n }\n if (\n options.positiveSpeechThreshold < 0 ||\n options.negativeSpeechThreshold > 1\n ) {\n Logger.error('postiveSpeechThreshold should be a number between 0 and 1');\n }\n if (\n options.negativeSpeechThreshold < 0 ||\n options.negativeSpeechThreshold > options.positiveSpeechThreshold\n ) {\n Logger.error(\n 'negativeSpeechThreshold should be between 0 and positiveSpeechThreshold'\n );\n }\n if (options.preSpeechPadFrames < 0) {\n Logger.error('preSpeechPadFrames should be positive');\n }\n if (options.redemptionFrames < 0) {\n Logger.error('preSpeechPadFrames should be positive');\n }\n}\n\nexport interface FrameProcessorInterface {\n resume: () => void;\n process: (arr: Float32Array) => Promise<{\n probs?: SpeechProbabilities\n msg?: Message\n audio?: Float32Array\n }>;\n endSegment: () => { msg?: Message; audio?: Float32Array };\n}\n\nconst concatArrays = (arrays: Float32Array[]): Float32Array => {\n const sizes = arrays.reduce(\n (out, next) => {\n out.push(out[out.length - 1] + next.length);\n return out;\n },\n [0]\n );\n const outArray = new Float32Array(sizes[sizes.length - 1]);\n arrays.forEach((arr, index) => {\n const place = sizes[index];\n outArray.set(arr, place);\n });\n return outArray;\n};\n\nexport class FrameProcessor implements FrameProcessorInterface {\n public speaking: boolean = false;\n public audioBuffer: Array<{ frame: Float32Array; isSpeech: boolean }>;\n public redemptionCounter: number = 0;\n public active: boolean = false;\n\n constructor(\n public modelProcessFunc: (\n frame: Float32Array\n ) => Promise<SpeechProbabilities>,\n public modelResetFunc: () => void,\n public options: FrameProcessorOptions\n ) {\n this.audioBuffer = [];\n this.reset();\n }\n\n public reset = () => {\n this.speaking = false;\n this.audioBuffer = [];\n this.modelResetFunc();\n this.redemptionCounter = 0;\n }\n\n public pause = () => {\n this.active = false;\n this.reset();\n }\n\n public resume = () => {\n this.active = true;\n }\n\n public endSegment = () => {\n const audioBuffer = this.audioBuffer;\n this.audioBuffer = [];\n const speaking = this.speaking;\n this.reset();\n\n const speechFrameCount = audioBuffer.reduce((acc, item) => {\n return acc + +item.isSpeech;\n }, 0);\n\n if (speaking) {\n if (speechFrameCount >= this.options.minSpeechFrames) {\n const audio = concatArrays(audioBuffer.map((item) => item.frame));\n return { msg: Message.SpeechEnd, audio };\n } else {\n return { msg: Message.VADMisfire };\n }\n }\n return {};\n }\n\n public process = async (frame: Float32Array) => {\n if (!this.active) {\n return {};\n }\n const probs = await this.modelProcessFunc(frame);\n this.audioBuffer.push({\n frame,\n isSpeech: probs.isSpeech >= this.options.positiveSpeechThreshold,\n });\n\n if (\n probs.isSpeech >= this.options.positiveSpeechThreshold &&\n this.redemptionCounter\n ) {\n this.redemptionCounter = 0;\n }\n\n if (\n probs.isSpeech >= this.options.positiveSpeechThreshold &&\n !this.speaking\n ) {\n this.speaking = true;\n return { probs, msg: Message.SpeechStart };\n }\n\n if (\n probs.isSpeech < this.options.negativeSpeechThreshold &&\n this.speaking &&\n ++this.redemptionCounter >= this.options.redemptionFrames\n ) {\n this.redemptionCounter = 0;\n this.speaking = false;\n\n const audioBuffer = this.audioBuffer;\n this.audioBuffer = [];\n\n const speechFrameCount = audioBuffer.reduce((acc, item) => {\n return acc + +item.isSpeech;\n }, 0);\n\n if (speechFrameCount >= this.options.minSpeechFrames) {\n const audio = concatArrays(audioBuffer.map((item) => item.frame));\n return { probs, msg: Message.SpeechEnd, audio };\n } else {\n return { probs, msg: Message.VADMisfire };\n }\n }\n\n if (!this.speaking) {\n while (this.audioBuffer.length > this.options.preSpeechPadFrames) {\n this.audioBuffer.shift();\n }\n }\n return { probs };\n }\n}\n","import Logger from \"../logger\";\n\n/** Minimal interface describing the ONNX Tensor fields this VAD module accesses. */\ninterface OrtTensor {\n readonly data: ArrayLike<number>\n}\n\n/** Minimal interface describing the ONNX InferenceSession output map. */\ninterface OrtSessionOutput {\n hn: OrtTensor\n cn: OrtTensor\n output: OrtTensor\n}\n\n/** Minimal ONNX Runtime API interface describing only what the VAD module uses. */\nexport interface ONNXRuntimeAPI {\n InferenceSession: {\n create(model: ArrayBuffer): Promise<{\n run(feeds: Record<string, OrtTensor>): Promise<OrtSessionOutput>\n }>\n }\n Tensor: new (type: string, data: unknown, dims?: number[]) => OrtTensor\n}\n\nexport type ModelFetcher = () => Promise<ArrayBuffer>;\n\nexport interface SpeechProbabilities {\n notSpeech: number;\n isSpeech: number;\n}\n\nexport interface Model {\n reset_state: () => void;\n process: (arr: Float32Array) => Promise<SpeechProbabilities>;\n}\n\nexport class Silero {\n\n public static new = async (ort: ONNXRuntimeAPI, modelFetcher: ModelFetcher) => {\n const model = new Silero(ort, modelFetcher);\n await model.init();\n return model;\n }\n public _session!: { run(feeds: Record<string, OrtTensor>): Promise<OrtSessionOutput> };\n public _h!: OrtTensor;\n public _c!: OrtTensor;\n public _sr!: OrtTensor;\n\n constructor(\n private ort: ONNXRuntimeAPI,\n private modelFetcher: ModelFetcher\n ) {}\n\n public init = async () => {\n Logger.debug('[VAD] initializing');\n const modelArrayBuffer = await this.modelFetcher();\n this._session = await this.ort.InferenceSession.create(modelArrayBuffer);\n this._sr = new this.ort.Tensor('int64', [BigInt(16000)]);\n this.reset_state();\n Logger.debug('[VAD] is initialized');\n }\n\n public reset_state = () => {\n const zeroes = Array(2 * 64).fill(0);\n this._h = new this.ort.Tensor('float32', zeroes, [2, 1, 64]);\n this._c = new this.ort.Tensor('float32', zeroes, [2, 1, 64]);\n }\n\n public process = async (audioFrame: Float32Array): Promise<SpeechProbabilities> => {\n const t = new this.ort.Tensor('float32', audioFrame, [1, audioFrame.length]);\n const inputs = {\n input: t,\n h: this._h,\n c: this._c,\n sr: this._sr,\n };\n const out = await this._session.run(inputs);\n this._h = out.hn;\n this._c = out.cn;\n const isSpeech = out.output.data[0];\n const notSpeech = 1 - isSpeech;\n return { notSpeech, isSpeech };\n }\n}\n","/**\n * This VAD module is a fork from the following project:\n * https://github.com/ricky0123/vad/tree/master/packages/web\n *\n * It has been modified to only include what is required for our\n * own VAD implementation.\n *\n * This module is a wrapper to bring the following ML Model into\n * JS: https://github.com/snakers4/silero-vad\n */\n\nimport * as ort from 'onnxruntime-web';\nimport { defaultFrameProcessorOptions, FrameProcessor, FrameProcessorOptions, validateOptions } from './frame-processor';\nimport { Message } from './messages';\nimport { Silero, SpeechProbabilities, type ONNXRuntimeAPI } from './models';\nimport Logger from '../logger';\n\ninterface RealTimeVADCallbacks {\n /** Callback to run after each frame. The size (number of samples) of a frame is given by `frameSamples`. */\n onFrameProcessed: (probabilities: SpeechProbabilities) => void;\n\n /** Callback to run if speech start was detected but `onSpeechEnd` will not be run because the\n * audio segment is smaller than `minSpeechFrames`.\n */\n onVADMisfire: () => void;\n\n /** Callback to run when speech start is detected */\n onSpeechStart: () => void;\n\n /**\n * Callback to run when speech end is detected.\n * Takes as arg a Float32Array of audio samples between -1 and 1, sample rate 16000.\n * This will not run if the audio segment is smaller than `minSpeechFrames`.\n */\n onSpeechEnd: (audio: Float32Array) => void;\n}\n\ninterface RealTimeVADOptionsWithStream\n extends FrameProcessorOptions,\n RealTimeVADCallbacks {\n stream: MediaStream;\n assetBasePath: string;\n}\n\nexport type RealTimeVADOptions = RealTimeVADOptionsWithStream;\n\nexport const defaultRealTimeVADOptions: RealTimeVADOptions = {\n ...defaultFrameProcessorOptions,\n onFrameProcessed: (_probabilities) => {},\n onVADMisfire: () => {\n Logger.debug('VAD misfire');\n },\n onSpeechStart: () => {\n Logger.debug('Detected speech start');\n },\n onSpeechEnd: () => {\n Logger.debug('Detected speech end');\n },\n stream: new MediaStream(),\n assetBasePath: ''\n};\n\nexport class MicVAD {\n\n public static async new(options: Partial<RealTimeVADOptions> = {}): Promise<MicVAD> {\n const vad = new MicVAD({ ...defaultRealTimeVADOptions, ...options });\n await vad.init();\n return vad;\n }\n // @ts-expect-error assigned in init()\n public audioContext: AudioContext;\n // @ts-expect-error assigned in init()\n public stream: MediaStream;\n // @ts-expect-error assigned in init()\n public audioNodeVAD: AudioNodeVAD;\n public listening: boolean = false;\n\n constructor(public options: RealTimeVADOptions) {\n validateOptions(options);\n }\n\n public init = async () => {\n Logger.info('Initializing real time vad.');\n if (this.options.stream === undefined) {\n if (!navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone access is not available in this context')\n }\n this.stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n channelCount: 1,\n echoCancellation: true,\n autoGainControl: true,\n noiseSuppression: true,\n },\n });\n } else { this.stream = this.options.stream; }\n\n this.audioContext = new AudioContext();\n const source = new MediaStreamAudioSourceNode(this.audioContext, {\n mediaStream: this.stream,\n });\n\n this.audioNodeVAD = await AudioNodeVAD.new(this.audioContext, this.options);\n this.audioNodeVAD.receive(source);\n }\n\n public pause = () => {\n this.audioNodeVAD.pause();\n this.listening = false;\n }\n\n public start = () => {\n this.audioNodeVAD.start();\n this.listening = true;\n }\n}\n\nexport class AudioNodeVAD {\n\n public static async new(\n ctx: AudioContext,\n options: Partial<RealTimeVADOptions> = {}\n ): Promise<AudioNodeVAD> {\n const vad = new AudioNodeVAD(ctx, {\n ...defaultRealTimeVADOptions,\n ...options\n });\n await vad.init();\n return vad;\n }\n // @ts-expect-error assigned in init()\n public frameProcessor: FrameProcessor;\n // @ts-expect-error assigned in init()\n public entryNode: AudioNode;\n\n constructor(public ctx: AudioContext, public options: RealTimeVADOptions) {\n validateOptions(options);\n }\n\n public pause = () => {\n this.frameProcessor.pause();\n }\n\n public start = () => {\n this.frameProcessor.resume();\n }\n\n public receive = (node: AudioNode) => {\n node.connect(this.entryNode);\n }\n\n public processFrame = async (frame: Float32Array) => {\n const { probs, msg, audio } = await this.frameProcessor.process(frame);\n if (probs !== undefined) {\n this.options.onFrameProcessed(probs);\n }\n switch (msg) {\n case Message.SpeechStart:\n this.options.onSpeechStart();\n break;\n\n case Message.VADMisfire:\n this.options.onVADMisfire();\n break;\n\n case Message.SpeechEnd:\n // @ts-expect-error audio param type mismatch\n this.options.onSpeechEnd(audio);\n break;\n\n default:\n break;\n }\n }\n\n public init = async () => {\n await this.ctx.audioWorklet.addModule(this.options.assetBasePath + 'vad.worklet.bundle.min.js');\n const vadNode = new AudioWorkletNode(this.ctx, 'vad-helper-worklet', {\n processorOptions: {\n frameSamples: this.options.frameSamples,\n },\n });\n this.entryNode = vadNode;\n\n // set the base path for loading models\n ort.env.wasm.wasmPaths = this.options.assetBasePath;\n\n const model = await Silero.new(ort as unknown as ONNXRuntimeAPI, this.modelFetcher);\n\n this.frameProcessor = new FrameProcessor(model.process, model.reset_state, {\n frameSamples: this.options.frameSamples,\n positiveSpeechThreshold: this.options.positiveSpeechThreshold,\n negativeSpeechThreshold: this.options.negativeSpeechThreshold,\n redemptionFrames: this.options.redemptionFrames,\n preSpeechPadFrames: this.options.preSpeechPadFrames,\n minSpeechFrames: this.options.minSpeechFrames,\n });\n\n vadNode.port.onmessage = async (ev: MessageEvent) => {\n switch (ev.data?.message) {\n case Message.AudioFrame: {\n const buffer: ArrayBuffer = ev.data.data;\n const frame = new Float32Array(buffer);\n await this.processFrame(frame);\n break;\n }\n\n default:\n break;\n }\n };\n }\n\n public modelFetcher = async () => {\n const modelURL = this.options.assetBasePath + 'silero_vad.onnx';\n return await fetch(modelURL).then((r) => r.arrayBuffer());\n }\n\n}\n","export enum SpeechRecognitionMessageAction {\n startTranscription = 'startTranscription',\n stopTranscription = 'stopTranscription'\n}\n","import { type DataChannelAction } from \"../DataChannelAction\"\nimport { type DataChannelMessage } from \"../DataChannelMessage\"\nimport { DataChannelActionType } from \"../DataChannelActionType\"\nimport { uuidv4 } from '../../lib/uuid'\n\nexport interface InterimTranscriptData {\n requestId: string\n\n confidence: number\n isFinal: boolean\n languageCode: string\n stability: number\n transcript: string \n}\n\nexport class InterimTranscript implements DataChannelMessage {\n constructor(private readonly transcript: string, private readonly stability: number, private readonly confidence: number, private readonly languageCode: string, private readonly isFinal: boolean, private readonly requestId = uuidv4()) {}\n public toJSON(): DataChannelAction {\n const action: DataChannelAction = {\n action: DataChannelActionType.InterimTranscript,\n data: {\n requestId: this.requestId,\n transcript: this.transcript,\n stability: this.stability,\n confidence: this.confidence,\n languageCode: this.languageCode,\n isFinal: this.isFinal\n }\n }\n return action\n }\n}\n","import { RingBuffer } from 'ring-buffer-ts'\nimport { MicVAD } from './lib/vad/real-time-vad'\nimport { type AvatarAnswerMessage, AvatarInterruptedMessage, CustomMetadataUpdated, DeviceErrorMessage, type PromptResultMessage, SessionErrorMessage, SpeechTranscriptionMessage, UneeqMessageType, UserStartedSpeakingMessage, UserStoppedSpeakingMessage, VadInterruptionAllowedMessage, EnableMicrophoneUpdatedMessage, type UneeqMessage } from './types/UneeqMessages'\nimport { type SpeechRecognitionOptions } from './types/SpeechHandlerOptions'\nimport { SpeechRecognitionMessageAction } from './types/SpeechRecognitionMessageAction'\nimport { type SpeechRecognitionStartMessage } from './types/SpeechRecognitionStartMessage'\nimport { type SpeechRecognitionStopMessage } from './types/SpeechRecognitionStopMessage'\nimport { type SpeechTranscriptionResult } from './types/SpeechTranscriptionResult'\nimport Logger from \"./lib/logger\"\nimport { type PromptMetadata } from './types/PromptMetadata'\nimport { StopSpeaking } from './webrtc-data-channel/messages/StopSpeaking'\nimport { ChatPrompt } from './webrtc-data-channel/messages/ChatPrompt'\nimport { UserSpeaking, UserSpeakingState } from './webrtc-data-channel/messages/UserSpeaking'\nimport { type DataChannelMessage } from './webrtc-data-channel/DataChannelMessage'\nimport { InterimTranscript } from './webrtc-data-channel/messages/InterimTranscript'\nimport { type SpeechRecognitionInterface } from './types/SpeechRecognitionInterface'\n\n// The amount of audio chunks to store at any moment before VAD activates.\n// Once VAD activates, send these stored audio chunks before the live speech.\nconst speechBufferLength: number = 5\n\n// The size in milliseconds of each audio chunk to send for transcription.\nconst audioChunkSizeMs: number = 100\n\n// The audio sample rate and channel count to use for the audio stream.\nconst audioSampleRate: number = 48000\nconst audioChannelCount: number = 1\n\n// The maximum number of audio chunks to store in the websocket reconnection queue.\n// The reconnection queue is stored in memory and is used to resend audio chunks if\n// the websocket connection is lost.\nconst websocketReconnectionQueueLimit: number = 3000\n\n// Default path to load assets from (e.g. WASM files, etc).\nconst defaultAssetPath: string = 'https://cdn.uneeq.io/assets/platform/speech-recognition/'\n\n// The minimum required transcription stability for an interim transcription message to be\n// sent to the implementer.\nconst transcriptionStabilityThreshold: number = 0.5\n\n// Logging prefix for speech recognition related logs\nconst LOG_PREFIX: string = '[Google STT]'\n\ninterface WorkerOptions {\n OggOpusEncoderWasmPath: string\n WebMOpusEncoderWasmPath: string\n}\n\ninterface OpusMediaRecorderOptions {\n mimeType: string\n audioBitsPerSecond: number\n}\n\n/**\n * Google Speech-to-Text implementation\n * Uses WebRTC audio, VAD (Voice Activity Detection), and MediaRecorder\n * to send audio to UneeQ's speech recognition service via WebSocket, which is then passed to Google\n */\nexport class GoogleSTT implements SpeechRecognitionInterface {\n private ws!: WebSocket\n private mediaRecorder!: MediaRecorder\n private readonly speechBuffer = new RingBuffer<Blob>(speechBufferLength)\n private recordingLive: boolean = false\n private headerBlob!: Blob\n private scriptsLoadedPromise!: Promise<void>\n private reconnectWs: boolean = true\n private wsReconnectMessageQueue: Array<string | Blob> = []\n private storedTranscription?: SpeechTranscriptionResult\n private awaitingFinalTranscript: boolean = false\n private isMicrophoneMuted: boolean = true\n private isTemporarilyPaused: boolean = false\n private vadInitialising: boolean = false\n private pausedDuringVadInit: boolean = false\n private isSignalingReconnecting: boolean = false\n\n public stream?: MediaStream\n public micVad!: MicVAD\n public digitalHumanSpeaking: boolean = false\n public interruptionBlocked: boolean = false\n\n constructor(public options: SpeechRecognitionOptions) {\n this.options.assetBasePath = this.options.assetBasePath ?? defaultAssetPath\n this.options.enableVad = this.options.enableVad ?? true\n this.options.enableInterrupt = this.options.enableInterrupt ?? false\n void this.loadSavedAudioHeaders()\n this.loadScripts()\n this.handleAppMessages()\n }\n\n public async startRecognition(): Promise<void> {\n Logger.info(`${LOG_PREFIX} start recognition`)\n this.reconnectWs = true\n this.isMicrophoneMuted = false\n this.initWebsocket(this.options)\n }\n\n public async stopRecognition(): Promise<void> {\n Logger.info(`${LOG_PREFIX} stop recognition`)\n this.isMicrophoneMuted = true\n this.stopRecognitionInternal()\n }\n\n private stopRecognitionInternal(): void {\n if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {\n this.mediaRecorder.stop()\n }\n if (this.stream) {\n this.stream.getTracks().forEach((track) => {\n track.stop()\n })\n this.stream = undefined\n this.clientMsgSend(new EnableMicrophoneUpdatedMessage(false))\n }\n this.pauseMicVad()\n }\n\n public pause(): boolean {\n Logger.info(`${LOG_PREFIX} pausing - setting temporarily paused state`)\n this.isTemporarilyPaused = true\n\n if (this.vadInitialising) {\n this.pausedDuringVadInit = true\n Logger.warn(`${LOG_PREFIX} pauseSpeechRecognition() called while VAD is still initialising. ` +\n `VAD will not auto-start when initialisation completes. ` +\n `Call resumeSpeechRecognition() after receiving EnableMicrophoneUpdated to start VAD.`)\n }\n\n if (!this.stream || !this.micVad) {\n Logger.warn(`${LOG_PREFIX} Problem pausing speech recognition, stream live: ${this.stream !== undefined} or microphone voice detection live: ${this.micVad !== undefined}.`)\n }\n\n this.sendStoredTranscriptionIfReady()\n this.stream?.getTracks().forEach((track) => { track.enabled = false })\n this.pauseMicVad()\n this.onVadSpeechEnd()\n\n Logger.info(`${LOG_PREFIX} paused`)\n\n return true\n }\n\n public resume(): boolean {\n Logger.info(`${LOG_PREFIX} resuming - clearing temporarily paused state`)\n this.isTemporarilyPaused = false\n\n // If interruption was blocked clear while resuming speech recognition.\n if (this.interruptionBlocked) {\n this.digitalHumanSpeaking = false\n this.interruptionBlocked = false\n this.clientMsgSend(new VadInterruptionAllowedMessage(true))\n }\n\n // If VAD is disabled and the digital human is speaking, stop the digital human from speaking when push to talk is engaged.\n if (!this.options.enableVad && this.digitalHumanSpeaking) {\n this.dataChannelMsgSend(new StopSpeaking())\n this.digitalHumanSpeaking = false\n }\n\n this.storedTranscription = undefined\n\n // Check if microphone is globally muted\n if (this.isMicrophoneMuted) {\n Logger.debug(`${LOG_PREFIX} Cannot resume: microphone is globally muted`)\n return false\n }\n\n if (this.stream && this.micVad) {\n this.stream.getTracks().forEach((track) => { track.enabled = true })\n\n Logger.info('stream and micVad exist, starting VAD')\n\n const result = this.startMicVad()\n Logger.info(`${LOG_PREFIX} resumed: ` + result)\n return result\n }\n\n \n Logger.info(`${LOG_PREFIX} No stream available after reconnect — performing full STT restart`)\n this.reconnectWs = true\n this.isMicrophoneMuted = false\n this.initWebsocket(this.options)\n return true\n }\n\n public setChatMetadata(chatMetadata: PromptMetadata): void {\n this.options.promptMetadata = chatMetadata\n }\n\n public pauseVadIfInterruptNotAllowed(): void {\n if (this.micVad && !this.options.enableInterrupt) {\n this.micVad.pause()\n this.clientMsgSend(new VadInterruptionAllowedMessage(false))\n }\n if (!this.options.enableInterrupt) {\n this.interruptionBlocked = true\n }\n }\n\n private async loadScript(url: string): Promise<void> {\n return await new Promise<void>((resolve) => {\n const script = document.createElement('script')\n script.src = url\n script.onload = () => {\n resolve()\n }\n document.body.appendChild(script)\n })\n }\n\n private loadScripts(): void {\n this.scriptsLoadedPromise = new Promise((resolve) => {\n // load the opus media recorder script first or there will be a race condition.\n this.loadScript(this.options.assetBasePath + 'OpusMediaRecorder.umd.js').then(() => {\n // once opus media recorder has finished loading, load the encoder worker.\n this.loadScript(this.options.assetBasePath + 'encoderWorker.umd.js').then(() => {\n resolve()\n }).catch((err) => {\n Logger.error(`${LOG_PREFIX} Error loading encoderWorker.umd.js`, Logger.serialiseError(err))\n })\n }).catch((err) => {\n Logger.error(`${LOG_PREFIX} Error loading OpusMediaRecorder.umd.js`, Logger.serialiseError(err))\n })\n })\n }\n\n private handleAppMessages(): void {\n this.options.messages.subscribe((msg) => {\n switch (msg.uneeqMessageType) {\n case UneeqMessageType.AvatarStartedSpeaking:\n this.digitalHumanSpeaking = true\n this.pauseVadIfInterruptNotAllowed()\n break\n\n case UneeqMessageType.PromptResult: {\n const promptResultMessage = msg as PromptResultMessage\n if (!promptResultMessage.promptResult.success) {\n // The prompt failed, the digital human is not speaking, release the mic.\n this.handleSpeakingEnd()\n }\n break\n }\n // TODO Need to handle PromptResponse here instead (p2)\n // If the avatar answer is blank (after removing XML), then we should release the mic.\n case UneeqMessageType.AvatarAnswer: {\n const answer = msg as AvatarAnswerMessage\n if (answer.answerSpeech.replace(/<[^>]*>/g, '') === '') {\n // The response contained nothing to speak, release the mic.\n this.handleSpeakingEnd()\n }\n break\n }\n\n case UneeqMessageType.AvatarStoppedSpeaking: {\n this.handleSpeakingEnd()\n break\n }\n\n case UneeqMessageType.SessionEnded: {\n this.isSignalingReconnecting = false\n this.reconnectWs = false\n this.endRecognition()\n break\n }\n\n // A session reconnection can occur at any point - when a user is speaking, when a digital human is speaking, or neither, so we need to reset the speech recognition state.\n case UneeqMessageType.SessionReconnecting: {\n // Mark reconnecting BEFORE endRecognition() so that any concurrent STT WebSocket\n // error events (which fire on the next event loop tick) see the flag and do not\n // escalate to a fatal SessionError. \n this.isSignalingReconnecting = true\n this.handleSpeakingEnd()\n this.reconnectWs = false\n this.endRecognition()\n break\n }\n\n case UneeqMessageType.SessionReconnectingFinished: {\n this.isSignalingReconnecting = false\n break\n }\n\n // A soft switch tears down the old signaling connection - stop reconnecting so we don't loop.\n // reconnectWs is reset to true in startRecognition() when the new connection is ready.\n case UneeqMessageType.SoftSwitchStarting: {\n this.handleSpeakingEnd()\n this.reconnectWs = false\n this.endRecognition()\n break\n }\n\n // TODO Should be updated to get meta data from PromptResponse message (p2)\n case UneeqMessageType.CustomMetadataUpdated: {\n this.options.promptMetadata = (msg as CustomMetadataUpdated).chatMetadata\n break\n }\n\n case UneeqMessageType.SessionBackendError: {\n this.handleSpeakingEnd()\n break\n }\n }\n })\n }\n\n private endRecognition(): void {\n Logger.info(`${LOG_PREFIX} ending recognition`)\n this.stopRecognitionInternal()\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.onclose = null // prevent handleWebsocketClose from triggering a reconnect\n this.ws.close()\n }\n }\n\n private handleSpeakingEnd(): void {\n this.digitalHumanSpeaking = false\n this.interruptionBlocked = false\n\n // Only resume VAD if microphone is not globally muted AND not temporarily paused\n if (!this.isMicrophoneMuted && !this.isTemporarilyPaused) {\n Logger.info('mic not muted and not paused, starting VAD after digital human speaking ended')\n this.startMicVad()\n }\n\n if (!this.options.enableInterrupt) {\n this.clientMsgSend(new VadInterruptionAllowedMessage(true))\n }\n }\n\n private initWebsocket(options: SpeechRecognitionOptions): void {\n Logger.info(`${LOG_PREFIX} initWebsocket - stopping any existing recognition`)\n this.endRecognition()\n\n let connectionUrl = options.apiUrl.replace('https://', 'wss://').replace('http://', 'ws://')\n connectionUrl += `/speech-recognition-service/ws/recognize?jwt=${options.jwtToken}&session_id=${options.sessionId}`\n\n Logger.info(`${LOG_PREFIX} Initializing speech recognition websocket to ` + connectionUrl)\n\n this.ws = new WebSocket(connectionUrl)\n\n this.ws.onopen = () => {\n Logger.info(`${LOG_PREFIX} Speech recognition web socket opened`)\n this.sendWebsocketMessageQueue()\n this.initMicrophone()\n }\n this.ws.onclose = (e: Event) => { this.handleWebsocketClose(e) }\n this.ws.onerror = (err: Event) => {\n Logger.error(`${LOG_PREFIX} WebSocket error`, Logger.serialiseError(err))\n if (this.isSignalingReconnecting) {\n // The signaling connection is already attempting to reconnect (same network\n // blip that dropped this WebSocket). \n // STT will be restarted by resume() once SessionReconnectingFinished fires.\n Logger.info(`${LOG_PREFIX} WebSocket error suppressed — signaling reconnection in progress`)\n return\n }\n // Surface connection errors as session errors so the UI can display them.\n // These are NOT microphone/device errors — they indicate the speech recognition\n // service is unreachable (e.g. service down, network issue, auth failure).\n this.clientMsgSend(new SessionErrorMessage('Speech recognition service connection failed'))\n }\n this.ws.onmessage = (msg) => { this.handleWebsocketMessage(msg) }\n }\n\n private sendWebsocketMessageQueue(): void {\n Logger.info(`${LOG_PREFIX} Sending all queued websocket messages, queue length: ` + this.wsReconnectMessageQueue.length)\n this.wsReconnectMessageQueue.forEach((msg) => {\n this.wsSend(msg, false, false)\n })\n this.wsReconnectMessageQueue = []\n }\n\n private async loadSavedAudioHeaders(): Promise<void> {\n // this will be replaced with opus media recorder lib that will give us the first packets with header\n const fileName = this.options.assetBasePath + 'opus-media-recorder-header.webm'\n\n // load the saved header and send that:\n const response = await fetch(fileName)\n const arrayBuffer = await response.arrayBuffer()\n\n // Set the correct MIME type\n const mimeType = 'audio/webm;codecs=opus'\n this.headerBlob = new Blob([arrayBuffer], { type: mimeType })\n }\n\n private initVoiceActivityDetection(stream: MediaStream): void {\n Logger.info('[VAD] Going to initialize VAD module, with enableVad = ' + this.options.enableVad)\n this.vadInitialising = true\n this.pausedDuringVadInit = false\n MicVAD.new({\n onSpeechStart: () => { this.onVadSpeechStart() },\n onSpeechEnd: () => { this.onVadSpeechEnd() },\n minSpeechFrames: 1,\n positiveSpeechThreshold: this.options.enableVad ? 0.5 : 0,\n negativeSpeechThreshold: this.options.enableVad ? 0.35 : 0,\n redemptionFrames: 8,\n assetBasePath: this.options.assetBasePath,\n stream\n }).then((vad) => {\n this.micVad = vad\n this.vadInitialising = false\n // Don't start VAD if interruption has been blocked OR microphone is muted OR temporarily paused\n if (!this.interruptionBlocked && !this.isMicrophoneMuted && !this.isTemporarilyPaused) {\n Logger.info(`${LOG_PREFIX} interruption not blocked and mic not muted, starting VAD module`)\n this.startMicVad()\n } else {\n if (this.interruptionBlocked) {\n this.clientMsgSend(new VadInterruptionAllowedMessage(false))\n }\n if (this.pausedDuringVadInit) {\n Logger.warn(`[VAD] VAD module created but not started because pauseSpeechRecognition() ` +\n `was called during async VAD initialisation. Call resumeSpeechRecognition() to start VAD.`)\n } else {\n Logger.info(`[VAD] VAD module created but not started: ` +\n `interruptionBlocked=${this.interruptionBlocked}, ` +\n `isMicrophoneMuted=${this.isMicrophoneMuted}, ` +\n `isTemporarilyPaused=${this.isTemporarilyPaused}`)\n }\n }\n this.pausedDuringVadInit = false\n Logger.info('[VAD] module has started')\n }).catch((err) => {\n this.vadInitialising = false\n this.pausedDuringVadInit = false\n Logger.error('[VAD] could not start module', Logger.serialiseError(err))\n })\n }\n\n private startMicVad(): boolean {\n if (this.micVad) {\n if (this.options.enableInterrupt || !this.digitalHumanSpeaking) {\n this.micVad.start()\n Logger.info('[VAD] VAD has been started / unpaused')\n return true\n }\n }\n Logger.info(`[VAD] VAD has not been started, microphone voice detection live: ${this.micVad !== undefined}, enableInterrupt: ${this.options.enableInterrupt}, isDigitalHumanSpeaking: ${this.digitalHumanSpeaking}.`)\n return false\n }\n\n private pauseMicVad(): boolean {\n if (this.micVad) {\n this.micVad.pause()\n Logger.info('[VAD] has been paused')\n return true\n } else {\n Logger.info('[VAD] has not been initialized so has not been paused ')\n return false\n }\n }\n\n private onVadSpeechStart(): void {\n Logger.info('[VAD] User started speaking')\n this.dataChannelMsgSend(new UserSpeaking(UserSpeakingState.Start))\n this.clientMsgSend(new UserStartedSpeakingMessage())\n\n // Send the start recording message via websocket to speech recognition service\n this.sendStartMessage()\n\n // Send the headers audio data\n this.wsSend(this.headerBlob)\n\n // Send what ever speech audio that was buffered before we got the VAD start trigger\n Logger.info('[VAD] Sending buffered speech audio')\n const bufferedAudio = new Blob(this.speechBuffer.toArray(), {\n type: 'audio/webm;codecs=opus'\n })\n this.wsSend(bufferedAudio)\n\n // Turn on the recordingLive flag, which causes live audio to be sent as it comes in\n this.recordingLive = true\n this.speechBuffer.clear()\n }\n\n private onVadSpeechEnd(): void {\n Logger.info('[VAD] User has stopped speaking or intentionally ending current VAD')\n this.dataChannelMsgSend(new UserSpeaking(UserSpeakingState.Stop))\n this.clientMsgSend(new UserStoppedSpeakingMessage())\n this.recordingLive = false\n this.sendStopMessage()\n }\n\n private sendStoredTranscriptionIfReady(): void {\n if (!this.options.enableVad && this.storedTranscription !== undefined && !this.awaitingFinalTranscript) {\n // send the transcript\n this.sendChatPrompt(this.storedTranscription.transcript)\n this.clientMsgSend(new SpeechTranscriptionMessage(this.storedTranscription))\n this.storedTranscription = undefined\n }\n }\n\n private handleWebsocketMessage(wsMsg: MessageEvent): void {\n Logger.debug(`${LOG_PREFIX} Got a websocket message: `, wsMsg)\n try {\n if (typeof wsMsg.data === 'string') {\n const msg = JSON.parse(wsMsg.data)\n const result = msg.results ? msg.results[0] : null\n if (msg.state === 'response' && result) {\n Logger.info(`${LOG_PREFIX} Speech transcription result ${JSON.stringify(result)}`)\n if (this.options.enableVad) {\n this.handleVadTranscriptionResult(result as SpeechTranscriptionResult)\n } else {\n this.handlePttTranscriptionResult(result as SpeechTranscriptionResult)\n }\n }\n }\n } catch (err) {\n Logger.error(`${LOG_PREFIX} Error processing message speech recognition socket message`, Logger.serialiseError(err))\n }\n }\n\n private handleWebsocketClose(e: Event): void {\n Logger.warn(`${LOG_PREFIX} WebSocket closed`, e)\n if (this.reconnectWs) {\n // TODO implement a retry count\n this.initWebsocket(this.options)\n }\n }\n\n private sendStartMessage(): void {\n // Tell the speech recognition service (remote application) that a speech transcription request is starting\n const startMsg: SpeechRecognitionStartMessage = {\n action: SpeechRecognitionMessageAction.startTranscription,\n channels: audioChannelCount,\n sampleRate: audioSampleRate,\n interimResults: true,\n lang: this.options.locales || 'en-US',\n phrases: this.options.hintPhrases ?? '',\n phraseBoost: this.options.hintPhrasesBoost ?? 0\n }\n this.wsSend(JSON.stringify(startMsg))\n }\n\n private sendStopMessage(): void {\n const stopMsg: SpeechRecognitionStopMessage = {\n action: SpeechRecognitionMessageAction.stopTranscription\n }\n this.wsSend(JSON.stringify(stopMsg), true)\n }\n\n private wsSend(msg: string | Blob, isStopRecognitionMsg: boolean = false, addToQueue: boolean = true): void {\n if (this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(msg)\n }\n\n if (this.reconnectWs && addToQueue) {\n this.wsReconnectMessageQueue.push(msg) // store messages into queue incase websocket is dropped.\n if (isStopRecognitionMsg && this.ws.readyState === WebSocket.OPEN) {\n this.wsReconnectMessageQueue = []\n }\n if (this.wsReconnectMessageQueue.length > websocketReconnectionQueueLimit) {\n this.handleWsReconnectQueueOverflow()\n }\n }\n }\n\n private handleWsReconnectQueueOverflow(): void {\n // The websocket reconnection queue has gotten very long, something might be wrong. Resetting the queue.\n // If the server websocket connection is lost (server restart, etc), the users utterance will be lost.\n Logger.warn(`${LOG_PREFIX} The websocket reconnection queue has exceeded maximum length of ` + websocketReconnectionQueueLimit +\n '. The audio reconnection queue will be reset, speech transcription performance may be impacted in the event of a reconnection.'\n )\n this.wsReconnectMessageQueue = []\n }\n\n private initMicrophone(): void {\n if (!this.stream) {\n if (!navigator.mediaDevices?.getUserMedia) {\n Logger.error('[Microphone] navigator.mediaDevices.getUserMedia is not available in this context')\n this.clientMsgSend(new DeviceErrorMessage(new Error('Microphone access is not available in this context')))\n return\n }\n navigator.mediaDevices.getUserMedia({\n audio: {\n deviceId: this.options.microphoneDeviceId ? { exact: this.options.microphoneDeviceId } : undefined,\n echoCancellation: true,\n sampleRate: audioSampleRate,\n channelCount: audioChannelCount\n }\n })\n .then((stream) => {\n this.stream = stream\n this.initVoiceActivityDetection(stream)\n this.initMediaRecorder(stream)\n this.clientMsgSend(new EnableMicrophoneUpdatedMessage(true))\n })\n .catch((err) => {\n Logger.error('[Microphone] Error starting recording', Logger.serialiseError(err))\n this.clientMsgSend(new DeviceErrorMessage(new Error(JSON.stringify(err))))\n })\n } else {\n Logger.warn('[Microphone] Microphone already initialized, stream already exists, skipping initialization.')\n }\n }\n\n private getMediaRecorder(stream: MediaStream, mediaOptions: OpusMediaRecorderOptions, workerOptions: WorkerOptions): MediaRecorder {\n return new (window as unknown as { OpusMediaRecorder: new (s: MediaStream, o: MediaRecorderOptions, w: object) => MediaRecorder }).OpusMediaRecorder(stream, mediaOptions, workerOptions)\n }\n\n private initMediaRecorder(stream: MediaStream): void {\n const workerOptions: WorkerOptions = {\n OggOpusEncoderWasmPath: this.options.assetBasePath + 'OggOpusEncoder.wasm',\n WebMOpusEncoderWasmPath: this.options.assetBasePath + 'WebMOpusEncoder.wasm'\n }\n const mediaOptions: OpusMediaRecorderOptions = {\n mimeType: 'audio/webm',\n audioBitsPerSecond: audioSampleRate\n }\n\n this.scriptsLoadedPromise.then(() => { // all scripts are loaded\n this.mediaRecorder = this.getMediaRecorder(stream, mediaOptions, workerOptions)\n this.mediaRecorder.ondataavailable = (e) => { this.mediaRecorderOnData(e) }\n this.mediaRecorder.onstop = () => { this.sendStopMessage() }\n this.mediaRecorder.start(audioChunkSizeMs)\n }).catch((err) => {\n Logger.error('[Microphone] Error initializing media recorder', Logger.serialiseError(err))\n })\n }\n\n private mediaRecorderOnData(event: BlobEvent): void {\n if (event.data && event.data.size > 0) {\n if (this.recordingLive) {\n this.wsSend(event.data)\n } else {\n this.speechBuffer.add(event.data)\n }\n }\n }\n\n /*\n * If the recording is live (VAD), append final STT results until recording is no longer live,\n then send it as a question.\n */\n private handlePttTranscriptionResult(result: SpeechTranscriptionResult): void {\n this.awaitingFinalTranscript = !result.final\n\n try {\n if (this.recordingLive) {\n if (result.final) {\n if (this.storedTranscription === undefined) {\n this.storedTranscription = result\n } else {\n this.storedTranscription.transcript += result.transcript\n }\n } else if (this.digitalHumanSpeaking) {\n this.dataChannelMsgSend(new StopSpeaking())\n this.clientMsgSend(new AvatarInterruptedMessage())\n this.digitalHumanSpeaking = false\n }\n } else {\n if (result.final) {\n if (this.storedTranscription !== undefined) {\n result.transcript = this.storedTranscription.transcript + result.transcript\n }\n\n this.options.promptMetadata.userSpokenLocale = result.language_code\n this.sendChatPrompt(result.transcript)\n this.sendInterimTranscription(result)\n this.clientMsgSend(new SpeechTranscriptionMessage(result))\n this.storedTranscription = undefined\n }\n }\n\n // Use stability if provided, default to 1.0 (stable) if not\n const stability = result.stability ?? 1.0\n if (stability > transcriptionStabilityThreshold && !result.final) {\n if (this.storedTranscription !== undefined) {\n result.transcript = this.storedTranscription.transcript + result.transcript\n }\n this.clientMsgSend(new SpeechTranscriptionMessage(result))\n this.sendInterimTranscription(result)\n }\n } catch {\n this.clientMsgSend(new SessionErrorMessage('Error processing speech transcription result'))\n this.storedTranscription = undefined\n }\n }\n\n private handleVadTranscriptionResult(result: SpeechTranscriptionResult): void {\n if (result.transcript !== '') {\n if (result.final) {\n this.options.promptMetadata.userSpokenLocale = result.language_code\n this.sendChatPrompt(result.transcript)\n } else if (this.digitalHumanSpeaking) {\n this.dataChannelMsgSend(new StopSpeaking())\n this.clientMsgSend(new AvatarInterruptedMessage())\n this.digitalHumanSpeaking = false\n }\n this.sendInterimTranscription(result)\n }\n // Use stability if provided, default to 1.0 (stable) if not\n const vadStability = result.stability ?? 1.0\n if (vadStability > transcriptionStabilityThreshold || result.final) {\n this.clientMsgSend(new SpeechTranscriptionMessage(result))\n }\n }\n\n private sendChatPrompt(transcript: string): void {\n if (transcript && transcript.trim() !== '') {\n this.pauseVadIfInterruptNotAllowed()\n this.dataChannelMsgSend(new ChatPrompt(transcript, this.options.promptMetadata))\n }\n }\n\n private sendInterimTranscription(result: SpeechTranscriptionResult): void {\n if (result.transcript && result.transcript.trim() !== '') {\n // Use stability value if provided, default to 1.0 (stable) if not\n const stability = result.stability ?? 1.0\n this.dataChannelMsgSend(new InterimTranscript(result.transcript, stability, result.confidence, result.language_code, result.final))\n }\n }\n\n // Send a message on the data channel to renderer\n private dataChannelMsgSend(msg: DataChannelMessage): void {\n this.options.sendMessage(msg)\n }\n\n // Send a message to the client implementation, i.e hosted experience\n private clientMsgSend(msg: UneeqMessage): void {\n this.options.messages.next(msg)\n }\n}\n"],"names":["Message","RECOMMENDED_FRAME_SAMPLES","validateOptions","options","includes","frameSamples","warn","positiveSpeechThreshold","negativeSpeechThreshold","error","preSpeechPadFrames","redemptionFrames","concatArrays","arrays","sizes","reduce","out","next","push","length","outArray","Float32Array","forEach","arr","index","place","set","FrameProcessor","modelProcessFunc","modelResetFunc","speaking","audioBuffer","redemptionCounter","active","constructor","this","reset","pause","resume","endSegment","speechFrameCount","acc","item","isSpeech","minSpeechFrames","audio","map","frame","msg","SpeechEnd","VADMisfire","process","async","probs","SpeechStart","shift","Silero","ort","modelFetcher","static","model","init","_session","_h","_c","_sr","debug","modelArrayBuffer","InferenceSession","create","Tensor","BigInt","reset_state","zeroes","Array","fill","audioFrame","inputs","input","h","c","sr","run","hn","cn","output","data","notSpeech","defaultRealTimeVADOptions","onFrameProcessed","_probabilities","onVADMisfire","onSpeechStart","onSpeechEnd","stream","MediaStream","assetBasePath","MicVAD","vad","audioContext","audioNodeVAD","listening","info","undefined","navigator","mediaDevices","getUserMedia","Error","channelCount","echoCancellation","autoGainControl","noiseSuppression","AudioContext","source","MediaStreamAudioSourceNode","mediaStream","AudioNodeVAD","new","receive","start","ctx","frameProcessor","entryNode","node","connect","processFrame","audioWorklet","addModule","vadNode","AudioWorkletNode","processorOptions","wasm","wasmPaths","port","onmessage","ev","message","AudioFrame","buffer","modelURL","fetch","then","r","arrayBuffer","SpeechRecognitionMessageAction","InterimTranscript","transcript","stability","confidence","languageCode","isFinal","requestId","uuid","toJSON","action","DataChannelActionType","audioSampleRate","LOG_PREFIX","GoogleSTT","ws","mediaRecorder","speechBuffer","RingBuffer","recordingLive","headerBlob","scriptsLoadedPromise","reconnectWs","wsReconnectMessageQueue","storedTranscription","awaitingFinalTranscript","isMicrophoneMuted","isTemporarilyPaused","vadInitialising","pausedDuringVadInit","isSignalingReconnecting","micVad","digitalHumanSpeaking","interruptionBlocked","enableVad","enableInterrupt","loadSavedAudioHeaders","loadScripts","handleAppMessages","startRecognition","initWebsocket","stopRecognition","stopRecognitionInternal","state","stop","getTracks","track","clientMsgSend","pauseMicVad","sendStoredTranscriptionIfReady","enabled","onVadSpeechEnd","dataChannelMsgSend","StopSpeaking","result","startMicVad","setChatMetadata","chatMetadata","promptMetadata","pauseVadIfInterruptNotAllowed","loadScript","url","Promise","resolve","script","document","createElement","src","onload","body","appendChild","catch","err","serialiseError","messages","subscribe","uneeqMessageType","AvatarStartedSpeaking","PromptResult","promptResult","success","handleSpeakingEnd","AvatarAnswer","answerSpeech","replace","AvatarStoppedSpeaking","SessionEnded","endRecognition","SessionReconnecting","SessionReconnectingFinished","SoftSwitchStarting","CustomMetadataUpdated","SessionBackendError","readyState","WebSocket","OPEN","onclose","close","connectionUrl","apiUrl","jwtToken","sessionId","onopen","sendWebsocketMessageQueue","initMicrophone","e","handleWebsocketClose","onerror","handleWebsocketMessage","wsSend","fileName","response","Blob","type","initVoiceActivityDetection","onVadSpeechStart","UserSpeaking","Start","sendStartMessage","bufferedAudio","toArray","clear","Stop","sendStopMessage","sendChatPrompt","wsMsg","JSON","parse","results","stringify","handleVadTranscriptionResult","handlePttTranscriptionResult","startMsg","startTranscription","channels","sampleRate","interimResults","lang","locales","phrases","hintPhrases","phraseBoost","hintPhrasesBoost","stopMsg","stopTranscription","isStopRecognitionMsg","addToQueue","send","handleWsReconnectQueueOverflow","deviceId","microphoneDeviceId","exact","initMediaRecorder","getMediaRecorder","mediaOptions","workerOptions","window","OpusMediaRecorder","OggOpusEncoderWasmPath","WebMOpusEncoderWasmPath","mimeType","audioBitsPerSecond","ondataavailable","mediaRecorderOnData","onstop","event","size","add","final","userSpokenLocale","language_code","sendInterimTranscription","trim","ChatPrompt","sendMessage"],"sourceRoot":""}
|