window.speechSynthesis and SpeechSynthesisUtterance. Speech plays
through the device speaker without a web permission prompt.function speakResult(text) {
if (
!('speechSynthesis' in window) ||
!('SpeechSynthesisUtterance' in window)
) {
showSpeechUnavailable();
return;
}
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'en-US';
utterance.onerror = (event) => {
console.warn('Speech failed:', event.error);
showSpeechUnavailable();
};
window.speechSynthesis.speak(utterance);
}
| Property | Value |
|---|---|
name | Default |
voiceURI | default |
lang | en-US |
localService | true |
default | true |
speechSynthesis.getVoices() returns the list synchronously. The runtime does
not provide alternate selectable voices or fire voiceschanged, so a voice
picker has no effect.| Property | Behavior |
|---|---|
text | The string to speak. Keep it concise. |
lang | Forwarded to the native speech engine. Available locales can vary; test every language the app offers and retain visible fallback text. |
rate | Clamped to the Web Speech range from 0.1 through 10. |
pitch | Clamped from 0.1 through 2. |
voice | Accepted by the web object, but the runtime uses its default voice. |
volume | Accepted but ignored. Speech uses the user’s system volume. |
const utterance = new SpeechSynthesisUtterance('Directions ready');
utterance.onstart = () => showSpeakingIndicator();
utterance.onend = () => hideSpeakingIndicator();
utterance.onerror = (event) => {
hideSpeakingIndicator();
showSpeechUnavailable();
console.warn('Speech failed:', event.error);
};
speechSynthesis.speak(utterance);
start, end, error, pause, and resume
events. It does not provide word or markup timing, so boundary and mark do not fire.
Use utterance-level events for status instead of word-level highlighting or
synchronized captions.speechSynthesis object itself do not fire. Attach
handlers to the utterance instead.speak() queues an utterance. Decide whether a new message should wait or
replace current speech, and reserve cancel() for the replacement case.pause() stops
the active speech, and resume() speaks that utterance again from the beginning
before continuing the queue. Prefer short utterances over long passages that
need scrubbing.cancel() clears the queue and stops the active utterance. Cancelled utterances
receive end, not error, so track whether a request was superseded:let speechGeneration = 0;
function replaceSpeech(text) {
const generation = ++speechGeneration;
speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.onend = () => {
if (generation === speechGeneration) {
hideSpeakingIndicator();
}
};
utterance.onerror = () => {
if (generation === speechGeneration) {
hideSpeakingIndicator();
showSpeechUnavailable();
}
};
showSpeakingIndicator();
speechSynthesis.speak(utterance);
}
speechSynthesis.speaking, .pending, and .paused when the interface
needs to reflect queue state. Cancel speech when its page is left or its content
is no longer relevant.error and retain the visible result. The runtime reports these codes directly:| Code | Meaning |
|---|---|
not-allowed | The call was not authorized by the page-scoped bridge. |
invalid-argument | The utterance could not be parsed or used. |
synthesis-failed | The native engine could not produce speech. |