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);
  }
}