
WebSocket — это прогрессивный стандарт полнодуплексной (двусторонней) связи между клиентом и сторонним сервисом в режиме реального времени. Веб-сокеты используются для организации непрерывного обмена данными без дополнительных HTTP-запросов.
И мы рады сообщить вам, что все это стало возможным в Voximplant благодаря новому модулю VoxEngine, который называется – сюрприз – WebSocket. Отныне вы сможете передавать текст и аудио, пользуясь преимуществами веб-сокетов в полной мере. Проще говоря, у вас появился еще один инструмент, чтобы прокачать ваше приложение.
Из этой статьи вы узнаете, как создать исходящее WebSocket-соединение, передать через него аудиопоток и преобразовать его в текст с помощью Google Cloud Speech-to-Text API.
Обратите внимание, что у Voximplant есть встроенная функциональность преобразования речи в текст в реальном времени под управлением модуля ASR. Данный модуль использует функции от Google, Yandex и Tinkoff, см. подробности здесь.
Текущая же статья описывает случай, когда вы хотите использовать сторонний сервис преобразования речи в текст и/или тратить средства с его аккаунта, а не с аккаунта Voximplant.
Наш модуль предусматривает два формата работы:
- создание исходящего соединения;
- прием входящего соединения и создание веб-сокета дл�� него.
Исходящее
Первое, что вам нужно сделать при создании исходящего соединения — это запустить сценарий VoxEngine. Затем вызвать метод VoxEngine.createWebSocket, который создаст объект WebSocket. Данный метод принимает 2 параметра: URL в формате 'wss: // + domain + path' и protocols (опционально). Вот как это будет выглядеть в коде:
VoxEngine.addEventListener(AppEvents.CallAlerting, function(e) { const webSocket = VoxEngine.createWebSocket( /*url*/ "wss://your_link/"); // Здесь вы можете обработать исходящее соединение });
Если все сработало, метод call.sendMediaTo направит аудиопоток в сокет, а WebSocket.send, в свою очередь, будет пересылать декодированный аудиопоток в формате JSON через него. В результате чего вам будут приходить сообщения от сервиса, обрабатывающего запросы.
Метод WebSocket.close нужен, чтобы закрыть соединение. Обратите внимание, что соединение может быть закрыто как со стороны клиента, так и со стороны сервера.

Входящее
Для того, чтобы разрешить входящие соединения, необходимо сообщить об этом через VoxEngine.allowWebSocketConnections, а также подписаться на событие AppEvents.WebSocket. После чего можно будет принять входящее соединение и получить объект WebSocket: event.WebSocket. Смотрите код ниже:
VoxEngine.allowWebSocketConnections(); VoxEngine.addEventListener(AppEvents.WebSocket, function(e) { // Здесь вы можете обработать входящее соединение });
Для создания входящего веб-сокета нужен управляющий accessSecureURL. Его можно взять из события AppEvents.Started или ответа на HTTP-запрос, который запустил сессию. Обратите внимание, что «https» должно быть изменено на 'wss' в URL.
Остальные шаги идентичны представленным в схеме исходящего соединения.

Вам понадобятся
Чтобы заимплементить технологию WebSocket и распознавание речи в ваше приложение, вам понадобятся:
- аккаунт Voximplant. Если у вас его нет, смело регистрируйтесь по ссылке;
- приложение Voximplant, а также сценарий, правило и один пользователь. Все это будет создано в данном туториале;
- простой бэкенд (мы запустим сервер на node.js) с подключенным Cloud client library для Speech-to-Text API;
- веб-клиент для совершения звонка (мы будем использовать вебфон на phone.voximplant.com).
1. Настройки VOXIMPLANT
Для начала авторизуйтесь в вашем аккаунте на manage.voximplant.com/auth. В меню слева нажмите «Приложения», создайте новое и назовите его websocket. Зайдите в ваше приложение, переключитесь на вкладку «Сценарии», создайте сценарий и вставьте в него следующий код:
require(Modules.WebSocket); VoxEngine.addEventListener(AppEvents.CallAlerting, function(e) { call = e.call; call.answer(); const webSocket = VoxEngine.createWebSocket( /*url*/ "wss://your_ngrok_link/"); webSocket.addEventListener(WebSocketEvents.ERROR, function(e) { Logger.write("LOG OUTGOING: WebSocketEvents.ERROR"); call.sendMessage("LOG OUTGOING: WebSocketEvents.ERROR"); }); webSocket.addEventListener(WebSocketEvents.CLOSE, function(e) { Logger.write("LOG OUTGOING: WebSocketEvents.CLOSE: " + e.reason); call.sendMessage("LOG OUTGOING: WebSocketEvents.CLOSE: " + e.reason); }); webSocket.addEventListener(WebSocketEvents.OPEN, function(e) { Logger.write("LOG OUTGOING: WebSocketEvents.OPEN"); Logger.write(JSON.stringify(e)) call.sendMessage("LOG OUTGOING: WebSocketEvents.OPEN"); }); webSocket.addEventListener(WebSocketEvents.MESSAGE, function(e) { Logger.write("LOG OUTGOING: WebSocketEvents.MESSAGE: " + e.text); call.sendMessage("LOG OUTGOING: WebSocketEvents.MESSAGE: " + e.text); if (e.text == "Hi there, I am a WebSocket server") { call.sendMediaTo(webSocket, { encoding: WebSocketAudioEncoding.ULAW, "tag": "MyAudioStream", "customParameters": { "param1": "12345" } }); } }); call.addEventListener(CallEvents.Disconnected, function(e) { Logger.write("LOG OUTGOING: terminating in 1 sec"); webSocket.close(); setTimeout(VoxEngine.terminate, 1000); }); });
Этот сценарий VoxEngine отправляет аудиопоток в WebSocket, а также отслеживает его события (ERROR, CLOSE, OPEN, MESSAGE). Углубиться в детали сценария мы сможем немного позже.
А сейчас давайте перейдем на вкладку «Роутинг», нажмем «Новое правило» и назовем его socketRule. Теперь осталось лишь выбрать ваш сценарий, а маску оставить по умолчанию ( .* ).

Последнее, что требуется сделать на данном этапе – создать пользователя. Переключитесь на вкладку «Пользователи», нажмите «Создать пользователя», укажите имя (например, socketUser) и пароль, затем кликните «Создать». Эта пара логин-пароль понадобится нам для аутентификации в веб-клиенте н�� последнем шаге.

Конфигурация завершена, но прежде чем приступить к созданию сервера, давайте рассмотрим, как работает модуль WebSocket в нашем сценарии.
2. Детали сценария
Модуль WebSocket позволяет разработчикам открывать устойчивое соединение и отправлять через него данные. Чтобы использовать этот модуль, мы должны подключить его в самом начале сценария:
require(Modules.WebSocket);
Через метод createWebSocket мы определяем URL-адрес и protocols (опционально). Вы можете узнать, как получить URL для WebSocket из следующего раздела.
const webSocket = VoxEngine.createWebSocket( /*url*/ "wss://your_ngrok_link/");
После создания объекта WebSocket мы продолжаем управление звонком внутри обработчика. А именно, отправляем медиа объекту WebSocket с помощью метода call.sendMediaTo.
Здесь можно установить предпочтительный формат кодировки, тэг и некоторые кастомные параметры. Если вы не установите кодировку, по умолчанию будет использоваться PCM8.
Данный метод вызывается, когда приходит сообщение об успешном соединении. В нашем сценарии код вызова выглядит так:
call.sendMediaTo(webSocket, { encoding: WebSocketAudioEncoding.ULAW, "tag": "MyAudioStream", "customParameters": { "param1": "12345" } });
Все остальные события WebSocket, которые вы видите в коде, предназначены для отладки, они отправляют сведения в лог Voximplant-сессии. Вы можете убрать их, если хотите.
Наконец, мы добавляем правильный обработчик завершения передачи данных. В нашем случае сессия Voximplant закончит свою работу через 1 секунду после завершения установленного вызова (Disconnected):
call.addEventListener(CallEvents.Disconnected, function(e) { Logger.write("LOG OUTGOING: terminating in 1 sec"); webSocket.close(); setTimeout(VoxEngine.terminate, 1000); });
Теперь, когда логика сценария ясна, пришло время перейти к следующей, очень важной части нашего примера.
3. Backend
Во-первых, убедитесь, что на вашем компьютере установлен Node.js. Скачать его можно с основного сайта Node.js. Затем выполните следующие команды в окне терминала одну за другой, чтобы настроить рабочую среду:
npm install express npm install ws npm install @google-cloud/speech
А когда это будет сделано, создайте пустой JS-файл и поместите туда следующий код (нюансы кода будут освещены ниже):
const app = require('express')(); const http = require('http').createServer(app); const WebSocket = require('ws'); const fs = require('fs'); const wss = new WebSocket.Server({ server: http }); // Импортируем клиентскую библиотеку для Google Cloud const speech = require('@google-cloud/speech'); // Создаем инстанс клиента const client = new speech.SpeechClient(); const config = { encoding: 'MULAW', sampleRateHertz: 8000, languageCode: 'ru-RU', }; const request = { config, interimResults: true, }; let audioInput = []; let recognizeStream = null; process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; app.get('/', function(req, res) { res.send('<h1>Hello world</h1>'); }); wss.on('connection', (ws) => { // Создаем поток, позволяющий сохранить переданные данные в файл var wstream = fs.createWriteStream('myBinaryFile'); // Очищаем текущий audioInput audioInput = []; // Инициируем распознавание аудиопотока recognizeStream = client .streamingRecognize(request) .on('error', err => { ws.close(); }) .on('data', data => { ws.send(data.results[0].alternatives[0].transcript) process.stdout.write( data.results[0] && data.results[0].alternatives[0] ? `Transcription: ${data.results[0].alternatives[0].transcript}\n` : `\n\nError occurred, press Ctrl+C\n` ) }); ws.on('close', (message) => { console.log('The time limit for speech recognition has been reached. Please disconnect and call again.'); wstream.end(); }) // Соединение установлено, добавляем логику для события message ws.on('message', (message) => { // Помещаем аудио в формате base64 в recognizeStream try { let data = JSON.parse(message) if (data.event == "media") { let b64data = data.media.payload; let buff = new Buffer.from(b64data, 'base64'); recognizeStream.write(buff); wstream.write(buff); } } catch (err) { console.log(message) } }); // Отправляем уведомление об установке соединения ws.send('Hi there, I am a WebSocket server'); }); http.listen(3000, function() { console.log('listening on *:3000'); });
Теперь, когда сервер настроен, он поможет нам выполнить распознавание речи. Протестируйте свое решение локально, создав туннель на localhost 3000 с помощью ngrok.
Для этого выполните следующие действия:
- Установите ngrok, следуя инструкциям на его сайте.
- Укажите свой authtoken для ngrok, чтобы привязать клиента к этой учетной записи.
- Выполните команду
node your_file_name.js, чтобы запустить ваш сервер на localhost: 3000. - Перейдите в папку ngrok на вашем компьютере и выполните команду
./ngrok http 3000, чтобы сделать туннель между работающим локальным сервером и публичным URL.
Обратите внимание на сгенерированный публичный URL, мы используем его в качестве WebSocket URL с префиксом 'wss' в сценарии:

4. Распознавание речи
Вы наверняка заметили, что код нашего бэкенда содержит строки, относящиеся к Google Cloud.
Сама библиотека импортируется следующим образом:
const speech = require('@google-cloud/speech');
Теперь вам нужно указать, как обрабатывать запрос на распознавание речи. Для этого выберите encoding, sampleRateHertz и languageCode в создаваемом config:
const config = { encoding: 'MULAW', sampleRateHertz: 8000, languageCode: 'en-US', };
Далее, создайте поток на запись, позволяющий сохранить переданные данные в файл:
var wstream = fs.createWriteStream('myBinaryFile');
Когда все настроено, необходимо распарсить сообщение и поместить аудиоданные в формате base64 в recognizeStream:
let data = JSON.parse(message) if (data.event == "media") { b64data = data.media.payload; let buff = new Buffer.from(b64data, 'base64'); recognizeStream.write(buff); wstream.write(buff); }
Сразу после этого будет инициирован запрос на распознавание и начнется обработка этого запроса:
recognizeStream = client .streamingRecognize(request) .on('data', data => { ws.send(data.results[0].alternatives[0].transcript) });
И наконец, предоставьте учетные данные вашего service account, чтобы подключить библиотеку Google к ее бэкенду. Для этого перейдите на страницу аутентификации Google и выполните все шаги, перечисленные там. Затем запустите команду экспорта в той же рабочей области (в той же вкладке «Терминал»), что и команду
node your_file_name.js:export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"
Запускаем сокеты
Откройте phone.voximplant.com, заполните форму и нажмите Sign in. Username и password относятся к пользователю, созданному на шаге 1:

После успешной авторизации нажмите Call и начните говорить. Облако Speech-to-Text будет преобразовывать вашу речь в текст в режиме реального времени, и вы сможете увидеть этот текст в окне терминала.
Вы справились, поздравляем! Надеемся, что статья пришлась вам по нраву, и желаем больше интеграций и новых челленджей.

