Files
alice-backend/server/public/room-mic.js
T

84 lines
3.4 KiB
JavaScript

const button = document.querySelector('#record');
const status = document.querySelector('#status');
const download = document.querySelector('#download');
let objectUrl;
function wav(blocks, rate) {
const n = blocks.reduce((count, block) => count + block.length, 0);
const data = new DataView(new ArrayBuffer(44 + n * 2));
const ascii = (offset, value) => [...value].forEach((letter, i) => data.setUint8(offset + i, letter.charCodeAt(0)));
ascii(0, 'RIFF');
data.setUint32(4, 36 + n * 2, true);
ascii(8, 'WAVEfmt ');
data.setUint32(16, 16, true);
data.setUint16(20, 1, true);
data.setUint16(22, 1, true);
data.setUint32(24, rate, true);
data.setUint32(28, rate * 2, true);
data.setUint16(32, 2, true);
data.setUint16(34, 16, true);
ascii(36, 'data');
data.setUint32(40, n * 2, true);
let offset = 44;
for (const block of blocks) {
for (const value of block) {
data.setInt16(offset, Math.round(Math.max(-1, Math.min(1, value)) * 32767), true);
offset += 2;
}
}
return new Blob([data], { type: 'audio/wav' });
}
button.onclick = async () => {
button.disabled = true;
let audio, stream, deadline, node;
const close = () => {
clearTimeout(deadline);
node?.disconnect();
stream?.getTracks().forEach(track => track.stop());
if (audio && audio.state !== 'closed') void audio.close();
button.disabled = false;
};
try {
audio = new AudioContext();
await audio.resume();
stream = await navigator.mediaDevices.getUserMedia({
video: false,
audio: { channelCount: 1, echoCancellation: false, noiseSuppression: false, autoGainControl: false },
});
const settings = stream.getAudioTracks()[0].getSettings();
if ([settings.echoCancellation, settings.noiseSuppression, settings.autoGainControl].includes(true)) {
throw new Error('Браузер оставил включённой обработку микрофона. Для точного замера используй другой браузер или Mac.');
}
await audio.audioWorklet.addModule('/room-mic-worklet.js');
node = new AudioWorkletNode(audio, 'room-mic');
const blocks = [];
node.port.onmessage = ({ data }) => {
if (data !== 'done') {
blocks.push(data);
return;
}
const blob = wav(blocks, audio.sampleRate);
if (objectUrl) URL.revokeObjectURL(objectUrl);
objectUrl = URL.createObjectURL(blob);
download.href = objectUrl;
download.download = `room-${new Date().toISOString().replaceAll(':', '-')}.wav`;
download.hidden = false;
status.textContent = 'Запись готова. Файл сохранится на этом устройстве.';
close();
};
audio.createMediaStreamSource(stream).connect(node);
node.connect(audio.destination);
const unreported = [settings.echoCancellation, settings.noiseSuppression, settings.autoGainControl].some(value => value === undefined);
status.textContent = 'Запись идёт. Запусти тестовый сигнал.' +
(unreported ? ' Браузер не сообщил все настройки обработки.' : '');
deadline = setTimeout(() => {
close();
status.textContent = 'Запись прервалась. Оставь вкладку открытой и попробуй снова.';
}, 19000);
} catch (error) {
close();
status.textContent = error.message;
}
};