From 0779b9135d02c7c17ca4af482860780dc852ed34 Mon Sep 17 00:00:00 2001 From: examples-bot Date: Wed, 1 Apr 2026 12:18:26 +0000 Subject: [PATCH] fix(examples): resolve async race dropping Twilio audio in 020-twilio-media-streams-node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit express-ws does not await async WebSocket handlers, so Twilio message listeners were registered after the Deepgram connection was established, causing early audio frames to be silently dropped. Moved Twilio WS handlers before async Deepgram setup and buffer media until ready. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../src/index.js | 76 +++++++++++-------- .../tests/test.js | 2 +- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/examples/020-twilio-media-streams-node/src/index.js b/examples/020-twilio-media-streams-node/src/index.js index 5585157..ca5b84f 100644 --- a/examples/020-twilio-media-streams-node/src/index.js +++ b/examples/020-twilio-media-streams-node/src/index.js @@ -48,37 +48,14 @@ function createApp() { console.log(`[voice] New call → streaming to ${streamUrl}`); }); - app.ws('/media', async (twilioWs) => { + app.ws('/media', (twilioWs) => { let dgConnection = null; + let dgReady = false; let streamSid = null; + const mediaQueue = []; console.log('[media] Twilio WebSocket connected'); - dgConnection = await deepgram.listen.v1.createConnection(DEEPGRAM_LIVE_OPTIONS); - - dgConnection.on('open', () => { - console.log('[deepgram] Connection opened'); - }); - - dgConnection.on('error', (err) => { - console.error('[deepgram] Error:', err.message); - }); - - dgConnection.on('close', () => { - console.log('[deepgram] Connection closed'); - }); - - dgConnection.on('message', (data) => { - const transcript = data?.channel?.alternatives?.[0]?.transcript; - if (transcript) { - const tag = data.is_final ? 'final' : 'interim'; - console.log(`[${tag}] ${transcript}`); - } - }); - - dgConnection.connect(); - await dgConnection.waitForOpen(); - twilioWs.on('message', (raw) => { try { const message = JSON.parse(raw); @@ -94,12 +71,13 @@ function createApp() { break; case 'media': - try { - if (dgConnection) { + if (dgReady && dgConnection) { + try { dgConnection.sendMedia(Buffer.from(message.media.payload, 'base64')); - } - } catch {} - + } catch {} + } else { + mediaQueue.push(message.media.payload); + } break; case 'stop': @@ -135,6 +113,42 @@ function createApp() { dgConnection = null; } }); + + (async () => { + dgConnection = await deepgram.listen.v1.createConnection(DEEPGRAM_LIVE_OPTIONS); + + dgConnection.on('open', () => { + console.log('[deepgram] Connection opened'); + dgReady = true; + while (mediaQueue.length > 0) { + try { + dgConnection.sendMedia(Buffer.from(mediaQueue.shift(), 'base64')); + } catch {} + } + }); + + dgConnection.on('error', (err) => { + console.error('[deepgram] Error:', err.message); + }); + + dgConnection.on('close', () => { + console.log('[deepgram] Connection closed'); + dgReady = false; + }); + + dgConnection.on('message', (data) => { + const transcript = data?.channel?.alternatives?.[0]?.transcript; + if (transcript) { + const tag = data.is_final ? 'final' : 'interim'; + console.log(`[${tag}] ${transcript}`); + } + }); + + dgConnection.connect(); + await dgConnection.waitForOpen(); + })().catch((err) => { + console.error('[deepgram] Setup failed:', err.message); + }); }); app.get('/', (_req, res) => { diff --git a/examples/020-twilio-media-streams-node/tests/test.js b/examples/020-twilio-media-streams-node/tests/test.js index 1a33f42..49e7675 100644 --- a/examples/020-twilio-media-streams-node/tests/test.js +++ b/examples/020-twilio-media-streams-node/tests/test.js @@ -195,8 +195,8 @@ function testMediaStreamFlow(port, audioData) { if (ws.readyState !== WebSocket.OPEN) return; if (offset >= audioData.length || offset >= MAX_BYTES) { - // 4. "stop" — call ended ws.send(JSON.stringify({ event: 'stop', streamSid: 'MZ_ci_test' })); + setTimeout(() => ws.close(), 500); return; }