封装一个支持语音输入的输入框

前言

这两天逛MDN查某个API文档的时候,找到两个好玩的API,一个是语音转文字一个是文字转语音下面给大家分享一下,顺便封装一个支持语音输入的输入框。

语音转文字

说明

语音转文字主要使用了webkitSpeechRecognition这个api,可以new一个语音识别对象,它支持以下属性配置。

属性 官方说明 实战说明
lang 返回并设置当前的语言 我用不同语言测试了一下,这个属性并不是强制的,比如设置了英文,说中文还是能识别出来,但是有些中文会被识别返回拼音。
continuous 控制是为每次识别返回连续结果,还是仅返回单个结果。默认为单个(false)。 也就是说如果这个属性设置为false,说一段话后会自动停止识别。如果为true,会一直识别,直到调用stop或abort方法停止识别。
interimResults 这个属性控制是否返回临时结果,true表示返回,false不返回。 如果为true,说话的时候会实时返回,如果为false,一段话结束后才返回。
maxAlternatives 设置每个结果提供的最大数量。默认值为 1。 这个属性控制识别后返回的结果数量,如果设置为2,会返回两个识别的可能结果。

实战

介绍完api后,我们来实战一下,封装一个支持语音输入的输入框。

jsx 复制代码
import { AudioOutlined, LoadingOutlined } from '@ant-design/icons';
import { Button, Input, Space } from 'antd';
import { useEffect, useMemo, useRef, useState } from 'react';

function App() {

  const [loading, setLoading] = useState(false);
  const [text, setText] = useState('');

  const recognition = useRef(new window.webkitSpeechRecognition())

  const lastLength = useRef(0);

  useEffect(() => {
    // 设置语言
    recognition.current.lang = 'zh';
    // 开启连续识别
    recognition.current.continuous = true;
    // 开启实时识别
    recognition.current.interimResults = true;

    function onresult(event) {
      // 这个事件会把前面识别的结果都返回回来,所以需要取最后一个识别结果
      const length = event.results.length;
      // 没有新的识别结果的时候,事件也会触发,所以这里判断一下如果没有新的识别结果,就不取最后一个识别结果了。
      if (lastLength.current === length) {
        return;
      }

      lastLength.current = length;

      console.log(event.results);

      // 获取最后一个识别结果
      const transcript = event.results[length - 1]?.[0]?.transcript;

      // 将最后一个识别结果添加到文本
      if (transcript) {
        setText(text => text + transcript);
      }
    }

    // 监听语音识别结果
    recognition.current.addEventListener('result', onresult)

    return () => {
      if (recognition.current) {
        recognition.current.removeEventListener('result', onresult)
      }
    }
  }, [])


  function click() {
    if (loading) {
      recognition.current.stop();
      setLoading(false);
      return;
    }
    setLoading(true);

    lastLength.current = 0;
    recognition.current.start();
  }

  const icon = useMemo(() => {
    if (loading) {
      return <LoadingOutlined style={{
        fontSize: 16,
        color: '#ffffff',
      }} />
    }
    return <AudioOutlined
      style={{
        fontSize: 16,
        color: '#ffffff',
      }}
    />
  }, [loading]);

  return (
    <div style={{ textAlign: 'center', marginTop: 200 }}>
      <Space.Compact style={{ width: 600 }}>
        <Input size='large' value={text} />
        <Button
          size='large'
          type="primary"
          onClick={click}
          icon={icon}
        />
      </Space.Compact>
    </div>
  )
}

export default App

上面就是全部代码,有个问题需要注意一下,官网例子中使用new SpeechRecognition()这个方法new语音识别对象,但是我测试了一遍发现,google、Firefox、edge、Safari浏览器都会报错,推荐使用window.webkitSpeechRecognition方式初始化。

兼容性

可以看到火狐浏览器是不支持的,经测试代码在火狐浏览器会报错。截图中说支持edge浏览器,经测试代码在edge中不报错,但是不返回识别结果。google和Safari浏览器可以正常使用。

效果展示

文字转语音

说明

浏览器也支持文本转语音,准确的说是读文本。这个功能主要用到了两个api,speechSynthesisSpeechSynthesisUtterance,不过核心是这个SpeechSynthesisUtterance

SpeechSynthesisUtterance

属性 官方说明
lang 返回并设置当前的语言
pitch 代表音调值的浮点数,默认为1,改为其他值,会发出奇怪的声音。
rate 控制播放速度
text 要播放的文本
voice 播放的音调
volume 控制声音大小

实战

jsx 复制代码
import { Button, Input, Select, Space } from 'antd';
import { useEffect, useState } from 'react';

function App() {
  const [text, setText] = useState('');
  const [voice, setVoice] = useState();
  const [voices, setVoices] = useState([]);

  const [loading, setLoading] = useState(false);

  async function getVoices() {
    // 获取声音,因为这个返回值不稳定,所以加了个定时器获取,保证能返回声音类型
    return new Promise(resolve => {
      const timer = setInterval(() => {
        const voices = window.speechSynthesis.getVoices();
        if (voices?.length) {
          resolve(window.speechSynthesis.getVoices());
          clearInterval(timer);
        }
      }, 30);
    })
  }

  useEffect(() => {
    // 获取声音,并设置给下拉框
    getVoices()
      .then(voices => {
        setVoices(voices);
      });
  }, []);

  function click() {
    const synth = window.speechSynthesis;

    if (loading) {
      setLoading(false);
      synth.cancel();
      return;
    }

    const utterThis = new SpeechSynthesisUtterance();

    // 播放介绍
    utterThis.onend = () => {
      setLoading(false);
    }

    // 设置文本
    utterThis.text = text;
    // 设置语言
    utterThis.lang = 'zh-CN';
    // 设置声音类型
    utterThis.voice = voices.find(v => v.name === voice);
    // 开始播放
    synth.speak(utterThis);

    setLoading(true);
  }

  return (
    <div style={{ textAlign: 'center', marginTop: 200 }}>
      <Space.Compact style={{ width: 600 }}>
        <Select
          size='large'
          value={voice}
          options={voices}
          style={{ width: 200 }}
          onChange={(value) => setVoice(value)}
          fieldNames={{ label: 'name', value: 'name' }}
        />
        <Input
          size='large'
          value={text}
          onChange={e => setText(e.target.value)}
        />
        <Button
          size='large'
          type="primary"
          onClick={click}
        >
          {loading ? '停止' : '播放'}
        </Button>
      </Space.Compact>
    </div>
  )
}

export default App

有个问题需要注意一下,获取声音类型的方法,第一次调用永远获取不到,第二次才能获取到,为了保证能获取到,加了个定时器一直去获取,直到成功。

兼容性

经过实战测试pc的浏览器大部分都支持。

最后

写完文章后,才发现语音转文字好像要挂代理才能正常使用,有点尴尬。写都写了,发出来吧,让大家了解一下这两个api。

体验地址:dbfu.github.io/voice-input...

相关推荐
风清扬_jd4 分钟前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome
丁总学Java19 分钟前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
It'sMyGo29 分钟前
Javascript数组研究09_Array.prototype[Symbol.unscopables]
开发语言·javascript·原型模式
懒羊羊大王呀30 分钟前
CSS——属性值计算
前端·css
李是啥也不会1 小时前
数组的概念
javascript
无咎.lsy1 小时前
vue之vuex的使用及举例
前端·javascript·vue.js
fishmemory7sec1 小时前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec1 小时前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
豆豆2 小时前
为什么用PageAdmin CMS建设网站?
服务器·开发语言·前端·php·软件构建
JUNAI_Strive_ving2 小时前
番茄小说逆向爬取
javascript·python