Reputation: 9122
Running a speechSynthesis.speak(utterance) on Chrome with "Google UK English Female" voice, and sometimes, randomly (seems like a bug) - a male voice will be spoken instead.
Any ideas for a fix / workaround?
It seems to be indeed a Google Chrome bug. Discussion and updates on its bug ticket: https://bugs.chromium.org/p/chromium/issues/detail?id=1344469
Here's my code, you can run it with 'Run code snippet'.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<title>Test Google Voices Bug</title>
</head>
<body>
<select id="voice_selector"></select>
<div>
<button id="toggle_speech_btn" onclick="toggleSpeak()">Speak</button>
</div>
<br/><br/>
<ul id="voicesList"></ul>
<main>
<p>1. Some text to read. </p>
<p>2. Hello!</p>
<p>3. Ronen. </p>
<p>4. Some more text to read. </p>
<p>5. Some text to read. </p>
<p>6. Some text to read. </p>
<p>7. Some text to read. </p>
<p>8. Some text to read. </p>
<p>9. Some text to read. </p>
<p>10. Some text to read. </p>
</main>
<script>
function populateVoices(voices) {
console.log('init tts with voices: ', voices);
let el = document.querySelector('#voice_selector');
if (el) {
for (const voice of voices) {
let option = document.createElement("option");
option.innerHTML = voice.name + ", " + voice.lang + ", " + voice.voiceURI;
option.value = voice.voiceURI;
el.append(option);
}
}
document.getElementById('voice_selector').value = "Google UK English Female";
}
function onStart (ev) {
console.log('tts started, with ev = ', ev);
currentEl.scrollIntoView();
}
function onEnd(ev) {
console.log('tts done, with ev = ', ev);
if (shouldSpeak) {
caretProgress();
if (currentEl) {
speakOutText(currentEl.textContent);
} else {
speak(); // ie start from beginning
}
}
}
function speakOutText(txt) {
let utter = new SpeechSynthesisUtterance(txt);
let selectedVoiceURI = document.getElementById('voice_selector').value;
utter.voice = speechSynthesis.getVoices().filter((voice)=>{
if (voice.voiceURI==selectedVoiceURI) {
return voice;
}
})[0];
utter.onstart = onStart;
utter.onend = onEnd;
speechSynthesis.speak(utter);
}
let voices = speechSynthesis.getVoices();
if (voices && voices.length>0) {
populateVoices(voices);
}
speechSynthesis.onvoiceschanged = () => {
voices = speechSynthesis.getVoices();
populateVoices(voices);
}
let shouldSpeak = false;
let currentEl;
function speak() {
shouldSpeak = true;
if (!currentEl) {
caretProgress();
}
document.getElementById('toggle_speech_btn').innerHTML = "Stop";
speakOutText(currentEl.textContent);
}
function stop() {
shouldSpeak = false;
document.getElementById('toggle_speech_btn').innerHTML = "Speak";
}
function toggleSpeak() {
if (shouldSpeak) {
stop();
} else {
speak();
}
}
function caretProgress() {
if (!currentEl) {
currentEl = document.querySelector('main');
currentEl = currentEl.firstElementChild;
} else {
if (currentEl instanceof Element)
currentEl = currentEl.nextElementSibling;
}
}
</script>
</body>
</html>
Upvotes: 2
Views: 990
Reputation: 303
I wish I had a better answer for you but I agree it's a browser bug, I can't find anything wrong with your code and haven't been able to find a workaround. Additionally from what I have read online (and experienced myself) the speechSynthesis
API is super buggy and inconsistent across different browsers / devices.
My advice would therefore be to use an API such as Google's Text-toSpeech or IBM's Watson Text to Speech Voices, you should get consistent results back but obviously they are paid services, I'm not aware of any good ones you can use for free but maybe you can find one, kind of surprised there doesn't seem to be a good open source js library for it.
Worth noting that most of the paid services will give you a certain amount for free though, for example Watson will give you 10k characters per month, and about £0.015 per thousand chars there after so depending on your use case it might still be free.
Upvotes: 3