admin管理员组

文章数量:1289828

Im trying to use the speechSynthesis API. It's working on desktop browsers and mobile Chrome but not mobile Safari.

  const msg = new SpeechSynthesisUtterance("Hello World");
  window.speechSynthesis.speak(msg);

I added a little test and it seems the API is supported on Safari, could it be a permissions issue that it's not working?

  if ("speechSynthesis" in window) {
    alert("yay");
  } else {
    alert("no");
  }

Im trying to use the speechSynthesis API. It's working on desktop browsers and mobile Chrome but not mobile Safari.

  const msg = new SpeechSynthesisUtterance("Hello World");
  window.speechSynthesis.speak(msg);

I added a little test and it seems the API is supported on Safari, could it be a permissions issue that it's not working?

  if ("speechSynthesis" in window) {
    alert("yay");
  } else {
    alert("no");
  }
Share Improve this question asked May 7, 2020 at 13:15 EvanssEvanss 23.2k100 gold badges322 silver badges555 bronze badges 3
  • Is your 'speak' javascript attached to a user interaction? – Frazer Commented May 22, 2020 at 21:33
  • @Frazer no it's based on a timer, however the user has already clicked a button to start the timer. – Evanss Commented May 23, 2020 at 13:47
  • Can you add that bit of the code? – Frazer Commented May 23, 2020 at 19:02
Add a ment  | 

3 Answers 3

Reset to default 4

On my end the issue broke down to proper loading speech synthesis on mobile Safari.

There are some things to check in order:

  • are voices loaded?
  • are voices even installed on your system?
  • is the utterance configured correctly?
  • is the speak function called from within a user interaction event?

The following example summarizes these checks and works on MacOS desktop Browsers plus iOS Safari:

let _speechSynth
let _voices
const _cache = {}

/**
 * retries until there have been voices loaded. No stopper flag included in this example. 
 * Note that this function assumes, that there are voices installed on the host system.
 */

function loadVoicesWhenAvailable (onComplete = () => {}) {
  _speechSynth = window.speechSynthesis
  const voices = _speechSynth.getVoices()

  if (voices.length !== 0) {
    _voices = voices
    onComplete()
  } else {
    return setTimeout(function () { loadVoicesWhenAvailable(onComplete) }, 100)
  }
}

/**
 * Returns the first found voice for a given language code.
 */

function getVoices (locale) {
  if (!_speechSynth) {
    throw new Error('Browser does not support speech synthesis')
  }
  if (_cache[locale]) return _cache[locale]

  _cache[locale] = _voices.filter(voice => voice.lang === locale)
  return _cache[locale]
}

/**
 * Speak a certain text 
 * @param locale the locale this voice requires
 * @param text the text to speak
 * @param onEnd callback if tts is finished
 */

function playByText (locale, text, onEnd) {
  const voices = getVoices(locale)

  // TODO load preference here, e.g. male / female etc.
  // TODO but for now we just use the first occurrence
  const utterance = new window.SpeechSynthesisUtterance()
  utterance.voice = voices[0]
  utterance.pitch = 1
  utterance.rate = 1
  utterance.voiceURI = 'native'
  utterance.volume = 1
  utterance.rate = 1
  utterance.pitch = 0.8
  utterance.text = text
  utterance.lang = locale

  if (onEnd) {
    utterance.onend = onEnd
  }

  _speechSynth.cancel() // cancel current speak, if any is running
  _speechSynth.speak(utterance)
}

// on document ready
loadVoicesWhenAvailable(function () {
 console.log("loaded") 
})

function speak () {
  setTimeout(() => playByText("en-US", "Hello, world"), 300)
}
<button onclick="speak()">speak</button>

Details on the code are added as ments within the snippet.

It may just be a question of timing to get the voices, so if we request them when loading the page they'll be ready before the user clicks the Speak button.

I haven't put in any timeout to keep the example simple.

if ( 'speechSynthesis' in window ) {
  speechSynthesis.cancel(); // removes anything 'stuck'
  speechSynthesis.getVoices();
  // Safari loads voices synchronously so now safe to enable
  speakBtn.disabled = false;
}

const speak = () => {
  const utter = new SpeechSynthesisUtterance();
  utter.text = textToSpeak.value || textToSpeak.placeholder;
  speechSynthesis.speak(utter);
};  

speakBtn.addEventListener('click', speak);
<input type="text" id="textToSpeak" placeholder="1, 2, 3">
<button type="button" id="speakBtn" disabled>Speak</button>

This is a very basic example, not selecting the voice to be used or any language.

If this doesn't work there is another problem. Have you tested that the sound works on your device? Is the "soft mute" on?

As per @Frazer's ment, mobile Safari seems to require that the first utterance happens during a user interaction (even if it's empty!) in order to enable utterances in non-user-interactive places like timeouts/intervals. This works for me:

function speak(msg) {
  speechSynthesis.speak(new SpeechSynthesisUtterance(msg));
}

const startTimerButton = document.getElementById('start-timer');
startTimerButton.addEventListener('click', () => { 
  // Mobile Safari requires an utterance (even a blank one) during
  // a user interaction to enable utterances during timeouts.
  speak('');

  setInterval(() => {
    speak('another five seconds passed');
  }, 5 * 1000);
});
<button id="start-timer">Start</button>

本文标签: javascriptspeechSynthesis not working on mobile Safari even though it39s supportedStack Overflow