Crear APK gratis

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 de getVoices()), rate, pitch y lang llegan todos al motor del teléfono. Con un voice y un lang, gana la voz; sin ninguno, habla la voz predeterminada del motor.
  • boundary se activa antes de cada palabra con charIndex / 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í que pause() lo detiene y resume() dice el resto de la frase desde la última palabra que informó el motor (se activan los eventos pause / resume). En un motor que no informa posiciones de palabras, resume() inicia esa locución de nuevo desde el principio. speaking sigue en true mientras está en pausa; pending es true mientras esperan locuciones en cola.
  • cancel() se activa error con error: '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 (ver ttsSpeak). 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 off

Notas: 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.

Generado a partir del runtime de la app y sus archivos de ejemplo en cada compilación de la documentación. Léelo como Markdown · Todas las familias.

Comprobado con el puente publicado el 2026-10-22.