获取语音合成中的语音列表(Web语音API)

问题描述 投票:48回答:10

以下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上的首次呼叫仍无法正常工作。它在页面加载时返回空,但此后可以工作。

问题:

由于可能是Beta版的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触发返回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);
dom-events voice speech-synthesis webspeech-api
10个回答
94
投票

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

0
投票

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


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

3
投票

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

这是我想出的。到目前为止,它似乎仍然有效,如果出现问题,它将进行更新。

loadVoicesWhenAvailable();

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

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

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

2
投票

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


2
投票

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

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

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

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


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(适用于Mac和iOS)zh-CN Google UK英文女zh-CN Google UK英语男性en_GB英文英国hi-IN Googleहिन्दीhi-IN Lekha(Mac和iOS)hi_IN印度北印度语


1
投票

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


0
投票

我必须为此做我自己的研究,以确保我正确地理解它,因此只需共享(可以自由编辑)。

© www.soinside.com 2019 - 2024. All rights reserved.