语音合成(Text To Speech):在日常开发需求中,产品不乏有一些语音播报的需求存在,拿我司来举例,中台系统的消息语音播报、数字孪生党建项目的语音讲解功能~
- 项目使用界面截图:
方案
1.Web Speech API
浏览器内置的语音合成API,提供了speechSynthesis
接口用于文本到语音的转换。只适用于支持Web Speech API的浏览器,如Chrome、Firefox等。
2.一些开发者封装的简单轮子
比如say.js 、annyang库,其实就是在原生的基础上封装一层,使得调用更便捷
3.各大厂商语音合成解决方案
不容置疑,这是最好的解决方案,相较于前两种的优点大致有:
- 播报声音真实自然
- 支持多方言,多语种
- 100+发音人,男女老少,海量风格随心选
- 动态调参,自由配置
- 可自定义文本讲述者语音
- 支持标记语言(SSML)方式优化发音和断句
- 离线识别分析
原生文字转语音
看我们的title,我们今天这篇文章要总结的是浏览器原生语音合成功能,因为老大让我调研了。。。
Web Speech API闪亮登场
来自
SpeechSynthesisUtterance是HTML5中新增的API,用于将指定文字合成为对应的语音。它包含一些配置项,可以指定如何去阅读(如语言、音量、音调等)。
SpeechSynthesis
语音合成通过 SpeechSynthesis 接口进行访问,它提供了文字到语音(TTS)的能力,这使得程序能够读出它们的文字内容(通常使用设备默认的语音合成器)。不同的声音类类型通过 SpeechSynthesisVoice (en-US) 对象进行表示,不同部分的文字则由 SpeechSynthesisUtterance (en-US) 对象来表示。你可以将它们传递给 SpeechSynthesis.speak() (en-US) 方法来产生语音。
- 属性
pending :表示当前播放列表是否有未播完的语音,即播放列表长度是否大于 2;
speaking :表示当前是否有语音正在进行播放,无论是播放还是暂停的状态, 也就是播放列表是否为空;
paused: 表示当前是否是暂停状态; - 事件
onvoiceschanged:监听事件,当 SpeechSynthesisVoice 列表发生改变的时候触发,通常也是在这个事件中通过 SpeechSynthesis.getVoices 来获取到浏览器支持的语音配置对象; - 方法
cancel :移除所有语音队列中的语音,相当于清空的功能;
pause :将当前语音对象置为暂停状态,这里需要注意一下,当调用这个方法时,整个浏览器的语音播放会处于一个暂停状态,如果没有调用 cancel 或 resume 方法,则不会继续播放,包括其他标签页添加进语音播放列表的语音合成对象;
resume :将当前语音对象置为非暂停状态,如果是暂停状态,则继续播放剩下的语音;
speak :添加一个语音对象到语音播放列表,当前列表为空时,则会马上播放,如果当前播放列表存在其他语音对象,则会等到其他语音对象播放完毕之后播放;
getVoices: 返回当前设备所有可用声音的列表; - 参考
mdn中文文档:Web 开发者指南 | MDN
代码
- 封装
js
// 定义 SpeakVoice 类并将其导出为默认导出
export default class SpeakVoice {
constructor() {
this.synth = window.speechSynthesis; // 启用文本
this.instance = new SpeechSynthesisUtterance();
this.voices = [];
this.initVoice();
}
// 初始化
initVoice() {
this.instance.volume = 1; // 声音音量:1,范围从0到1
this.instance.rate = 1; // 设置语速:1,范围从0到100
this.instance.lang = "zh-CN"; // 使用的语言:中文
}
// 语音开始
handleSpeak() {
this.synth.speak(this.instance); // 播放
}
// 语音队列重播
handleReply(text) {
this.instance.text = text;
this.handleCancel();
this.handleSpeak();
}
// 语音队列删除 , 删除队列中所有的语音.如果正在播放,则直接停止
handleCancel() {
this.synth.cancel();
}
// 语音暂停, 暂停语音该次语音播放
handleStop() {
this.synth.pause();
}
// 恢复暂停的语音
handleResume() {
this.synth.resume();
}
// 声音切换
setVoiceType(voiceName) {
const voice = this.voices.find((voice) => voice.name === voiceName);
if (voice) {
this.instance.voice = voice;
} else {
console.warn("指定的语音未找到:", voiceName);
}
}
// 获取可用的中文语音
async getSpeechCnVoices() {
try {
const voices = await this.getSpeechVoices();
const cnVoices = voices.filter(
(item) => item.lang.startsWith("zh-") && item.localService
);
if (cnVoices.length === 0) {
throw new Error("没有可用的中文语音!");
}
this.voices = cnVoices;
return cnVoices;
} catch (error) {
console.error(error);
throw error;
}
}
// 异步获取所有可用的语音
async getSpeechVoices() {
// 返回一个新的 Promise
return new Promise((resolve) => {
// 如果当前没有可用的语音,等待 onvoiceschanged 事件触发
if (this.synth.getVoices().length === 0) {
this.synth.onvoiceschanged = () => resolve(this.synth.getVoices());
} else {
// 如果已经有可用的语音,立即解析 Promise
resolve(this.synth.getVoices());
}
});
}
}
- 调用
js
<template>
<div class="speak-voice">
<el-button title="播放" @click="handleSpeak">播放</el-button>
<el-button title="暂停" @click="speakVoice.handleStop()">暂停</el-button>
<el-button title="继续" @click="speakVoice.handleResume()">继续</el-button>
<el-button title="取消" @click="speakVoice.handleCancel()">取消</el-button>
<el-select
v-model="selectedVoice"
@change="handleVoiceChange"
placeholder="选择语音"
size="large"
style="width: 240px"
>
<el-option
v-for="item in voices"
:key="item.name"
:label="item.name"
:value="item.name"
/>
</el-select>
<textarea v-model="text" style="width: 200px; height: 100px"></textarea>
</div>
</template>
<script setup>
import SpeakVoice from "../utils/home.js";
import { ref } from "vue";
let text = ref(
"春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明!"
);
const voices = ref([]);
const selectedVoice = ref(""); // 默认选中的语音名称
const speakVoice = new SpeakVoice();
speakVoice.getSpeechCnVoices().then((availableVoices) => {
voices.value = availableVoices;
selectedVoice.value = voices.value[0].name; // 默认选择第一个语音
});
const handleSpeak = () => {
speakVoice.handleReply(text.value);
};
const handleVoiceChange = (newVoiceName) => {
speakVoice.setVoiceType(newVoiceName);
};
</script>
最后
根据不同的应用场景来使用不同的技术,希望这篇文章对你有所帮助!
水平有限,还不能写到尽善尽美,希望大家多多交流,跟春野一同进步!!!