Skip to content

Commit 30f0aed

Browse files
committed
fix app with wyoming
1 parent bdadc61 commit 30f0aed

File tree

2 files changed

+93
-10
lines changed

2 files changed

+93
-10
lines changed

app/app/hooks/useAudioStreamer.ts

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
1530
export 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;

app/app/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,10 @@ export default function App() {
276276
await audioStreamer.startStreaming(finalWebSocketUrl);
277277

278278
// Then start OMI audio listener
279-
await originalStartAudioListener((audioBytes) => {
279+
await originalStartAudioListener(async (audioBytes) => {
280280
const wsReadyState = audioStreamer.getWebSocketReadyState();
281281
if (wsReadyState === WebSocket.OPEN && audioBytes.length > 0) {
282-
audioStreamer.sendAudio(audioBytes);
282+
await audioStreamer.sendAudio(audioBytes);
283283
}
284284
});
285285
} catch (error) {

0 commit comments

Comments
 (0)