2

My following HTML page correctly speaks the text but it is not speaking in a female voice Microsoft Zira Desktop - English (United States). Question: What I may be missing here and how can we make it speak in a female voice?

Remark: I tried this html in MS Edge and Google Chrome multiple times with and without refreshing the page but it keeps speaking with the same male voice. It seems it is ignoring the msg.voice value in the JavaScript below. I am using Windows 10 - that probably should not matter.

<!DOCTYPE html>
<html>
<head>
<script>
    function myFunction() {
  var msg = new SpeechSynthesisUtterance();
      msg.voice = speechSynthesis.getVoices().filter(function(voice) {
    return voice.name == "Microsoft Zira Desktop - English (United States)"})[0];
    msg.text = document.getElementById("testDiv").textContent;
    window.speechSynthesis.speak(msg);
}
</script>
</head>
<body>

<h2>JavaScript in Head</h2>

<div id="testDiv">SQL Managed Instance gives you an instance of SQL Server but removes much of the <b>overhead</b> of managing a <u>virtual machine</u>. Most of the features available in SQL Server are available in SQL Managed Instance. This option is ideal for customers who want to use instance-scoped features and want to move to Azure without rearchitecting their applications.</div>

<button type="button" onclick="myFunction()">Try it</button>

</body>
</html>

UPDATE

Per a suggestion from user @Frazer, I ran speechSynthesis.getVoices() in my google chrome console and got the following results - that does contain Microsoft Zira .... voice:

enter image description here

Observation:

Following this advice, I moved the the <script> block to end of the body block (just before </body>) but still the same male voice. HOWEVER, when I replaced the voice from Microsoft Zira Desktop - English (United States) to Google UK English Female, the following happens: On the first click of Try it button, the speaker is still the default male, but on every subsequent clicks on this button, I correctly get the Google UK English Female voice. Note: The Microsoft Zira Desktop - English (United States) does nothing in the above scenario. This leads me to believe that this technology still is experiential - as mentioned here.

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
nam
  • 21,967
  • 37
  • 158
  • 332
  • Your code works for me. What happens if you run speechSynthesis.getVoices() in your console? Is Zira one of the voices returned? – Frazer Jul 31 '21 at 07:25
  • @Frazer Thank you for looking into the issue. Per your suggestion I ran `speechSynthesis.getVoices()` in console and it does contain zira. I have added an **UPDATE** section to answer your question. – nam Jul 31 '21 at 14:28
  • What happens if you try one of the Google voices e.g. 'Google UK English Female'? – Frazer Jul 31 '21 at 15:18
  • @Frazer Still exact same (probably default on Windows 10) male voice. On other online apps, when I select a different voice (male or female) from their dropdown, it correctly speaks with the selected voice. It is just not happening on my local html file (shown above). – nam Jul 31 '21 at 15:24
  • What happens if you run these in sequence in the console? And what output do you get?: speechSynthesis.getVoices(); const u = new SpeechSynthesisUtterance('1'); u.voice = speechSynthesis.getVoices()[2]; speechSynthesis.speak(u); – Frazer Jul 31 '21 at 15:55
  • @Frazer After I moved the script tag at the end of the body tag: On the first and third code, I get a female speaker saying 1. On the second code, the output is `SpeechSynthesisVoice {voiceURI: "Microsoft Zira - English (United States)", name: "Microsoft Zira - English (United States)", lang: "en-US", localService: true, default: false} default: false lang: "en-US" localService: true name: "Microsoft Zira - English (United States)" voiceURI: "Microsoft Zira - English (United States)" [[Prototype]]: SpeechSynthesisVoice .....etc. etc.` – nam Jul 31 '21 at 16:10
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/235487/discussion-between-nam-and-frazer). – nam Jul 31 '21 at 16:28

2 Answers2

1

Why Does it Work for Some Browsers?

I have an answer to a similar question here, Why is the voiceschanged event fired on page load?, but I think your situation is sufficiently different to merit a new answer.

First, why does it work sometimes? Because "Microsoft Zira Desktop - English (United States)" is retrieved from the web, through an API call, and this data is not available by the time the next line executes. Basically, you should wait until onvoiceschanged is called before actually calling getVoices() to get the voices.

To quote the docs...

With Chrome however, you have to wait for the event to fire before populating the list, hence the bottom if statement seen below. (Source: MDN WebDocs: SpeechSynthesis.onvoiceschanged) (Emphasis mine.)

If the list doesn't populate, and you don't have the female language available, the male will play by default.

Because Constructor `getVoices()` Makes an API Call, Treat it as Asynchronous

Try running your code like so...

var msg = new SpeechSynthesisUtterance();
var voices = window.speechSynthesis.getVoices();

window.speechSynthesis.onvoiceschanged = function() {
    voices = window.speechSynthesis.getVoices();
};

function myFunction() {
    console.log(voices);
      msg.voice = voices.filter(function(voice) {
    return voice.name == "Microsoft Zira - English (United States)"})[0];
    console.log(msg.voice);
    msg.text = document.getElementById("testDiv").textContent;
    window.speechSynthesis.speak(msg);
}

P.S. Here is my own coding example of how I handle the voices loading on a text-to-audio reader: GreenGluon CMS: text-audio.js Also here: PronounceThat.com pronounce-that.js

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
  • I ran your code (that you modified from my code). When page is first loaded in Google Chrome console, it prints `Before page is fully loaded. Test.html:7 []length: 0[[Prototype]]: Array(0)`. And when I click on the `Try it` button, it prints `Now page is fully loaded.` along with the text shown in the screenshot of **UPDATE** section of my post above. From this test, it seems it should work - but for some reasons, it is not. Web connectivity does not seem to be an issue. Any other suggestions? – nam Jul 31 '21 at 15:04
  • Hrm, I use my code at https://www.pronouncethat.com/ . It's also basically the same thing, just a text-to-audio button thing. Does that work? It seems like you may have a weird, special case, but loading the voices with speechSynthesis is always tricky. – HoldOffHunger Jul 31 '21 at 16:52
  • Oh, hey, just a thought, I'm looking at differences between pronouncethat.com and your code, maybe put `var msg = new SpeechSynthesisUtterance();` outside of the function definition, so, that the voices are definitely loaded at the time you actually call it. Alternatively, move your logic over to `window.speechSynthesis.onvoiceschanged = function () {}`. – HoldOffHunger Jul 31 '21 at 16:56
  • Your site works perfectly with selecting `Microsoft Zira Desktop - English (United States)`. Also, per your suggestion, moved `var msg = new SpeechSynthesisUtterance();` outside the function and it works event without moving the script block to the end of the body block. However, the first click still produces the default male voice. And, it works with `Google UK English Female` but not with `Microsoft Zira Desktop - English (United States)`. And, it works on Chrome, but not with Edge. I agree that I may have a weird, special case. – nam Jul 31 '21 at 17:19
  • @nam Hey, I added some code up above to my answer, that I think *should* resolve your issue 100%. It's in the new, middle section, "CALL YOUR CODE HERE", etc.. I *think* this should solve it. – HoldOffHunger Jul 31 '21 at 20:28
  • First it gave error at `if(voices.length > 0)` saying voices is not defined. So, I added the line `let voices = speechSynthesis.getVoices()` after `var utterance = new SpeechSynthesisUtterance();`. Then it is giving error: `Uncaught ReferenceError: myFunction is not defined at HTMLButtonElement.onclick` at line ``. – nam Jul 31 '21 at 21:41
  • @nam: Oops! sorry! Ha! There's a typo in the name in your code, it's "Microsoft Zira", not "Microsoft Zira Desktop." Look at my code sample, runs fine for me. Also, cleaned up answer. – HoldOffHunger Jul 31 '21 at 21:56
  • Your modified code worked. It seems `Microsoft Zira Desktop - English (United States)` is being used before as mentioned [here](https://stackoverflow.com/a/51010373/1232087), [here](https://stackoverflow.com/a/51937192/1232087), and [here](https://www.ghacks.net/2018/08/11/unlock-all-windows-10-tts-voices-system-wide-to-get-more-of-them/). Moreover, the user Frazer also tested my html with same same language and according to Frazer it works on his side. – nam Jul 31 '21 at 22:34
1

Add the 'disabled' attribute to your button then try this before the /body tag with either Zira or the Chrome voice.

speechSynthesis.onvoiceschanged = () => {
  voices = speechSynthesis.getVoices()
  if (voices.length) document.querySelector("button").disabled = false
}
let voices = speechSynthesis.getVoices()

const  myFunction = () => {
  const msg = new SpeechSynthesisUtterance();
  msg.voice = voices.filter(voice => {
    return voice.name === "Microsoft Zira Desktop - English (United States)"
  })[0];
  msg.text = document.getElementById("testDiv").textContent;
  speechSynthesis.speak(msg);
}
Frazer
  • 1,049
  • 1
  • 10
  • 16
  • One good improvement: On Chrome with `Google UK English Female`, your code works on the first click of the button, as well. What is still not happening: On Chrome, it does not work with `Microsoft Zira Desktop - English (United States)`. On Edge, it keeps using default male voice with both `Microsoft Zira....` or `Google UK English Female`. Not sure what is happening with Microsoft related issue. At least my upvote (+1) for your good effort. – nam Jul 31 '21 at 18:23