获取语​​音列表语音合成Chrome(Web Speech API)

时间:2014-02-02 17:25:55

标签: google-chrome javascript-events voice speech-synthesis webspeech-api

以下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上的第一个呼叫仍然无法正常工作。它在页面加载时返回空,但在之后工作。

问题:

由于测试版可能是a bug,我放弃了“为什么”的问题。

现在,问题是您是否要在页面加载时访问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函数后返回空数组?正如您所看到的,如果单击该链接,同一函数会通过onclick triger返回Chrome的可用语音数组?

在页面加载后,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);

12 个答案:

答案 0 :(得分:86)

根据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();
    ...
};

答案 1 :(得分:5)

你可以使用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);

答案 2 :(得分:3)

起初我使用的是onvoices,但是即使在加载声音之后它也会继续发射,所以我的目标是不惜一切代价避免发票。

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

loadVoicesWhenAvailable();

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

         if (voices.length !== 0) {
                console.log("start loading voices");
                LoadVoices();
            }
            else {
                setTimeout(function () { loadVoicesWhenAvailable(); }, 10)
            }
    }

答案 3 :(得分:3)

继承人的答案

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);
  });
}

答案 4 :(得分:2)

首先,非常感谢你的回答。其次,如果有人再次遇到这个问题/答案,这是一个有用的JSBin:http://jsbin.com/gosaqihi/9/edit?js,console

答案 5 :(得分:2)

确保在需要之前加载语音的另一种方法是将其加载状态绑定到promise,然后从then发送语音命令:

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

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

当你拨打listVoices时,它会先等待声音加载,或者在下一个时刻发出你的行动。

答案 6 :(得分: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谷歌英国英语女性和n-GB谷歌英国英语男性之外你还会得到en-GB Daniel

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

答案 7 :(得分:2)

研究了Google Chrome和Firefox上的行为后,便可以听到所有声音:

由于它涉及异步事物,因此最好通过一个承诺来完成:

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. 在Google Chrome浏览器上,我们最初不必致电getVoices()。我们只需要听事件,然后事件就会发生。在Firefox上,侦听还不够,您必须调用getVoices(),然后侦听事件voiceschanged,并在收到通知后使用getVoices()设置数组。
  3. 使用promise可使代码更清晰。与发出声音有关的所有内容都在此承诺代码中。如果您不使用诺言,而是将这段代码放在语音例程中,那就太乱了。
  4. 您可以写一个voiceObtained的诺言来解决您想要的声音,然后您的函数可以说出一些话:voiceObtained.then(voice => { }),并在该处理程序中,调用window.speechSynthesis.speak()说点什么。或者,您甚至可以写一个承诺speechReady("hello world").then(speech => { window.speechSynthesis.speak(speech) })说些什么。

答案 8 :(得分: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/

在Firefox / Safari和Chrome(也可以在Google Apps脚本中工作,但只能在HTML中工作)。

答案 9 :(得分: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();
    }
  });
}

答案 10 :(得分:0)

为此,我必须进行自己的研究,以确保自己理解正确,因此可以共享(随意编辑)。

我的目标是:

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

基本功能在MDN的official live demo中得到了展示:

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

但是我想更好地理解它。

要分解话题...

语音合成

  

Web Speech APISpeechSynthesis接口是控制器   语音服务接口;这可以用来找回   关于设备上可用的合成音色的信息,开始   并暂停语音和其他命令。

Source

发票已更改

  

onvoiceschanged界面的SpeechSynthesis属性   代表事件处理程序,当   SpeechSynthesisVoice个对象将由   SpeechSynthesis.getVoices()方法已更改(当voiceschanged   事件触发。)

Source

示例A

如果我的应用程序仅具有:

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

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

enter image description here

示例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是一个空数组。

enter image description here

解决方案

尽管我不确定实施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()
}

答案 11 :(得分:0)

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

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

};