JS原生文字转语音(不需安装任何包和插件)

语音合成(Text To Speech):在日常开发需求中,产品不乏有一些语音播报的需求存在,拿我司来举例,中台系统的消息语音播报、数字孪生党建项目的语音讲解功能~

  • 项目使用界面截图:

方案

1.Web Speech API

浏览器内置的语音合成API,提供了speechSynthesis接口用于文本到语音的转换。只适用于支持Web Speech API的浏览器,如Chrome、Firefox等。

2.一些开发者封装的简单轮子

比如say.jsannyang库,其实就是在原生的基础上封装一层,使得调用更便捷

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>

最后

根据不同的应用场景来使用不同的技术,希望这篇文章对你有所帮助!

水平有限,还不能写到尽善尽美,希望大家多多交流,跟春野一同进步!!!

相关推荐
十一吖i18 分钟前
前端将后端返回的文件下载到本地
vue.js·elementplus
光影少年19 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
Rattenking24 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
熊的猫1 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
mosen8682 小时前
Uniapp去除顶部导航栏-小程序、H5、APP适用
vue.js·微信小程序·小程序·uni-app·uniapp
别拿曾经看以后~3 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
川石课堂软件测试3 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
JerryXZR3 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
problc4 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
Gavin_9154 小时前
【JavaScript】模块化开发
前端·javascript·vue.js