3.4 KiB
3.4 KiB
TTS (Text-to-Speech) Fixes Reference
Architecture
Key Components
TTSController(src/services/tts/TTSController.ts) - Core state machineEdgeTTSClient(src/services/tts/EdgeTTSClient.ts) - Edge TTS provideruseTTSControlhook (src/app/reader/hooks/useTTSControl.ts) - React integrationuseTTSMediaSessionhook (src/app/reader/hooks/useTTSMediaSession.ts) - Media controls
Section-Aware TTS Model
TTS tracks its own section independently from the view via #ttsSectionIndex:
#initTTSForSection()- Creates TTS document for a section without changing the view#initTTSForNextSection()/#initTTSForPrevSection()- Navigate TTS across sections#getHighlighter()- Only returns highlighter if view section matches TTS sectiononSectionChangecallback - Notifies UI when TTS crosses section boundary- Highlights use CFI strings (not raw Range objects) for cross-section compatibility
State Management Pitfalls
-
#ttsSectionIndexmust match view section for highlights to work- If
-1, all highlight calls are suppressed shutdown()sets it to-1but must also null outthis.view.tts
- If
-
Guards/Refs that block re-entry:
- The old
ttsOnRefguard blocked TTS restart from annotations (removed in #3292) view.ttsreference surviving shutdown blocked re-initialization (#3400)
- The old
-
Timeouts that fire after pause:
- Edge TTS had a safety timeout that advanced sentences even when paused (#3244)
- Solution: removed the entire
ontimeupdatesafety timeout mechanism
Fix History
| Issue | Problem | Root Cause | Fix |
|---|---|---|---|
| #3100 | TTS scrolls too far | TTS coupled to view section | Added #ttsSectionIndex, "Back to TTS Location" button |
| #3198 | TTS doesn't follow to next section | No onSectionChange callback |
Added section change notification, extracted hooks |
| #3244 | Paused TTS advances | Safety timeout fires after pause | Removed ontimeupdate timeout mechanism |
| #3291 | TTS fails without lang attribute | Invalid SSML from missing lang | Set lang/xml:lang on html element from ttsLang |
| #3292 | Can't restart TTS from annotation | ttsOnRef blocks re-entry |
Removed the guard ref entirely |
| #3400 | TTS highlight stops after restart | view.tts not nulled on shutdown |
Added this.view.tts = null in shutdown() |
| #4033 | Voice count flip-flops within one book (17↔5) | All 3 clients filtered voices by full locale (v.lang.startsWith(locale)); panel lang refreshes from the speaking mark (getSpeakingLang), and books mix region variants — Standard Ebooks boilerplate is en-US (17 Edge voices), body en-GB (5 Edge voices) |
PR #4565: filter by primary lang (isSameLang) in Edge/Web/Native getVoices; new TTSUtils.sortVoicesPreferLocaleFunc(locale) keeps exact-locale voices first so getVoiceIdFromLang default stays region-aware. Also fixed zh-Hans → empty Edge list |
Debugging TTS Issues
- TTS doesn't start: Check
#initTTSForSection()- doesview.tts.doc === docshortcut early? - No highlights: Check
#ttsSectionIndexmatches view's section index - Advances when paused: Look for setTimeout/timer callbacks that bypass pause state
- Can't restart: Check for refs/guards that prevent re-entry into speak handlers
- Fails on some chapters: Check if chapter has lang attribute and XHTML namespace
- SSML errors: Check
src/utils/ssml.tsfor proper namespace/lang handling