diff --git a/public/app.js b/public/app.js index 037c477..6990081 100644 --- a/public/app.js +++ b/public/app.js @@ -775,27 +775,14 @@ const initApp = () => { // TTS — Speech Synthesis nativa (voz do navegador) // ========================================================================== - let cachedVoices = []; + let currentSpeakingBtn = null; - const loadVoices = () => { - const voices = window.speechSynthesis.getVoices(); - if (voices.length > 0) { - cachedVoices = voices; - console.log('Vozes pt-BR disponíveis:', cachedVoices.filter(v => v.lang.startsWith('pt')).map(v => v.name)); - } else { - window.speechSynthesis.addEventListener('voiceschanged', () => { - cachedVoices = window.speechSynthesis.getVoices(); - console.log('Vozes pt-BR disponíveis:', cachedVoices.filter(v => v.lang.startsWith('pt')).map(v => v.name)); - }, { once: true }); - } - }; - loadVoices(); - - // Selecionar melhor voz pt-BR disponível no dispositivo + // Selecionar melhor voz pt-BR disponível no dispositivo de forma dinâmica const getBestVoice = () => { - const ptBR = cachedVoices.filter(v => v.lang === 'pt-BR' || v.lang === 'pt_BR'); + const voices = window.speechSynthesis.getVoices(); + const ptBR = voices.filter(v => v.lang === 'pt-BR' || v.lang === 'pt_BR'); if (ptBR.length === 0) { - const pt = cachedVoices.filter(v => v.lang.startsWith('pt')); + const pt = voices.filter(v => v.lang.startsWith('pt')); return pt[0] || null; } @@ -819,19 +806,34 @@ const initApp = () => { return ptBR[0]; }; + const resetSpeakBtnState = (btn) => { + if (btn) { + btn.classList.remove('speaking'); + const span = btn.querySelector('span'); + if (span) span.textContent = 'Ouvir'; + } + }; + // Falar texto com TTS nativa const speakText = (text, btn) => { - // Se já está falando, parar - if (window.speechSynthesis.speaking) { + if (!text) return; + + // Se clicar no botão que já está ativamente falando, parar a reprodução + if (window.speechSynthesis.speaking && currentSpeakingBtn === btn) { window.speechSynthesis.cancel(); - if (btn) { - btn.classList.remove('speaking'); - const span = btn.querySelector('span'); - if (span) span.textContent = 'Ouvir'; - } + resetSpeakBtnState(btn); + currentSpeakingBtn = null; return; } + // Se houver outra fala em andamento ou fila travada, limpa tudo e reseta o botão antigo + window.speechSynthesis.cancel(); + if (currentSpeakingBtn) { + resetSpeakBtnState(currentSpeakingBtn); + } + + currentSpeakingBtn = btn; + // Limpar markdown antes de falar const clean = text .replace(/```[\s\S]*?```/g, '') @@ -842,7 +844,10 @@ const initApp = () => { .replace(/\s{2,}/g, ' ') .trim(); - if (!clean) return; + if (!clean) { + currentSpeakingBtn = null; + return; + } const utterance = new SpeechSynthesisUtterance(clean); utterance.lang = 'pt-BR'; @@ -860,14 +865,17 @@ const initApp = () => { } }; - utterance.onend = utterance.onerror = () => { - if (btn) { - btn.classList.remove('speaking'); - const span = btn.querySelector('span'); - if (span) span.textContent = 'Ouvir'; + const handleStop = (event) => { + console.log('TTS finalizado ou com erro:', event.type); + if (currentSpeakingBtn === btn) { + resetSpeakBtnState(btn); + currentSpeakingBtn = null; } }; + utterance.onend = handleStop; + utterance.onerror = handleStop; + window.speechSynthesis.speak(utterance); };