diff --git a/examples/020-twilio-media-streams-node/src/index.js b/examples/020-twilio-media-streams-node/src/index.js index 5585157..9992179 100644 --- a/examples/020-twilio-media-streams-node/src/index.js +++ b/examples/020-twilio-media-streams-node/src/index.js @@ -5,7 +5,6 @@ require('dotenv').config(); const express = require('express'); const expressWs = require('express-ws'); const { DeepgramClient } = require('@deepgram/sdk'); -const twilio = require('twilio'); const PORT = process.env.PORT || 3000; @@ -29,7 +28,9 @@ function createApp() { process.exit(1); } - const deepgram = new DeepgramClient({ apiKey: process.env.DEEPGRAM_API_KEY }); + const deepgram = new DeepgramClient({ + apiKey: process.env.DEEPGRAM_API_KEY, + }); app.post('/voice', (req, res) => { const host = req.headers.host; @@ -48,37 +49,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,21 +72,23 @@ 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': console.log('[twilio] Stream stopped'); if (dgConnection) { - try { dgConnection.sendFinalize({ type: 'Finalize' }); } catch {} + try { dgConnection.sendCloseStream({ type: 'CloseStream' }); } catch {} try { dgConnection.close(); } catch {} dgConnection = null; } + twilioWs.close(); break; default: @@ -122,7 +102,7 @@ function createApp() { twilioWs.on('close', () => { console.log('[media] Twilio WebSocket closed'); if (dgConnection) { - try { dgConnection.sendFinalize({ type: 'Finalize' }); } catch {} + try { dgConnection.sendCloseStream({ type: 'CloseStream' }); } catch {} try { dgConnection.close(); } catch {} dgConnection = null; } @@ -135,6 +115,43 @@ function createApp() { dgConnection = null; } }); + + (async () => { + dgConnection = await deepgram.listen.v1.connect(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(); + + dgReady = true; + for (const payload of mediaQueue) { + try { + dgConnection.sendMedia(Buffer.from(payload, 'base64')); + } catch {} + } + mediaQueue.length = 0; + })().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; }