@@ -12,6 +12,21 @@ interface UseAudioStreamer {
1212 sendAudio : ( audioBytes : Uint8Array ) => void ;
1313}
1414
15+ // Wyoming Protocol Types
16+ interface WyomingEvent {
17+ type : string ;
18+ data ?: any ;
19+ version ?: string ;
20+ payload_length ?: number | null ;
21+ }
22+
23+ // Audio format constants (matching OMI device format)
24+ const AUDIO_FORMAT = {
25+ rate : 16000 , // 16kHz sample rate
26+ width : 2 , // 16-bit samples (2 bytes)
27+ channels : 1 // Mono audio
28+ } ;
29+
1530export const useAudioStreamer = ( ) : UseAudioStreamer => {
1631 const [ isStreaming , setIsStreaming ] = useState < boolean > ( false ) ;
1732 const [ isConnecting , setIsConnecting ] = useState < boolean > ( false ) ;
@@ -24,7 +39,42 @@ export const useAudioStreamer = (): UseAudioStreamer => {
2439 const MAX_RECONNECT_ATTEMPTS = 5 ;
2540 const RECONNECT_DELAY = 3000 ; // 3 seconds between reconnects
2641
27- const stopStreaming = useCallback ( ( ) => {
42+ // Helper function to send Wyoming protocol events
43+ const sendWyomingEvent = useCallback ( async ( event : WyomingEvent , payload ?: Uint8Array ) => {
44+ if ( ! websocketRef . current || websocketRef . current . readyState !== WebSocket . OPEN ) {
45+ console . log ( '[AudioStreamer] WebSocket not ready for Wyoming event' ) ;
46+ return ;
47+ }
48+
49+ try {
50+ // Add version to event
51+ event . version = "1.0.0" ;
52+
53+ // Add payload_length if payload exists
54+ if ( payload ) {
55+ event . payload_length = payload . length ;
56+ } else {
57+ event . payload_length = null ;
58+ }
59+
60+ // Send JSON header with newline
61+ const jsonHeader = JSON . stringify ( event ) + '\n' ;
62+ websocketRef . current . send ( jsonHeader ) ;
63+ console . debug ( `[AudioStreamer] Sent Wyoming event: ${ event . type } (payload_length: ${ event . payload_length } )` ) ;
64+
65+ // Send binary payload if exists
66+ if ( payload && payload . length > 0 ) {
67+ websocketRef . current . send ( payload ) ;
68+ console . debug ( `[AudioStreamer] Sent audio payload: ${ payload . length } bytes` ) ;
69+ }
70+ } catch ( e ) {
71+ const errorMessage = ( e as any ) . message || 'Error sending Wyoming event.' ;
72+ console . error ( '[AudioStreamer] Error sending Wyoming event:' , errorMessage ) ;
73+ setError ( errorMessage ) ;
74+ }
75+ } , [ ] ) ;
76+
77+ const stopStreaming = useCallback ( async ( ) => {
2878 if ( websocketRef . current ) {
2979 console . log ( '[AudioStreamer] Closing WebSocket connection.' ) ;
3080 // Mark that we're manually stopping the connection
@@ -36,12 +86,26 @@ export const useAudioStreamer = (): UseAudioStreamer => {
3686 reconnectTimeoutRef . current = null ;
3787 }
3888
89+ // Send audio-stop event before closing
90+ if ( websocketRef . current . readyState === WebSocket . OPEN ) {
91+ try {
92+ const audioStopEvent : WyomingEvent = {
93+ type : 'audio-stop' ,
94+ data : { timestamp : Date . now ( ) }
95+ } ;
96+ await sendWyomingEvent ( audioStopEvent ) ;
97+ console . log ( '[AudioStreamer] Sent audio-stop event' ) ;
98+ } catch ( e ) {
99+ console . error ( '[AudioStreamer] Error sending audio-stop:' , e ) ;
100+ }
101+ }
102+
39103 websocketRef . current . close ( ) ;
40104 websocketRef . current = null ;
41105 }
42106 setIsStreaming ( false ) ;
43107 setIsConnecting ( false ) ;
44- } , [ ] ) ;
108+ } , [ sendWyomingEvent ] ) ;
45109
46110 const attemptReconnect = useCallback ( ( ) => {
47111 if ( manuallyStoppedRef . current || ! currentUrlRef . current ) {
@@ -113,14 +177,27 @@ export const useAudioStreamer = (): UseAudioStreamer => {
113177 try {
114178 const ws = new WebSocket ( url . trim ( ) ) ;
115179
116- ws . onopen = ( ) => {
180+ ws . onopen = async ( ) => {
117181 console . log ( '[AudioStreamer] WebSocket connection established.' ) ;
118182 setIsConnecting ( false ) ;
119183 setIsStreaming ( true ) ;
120184 setError ( null ) ;
121185 websocketRef . current = ws ; // Assign ref only on successful open
122186 // Reset reconnect attempts on successful connection
123187 reconnectAttemptsRef . current = 0 ;
188+
189+ // Send audio-start event to begin session
190+ try {
191+ const audioStartEvent : WyomingEvent = {
192+ type : 'audio-start' ,
193+ data : AUDIO_FORMAT
194+ } ;
195+ await sendWyomingEvent ( audioStartEvent ) ;
196+ console . log ( `[AudioStreamer] Sent audio-start event (rate=${ AUDIO_FORMAT . rate } , width=${ AUDIO_FORMAT . width } , channels=${ AUDIO_FORMAT . channels } )` ) ;
197+ } catch ( e ) {
198+ console . error ( '[AudioStreamer] Error sending audio-start:' , e ) ;
199+ }
200+
124201 resolve ( ) ;
125202 } ;
126203
@@ -181,13 +258,19 @@ export const useAudioStreamer = (): UseAudioStreamer => {
181258 reject ( new Error ( errorMessage ) ) ;
182259 }
183260 } ) ;
184- } , [ stopStreaming , attemptReconnect ] ) ;
261+ } , [ stopStreaming , attemptReconnect , sendWyomingEvent ] ) ;
185262
186- const sendAudio = useCallback ( ( audioBytes : Uint8Array ) => {
263+ const sendAudio = useCallback ( async ( audioBytes : Uint8Array ) => {
187264 if ( websocketRef . current && websocketRef . current . readyState === WebSocket . OPEN && audioBytes . length > 0 ) {
188265 try {
189- // console.debug(`[AudioStreamer] Attempting to send audio: ${audioBytes.length} bytes. WebSocket readyState: ${websocketRef.current?.readyState}`);
190- websocketRef . current . send ( audioBytes ) ;
266+ // Create Wyoming AudioChunk event
267+ const audioChunkEvent : WyomingEvent = {
268+ type : 'audio-chunk' ,
269+ data : AUDIO_FORMAT
270+ } ;
271+
272+ // Send Wyoming event with audio payload
273+ await sendWyomingEvent ( audioChunkEvent , audioBytes ) ;
191274 } catch ( e ) {
192275 const errorMessage = ( e as any ) . message || 'Error sending audio data.' ;
193276 console . error ( '[AudioStreamer] Error sending audio:' , errorMessage ) ;
@@ -198,7 +281,7 @@ export const useAudioStreamer = (): UseAudioStreamer => {
198281 // Log why it didn't send
199282 console . log ( `[AudioStreamer] NOT sending audio. Conditions check: websocketRef.current exists: ${ ! ! websocketRef . current } , readyState === OPEN: ${ websocketRef . current ?. readyState === WebSocket . OPEN } , audioBytes.length > 0: ${ audioBytes . length > 0 } . Actual readyState: ${ websocketRef . current ?. readyState } ` ) ;
200283 }
201- } , [ ] ) ;
284+ } , [ sendWyomingEvent ] ) ;
202285
203286 const getWebSocketReadyState = useCallback ( ( ) => {
204287 return websocketRef . current ?. readyState ;
0 commit comments