Скорее всего, вы уже установили хотя бы одно расширение для браузера. Поскольку Chrome используют более 2,5 миллиардов (да, с буквой «б») пользователей по всему миру, это отличная платформа для создания и выпуска приложений. В этом руководстве вы создадите расширение Chrome, которое будет захватывать аудиозаписи вкладок браузера и транскрибировать их с помощью Deepgram.
Манифест
Создайте файл manifest.json
. Этот файл содержит важную информацию о нашем расширении, которая необходима браузеру для его загрузки (и публикации в Chrome Web Store). Добавьте в него следующее:
{
"name": "Transcribe Tab Audio",
"version": "1.0",
"manifest_version": 3,
"host_permissions": ["*://*/"],
"permissions": ["storage", "tabs", "scripting"]
}
host_permissions
указывает, на каких веб-страницах будет активно это расширение — *
соответствует всем, поэтому оно будет работать на каждой странице. Вы можете изменить этот параметр, если хотите, чтобы расширение работало только на определенных страницах или доменах.
Указанные полномочия
также необходимы для этого проекта — "storage"
позволяет расширению хранить небольшие объемы данных на машине, "tabs"
обеспечивает доступ ко всем полям данных относительно вкладок в браузере, а "scripting"
позволяет нам выполнять файлы JavaScript — подробнее об этом позже.
На данный момент у вас есть действующее расширение Chrome — давайте загрузим его. Перейдите по адресу chrome://extensions, включите режим разработчика и нажмите Load Unpacked. Выберите папку с файлом manifest.json
, и вы увидите, как расширение появится в вашем браузере.
Сейчас оно немного потертое — время исправить это.
Создание всплывающего окна
Всплывающее окно расширения — это небольшая панель, которая появляется, когда вы нажимаете на значок расширения в адресной строке.
Создайте файл popup.html
:
<!DOCTYPE html>
<html>
<head>
</head>
<body style="padding: 1em;">
<button id="start">Start transcription</button>
<p id="transcript"></p>
<script src="popup.js"></script>
</body>
</html>
В файле manifest.json
укажите файл всплывающего окна, добавив это свойство:
"action": {
"default_popup": "popup.html"
}
Возможно, вы заметили, что связанный файл JavaScript еще не существует. Прежде чем мы создадим его, важно отметить, что как только всплывающее окно будет закрыто, страница как будто перестанет существовать, и код перестанет выполняться. По этой причине расширение должно внедрить некоторый код для выполнения на текущей веб-странице. Это означает, что код продолжит выполняться даже после закрытия всплывающего окна.
Для этого создайте файл popup.js
:
document.getElementById('start').addEventListener('click', async () => {
const tab = await getCurrentTab()
if(!tab) return alert('Require an active tab')
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['main.js']
})
})
async function getCurrentTab() {
const queryOptions = { active: true, lastFocusedWindow: true }
const [tab] = await chrome.tabs.query(queryOptions)
return tab
}
Когда будет нажата кнопка запуска, она получит активную вкладку и внедрит файл main.js
. Создайте его:
alert('This is an injected script!')
Откройте расширение и нажмите кнопку. Вы должны увидеть оповещение! Удалите предупреждение, прежде чем двигаться дальше.
Транскрибирование аудиозаписи вкладки
В файле main.js
запросите доступ к дисплею пользователя, проверьте, что к нему подключен звук, и подключите его к MediaRecorder:
navigator.mediaDevices.getDisplayMedia({ video: true, audio: true }).then(stream => {
if(stream.getAudioTracks().length == 0) return alert('You must share your tab with audio. Refresh the page.')
const recorder = new MediaRecorder(stream, { mimeType: 'audio/webm' })
// Further code here
})
Попробуйте. Когда вы предоставляете общий доступ к вкладке, убедитесь, что вы также предоставляете общий доступ к звуку вкладки. Если нет, мы настроили предупреждение, чтобы показать ошибку и остановить дальнейшее выполнение кода.
Подключитесь к Deepgram с помощью WebSocket и, как только соединение будет открыто, начните отправлять аудиоданные вкладки:
socket = new WebSocket('wss://api.deepgram.com/v1/listen?tier=enhanced', ['token', 'YOUR_DEEPGRAM_API_KEY'])
recorder.addEventListener('dataavailable', evt => {
if(evt.data.size > 0 && socket.readyState == 1) socket.send(evt.data)
})
socket.onopen = () => { recorder.start(250) }
// Further code here
Обратите внимание, что socket
находится в глобальной области видимости (об этом свидетельствует отсутствие ключевых слов var
, let
или const
), чтобы мы могли позже закрыть соединение.
Затем прослушайте транскрипты, выданные Deepgram:
socket.onmessage = msg => {
const { transcript } = JSON.parse(msg.data).channel.alternatives[0]
if(transcript) {
console.log(transcript)
}
}
Перейдите на вкладку с аудио, начните транскрибировать и посмотрите в инструментах разработчика вашего браузера.
Отлично! Все определенно сходится.
Передача данных из сценария контента во всплывающее окно
Не стоит ожидать, что пользователи откроют консоль браузера, чтобы увидеть транскрипты. Вы можете отправлять «сообщения» из внедренного скрипта во всплывающее окно, но если окно будет закрыто, они не будут получены. Итак, вот план:
- Когда появится новая расшифровка, поместите ее в хранилище chrome.
- Отправьте сообщение из внедренного скрипта во всплывающее окно, чтобы сказать, что доступна новая расшифровка.
- Если всплывающее окно открыто, отобразить последнюю расшифровку из хранилища.
- Когда всплывающее окно откроется, получите последнюю расшифровку (даже если сообщения пропущены, это позволит нам получить актуальную информацию).
Chrome Storage — это специфический для расширений API, который действует аналогично localStorage, но более специализирован для нужд расширений и может быть синхронизирован с помощью Chrome Sync (в данном расширении это не так).
В самом верху main.js
, поверх всего остального кода, создайте новый ключ транскрипта в хранилище Chrome и задайте начальное значение пустой строки:
chrome.storage.local.set({ transcript: '' })
Замените console.log(transcript)
на:
chrome.storage.local.get('transcript', data => {
chrome.storage.local.set({
transcript: data.transcript += ' ' + transcript
})
// Throws error when popup is closed, so this swallows the errors with catch.
chrome.runtime.sendMessage({
message: 'transcriptavailable'
}).catch(err => ({}))
})
Это позволяет получить существующую расшифровку и добавить новую расшифровку в ее конец. Затем отправляется сообщение со значением ‘transcriptavailable’, которое мы теперь можем прослушать в popup.js
.
В нижней части popup.js
:
chrome.runtime.onMessage.addListener(({ message }) => {
if(message == 'transcriptavailable') {
showLatestTranscript()
}
})
function showLatestTranscript() {
chrome.storage.local.get('transcript', ({ transcript }) => {
document.getElementById('transcript').innerHTML = transcript
})
}
Также получите последнюю расшифровку в самом верху popup.js
, над всем остальным кодом:
showLatestTranscript()
Остановка транскрипции
Добавьте кнопку, чуть ниже кнопки запуска, в popup.html
:
<button id="stop">Stop transcription</button>
Когда кнопка будет нажата, отправьте сообщение обратно внедренному скрипту. В popup.js
:
document.getElementById('stop').addEventListener('click', async () => {
const tab = await getCurrentTab()
if(!tab) return alert('Require an active tab')
chrome.tabs.sendMessage(tab.id, { message: 'stop' })
})
В самом низу main.js
, ниже всего остального кода, получите сообщение и закройте WebSocket-соединение с Deepgram:
chrome.runtime.onMessage.addListener(({ message }) => {
if(message == 'stop') {
socket.close()
alert('Transcription ended')
}
})
Превосходно.
Создание страницы параметров
Сейчас ваш API-ключ Deepgram закодирован прямо в приложении. Далее вы создадите страницу опций, чтобы пользователь мог ввести свой ключ, сохранить его в хранилище Chrome и использовать это значение при подключении к Deepgram.
В manifest.json
добавьте следующее свойство:
"options_page": "options.html"
Создайте и откройте файл options.html
:
<!DOCTYPE html>
<html>
<body>
<h1>Provide your Deepgram API Key</h1>
<input type="text" id="api">
<button>Save</button>
<script src="options.js"></script>
</body>
</html>
Создайте и откройте файл options.js
:
const api = document.getElementById('api')
const button = document.querySelector('button')
// If it exists, load it in
chrome.storage.local.get('key', ({ key }) => {
if(key) api.value = key
})
button.addEventListener('click', () => {
const key = api.value
chrome.storage.local.set({ key }, () => {
alert('Deepgram API Key Set')
})
})
Пора воспользоваться ключом. В верхней части файла main.js
, над всем остальным кодом:
let apiKey
chrome.storage.local.get('key', ({ key }) => apiKey = key)
После этого apiKey
будет либо undefined
, либо строкой с ключом API.
Замените следующее в файле main.js
:
socket = new WebSocket('wss://api.deepgram.com/v1/listen?tier=enhanced', ['token', 'YOUR_DEEPGRAM_API_KEY'])
// Replace with 👇
if(!apiKey) return alert('You must provide a Deepgram API Key in the options page.')
socket = new WebSocket('wss://api.deepgram.com/v1/listen?tier=enhanced', ['token', apiKey])
Щелкните расширение правой кнопкой мыши и выберите Options, чтобы открыть новую страницу. Сохраните свой API-ключ Deepgram, и расширение будет работать.
Доступ к аудио и микрофону вкладки браузера
Гипотетическая ситуация — вы хотите расшифровать браузерный видеозвонок с помощью этого расширения. Все голоса будут расшифрованы, кроме вашего — это потому, что ваш звук не проходит через вкладку (иначе вы бы услышали себя!), поэтому давайте изменим это расширение так, чтобы ваш микрофон и звук вкладки были расшифрованы вместе.
Если вы хотите транскрибировать только звук вкладки, пропустите до конца.
На данный момент в main.js
вы запрашиваете отображение пользователя, проверяете наличие звука и передаете полученный поток в MediaRecorder. Теперь мы должны:
- Получить доступ к пользовательскому дисплею и проверить, есть ли там звук.
- Получить доступ к аудиоустройству пользователя (микрофону).
- Создать новый, пустой
AudioContext
. - Смешайте два источника звука вместе как источники в одном
AudioContext
. - Создайте MediaRecorder с
AudioContext
, содержащим теперь два источника.
В самом низу файла main.js, ниже всего остального кода:
// https://stackoverflow.com/a/47071576
function mix(audioContext, streams) {
const dest = audioContext.createMediaStreamDestination()
streams.forEach(stream => {
const source = audioContext.createMediaStreamSource(stream)
source.connect(dest)
})
return dest.stream
}
Замените следующее в main.js
:
const recorder = new MediaRecorder(stream, { mimeType: 'audio/webm' })
// Replace with 👇
const micStream = await navigator.mediaDevices.getUserMedia({ audio: true })
const audioContext = new AudioContext()
const mixed = mix(audioContext, [stream, micStream])
const recorder = new MediaRecorder(mixed, { mimeType: 'audio/webm' })
Добавьте ключевое слово async
непосредственно перед stream
в функции .then()
:
navigator.mediaDevices.getDisplayMedia({ video: true, audio: true }).then(await stream => {
Бум. Готово.
Следующие шаги
Вы можете многое сделать для улучшения своего расширения Chrome — сделать его более красивым с помощью CSS, изменить способ отображения транскриптов или изменить значок расширения во время записи. Вы также можете рассмотреть возможность использования функций Deepgram, таких как диаризация, для обнаружения разных дикторов и отображения их по-разному.
Полный готовый код этого проекта вы можете найти на GitHub по адресу deepgram-devs/transcription-chrome-extension. Как всегда, если у вас есть вопросы, пожалуйста, обращайтесь в Twitter (мы @DeepgramDevs).