@@ -105,6 +105,60 @@ describe('TTS stream idle timeout', () => {
105105 expect ( audioOut . firstFrameFut . done ) . toBe ( true ) ;
106106 } ) ;
107107
108+ it ( 'ignores PLAYBACK_STARTED from another segment before its own first frame' , async ( ) => {
109+ // Stalled stream so the forwarder is still waiting on its first read when a
110+ // stray event (from an interrupted overlapping segment) arrives; the idle
111+ // timeout then ends the loop without this segment ever capturing a frame.
112+ const stalledStream = new ReadableStream < AudioFrame > ( { start ( ) { } } ) ;
113+
114+ const audioOutput = new MockAudioOutput ( ) ;
115+ const controller = new AbortController ( ) ;
116+ const [ task , audioOut ] = performAudioForwarding ( stalledStream , audioOutput , controller , 500 ) ;
117+
118+ // Reject path is expected (no first frame ever captured).
119+ audioOut . firstFrameFut . await . catch ( ( ) => { } ) ;
120+
121+ vi . useFakeTimers ( ) ;
122+
123+ audioOutput . onPlaybackStarted ( Date . now ( ) ) ;
124+ expect ( audioOut . firstFrameFut . done ) . toBe ( false ) ;
125+
126+ const taskPromise = task . result ;
127+ await vi . advanceTimersByTimeAsync ( 600 ) ;
128+ await taskPromise ;
129+
130+ vi . useRealTimers ( ) ;
131+
132+ expect ( audioOutput . capturedFrames . length ) . toBe ( 0 ) ;
133+ expect ( audioOut . firstFrameFut . rejected ) . toBe ( true ) ;
134+ } ) ;
135+
136+ it ( 'resamples a rate-mismatched frame even after a stray PLAYBACK_STARTED' , async ( ) => {
137+ // Output is 24kHz; frames are 16kHz and must be resampled regardless of any
138+ // stray PLAYBACK_STARTED resolving firstFrameFut early.
139+ const stream = new ReadableStream < AudioFrame > ( {
140+ start ( controller ) {
141+ controller . enqueue ( createSilentFrame ( 16000 ) ) ;
142+ controller . enqueue ( createSilentFrame ( 16000 ) ) ;
143+ controller . close ( ) ;
144+ } ,
145+ } ) ;
146+
147+ const audioOutput = new MockAudioOutput ( ) ;
148+ const controller = new AbortController ( ) ;
149+ const [ task , audioOut ] = performAudioForwarding ( stream , audioOutput , controller ) ;
150+
151+ audioOutput . onPlaybackStarted ( Date . now ( ) ) ;
152+
153+ await task . result ;
154+
155+ expect ( audioOut . firstFrameFut . done ) . toBe ( true ) ;
156+ expect ( audioOutput . capturedFrames . length ) . toBeGreaterThan ( 0 ) ;
157+ for ( const f of audioOutput . capturedFrames ) {
158+ expect ( f . sampleRate ) . toBe ( 24000 ) ;
159+ }
160+ } ) ;
161+
108162 it ( 'performTTSInference completes when TTS node returns stalled stream' , async ( ) => {
109163 const stalledTtsStream = new ReadableStream < AudioFrame > ( {
110164 start ( controller ) {
0 commit comments