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:
+24
-13
@@ -9279,7 +9279,15 @@ function onTtsEngineChange(engine) {
|
|||||||
/** Populate voice selector from Web Speech API. Called on settings load and on voiceschanged. */
|
/** Populate voice selector from Web Speech API. Called on settings load and on voiceschanged. */
|
||||||
function _initBrowserTtsVoices(selectedVoice) {
|
function _initBrowserTtsVoices(selectedVoice) {
|
||||||
const sel = document.getElementById('setting-tts-voice');
|
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 populate = () => {
|
||||||
const voices = window.speechSynthesis.getVoices();
|
const voices = window.speechSynthesis.getVoices();
|
||||||
@@ -9291,7 +9299,7 @@ function _initBrowserTtsVoices(selectedVoice) {
|
|||||||
sel.innerHTML = sorted.map(v =>
|
sel.innerHTML = sorted.map(v =>
|
||||||
`<option value="${v.name}" ${v.name === selectedVoice ? 'selected' : ''}>${v.name} (${v.lang})${v.localService ? '' : ' ☁️'}</option>`
|
`<option value="${v.name}" ${v.name === selectedVoice ? 'selected' : ''}>${v.name} (${v.lang})${v.localService ? '' : ' ☁️'}</option>`
|
||||||
).join('');
|
).join('');
|
||||||
// Auto-select Paola if no preference and it exists
|
// Auto-select first Italian voice if no preference set
|
||||||
if (!selectedVoice) {
|
if (!selectedVoice) {
|
||||||
const paola = sorted.find(v => v.name === 'Paola');
|
const paola = sorted.find(v => v.name === 'Paola');
|
||||||
const firstIt = sorted.find(v => v.lang.startsWith('it'));
|
const firstIt = sorted.find(v => v.lang.startsWith('it'));
|
||||||
@@ -9301,23 +9309,26 @@ function _initBrowserTtsVoices(selectedVoice) {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Some browsers (Chrome) load voices async: onvoiceschanged fires once they're ready.
|
// Try immediately (voices already cached from previous call)
|
||||||
// But if voices are already loaded, onvoiceschanged never fires → try immediately too.
|
if (populate()) return;
|
||||||
// Additionally, onvoiceschanged may have already fired before we assign the handler
|
|
||||||
// (race condition), so we also poll with short retries as fallback.
|
// onvoiceschanged fires in Firefox / some Chrome versions
|
||||||
if (!populate()) {
|
|
||||||
// Register the event handler for browsers that fire it
|
|
||||||
if (window.speechSynthesis.onvoiceschanged !== undefined) {
|
|
||||||
window.speechSynthesis.onvoiceschanged = () => { populate(); };
|
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;
|
let tries = 0;
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
tries++;
|
tries++;
|
||||||
if (populate() || tries >= 20) clearInterval(interval);
|
if (populate()) {
|
||||||
}, 200);
|
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). */
|
/** Speak text using the browser Web Speech API (offline). */
|
||||||
function _speakBrowser(text) {
|
function _speakBrowser(text) {
|
||||||
|
|||||||
+5
-2
@@ -930,10 +930,13 @@
|
|||||||
<div id="tts-browser-section">
|
<div id="tts-browser-section">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>🗣️ Voce</label>
|
<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>
|
<option value="">— Caricamento voci… —</option>
|
||||||
</select>
|
</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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>⚡ Velocità: <span id="tts-rate-label">1.0</span>×</label>
|
<label>⚡ Velocità: <span id="tts-rate-label">1.0</span>×</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user