diff --git a/samples/js/live-audio-transcription-example/app.js b/samples/js/live-audio-transcription-example/app.js index 794c3972..651d30f3 100644 --- a/samples/js/live-audio-transcription-example/app.js +++ b/samples/js/live-audio-transcription-example/app.js @@ -39,9 +39,10 @@ console.log('Loading model...'); await model.load(); console.log('✓ Model loaded'); -// Create live transcription session +// Create live transcription session (same pattern as C# sample). const audioClient = model.createAudioClient(); const session = audioClient.createLiveTranscriptionSession(); + session.settings.sampleRate = 16000; // Default is 16000; shown here for clarity session.settings.channels = 1; session.settings.bitsPerSample = 16; @@ -56,10 +57,12 @@ const readPromise = (async () => { try { for await (const result of session.getTranscriptionStream()) { const text = result.content?.[0]?.text; + if (!text) continue; + + // `is_final` is a transcript-state marker only. It should not stop the app. if (result.is_final) { - console.log(); - console.log(` [FINAL] ${text}`); - } else if (text) { + process.stdout.write(`\n [FINAL] ${text}\n`); + } else { process.stdout.write(text); } } @@ -88,22 +91,52 @@ try { ? portAudio.SampleFormat16Bit : portAudio.SampleFormat32Bit, sampleRate: session.settings.sampleRate, - framesPerBuffer: 1600, // 100ms chunks - maxQueue: 15 // buffer during event-loop blocks from sync FFI calls + // Larger chunk size lowers callback frequency and reduces overflow risk. + framesPerBuffer: 3200, + // Allow deeper native queue during occasional event-loop stalls. + maxQueue: 64 } }); - let appendPending = false; + const appendQueue = []; + let pumping = false; + let warnedQueueDrop = false; + + const pumpAudio = async () => { + if (pumping) return; + pumping = true; + try { + while (appendQueue.length > 0) { + const pcm = appendQueue.shift(); + await session.append(pcm); + } + } catch (err) { + console.error('append error:', err.message); + } finally { + pumping = false; + // Handle race where new data arrived after loop exit. + if (appendQueue.length > 0) { + void pumpAudio(); + } + } + }; + audioInput.on('data', (buffer) => { - if (appendPending) return; // drop frame while backpressured const pcm = new Uint8Array(buffer); - appendPending = true; - session.append(pcm).then(() => { - appendPending = false; - }).catch((err) => { - appendPending = false; - console.error('append error:', err.message); - }); + const copy = new Uint8Array(pcm.length); + copy.set(pcm); + + // Keep a bounded queue to avoid unbounded memory growth. + if (appendQueue.length >= 100) { + appendQueue.shift(); + if (!warnedQueueDrop) { + warnedQueueDrop = true; + console.warn('Audio append queue overflow; dropping oldest chunk to keep stream alive.'); + } + } + + appendQueue.push(copy); + void pumpAudio(); }); console.log();