In JavaScript, for text to speech, when the voiceschanged event was being listened to, nothing happened with an array of voices? [duplicate]

爷,独闯天下 提交于 2020-03-02 07:52:06

问题


Related to this text to speech question, I have the following code:

First of all, I am not sure if this is the best way to write the code, because it is initializing, and then setting a listener to "call itself". (seems a little bit hacky).

Second, although the listeners got "notified", the voices.length is still 0. Nothing really happens with an array of voices. I had to uncomment this line:

// voices = synth.getVoices();   // this line should not be needed

so that it can come back with an array of voices, but even so, the first word was not pronounced. Why does synth.getVoices() need to be called twice, and why did the first word not get pronounced? It would appear you only have to call synth.getVoices() once.

Note that if you try it as a snippet, no voice will be given out (due to iframe or security reasons? To hear something, the code needs to run in developer's console).

(one note when I was debugging: if all the voices were obtained, and the last 3 lines were run again, the second and third line became the same. It looks like some kind of "speech end" event needs to be listened to, to serialize them one by one -- maybe using a promise or async function. But then further debugging showed that it seemed a new instance of SpeechSynthesisUtterance is needed each time, so I moved the let msg = new SpeechSynthesisUtterance(); to inside of the last else { } and running those 3 lines have no problem).

So let me hide the original snippet:

let msg, synth, voices;

function foo(phrase) {
  if (!voices) {
    msg = new SpeechSynthesisUtterance();
    synth = window.speechSynthesis;
    voices = synth.getVoices();

    console.log("Waiting 01", voices);
    synth.addEventListener('voiceschanged', function() {
      foo(phrase);
    });
  } else if (voices.length === 0) {
    // this section is needed if foo() is called twice or multiple times in a row initially

    console.log("Waiting 02", voices);

    // voices = synth.getVoices();   // this line should not be needed
    synth.addEventListener('voiceschanged', function() {
      foo(phrase);
    });
  } else {
    console.log("How many voices", voices.length);

    // the voices are ready and could be changed here,
    // but since each system is different, so it won't be
    // changed here:
    // msg.voice = voices[0];
    msg.text = phrase;
    synth.speak(msg);
  }
}

foo("Hello");
foo("World");
foo("a third line");

and show the improved version (which still has the same problem):

let msg, synth, voices;

function foo(phrase) {
  if (!voices) {
    synth = window.speechSynthesis;
    voices = synth.getVoices();

    console.log("Waiting 01", voices);
    synth.addEventListener('voiceschanged', function() {
      foo(phrase);
    });
  } else if (voices.length === 0) {
    // this section is needed if foo() is called twice or multiple times in a row initially.
    // synth.getVoices() has been called and we shouldn't need to call it again.
    // but if voices.length is still 0 we just again listen on the voiceschanged event and when ready, call foo(phrase)

    console.log("Waiting 02", voices);

    // voices = synth.getVoices();   // this line should not be needed
    synth.addEventListener('voiceschanged', function() {
      foo(phrase);
    });
  } else {
    let msg = new SpeechSynthesisUtterance();

    console.log("How many voices", voices.length);

    // the voices are ready and could be changed here,
    // but since each system is different, so it won't be
    // changed here:
    // msg.voice = voices[0];
    msg.text = phrase;
    synth.speak(msg);
  }
}

foo("Hello");
foo("World");
foo("a third line");

回答1:


OK, I think I found the issue.

The issue is with voices = synth.getVoices();

That's the recommended way to get the voices in the sample code, but note that in similar situation for event driven or "future value" or promise programming, the value is not there. It is in the future, possibly in the event object, or provided to the callback (provided to the handler or the "listener" or "observer"). (Or we might think, voices is an array. Can't it just get populated? The answer seems like a "no" in this case.)

In this case, the event doesn't seem to have the voices in it. So we need to call voices = synth.getVoices(); again when the handler gets called, so the code becomes:

(Update: it seems some sample code I saw didn't do it correctly. We don't even need to call voices = synth.getVoices() the first time. When the webpage initializes itself, even if the page doesn't use voices, the event voiceschanged will get fired and the handler called. So the final code can have the first call to synth.getVoices() removed):

But what if when we add the event listener, or when the page already loaded, and we run the following code snippet, the event voiceschanged already fired and so we can listen, but we won't get notified. The answer is: it seems when we register the listener, seems like the listener always get notified even if the event happened already or the system just notify the listener no matter what, for the first time, similar to a promise.then(). But to be safe, if we don't count on this fact, we really should call synth.getVoices() twice. The first time and if voices.length is 0, then listen on the event for the change to set it when ready.

Actually, one observation is this: even when the page is loaded for 3 minutes, and if in the debug console, we do a synth.getVoices(), it is still an empty array. It won't be an empty array only if we do synth.addEventListener('voiceschanged'... and wait to be notified. It actually is similar to quantum mechanics: if we don't observe it, the voices are not there. But once we observe it, the voices will be there. (observing it or not really should not matter whether the voices are there or not). So if we really live in a world which is a virtual reality inside of some computer... that program might be behaving the same way... if we think about why in quantum mechanics, once we observe something, it is different). The behavior of voiceschanged is, if we don't observe it, the event will not occur. But once we observe it, the event will occur.

another way to have it return some voices is by console.log(window.speechSynthesis.getVoices()) in the debug console, and wait even just a half second and run console.log(window.speechSynthesis.getVoices()) again. Waiting just one event cycle won't work. If we do the following in one line, it won't work: console.log(window.speechSynthesis.getVoices()); setTimeout(function() { console.log(window.speechSynthesis.getVoices()); }, 0);

const synth = window.speechSynthesis;
let voices;

function foo(phrase) {
  if (!voices) {
    console.log("Waiting 01", voices);

    synth.addEventListener('voiceschanged', function(ev) {
      voices = voices || synth.getVoices();  
      foo(phrase);
    });
  } else {
    let msg = new SpeechSynthesisUtterance(); 

    console.log("How many voices", voices.length);

    // the voices are ready and could be changed here,
    // but since each system is different, so it won't be
    // changed here:
    // msg.voice = voices[0];
    msg.text = phrase;
    synth.speak(msg);
  }
}

foo("Hello");
foo("World");
foo("a third line");

We might even not use voices = synth.getVoices(); the very first time, and just use voices = []; synth.getVoices(); because if we set it to synth.getVoices(), other coders reading the code might have some expectation that it will get populated.



来源:https://stackoverflow.com/questions/59778957/in-javascript-for-text-to-speech-when-the-voiceschanged-event-was-being-listen

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!