Chinese text plays once with Web Speech API, but not a second time
Asked Answered
T

3

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:

我是一个兵。来自老百姓。 打败了日本狗强盗。 消灭了蒋匪军。
Tactic answered 2/8, 2018 at 22:21 Comment(3)
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.Mckee
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 againTactic
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);Indices
T
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.

Tactic answered 6/8, 2018 at 16:27 Comment(0)
A
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>
Alecto answered 5/8, 2018 at 3:54 Comment(12)
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.Tactic
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.Tactic
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.Tactic
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.Tactic
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.Alecto
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)Tactic
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.Tactic
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.Tactic
Thank you Andy. You should try asking the same question on Google group too. And what did you find about sound dispersion?Alecto
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.Tactic
I would suggest you update my answer or write your own so that community gets the exact analysis.Alecto
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.Tactic
S
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)

Sheave answered 5/8, 2018 at 3:37 Comment(3)
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.Mckee
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.Sheave
@JeremyJStarcher, See my answer, the observation is irrespective of language.Alecto
T
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.

Tactic answered 6/8, 2018 at 16:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.