File size: 4,602 Bytes
3fde5f3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | class AudioRecorder {
constructor() {
this.mediaRecorder = null;
this.audioChunks = [];
this.isRecording = false;
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
async start() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
channelCount: 1,
sampleRate: 16000
}
});
this.mediaRecorder = new MediaRecorder(stream);
this.audioChunks = [];
this.isRecording = true;
this.mediaRecorder.addEventListener("dataavailable", (event) => {
this.audioChunks.push(event.data);
});
this.mediaRecorder.start();
return true;
} catch (error) {
console.error("Error starting recording:", error);
throw error;
}
}
async stop() {
return new Promise(async (resolve) => {
this.mediaRecorder.addEventListener("stop", async () => {
const audioBlob = new Blob(this.audioChunks, { type: 'audio/wav' });
this.isRecording = false;
// Convert to WAV with correct format
const arrayBuffer = await audioBlob.arrayBuffer();
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
// Create WAV file
const wavBuffer = await this.createWAV(audioBuffer);
const wavBlob = new Blob([wavBuffer], { type: 'audio/wav' });
resolve(wavBlob);
});
this.mediaRecorder.stop();
this.mediaRecorder.stream.getTracks().forEach(track => track.stop());
});
}
async createWAV(audioBuffer) {
const numChannels = 1; // Mono
const sampleRate = 16000; // Target sample rate
const format = 1; // PCM
const bitDepth = 16;
// Resample if needed
let samples = audioBuffer.getChannelData(0);
if (audioBuffer.sampleRate !== sampleRate) {
samples = await this.resampleAudio(samples, audioBuffer.sampleRate, sampleRate);
}
const dataLength = samples.length * (bitDepth / 8);
const headerLength = 44;
const totalLength = headerLength + dataLength;
const buffer = new ArrayBuffer(totalLength);
const view = new DataView(buffer);
// Write WAV header
this.writeString(view, 0, 'RIFF');
view.setUint32(4, totalLength - 8, true);
this.writeString(view, 8, 'WAVE');
this.writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, format, true);
view.setUint16(22, numChannels, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * numChannels * (bitDepth / 8), true);
view.setUint16(32, numChannels * (bitDepth / 8), true);
view.setUint16(34, bitDepth, true);
this.writeString(view, 36, 'data');
view.setUint32(40, dataLength, true);
// Write audio data
this.floatTo16BitPCM(view, 44, samples);
return buffer;
}
writeString(view, offset, string) {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
floatTo16BitPCM(view, offset, input) {
for (let i = 0; i < input.length; i++, offset += 2) {
const s = Math.max(-1, Math.min(1, input[i]));
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}
async resampleAudio(audioData, originalSampleRate, targetSampleRate) {
const originalLength = audioData.length;
const ratio = targetSampleRate / originalSampleRate;
const newLength = Math.round(originalLength * ratio);
const result = new Float32Array(newLength);
for (let i = 0; i < newLength; i++) {
const position = i / ratio;
const index = Math.floor(position);
const fraction = position - index;
if (index + 1 < originalLength) {
result[i] = audioData[index] * (1 - fraction) + audioData[index + 1] * fraction;
} else {
result[i] = audioData[index];
}
}
return result;
}
isActive() {
return this.isRecording;
}
} |