Merge pull request #3868 from YunZLu/staging Fix Edge Browser TTS Compatibility

586ce36167f4faee2620d7fac209494c75e11ae1

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
1 files changed, +63 -14Showing whitespace changes
public/scripts/extensions/tts/system.js+63 -14
@@ -79,6 +79,10 @@ class SystemTtsProvider {
79 // Config //79 // Config //
80 //########//80 //########//
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
82 settings;86 settings;
83 ready = false;87 ready = false;
84 voices = [];88 voices = [];
@@ -168,51 +172,97 @@ class SystemTtsProvider {
168 //#################//172 //#################//
169 fetchTtsVoiceObjects() {173 fetchTtsVoiceObjects() {
170 if (!('speechSynthesis' in window)) {174 if (!('speechSynthesis' in window)) {
171 return [];175 return Promise.resolve([]);
172 }176 }
173177
174 return new Promise((resolve) => {178 return new Promise((resolve) => {
175 setTimeout(() => {179 setTimeout(() => {
176 const voices = speechSynthesis180 let voices = speechSynthesis.getVoices();
177 .getVoices()181
182 if (voices.length === 0) {
183 // Edge compat: Provide default when voices empty
184 console.warn('SystemTTS: getVoices() returned empty list. Providing browser default option.');
185 const defaultVoice = {
186 name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
187 voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
188 preview_url: false,
189 lang: navigator.language || 'en-US',
190 };
191 resolve([defaultVoice]);
192 } else {
193 const mappedVoices = voices
178 .sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))194 .sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))
179 .map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));195 .map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));
180196 resolve(mappedVoices);
181 resolve(voices);197 }
182 }, 1);198 }, 50);
183 });199 });
184 }200 }
185201
186 previewTtsVoice(voiceId) {202 previewTtsVoice(voiceId) {
187 if (!('speechSynthesis' in window)) {203 if (!('speechSynthesis' in window)) {
188 throw 'Speech synthesis API is not supported';204 throw new Error('Speech synthesis API is not supported');
189 }205 }
190206
191 const voice = speechSynthesis.getVoices().find(x => x.voiceURI === voiceId);207 let voice = null;
208 if (voiceId !== SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID) {
209 const voices = speechSynthesis.getVoices();
210 voice = voices.find(x => x.voiceURI === voiceId);
192211
193 if (!voice) {212 if (!voice && voices.length > 0) {
194 throw `TTS Voice id ${voiceId} not found`;213 console.warn(`SystemTTS Preview: Voice ID "${voiceId}" not found among available voices. Using browser default.`);
214 } else if (!voice && voices.length === 0) {
215 console.warn('SystemTTS Preview: Voice list is empty. Using browser default.');
216 }
217 } else {
218 console.log('SystemTTS Preview: Using browser default voice as requested.');
195 }219 }
196220
197 speechSynthesis.cancel();221 speechSynthesis.cancel();
198 const text = getPreviewString(voice.lang);222 const langForPreview = voice ? voice.lang : (navigator.language || 'en-US');
223 const text = getPreviewString(langForPreview);
199 const utterance = new SpeechSynthesisUtterance(text);224 const utterance = new SpeechSynthesisUtterance(text);
225
226 if (voice) {
200 utterance.voice = voice;227 utterance.voice = voice;
228 }
229
201 utterance.rate = this.settings.rate || 1;230 utterance.rate = this.settings.rate || 1;
202 utterance.pitch = this.settings.pitch || 1;231 utterance.pitch = this.settings.pitch || 1;
232
233 utterance.onerror = (event) => {
234 console.error(`SystemTTS Preview Error: ${event.error}`, event);
235 };
236
203 speechSynthesis.speak(utterance);237 speechSynthesis.speak(utterance);
204 }238 }
205239
206 async getVoice(voiceName) {240 async getVoice(voiceName) {
207 if (!('speechSynthesis' in window)) {241 if (!('speechSynthesis' in window)) {
208 return { voice_id: null };242 return { voice_id: null, name: 'API Not Supported' };
243 }
244
245 if (voiceName === SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME) {
246 return {
247 voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
248 name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
249 };
209 }250 }
210251
211 const voices = speechSynthesis.getVoices();252 const voices = speechSynthesis.getVoices();
253
254 if (voices.length === 0) {
255 console.warn('SystemTTS: Empty voice list, using default fallback');
256 return {
257 voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
258 name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
259 };
260 }
261
212 const match = voices.find(x => x.name == voiceName);262 const match = voices.find(x => x.name == voiceName);
213263
214 if (!match) {264 if (!match) {
215 throw `TTS Voice name ${voiceName} not found`;265 throw new Error(`SystemTTS getVoice: TTS Voice name "${voiceName}" not found`);
216 }266 }
217267
218 return { voice_id: match.voiceURI, name: match.name };268 return { voice_id: match.voiceURI, name: match.name };
@@ -237,7 +287,6 @@ class SystemTtsProvider {
237 speechUtteranceChunker(utterance, {287 speechUtteranceChunker(utterance, {
238 chunkLength: 200,288 chunkLength: 200,
239 }, function () {289 }, function () {
240 //some code to execute when done
241 resolve(silence);290 resolve(silence);
242 console.log('System TTS done');291 console.log('System TTS done');
243 });292 });