ishaq101 commited on
Commit
cecec00
·
1 Parent(s): 017da10

Fix garbled audio

Browse files

1. Buffer 200ms → 500ms — lebih banyak data di-buffer sebelum playback dimulai, mengurangi dampak network jitter dan memberikan Web Audio API lebih banyak headroom untuk render audio tanpa glitch
2. flush() setelah stream selesai — fix bug di mana audio pendek (total < 500ms) tidak pernah diputar karena threshold tidak pernah tercapai; flush() memaksa semua pending buffers di-schedule setelah stream berakhir

src/app/components/Main.tsx CHANGED
@@ -414,6 +414,8 @@ export default function Main() {
414
  );
415
  }
416
  }
 
 
417
  const totalBytes = chunks.reduce((acc, c) => acc + c.byteLength, 0);
418
  const durationMs = (totalBytes / 2 / sampleRate) * 1000;
419
  await new Promise<void>((resolve) => setTimeout(resolve, durationMs + 300));
 
414
  );
415
  }
416
  }
417
+ // Play any remaining buffered audio that didn't reach the threshold (short responses)
418
+ player.flush(!startedNotified ? () => { startedNotified = true; onStarted?.(); } : undefined);
419
  const totalBytes = chunks.reduce((acc, c) => acc + c.byteLength, 0);
420
  const durationMs = (totalBytes / 2 / sampleRate) * 1000;
421
  await new Promise<void>((resolve) => setTimeout(resolve, durationMs + 300));
src/audio/AudioPlayer.ts CHANGED
@@ -16,7 +16,7 @@ export class AudioPlayer {
16
  this.context = null;
17
  }
18
  this.context = new AudioContext({ sampleRate });
19
- this.bufferThresholdBytes = Math.floor(sampleRate * 0.2) * 2; // 200ms
20
  this.nextPlayTime = 0;
21
  this.started = false;
22
  this.resumed = false;
@@ -70,6 +70,28 @@ export class AudioPlayer {
70
  }
71
  }
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  drain(): void {
74
  // Let queued buffers play out — nothing to do, the AudioContext schedule handles it
75
  }
 
16
  this.context = null;
17
  }
18
  this.context = new AudioContext({ sampleRate });
19
+ this.bufferThresholdBytes = Math.floor(sampleRate * 0.5) * 2; // 500ms
20
  this.nextPlayTime = 0;
21
  this.started = false;
22
  this.resumed = false;
 
70
  }
71
  }
72
 
73
+ // Call after the stream ends to play any buffered audio that hasn't started yet
74
+ // (handles responses shorter than the buffer threshold)
75
+ flush(onStarted?: () => void): void {
76
+ if (!this.context || this.started || this.pendingBuffers.length === 0) return;
77
+ this.started = true;
78
+ void this.context.resume().then(() => {
79
+ if (!this.context) return;
80
+ this.resumed = true;
81
+ this.nextPlayTime = this.context.currentTime;
82
+ onStarted?.();
83
+ const toSchedule = this.pendingBuffers.splice(0);
84
+ this.pendingBuffers = [];
85
+ for (const buf of toSchedule) {
86
+ const source = this.context.createBufferSource();
87
+ source.buffer = buf;
88
+ source.connect(this.context.destination);
89
+ source.start(this.nextPlayTime);
90
+ this.nextPlayTime += buf.duration;
91
+ }
92
+ });
93
+ }
94
+
95
  drain(): void {
96
  // Let queued buffers play out — nothing to do, the AudioContext schedule handles it
97
  }