一、关于HTML5语音Web Speech API
HTML5中和Web Speech相关的API实际上有两类,一类是“语音识别(Speech Recognition)”,另外一个就是“语音合成(Speech Synthesis)”,这两个名词听上去很高大上,实际上指的分别是“语音转文字”,和“文字变语音”。
而本文要介绍的就是这里的“语音合成-文字变语音”。为什么称为“合成”呢?比方说你Siri发音“你好,世界!” 实际上是把“你”、“好”、“世”、“界”这4个字的读音给合并在一起,因此,称为“语音合成”。
“语音识别”和“语音合成”看上去像是正反两方面,应该带有镜面气质,实际上,至少从兼容性来看,两者并无法直接对等。“语音识别(Speech Recognition)”目前的就Chrome浏览器和Opera浏览器默认支持,但是,“语音合成(Speech Synthesis)”的兼容性要好上太多了Chrome,FF,Edge,Safari等等都是支持的。
使用的基本套路如下:
-
创建SpeechRecognition的新实例。由于到目前为止,浏览器还没有广泛支持,所以需要webKit的前缀:
var newRecognition = webkitSpeechRecognition();
- 设置是持续听还是听到声音之后就关闭接收。通过设置continuous属性值实现。一般聊天沟通使用false属性值,如果是写文章写公众号之类的则可以设置为true,如下示意:
newRecognition.continuous = true;
- 控制语音识别的开启和停止,可以使用start()和stop()方法:
// 开启
newRecognition.start();
// 停止
newRecognition.stop();
- 对识别到的结果进行处理,可以使用一些事件方法,比方说onresult:
newRecognition.onresult = function(event) {
console.log(event);
}
除了result事件外,还有其他一些事件,例如,soundstart、speechstart、error等。
二、关于语音合成Speech Synthesis API
先从最简单的例子说起,如果想让浏览器读出“你好,世界!”的声音,可以下面的JS代码:
var utterThis = new window.SpeechSynthesisUtterance('你好,世界!');
window.speechSynthesis.speak(utterThis);
没错,只需要这么一点代码就足够了,大家可以在自己浏览器的控制台里面运行上面两行代码,看看有没有读出声音。
上面代码出现了两个长长的对象,SpeechSynthesisUtterance
和speechSynthesis
,就是语音合成Speech Synthesis API的核心。
首先是SpeechSynthesisUtterance
对象,主要用来构建语音合成实例,例如上面代码中的实例对象utterThis
。我们可以直接在构建的时候就把要读的文字内容写进去:
var utterThis = new window.SpeechSynthesisUtterance('你好,世界!');
又或者是使用实例对象的一些属性,包括:
text
– 要合成的文字内容,字符串。lang
– 使用的语言,字符串, 例如:"zh-cn"
voiceURI
– 指定希望使用的声音和服务,字符串。volume
– 声音的音量,区间范围是0
到1
,默认是1
。rate
– 语速,数值,默认值是1
,范围是0.1
到10
,表示语速的倍数,例如2
表示正常语速的两倍。pitch
– 表示说话的音高,数值,范围从0
(最小)到2
(最大)。默认值为1
。
因此上面的代码也可以写作:
var utterThis = new window.SpeechSynthesisUtterance();
utterThis.text = '你好,世界!';
不仅如此,该实例对象还暴露了一些方法:
onstart
– 语音合成开始时候的回调。onpause
– 语音合成暂停时候的回调。onresume
– 语音合成重新开始时候的回调。onend
– 语音合成结束时候的回调。
接下来是speechSynthesis
对象,主要作用是触发行为,例如读,停,还原等:
-
speak()
– 只能接收SpeechSynthesisUtterance
作为唯一的参数,作用是读合成的话语。 -
cancel() – 删除队列中所有的语音.如果正在播放,则直接停止。
-
pause()
– 暂停合成过程。 -
resume()
– 重新开始合成过程。 -
getVoices
– 此方法不接受任何参数,用来返回浏览器支持的语音包列表注意:必须添加在voiceschanged事件中才能生效,是个数组
三、项目源码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title></title>
</head>
<body>
<button onclick='playRadio()' id="played">播报语音</button>
<button onclick='pauseRadio()' id="pasued">暂停</button>
<button onclick='resumeRadio()' id="resumed">重新播报</button>
<button onclick='stopRadio()' id="stoped">停止</button>
<script>
function playRadio(){
updataVoice();
}
function pauseRadio(){
window.speechSynthesis.pause();
}
function resumeRadio(){
window.speechSynthesis.resume();
}
function stopRadio(){
window.speechSynthesis.cancel();
}
//获取最新数据的定时器
var voiceUpdatatimer; //获取最新数据的定时器
var newVoiceText; //最新数据
var newVoiceArray; //深拷贝后的最新数据防止修改影响原数组
//备注:这是根据设置设置每级告警要播放几次 我这个有4个等级告警,可以根据ajax获取设置的告警次数去更改默认的次数
var alarmLevelOnePlayback = 2; //这是根据1级告警的播放次数 默认播放4遍
var alarmLevelTowPlayback = 2; //这是根据2级告警的播放次数 默认播放3遍
var alarmLevelThreePlayback = 2; //这是根据3级告警的播放次数 默认播放3遍
var alarmLevelFourPlayback = 2; //这是根据4级告警的播放次数 默认播放2遍
var voiceState = true; //这是一个卡锁在播放本条数据时新来数据不能影响到当前的播报,播放时为false,播放完为true
var to_speak; //谷歌阅读声明全局变量防止函数执行完销毁造成阅读不全
//更新告警语音
function updataVoice() {
//每次新数据来时需要清理存在的定时器
clearInterval(voiceUpdatatimer)
//获取最新数据的定时器
voiceUpdatatimer = setTimeout(function () {
//alarmVoice数组结构: [{areaName:"区域",stationName:"局站",deviceName:"设备",alarmLevel:"告警级别", beginTime"开始告警时间",platNo:"121253"}]
//测试数据
var alarmVoice = [{
areaName: "中国",
stationName: "天津环保科技股份有限公司",
deviceName: "测试设备1",
alarmLevel: "2",
beginTime: "2020-02-20 16:04:48",
platNo: "121253"
}];
//这个是个深拷贝为了不影响原数组,而且根据alarmLevel进行从小到大的排序(compareVoiceArray函数是排序数组对象),告警等级高的肯定在最前面(alarmLevel(告警的等级),是你数组对象中的一个属性)
newVoiceArray = JSON.parse(JSON.stringify(compareVoiceArray(alarmVoice, "alarmLevel")));
if (newVoiceArray.length > 0) {
//去执行声音播报模块
voiceAnnouncements()
} else {
newVoiceText = ""
}
}, 1000)
}
//排序数据
function compareVoiceArray(alarmVoice, compareVal) {
function compare(voiceVal) {
return function (obj1, obj2) {
var value1 = obj1[voiceVal];
var value2 = obj2[voiceVal];
return value1 - value2;
}
}
return (alarmVoice.sort(compare(compareVal)))
}
//判断浏览器 没做细的判断
var userAgent = navigator.userAgent;
try {
//简单判断是ie浏览器 如果想要更细致再加判断
if (userAgent.indexOf('Trident') > -1) {
//声明成全局变量 如果放在函数内部 函数执行完会销毁,就会造成语音播放不全就停止了
var voiceObj = new ActiveXObject("Sapi.SpVoice");
}
} catch (err) {
var voiceObj = ''
}
//声音播报函数
function voiceAnnouncements() {
var voicLevel; //win7读2发音liang win10正常 这是做个处理让发音一致
var voiceTimeArry; //win7读取时间 在读到最后时会拉掉秒不读 这是事单独取出时间的字符串拼接出出秒 ;win10正常
var voiceTime;
var useVoiceText = "" //阅读的文本
//为0不阅读 ,唯一阅读 项目上还有声音的播放 两个只能使用一个所以添加这个变量 如果只有声音播报大于1就可以
var numberOfPlayback = 1; //阅读的次数
//对2做替换处理替换成汉字二(对win7差异做处理)
if (newVoiceArray[0].alarmLevel == 2) {
voicLevel = "二级:"
} else {
voicLevel = newVoiceArray[0].alarmLevel + "级:"
}
//对时间处理 年月日阅读都正常可以直接使用 把时间的字符串取出单独拼接
voiceTimeArry = newVoiceArray[0].beginTime.split(" ")
voiceTime = voiceTimeArry[1].split(":")
//用:做的停顿, win10可以\r停顿就可以
newVoiceText = [":::区域:" + newVoiceArray[0].areaName + ":: 局站:" + newVoiceArray[0].stationName + "::: 设备:" + newVoiceArray[0].deviceName + "::: 告警级别:" + voicLevel + " ::: 开始告警时间:" + voiceTimeArry[0] + ":" + voiceTime[0] + "点" + voiceTime[1] + "分" + voiceTime[2] + "秒", newVoiceArray[0].platNo, newVoiceArray[0].alarmLevel]
if (voiceState) {
if (newVoiceText[2] == 1) {
numberOfPlayback = alarmLevelOnePlayback; //1级告警播放次数
}
if (newVoiceText[2] == 2) {
numberOfPlayback = alarmLevelTowPlayback; //2级告警播放次数
}
if (newVoiceText[2] == 3) {
numberOfPlayback = alarmLevelThreePlayback; //3级告警播放次数
}
if (newVoiceText[2] == 4) {
numberOfPlayback = alarmLevelFourPlayback; //4级告警播放次数
}
//要播放几次都循环拼接到一起
for (var i = 0; i < numberOfPlayback; i++) {
useVoiceText += newVoiceText[0]
console.log(newVoiceText[0])
}
//注意ie需要设置activeX的权限 否则播放不出来
//这ie的播放模 (目前只做了ie跟谷歌)
if (!('speechSynthesis' in window)) {
try {
voiceState = false; //播放时为false
voiceObj.Speak(useVoiceText, 1); //播放语音
//定时器获取是否播放结束 播放时voiceObj.Status.RunningState=2 判断不紧密,如果有结束回调函数最好用结束回调函数,我目前没找到
voiceTimer = setInterval(function () {
if (voiceObj.Status.RunningState != 2) {
clearInterval(voiceTimer)
//阅读完去拿新数据
voiceState = true;
for (var j = 0; j < newVoiceArray.length; j++) {
if (newVoiceArray[j].platNo == newVoiceText[1]) {
newVoiceArray.splice(j, 1);
}
}
if (newVoiceArray.length > 0) {
voiceAnnouncements()
}
}
}, 1000)
} catch (err) {
alert("你的浏览器不支持语音(activeX权限设置)")
}
} else {
voiceState = false; //播放时不能让影响
to_speak = window.speechSynthesis,
to_speak = new SpeechSynthesisUtterance(useVoiceText);
to_speak.onend = function (event) {
//播放完新数据可以进来
voiceState = true;
//删除刚刚播放完的数据
for (var j = 0; j < newVoiceArray.length; j++) {
if (newVoiceArray[j].platNo == newVoiceText[1]) {
newVoiceArray.splice(j, 1);
}
}
//重新获取
if (newVoiceArray.length > 0) {
voiceAnnouncements()
}
}
//开始阅读
window.speechSynthesis.speak(to_speak);
//window.speechSynthesis.cancel();
}
}
}
</script>
</body>
</html>
注:
1.voiceAnnouncements()方法,对播报的语音进行解析拼接,将级别2,改为二级,将时间字符串拼接后再播放;可设置循环播放次数;
2.兼容ie浏览器,谷歌浏览器、360浏览器
3.离线状态下也能用
4.是HTML5的特性,浏览器要支持HTML5.
来源:https://zhuanlan.zhihu.com/p/41179191
来源:https://www.jianshu.com/p/92dec635f6c5