API da ponte JavaScript
Texto para fala
As vozes do dispositivo, faladas a partir da página.
SpeechRecognition padrão web#
webkitSpeechRecognition / SpeechRecognition — speech to text
Exemplo
A Web Speech API padrão para ditado: o usuário fala e a página recebe o texto. O Chrome no Android a tem e a WebView do Android não, então o app a fornece sobre o reconhecedor de fala do próprio Android. O código escrito para o Chrome funciona sem alterações.
Retorna: start() começa a ouvir; os eventos result trazem event.results[i][0].transcript (e .confidence, .isFinal); os eventos error trazem event.error (not-allowed, no-speech, audio-capture, network, aborted, language-not-supported, service-not-allowed); end sempre vem por último. Requer: o microfone. O build o ativa sozinho quando seu código menciona SpeechRecognition, e o Android pergunta ao usuário na primeira vez que start() roda.
<button id="mic">Dictate</button>
<textarea id="note"></textarea>
<script>
const Rec = window.SpeechRecognition || window.webkitSpeechRecognition;
const mic = document.getElementById('mic');
if (!Rec) {
mic.hidden = true; // no speech recogniser on this phone
} else {
mic.addEventListener('click', () => {
const rec = new Rec();
rec.lang = 'en-IN'; // leave empty for the page / phone language
rec.interimResults = true; // live text while the user speaks
rec.onresult = (e) => {
let text = '';
for (const r of e.results) text += r[0].transcript;
document.getElementById('note').value = text;
};
rec.onerror = (e) => {
if (e.error === 'not-allowed') alert('Allow the microphone to dictate.');
};
rec.onend = () => { mic.disabled = false; };
mic.disabled = true;
rec.start();
});
}
</script>Ditado contínuo que para quando solicitado e informa cada erro:
const Rec = window.SpeechRecognition || window.webkitSpeechRecognition;
let rec = null;
function toggleDictation() {
if (!Rec) return showMessage('Voice typing is not available on this phone.');
if (rec) { rec.stop(); return; } // stop() delivers what was heard
rec = new Rec();
rec.continuous = true;
rec.onresult = (e) => {
for (let i = e.resultIndex; i < e.results.length; i++) {
if (e.results[i].isFinal) appendToNote(e.results[i][0].transcript);
}
};
rec.onerror = (e) => {
const msg = { 'not-allowed': 'Allow the microphone first.', 'no-speech': 'Nothing was heard.',
'audio-capture': 'No microphone found.', 'network': 'Speech service unreachable.' }[e.error];
if (msg) showMessage(msg);
};
rec.onend = () => { rec = null; };
rec.start();
}Diga um comando e execute-o, no idioma do celular:
const Rec = window.SpeechRecognition || window.webkitSpeechRecognition;
function listenForCommand() {
if (!Rec) return;
const rec = new Rec();
rec.lang = navigator.language;
rec.onresult = (e) => {
const said = e.results[0][0].transcript.toLowerCase();
if (said.includes('next')) goNext();
else if (said.includes('back')) goBack();
else showMessage('Heard: ' + said);
};
rec.onerror = (e) => {
if (e.error === 'language-not-supported') showMessage('This language is not supported here.');
};
rec.start();
}Observações: continuous = true continua ouvindo entre frases até você chamar stop(); depois de três rodadas em silêncio, termina com no-speech, para que um reconhecedor esquecido não fique ouvindo para sempre. stop() entrega o que foi ouvido; abort() descarta. O reconhecimento para quando o app vai para segundo plano, como acontece quando uma aba do Chrome fica oculta. Somente páginas que pertencem ao app (seus próprios arquivos ou o site do criador) podem usar o microfone dessa forma; qualquer outro site recebe not-allowed. A API não existe quando o celular não tem serviço de reconhecimento de fala, então verifique como acima. Transportes internos: __srStart, __srStop, __srAbort, __srAvailable; os resultados chegam como appmint:sr. Apps criados com IA usam speech.start() de @/lib/appmintNative.
speechSynthesis padrão web#
window.speechSynthesis + SpeechSynthesisUtterance
Exemplo
Lê texto em voz alta com o mecanismo de texto para fala do próprio celular, usando a Web Speech API normal.
Retorna: speechSynthesis.speak(u) não retorna nada; os eventos start / boundary / end / error / pause / resume da fala (como propriedades onstart … ou com addEventListener) informam o que aconteceu. getVoices() retorna um array de { voiceURI, name, lang, localService, default } de forma síncrona. Requer: nada. A WebView do Android não tem uma API de fala funcional própria; o app instala esta no início do documento em todos os frames.
Fale uma frase e saiba quando terminou:
function say(text) {
if (!('speechSynthesis' in window)) { alert('Speech is not available here'); return; }
var u = new SpeechSynthesisUtterance(text);
u.lang = 'en-US'; // BCP-47 tag: 'ta-IN', 'hi-IN', 'fr-FR' ...
u.rate = 1.2; // 0.1 .. 4 (1 = normal)
u.pitch = 1; // 0.1 .. 4 (1 = normal)
u.onstart = function () { document.body.classList.add('speaking'); };
u.onend = function () { document.body.classList.remove('speaking'); };
u.onerror = function (e) {
document.body.classList.remove('speaking');
console.log('Speech failed:', e.error); // 'synthesis-failed'
};
speechSynthesis.speak(u); // queues after anything already speaking
}
document.getElementById('stop').onclick = function () {
if ('speechSynthesis' in window) speechSynthesis.cancel(); // stops now, clears the queue
};As vozes chegam um instante depois da inicialização. Leia-as de novo quando voiceschanged disparar:
var voices = [];
function loadVoices() {
voices = speechSynthesis.getVoices(); // [] until the engine is ready
var tamil = voices.filter(function (v) { return v.lang.indexOf('ta') === 0; });
document.getElementById('tamil-btn').hidden = tamil.length === 0;
}
if ('speechSynthesis' in window) {
loadVoices();
speechSynthesis.addEventListener('voiceschanged', loadVoices);
}
function sayWithVoice(text, voice) {
var u = new SpeechSynthesisUtterance(text);
u.voice = voice; // a voice from getVoices(): that exact voice is used
u.volume = 0.8; // 0 .. 1
speechSynthesis.speak(u);
}Um leitor de artigos com um único botão Pausar / Continuar que destaca o parágrafo que está sendo lido:
var toggle = document.getElementById('pause-toggle');
toggle.onclick = function () {
if (!('speechSynthesis' in window) || !speechSynthesis.speaking) return;
if (speechSynthesis.paused) speechSynthesis.resume(); // continues from the last word
else speechSynthesis.pause();
};
function readArticle() {
if (!('speechSynthesis' in window)) return;
speechSynthesis.cancel(); // start over
document.querySelectorAll('article p').forEach(function (p) {
var u = new SpeechSynthesisUtterance(p.textContent);
u.onstart = function () { p.classList.add('reading'); };
u.onend = u.onerror = function () { p.classList.remove('reading'); };
u.onpause = function () { toggle.textContent = 'Resume'; };
u.onresume = function () { toggle.textContent = 'Pause'; };
speechSynthesis.speak(u); // paragraphs queue in order
});
toggle.textContent = 'Pause';
}Observaçþes:
volume(0-1),voice(a voz exata degetVoices()),rate,pitchelangchegam todos ao mecanismo do celular. Com umvoicee umlang, a voz prevalece; sem nenhum dos dois, o mecanismo fala com a voz padrão.boundarydispara antes de cada palavra comcharIndex/charLength(Android 8+; mecanismos que não informam posições de palavras não enviam nada).pause()/resume(): O mecanismo do Android não consegue pausar no meio de uma palavra, entãopause()o interrompe eresume()fala o resto da frase a partir da última palavra informada pelo mecanismo (os eventospause/resumedisparam). Em um mecanismo que não informa posições de palavras,resume()inicia essa fala de novo desde o começo.speakingcontinuatrueenquanto pausado;pendingétrueenquanto há falas na fila aguardando.cancel()disparaerrorcomerror: 'interrupted'na fala que está sendo dita e'canceled'nas que estão na fila, como diz o padrão: não os mostre como falhas.- Se o celular não tiver dados de voz para o idioma, o mecanismo ainda pode falar no idioma padrão dele. Verifique
getVoices()para ver o idioma antes. - Os eventos brutos por trás disso são
appmint:tts(vejattsSpeak). Você não precisa deles ao usar esta API.
__srAbort ponte#
window.WebToApk.__srAbort(id: String)
SpeechRecognition.abort(): para na hora e descarta.
Exemplo
Documentado junto com WebToApk.__srStart - o exemplo de lá mostra este também.
Transporte interno por trás de webkitSpeechRecognition / SpeechRecognition (iniciar, parar, abortar e saber se existe um reconhecedor no celular). As páginas usam a API padrão; nunca chame estes diretamente, porque os métodos __ podem mudar sem aviso.
Retorna: __srStart(id, options) retorna "" quando a escuta começa, ou um nome de erro da Web Speech (not-allowed, service-not-allowed). Tudo depois disso chega como eventos appmint:sr: {id, type} para start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} e {id, type:'error', error}. Requer: o microfone (armado a partir do formato da chamada).
Use a API pública:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Ditado contínuo com texto ao vivo e um idioma:
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SR) {
const rec = new SR();
rec.lang = 'en-US';
rec.continuous = true;
rec.interimResults = true;
rec.onresult = (e) => {
const r = e.results[e.results.length - 1];
note.value = r[0].transcript + (r.isFinal ? '\n' : '');
};
micBtn.onclick = () => rec.start();
stopBtn.onclick = () => rec.stop();
}Informe erros como um microfone recusado:
rec.onerror = (e) => {
if (e.error === 'not-allowed' || e.error === 'service-not-allowed') showMessage('Allow the microphone to dictate.');
else showMessage('Speech error: ' + e.error);
};
rec.onend = () => micBtn.classList.remove('live');Observações: Implementado no shell do app (SpeechRecognitionBridge.kt) sobre o SpeechRecognizer do Android. __srStart é recusado para páginas que não pertencem ao app.
__srAvailable ponte#
window.WebToApk.__srAvailable(): Boolean
Se um serviço de reconhecimento de fala está instalado (webkitSpeechRecognition só existe nesse caso).
Exemplo
Documentado junto com WebToApk.__srStart - o exemplo de lá mostra este também.
Transporte interno por trás de webkitSpeechRecognition / SpeechRecognition (iniciar, parar, abortar e saber se existe um reconhecedor no celular). As páginas usam a API padrão; nunca chame estes diretamente, porque os métodos __ podem mudar sem aviso.
Retorna: __srStart(id, options) retorna "" quando a escuta começa, ou um nome de erro da Web Speech (not-allowed, service-not-allowed). Tudo depois disso chega como eventos appmint:sr: {id, type} para start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} e {id, type:'error', error}. Requer: o microfone (armado a partir do formato da chamada).
Use a API pública:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Ditado contínuo com texto ao vivo e um idioma:
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SR) {
const rec = new SR();
rec.lang = 'en-US';
rec.continuous = true;
rec.interimResults = true;
rec.onresult = (e) => {
const r = e.results[e.results.length - 1];
note.value = r[0].transcript + (r.isFinal ? '\n' : '');
};
micBtn.onclick = () => rec.start();
stopBtn.onclick = () => rec.stop();
}Informe erros como um microfone recusado:
rec.onerror = (e) => {
if (e.error === 'not-allowed' || e.error === 'service-not-allowed') showMessage('Allow the microphone to dictate.');
else showMessage('Speech error: ' + e.error);
};
rec.onend = () => micBtn.classList.remove('live');Observações: Implementado no shell do app (SpeechRecognitionBridge.kt) sobre o SpeechRecognizer do Android. __srStart é recusado para páginas que não pertencem ao app.
__srStart ponte#
window.WebToApk.__srStart(id: String, optionsJson: String): String
SpeechRecognition.start(): "" ao iniciar (os eventos chegam como `appmint:sr`), ou o nome de erro da Web Speech. Com verificação de origem: o que o usuário diz são dados do usuário.
Exemplo
Transporte interno por trás de webkitSpeechRecognition / SpeechRecognition (iniciar, parar, abortar e saber se existe um reconhecedor no celular). As páginas usam a API padrão; nunca chame estes diretamente, porque os métodos __ podem mudar sem aviso.
Retorna: __srStart(id, options) retorna "" quando a escuta começa, ou um nome de erro da Web Speech (not-allowed, service-not-allowed). Tudo depois disso chega como eventos appmint:sr: {id, type} para start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} e {id, type:'error', error}. Requer: o microfone (armado a partir do formato da chamada).
Use a API pública:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Ditado contínuo com texto ao vivo e um idioma:
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SR) {
const rec = new SR();
rec.lang = 'en-US';
rec.continuous = true;
rec.interimResults = true;
rec.onresult = (e) => {
const r = e.results[e.results.length - 1];
note.value = r[0].transcript + (r.isFinal ? '\n' : '');
};
micBtn.onclick = () => rec.start();
stopBtn.onclick = () => rec.stop();
}Informe erros como um microfone recusado:
rec.onerror = (e) => {
if (e.error === 'not-allowed' || e.error === 'service-not-allowed') showMessage('Allow the microphone to dictate.');
else showMessage('Speech error: ' + e.error);
};
rec.onend = () => micBtn.classList.remove('live');Observações: Implementado no shell do app (SpeechRecognitionBridge.kt) sobre o SpeechRecognizer do Android. __srStart é recusado para páginas que não pertencem ao app.
__srStop ponte#
window.WebToApk.__srStop(id: String)
SpeechRecognition.stop(): para de ouvir e entrega o que foi ouvido.
Exemplo
Documentado junto com WebToApk.__srStart - o exemplo de lá mostra este também.
Transporte interno por trás de webkitSpeechRecognition / SpeechRecognition (iniciar, parar, abortar e saber se existe um reconhecedor no celular). As páginas usam a API padrão; nunca chame estes diretamente, porque os métodos __ podem mudar sem aviso.
Retorna: __srStart(id, options) retorna "" quando a escuta começa, ou um nome de erro da Web Speech (not-allowed, service-not-allowed). Tudo depois disso chega como eventos appmint:sr: {id, type} para start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} e {id, type:'error', error}. Requer: o microfone (armado a partir do formato da chamada).
Use a API pública:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Ditado contínuo com texto ao vivo e um idioma:
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SR) {
const rec = new SR();
rec.lang = 'en-US';
rec.continuous = true;
rec.interimResults = true;
rec.onresult = (e) => {
const r = e.results[e.results.length - 1];
note.value = r[0].transcript + (r.isFinal ? '\n' : '');
};
micBtn.onclick = () => rec.start();
stopBtn.onclick = () => rec.stop();
}Informe erros como um microfone recusado:
rec.onerror = (e) => {
if (e.error === 'not-allowed' || e.error === 'service-not-allowed') showMessage('Allow the microphone to dictate.');
else showMessage('Speech error: ' + e.error);
};
rec.onend = () => micBtn.classList.remove('live');Observações: Implementado no shell do app (SpeechRecognitionBridge.kt) sobre o SpeechRecognizer do Android. __srStart é recusado para páginas que não pertencem ao app.
__ttsSpeak ponte#
window.WebToApk.__ttsSpeak(json: String)
O speak completo do polyfill de speechSynthesis: `{text, lang, rate, pitch, volume (0-1), voice (um nome de getVoices(), ou ''), id}`. [ttsSpeak] continua existindo para páginas que chamam a ponte diretamente; não tem volume nem voz.
Exemplo
Transporte interno por trás de speechSynthesis.speak(utterance): a fala completa (texto, idioma, velocidade, tom, volume e a voz escolhida) como uma única string JSON. As páginas usam a API padrão speechSynthesis; nunca chame isto diretamente.
Retorna: nada. O progresso chega como eventos appmint:tts, que a camada speechSynthesis transforma nos eventos start / boundary / end / error da fala. Requer: nada.
A API padrão que isto oferece:
function readAloud(text) {
if (!('speechSynthesis' in window)) return;
var voice = speechSynthesis.getVoices().filter(function (v) { return v.lang === 'en-GB'; })[0];
var u = new SpeechSynthesisUtterance(text);
if (voice) u.voice = voice; // that exact voice
u.volume = 0.6; // 0 .. 1
u.addEventListener('boundary', function (e) { highlightWordAt(e.charIndex, e.charLength); });
u.addEventListener('end', function () { clearHighlight(); });
speechSynthesis.speak(u);
}Um seletor de voz que o usuário escolhe uma vez; toda fala seguinte leva exatamente essa voz:
var picker = document.getElementById('voice-picker');
function listVoices() {
var saved = localStorage.getItem('voiceName') || '';
picker.innerHTML = '';
speechSynthesis.getVoices().forEach(function (v) {
picker.add(new Option(v.name + ' (' + v.lang + ')', v.name, false, v.name === saved));
});
}
if ('speechSynthesis' in window) {
listVoices(); // [] until the engine is ready
speechSynthesis.onvoiceschanged = listVoices;
}
picker.onchange = function () { localStorage.setItem('voiceName', picker.value); };
function speakWithSavedVoice(text) {
var name = localStorage.getItem('voiceName');
var voice = speechSynthesis.getVoices().filter(function (v) { return v.name === name; })[0];
var u = new SpeechSynthesisUtterance(text);
if (voice) u.voice = voice; // its language is used too when u.lang is empty
speechSynthesis.speak(u);
}Avisos de treino: baixos e rápidos para contagens regressivas, em volume total e mais agudos para a troca:
function cue(text, loud) {
if (!('speechSynthesis' in window)) return;
var u = new SpeechSynthesisUtterance(text);
u.lang = 'en-US';
u.rate = 1.4; // 0.1 .. 4
u.pitch = loud ? 1.3 : 1; // 0.1 .. 4
u.volume = loud ? 1 : 0.4; // 0 .. 1
u.onerror = function (e) {
if (e.error === 'synthesis-failed') console.log('Cue not spoken:', text);
};
speechSynthesis.speak(u); // queues after the previous cue
}
cue('Ten seconds left', false);
cue('Switch sides!', true);Observações: O JSON é {text, lang, rate, pitch, volume, voice, id} (voice é um nome de getVoices() ou ''). O ttsSpeak(text, lang, rate, pitch, id) mais antigo continua existindo para páginas que chamam a ponte diretamente; não tem volume nem voz.
ttsCancel ponte#
window.WebToApk.ttsCancel()
Dá suporte ao polyfill window.speechSynthesis, que é instalado no início do documento em todos os frames: as páginas devem usar a API PADRÃO speechSynthesis, e estes serão chamados por elas. A WebView do Android não traz um speechSynthesis funcional próprio, e é por isso que existe esse redirecionamento.
Descrito por seu grupo, Texto para fala, e não separadamente.
Exemplo
Interrompe a fala agora e descarta tudo o que ainda aguardava para ser falado. As páginas normalmente chamam o speechSynthesis.cancel() padrão, que chama isto por você.
Retorna: nada. Requer: nada.
A forma pública (use esta):
document.getElementById('stop-reading').addEventListener('click', function () {
if ('speechSynthesis' in window) speechSynthesis.cancel();
});A chamada bruta:
if (window.WebToApk && typeof window.WebToApk.ttsCancel === 'function') {
window.WebToApk.ttsCancel();
}Diga apenas o status mais recente e descarte qualquer coisa mais antiga ainda na fila (um rastreador de entrega, um placar ao vivo):
function announceLatest(text) {
if (!('speechSynthesis' in window)) return;
speechSynthesis.cancel(); // the old line gets error 'interrupted', queued ones 'canceled'
var u = new SpeechSynthesisUtterance(text);
u.onerror = function (e) {
if (e.error === 'interrupted' || e.error === 'canceled') return; // replaced, not a failure
showToast('Could not read that aloud');
};
speechSynthesis.speak(u);
}
announceLatest('Your driver is 5 minutes away');
announceLatest('Your driver has arrived'); // cuts the first one offObservações: Uma fala interrompida ou descartada não envia o evento appmint:tts end nem error, então o onend de uma fala não roda depois de um cancelamento. Se você acompanha ids brutos de ttsSpeak por conta própria, esqueça-os ao chamar ttsCancel().
ttsGetVoices ponte#
window.WebToApk.ttsGetVoices(): String
Array JSON das vozes instaladas: [{name, lang, default}]. Vazio até o mecanismo terminar de iniciar: a página recebe 'voiceschanged' nesse momento e consulta de novo, exatamente como no contrato assíncrono de getVoices() do Chrome.
Exemplo
Lista as vozes de texto para fala instaladas no celular. As páginas normalmente usam o speechSynthesis.getVoices() padrão, que chama isto por você.
Retorna: uma string JSON, de forma síncrona: [{ "name": "en-us-x-sfg-local", "lang": "en-US", "default": false }, ...]. É "[]" até o mecanismo iniciar. Requer: nada.
A forma pública (use esta):
function fillLanguages() {
var langs = {};
speechSynthesis.getVoices().forEach(function (v) { langs[v.lang] = true; });
document.getElementById('langs').textContent = Object.keys(langs).join(', ');
}
if ('speechSynthesis' in window) {
fillLanguages();
speechSynthesis.onvoiceschanged = fillLanguages;
}A chamada direta (lembre-se JSON.parse):
function nativeVoices() {
if (!(window.WebToApk && typeof window.WebToApk.ttsGetVoices === 'function')) return [];
try { return JSON.parse(window.WebToApk.ttsGetVoices()); } catch (e) { return []; }
}
window.addEventListener('appmint:tts', function (e) {
if (e.detail && e.detail.event === 'voiceschanged') {
var hindi = nativeVoices().filter(function (v) { return v.lang.indexOf('hi') === 0; });
document.getElementById('hindi-btn').hidden = hindi.length === 0;
}
});Fale no idioma do celular quando houver uma voz instalada para ele; caso contrário, deixe o mecanismo usar a voz padrão:
function bestSpeechLang() {
if (!(window.WebToApk && typeof window.WebToApk.ttsGetVoices === 'function')) return '';
var voices;
try { voices = JSON.parse(window.WebToApk.ttsGetVoices()); } catch (e) { return ''; }
var want = navigator.language || 'en-US'; // e.g. 'ta-IN'
var exact = voices.filter(function (v) { return v.lang === want; })[0];
if (exact) return exact.lang;
var base = want.split('-')[0];
var near = voices.filter(function (v) { return v.lang.split('-')[0] === base; })[0];
return near ? near.lang : ''; // '' = the engine's default voice
}
if (window.WebToApk && typeof window.WebToApk.ttsSpeak === 'function') {
window.WebToApk.ttsSpeak(greetingText(), bestSpeechLang(), 1.0, 1.0, 'greeting');
}Observações: lang é uma tag BCP-47 como en-US ou ta-IN. O name de uma voz é o id do próprio mecanismo e não pode ser escolhido pelo nome ao falar; só o idioma dela é usado.
ttsIsSpeaking ponte#
window.WebToApk.ttsIsSpeaking(): Boolean
Dá suporte ao polyfill window.speechSynthesis, que é instalado no início do documento em todos os frames: as páginas devem usar a API PADRÃO speechSynthesis, e estes serão chamados por elas. A WebView do Android não traz um speechSynthesis funcional próprio, e é por isso que existe esse redirecionamento.
Descrito por seu grupo, Texto para fala, e não separadamente.
Exemplo
Informa se o mecanismo de texto para fala do celular está falando neste momento. As páginas normalmente usam speechSynthesis.speaking ou o onend da fala em vez disso.
Retorna: true / false, de forma síncrona. false antes de o mecanismo iniciar. Requer: nada.
A forma pública (use esta):
if ('speechSynthesis' in window && speechSynthesis.speaking) {
speechSynthesis.cancel();
}A chamada direta consulta o próprio mecanismo, então é exata mesmo que a fala tenha sido iniciada com a chamada direta ttsSpeak:
function isSpeaking() {
if (window.WebToApk && typeof window.WebToApk.ttsIsSpeaking === 'function') {
return window.WebToApk.ttsIsSpeaking() === true;
}
return false;
}
var btn = document.getElementById('read-btn');
setInterval(function () {
btn.textContent = isSpeaking() ? 'Stop' : 'Read aloud';
}, 500);Espere o app parar de falar antes de começar a gravar, para que o microfone não capte a própria voz dele:
function whenQuiet(fn) {
if (!(window.WebToApk && typeof window.WebToApk.ttsIsSpeaking === 'function')) { fn(); return; }
(function check() {
if (window.WebToApk.ttsIsSpeaking()) setTimeout(check, 300);
else fn();
})();
}
document.getElementById('answer-btn').addEventListener('click', function () {
whenQuiet(startRecordingAnswer); // e.g. after the question was read aloud
});Observações: Não consulte rápido demais; um evento (onend, ou appmint:tts com event: 'end') é melhor quando você mesmo iniciou a fala.
ttsSpeak ponte#
window.WebToApk.ttsSpeak(text: String, lang: String, rate: Double, pitch: Double, utteranceId: String)
Dá suporte ao polyfill window.speechSynthesis, que é instalado no início do documento em todos os frames: as páginas devem usar a API PADRÃO speechSynthesis, e estes serão chamados por elas. A WebView do Android não traz um speechSynthesis funcional próprio, e é por isso que existe esse redirecionamento.
Descrito por seu grupo, Texto para fala, e não separadamente.
Exemplo
Começa a falar o texto com o mecanismo de texto para fala do celular. As páginas normalmente usam a API speechSynthesis padrão, que chama isto por você.
Retorna: nada. O progresso chega depois como o evento appmint:tts (e o hook window.onAppMintTts(detail)) com detail = { id, event }, em que event é 'start', 'end' ou 'error', e id é o utteranceId que você passou. Requer: nada.
A forma pública (use esta):
if ('speechSynthesis' in window) {
var u = new SpeechSynthesisUtterance('Your order is ready');
u.lang = 'en-GB';
u.onend = function () { console.log('done'); };
speechSynthesis.speak(u);
}A chamada direta, se você quiser seus próprios ids. Argumentos: text, lang, rate, pitch, utteranceId:
var ttsWaiting = {};
window.addEventListener('appmint:tts', function (e) {
var d = e.detail || {};
if (d.event === 'voiceschanged') return; // engine ready, id is ''
var done = ttsWaiting[d.id];
if (!done || d.event === 'start') return;
delete ttsWaiting[d.id];
done(d.event === 'end'); // 'end' = spoke it, 'error' = failed
});
function speakRaw(text) {
return new Promise(function (resolve) {
if (!(window.WebToApk && typeof window.WebToApk.ttsSpeak === 'function')) { resolve(false); return; }
var id = 'mine_' + Date.now();
ttsWaiting[id] = resolve;
window.WebToApk.ttsSpeak(String(text), 'en-US', 1.0, 1.0, id);
});
}
speakRaw('Hello').then(function (ok) { console.log(ok ? 'spoken' : 'could not speak'); });Leia uma lista de etapas em ordem com seus próprios ids e destaque a etapa que está sendo falada:
var steps = Array.prototype.slice.call(document.querySelectorAll('.recipe-step'));
window.addEventListener('appmint:tts', function (e) {
var d = e.detail || {};
var m = /^recipe_(\d+)$/.exec(d.id || '');
if (!m) return; // not one of ours
var el = steps[Number(m[1])];
if (d.event === 'start') el.classList.add('reading');
if (d.event === 'end' || d.event === 'error') el.classList.remove('reading');
});
document.getElementById('read-recipe').addEventListener('click', function () {
if (!(window.WebToApk && typeof window.WebToApk.ttsSpeak === 'function')) return;
steps.forEach(function (el, i) {
// '' = the engine's default voice; 0.9 = a little slower for cooking
window.WebToApk.ttsSpeak(el.textContent, '', 0.9, 1.0, 'recipe_' + i);
});
});Observações: As falas entram em fila, uma após a outra. lang pode ser '' (a voz padrão do mecanismo); rate e pitch são limitados a 0,1-4. Se o mecanismo ainda está iniciando, o texto espera e é falado quando ele estiver pronto; se o mecanismo não conseguir iniciar, cada fala em espera recebe 'error'. Use seu próprio prefixo de id; ids que começam com wta_u pertencem à camada speechSynthesis. No Android 8+, appmint:tts também traz {id, event: 'boundary', charIndex, charLength} antes de cada palavra. Esta chamada direta não tem volume nem voz: speechSynthesis tem ambos.
ttsWarmUp ponte#
window.WebToApk.ttsWarmUp()
Dá suporte ao polyfill window.speechSynthesis, que é instalado no início do documento em todos os frames: as páginas devem usar a API PADRÃO speechSynthesis, e estes serão chamados por elas. A WebView do Android não traz um speechSynthesis funcional próprio, e é por isso que existe esse redirecionamento.
Descrito por seu grupo, Texto para fala, e não separadamente.
Exemplo
Inicia cedo o mecanismo de texto para fala do celular, para que o primeiro speak() não tenha atraso e getVoices() seja preenchido. A camada speechSynthesis do app já chama isto no início da página, então as páginas quase nunca precisam dele.
Retorna: nada. Quando o mecanismo está pronto, o evento appmint:tts dispara com detail = { id: '', event: 'voiceschanged' } (e speechSynthesis.onvoiceschanged roda). Requer: nada.
O jeito público (use este): basta ler as vozes quando estiverem prontas.
if ('speechSynthesis' in window) {
speechSynthesis.onvoiceschanged = function () {
console.log('voices:', speechSynthesis.getVoices().length);
};
}A chamada direta e depois espere o evento de pronto:
window.addEventListener('appmint:tts', function (e) {
if (e.detail && e.detail.event === 'voiceschanged') {
var list = JSON.parse(window.WebToApk.ttsGetVoices());
console.log('engine ready,', list.length, 'voices');
}
});
if (window.WebToApk && typeof window.WebToApk.ttsWarmUp === 'function') {
window.WebToApk.ttsWarmUp(); // safe to call many times
}Inicie o mecanismo quando uma tela "Ler em voz alta" abrir e ative o botão dela quando o mecanismo estiver pronto:
var readBtn = document.getElementById('read-aloud');
function engineReady() {
try { return JSON.parse(window.WebToApk.ttsGetVoices()).length > 0; } catch (e) { return false; }
}
function openReaderScreen() {
showReaderScreen();
if (!(window.WebToApk && typeof window.WebToApk.ttsWarmUp === 'function')) return;
readBtn.disabled = !engineReady(); // already started earlier? enable right away
window.WebToApk.ttsWarmUp(); // no-op when the engine is already running
}
window.addEventListener('appmint:tts', function (e) {
if (e.detail && e.detail.event === 'voiceschanged') readBtn.disabled = false;
});Observações: voiceschanged dispara uma vez, quando o mecanismo termina de iniciar. Se ele já iniciou (por exemplo, porque a camada speechSynthesis o aqueceu), você não o receberá de novo; chame ttsGetVoices() diretamente.