Spaces:
Running
Running
File size: 3,469 Bytes
36348bf 7f2a14a 0362c08 7f2a14a 0362c08 36348bf 0362c08 36348bf 0362c08 7f2a14a 0362c08 36348bf 0362c08 36348bf 7f2a14a 0362c08 7f2a14a 0362c08 7f2a14a 0362c08 36348bf 7f2a14a 0362c08 7f2a14a 0362c08 36348bf 0362c08 36348bf 0362c08 36348bf 0362c08 36348bf 7f2a14a 36348bf |
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 |
// 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);
}
} |