JavaScript bridge API
Синтез речи
Голоса устройства, звучащие со страницы.
SpeechRecognition веб-стандарт#
webkitSpeechRecognition / SpeechRecognition — speech to text
Пример
Стандартный Web Speech API для диктовки: пользователь говорит, страница получает текст. В Chrome на Android он есть, а в Android WebView нет, поэтому приложение предоставляет его поверх собственного распознавателя речи Android. Код, написанный для Chrome, работает без изменений.
Возвращает: start() начинает слушать; события result содержат event.results[i][0].transcript (а также .confidence, .isFinal); события error содержат event.error (not-allowed, no-speech, audio-capture, network, aborted, language-not-supported, service-not-allowed); end всегда приходит последним. Требуется: микрофон. Сборка включает его сама, когда в вашем коде упоминается SpeechRecognition, а Android спрашивает пользователя при первом запуске 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>Непрерывная диктовка, которая останавливается по запросу и сообщает о каждой ошибке:
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();
}Произнесите команду и выполните её на языке телефона:
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();
}Примечания: continuous = true продолжает слушать несколько предложений подряд, пока вы не вызовете stop(); после трёх тихих раундов он завершается с no-speech, чтобы забытый распознаватель не слушал вечно. stop() выдаёт услышанное; abort() отбрасывает его. Распознавание останавливается, когда приложение уходит в фон, как и при скрытии вкладки Chrome. Использовать микрофон таким образом могут только страницы, принадлежащие приложению (его собственные файлы или сайт создателя); любой другой сайт получает not-allowed. API отсутствует, если на телефоне нет службы распознавания речи, поэтому проверяйте его, как показано выше. Внутренние транспорты: __srStart, __srStop, __srAbort, __srAvailable; результаты приходят как appmint:sr. Приложения, созданные ИИ, используют speech.start() из @/lib/appmintNative.
speechSynthesis веб-стандарт#
window.speechSynthesis + SpeechSynthesisUtterance
Пример
Читает текст вслух собственным движком синтеза речи телефона, используя обычный Web Speech API.
Возвращает: speechSynthesis.speak(u) ничего не возвращает; о том, что произошло, сообщают события высказывания start / boundary / end / error / pause / resume (как свойства onstart … или через addEventListener). getVoices() синхронно возвращает массив { voiceURI, name, lang, localService, default }. Требуется: ничего. В Android WebView нет собственного работающего речевого API; приложение устанавливает этот при запуске документа в каждом фрейме.
Произнесите фразу и узнайте, когда она закончена:
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
};Голоса появляются через мгновение после запуска. Прочитайте их снова, когда сработает 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);
}Чтение статьи вслух с одной кнопкой «Пауза / Продолжить», которая подсвечивает читаемый абзац:
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';
}Примечания:
volume(0-1),voice(точный голос изgetVoices()),rate,pitchиlang— всё это доходит до движка телефона. При наличииvoiceиlangпобеждает голос; если нет ни того, ни другого, говорит голос движка по умолчанию.boundaryсрабатывает перед каждым словом сcharIndex/charLength(Android 8+; движки, которые не сообщают позиции слов, их не отправляют).pause()/resume(): Движок Android не умеет приостанавливаться посреди слова, поэтомуpause()останавливает его, аresume()произносит остаток фразы начиная с последнего слова, о котором сообщил движок (срабатывают событияpause/resume). Если движок не сообщает позиции слов,resume()начинает это высказывание заново с начала.speakingостаётсяtrueво время паузы;pendingравноtrue, пока ждут высказывания в очереди.cancel()срабатываетerrorсerror: 'interrupted'для произносимого высказывания и'canceled'для стоящих в очереди, как велит стандарт, — не показывайте их как сбои.- Если на телефоне нет голосовых данных для языка, движок всё равно может говорить на языке по умолчанию. Сначала проверьте
getVoices()для нужного языка. - Сырые события, на которых это построено, —
appmint:tts(см.ttsSpeak). Они не нужны, если вы используете этот API.
__srAbort мост#
window.WebToApk.__srAbort(id: String)
SpeechRecognition.abort(): немедленно остановить и отбросить.
Пример
Описано вместе с WebToApk.__srStart — пример там показывает и этот.
Внутренний транспорт для webkitSpeechRecognition / SpeechRecognition (запуск, остановка, прерывание и наличие распознавателя на телефоне). Страницы используют стандартный API; никогда не вызывайте это напрямую, потому что методы __ могут измениться без предупреждения.
Возвращает: __srStart(id, options) возвращает "", когда начинается прослушивание, или имя ошибки Web Speech (not-allowed, service-not-allowed). Всё дальнейшее приходит как события appmint:sr: {id, type} для start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} и {id, type:'error', error}. Требуется: микрофон (включается по форме вызова).
Используйте публичный API:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Непрерывная диктовка с текстом в реальном времени и выбором языка:
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();
}Сообщайте об ошибках, например об отказе в доступе к микрофону:
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');Примечания: Реализовано в оболочке приложения (SpeechRecognitionBridge.kt) поверх SpeechRecognizer Android. __srStart отклоняется для страниц, не принадлежащих приложению.
__srAvailable мост#
window.WebToApk.__srAvailable(): Boolean
Установлена ли служба распознавания речи (webkitSpeechRecognition существует только тогда).
Пример
Описано вместе с WebToApk.__srStart — пример там показывает и этот.
Внутренний транспорт для webkitSpeechRecognition / SpeechRecognition (запуск, остановка, прерывание и наличие распознавателя на телефоне). Страницы используют стандартный API; никогда не вызывайте это напрямую, потому что методы __ могут измениться без предупреждения.
Возвращает: __srStart(id, options) возвращает "", когда начинается прослушивание, или имя ошибки Web Speech (not-allowed, service-not-allowed). Всё дальнейшее приходит как события appmint:sr: {id, type} для start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} и {id, type:'error', error}. Требуется: микрофон (включается по форме вызова).
Используйте публичный API:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Непрерывная диктовка с текстом в реальном времени и выбором языка:
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();
}Сообщайте об ошибках, например об отказе в доступе к микрофону:
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');Примечания: Реализовано в оболочке приложения (SpeechRecognitionBridge.kt) поверх SpeechRecognizer Android. __srStart отклоняется для страниц, не принадлежащих приложению.
__srStart мост#
window.WebToApk.__srStart(id: String, optionsJson: String): String
SpeechRecognition.start(): "" при запуске (события приходят как `appmint:sr`), иначе имя ошибки Web Speech. С проверкой источника: сказанное пользователем — это его данные.
Пример
Внутренний транспорт для webkitSpeechRecognition / SpeechRecognition (запуск, остановка, прерывание и наличие распознавателя на телефоне). Страницы используют стандартный API; никогда не вызывайте это напрямую, потому что методы __ могут измениться без предупреждения.
Возвращает: __srStart(id, options) возвращает "", когда начинается прослушивание, или имя ошибки Web Speech (not-allowed, service-not-allowed). Всё дальнейшее приходит как события appmint:sr: {id, type} для start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} и {id, type:'error', error}. Требуется: микрофон (включается по форме вызова).
Используйте публичный API:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Непрерывная диктовка с текстом в реальном времени и выбором языка:
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();
}Сообщайте об ошибках, например об отказе в доступе к микрофону:
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');Примечания: Реализовано в оболочке приложения (SpeechRecognitionBridge.kt) поверх SpeechRecognizer Android. __srStart отклоняется для страниц, не принадлежащих приложению.
__srStop мост#
window.WebToApk.__srStop(id: String)
SpeechRecognition.stop(): прекратить слушать и выдать услышанное.
Пример
Описано вместе с WebToApk.__srStart — пример там показывает и этот.
Внутренний транспорт для webkitSpeechRecognition / SpeechRecognition (запуск, остановка, прерывание и наличие распознавателя на телефоне). Страницы используют стандартный API; никогда не вызывайте это напрямую, потому что методы __ могут измениться без предупреждения.
Возвращает: __srStart(id, options) возвращает "", когда начинается прослушивание, или имя ошибки Web Speech (not-allowed, service-not-allowed). Всё дальнейшее приходит как события appmint:sr: {id, type} для start/audiostart/speechstart/speechend/audioend/end, {id, type:'result', final, alternatives:[{transcript, confidence}]} и {id, type:'error', error}. Требуется: микрофон (включается по форме вызова).
Используйте публичный API:
const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.onresult = (e) => console.log(e.results[0][0].transcript);
rec.start();Непрерывная диктовка с текстом в реальном времени и выбором языка:
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();
}Сообщайте об ошибках, например об отказе в доступе к микрофону:
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');Примечания: Реализовано в оболочке приложения (SpeechRecognitionBridge.kt) поверх SpeechRecognizer Android. __srStart отклоняется для страниц, не принадлежащих приложению.
__ttsSpeak мост#
window.WebToApk.__ttsSpeak(json: String)
Полный вызов speak из полифила speechSynthesis: `{text, lang, rate, pitch, volume (0-1), voice (имя из getVoices() или ''), id}`. [ttsSpeak] остаётся для страниц, которые вызывают мост напрямую; в нём нет громкости и голоса.
Пример
Внутренний транспорт для speechSynthesis.speak(utterance): полное высказывание — текст, язык, скорость, высота тона, громкость и выбранный голос — одной JSON-строкой. Страницы используют стандартный API speechSynthesis; никогда не вызывайте это напрямую.
Возвращает: ничего. Ход выполнения приходит как события appmint:tts, которые слой speechSynthesis превращает в события высказывания start / boundary / end / error. Требуется: ничего.
Стандартный API, который это обеспечивает:
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);
}Выбор голоса, который пользователь делает один раз; каждое следующее высказывание использует именно этот голос:
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);
}Подсказки для тренировки: тихо и быстро для обратного отсчёта, на полной громкости и выше — для смены:
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);Примечания: JSON — это {text, lang, rate, pitch, volume, voice, id} (voice — имя из getVoices() или ''). Прежний ttsSpeak(text, lang, rate, pitch, id) остаётся для страниц, которые вызывают мост напрямую; в нём нет громкости и голоса.
ttsCancel мост#
window.WebToApk.ttsCancel()
Обеспечивает полифил window.speechSynthesis, который устанавливается при запуске документа в каждом фрейме, — страницам следует использовать СТАНДАРТНЫЙ API speechSynthesis, а эти вызовы будут сделаны за них. В Android WebView нет собственного работающего speechSynthesis, поэтому и существует такая подмена.
Описано в своей группе, Синтез речи, а не отдельно.
Пример
Немедленно останавливает речь и отбрасывает всё, что ещё ждало произнесения. Обычно страницы вызывают стандартный speechSynthesis.cancel(), который вызывает это за вас.
Возвращает: ничего. Требуется: ничего.
Публичный способ (используйте его):
document.getElementById('stop-reading').addEventListener('click', function () {
if ('speechSynthesis' in window) speechSynthesis.cancel();
});Прямой вызов:
if (window.WebToApk && typeof window.WebToApk.ttsCancel === 'function') {
window.WebToApk.ttsCancel();
}Произносите только самый новый статус и отбрасывайте всё более старое, что ещё стоит в очереди (отслеживание доставки, счёт матча):
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Примечания: Речь, которая прервана или отброшена, не отправляет события appmint:tts end или error, поэтому onend высказывания не срабатывает после отмены. Если вы сами отслеживаете «сырые» идентификаторы ttsSpeak, забудьте их при вызове ttsCancel().
ttsGetVoices мост#
window.WebToApk.ttsGetVoices(): String
JSON-массив установленных голосов: [{name, lang, default}]. Пуст, пока движок не завершит инициализацию, — тогда страница получает 'voiceschanged' и запрашивает список снова, точно как в асинхронном контракте getVoices() в Chrome.
Пример
Перечисляет голоса синтеза речи, установленные на телефоне. Обычно страницы используют стандартный speechSynthesis.getVoices(), который вызывает это за вас.
Возвращает: JSON-строку синхронно: [{ "name": "en-us-x-sfg-local", "lang": "en-US", "default": false }, ...]. Пока движок не запущен, это "[]". Требуется: ничего.
Публичный способ (используйте его):
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;
}Прямой вызов (помните 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;
}
});Говорите на языке телефона, если для него установлен голос, иначе пусть движок использует голос по умолчанию:
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');
}Примечания: lang — тег BCP-47, например en-US или ta-IN. name голоса — собственный идентификатор движка, и при произнесении выбрать голос по имени нельзя; используется только его язык.
ttsIsSpeaking мост#
window.WebToApk.ttsIsSpeaking(): Boolean
Обеспечивает полифил window.speechSynthesis, который устанавливается при запуске документа в каждом фрейме, — страницам следует использовать СТАНДАРТНЫЙ API speechSynthesis, а эти вызовы будут сделаны за них. В Android WebView нет собственного работающего speechSynthesis, поэтому и существует такая подмена.
Описано в своей группе, Синтез речи, а не отдельно.
Пример
Сообщает, говорит ли движок синтеза речи телефона в данный момент. Обычно вместо этого страницы используют speechSynthesis.speaking или onend высказывания.
Возвращает: true / false синхронно. false до запуска движка. Требуется: ничего.
Публичный способ (используйте его):
if ('speechSynthesis' in window && speechSynthesis.speaking) {
speechSynthesis.cancel();
}Прямой вызов обращается к самому движку, поэтому он точен, даже если речь была запущена через «сырой» 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);Подождите, пока приложение закончит говорить, прежде чем начинать запись, чтобы микрофон не ловил собственный голос приложения:
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
});Примечания: Не опрашивайте слишком часто; событие (onend или appmint:tts с event: 'end') лучше, если вы сами запустили речь.
ttsSpeak мост#
window.WebToApk.ttsSpeak(text: String, lang: String, rate: Double, pitch: Double, utteranceId: String)
Обеспечивает полифил window.speechSynthesis, который устанавливается при запуске документа в каждом фрейме, — страницам следует использовать СТАНДАРТНЫЙ API speechSynthesis, а эти вызовы будут сделаны за них. В Android WebView нет собственного работающего speechSynthesis, поэтому и существует такая подмена.
Описано в своей группе, Синтез речи, а не отдельно.
Пример
Начинает произносить текст движком синтеза речи телефона. Обычно страницы используют стандартный API speechSynthesis, который вызывает это за вас.
Возвращает: ничего. Ход выполнения приходит позже как событие appmint:tts (и хук window.onAppMintTts(detail)) с detail = { id, event }, где event — это 'start', 'end' или 'error', а id — переданный вами utteranceId. Требуется: ничего.
Публичный способ (используйте его):
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);
}Прямой вызов, если нужны собственные идентификаторы. Аргументы: 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'); });Прочитайте список шагов по порядку со своими идентификаторами и подсвечивайте произносимый шаг:
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);
});
});Примечания: Высказывания встают в очередь одно за другим. lang может быть '' (голос движка по умолчанию); rate и pitch ограничиваются диапазоном 0,1–4. Если движок ещё запускается, текст ждёт и произносится, когда он готов; если движок запустить не удаётся, каждое ожидающее высказывание получает 'error'. Используйте собственный префикс идентификатора; идентификаторы, начинающиеся с wta_u, принадлежат слою speechSynthesis. На Android 8+ appmint:tts также передаёт {id, event: 'boundary', charIndex, charLength} перед каждым словом. В этом прямом вызове нет громкости и голоса — они есть в speechSynthesis.
ttsWarmUp мост#
window.WebToApk.ttsWarmUp()
Обеспечивает полифил window.speechSynthesis, который устанавливается при запуске документа в каждом фрейме, — страницам следует использовать СТАНДАРТНЫЙ API speechSynthesis, а эти вызовы будут сделаны за них. В Android WebView нет собственного работающего speechSynthesis, поэтому и существует такая подмена.
Описано в своей группе, Синтез речи, а не отдельно.
Пример
Заранее запускает движок синтеза речи телефона, чтобы первый speak() не запаздывал, а getVoices() заполнился. Слой speechSynthesis приложения уже вызывает это при запуске страницы, поэтому страницам это почти никогда не нужно.
Возвращает: ничего. Когда движок готов, срабатывает событие appmint:tts с detail = { id: '', event: 'voiceschanged' } (и выполняется speechSynthesis.onvoiceschanged). Требуется: ничего.
Публичный способ (используйте его): просто читайте голоса, когда они готовы.
if ('speechSynthesis' in window) {
speechSynthesis.onvoiceschanged = function () {
console.log('voices:', speechSynthesis.getVoices().length);
};
}Прямой вызов, затем дождитесь события готовности:
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
}Запустите движок, когда открывается экран «Прочитать вслух», и включите его кнопку, когда движок готов:
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;
});Примечания: voiceschanged срабатывает один раз, когда движок завершает запуск. Если он уже запущен (например, потому что его «разогрел» слой speechSynthesis), вы не получите его снова; вызовите ttsGetVoices() напрямую.