Spaces:
Running
Running
File size: 3,573 Bytes
36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 36348bf 7f2a14a 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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
// src/lib/audio-recorder.ts
import EventEmitter from 'eventemitter3';
import { audioContext } from './utils';
import VolMeterWorket from './worklets/vol-meter';
export class AudioRecorder extends EventEmitter {
public recording = false;
private mediaStream: MediaStream | null = null;
private audioContext: AudioContext | null = null;
private processor: ScriptProcessorNode | null = null;
private source: MediaStreamAudioSourceNode | null = null;
private volMeter: AudioWorkletNode | null = null;
constructor() {
super();
}
private async init() {
if (!this.audioContext) {
this.audioContext = await audioContext({ id: 'audio-in' });
}
if (!this.mediaStream) {
try {
this.mediaStream = await navigator.mediaDevices.getUserMedia({
audio: {
sampleRate: 16000,
channelCount: 1,
echoCancellation: true,
noiseSuppression: true,
},
video: false,
});
} catch (err) {
console.error('Error getting media stream:', err);
this.emit('error', err);
return;
}
}
}
public async start() {
if (this.recording) {
return;
}
await this.init();
if (!this.audioContext || !this.mediaStream) {
console.error("AudioContext or MediaStream not available.");
return;
}
this.recording = true;
this.source = this.audioContext.createMediaStreamSource(this.mediaStream);
this.processor = this.audioContext.createScriptProcessor(1024, 1, 1);
this.processor.onaudioprocess = (e) => {
if (!this.recording) return;
const inputData = e.inputBuffer.getChannelData(0);
const pcm16 = this.floatTo16BitPCM(inputData);
const base64 = this.pcm16ToBase64(pcm16);
this.emit('data', base64);
};
// --- بخش کلیدی: اضافه کردن Volume Meter ---
try {
await this.audioContext.audioWorklet.addModule(VolMeterWorket);
this.volMeter = new AudioWorkletNode(this.audioContext, 'vumeter');
this.volMeter.port.onmessage = (event) => {
if(event.data.volume) {
// ارسال رویداد جدید به همراه حجم صدا
this.emit('volume', event.data.volume);
}
};
this.source.connect(this.volMeter);
} catch(err) {
console.error("Error adding AudioWorklet module", err);
}
// ------------------------------------------
this.source.connect(this.processor);
this.processor.connect(this.audioContext.destination);
this.emit('start');
}
public stop() {
if (!this.recording) {
return;
}
this.recording = false;
this.emit('stop');
this.source?.disconnect();
this.processor?.disconnect();
this.volMeter?.disconnect();
this.source = null;
this.processor = null;
this.volMeter = null;
}
// توابع کمکی برای تبدیل فرمت صدا
private floatTo16BitPCM(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 pcm16ToBase64(pcm16: Int16Array): string {
const buffer = pcm16.buffer;
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
} |