SotiproAlpha2 / src /lib /audio-recorder.ts
Ezmary's picture
Update src/lib/audio-recorder.ts
0362c08 verified
raw
history blame
3.47 kB
// src/lib/audio-recorder.ts
import EventEmitter from "eventemitter3";
import { audioContext } from "./utils";
import VolMeterWorket from "./worklets/vol-meter";
const CHUNK_SIZE = 2048;
export class AudioRecorder extends EventEmitter {
recording = false;
private audioCtx: AudioContext | null = null;
private microphone: MediaStreamAudioSourceNode | null = null;
private processor: ScriptProcessorNode | null = null;
private worklet: AudioWorkletNode | null = null;
private stream: MediaStream | null = null;
async start(): Promise<void> {
if (this.recording) return;
try {
this.stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.audioCtx = await audioContext({ id: "audio-in" });
if (this.audioCtx.state === "suspended") {
await this.audioCtx.resume();
}
await this.audioCtx.audioWorklet.addModule(VolMeterWorket);
this.microphone = this.audioCtx.createMediaStreamSource(this.stream);
this.processor = this.audioCtx.createScriptProcessor(CHUNK_SIZE, 1, 1);
this.worklet = new AudioWorkletNode(this.audioCtx, "vumeter-in");
this.worklet.port.onmessage = (ev) => {
// رویداد جدید برای ارسال ولوم
this.emit("volume", ev.data.volume);
};
this.processor.onaudioprocess = (e: AudioProcessingEvent) => {
if (!this.recording) return;
const inputData = e.inputBuffer.getChannelData(0);
const pcm16Data = this.convertToPCM16(inputData);
const base64 = this.toBase64(pcm16Data);
// محاسبه ولوم در اینجا و ارسال آن همراه با داده‌ها
const volume = this.getVolume(inputData);
this.emit("data", base64, volume);
};
this.microphone.connect(this.processor);
this.microphone.connect(this.worklet); // اتصال worklet برای گرفتن ولوم
this.processor.connect(this.audioCtx.destination);
this.recording = true;
this.emit("start");
} catch (err) {
console.error("Error starting audio recording:", err);
this.emit("error", err);
}
}
stop(): void {
if (!this.recording) return;
this.recording = false;
this.emit("stop");
if (this.stream) {
this.stream.getTracks().forEach((track) => track.stop());
}
if (this.microphone) {
this.microphone.disconnect();
}
if (this.processor) {
this.processor.disconnect();
}
if (this.worklet) {
this.worklet.disconnect();
}
}
private getVolume(data: Float32Array): number {
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum += data[i] * data[i];
}
const rms = Math.sqrt(sum / data.length);
// نرمال‌سازی ولوم به یک مقدار بین 0 و 1
return Math.min(1, rms * 5); // ضریب 5 برای محسوس‌تر کردن
}
private convertToPCM16(input: Float32Array): Int16Array {
const output = new Int16Array(input.length);
for (let i = 0; i < input.length; i++) {
const s = Math.max(-1, Math.min(1, input[i]));
output[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
}
return output;
}
private toBase64(pcm16Data: Int16Array): string {
const bytes = new Uint8Array(pcm16Data.buffer);
let binary = "";
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
}