SpeechSynthesis API的onend回调不起作用

34
我正在使用Google Chrome v34.0.1847.131上实现的语音合成API。自版本33开始,Chrome中已经实现了该API。
大部分时候文本到语音都可以正常工作,但是当将回调函数分配给onend时会出现问题。例如,以下代码:
var message = window.SpeechSynthesisUtterance("Hello world!");
message.onend = function(event) {
    console.log('Finished in ' + event.elapsedTime + ' seconds.');
};
window.speechSynthesis.speak(message);

有时会调用onend,有时不会调用。时间似乎完全错了。当它被调用时,打印的elapsedTime总是一些类似于1399237888的纪元时间。


1
谢谢查看。看到有其他人遇到同样的问题是令人鼓舞的。我猜这可能只是一个不完善的实现,所以这个问题可能没有答案。 - huu
1
看这个…出于某些奇怪的原因,如果您注销utterance对象“message”,那么它就能正常工作了。 :D http://jsfiddle.net/QYw6b/ - Muhammad Umer
3
实际上,我认为问题在于在声明消息对象后立即调用speak函数...如果你只需要这样做: setTimeout(function(){speechSynthesis.speak(u);},100); ,它就可以工作了...或者将speak函数绑定到点击事件上,它也能正常工作。 - Muhammad Umer
setTimeout(function(){speechSynthesis.speak(u);},1);同样有效,并且与直接调用它没有区别。似乎API只喜欢存在于回调函数中。希望能对我们观察到的行为有更多了解,但可能只有谷歌员工才知道确切原因。如果您愿意,您可以根据提供的信息撰写一个答案,我会给予应有的认可 :) - huu
1
我仍然在Windows的Chrome v46上看到这个问题。我不记得在Android的Chrome上发生过这种情况。 - Frank Schwieterman
显示剩余4条评论
9个回答

26
根据Kevin Hakanson的答案中提到的这个错误的评论,这可能是垃圾回收的问题。在调用speak之前将话语存储在一个变量中似乎可以解决问题
window.utterances = [];
var utterance = new SpeechSynthesisUtterance( 'hello' );
utterances.push( utterance );
speechSynthesis.speak( utterance );

2
谢谢!一旦我改变了我的utterance变量的范围,我就不再有这个问题了。我同意可能与浏览器内部GC相关的错误。 - vbguyny
成功了!为了让像我这样的初学者更清楚,将 SpeechSynthesisUtterance 变量的作用域扩大,例如全局作用域,就可以解决问题。 - M3RS
utterances 是在第一行创建的一个数组。push 将一个项添加到数组的末尾:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/push - techpeace
运行得非常好! - Norly Canarias
1
不错啊!将当前话语存储在全局 window 上,等到所有的说话结束后再将其删除——对我来说完美无缺! - chestozo
显示剩余3条评论

12

虽然这是我发现让它工作的方法,但我不确定这是否是正确的行为...

首先不要立即调用speak函数,使用回调。

其次,获取时间使用timeStamp而不是elapsedTime。你也可以直接使用performance.now()

var btn = document.getElementById('btn');
speechSynthesis.cancel()
var u = new SpeechSynthesisUtterance();
u.text = "This text was changed from the original demo.";

var t;
u.onstart = function (event) {
    t = event.timeStamp;
    console.log(t);
};

u.onend = function (event) {
    t = event.timeStamp - t;
    console.log(event.timeStamp);
    console.log((t / 1000) + " seconds");
};

btn.onclick = function () {speechSynthesis.speak(u);};

示例:http://jsfiddle.net/QYw6b/2/

您可以确保时间已经过去并且两个事件都已触发。


尽管这个例子可以工作,但一旦你使用非英语的语音,它就停止触发onend事件。 - Zero Dragon

6
你可以像我在Speakerbot (http://www.speakerbot.de/)中所做的那样使用EventListener来处理开始和结束事件。
当有话语时,我的脸会发生变化。
newUtt = new SpeechSynthesisUtterance();

newUtt.addEventListener('start', function () {
     console.log('started');
})

newUtt.addEventListener('end', function () {
     console.log('stopped');
})

4

我发现这里提出的两种解决方案在我刚写的应用程序中都无法正常工作。我能想到的唯一解决方案是一种(有点)繁忙等待:

function speak( text, onend ) {
  window.speechSynthesis.cancel();
  var ssu = new SpeechSynthesisUtterance( text );
  window.speechSynthesis.speak( ssu );
  function _wait() {
    if ( ! window.speechSynthesis.speaking ) {
      onend();
      return;
    }
    window.setTimeout( _wait, 200 );
  }
  _wait();
}

你可以在this codepen中找到一个完整的例子。


我喜欢你在调用cancel()函数时开始解决我的问题。但是对于我来说,speechSynthesis.speaking的值比它应该的时间长得多,所以繁忙等待对我没有用。 - ubershmekel

3

这看起来类似于2015年7月12日报告的一个Chromium漏洞。

问题509488: Web Speech API:SpeechSynthesisUtterance对象的“end”事件有时不会被触发


1

在说话之前打印话语似乎有效...如果我移除控制台,这个问题就会出现,不知道为什么。

console.log("utterance", utterThis);
synth.speak(utterThis);

0

我也发现使这个可靠地工作的唯一方法是使用 .cance。我使用了 17 秒的超时时间。我的所有录音都在 20 秒以下,所以这对我来说很有效。

utterance.onstart = function (event) {
setTimeout(function(){window.speechSynthesis.cancel();},17000);
};

之前我每尝试发送8-10条消息就会遇到这个问题。加入.cancel后,似乎总是可以解决了。我在调用时还调用了set timeout。

setTimeout(function(){window.speechSynthesis.speak(utterance);},100);

0

对我有效的唯一方法是避免使用 localServicetrue 的语音。这些语音从未触发 onend,而其他语音(localService 为 false)则会触发 onend


0
此外,在Chrome浏览器中(但不是Safari),如果您尝试发出空字符串,则回调永远不会被调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接