使用speechSynthesis实现文字转语音功能

一、需求分析

① 根据显示的数据列表依次播报每一则数据的主题;

② 静音功能:点击当前正在播报的语音时,暂停继续播报本则信息,切换下一则信息进行播报;点击非当前正在播报的信息时,跳过开启静音的信息。

二、使用技术

① SpeechSynthesis:语音服务的控制接口;它可以用于获取设备上关于可用的合成声音的信息,开始、暂停语音,或除此之外的其他命令。

本次使用的api有:pause()、resume()、speak()

MDN地址:SpeechSynthesis

② SpeechSynthesisUtterance

image.png

MDN地址:SpeechSynthesisUtterance

三、技术栈

node.js、vue3、typescript、ES6、axios

三、服务器搭建

本次使用nodejs 简易搭建服务器

1647098104(1).png

四、功能实现

①封装SpeechSynthesis及SpeechSynthesisUtterance的API

 * 播放声音
 */
const synth = window.speechSynthesis
const msg = new SpeechSynthesisUtterance()
export class WarningsSpeech {
  texts: Array<any>
  lang: string
  speed: number
  pauseFlag: boolean
  flag: number
  constructor(texts = [], lang = 'zh-CN', speed = 1) {
    this.texts = texts
    this.lang = lang
    this.speed = speed
    this.pauseFlag = false
    this.flag = 0
  }
  init() {
    msg.lang = this.lang
    synth.cancel()
  }
  speech() {
    this.init()
    if (this.pauseFlag) {
      this._judgePlay()
      this.resume()
    } else {
      msg.text = this.texts[this.flag]?.text ?? ''
      synth.speak(msg)
    }
    msg.onend = (e) => {
      this._judgePlay()
    }
  }
  _judgePlay() {
    for (let i = this.flag + 1; i < this.texts.length; i++) {
      if (this.texts[i].isMuted === false) {
        this.flag = i
        msg.text = this.texts[this.flag].text
        synth.speak(msg)
        return
      }
    }
  }
  pause() {
    synth.pause()
    this.pauseFlag = true
  }
  resume() {
    synth.resume()
    this.pauseFlag = false
  }
  muted() {
    if (!this.pauseFlag) {
      this.pause()
    }
    Promise.resolve(this.pauseFlag)
      .then((res) => {
        if (res) {
          this.speech()
        }
      })
      .then(() => {
        this.pauseFlag = false
      })
  }
  cancel() {
    synth.pause()
    this.pauseFlag = true
  }
}
复制代码

② 组件调用

  <div class="speech">
    <h1>文字转语音功能</h1>
    <ul class="speech_container">
      <template v-for="(items, index) in dataItems" :key="items.id">
        <li class="speech_slide">
          <span>{{ items.title }}</span>
          <i @click="handleMuted(items, index)" :class="{ disabled_i: items.isMuted }">静音</i>
        </li>
      </template>
    </ul>
  </div>
</template>

<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { getData } from '@/api/speech'
import { WarningsSpeech } from '@/utils/speechSythesis'

const dataItems = ref([])
const speech = ref<any>(null)

onMounted(() => {
  handleGetData()
})

/**
 * 获取数据
 */
const handleGetData = async () => {
  try {
    const { data: res } = await getData()
    const { data } = res
    dataItems.value = data.dataList.map((o: any) => {
      return {
        ...o,
        text: o.title,
        isMuted: false,
      }
    })
    speech.value = new WarningsSpeech(dataItems.value)
    speech.value.speech()
  } catch (e) {
    console.error('请求错误,请联系管理员!')
  }
}
/**
 * 静音按钮功能
 */
const handleMuted = (item: any, index: number) => {
  if (index === speech.value.flag) {
    speech.value.muted()
  }
  item.isMuted = !item.isMuted
  dataItems.value[index].isMuted = item.isMuted
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0;
}
a {
  color: #42b983;
}
.speech_container {
  width: 15vw;
  margin: 0 auto;
  padding: 20px 20px 50px;
  color: rgba(255, 255, 255, 0.85);
  border-radius: 8px 8px 0 0;
  background-image: -webkit-linear-gradient(#24c5b6, #b2ebd6);
}
.speech_slide {
  width: 100%;
  display: inline-flex;
  justify-content: space-between;
  align-items: center;
  margin: 5px 0;
}
.speech_slide i {
  font-style: normal;
  display: inline-block;
  padding: 2px 10px;
  box-sizing: border-box;
  border-radius: 6px;
  color: #24c5b6;
  font-size: 12px;
  background: rgba(255, 255, 255, 0.85);
  cursor: pointer;
}
.speech_slide .disabled_i {
  background: rgba(255, 255, 255, 0.55);
  color: #fff;
  cursor: not-allowed;
}
</style>

复制代码

五、代码分析

① node:使用node搭建服务器后,在vue.config.js中配置代理,才能够成功发送请求,否则会出现404;

② SpeechSynthesis及SpeechSynthesisUtterance功能封装:关键在于点击静音按钮判断是否为正在播报的语音,如果是,中断播报当前语音,并播报下一则未被静音的信息,实现方法为WarningsSpeech类中的muted方法;_judgePlay为类的私有属性,用于在语音结束的回调中,找到下一个未被静音的信息进行语音播报

六、成果展示

result.gif

七、SpeechSynthesis API兼容性:

image.png
查看兼容性

由此可知,大多数主流浏览器已支持此API。

七、结束

源码已上传至github(github.com/duangkey/sp…)

世界上最遥远的距离不是你我通过一根网线相连,而是你看到了,却没留下你的star!!!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享