API del puente de JavaScript
Texto a voz
Las voces del dispositivo, habladas desde la página.
SpeechRecognition estándar web#
webkitSpeechRecognition / SpeechRecognition — speech to text
Ejemplo
La API estándar Web Speech para dictado: el usuario habla y la página recibe texto. Chrome en Android la tiene y el WebView de Android no, así que la app la proporciona sobre el propio reconocedor de voz de Android. El código escrito para Chrome funciona sin cambios.
Devuelve: start() empieza a escuchar; los eventos result llevan event.results[i][0].transcript (y .confidence, .isFinal); los eventos error llevan event.error (not-allowed, no-speech, audio-capture, network, aborted, language-not-supported, service-not-allowed); end siempre llega al final. Requiere: el micrófono. La compilación lo activa por sí sola cuando tu código menciona SpeechRecognition, y Android pregunta al usuario la primera vez que se ejecuta start().
<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>Dictado continuo que se detiene cuando se pide e informa de cada error:
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();
}Di un comando y actúa en consecuencia, en el idioma del teléfono:
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();
}Notas: continuous = true sigue escuchando entre frases hasta que llames a stop(); tras tres rondas en silencio termina con no-speech, para que un reconocedor olvidado no escuche para siempre. stop() entrega lo que se oyó; abort() lo descarta. El reconocimiento se detiene cuando la app pasa a segundo plano, como cuando se oculta una pestaña de Chrome. Solo las páginas que pertenecen a la app (sus propios archivos o el sitio web del creador) pueden usar el micrófono de esta manera; cualquier otro sitio recibe not-allowed. La API no existe cuando el teléfono no tiene servicio de reconocimiento de voz, así que compruébalo como se indica arriba. Transportes internos: __srStart, __srStop, __srAbort, __srAvailable; los resultados llegan como appmint:sr. Las apps creadas con IA usan speech.start() desde @/lib/appmintNative.
speechSynthesis estándar web#
window.speechSynthesis + SpeechSynthesisUtterance
Ejemplo
Lee texto en voz alta con el motor de texto a voz del propio teléfono, usando la API Web Speech normal.
Devuelve: speechSynthesis.speak(u) no devuelve nada; los eventos start / boundary / end / error / pause / resume de la locución (como propiedades onstart … o con addEventListener) te dicen qué pasó. getVoices() devuelve una matriz de { voiceURI, name, lang, localService, default } de forma síncrona. Requiere: nada. El WebView de Android no tiene una API de voz que funcione por sí mismo; la app instala esta al inicio del documento en cada frame.
Di una frase y sabe cuándo ha terminado:
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
};Las voces llegan un momento después de iniciar. Léelas de nuevo cuando se active voiceschanged:
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);
}Un lector de artículos con un solo botón Pausar / Reanudar que resalta el párrafo que se está leyendo:
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';
}Notas:
volume(0-1),voice(la voz exacta degetVoices()),rate,pitchylangllegan todos al motor del teléfono. Con unvoicey unlang, gana la voz; sin ninguno, habla la voz predeterminada del motor.boundaryse activa antes de cada palabra concharIndex/charLength(Android 8+; los motores que no informan posiciones de palabras no envían ninguna).pause()/resume(): El motor de Android no puede pausar a mitad de palabra, así quepause()lo detiene yresume()dice el resto de la frase desde la última palabra que informó el motor (se activan los eventospause/resume). En un motor que no informa posiciones de palabras,resume()inicia esa locución de nuevo desde el principio.speakingsigue entruemientras está en pausa;pendingestruemientras esperan locuciones en cola.cancel()se activaerrorconerror: 'interrupted'en la locución que se está diciendo y'canceled'en las que están en cola, como indica el estándar; no los muestres como fallos.- Si el teléfono no tiene datos de voz para el idioma, el motor aún puede hablar en su idioma predeterminado. Comprueba
getVoices()para el idioma primero. - Los eventos en bruto detrás de esto son
appmint:tts(verttsSpeak). No los necesitas cuando usas esta API.
__srAbort puente#
window.WebToApk.__srAbort(id: String)
SpeechRecognition.abort(): detiene de inmediato y descarta.
Ejemplo
Documentado junto con WebToApk.__srStart: el ejemplo de allí también muestra este.
Transporte interno detrás de webkitSpeechRecognition / SpeechRecognition (iniciar, detener, abortar y si existe un reconocedor en el teléfono). Las páginas usan la API estándar; nunca llames a esto directamente, porque los métodos __ pueden cambiar sin aviso.
Devuelve: __srStart(id, options) devuelve "" cuando empieza a escuchar, o un nombre de error de Web Speech (not-allowed, service-not-allowed). Todo lo que sigue llega como eventos appmint:sr: {id, type} para start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} y {id, type:'error', error}. Requiere: el micrófono (se activa a partir de la forma de la llamada).
Usa la API pública:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Dictado continuo con texto en vivo y un 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();
}Informa de errores como un micrófono rechazado:
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');Notas: Implementado en la shell de la app (SpeechRecognitionBridge.kt) sobre el SpeechRecognizer de Android. __srStart se rechaza para las páginas que no pertenecen a la app.
__srAvailable puente#
window.WebToApk.__srAvailable(): Boolean
Si hay instalado un servicio de reconocimiento de voz (webkitSpeechRecognition existe solo entonces).
Ejemplo
Documentado junto con WebToApk.__srStart: el ejemplo de allí también muestra este.
Transporte interno detrás de webkitSpeechRecognition / SpeechRecognition (iniciar, detener, abortar y si existe un reconocedor en el teléfono). Las páginas usan la API estándar; nunca llames a esto directamente, porque los métodos __ pueden cambiar sin aviso.
Devuelve: __srStart(id, options) devuelve "" cuando empieza a escuchar, o un nombre de error de Web Speech (not-allowed, service-not-allowed). Todo lo que sigue llega como eventos appmint:sr: {id, type} para start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} y {id, type:'error', error}. Requiere: el micrófono (se activa a partir de la forma de la llamada).
Usa la API pública:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Dictado continuo con texto en vivo y un 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();
}Informa de errores como un micrófono rechazado:
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');Notas: Implementado en la shell de la app (SpeechRecognitionBridge.kt) sobre el SpeechRecognizer de Android. __srStart se rechaza para las páginas que no pertenecen a la app.
__srStart puente#
window.WebToApk.__srStart(id: String, optionsJson: String): String
SpeechRecognition.start(): "" al iniciar (los eventos llegan como `appmint:sr`), o si no el nombre del error de Web Speech. Con control de origen: lo que dice el usuario son datos del usuario.
Ejemplo
Transporte interno detrás de webkitSpeechRecognition / SpeechRecognition (iniciar, detener, abortar y si existe un reconocedor en el teléfono). Las páginas usan la API estándar; nunca llames a esto directamente, porque los métodos __ pueden cambiar sin aviso.
Devuelve: __srStart(id, options) devuelve "" cuando empieza a escuchar, o un nombre de error de Web Speech (not-allowed, service-not-allowed). Todo lo que sigue llega como eventos appmint:sr: {id, type} para start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} y {id, type:'error', error}. Requiere: el micrófono (se activa a partir de la forma de la llamada).
Usa la API pública:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Dictado continuo con texto en vivo y un 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();
}Informa de errores como un micrófono rechazado:
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');Notas: Implementado en la shell de la app (SpeechRecognitionBridge.kt) sobre el SpeechRecognizer de Android. __srStart se rechaza para las páginas que no pertenecen a la app.
__srStop puente#
window.WebToApk.__srStop(id: String)
SpeechRecognition.stop(): deja de escuchar y entrega lo que se oyó.
Ejemplo
Documentado junto con WebToApk.__srStart: el ejemplo de allí también muestra este.
Transporte interno detrás de webkitSpeechRecognition / SpeechRecognition (iniciar, detener, abortar y si existe un reconocedor en el teléfono). Las páginas usan la API estándar; nunca llames a esto directamente, porque los métodos __ pueden cambiar sin aviso.
Devuelve: __srStart(id, options) devuelve "" cuando empieza a escuchar, o un nombre de error de Web Speech (not-allowed, service-not-allowed). Todo lo que sigue llega como eventos appmint:sr: {id, type} para start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} y {id, type:'error', error}. Requiere: el micrófono (se activa a partir de la forma de la llamada).
Usa la API pública:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Dictado continuo con texto en vivo y un 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();
}Informa de errores como un micrófono rechazado:
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');Notas: Implementado en la shell de la app (SpeechRecognitionBridge.kt) sobre el SpeechRecognizer de Android. __srStart se rechaza para las páginas que no pertenecen a la app.
__ttsSpeak puente#
window.WebToApk.__ttsSpeak(json: String)
El speak completo del polyfill de speechSynthesis: `{text, lang, rate, pitch, volume (0-1), voice (un nombre de getVoices(), o ''), id}`. [ttsSpeak] se mantiene para las páginas que llaman al puente directamente; no tiene volumen ni voz.
Ejemplo
Transporte interno detrás de speechSynthesis.speak(utterance): la locución completa (texto, idioma, velocidad, tono, volumen y la voz elegida) como una sola cadena JSON. Las páginas usan la API estándar speechSynthesis; nunca llames a esto directamente.
Devuelve: nada. El progreso llega como eventos appmint:tts, que la capa speechSynthesis convierte en los eventos start / boundary / end / error de la locución. Requiere: nada.
La API estándar que esto lleva:
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);
}Un selector de voz que el usuario elige una vez; todas las locuciones posteriores llevan esa voz exacta:
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);
}Indicaciones de entrenamiento: bajas y rápidas para las cuentas atrás, a todo volumen y más agudas para el cambio:
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);Notas: El JSON es {text, lang, rate, pitch, volume, voice, id} (voice es un nombre de getVoices() o ''). El ttsSpeak(text, lang, rate, pitch, id) más antiguo se mantiene para las páginas que llaman al puente directamente; no tiene volumen ni voz.
ttsCancel puente#
window.WebToApk.ttsCancel()
Respalda el polyfill de window.speechSynthesis, que se instala al inicio del documento en cada frame: las páginas deben usar la API ESTÁNDAR speechSynthesis y se llamará a esto por ellas. El WebView de Android no incluye un speechSynthesis que funcione por sí mismo, por eso existe la redirección.
Descrito por su grupo, Texto a voz, en lugar de por separado.
Ejemplo
Detiene el habla ahora mismo y descarta todo lo que aún esté esperando para decirse. Las páginas normalmente llaman al speechSynthesis.cancel() estándar, que llama a esto por ti.
Devuelve: nada. Requiere: nada.
La forma pública (usa esta):
document.getElementById('stop-reading').addEventListener('click', function () {
if ('speechSynthesis' in window) speechSynthesis.cancel();
});La llamada en bruto:
if (window.WebToApk && typeof window.WebToApk.ttsCancel === 'function') {
window.WebToApk.ttsCancel();
}Di solo el estado más reciente y descarta cualquier cosa anterior que siga en cola (un seguimiento de entrega, un marcador en 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 offNotas: El habla que se corta o se descarta no envía ningún evento appmint:tts end ni error, así que el onend de una locución no se ejecuta tras una cancelación. Si haces seguimiento de ids ttsSpeak en bruto tú mismo, olvídalos cuando llames a ttsCancel().
ttsGetVoices puente#
window.WebToApk.ttsGetVoices(): String
Matriz JSON de las voces instaladas: [{name, lang, default}]. Vacía hasta que el motor termina de iniciarse; entonces la página recibe 'voiceschanged' y vuelve a consultar, exactamente como el contrato asíncrono de getVoices() de Chrome.
Ejemplo
Lista las voces de texto a voz instaladas en el teléfono. Las páginas normalmente usan el speechSynthesis.getVoices() estándar, que llama a esto por ti.
Devuelve: una cadena JSON, de forma síncrona: [{ "name": "en-us-x-sfg-local", "lang": "en-US", "default": false }, ...]. Es "[]" hasta que el motor se ha iniciado. Requiere: nada.
La forma pública (usa 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;
}La llamada directa (recuerda 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;
}
});Habla en el idioma del teléfono cuando hay una voz instalada para él; si no, deja que el motor use su voz predeterminada:
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');
}Notas: lang es una etiqueta BCP-47 como en-US o ta-IN. El name de una voz es el id propio del motor y no se puede elegir por nombre al hablar; solo se usa su idioma.
ttsIsSpeaking puente#
window.WebToApk.ttsIsSpeaking(): Boolean
Respalda el polyfill de window.speechSynthesis, que se instala al inicio del documento en cada frame: las páginas deben usar la API ESTÁNDAR speechSynthesis y se llamará a esto por ellas. El WebView de Android no incluye un speechSynthesis que funcione por sí mismo, por eso existe la redirección.
Descrito por su grupo, Texto a voz, en lugar de por separado.
Ejemplo
Te indica si el motor de texto a voz del teléfono está hablando en este momento. Las páginas normalmente usan speechSynthesis.speaking o el onend de la locución en su lugar.
Devuelve: true / false, de forma síncrona. false antes de que el motor se haya iniciado. Requiere: nada.
La forma pública (usa esta):
if ('speechSynthesis' in window && speechSynthesis.speaking) {
speechSynthesis.cancel();
}La llamada directa consulta al propio motor, así que es exacta incluso si el habla se inició con la llamada directa 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);Espera a que la app deje de hablar antes de empezar a grabar, para que el micrófono no capte su propia voz:
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
});Notas: No consultes con rapidez; es mejor un evento (onend, o appmint:tts con event: 'end') cuando iniciaste el habla tú mismo.
ttsSpeak puente#
window.WebToApk.ttsSpeak(text: String, lang: String, rate: Double, pitch: Double, utteranceId: String)
Respalda el polyfill de window.speechSynthesis, que se instala al inicio del documento en cada frame: las páginas deben usar la API ESTÁNDAR speechSynthesis y se llamará a esto por ellas. El WebView de Android no incluye un speechSynthesis que funcione por sí mismo, por eso existe la redirección.
Descrito por su grupo, Texto a voz, en lugar de por separado.
Ejemplo
Empieza a decir el texto con el motor de texto a voz del teléfono. Las páginas normalmente usan la API estándar speechSynthesis, que llama a esto por ti.
Devuelve: nada. El progreso llega después como el evento appmint:tts (y el hook window.onAppMintTts(detail)) con detail = { id, event }, donde event es 'start', 'end' o 'error', e id es el utteranceId que pasaste. Requiere: nada.
La forma pública (usa 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);
}La llamada directa, si quieres tus propios 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'); });Lee una lista de pasos en orden con tus propios ids y resalta el paso que se está diciendo:
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);
});
});Notas: Las locuciones se ponen en cola una tras otra. lang puede ser '' (la voz predeterminada del motor); rate y pitch se limitan a 0.1-4. Si el motor aún se está iniciando, el texto espera y se dice cuando está listo; si el motor no puede iniciarse, cada locución en espera recibe 'error'. Usa tu propio prefijo de id; los ids que empiezan por wta_u pertenecen a la capa speechSynthesis. En Android 8+ appmint:tts también lleva {id, event: 'boundary', charIndex, charLength} antes de cada palabra. Esta llamada directa no tiene volumen ni voz; speechSynthesis tiene ambos.
ttsWarmUp puente#
window.WebToApk.ttsWarmUp()
Respalda el polyfill de window.speechSynthesis, que se instala al inicio del documento en cada frame: las páginas deben usar la API ESTÁNDAR speechSynthesis y se llamará a esto por ellas. El WebView de Android no incluye un speechSynthesis que funcione por sí mismo, por eso existe la redirección.
Descrito por su grupo, Texto a voz, en lugar de por separado.
Ejemplo
Inicia pronto el motor de texto a voz del teléfono, para que el primer speak() no tenga retraso y se rellene getVoices(). La capa speechSynthesis de la app ya llama a esto al iniciar la página, así que las páginas casi nunca lo necesitan.
Devuelve: nada. Cuando el motor está listo, se activa el evento appmint:tts con detail = { id: '', event: 'voiceschanged' } (y se ejecuta speechSynthesis.onvoiceschanged). Requiere: nada.
La forma pública (usa esta): simplemente lee las voces cuando estén listas.
if ('speechSynthesis' in window) {
speechSynthesis.onvoiceschanged = function () {
console.log('voices:', speechSynthesis.getVoices().length);
};
}La llamada directa y luego esperar el evento ready:
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
}Inicia el motor cuando se abre una pantalla «Leer en voz alta» y activa su botón cuando el motor esté listo:
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;
});Notas: voiceschanged se activa una vez, cuando el motor termina de iniciarse. Si ya se había iniciado (por ejemplo porque la capa speechSynthesis lo precalentó), no lo recibirás de nuevo; llama a ttsGetVoices() directamente.