Запись аудио в браузере с помощью Web Audio API
Большинство разработчиков полагают, что Web Audio API отвечает за запись. Это не так — по крайней мере, не напрямую. Web Audio API — это движок обработки и маршрутизации. За саму запись отвечает MediaRecorder API. Понимание этого различия — самый быстрый способ перестать строить не то, что нужно.
В этой статье рассматривается актуальная рекомендуемая архитектура для записи аудио в браузере: захват микрофонного входа с помощью getUserMedia(), опциональная маршрутизация звука через граф Web Audio и кодирование с помощью MediaRecorder.
Ключевые выводы
- Web Audio API обрабатывает и маршрутизирует звук, но именно
MediaRecorderфактически записывает его в файл. - Полный конвейер записи состоит из трёх этапов: захват с помощью
getUserMedia(), опциональная обработка через узлы Web Audio и кодирование с помощьюMediaRecorder. getUserMedia()требует безопасного контекста (HTTPS или localhost) и явного разрешения пользователя на доступ к микрофону.- Используйте
AudioWorkletдля кастомной DSP-обработки;ScriptProcessorNodeустарел и вызывает сбои под нагрузкой. - Всегда проверяйте поддержку MIME-типа через
MediaRecorder.isTypeSupported()перед записью, поскольку Safari и Chrome поддерживают разные форматы.
Как на самом деле работает запись аудио в браузере
Современный конвейер записи состоит из трёх отдельных этапов:
- Захват —
getUserMedia()запрашивает доступ к микрофону и возвращаетMediaStream. - Обработка (опционально) — Web Audio API анализирует или преобразует поток через аудиоузлы.
- Запись —
MediaRecorderкодирует поток в файл.
Web Audio API не нужен для простой записи. Но если вам требуется шумоподавление, контроль усиления, визуализация или кастомные эффекты через AudioWorklet, он чисто встраивается в середину этой цепочки.
Тонкая, но важная деталь: MediaRecorder записывает MediaStream напрямую. Если вы хотите записать обработанный выход графа Web Audio, вам нужно перенаправить граф обратно в поток с помощью audioContext.createMediaStreamDestination() и передать .stream этого узла в MediaRecorder.
Шаг 1: Запрос доступа к микрофону через getUserMedia
getUserMedia() требует безопасного контекста (HTTPS или localhost) и явного разрешения пользователя, прежде чем браузер предоставит доступ к микрофону.
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 44100
}
});
} catch (err) {
console.error('Microphone access failed:', err.name, err.message);
}
Всегда оборачивайте это в try/catch. Пользователи могут отказать в разрешении, а некоторые окружения (например, HTTP-страницы) сразу отклонят вызов с ошибкой NotAllowedError или SecurityError. Обратите внимание, что ограничения вроде sampleRate являются лишь подсказками — браузеры могут их игнорировать в зависимости от используемого оборудования.
Шаг 2: Маршрутизация через Web Audio API (опционально)
Если вам нужно проанализировать или обработать звук перед записью, создайте AudioContext и пропустите поток через него:
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
// Example: connect to an AnalyserNode for visualization
const analyser = audioContext.createAnalyser();
source.connect(analyser);
// To record the processed output, route to a MediaStreamDestination
const destination = audioContext.createMediaStreamDestination();
analyser.connect(destination);
// destination.stream is what you pass to MediaRecorder
Для кастомной обработки — например, шумового гейта или измерителя уровня в реальном времени — используйте AudioWorklet вместо устаревшего ScriptProcessorNode. AudioWorklet работает вне основного потока, что означает, что он не будет блокировать ваш UI и терять аудиосэмплы под нагрузкой.
Примечание: В iOS Safari
AudioContextзапускается в приостановленном состоянии до тех пор, пока не будет активирован прямым взаимодействием пользователя. Создавайте его (или вызывайтеaudioContext.resume()) внутри обработчика клика по кнопке, а не при загрузке страницы.
Discover how at OpenReplay.com.
Шаг 3: Запись и экспорт с помощью MediaRecorder
MediaRecorder принимает MediaStream и кодирует его. Не задавайте MIME-тип жёстко — сначала проверьте поддержку:
function pickMimeType() {
const candidates = [
'audio/webm;codecs=opus',
'audio/ogg;codecs=opus',
'audio/mp4'
];
return candidates.find(type => MediaRecorder.isTypeSupported(type)) || '';
}
const mimeType = pickMimeType();
const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
const chunks = [];
recorder.ondataavailable = (e) => {
if (e.data.size > 0) chunks.push(e.data);
};
recorder.onstop = () => {
const blob = new Blob(chunks, {
type: recorder.mimeType || mimeType || chunks[0]?.type || 'audio/webm'
});
const url = URL.createObjectURL(blob);
document.querySelector('audio').src = url;
};
recorder.start();
WebM/Opus обычно является лучшим вариантом по умолчанию для современных браузеров — небольшой размер файла, отличное качество. Safari часто предпочитает audio/mp4. Перебор списка кандидатов и выбор первого поддерживаемого браузером типа — наиболее надёжная стратегия. Проверить текущую поддержку MediaRecorder и связанных форматов в браузерах можно на Can I Use.
MediaRecorder и Web Audio API: краткий справочник
| Задача | Использовать |
|---|---|
| Простая запись с микрофона | getUserMedia + MediaRecorder |
| Эффекты или фильтры в реальном времени | Узлы Web Audio API |
| Кастомная DSP-обработка | AudioWorklet |
| Визуализация аудио | AnalyserNode |
| Кодирование в файл | MediaRecorder |
Чего следует избегать
ScriptProcessorNode— устарел, выполняется в основном потоке, вызывает сбои звука под нагрузкой.- Жёстко заданные MIME-типы — молча ломаются в Safari и старых версиях Firefox.
- Создание
AudioContextпри загрузке страницы — браузеры приостанавливают его до пользовательского жеста, поэтому возобновляйте его внутри обработчика событий. - Забывать остановить поток — вызывайте
stream.getTracks().forEach(t => t.stop())по завершении, иначе индикатор микрофона останется активным.
Заключение
Запись аудио в браузере — это задача для двух API: getUserMedia() и MediaRecorder отвечают за захват и кодирование, а Web Audio API обрабатывает всё, что находится между ними. Начните с простейшего конвейера, отвечающего вашим требованиям, добавляйте обработку Web Audio только тогда, когда она вам действительно нужна, и всегда проверяйте поддержку MIME-типа перед настройкой MediaRecorder. Вот и вся архитектура в одном предложении.
Часто задаваемые вопросы
Нет. Для базовой записи достаточно getUserMedia() и MediaRecorder. Web Audio API становится необходим только тогда, когда нужно обработать, проанализировать или преобразовать звук перед записью — например, применить фильтры, построить визуализации или запустить кастомную DSP-обработку через AudioWorklet.
Safari и Chrome поддерживают разные форматы записи. Safari часто предпочитает audio/mp4, тогда как Chrome обычно использует WebM/Opus. Если вы жёстко зададите неподдерживаемый MIME-тип, MediaRecorder может выбросить ошибку или создать непригодный для использования вывод. Всегда используйте MediaRecorder.isTypeSupported() для определения совместимого формата во время выполнения.
Создайте MediaStreamAudioDestinationNode с помощью audioContext.createMediaStreamDestination(), подключите к нему конечный узел вашего аудиографа и передайте свойство .stream в MediaRecorder. Это захватит обработанный звук, а не сырой поток с микрофона, полученный через getUserMedia().
iOS Safari требует, чтобы AudioContext был создан или возобновлён внутри прямого пользовательского жеста, такого как событие клика или касания. Если вы создаёте его во время загрузки страницы, он остаётся приостановленным. Перенесите создание AudioContext в обработчик кнопки или вызовите audioContext.resume() внутри него, чтобы разблокировать воспроизведение и обработку.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.