Однажды я подумал, насколько трудно и дорого в наши дни сделать голосового помощника, который будет впопад отвечать на вопросы. А если конкретнее, то веб-приложение, которое записывает аудио с вопросом, расшифровывает аудио в текст, находит подходящий ответ, тоже текстовый, и возвращает аудио версию ответа - вот функциональные требования, который я себе набросал. Я создал простой React проект с помощью create-react-app и добавил компонент “​​RecorderAndTranscriber”, который и содержит весь функционал клиентской части. Стоит отметить использование метода getUserMedia из MediaDevices API чтобы получить доступ к микрофону. Дальше этот доступ достаётся MediaRecorder, через который уже и записывается аудио. Для таймера я использую setInterval. Пустой массив необязательным параметром в React hook - useEffect, чтобы он вызывался только раз, при создании компонента. Сохранённый поток используем для создания экземпляра MediaRecorder, который я тоже сохраняю. Дальше я добавил блок для запуска счётчика секунд, прошедших с начала записи. Hook срабатывает только при изменении значения initTimer, а callback для setInterval обновляет значение счётчика и останавливает запись если длина записи 60 секунд. Дело в том, что 60 секунд и/или 10Mb это ограничение Speech-to-Text API на аудио файлы, которые можно расшифровать, отправляя файлы напрямую. Большие файлы нужно сначала загружать в файловое хранилище Google Cloud Storage. Подробнее про ограничения можно прочитать тут. Следующий момент, который стоит упомянуть, это как происходит запись. Для начала я проверяю, что экземпляр класса MediaRecorder существует и его статус inactive, один из трёх возможных. Дальше обновляется переменная initTimer, чтобы создать и запустить interval. Чтобы контролировать запись я подписался на обработку двух событий ondataavailable и onstop. В обработчике для ondataavailable сохраняется новый кусочек аудио в заранее созданный массив. А по срабатыванию onstop, из кусочков создаётся blod файл и добавляется к списку готовых записей. В объекте записи я сохраняю url на аудио файл, чтобы использовать в DOM элементе audio, как значение для src, а поле blob, чтобы отправлять на серверную часть. Для поддержания работы клиентской части я выбрал связку Node.js и ​​Express. Создал файл index.js, в котором и собрал API с методами:         а) getTranscription(audio_blob_file)         б) getWordErrorRate(text_from_google, text_from_human)         с) getAnswer(text_from_google) Чтобы вычислить Word Error Rate я взял скрипт на python из проекта tensorflow/lingvo и переписал его в js. По сути это просто решение задачи Edit Distance, плюс расчёт ошибки по каждому из трёх типов: удаление, добавление, замена. Я получил не самый интеллектуальный метод сравнения текстов, но достаточный, чтобы в дальнейшем можно было добавлять дополнительные параметры к запросам к Speech-to-Text. Для getTranscription я взял готовый код из документации к Speech-to-Text, а для перевода текста ответа в аудио файл - из документации к Text-to-Speech. Немного запутанным оказалось создание ключа доступа к google cloud с серверной части. Для начала нужно было создать проект, потом включить Speech-to-Text API и Text-to-Speech API, создать ключ доступа и, наконец, добавить путь к ключу в переменную GOOGLE_APPLICATION_CREDENTIALS. Чтобы json файл с ключом, нужно создать Service account для проекта. После нажатия Create and Continue и Done во вкладке Credentials, в таблице Service Accounts появиться новый аккаунт. Если перейти в этот аккаунт, на вкладке Keys можно нажать на Add Key, и получить json-файл с ключом. Этот ключ необходим, чтобы серверная часть могла получить доступ к Google Cloud сервисам, активированным в проекте. На этом предлагаю закончить первую часть. Описание базы данных и моих экспериментов с ненормативной лексикой будет во второй части статьи.