electron: speechSynthesis.getVoices() returns an empty array

Preflight Checklist

  • I have read the Contributing Guidelines for this project.
  • I agree to follow the Code of Conduct that this project adheres to.
  • I have searched the issue tracker for an issue that matches the one I want to file, without success.

Issue Details

  • Electron Version:

    • ^8.0.2
  • Operating System:

    • Ubuntu 18.04 x64
  • Last Known Working Electron version:

    • Not ^4.x.y, I never tried below

Expected Behavior

An array of voices is expected

Actual Behavior

speechSynthesis.getVoices() returns an empty array always with or without the internet

To Reproduce

Here fiddle link: https://gist.github.com/cfe130888196196153f5240e30d8e4b5

$ git clone https://github.com/whizyrel/app-bible -b electron-devel
$ npm install
$ electron .

Screenshots

Screenshot from 2020-04-01 06-57-30

Additional Information

With or without the internet speechSynthesis.getVoices() returns an empty array. In the browser, it returns an array of SpeechSynthesisVoice using chrome when the internet is connected. Further info: https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/getVoices

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 20 (5 by maintainers)

Most upvoted comments

I don’t know if this is the case but this worked for me.

I think the reason that getVoices() returns an empty array is because they are not there by default. BUT, the following works for me:

const speech = window.speechSynthesis;
if(speech.onvoiceschanged !== undefined)
{

	speech.onvoiceschanged = () => populateVoiceList();

}
function populateVoiceList()
{
	speech.getVoices(); // now should have an array of all voices
}

@LoLei You are right. List of voices are returned after manually reloading the page but this does not fit angular at all. So what i did was load from URL the first time and when it resolves, reload manually and load from URL again. Well, it may not be smart, but it looks fair and is not what i want but I would take it. This is a work around for anyone that might have this issue again, thanks @LoLei. I guess this issue can be closed now, if there is no activity in a long time, I would close it.

summarily,

  • install espeak. I noticed all the voices have espeak-ng in the voiceURI value
...
[
   {voiceURI: '... espeak-ng'}
]
...
  • win.minimize()
  • win.hide()
  • Load from URL
  • reload page
  • Load from URL again
  • show window
win.loadURL(
   url.format({...})
).then(() => {
   win.reload();
   win.loadURL(
      url.format({...})
   ).then(() => win.show());
);

Thanks everyone.

@caarmen: In case this helps, I’ve described in a comment above how I use a workaround that doesn’t rely on any framework like Angular:

Awesome @LoLei, this works! Thanks! 🙇‍♀️

@caarmen: In case this helps, I’ve described in a comment above how I use a workaround that doesn’t rely on any framework like Angular:

So what I ended up doing, in index.html:

  1. On page load: window.speechSynthesis.getVoices() // nonworking but necessary
  2. Reload page
  3. Enjoy working window.speechSynthesis.getVoices()

Of course also with setting a flag to refresh the page only once.

I use something like this to call getVoices on the first page load and then trigger a page reload:

window.addEventListener("load", () => {
  window.speechSynthesis.getVoices();
  if (window.localStorage) {
    if (!localStorage.getItem('refresh')) {
      localStorage['refresh'] = true;
      location.reload(true);
    } else {
      localStorage.removeItem('refresh');
    }
  }
});

@codebytere We found a cumbersome workaround, the underlying issue is still there.

I’m getting the same on Linux Electron 9.0.0.

I have installed speech-dispatcher and espeak-ng. I am starting Electron with app.commandLine.appendSwitch('enable-speech-dispatcher').

Calling window.speechSynthesis.getVoices() at any time after first loading the page, returns an empty array.

In Firefox, it returns

Array(109) [ SpeechSynthesisVoice, SpeechSynthesisVoice, SpeechSynthesisVoice, SpeechSynthesisVoice, SpeechSynthesisVoice, SpeechSynthesisVoice, SpeechSynthesisVoice, SpeechSynthesisVoice, SpeechSynthesisVoice, SpeechSynthesisVoice, … ]

However, the default voice can still be used in Electron. Which is unfortunately in Afrikaans, since it is the first voice in the list.

So using a voice from within Electron is possible, it uses the default voice, but retrieving a list of voices is not possible, and therefore setting the voice to a new one is not possible either.

It can be tested by using this website: https://mdn.github.io/web-speech-api/speak-easy-synthesis/ With a slight modification to get arround a null reference due to the empty list.

I can confirm though that this also happens in Chromium (83.0.4103.61) (chromium --enable-speech-dispatcher) itself, although Chromium reports:

You are using an unsupported command-line flag: --enable-speech-dispatcher. Stability and security will suffer.

However, I also found out that if you let Chromium sit for a long while, and try window.speechSynthesis.getVoices() again, it will load the full array. Or if you reload the page. Sometimes. It does not seem deterministic. As pointed out here, it seem like the speechSynthesis module itself is loaded some time after the page is loaded. This is a distinct event from the voiceschanged event, which I’ve tried incorporating in Chromium to no avail. In Firefox, it works fine.

I can get it working in Electron relatively consistently by starting the Electron app from the command line, then reloading the page manually. Once the initial reload is done, the voices are ready as well.

Another difference between Firefox and Electron/Chromium: In Firefox the voices have their languages set properly in the lang property. E.g.

SpeechSynthesisVoice
default: false
lang: "en-GB"
localService: false
name: "English (Scotland)"
voiceURI: "urn:moz-tts:speechd:English%20(Scotland)?en-GB"

And in Chromium the lang property is missing and the name contains the local synth software for some reason.

SpeechSynthesisVoice
default: true
lang: ""
localService: true
name: "Afrikaans espeak-ng"
voiceURI: "Afrikaans espeak-ng"

So all I can conclude is that something is messed up.

I have looked at https://github.com/electron/electron/issues/586, https://github.com/electron/electron/issues/11585.

EDIT

Turns out it doesn’t solely depend on a reload. The nonworking window.speechSynthesis.getVoices() also needs to be called once before the reload.

So what I ended up doing, in index.html:

  1. On page load: window.speechSynthesis.getVoices() // nonworking but necessary
  2. Reload page
  3. Enjoy working window.speechSynthesis.getVoices()

Of course also with setting a flag to refresh the page only once.

Also reproducible on macOS - i’ll try to take a look soon.

Seems like this has never worked (I tried v4–v9) on macOS, so this isn’t a regression.

https://github.com/electron/electron/pull/14070 seems relevant.