Why
VocableTextToSpeech.speak() performs blocking binder IPC into the TTS engine process on the caller's thread — setLanguage() (up to twice, with locale fallback) plus the per-call tts.voices enumeration that powers #632's live default-voice fallback — and every production call site invokes it from the main thread. The calls can take tens to hundreds of milliseconds, during which the Choreographer stops ticking: the gaze cursor visibly freezes and gaze input effectively pauses on every spoken selection. This is currently masked by GazeClickable's post-selection hold (the button is deliberately frozen green during the same window), which is how it went unnoticed — it was exposed during #678's smoothing work when that hold was temporarily disabled.
Scope
Move speak()'s blocking engine work off the main thread without changing spoken behavior.
Acceptance Criteria
Out of Scope
- Whether the post-selection hold itself should remain coupled to TTS completion (a confirmed iOS/Android behavior difference — product question, not yet tracked)
- Any debug flag for skipping the selection hold (not yet tracked)
Links
Why
VocableTextToSpeech.speak()performs blocking binder IPC into the TTS engine process on the caller's thread —setLanguage()(up to twice, with locale fallback) plus the per-calltts.voicesenumeration that powers #632's live default-voice fallback — and every production call site invokes it from the main thread. The calls can take tens to hundreds of milliseconds, during which the Choreographer stops ticking: the gaze cursor visibly freezes and gaze input effectively pauses on every spoken selection. This is currently masked byGazeClickable's post-selection hold (the button is deliberately frozen green during the same window), which is how it went unnoticed — it was exposed during #678's smoothing work when that hold was temporarily disabled.Scope
Move
speak()'s blocking engine work off the main thread without changing spoken behavior.Acceptance Criteria
speak()performs no blocking TTS engine calls (setLanguage, voices enumeration, speak) on the caller's threadspeak()calls cannot interleave engine-state mutations (single dedicated thread, not a pool)isSpeakingFlowtransitionsOut of Scope
Links