fix: TTS voices — retry 10s, message on failure, refresh button

- Retry loop extended to 10s (50×200ms) for slow Android WebViews
- Show 'Nessuna voce disponibile' after timeout instead of infinite loader
- Show 'Voce non supportata dal browser' if speechSynthesis missing
- Reset to loading state on each settings open (fixes stale empty select)
- Add refresh button ↺ to force-reload voices manually
This commit is contained in:
dadaloop82
2026-04-26 17:28:05 +00:00
parent f57ad4b330
commit 37299e60c9
2 changed files with 35 additions and 21 deletions
+24 -13
View File
@@ -9279,7 +9279,15 @@ function onTtsEngineChange(engine) {
/** Populate voice selector from Web Speech API. Called on settings load and on voiceschanged. */
function _initBrowserTtsVoices(selectedVoice) {
const sel = document.getElementById('setting-tts-voice');
if (!sel || !window.speechSynthesis) return;
if (!sel) return;
if (!window.speechSynthesis) {
sel.innerHTML = '<option value="">— Voce non supportata dal browser —</option>';
return;
}
// Reset to loading state each time (settings page may be re-opened)
sel.innerHTML = '<option value="">— Caricamento voci… —</option>';
const populate = () => {
const voices = window.speechSynthesis.getVoices();
@@ -9291,7 +9299,7 @@ function _initBrowserTtsVoices(selectedVoice) {
sel.innerHTML = sorted.map(v =>
`<option value="${v.name}" ${v.name === selectedVoice ? 'selected' : ''}>${v.name} (${v.lang})${v.localService ? '' : ' ☁️'}</option>`
).join('');
// Auto-select Paola if no preference and it exists
// Auto-select first Italian voice if no preference set
if (!selectedVoice) {
const paola = sorted.find(v => v.name === 'Paola');
const firstIt = sorted.find(v => v.lang.startsWith('it'));
@@ -9301,22 +9309,25 @@ function _initBrowserTtsVoices(selectedVoice) {
return true;
};
// Some browsers (Chrome) load voices async: onvoiceschanged fires once they're ready.
// But if voices are already loaded, onvoiceschanged never fires → try immediately too.
// Additionally, onvoiceschanged may have already fired before we assign the handler
// (race condition), so we also poll with short retries as fallback.
if (!populate()) {
// Register the event handler for browsers that fire it
if (window.speechSynthesis.onvoiceschanged !== undefined) {
// Try immediately (voices already cached from previous call)
if (populate()) return;
// onvoiceschanged fires in Firefox / some Chrome versions
window.speechSynthesis.onvoiceschanged = () => { populate(); };
}
// Fallback retry loop: try every 200ms for up to 4 seconds
// Polling fallback: Chrome/WebView loads voices async (up to ~3s on desktop, longer on Android)
let tries = 0;
const interval = setInterval(() => {
tries++;
if (populate() || tries >= 20) clearInterval(interval);
}, 200);
if (populate()) {
clearInterval(interval);
} else if (tries >= 50) { // 50 × 200ms = 10s
clearInterval(interval);
if (!window.speechSynthesis.getVoices().length) {
sel.innerHTML = '<option value="">— Nessuna voce disponibile su questo dispositivo —</option>';
}
}
}, 200);
}
/** Speak text using the browser Web Speech API (offline). */
+5 -2
View File
@@ -930,10 +930,13 @@
<div id="tts-browser-section">
<div class="form-group">
<label>🗣️ Voce</label>
<select id="setting-tts-voice" class="form-input">
<div style="display:flex;gap:8px;align-items:center">
<select id="setting-tts-voice" class="form-input" style="flex:1">
<option value="">— Caricamento voci… —</option>
</select>
<p class="settings-hint">Le voci disponibili dipendono dal sistema operativo e dal browser. Su macOS/iOS è disponibile la voce <strong>Paola</strong> (italiano).</p>
<button type="button" class="btn btn-secondary" style="padding:8px 12px;white-space:nowrap;flex-shrink:0" onclick="_initBrowserTtsVoices(document.getElementById('setting-tts-voice').value)"></button>
</div>
<p class="settings-hint">Le voci disponibili dipendono dal sistema operativo e dal browser. Su macOS/iOS è disponibile la voce <strong>Paola</strong> (italiano). Premi ↺ se la lista non si carica.</p>
</div>
<div class="form-group">
<label>⚡ Velocità: <span id="tts-rate-label">1.0</span>×</label>