11import { PUBLIC_SERVICE_URL } from "$env/static/public" ;
2- import { AudioRecordingWorklet } from "$lib/helpers/pcmProcessor" ;
2+ import { AudioRecordingWorklet } from "$lib/helpers/realtime/ pcmProcessor" ;
33
44// @ts -ignore
55const AudioContext = window . AudioContext || window . webkitAudioContext ;
@@ -15,60 +15,63 @@ let audioQueue = [];
1515/** @type {boolean } */
1616let isPlaying = false ;
1717
18- export const realtimeChat = {
19-
20- /** @type {WebSocket | null } */
21- socket : null ,
18+ /** @type {WebSocket | null } */
19+ let socket = null ;
2220
23- /** @type {MediaStream | null } */
24- mediaStream : null ,
21+ /** @type {MediaStream | null } */
22+ let mediaStream = null ;
2523
26- /** @type {AudioWorkletNode | null } */
27- workletNode : null ,
24+ /** @type {AudioWorkletNode | null } */
25+ let workletNode = null ;
2826
29- /** @type {MediaStreamAudioSourceNode | null } */
30- micSource : null ,
27+ /** @type {MediaStreamAudioSourceNode | null } */
28+ let micSource = null ;
3129
30+ export const realtimeChat = {
31+
3232 /**
3333 * @param {string } agentId
3434 * @param {string } conversationId
3535 */
3636 start ( agentId , conversationId ) {
3737 reset ( ) ;
3838 const wsUrl = buildWebsocketUrl ( ) ;
39- this . socket = new WebSocket ( `${ wsUrl } /chat/stream/${ agentId } /${ conversationId } ` ) ;
39+ socket = new WebSocket ( `${ wsUrl } /chat/stream/${ agentId } /${ conversationId } ` ) ;
4040
41- this . socket . onopen = async ( ) => {
41+ socket . onopen = async ( ) => {
4242 console . log ( "WebSocket connected" ) ;
4343
44- this . socket ?. send ( JSON . stringify ( {
44+ socket ?. send ( JSON . stringify ( {
4545 event : "start"
4646 } ) ) ;
4747
48- this . mediaStream = await navigator . mediaDevices . getUserMedia ( { audio : true } ) ;
48+ mediaStream = await navigator . mediaDevices . getUserMedia ( { audio : true } ) ;
4949 audioCtx = new AudioContext ( { sampleRate : sampleRate } ) ;
5050
5151 const workletName = "audio-recorder-worklet" ;
5252 const src = createWorkletFromSrc ( workletName , AudioRecordingWorklet ) ;
5353 await audioCtx . audioWorklet . addModule ( src ) ;
5454
55- this . workletNode = new AudioWorkletNode ( audioCtx , workletName ) ;
56- this . micSource = audioCtx . createMediaStreamSource ( this . mediaStream ) ;
57- this . micSource . connect ( this . workletNode ) ;
55+ workletNode = new AudioWorkletNode ( audioCtx , workletName ) ;
56+ micSource = audioCtx . createMediaStreamSource ( mediaStream ) ;
57+ micSource . connect ( workletNode ) ;
5858
59- this . workletNode . port . onmessage = event => {
59+ workletNode . port . onmessage = event => {
6060 const arrayBuffer = event . data . data . int16arrayBuffer ;
61- if ( arrayBuffer && this . socket ?. readyState === WebSocket . OPEN ) {
61+ if ( arrayBuffer && socket ?. readyState === WebSocket . OPEN ) {
62+ if ( event . data . data . speaking ) {
63+ reset ( ) ;
64+ }
6265 const arrayBufferString = arrayBufferToBase64 ( arrayBuffer ) ;
63- this . socket . send ( JSON . stringify ( {
66+ socket . send ( JSON . stringify ( {
6467 event : 'media' ,
6568 payload : arrayBufferString
6669 } ) ) ;
6770 }
6871 } ;
6972 } ;
7073
71- this . socket . onmessage = ( /** @type {MessageEvent } */ e ) => {
74+ socket . onmessage = ( /** @type {MessageEvent } */ e ) => {
7275 try {
7376 const json = JSON . parse ( e . data ) ;
7477 if ( json . event === 'media' && ! ! json . media . payload ) {
@@ -80,34 +83,37 @@ export const realtimeChat = {
8083 }
8184 } ;
8285
83- this . socket . onclose = ( ) => {
86+ socket . onclose = ( ) => {
8487 console . log ( "Websocket closed" ) ;
8588 } ;
8689
87- this . socket . onerror = ( /** @type {Event } */ e ) => {
90+ socket . onerror = ( /** @type {Event } */ e ) => {
8891 console . error ( 'WebSocket error' , e ) ;
8992 } ;
9093 } ,
9194
9295 stop ( ) {
9396 reset ( ) ;
9497
95- if ( this . mediaStream ) {
96- this . mediaStream . getTracks ( ) . forEach ( t => t . stop ( ) ) ;
97- this . mediaStream = null ;
98+ if ( mediaStream ) {
99+ mediaStream . getTracks ( ) . forEach ( t => t . stop ( ) ) ;
100+ mediaStream = null ;
98101 }
99102
100- if ( this . workletNode ) {
101- this . micSource ?. disconnect ( this . workletNode ) ;
102- this . workletNode . port . close ( ) ;
103- this . workletNode . disconnect ( ) ;
103+ if ( workletNode ) {
104+ micSource ?. disconnect ( workletNode ) ;
105+ workletNode . port . close ( ) ;
106+ workletNode . disconnect ( ) ;
107+ micSource = null ;
108+ workletNode = null ;
104109 }
105110
106- if ( this . socket ?. readyState === WebSocket . OPEN ) {
107- this . socket . send ( JSON . stringify ( {
111+ if ( socket ?. readyState === WebSocket . OPEN ) {
112+ socket . send ( JSON . stringify ( {
108113 event : 'disconnect'
109114 } ) ) ;
110- this . socket . close ( ) ;
115+ socket . close ( ) ;
116+ socket = null ;
111117 }
112118 }
113119} ;
@@ -143,7 +149,7 @@ function enqueueAudioChunk(base64Audio) {
143149 audioQueue . push ( audioBuffer ) ;
144150
145151 if ( ! isPlaying ) {
146- playNext ( ) ;
152+ playNext ( ) ;
147153 }
148154}
149155
@@ -159,7 +165,6 @@ function playNext() {
159165 const source = audioCtx . createBufferSource ( ) ;
160166 source . buffer = buffer ;
161167 source . connect ( audioCtx . destination ) ;
162-
163168 source . onended = ( ) => {
164169 playNext ( ) ;
165170 } ;
@@ -221,7 +226,7 @@ function convert16BitPCMToFloat32(buffer) {
221226 for ( let i = 0 ; i < chunk . length / 2 ; i ++ ) {
222227 try {
223228 const int16 = dataView . getInt16 ( i * 2 , true ) ;
224- output [ i ] = int16 / 32767 ;
229+ output [ i ] = int16 / 32768 ;
225230 } catch ( e ) {
226231 console . error ( e ) ;
227232 }
0 commit comments