synthesizeStream() returns a Flow<TTSOutput>, one emission per audio chunk. It is not a
suspend function and takes no callback.
TTSOutput, so sample_rate, audio_format, chunk_index, is_final,
and duration_ms are all available per chunk. Read the sample rate from the first chunk rather
than assuming one.
Cancellation
Cancelling the collector closes the flow, which sets an internal cancellation flag observed by the native chunk callback and then calls the native stop. The synthesizer stops emitting rather than running to completion in the background.stopSynthesis() stops synthesis explicitly:
Dispatchers.IO inside the SDK, so collecting from the main thread does
not block the UI.
Silent completion
The flow closes without emitting when the SDK is not initialized or no speech-synthesis model is loaded. It does not throw. Check the loaded model first if you need to distinguish “no voice” from “nothing to say”:Player
synthesizeStream() produces buffers; playback is yours. A single consumer coroutine pulling off a
channel keeps chunks in order.
Speaking an LLM response as it arrives
A useful pattern for voice UIs is to speak sentence by sentence while the model is still generating, rather than waiting for the whole reply. Split the token stream on sentence boundaries and hand each completed sentence tospeak(), which suspends until its own audio finishes, so
sequential calls do not overlap.