I'm dealing with an issue where the onend event is not triggered in certain cases with SpeechSynthesis in the Web Speech api in Chrome (113.0.5672.63). There's this SO thread which discusses the same issue: SpeechSynthesis API onend callback not working but most of the responses in there are pretty stale so I'm hoping it's not an issue to start a new question.
I have this jsfiddle which demonstrates the problem and it seems quite apparent that it's tied to the length of the audio for certain voices. The default jsfiddle is using one of the Google voices (146 - 'Google UK English Male'). Once the problem happens I have to close and reopen Chrome to get it working again. Then if you do anything to reduce the duration of the speech clip (reduce the text or increase the rate), the onend event fires as expected. Some of the other voices don't seem to have a problem (like #0, 'Samantha'), however the other higher quality google voices exhibit the same issue (like 145 - 'Google US English). The voice data I'm citing is from using Chrome on MacOS.
https://jsfiddle.net/opike99/wusq05oa/36/
The above jsfiddle also includes the suggested workaround from here: https://stackoverflow.com/a/35935851/549226
Since it seems that the timing issue is the result of garbage collection and by storing the utterance at window scope, the object won't be garbage collected. However as one can see, the workaround is not 'workarounding' in the jsfiddle code. I thought maybe the iframe in the jsfiddle might be impacting the effectiveness of the workaround so I tried the same code in a different context without an iframe, but still no dice.
Embedded jsfiddle code:
var wordsToSpeak = [
'We propose additional funds for other engine development and for unmanned explorations—explorations which are particularly important for one purpose which this nation will never overlook: the survival of the man who first makes this daring flight.',
'Now end event is fired!',
'You can use global variables to store utterance object.'
];
let voice;
const getVoices = () => {
if (window.speechSynthesis) {
const tmp = speechSynthesis.getVoices();
if (tmp.length !== 0) {
// This voice has a problem.
voice = tmp[146];
// This voice doesn't have a problem.
//voice = tmp[0];
}
}
};
// this object for storing utterances globally
window.utterances = [];
getVoices();
speechSynthesis.addEventListener('voiceschanged', getVoices);
function speak(text) {
var utterance = new SpeechSynthesisUtterance(text);
console.log(voice);
utterance.voice = voice;
utterance.onend = function() {
// this code is not called
console.log(text);
};
// if you will comment this line, end event will not work!
window.utterances.push(utterance);
speechSynthesis.speak(utterance);
}
document.getElementById("x").addEventListener("click", () => {
for (var i = 0; i < wordsToSpeak.length; i++) {
speak(wordsToSpeak[i])
}
});