22// honoring random/sequence; fall back to speaking by language if no preferred
33// IDs are currently available.
44
5- import { createVoiceTTS } from "@/util/speak" ;
5+ import { createVoiceTTS , createVoiceTTSConcurrent } from "@/util/speak" ;
66import { useSettingsStore } from "@/store/settings" ;
77import { getVoicesCached } from "@/util/tts-voices" ;
88
9- export async function speakWithStackPrefs ( uiCode : string , text : string , rate : number ) {
9+ /**
10+ * Helper to get the voice ID to use based on stack preferences.
11+ * Returns undefined if no preferred voice is available.
12+ */
13+ async function getPreferredVoiceId ( uiCode : string ) : Promise < string | undefined > {
1014 const state = useSettingsStore . getState ( ) ;
1115 const { voicePrefs, nextVoiceId } = state ;
1216
@@ -20,28 +24,35 @@ export async function speakWithStackPrefs(uiCode: string, text: string, rate: nu
2024 const baseIds = basePref ?. ids ?? [ ] ;
2125 const mergedPrefIds = Array . from ( new Set ( [ ...exactIds , ...baseIds ] ) ) ;
2226
23- // If there are no prefs at all, just speak with language
24- // console.log("mergedPrefIds", mergedPrefIds);
2527 if ( mergedPrefIds . length === 0 ) {
26- await createVoiceTTS ( uiCode ) ( text , rate ) ;
27- return ;
28+ return undefined ;
2829 }
2930
3031 // Validate against currently available voices (native first, browser fallback)
3132 const available = await getVoicesCached ( { maxAgeMs : 30_000 } ) ;
3233 const availableIds = new Set ( available . map ( ( v ) => v . id ) ) ;
3334 const pool = mergedPrefIds . filter ( ( id ) => availableIds . has ( id ) ) ;
3435
35- // console.warn(pool)
3636 if ( pool . length === 0 ) {
37- // Preferred IDs aren’t installed/available right now; speak by language
38- await createVoiceTTS ( uiCode ) ( text , rate ) ;
39- return ;
37+ return undefined ;
4038 }
4139
42- // Use the exact entry’ s mode if present; otherwise base
40+ // Use the exact entry' s mode if present; otherwise base
4341 const langKeyForMode = exactPref ? uiCode : base ;
44- const chosenId = nextVoiceId ( langKeyForMode , pool ) ;
42+ return nextVoiceId ( langKeyForMode , pool ) ;
43+ }
4544
45+ export async function speakWithStackPrefs ( uiCode : string , text : string , rate : number ) {
46+ const chosenId = await getPreferredVoiceId ( uiCode ) ;
4647 await createVoiceTTS ( uiCode ) ( text , rate , chosenId ) ;
4748}
49+
50+ /**
51+ * Speak concurrently using the synthesizer pool (allows overlapping audio on macOS/iOS).
52+ * On Android, falls back to sequential playback due to platform limitations.
53+ * Returns an utterance ID for tracking completion.
54+ */
55+ export async function speakConcurrentWithStackPrefs ( uiCode : string , text : string , rate : number ) : Promise < string > {
56+ const chosenId = await getPreferredVoiceId ( uiCode ) ;
57+ return await createVoiceTTSConcurrent ( uiCode ) ( text , rate , chosenId ) ;
58+ }
0 commit comments