Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a SpeechSynthesisUtterance sometimes not fire an 'end' event in Chromium-based browsers?

In both Chrome (v72, W10) and Opera, the following snippet very occasionally does not seem to run the attached end listener to the SpeechSynthesisUtterance, maybe 1 out of 50 times the snippet is run. (Sorry, in the original version of this, it could be reproduced much more easily - now, creating the utterance on button click looks to have made the bug much more rare)

button.onclick = () => {
  console.log('start script');
  button.disabled = true;
  const utt = new SpeechSynthesisUtterance('e');
  utt.addEventListener('end', () => {
    console.log('end event triggered');
  });

  // just for debugging completeness, no errors seem to be thrown though
  utt.addEventListener('error', (err) => {
    console.log('err', err)
  });

  speechSynthesis.speak(utt);
  setTimeout(() => {
    console.log('finished?');
  }, 1500);
};
<button id="button">click</button>

From what I've seen, if the end event ever activates, it will always activate within a given pageload, which is why I disable the button in the above snippet. (you'll have to rerun the snippet many times to see the problem)

You can reproduce it more readily if you run the below snippet in Chrome (72 on W10) with autoplay restrictions disabled. (go to chrome://flags/, change Autoplay policy to No user gesture is required).

(In Opera, it seems to be similarly difficult to reproduce as in the first snippet, unfortunately)

console.log('start script');
function say(text) {
  const utt = new SpeechSynthesisUtterance(text);
  utt.addEventListener('end', () => console.log('end: ' + text));
  
  // just for debugging completeness, no errors seem to be thrown though
  utt.addEventListener('error', (err) => {
    console.log('err on ' + text + ', ', err)
  });
  
  speechSynthesis.speak(utt);
}

say('foo');
say('bar');

Firefox (56) does not have this issue as far as I can see - in it, the end listener always fires properly.

Am I somehow not attaching the listener sufficiently properly, or is this a Chromium bug?

like image 832
Snow Avatar asked Feb 25 '19 07:02

Snow


1 Answers

Edit/Update: @Ouroborus pointed out that this is indeed an open Chromium bug


I fired up Sawbuck and started attempts to reproduce this. When the issue occurs, I consistently see gc activity happening between the 'start script' and 'finished?' logs.

Example of success:

enter image description here

Example of failure:

enter image description here

So, it seems like it may be that the gc process is interfering with the end event being delivered.

To further test this theory I started chrome with the --js-flags="--expose-gc" flag which enables the v8 gc function, allowing one to force garbage collection.

If I modify your test code and add window.gc() before console.log('start script'), I can no longer reproduce the issue (>50 attempts). It's possible this is because it reduces/eliminates the chance that gc occurs during the speech utterance.

It seems that you may be able to prevent the SpeechSynthesisUtterance object from being gc'd by console.log-ing it. This does seem to result in consistent delivery of the event. Obviously preventing their collection is probably not ideal if you're creating a great number of these objects:

button.onclick = () => {
  console.log('start script');
  button.disabled = true;
  const utt = new SpeechSynthesisUtterance('e');
  
  // Prevent garbage collection of utt object
  console.log(utt);

  utt.addEventListener('end', () => {
    console.log('end event triggered');
  });

  // just for debugging completeness, no errors seem to be thrown though
  utt.addEventListener('error', (err) => {
    console.log('err', err)
  });

  speechSynthesis.speak(utt);
  setTimeout(() => {
    console.log('finished?');
  }, 1500);
};
<button id="button">click</button>
like image 152
cody Avatar answered Nov 11 '22 22:11

cody