59

以下 HTML 在第一次单击时在控制台中显示空数组:

<!DOCTYPE html>
<html>
    <head>
        <script>
            function test(){
                console.log(window.speechSynthesis.getVoices())
            }
        </script>
    </head>
    <body>
        <a href="#" onclick="test()">Test</a>
    </body>
</html>

在第二次单击中,您将获得预期的列表。

如果你添加onload事件来调用这个函数(<body onload="test()">),那么你可以在第一次点击时得到正确的结果。请注意,第一次调用onload仍然无法正常工作。它在页面加载时返回空,但之后工作。

问题:

由于它可能是测试版中的一个错误,我放弃了“为什么”的问题。

现在,问题是您是否想window.speechSynthesis在页面加载时访问:

  • 解决这个问题的最佳方法是什么?
  • 您如何确保它会speechSynthesis在页面加载时加载?

背景和测试:

我正在测试 Web Speech API 中的新功能,然后在我的代码中遇到了这个问题:

<script type="text/javascript">
$(document).ready(function(){
    // Browser support messages. (You might need Chrome 33.0 Beta)
    if (!('speechSynthesis' in window)) {
      alert("You don't have speechSynthesis");
    }

    var voices = window.speechSynthesis.getVoices();
    console.log(voices) // []

    $("#test").on('click', function(){
        var voices = window.speechSynthesis.getVoices();
        console.log(voices); // [SpeechSynthesisVoice, ...]
    });
});
</script>
<a id="test" href="#">click here if 'ready()' didn't work</a>

我的问题是:为什么在加载页面并触发函数window.speechSynthesis.getVoices()后返回空数组?onready如您所见,如果您单击链接,相同的函数会通过触发器返回一组可用的 Chromeonclick语音?

页面加载后似乎 Chrome 加载window.speechSynthesis了!

问题不在ready事件中。如果我var voice=...ready函数中删除该行,首先单击它会在控制台中显示空列表。但第二次点击工作正常。

第一次通话后似乎window.speechSynthesis需要更多时间来加载。你需要调用它两次!而且,您需要等待并让它在第二次调用之前加载window.speechSynthesis。例如,如果您第一次运行以下代码,控制台中会显示两个空数组:

// First speechSynthesis call
var voices = window.speechSynthesis.getVoices();
console.log(voices);

// Second speechSynthesis call
voices = window.speechSynthesis.getVoices();
console.log(voices);
4

11 回答 11

108

根据Web Speech API Errata (E11 2013-10-17),语音列表与页面异步加载。onvoiceschanged加载它们时会触发一个事件。

voiceschanged:当 getVoices 方法将返回的 SpeechSynthesisVoiceList 的内容发生变化时触发。示例包括:异步确定列表的服务器端合成,或安装/卸载客户端语音时。

因此,诀窍是从该事件侦听器的回调中设置您的声音:

// wait on voices to be loaded before fetching list
window.speechSynthesis.onvoiceschanged = function() {
    window.speechSynthesis.getVoices();
    ...
};
于 2014-04-10T04:42:22.123 回答
8

您可以使用 setInterval 等到声音加载后再使用它们,但是您需要然后清除 setInterval:

var timer = setInterval(function() {
    var voices = speechSynthesis.getVoices();
    console.log(voices);
    if (voices.length !== 0) {
      var msg = new SpeechSynthesisUtterance(/*some string here*/);
      msg.voice = voices[/*some number here to choose from array*/];
      speechSynthesis.speak(msg);
      clearInterval(timer);
    }
}, 200);

$("#test").on('click', timer);
于 2016-02-10T20:42:51.153 回答
6

在研究了 Google Chrome 和 Firefox 上的行为之后,这是可以得到所有声音的:

因为它涉及到一些异步的东西,所以最好用一个 Promise 来完成:

const allVoicesObtained = new Promise(function(resolve, reject) {
  let voices = window.speechSynthesis.getVoices();
  if (voices.length !== 0) {
    resolve(voices);
  } else {
    window.speechSynthesis.addEventListener("voiceschanged", function() {
      voices = window.speechSynthesis.getVoices();
      resolve(voices);
    });
  }
});

allVoicesObtained.then(voices => console.log("All voices:", voices));

笔记:

  1. 当事件voiceschanged触发时,我们需要.getVoices()再次调用。原始数组不会填充内容。
  2. 在谷歌浏览器上,我们不需要一getVoices()开始就打电话。我们只需要监听事件,它就会发生。在 Firefox 上,仅监听是不够的,您必须调用getVoices()然后监听事件,并在收到通知后voiceschanged设置数组 using 。getVoices()
  3. 使用 Promise 可以让代码更干净。与获取声音相关的所有内容都在此承诺代码中。如果你不使用promise,而是把这段代码放在你的语音例程中,那会很混乱。
  4. 你可以写一个voiceObtainedpromise 来解决你想要的声音,然后你的函数来说某事就可以了: voiceObtained.then(voice => { })在那个处理程序中,调用window.speechSynthesis.speak()来说话。或者你甚至可以写一个承诺speechReady("hello world").then(speech => { window.speechSynthesis.speak(speech) })说些什么。
于 2020-01-17T11:41:01.803 回答
4

这是答案

function synthVoice(text) {

  const awaitVoices = new Promise(resolve=> 
    window.speechSynthesis.onvoiceschanged = resolve)  
  .then(()=> {
    const synth = window.speechSynthesis;

    var voices = synth.getVoices();
    console.log(voices)

    const utterance = new SpeechSynthesisUtterance();
    utterance.voice = voices[3];        
    utterance.text = text;

    synth.speak(utterance);
  });
}
于 2017-11-29T04:47:18.920 回答
3

起初我使用 onvoiceschanged ,但即使在声音加载后它仍然会触发,所以我的目标是不惜一切代价避免 onvoiceschanged 。

这就是我想出的。到目前为止它似乎工作,如果它坏了会更新。

loadVoicesWhenAvailable();

function loadVoicesWhenAvailable() {
         voices = synth.getVoices();

         if (voices.length !== 0) {
                console.log("start loading voices");
                LoadVoices();
            }
            else {
                setTimeout(function () { loadVoicesWhenAvailable(); }, 10)
            }
    }
于 2017-10-06T16:27:54.307 回答
2

Salman Oskooi 的 setInterval 解决方案非常完美

请参阅https://jsfiddle.net/exrx8e1y/

function myFunction() {

  dtlarea=document.getElementById("details");
  //dtlarea.style.display="none";
  dtltxt="";

  var mytimer = setInterval(function() {

      var voices = speechSynthesis.getVoices();
      //console.log(voices);
      if (voices.length !== 0) {

        var msg = new SpeechSynthesisUtterance();

        msg.rate = document.getElementById("rate").value; // 0.1 to 10
        msg.pitch = document.getElementById("pitch").value; //0 to 2
        msg.volume = document.getElementById("volume").value; // 0 to 1

        msg.text = document.getElementById("sampletext").value; 
        msg.lang =  document.getElementById("lang").value; //'hi-IN';

        for(var i=0;i<voices.length;i++){

            dtltxt+=voices[i].lang+' '+voices[i].name+'\n';

            if(voices[i].lang==msg.lang) {
              msg.voice = voices[i]; // Note: some voices don't support altering params
              msg.voiceURI = voices[i].voiceURI;
              // break;
            }
        }

        msg.onend = function(e) {
          console.log('Finished in ' + event.elapsedTime + ' seconds.');
          dtlarea.value=dtltxt; 
        };

        speechSynthesis.speak(msg);

        clearInterval(mytimer);

      }
  }, 1000);

} 

这在适用于 MAC、Linux(Ubuntu)、Windows 和 Android 的 Chrome 上运行良好

Android 有非标准的 en_GB 而其他人有 en-GB 作为语言代码你还会看到相同的语言(lang)有多个名称

在 Mac Chrome 上,除了 en-GB Google UK English Female 和 n-GB Google UK English Male 之外,您还可以获得 en-GB Daniel

en-GB Daniel(Mac 和 iOS) en-GB Google 英国英语女性 en-GB Google 英国英语男性 en_GB 英语英国 hi-IN Google हिन्दी hi-IN Lekha(Mac 和 iOS) hi_IN 印度印地语

于 2018-01-02T07:15:33.047 回答
1

另一种确保在您需要语音之前加载语音的方法是将它们的加载状态绑定到一个承诺,然后从 a 发送您的语音命令then

const awaitVoices = new Promise(done => speechSynthesis.onvoiceschanged = done);

function listVoices() {
    awaitVoices.then(()=> {
        let voices = speechSynthesis.getVoices();
        console.log(voices);
    });
}

当您调用 时listVoices,它会等待声音首先加载,或者在下一个滴答声中调度您的操作。

于 2017-05-16T09:51:11.247 回答
1
async function speak(txt) {
    await initVoices();
    const u = new SpeechSynthesisUtterance(txt);
    u.voice = speechSynthesis.getVoices()[3];
    speechSynthesis.speak(u);
}

function initVoices() {
  return new Promise(function (res, rej){
    speechSynthesis.getVoices();
    if (window.speechSynthesis.onvoiceschanged) {
       res();
    } else {
      window.speechSynthesis.onvoiceschanged = () => res();
    }
  });
}
于 2021-02-03T21:44:21.710 回答
1

我使用此代码成功加载了语音:

<select id="voices"></select>

...

  function loadVoices() {
    populateVoiceList();
    if (speechSynthesis.onvoiceschanged !== undefined) {
      speechSynthesis.onvoiceschanged = populateVoiceList;
    }
  }

  function populateVoiceList() {
    var allVoices = speechSynthesis.getVoices();
    allVoices.forEach(function(voice, index) {
      var option = $('<option>').val(index).html(voice.name).prop("selected", voice.default);
      $('#voices').append(option);
    });
    if (allVoices.length > 0 && speechSynthesis.onvoiceschanged !== undefined) {
      // unregister event listener (it is fired multiple times)
      speechSynthesis.onvoiceschanged = null;
    }
  }

我从这篇文章中找到了“onvoiceschanged”代码:https ://hacks.mozilla.org/2016/01/firefox-and-the-web-speech-api/

注意:需要 JQuery。

适用于 Firefox/Safari 和 Chrome(也适用于 Google Apps 脚本 - 但仅适用于 HTML)。

于 2018-10-09T08:06:12.997 回答
0

我必须为此做自己的研究以确保我正确理解它,所以只是分享(随意编辑)。

我的目标是:

  • 获取我的设备上可用的声音列表
  • 用这些声音填充选择元素(在特定页面加载后)
  • 使用易于理解的代码

MDN 的官方现场演示演示了基本功能:

https://github.com/mdn/web-speech-api/tree/master/speak-easy-synthesis

但我想更好地理解它。

为了打破话题...

语音合成

Web Speech APISpeechSynthesis接口是语音服务的控制器接口;这可用于检索有关设备上可用的合成语音、开始和暂停语音以及其他命令的信息。

资源

onvoiceschanged

接口的onvoiceschanged属性SpeechSynthesis表示一个事件处理程序,该处理程序将 SpeechSynthesisVoice在方法返回的对象 列表SpeechSynthesis.getVoices()发生更改时运行(当voiceschanged 事件触发时)。

资源

示例 A

如果我的申请只有:

var synth = window.speechSynthesis;
console.log(synth);
console.log(synth.onvoiceschanged);

Chrome 开发者工具控制台将显示:

在此处输入图像描述

示例 B

如果我将代码更改为:

var synth = window.speechSynthesis;

console.log("BEFORE");
console.log(synth);
console.log(synth.onvoiceschanged);

console.log("AFTER");
var voices = synth.getVoices();

console.log(voices);
console.log(synth);
console.log(synth.onvoiceschanged);

前后状态相同,voices都是一个空数组。

在此处输入图像描述

解决方案

尽管我对实施Promises没有信心,但以下内容对我有用:

定义函数

var synth = window.speechSynthesis;
// declare so that values are accessible globally
var voices = [];


function set_up_speech() {

    return new Promise(function(resolve, reject) {

        // get the voices
        var voices = synth.getVoices();

        // get reference to select element
        var $select_topic_speaking_voice = $("#select_topic_speaking_voice");

        // for each voice, generate select option html and append to select
        for (var i = 0; i < voices.length; i++) {

            var option = $("<option></option>");

            var suffix = "";

            // if it is the default voice, add suffix text  
            if (voices[i].default) {
                suffix = " -- DEFAULT";
            }

            // create the option text
            var option_text = voices[i].name + " (" + voices[i].lang + suffix + ")";

            // add the option text
            option.text(option_text);

            // add option attributes
            option.attr("data-lang", voices[i].lang);
            option.attr("data-name", voices[i].name);

            // append option to select element
            $select_topic_speaking_voice.append(option);
        }

        // resolve the voices value
        resolve(voices)

    });

}

调用函数

// in your handler, populate the select element    
if (page_title === "something") {
set_up_speech()
}
于 2018-08-24T05:41:20.810 回答
0

Android Chrome - 关闭数据保护程序。这对我很有帮助。(Chrome 71.0.3578.99)

// wait until the voices load
   window.speechSynthesis.onvoiceschanged = function() {
    window.speechSynthesis.getVoices();

};
于 2019-01-04T19:28:05.970 回答