ishaq101 commited on
Commit
6078ab4
·
1 Parent(s): 3f6c44e

fix chunk empty audio

Browse files
src/audio/AudioPlayer.ts CHANGED
@@ -4,6 +4,7 @@ export class AudioPlayer {
4
  private context: AudioContext | null = null;
5
  private nextPlayTime = 0;
6
  private started = false;
 
7
  private pendingBytes = 0;
8
  private bufferThresholdBytes = 6400; // 200ms at 16kHz default
9
  private pendingBuffers: AudioBuffer[] = [];
@@ -18,6 +19,7 @@ export class AudioPlayer {
18
  this.bufferThresholdBytes = Math.floor(sampleRate * 0.2) * 2; // 200ms
19
  this.nextPlayTime = 0;
20
  this.started = false;
 
21
  this.pendingBytes = 0;
22
  this.pendingBuffers = [];
23
  }
@@ -25,7 +27,7 @@ export class AudioPlayer {
25
  enqueue(rawPcm: ArrayBuffer, onStarted?: () => void): void {
26
  if (!this.context) return;
27
 
28
- const int16 = new Int16Array(rawPcm);
29
  const float32 = new Float32Array(int16.length);
30
  for (let i = 0; i < int16.length; i++) {
31
  float32[i] = int16[i] / 32768;
@@ -36,17 +38,27 @@ export class AudioPlayer {
36
 
37
  this.pendingBytes += rawPcm.byteLength;
38
 
39
- if (!this.started) {
 
 
 
 
 
 
 
 
40
  this.pendingBuffers.push(audioBuffer);
41
- if (this.pendingBytes >= this.bufferThresholdBytes) {
 
42
  this.started = true;
43
- const buffered = this.pendingBuffers.splice(0);
44
- this.pendingBuffers = [];
45
  void this.context.resume().then(() => {
46
  if (!this.context) return;
 
47
  this.nextPlayTime = this.context.currentTime;
48
  onStarted?.();
49
- for (const buf of buffered) {
 
 
50
  const source = this.context.createBufferSource();
51
  source.buffer = buf;
52
  source.connect(this.context.destination);
@@ -55,12 +67,6 @@ export class AudioPlayer {
55
  }
56
  });
57
  }
58
- } else {
59
- const source = this.context.createBufferSource();
60
- source.buffer = audioBuffer;
61
- source.connect(this.context.destination);
62
- source.start(this.nextPlayTime);
63
- this.nextPlayTime += audioBuffer.duration;
64
  }
65
  }
66
 
@@ -74,6 +80,7 @@ export class AudioPlayer {
74
  this.context = null;
75
  this.nextPlayTime = 0;
76
  this.started = false;
 
77
  this.pendingBytes = 0;
78
  this.pendingBuffers = [];
79
  }
@@ -87,7 +94,7 @@ export function replayAudio(
87
  let nextTime = ctx.currentTime + 0.05;
88
 
89
  for (const chunk of chunks) {
90
- const int16 = new Int16Array(chunk);
91
  const float32 = new Float32Array(int16.length);
92
  for (let i = 0; i < int16.length; i++) float32[i] = int16[i] / 32768;
93
  const buf = ctx.createBuffer(1, float32.length, sampleRate);
 
4
  private context: AudioContext | null = null;
5
  private nextPlayTime = 0;
6
  private started = false;
7
+ private resumed = false;
8
  private pendingBytes = 0;
9
  private bufferThresholdBytes = 6400; // 200ms at 16kHz default
10
  private pendingBuffers: AudioBuffer[] = [];
 
19
  this.bufferThresholdBytes = Math.floor(sampleRate * 0.2) * 2; // 200ms
20
  this.nextPlayTime = 0;
21
  this.started = false;
22
+ this.resumed = false;
23
  this.pendingBytes = 0;
24
  this.pendingBuffers = [];
25
  }
 
27
  enqueue(rawPcm: ArrayBuffer, onStarted?: () => void): void {
28
  if (!this.context) return;
29
 
30
+ const int16 = new Int16Array(rawPcm, 0, Math.floor(rawPcm.byteLength / 2));
31
  const float32 = new Float32Array(int16.length);
32
  for (let i = 0; i < int16.length; i++) {
33
  float32[i] = int16[i] / 32768;
 
38
 
39
  this.pendingBytes += rawPcm.byteLength;
40
 
41
+ if (this.resumed) {
42
+ // AudioContext is running — schedule immediately
43
+ const source = this.context.createBufferSource();
44
+ source.buffer = audioBuffer;
45
+ source.connect(this.context.destination);
46
+ source.start(this.nextPlayTime);
47
+ this.nextPlayTime += audioBuffer.duration;
48
+ } else {
49
+ // Still buffering: waiting for threshold or waiting for ctx.resume() to complete
50
  this.pendingBuffers.push(audioBuffer);
51
+
52
+ if (!this.started && this.pendingBytes >= this.bufferThresholdBytes) {
53
  this.started = true;
 
 
54
  void this.context.resume().then(() => {
55
  if (!this.context) return;
56
+ this.resumed = true;
57
  this.nextPlayTime = this.context.currentTime;
58
  onStarted?.();
59
+ const toSchedule = this.pendingBuffers.splice(0);
60
+ this.pendingBuffers = [];
61
+ for (const buf of toSchedule) {
62
  const source = this.context.createBufferSource();
63
  source.buffer = buf;
64
  source.connect(this.context.destination);
 
67
  }
68
  });
69
  }
 
 
 
 
 
 
70
  }
71
  }
72
 
 
80
  this.context = null;
81
  this.nextPlayTime = 0;
82
  this.started = false;
83
+ this.resumed = false;
84
  this.pendingBytes = 0;
85
  this.pendingBuffers = [];
86
  }
 
94
  let nextTime = ctx.currentTime + 0.05;
95
 
96
  for (const chunk of chunks) {
97
+ const int16 = new Int16Array(chunk, 0, Math.floor(chunk.byteLength / 2));
98
  const float32 = new Float32Array(int16.length);
99
  for (let i = 0; i < int16.length; i++) float32[i] = int16[i] / 32768;
100
  const buf = ctx.createBuffer(1, float32.length, sampleRate);
src/audio/AudioRecorder.ts CHANGED
@@ -4,7 +4,7 @@ const CHUNK_SAMPLES = 3200; // 100ms at 16kHz
4
  export class AudioRecorder {
5
  private context: AudioContext | null = null;
6
  private stream: MediaStream | null = null;
7
- private processor: ScriptProcessorNode | null = null;
8
  private accumulator: Float32Array = new Float32Array(0);
9
  micLevel = 0;
10
 
@@ -19,13 +19,16 @@ export class AudioRecorder {
19
 
20
  this.context = new AudioContext({ sampleRate: TARGET_SAMPLE_RATE });
21
  await this.context.resume();
22
- const source = this.context.createMediaStreamSource(this.stream);
23
 
24
- // bufferSize 512 → fires every ~32ms at 16kHz; we accumulate to 100ms
25
- this.processor = this.context.createScriptProcessor(512, 1, 1);
 
 
 
 
26
 
27
- this.processor.onaudioprocess = (e) => {
28
- const input = e.inputBuffer.getChannelData(0);
29
  const combined = new Float32Array(this.accumulator.length + input.length);
30
  combined.set(this.accumulator);
31
  combined.set(input, this.accumulator.length);
@@ -51,13 +54,12 @@ export class AudioRecorder {
51
  }
52
  };
53
 
54
- source.connect(this.processor);
55
- this.processor.connect(this.context.destination);
56
  }
57
 
58
  stop(): void {
59
- this.processor?.disconnect();
60
- this.processor = null;
61
  this.stream?.getTracks().forEach((t) => t.stop());
62
  this.stream = null;
63
  this.context?.close();
 
4
  export class AudioRecorder {
5
  private context: AudioContext | null = null;
6
  private stream: MediaStream | null = null;
7
+ private workletNode: AudioWorkletNode | null = null;
8
  private accumulator: Float32Array = new Float32Array(0);
9
  micLevel = 0;
10
 
 
19
 
20
  this.context = new AudioContext({ sampleRate: TARGET_SAMPLE_RATE });
21
  await this.context.resume();
 
22
 
23
+ await this.context.audioWorklet.addModule(
24
+ new URL("./RecorderWorkletProcessor.js", import.meta.url).href
25
+ );
26
+
27
+ const source = this.context.createMediaStreamSource(this.stream);
28
+ this.workletNode = new AudioWorkletNode(this.context, "recorder-processor");
29
 
30
+ this.workletNode.port.onmessage = (e: MessageEvent<Float32Array>) => {
31
+ const input = e.data;
32
  const combined = new Float32Array(this.accumulator.length + input.length);
33
  combined.set(this.accumulator);
34
  combined.set(input, this.accumulator.length);
 
54
  }
55
  };
56
 
57
+ source.connect(this.workletNode);
 
58
  }
59
 
60
  stop(): void {
61
+ this.workletNode?.disconnect();
62
+ this.workletNode = null;
63
  this.stream?.getTracks().forEach((t) => t.stop());
64
  this.stream = null;
65
  this.context?.close();
src/audio/RecorderWorkletProcessor.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class RecorderProcessor extends AudioWorkletProcessor {
2
+ process(inputs) {
3
+ const channel = inputs[0]?.[0];
4
+ if (channel?.length) {
5
+ const copy = channel.slice();
6
+ this.port.postMessage(copy, [copy.buffer]);
7
+ }
8
+ return true;
9
+ }
10
+ }
11
+
12
+ registerProcessor("recorder-processor", RecorderProcessor);