@@ -90,7 +90,11 @@ export abstract class TTS extends (EventEmitter as new () => TypedEmitter<TTSCal
9090 /**
9191 * Receives text and returns synthesis in the form of a {@link ChunkedStream}
9292 */
93- abstract synthesize ( text : string ) : ChunkedStream ;
93+ abstract synthesize (
94+ text : string ,
95+ connOptions ?: APIConnectOptions ,
96+ abortSignal ?: AbortSignal ,
97+ ) : ChunkedStream ;
9498
9599 /**
96100 * Returns a {@link SynthesizeStream} that can be used to push text and receive audio data
@@ -131,30 +135,33 @@ export abstract class SynthesizeStream
131135 SynthesizedAudio | typeof SynthesizeStream . END_OF_STREAM
132136 > ( ) ;
133137 protected closed = false ;
134- abstract label : string ;
135- #tts: TTS ;
136- #metricsPendingTexts: string [ ] = [ ] ;
137- #metricsText = '' ;
138- #monitorMetricsTask?: Promise < void > ;
139- private _connOptions : APIConnectOptions ;
138+ protected connOptions : APIConnectOptions ;
140139 protected abortController = new AbortController ( ) ;
141- #ttsRequestSpan?: Span ;
142140
143141 private deferredInputStream : DeferredReadableStream <
144142 string | typeof SynthesizeStream . FLUSH_SENTINEL
145143 > ;
146144 private logger = log ( ) ;
147145
146+ abstract label : string ;
147+
148+ #tts: TTS ;
149+ #metricsPendingTexts: string [ ] = [ ] ;
150+ #metricsText = '' ;
151+ #monitorMetricsTask?: Promise < void > ;
152+ #ttsRequestSpan?: Span ;
153+
148154 constructor ( tts : TTS , connOptions : APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS ) {
149155 this . #tts = tts ;
150- this . _connOptions = connOptions ;
156+ this . connOptions = connOptions ;
151157 this . deferredInputStream = new DeferredReadableStream ( ) ;
152158 this . pumpInput ( ) ;
159+
153160 this . abortController . signal . addEventListener ( 'abort' , ( ) => {
154161 this . deferredInputStream . detachSource ( ) ;
155162 // TODO (AJS-36) clean this up when we refactor with streams
156- this . input . close ( ) ;
157- this . output . close ( ) ;
163+ if ( ! this . input . closed ) this . input . close ( ) ;
164+ if ( ! this . output . closed ) this . output . close ( ) ;
158165 this . closed = true ;
159166 } ) ;
160167
@@ -172,7 +179,7 @@ export abstract class SynthesizeStream
172179 [ traceTypes . ATTR_TTS_LABEL ] : this . #tts. label ,
173180 } ) ;
174181
175- for ( let i = 0 ; i < this . _connOptions . maxRetry + 1 ; i ++ ) {
182+ for ( let i = 0 ; i < this . connOptions . maxRetry + 1 ; i ++ ) {
176183 try {
177184 return await tracer . startActiveSpan (
178185 async ( attemptSpan ) => {
@@ -188,15 +195,15 @@ export abstract class SynthesizeStream
188195 ) ;
189196 } catch ( error ) {
190197 if ( error instanceof APIError ) {
191- const retryInterval = intervalForRetry ( this . _connOptions , i ) ;
198+ const retryInterval = intervalForRetry ( this . connOptions , i ) ;
192199
193- if ( this . _connOptions . maxRetry === 0 || ! error . retryable ) {
200+ if ( this . connOptions . maxRetry === 0 || ! error . retryable ) {
194201 this . emitError ( { error, recoverable : false } ) ;
195202 throw error ;
196- } else if ( i === this . _connOptions . maxRetry ) {
203+ } else if ( i === this . connOptions . maxRetry ) {
197204 this . emitError ( { error, recoverable : false } ) ;
198205 throw new APIConnectionError ( {
199- message : `failed to generate TTS completion after ${ this . _connOptions . maxRetry + 1 } attempts` ,
206+ message : `failed to generate TTS completion after ${ this . connOptions . maxRetry + 1 } attempts` ,
200207 options : { retryable : false } ,
201208 } ) ;
202209 } else {
@@ -380,6 +387,10 @@ export abstract class SynthesizeStream
380387 return this . output . next ( ) ;
381388 }
382389
390+ get abortSignal ( ) : AbortSignal {
391+ return this . abortController . signal ;
392+ }
393+
383394 /** Close both the input and output of the TTS stream */
384395 close ( ) {
385396 this . abortController . abort ( ) ;
@@ -415,15 +426,22 @@ export abstract class ChunkedStream implements AsyncIterableIterator<Synthesized
415426 private _connOptions : APIConnectOptions ;
416427 private logger = log ( ) ;
417428
429+ protected abortController = new AbortController ( ) ;
430+
418431 constructor (
419432 text : string ,
420433 tts : TTS ,
421434 connOptions : APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS ,
435+ abortSignal ?: AbortSignal ,
422436 ) {
423437 this . #text = text ;
424438 this . #tts = tts ;
425439 this . _connOptions = connOptions ;
426440
441+ if ( abortSignal ) {
442+ abortSignal . addEventListener ( 'abort' , ( ) => this . abortController . abort ( ) , { once : true } ) ;
443+ }
444+
427445 this . monitorMetrics ( ) ;
428446
429447 // this is a hack to immitate asyncio.create_task so that mainTask
@@ -510,6 +528,10 @@ export abstract class ChunkedStream implements AsyncIterableIterator<Synthesized
510528 return this . #text;
511529 }
512530
531+ get abortSignal ( ) : AbortSignal {
532+ return this . abortController . signal ;
533+ }
534+
513535 protected async monitorMetrics ( ) {
514536 const startTime = process . hrtime . bigint ( ) ;
515537 let audioDurationMs = 0 ;
@@ -564,8 +586,9 @@ export abstract class ChunkedStream implements AsyncIterableIterator<Synthesized
564586
565587 /** Close both the input and output of the TTS stream */
566588 close ( ) {
567- this . queue . close ( ) ;
568- this . output . close ( ) ;
589+ if ( ! this . queue . closed ) this . queue . close ( ) ;
590+ if ( ! this . output . closed ) this . output . close ( ) ;
591+ if ( ! this . abortController . signal . aborted ) this . abortController . abort ( ) ;
569592 this . closed = true ;
570593 }
571594
0 commit comments