function connectToTTSWebSocket() {
return new Promise((resolve, reject) => {
log('Attempting to connect to TTS WebSocket');
ttsWebSocket = new WebSocket(TTS_WEBSOCKET_URL);
ttsWebSocket.on('open', () => {
log('Connected to TTS WebSocket');
resolve(ttsWebSocket);
});
ttsWebSocket.on('error', (error) => {
log(`TTS WebSocket error: ${error.message}`);
reject(error);
});
ttsWebSocket.on('close', (code, reason) => {
log(`TTS WebSocket closed. Code: ${code}, Reason: ${reason}`);
reject(new Error('TTS WebSocket closed unexpectedly'));
});
});
}
function sendTTSMessage(message) {
const textMessage = {
'model_id': modelId,
'transcript': message,
'voice': voice,
'output_format': {
'container': 'raw',
'encoding': 'pcm_mulaw',
'sample_rate': 8000
}
};
log(`Sending message to TTS WebSocket: ${message}`);
ttsWebSocket.send(JSON.stringify(textMessage));
}
function testTTSWebSocket() {
return new Promise((resolve, reject) => {
const testMessage = 'This is a test message';
let receivedAudio = false;
sendTTSMessage(testMessage);
const timeout = setTimeout(() => {
if (!receivedAudio) {
reject(new Error('Timeout: No audio received from TTS WebSocket'));
}
}, 10000); // 10 second timeout
ttsWebSocket.on('message', (audioChunk) => {
if (!receivedAudio) {
log(audioChunk);
log('Received audio chunk from TTS for test message');
receivedAudio = true;
clearTimeout(timeout);
resolve();
}
});
});
}
async function startCall(twilioWebsocketUrl) {
try {
log(`Initiating call with WebSocket URL: ${twilioWebsocketUrl}`);
const call = await client.calls.create({
twiml: `<Response><Connect><Stream url="${twilioWebsocketUrl}"/></Connect></Response>`,
to: outbound, // Replace with the phone number you want to call
from: inbound // Replace with your Twilio phone number
});
callSid = call.sid;
log(`Call initiated. SID: ${callSid}`);
} catch (error) {
log(`Error initiating call: ${error.message}`);
throw error;
}
}
async function hangupCall() {
try {
log(`Attempting to hang up call: ${callSid}`);
await client.calls(callSid).update({status: 'completed'});
log('Call hung up successfully');
} catch (error) {
log(`Error hanging up call: ${error.message}`);
}
}
function setupTwilioWebSocket() {
return new Promise((resolve, reject) => {
const server = http.createServer((req, res) => {
log(`Received HTTP request: ${req.method} ${req.url}`);
res.writeHead(200);
res.end('WebSocket server is running');
});
const wss = new WebSocket.Server({ server });
log('WebSocket server created');
wss.on('connection', (twilioWs, request) => {
log(`Twilio WebSocket connection attempt from ${request.socket.remoteAddress}`);
let streamSid = null;
twilioWs.on('message', (message) => {
try {
const msg = JSON.parse(message);
log(`Received message from Twilio: ${JSON.stringify(msg)}`);
if (msg.event === 'start') {
log('Media stream started');
streamSid = msg.start.streamSid;
log(`Stream SID: ${streamSid}`);
sendTTSMessage(partialResponse);
} else if (msg.event === 'media' && !messageComplete) {
log('Received media event');
} else if (msg.event === 'stop') {
log('Media stream stopped');
hangupCall();
}
} catch (error) {
log(`Error processing Twilio message: ${error.message}`);
}
});
twilioWs.on('close', (code, reason) => {
log(`Twilio WebSocket disconnected. Code: ${code}, Reason: ${reason}`);
});
twilioWs.on('error', (error) => {
log(`Twilio WebSocket error: ${error.message}`);
});
// Handle incoming audio chunks from TTS WebSocket
ttsWebSocket.on('message', (audioChunk) => {
log('Received audio chunk from TTS');
try {
if (streamSid) {
twilioWs.send(JSON.stringify({
event: 'media',
streamSid: streamSid,
media: {
payload: JSON.parse(audioChunk)['data']
}
}));
audioChunksReceived++;
log(`Audio chunks received: ${audioChunksReceived}`);
if (audioChunksReceived >= 50) {
messageComplete = true;
log('Message complete, preparing to hang up');
setTimeout(hangupCall, 2000);
}
} else {
log('Warning: Received audio chunk but streamSid is not set');
}
} catch (error) {
log(`Error sending audio chunk to Twilio: ${error.message}`);
}
});
log('Twilio WebSocket connected and handlers set up');
});
wss.on('error', (error) => {
log(`WebSocket server error: ${error.message}`);
});
server.listen(0, () => {
const port = server.address().port;
log(`Twilio WebSocket server is running on port ${port}`);
resolve(port);
});
server.on('error', (error) => {
log(`HTTP server error: ${error.message}`);
reject(error);
});
});
}
async function setupNgrokTunnel(port) {
try {
const httpsUrl = await ngrok.connect(port);
// Convert https:// to wss://
const wssUrl = httpsUrl.replace('https://', 'wss://');
log(`ngrok tunnel established: ${wssUrl}`);
return wssUrl;
} catch (error) {
log(`Error setting up ngrok tunnel: ${error.message}`);
throw error;
}
}
async function main() {
try {
log('Starting application');
await connectToTTSWebSocket();
log('TTS WebSocket connected successfully');
await testTTSWebSocket();
log('TTS WebSocket test passed successfully');
const twilioWebsocketPort = await setupTwilioWebSocket();
log(`Twilio WebSocket server set up on port ${twilioWebsocketPort}`);
const twilioWebsocketUrl = await setupNgrokTunnel(twilioWebsocketPort);
await startCall(twilioWebsocketUrl);
} catch (error) {
log(`Error in main function: ${error.message}`);
}
}
// Run the script
main();