5

So I am using a modified script to try to play some text from the Web Speech API.

The code was originally here:

Chrome Speech Synthesis with longer texts

Here's my modified variant:

function googleSpeech(text, rate) {
    if (!reading) {
        speechSynthesis.cancel();
        if (timer) {
            clearInterval(timer);
        }
        let msg = new SpeechSynthesisUtterance();
        let voices = window.speechSynthesis.getVoices();
        msg.voice = voices[63];
        msg.voiceURI = 'native';
        msg.volume = 1; // 0 to 1
        msg.rate = rate; // 0.1 to 10
        msg.pitch = 1; //0 to 2
        msg.text = text;
        msg.lang = 'zh-CN';

        msg.onerror = function (e) {
            speechSynthesis.cancel();
            reading = false;
            clearInterval(timer);
        };

        msg.onpause = function (e) {
        };

        msg.onboundary = function (event) {
        };

        msg.onend = function (e) {
            speechSynthesis.cancel();
            reading = false;
            clearInterval(timer);
        };

        speechSynthesis.onerror = function (e) {
            speechSynthesis.cancel();
            reading = false;
            clearInterval(timer);
        };

        console.log(msg);
        speechSynthesis.speak(msg);

        timer = setInterval(function () {
            if (speechSynthesis.paused) {
                speechSynthesis.resume();
            }

        }, 100);

        reading = true;
    }

}

I am able to get this to play ONCE. Whenever I try to get it to play again, it doesn't work - this is despite running speechSynthesis.cancel();. When I reload the page, everything works fine again - for one playback.

Is there any consistent way to play the text again? It seems like this might be related to the many bugs in the Web Speech API. This is on Chrome 68.

Here's a sample of the text I am playing:

我是一个兵。来自老百姓。 打败了日本狗强盗。 消灭了蒋匪军。
Steven Matthews
  • 9,705
  • 45
  • 126
  • 232
  • And did you actually tried without this workaround for a 2014 bug? Reading this sentence directly with no convoluted workaround whatsoever just work on my chrome 68 on osX. – Kaiido Aug 05 '18 at 03:48
  • Bug still seems to exist in 2018 version as far as I can tell. At least according to reports I was reading. I know I would frequently have playback stop and not start again – Steven Matthews Aug 05 '18 at 03:52
  • Except for undefined, your code works fine. Why doesn't it work in some cases? Not sure. How is it called? Voice Synth shouldn't be more complicated than: `var ut = new SpeechSynthesisUtterance(); ut.text = "hello"; window.speechSynthesis.speak(ut);` – HoldOffHunger Aug 05 '18 at 04:52

3 Answers3

4

Your code worked as it is, except I had to define reading = false and timer = false before the function.

My observation is when you pass rate value not between 0.1 to 1o, your function is called only once. You may have to check your rate values.

Additionally, if rate values not between specified, your onend event doesn't get a call.

My system is Mac and chrome is Version 67.0.3396.99 (Official Build) (64-bit)

<script type="text/javascript">
    

reading = false;
timer = false;
function googleSpeech(text, rate) {
    if (!reading) {
        speechSynthesis.cancel();
        if (timer) {
            clearInterval(timer);
        }
        let msg = new SpeechSynthesisUtterance();
        let voices = window.speechSynthesis.getVoices();
        msg.voice = voices[63];
        msg.voiceURI = 'native';
        msg.volume = 1; // 0 to 1
        msg.rate = rate; // 0.1 to 10
        msg.pitch = 1; //0 to 2
        msg.text = text;
        msg.lang = 'zh-CN';

        msg.onerror = function (e) {
            speechSynthesis.cancel();
            reading = false;
            clearInterval(timer);
        };

        msg.onpause = function (e) {
        };

        msg.onboundary = function (event) {
        };

        msg.onend = function (e) {
            speechSynthesis.cancel();
            reading = false;
            clearInterval(timer);
        };

        speechSynthesis.onerror = function (e) {
            speechSynthesis.cancel();
            reading = false;
            clearInterval(timer);
        };

        console.log(msg);
        speechSynthesis.speak(msg);

        timer = setInterval(function () {
            if (speechSynthesis.paused) {
                speechSynthesis.resume();
            }

        }, 100);

        reading = true;
    }

}
</script>

<button  onclick="googleSpeech('我是一个兵。来自老百姓。 打败了日本狗强盗。 消灭了蒋匪军。',1)"> Play </button>
Kishor Pawar
  • 3,386
  • 3
  • 28
  • 61
  • I had reading and timer set elsewhere to false (or, sometimes, null), always with this problem. I am consistently testing with a rate value of 5. Your snippet works here, however my instance does not work every time, even when using your changes. It runs sometimes, but sometimes it just completely stops and won't run again. – Steven Matthews Aug 05 '18 at 20:24
  • Interesting, I think it may be an interaction with some of my code that is wrappering this function that is causing the issue. When testing it outside of this wrapper, your code seems to work fine, but inside of it I have the same issue. I'll test and report back. While that isn't technically the solution, if that's true, I'll award the points to you as you led me in that direction. – Steven Matthews Aug 05 '18 at 20:51
  • I do notice that I get a slightly odder sounding voice the second time I run it, and also some garbled noise that sounds like incredibly rapid talking, in another voice. – Steven Matthews Aug 05 '18 at 20:52
  • Testing it further, when putting the onclick event inside the button, it always seems to work. When attempting to bind it to a click event on the button, it only plays once. – Steven Matthews Aug 05 '18 at 21:25
  • Andy I haven't worked on this API before, the observation is out of debugging your code. You should put more consoles to isolate the issue. Yes I also observed sound dispersion. You may have to expose more code, that may help us debugging more. – Kishor Pawar Aug 06 '18 at 00:54
  • I have been testing and I'm finding that it seems to actually be related to the rate property. Certain values seem like they prevent the speech synthesis process from working a second time. I can confirm that 1 and .5 both work, however 2.7 does not (even though up to 10 should theoretically be supported) – Steven Matthews Aug 06 '18 at 16:14
  • Yep, I have been able to confirm the bug, it's related to rate. Rather than .1 to 10 for rate, Chrome 68 seems to only support values between .5 and 2. When I am using anything beyond those values, it only plays once. When I use a value within that range, it plays as often as I want it to play. – Steven Matthews Aug 06 '18 at 16:20
  • I am going to award you the points as you led to this conclusion, but I am going to post another answer answering what the exact bug was. – Steven Matthews Aug 06 '18 at 16:21
  • Thank you Andy. You should try asking the same question on Google group too. And what did you find about sound dispersion? – Kishor Pawar Aug 06 '18 at 16:22
  • 1
    I believe sound dispersion is related to trying to play a sound outside of the smaller range as it does not seem to happen when I am using a value between .5 and 2.0 for rate. I'll post what I've found on the Google group as well, but I have a solution good enough for my needs now. – Steven Matthews Aug 06 '18 at 16:25
  • I would suggest you update my answer or write your own so that community gets the exact analysis. – Kishor Pawar Aug 06 '18 at 16:29
  • 1
    I did write my own answer, but I awarded the points to you as you led me to the solution. If you refresh this page, you'll see my answer. – Steven Matthews Aug 06 '18 at 16:30
1

let reading = false;
let timer;
// let VV="ja-JP";
let VV = 'en-US';


function handleSaying(msg, text, rate) {
  let voices = window.speechSynthesis.getVoices();


  // console.log(voices);
  msg.voice = voices.filter(v => v.lang === VV)[0];

  // console.log("voice: ", msg.voice);
  msg.voiceURI = 'native';
  msg.volume = 1; // 0 to 1
  msg.rate = rate; // 0.1 to 10
  msg.pitch = 1; //0 to 2
  msg.text = text;
  msg.lang = VV;

  msg.onerror = function(e) {
    speechSynthesis.cancel();
    reading = false;
    clearInterval(timer);
  };

  msg.onpause = function(e) {};

  msg.onboundary = function(event) {};

  msg.onend = function(e) {
    console.log("On end...");
    // speechSynthesis.cancel();
    reading = false;
    clearInterval(timer);
  };

  speechSynthesis.onerror = function(e) {
    speechSynthesis.cancel();
    reading = false;
    clearInterval(timer);
  };

  speechSynthesis.speak(msg);

  timer = setInterval(function() {
    if (speechSynthesis.paused) {
      speechSynthesis.resume();
    }

  }, 100);

  reading = true;
}


function googleSpeech(text, rate) {

  if (!reading) {
    speechSynthesis.cancel();
    if (timer) {
      clearInterval(timer);
    }
    let msg = new SpeechSynthesisUtterance();

    // Here is the problem -- if the voices are ALREADY loaded from an earlier attempt
    // onvoiceschanged does not fire a second time

    if (window.speechSynthesis.getVoices().length > 0) {
      handleSaying(msg, text, rate);
    }


    // wait on voices to be loaded before fetching list
    window.speechSynthesis.onvoiceschanged = function() {
      handleSaying(msg, text, rate);
    }

  };

}
<button type="button" onclick="googleSpeech('The quick brown fox jumped over the lazy dogs', 5)">English</button>

I've modified your code to play the voice twice and I commented in the code what the issue was. I ran into this problem before -- its a puzzler.

I've also had the issue of the onvoicechanged firing MORE than once as it loaded voices then loaded a few more. I didn't put in the code to verify the voice that we want exists.

My system wouldn't play whatever language you were trying to use, so I changed to English text and voice, but that is easy enough to change back.

I also removed the hard-coded reference to a specific voice number, since those can change. Instead I look for the first voice matching the language ID.

You may want to reference this answer.

Getting the list of voices in speechSynthesis of Chrome (Web Speech API)

Jeremy J Starcher
  • 23,369
  • 6
  • 54
  • 74
  • The bug that has been linked by OP was caused by non-native voices and long texts. So changing the voice and the text length might not be a good step into an answer here. – Kaiido Aug 05 '18 at 03:47
  • Unfortunately, it wasn't a voice that I had available so I had to get the code running with what I had. I *believe* the issue was caused by the event not firing a second time, which should be the root problem, regardless of voice chosen. – Jeremy J Starcher Aug 05 '18 at 03:49
  • @JeremyJStarcher, See my answer, the observation is irrespective of language. – Kishor Pawar Aug 05 '18 at 04:05
1

The answer actually turned out to be related to the value of the rate property.

While the official documentation for the Web Speech API says that it supports a value of .1 to 10 for rate, it appears that Chrome (at least Chrome 68, the version I am running) does not fully support a rate outside of the range of .5 to 2.

Anything outside this range causes sound dispersion and breaks after one use of the API and the sound will not work anymore until page refresh.

Steven Matthews
  • 9,705
  • 45
  • 126
  • 232