@@ -39,9 +39,10 @@ console.log('Loading model...');
3939await model . load ( ) ;
4040console . log ( '✓ Model loaded' ) ;
4141
42- // Create live transcription session
42+ // Create live transcription session (same pattern as C# sample).
4343const audioClient = model . createAudioClient ( ) ;
4444const session = audioClient . createLiveTranscriptionSession ( ) ;
45+
4546session . settings . sampleRate = 16000 ; // Default is 16000; shown here for clarity
4647session . settings . channels = 1 ;
4748session . settings . bitsPerSample = 16 ;
@@ -56,10 +57,12 @@ const readPromise = (async () => {
5657 try {
5758 for await ( const result of session . getTranscriptionStream ( ) ) {
5859 const text = result . content ?. [ 0 ] ?. text ;
60+ if ( ! text ) continue ;
61+
62+ // `is_final` is a transcript-state marker only. It should not stop the app.
5963 if ( result . is_final ) {
60- console . log ( ) ;
61- console . log ( ` [FINAL] ${ text } ` ) ;
62- } else if ( text ) {
64+ process . stdout . write ( `\n [FINAL] ${ text } \n` ) ;
65+ } else {
6366 process . stdout . write ( text ) ;
6467 }
6568 }
@@ -88,22 +91,52 @@ try {
8891 ? portAudio . SampleFormat16Bit
8992 : portAudio . SampleFormat32Bit ,
9093 sampleRate : session . settings . sampleRate ,
91- framesPerBuffer : 1600 , // 100ms chunks
92- maxQueue : 15 // buffer during event-loop blocks from sync FFI calls
94+ // Larger chunk size lowers callback frequency and reduces overflow risk.
95+ framesPerBuffer : 3200 ,
96+ // Allow deeper native queue during occasional event-loop stalls.
97+ maxQueue : 64
9398 }
9499 } ) ;
95100
96- let appendPending = false ;
101+ const appendQueue = [ ] ;
102+ let pumping = false ;
103+ let warnedQueueDrop = false ;
104+
105+ const pumpAudio = async ( ) => {
106+ if ( pumping ) return ;
107+ pumping = true ;
108+ try {
109+ while ( appendQueue . length > 0 ) {
110+ const pcm = appendQueue . shift ( ) ;
111+ await session . append ( pcm ) ;
112+ }
113+ } catch ( err ) {
114+ console . error ( 'append error:' , err . message ) ;
115+ } finally {
116+ pumping = false ;
117+ // Handle race where new data arrived after loop exit.
118+ if ( appendQueue . length > 0 ) {
119+ void pumpAudio ( ) ;
120+ }
121+ }
122+ } ;
123+
97124 audioInput . on ( 'data' , ( buffer ) => {
98- if ( appendPending ) return ; // drop frame while backpressured
99125 const pcm = new Uint8Array ( buffer ) ;
100- appendPending = true ;
101- session . append ( pcm ) . then ( ( ) => {
102- appendPending = false ;
103- } ) . catch ( ( err ) => {
104- appendPending = false ;
105- console . error ( 'append error:' , err . message ) ;
106- } ) ;
126+ const copy = new Uint8Array ( pcm . length ) ;
127+ copy . set ( pcm ) ;
128+
129+ // Keep a bounded queue to avoid unbounded memory growth.
130+ if ( appendQueue . length >= 100 ) {
131+ appendQueue . shift ( ) ;
132+ if ( ! warnedQueueDrop ) {
133+ warnedQueueDrop = true ;
134+ console . warn ( 'Audio append queue overflow; dropping oldest chunk to keep stream alive.' ) ;
135+ }
136+ }
137+
138+ appendQueue . push ( copy ) ;
139+ void pumpAudio ( ) ;
107140 } ) ;
108141
109142 console . log ( ) ;
0 commit comments