Fix Edge Browser TTS Compatibility Edge-compatible fallback for empty Web Speech voice lists

d511875db99ebd441563ec5e057e6dc102dbf345

YunZLu <130174259+YunZLu@users.noreply.github.com>

Signed
1 files changed, +94 -18Ignore whitespace
public/scripts/extensions/tts/system.js+94 -18
@@ -79,6 +79,10 @@ class SystemTtsProvider {
7979 // Config //
8080 //########//
8181
82+ // Static constants for the simulated default voice
83+ static BROWSER_DEFAULT_VOICE_ID = '__browser_default__';
84+ static BROWSER_DEFAULT_VOICE_NAME = 'System Default Voice';
85+
8286 settings;
8387 ready = false;
8488 voices = [];
@@ -168,51 +172,123 @@ class SystemTtsProvider {
168172 //#################//
169173 fetchTtsVoiceObjects() {
170174 if (!('speechSynthesis' in window)) {
171- return [];
175+ // Browser doesn't support speech synthesis
176+ return Promise.resolve([]);
172177 }
173178
174179 return new Promise((resolve) => {
180+ // Use a minimal timeout to allow the voice list to potentially populate
175181 setTimeout(() => {
176182 constlet voices = speechSynthesis.getVoices();
177- .getVoices()
183+
178- .sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))
184+ if (voices.length === 0) {
179- .map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));
185+ // If no voices returned (e.g., Edge on first load), provide a default option
180-
186+ console.warn('SystemTTS: getVoices() returned empty list. Providing browser default option.');
181- resolve(voices);
187+ const defaultVoice = {
182- }, 1);
188+ name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
189+ voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
190+ preview_url: false,
191+ // Try to guess the browser's default language
192+ lang: navigator.language || 'en-US',
193+ };
194+ resolve([defaultVoice]);
195+ } else {
196+ // If voices are available, map them as before
197+ const mappedVoices = voices
198+ .sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))
199+ .map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));
200+ resolve(mappedVoices);
201+ }
202+ }, 50); // Increased timeout slightly just in case it helps voice population on some browsers
183203 });
184204 }
185205
206+
186207 previewTtsVoice(voiceId) {
187208 if (!('speechSynthesis' in window)) {
188209 throw new Error('Speech synthesis API is not supported'); // Keep Error type for consistency
189210 }
190211
191- const voice = speechSynthesis.getVoices().find(x => x.voiceURI === voiceId);
212+ let voice = null;
192-
213+ // Check if the requested voice is NOT the browser default
193214 if (voiceId !voice== SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID) {
194- throw `TTS Voice id ${voiceId} not found`;
215+ const voices = speechSynthesis.getVoices();
216+ // Try to find the actual voice
217+ voice = voices.find(x => x.voiceURI === voiceId);
218+
219+ if (!voice && voices.length > 0) {
220+ // If voices are loaded but the specific ID wasn't found, log a warning
221+ console.warn(`SystemTTS Preview: Voice ID "${voiceId}" not found among available voices. Using browser default.`);
222+ // Fallback to default (voice remains null)
223+ } else if (!voice && voices.length === 0) {
224+ // If no voices are loaded at all, we expect to use default
225+ console.warn('SystemTTS Preview: Voice list is empty. Using browser default.');
226+ // Fallback to default (voice remains null)
227+ }
228+ } else {
229+ console.log('SystemTTS Preview: Using browser default voice as requested.');
230+ // Use default (voice remains null)
195231 }
196232
197233 speechSynthesis.cancel(); // Stop any previous speech
198- const text = getPreviewString(voice.lang);
234+ // Use the language from the found voice if available, otherwise default to 'en-US' or browser lang for the preview text
235+ const langForPreview = voice ? voice.lang : (navigator.language || 'en-US');
236+ const text = getPreviewString(langForPreview);
199237 const utterance = new SpeechSynthesisUtterance(text);
200- utterance.voice = voice;
238+
239+ // Only set the voice if we found a specific one and it wasn't the default request
240+ if (voice) {
241+ utterance.voice = voice;
242+ }
243+ // Otherwise, utterance.voice remains null/undefined, causing the browser to use its default
244+
201245 utterance.rate = this.settings.rate || 1;
202246 utterance.pitch = this.settings.pitch || 1;
247+
248+ // Add error handling for the speech itself
249+ utterance.onerror = (event) => {
250+ console.error(`SystemTTS Preview Error: ${event.error}`, event);
251+ // Potentially notify the user here
252+ };
253+
203254 speechSynthesis.speak(utterance);
204255 }
205256
206257 async getVoice(voiceName) {
207258 if (!('speechSynthesis' in window)) {
208- return { voice_id: null };
259+ // Return a predictable null-like structure if API not supported
260+ return { voice_id: null, name: 'API Not Supported' };
209261 }
210262
263+ // Check if the requested name is the browser default placeholder
264+ if (voiceName === SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME) {
265+ return {
266+ voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
267+ name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
268+ };
269+ }
270+
271+ // Attempt to get voices, might be async
272+ // Note: This relies on voices potentially being populated by now.
273+ // A more robust approach might involve re-calling fetchTtsVoiceObjects if needed,
274+ // but sticking to minimal changes based on original code structure.
211275 const voices = speechSynthesis.getVoices();
276+
277+ if (voices.length === 0) {
278+ // If voices are still empty, we can't find any specific name
279+ console.warn(`SystemTTS getVoice: Voice list empty, cannot find "${voiceName}". Falling back to browser default ID.`);
280+ // Return the default placeholder as a fallback in this edge case
281+ return {
282+ voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
283+ name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
284+ };
285+ }
286+
212287 const match = voices.find(x => x.name == voiceName);
213288
214289 if (!match) {
215- throw `TTS Voice name ${voiceName} not found`;
290+ // If voices are loaded but name not found, throw error as before
291+ throw new Error(`SystemTTS getVoice: TTS Voice name "${voiceName}" not found`);
216292 }
217293
218294 return { voice_id: match.voiceURI, name: match.name };