一、关于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);

没错,只需要这么一点代码就足够了,大家可以在自己浏览器的控制台里面运行上面两行代码,看看有没有读出声音。

上面代码出现了两个长长的对象,SpeechSynthesisUtterancespeechSynthesis,就是语音合成Speech Synthesis API的核心。

首先是SpeechSynthesisUtterance对象,主要用来构建语音合成实例,例如上面代码中的实例对象utterThis。我们可以直接在构建的时候就把要读的文字内容写进去:

var utterThis = new window.SpeechSynthesisUtterance('你好,世界!');

又或者是使用实例对象的一些属性,包括:

  • text – 要合成的文字内容,字符串。
  • lang – 使用的语言,字符串, 例如:"zh-cn"
  • voiceURI – 指定希望使用的声音和服务,字符串。
  • volume – 声音的音量,区间范围是01,默认是1
  • rate – 语速,数值,默认值是1,范围是0.110,表示语速的倍数,例如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

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐