Skip to content

Commit d26a14c

Browse files
examples-botclaude
andcommitted
fix(020): update SDK v5 API, fix async race condition and test timeout
- Use `client.listen.v1.connect()` instead of deprecated `createConnection()` alias - Use `sendCloseStream({ type: 'CloseStream' })` instead of `sendFinalize({ type: 'Finalize' })` - Make WS handler synchronous; queue media until Deepgram is ready (express-ws race condition) - Close Twilio WS after stop event; close test WS after sending stop - Remove unused `twilio` import 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ea58bab commit d26a14c

2 files changed

Lines changed: 53 additions & 36 deletions

File tree

examples/020-twilio-media-streams-node/src/index.js

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ require('dotenv').config();
55
const express = require('express');
66
const expressWs = require('express-ws');
77
const { DeepgramClient } = require('@deepgram/sdk');
8-
const twilio = require('twilio');
98

109
const PORT = process.env.PORT || 3000;
1110

@@ -29,7 +28,9 @@ function createApp() {
2928
process.exit(1);
3029
}
3130

32-
const deepgram = new DeepgramClient({ apiKey: process.env.DEEPGRAM_API_KEY });
31+
const deepgram = new DeepgramClient({
32+
apiKey: process.env.DEEPGRAM_API_KEY,
33+
});
3334

3435
app.post('/voice', (req, res) => {
3536
const host = req.headers.host;
@@ -48,37 +49,14 @@ function createApp() {
4849
console.log(`[voice] New call → streaming to ${streamUrl}`);
4950
});
5051

51-
app.ws('/media', async (twilioWs) => {
52+
app.ws('/media', (twilioWs) => {
5253
let dgConnection = null;
54+
let dgReady = false;
5355
let streamSid = null;
56+
const mediaQueue = [];
5457

5558
console.log('[media] Twilio WebSocket connected');
5659

57-
dgConnection = await deepgram.listen.v1.createConnection(DEEPGRAM_LIVE_OPTIONS);
58-
59-
dgConnection.on('open', () => {
60-
console.log('[deepgram] Connection opened');
61-
});
62-
63-
dgConnection.on('error', (err) => {
64-
console.error('[deepgram] Error:', err.message);
65-
});
66-
67-
dgConnection.on('close', () => {
68-
console.log('[deepgram] Connection closed');
69-
});
70-
71-
dgConnection.on('message', (data) => {
72-
const transcript = data?.channel?.alternatives?.[0]?.transcript;
73-
if (transcript) {
74-
const tag = data.is_final ? 'final' : 'interim';
75-
console.log(`[${tag}] ${transcript}`);
76-
}
77-
});
78-
79-
dgConnection.connect();
80-
await dgConnection.waitForOpen();
81-
8260
twilioWs.on('message', (raw) => {
8361
try {
8462
const message = JSON.parse(raw);
@@ -94,21 +72,23 @@ function createApp() {
9472
break;
9573

9674
case 'media':
97-
try {
98-
if (dgConnection) {
75+
if (dgReady && dgConnection) {
76+
try {
9977
dgConnection.sendMedia(Buffer.from(message.media.payload, 'base64'));
100-
}
101-
} catch {}
102-
78+
} catch {}
79+
} else {
80+
mediaQueue.push(message.media.payload);
81+
}
10382
break;
10483

10584
case 'stop':
10685
console.log('[twilio] Stream stopped');
10786
if (dgConnection) {
108-
try { dgConnection.sendFinalize({ type: 'Finalize' }); } catch {}
87+
try { dgConnection.sendCloseStream({ type: 'CloseStream' }); } catch {}
10988
try { dgConnection.close(); } catch {}
11089
dgConnection = null;
11190
}
91+
twilioWs.close();
11292
break;
11393

11494
default:
@@ -122,7 +102,7 @@ function createApp() {
122102
twilioWs.on('close', () => {
123103
console.log('[media] Twilio WebSocket closed');
124104
if (dgConnection) {
125-
try { dgConnection.sendFinalize({ type: 'Finalize' }); } catch {}
105+
try { dgConnection.sendCloseStream({ type: 'CloseStream' }); } catch {}
126106
try { dgConnection.close(); } catch {}
127107
dgConnection = null;
128108
}
@@ -135,6 +115,43 @@ function createApp() {
135115
dgConnection = null;
136116
}
137117
});
118+
119+
(async () => {
120+
dgConnection = await deepgram.listen.v1.connect(DEEPGRAM_LIVE_OPTIONS);
121+
122+
dgConnection.on('open', () => {
123+
console.log('[deepgram] Connection opened');
124+
});
125+
126+
dgConnection.on('error', (err) => {
127+
console.error('[deepgram] Error:', err.message);
128+
});
129+
130+
dgConnection.on('close', () => {
131+
console.log('[deepgram] Connection closed');
132+
});
133+
134+
dgConnection.on('message', (data) => {
135+
const transcript = data?.channel?.alternatives?.[0]?.transcript;
136+
if (transcript) {
137+
const tag = data.is_final ? 'final' : 'interim';
138+
console.log(`[${tag}] ${transcript}`);
139+
}
140+
});
141+
142+
dgConnection.connect();
143+
await dgConnection.waitForOpen();
144+
145+
dgReady = true;
146+
for (const payload of mediaQueue) {
147+
try {
148+
dgConnection.sendMedia(Buffer.from(payload, 'base64'));
149+
} catch {}
150+
}
151+
mediaQueue.length = 0;
152+
})().catch((err) => {
153+
console.error('[deepgram] Setup failed:', err.message);
154+
});
138155
});
139156

140157
app.get('/', (_req, res) => {

examples/020-twilio-media-streams-node/tests/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,8 @@ function testMediaStreamFlow(port, audioData) {
195195
if (ws.readyState !== WebSocket.OPEN) return;
196196

197197
if (offset >= audioData.length || offset >= MAX_BYTES) {
198-
// 4. "stop" — call ended
199198
ws.send(JSON.stringify({ event: 'stop', streamSid: 'MZ_ci_test' }));
199+
setTimeout(() => ws.close(), 500);
200200
return;
201201
}
202202

0 commit comments

Comments
 (0)