前言

先上波科大讯飞官方文档地址:https://www.xfyun.cn/doc/asr/voicedictation/API.html

需要提前准备好密钥信息APPIDAPISecretAPIKey,怎么获取这三个配置我就省略了
这个案例使用的是webAPI

下载官方demo

这里我下载的是webAPI 语音听写流式API demo js语言,下载后得到以下文件

1.png

官方demo直接用原始html+js写的,我这里直接改成vue写法

需要把index.umd.jsprocessor.worker.jsprocessor.worklet.js放到项目/public/js/xf

安装依赖

npm i crypto-js

直接上代码吧,自行修改APPIDAPISecretAPIKey就能直接用

完整代码

<template>
  <div>
    <button @click="SR">{{ btnTxt }}</button>
    <br>
    <!-- {{ resultTextTemp }} -->
    <br>
    {{ result }}
  </div>
</template>

<script>
import CryptoJS from 'crypto-js'
// import RecorderManager from '@/../public/static/js/xf/index.umd.js'
export default {
  data() {
    return {
      RecorderManager: require('@/../public/static/js/xf/index.umd.js'),
      APPID: "自行修改",
      API_SECRET: "自行修改",
      API_KEY: "自行修改",
      btnStatus:"UNDEFINED", // "UNDEFINED" "CONNECTING" "OPEN" "CLOSING" "CLOSED"
      recorder: null,
      iatWS: null,
      resultText: '',
      resultTextTemp: '',
      result: '',
      countdownInterval: null,
      btnTxt: '开始录音',
    };
  },
  mounted() {
    this.recorder = new this.RecorderManager('./static/js/xf'); // processor.worker.js和processor.worklet.js的根目录(注意: 这里的路径是相对于index.html的,而不是相对于本vue)
    this.recorder.onStart = () => {
      this.changeBtnStatus("OPEN");
    }
    this.recorder.onFrameRecorded = ({ isLastFrame, frameBuffer }) => {
      if (this.iatWS.readyState === this.iatWS.OPEN) {
        this.iatWS.send(
          JSON.stringify({
            data: {
              status: isLastFrame ? 2 : 1,
              // status: 1,
              format: "audio/L16;rate=16000",
              encoding: "raw",
              audio: this.toBase64(frameBuffer),
            },
          })
        );
        if (isLastFrame) {
          this.changeBtnStatus("CLOSING");
        }
      }
    };

    this.recorder.onStop = () => {
      clearInterval(this.countdownInterval);
    };
  },
  methods: {
    // 录音按钮点击事件
    SR() {
      if (this.btnStatus === "UNDEFINED" || this.btnStatus === "CLOSED") {
        this.connectWebSocket();
      } else if (this.btnStatus === "CONNECTING" || this.btnStatus === "OPEN") {
        // 结束录音
        this.recorder.stop();
      }
    },
    // 获取websocket地址
    getWebSocketUrl() {
      // 请求地址根据语种不同变化
      let url = "wss://iat-api.xfyun.cn/v2/iat";
      let host = "iat-api.xfyun.cn";
      let apiKey = this.API_KEY;
      let apiSecret = this.API_SECRET;
      let date = new Date().toGMTString();
      let algorithm = "hmac-sha256";
      let headers = "host date request-line";
      let signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`;
      let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
      let signature = CryptoJS.enc.Base64.stringify(signatureSha);
      let authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
      let authorization = btoa(authorizationOrigin);
      url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
      return url;
    },
    // 连接服务器
    connectWebSocket() {
      const websocketUrl = this.getWebSocketUrl();
      if ("WebSocket" in window) {
        this.iatWS = new WebSocket(websocketUrl);
      } else if ("MozWebSocket" in window) {
        this.iatWS = new MozWebSocket(websocketUrl);
      } else {
        alert("浏览器不支持WebSocket");
        return;
      }
      this.changeBtnStatus("CONNECTING");
      this.iatWS.onopen = (e) => {
        console.log(e);
        // 开始录音
        this.recorder.start({
          sampleRate: 16000,
          frameSize: 1280,
        });
        let params = {
          common: {
            app_id: this.APPID,
          },
          business: {
            language: "zh_cn",
            domain: "iat",
            accent: "mandarin",
            vad_eos: 5000,
            dwa: "wpgs",
            ptt: 0, // 标点符号
          },
          data: {
            status: 0,
            format: "audio/L16;rate=16000",
            encoding: "raw",
          },
        };
        this.iatWS.send(JSON.stringify(params));
      };
      this.iatWS.onmessage = (e) => {
        this.renderResult(e.data);
      };
      this.iatWS.onerror = (e) => {
        console.log(e);
        this.recorder.stop();
        this.changeBtnStatus("CLOSED");
      };
      this.iatWS.onclose = (e) => {
        console.log(e);
        this.recorder.stop();
        this.changeBtnStatus("CLOSED");
      };
    },
    toBase64(buffer) {
      let binary = "";
      let bytes = new Uint8Array(buffer);
      let len = bytes.byteLength;
      for (let i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
      }
      return window.btoa(binary);
    },
    changeBtnStatus(status) {
      this.btnStatus = status;
      if (status === 'CONNECTING') {
        this.btnTxt = '建立连接中';
        this.resultText = '';
        this.resultTextTemp = '';
      } else if (status === 'OPEN') {
        this.countdown();
      } else if (status === 'CLOSING') {
        this.btnTxt = '关闭连接中';
      } else if (status === 'CLOSED') {
        this.btnTxt = '开始录音';
      }
    },
    countdown() {
      let seconds = 60;
      this.btnTxt = `录音中(${seconds}s)`;
      this.countdownInterval = setInterval(() => {
        seconds = seconds - 1;
        if (seconds <= 0) {
          clearInterval(this.countdownInterval);
          this.recorder.stop();
        } else {
          this.btnTxt = `录音中(${seconds}s)`;
        }
      }, 1000);
    },
    // 渲染结果
    renderResult(resultData) {
      // 识别结束
      let jsonData = JSON.parse(resultData);
      if (jsonData.data && jsonData.data.result) {
        let data = jsonData.data.result;
        let str = "";
        let ws = data.ws;
        for (let i = 0; i < ws.length; i++) {
          str = str + ws[i].cw[0].w;
        }
        // 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
        // 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
        if (data.pgs) {
          if (data.pgs === "apd") {
            // 将resultTextTemp同步给resultText
            this.resultText = this.resultTextTemp;
          }
          // 将结果存储在resultTextTemp中
          this.resultTextTemp = this.resultText + str;
        } else {
          this.resultText = this.resultText + str;
        }
        this.result = this.resultTextTemp || this.resultText || '';
      }
      if (jsonData.code === 0 && jsonData.data.status === 2) {
        this.iatWS.close();
      }
      if (jsonData.code !== 0) {
        this.iatWS.close();
        console.error(jsonData);
      }
    }
  }
};
</script>