在React使用腾讯云实时语音转文字

首先是组件

import { signCallback } from './asr/asrauthentication'
import WebAudioSpeechRecognizer from './asr/webaudiospeechrecognizer'
import './asr/speechrecognizer'

    const [webAudioSpeechRecognizer, setWebAudioSpeechRecognizer] = useState(null)
    const [id, setID] = useState(nanoid())
    useEffect(()=> {
        const params = {
            signCallback: signCallback, // 鉴权函数,若直接使用默认鉴权函数。可不传此参数
            // 用户参数
            secretid: config.secretId,
            secretkey: config.secretKey,
            appid: config.appId,
            // 临时密钥参数,非必填
            // token: config.token,
            // 实时识别接口参数
            engine_model_type: '16k_zh', // 因为内置WebRecorder采样16k的数据,所以参数 engineModelType 需要选择16k的引擎,为 '16k_zh'
            // 以下为非必填参数,可跟据业务自行修改
            // voice_format : 1,
            // hotword_id : '08003a00000000000000000000000000',
            // needvad: 1,
            // filter_dirty: 1,
            // filter_modal: 2,
            // filter_punc: 0,
            // convert_num_mode : 1,
            // word_info: 2
        }
        const _webAudioSpeechRecognizer = new WebAudioSpeechRecognizer(params)
        // 开始识别
        _webAudioSpeechRecognizer.OnRecognitionStart = (res) => {
            console.log('开始识别', res);
        };
        // 一句话开始
        _webAudioSpeechRecognizer.OnSentenceBegin = (res) => {
            setCanStop()
            console.log('一句话开始', res);
        };
        // 识别变化时
        _webAudioSpeechRecognizer.OnRecognitionResultChange = (res) => {        
            setText(res.result.voice_text_str)
        };
        // 一句话结束
        _webAudioSpeechRecognizer.OnSentenceEnd = (res) => {
            
            setText(text += res.result.voice_text_str)
        };
        // 识别结束
        _webAudioSpeechRecognizer.OnRecognitionComplete = (res) => {
            setID(nanoid())
        };
        // 识别错误
        _webAudioSpeechRecognizer.OnError = (res) => {
            console.log('识别失败-------------', res)
        };
        setWebAudioSpeechRecognizer(_webAudioSpeechRecognizer)
    },[id])

    const onTalking = () => {
        if (talking) {
            onEnd()
            return
        }
        setTalking(true)
        webAudioSpeechRecognizer.start()

        showRecording("recording", "Recording")
    }

    const onEnd = () => {
        webAudioSpeechRecognizer.stop();
        setTalking(false);
        hiddentRecording("recording", "Recording");
    }

    return (
        <Fragment>
            <TextArea value={text} allowClear />
            <div style={{ height: '10px' }}></div>
            <div style={{ display: 'flex', justifyContent: 'center' }}>
                <Button size='large' type='text' onContextMenu={(event) => { event.preventDefault() }} onClick={onTalking} onTouchStart={onTalking} onTouchEnd={onEnd} style={{ width: '300px', background: talking ? '#b7eb8f' : '#d9d9d9' }} icon={<AudioOutlined style={{ fontSize: "30px" }} />}><FormattedMessage id="B5" /></Button>
            </div>
        </Fragment>
    )

新建一个asr文件夹

新建asrauthentication.js

import config from "../config";
import  CryptoJS from './cryptojs'
/** 获取签名 start */

function toUint8Array(wordArray) {
  // Shortcuts
  const words = wordArray.words;
  const sigBytes = wordArray.sigBytes;

  // Convert
  const u8 = new Uint8Array(sigBytes);
  for (let i = 0; i < sigBytes; i++) {
    u8[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
  }
  return u8;
}

function Uint8ArrayToString(fileData){
  let dataString = '';
  for (let i = 0; i < fileData.length; i++) {
    dataString += String.fromCharCode(fileData[i]);
  }
  return dataString;
}
// 签名函数示例
function signCallback(signStr) {
  const secretKey = config.secretKey;
  const hash = CryptoJS.HmacSHA1(signStr, secretKey);
  const bytes = Uint8ArrayToString(toUint8Array(hash));
  return window.btoa(bytes);
}

/** 获取签名 end */
export {signCallback}

新建cryptojs.js

/*
 * [js-sha1]
 *
 * @version 0.6.0
 * @copyright H, J-C 2018-9-28
 * @license MIT
 */

var CryptoJS = CryptoJS || function (g, l) {
    var e = {}, d = e.lib = {}, m = function () { }, k = d.Base = {
        extend: function (a) {
            m.prototype = this;
            var c = new m;
            a && c.mixIn(a);
            c.hasOwnProperty("init") || (c.init = function () {
            c.$super.init.apply(this, arguments)
            });
            c.init.prototype = c;
            c.$super = this;
            return c
        },
        create: function () {
            var a = this.extend();
            a.init.apply(a, arguments);
            return a
        },
        init: function () { },
        mixIn: function (a) {
            for (var c in a) a.hasOwnProperty(c) && (this[c] = a[c]);
            a.hasOwnProperty("toString") && (this.toString = a.toString)
        },
        clone: function () {
            return this.init.prototype.extend(this)
        }
    },
    p = d.WordArray = k.extend({
        init: function (a, c) {
            a = this.words = a || [];
            this.sigBytes = c != l ? c : 4 * a.length
        },
        toString: function (a) {
            return (a || n).stringify(this)
        },
        concat: function (a) {
            var c = this.words,
                q = a.words,
                f = this.sigBytes;
            a = a.sigBytes;
            this.clamp();
            if (f % 4)
                for (var b = 0; b < a; b++) c[f + b >>> 2] |= (q[b >>> 2] >>> 24 - 8 * (b % 4) & 255) << 24 - 8 * ((f + b) % 4);
            else if (65535 < q.length)
                for (b = 0; b < a; b += 4) c[f + b >>> 2] = q[b >>> 2];
            else c.push.apply(c, q);
            this.sigBytes += a;
            return this
        },
        clamp: function () {
            var a = this.words,
            c = this.sigBytes;
            a[c >>> 2] &= 4294967295 << 32 - 8 * (c % 4);
            a.length = g.ceil(c / 4)
        },
        clone: function () {
            var a = k.clone.call(this);
            a.words = this.words.slice(0);
            return a
        },
        random: function (a) {
            for (var c = [], b = 0; b < a; b += 4) c.push(4294967296 * g.random() | 0);
            return new p.init(c, a)
        }
    }),
    b = e.enc = {}, n = b.Hex = {
        stringify: function (a) {
            var c = a.words;
            a = a.sigBytes;
            for (var b = [], f = 0; f < a; f++) {
                var d = c[f >>> 2] >>> 24 - 8 * (f % 4) & 255;
                b.push((d >>> 4).toString(16));
                b.push((d & 15).toString(16))
            }
            return b.join("")
        },
        parse: function (a) {
            for (var c = a.length, b = [], f = 0; f < c; f += 2) b[f >>> 3] |= parseInt(a.substr(f, 2), 16) << 24 - 4 * (f % 8);
            return new p.init(b, c / 2)
        }
    }, j = b.Latin1 = {
        stringify: function (a) {
          var c = a.words;
          a = a.sigBytes;
          for (var b = [], f = 0; f < a; f++) b.push(String.fromCharCode(c[f >>> 2] >>> 24 - 8 * (f % 4) & 255));
          return b.join("")
        },
        parse: function (a) {
          for (var c = a.length, b = [], f = 0; f < c; f++) b[f >>> 2] |= (a.charCodeAt(f) & 255) << 24 - 8 * (f % 4);
          return new p.init(b, c)
        }
    }, h = b.Utf8 = {
        stringify: function (a) {
            try {
                return decodeURIComponent(escape(j.stringify(a)))
            } catch (c) {
                throw Error("Malformed UTF-8 data");
            }
        },
        parse: function (a) {
            return j.parse(unescape(encodeURIComponent(a)))
        }
    },
    r = d.BufferedBlockAlgorithm = k.extend({
        reset: function () {
            this._data = new p.init;
            this._nDataBytes = 0
        },
        _append: function (a) {
            "string" == typeof a && (a = h.parse(a));
            this._data.concat(a);
            this._nDataBytes += a.sigBytes
        },
        _process: function (a) {
            var c = this._data,
            b = c.words,
            f = c.sigBytes,
            d = this.blockSize,
            e = f / (4 * d),
            e = a ? g.ceil(e) : g.max((e | 0) - this._minBufferSize, 0);
            a = e * d;
            f = g.min(4 * a, f);
            if (a) {
                for (var k = 0; k < a; k += d) this._doProcessBlock(b, k);
                k = b.splice(0, a);
                c.sigBytes -= f
            }
            return new p.init(k, f)
        },
        clone: function () {
            var a = k.clone.call(this);
            a._data = this._data.clone();
            return a
        },
        _minBufferSize: 0
    });
    d.Hasher = r.extend({
        cfg: k.extend(),
        init: function (a) {
            this.cfg = this.cfg.extend(a);
            this.reset()
        },
        reset: function () {
            r.reset.call(this);
            this._doReset()
        },
        update: function (a) {
            this._append(a);
            this._process();
            return this
        },
        finalize: function (a) {
            a && this._append(a);
            return this._doFinalize()
        },
        blockSize: 16,
        _createHelper: function (a) {
            return function (b, d) {
            return (new a.init(d)).finalize(b)
            }
        },
        _createHmacHelper: function (a) {
            return function (b, d) {
            return (new s.HMAC.init(a, d)).finalize(b)
            }
        }
    });
    var s = e.algo = {};
    return e
}(Math);

(function () {
    var g = CryptoJS,
        l = g.lib,
        e = l.WordArray,
        d = l.Hasher,
        m = [],
        l = g.algo.SHA1 = d.extend({
            _doReset: function () {
                this._hash = new e.init([1732584193, 4023233417, 2562383102, 271733878, 3285377520])
            },
            _doProcessBlock: function (d, e) {
                for (var b = this._hash.words, n = b[0], j = b[1], h = b[2], g = b[3], l = b[4], a = 0; 80 > a; a++) {
                    if (16 > a) m[a] = d[e + a] | 0;
                    else {
                    var c = m[a - 3] ^ m[a - 8] ^ m[a - 14] ^ m[a - 16];
                    m[a] = c << 1 | c >>> 31
                    }
                    c = (n << 5 | n >>> 27) + l + m[a];
                    c = 20 > a ? c + ((j & h | ~j & g) + 1518500249) : 40 > a ? c + ((j ^ h ^ g) + 1859775393) : 60 > a ? c + ((j & h | j & g | h & g) - 1894007588) : c + ((j ^ h ^ g) - 899497514);
                    l = g;
                    g = h;
                    h = j << 30 | j >>> 2;
                    j = n;
                    n = c
                }
                b[0] = b[0] + n | 0;
                b[1] = b[1] + j | 0;
                b[2] = b[2] + h | 0;
                b[3] = b[3] + g | 0;
                b[4] = b[4] + l | 0
            },
            _doFinalize: function () {
                var d = this._data,
                    e = d.words,
                    b = 8 * this._nDataBytes,
                    g = 8 * d.sigBytes;
                e[g >>> 5] |= 128 << 24 - g % 32;
                e[(g + 64 >>> 9 << 4) + 14] = Math.floor(b / 4294967296);
                e[(g + 64 >>> 9 << 4) + 15] = b;
                d.sigBytes = 4 * e.length;
                this._process();
                return this._hash
            },
            clone: function () {
                var e = d.clone.call(this);
                e._hash = this._hash.clone();
                return e
            }
    });
    g.SHA1 = d._createHelper(l);
    g.HmacSHA1 = d._createHmacHelper(l)
})();

(function () {
    var g = CryptoJS,
        l = g.enc.Utf8;
        g.algo.HMAC = g.lib.Base.extend({
        init: function (e, d) {
            e = this._hasher = new e.init;
            "string" == typeof d && (d = l.parse(d));
            var g = e.blockSize,
            k = 4 * g;
            d.sigBytes > k && (d = e.finalize(d));
            d.clamp();
            for (var p = this._oKey = d.clone(), b = this._iKey = d.clone(), n = p.words, j = b.words, h = 0; h < g; h++) n[h] ^= 1549556828, j[h] ^= 909522486;
            p.sigBytes = b.sigBytes = k;
            this.reset()
        },
        reset: function () {
            var e = this._hasher;
            e.reset();
            e.update(this._iKey)
        },
        update: function (e) {
            this._hasher.update(e);
            return this
        },
        finalize: function (e) {
            var d = this._hasher;
            e = d.finalize(e);
            d.reset();
            return d.finalize(this._oKey.clone().concat(e))
        }
    })
})();
  
  //使用算法
  // var key = "f7205fffe445421fdssdfsdfdsfs"
  // var sha1_result = CryptoJS.HmacSHA1("helloword", key)
  // console.log('-------',sha1_result.toString())


export default CryptoJS

新建speechrecognizer.js

import './cryptojs.js';

// 识别需要过滤的参数
const needFiltrationParams = ['appid', 'secretkey', 'signCallback', 'echoCancellation'];

function formatSignString(query, params){
    let strParam = "";
    let signStr = "asr.cloud.tencent.com/asr/v2/";
    if(query['appid']){
        signStr += query['appid'];
    }
    const keys = Object.keys(params);
    keys.sort();
    for (let i = 0, len = keys.length; i < len; i++) {
        strParam += `&${keys[i]}=${params[keys[i]]}`;
    }
    return `${signStr}?${strParam.slice(1)}`;
}
async function createQuery(query){
    let params = {};
    const time = new Date().getTime();

    async function getServerTime(){
        return new Promise((resolve, reject)=>{
            try {
                const xhr = new XMLHttpRequest();
                xhr.open("GET", 'https://asr.cloud.tencent.com/server_time', true);
                xhr.send();
                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4 && xhr.status === 200) {
                        resolve(xhr.responseText);
                    }
                }
            } catch (error) {
                reject(error);
            }
        })
    }
    const serverTime = await getServerTime();
    params['secretid'] = query.secretid || '';
    params['engine_model_type'] = query.engine_model_type || '16k_zh';
    params['timestamp'] = parseInt(serverTime) || Math.round(time / 1000);
    params['expired'] = Math.round(time / 1000) + 24 * 60 * 60;
    params['nonce'] = Math.round(time / 100000);
    params['voice_id'] = guid();
    params['voice_format'] = query.voice_format || 1;

    const tempQuery = { ...query };
    for (let i = 0, len = needFiltrationParams.length; i < len; i++) {
        if (tempQuery.hasOwnProperty(needFiltrationParams[i])) {
            delete tempQuery[needFiltrationParams[i]];
        }
    }

    params = {
        ...tempQuery,
        ...params,
    };
    return params;
}

export const guid = () => {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        const r = Math.random() * 16 | 0,
            v = c === 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
};
// 获取签名原文
async function getUrl(self, params) {
    if (!params.appid || !params.secretid) {
        self.isLog && console.log(self.requestId, '请确认是否填入账号信息', TAG);
        self.OnError('请确认是否填入账号信息');
        return false;
    }
    const urlQuery = await createQuery(params);
    const queryStr = formatSignString(params, urlQuery);
    let signature = '';
    if (params.signCallback) {
        signature = params.signCallback(queryStr);
    } else {
        signature = signCallback(params.secretkey, queryStr);
    }
    return `wss://${queryStr}&signature=${encodeURIComponent(signature)}`;
}
/** 获取签名 start */

function toUint8Array(wordArray) {
    // Shortcuts
    const words = wordArray.words;
    const sigBytes = wordArray.sigBytes;

    // Convert
    const u8 = new Uint8Array(sigBytes);
    for (let i = 0; i < sigBytes; i++) {
        u8[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
    }
    return u8;
}

function Uint8ArrayToString(fileData){
    let dataString = '';
    for (let i = 0; i < fileData.length; i++) {
        dataString += String.fromCharCode(fileData[i]);
    }
    return dataString;
}
// 签名函数示例
function signCallback(secretKey, signStr) {
    const hash = window.CryptoJSTest.HmacSHA1(signStr, secretKey);
    const bytes = Uint8ArrayToString(toUint8Array(hash));
    return window.btoa(bytes);
}

/** 获取签名 end */

const TAG = 'SpeechRecognizer';
export class SpeechRecognizer {
    constructor(params, requestId, isLog) {
        this.socket = null;
        this.isSignSuccess = false; // 是否鉴权成功
        this.isSentenceBegin = false; // 是否一句话开始
        this.query = {
            ...params
        };
        this.isRecognizeComplete = false; // 当前是否识别结束
        this.requestId = requestId;
        this.isLog = isLog;
        this.sendCount = 0;
        this.getMessageList = [];
    }
    // 暂停识别,关闭连接
    stop() {
        if (this.socket && this.socket.readyState === 1) {
            this.socket.send(JSON.stringify({type: 'end'}));
            this.isRecognizeComplete = true;
        } else {
            // this.OnError({ code : 6003, message: '连接未建立或连接已关闭' });
            if (this.socket && this.socket.readyState === 1) {
                this.socket.close();
            }
        }
    }
    // 建立websocket链接 data 为用户收集的音频数据
    async start(){
        this.socket = null;
        this.getMessageList = [];
        const url = await getUrl(this, this.query);
        if (!url) {
            this.isLog && console.log(this.requestId, '鉴权失败', TAG);
            this.OnError('鉴权失败');
            return
        }
        this.isLog && console.log(this.requestId, 'get ws url', url, TAG);
        if ('WebSocket' in window) {
            this.socket = new WebSocket(url);
        } else if ('MozWebSocket' in window) {
            this.socket = new MozWebSocket(url);
        } else {
            this.isLog && console.log(this.requestId, '浏览器不支持WebSocket', TAG);
            this.OnError('浏览器不支持WebSocket');
            return
        }
        this.socket.onopen = (e) => { // 连接建立时触发
            this.isLog && console.log(this.requestId, '连接建立', e, TAG);
        };
        this.socket.onmessage = async (e) => { // 连接建立时触发
            try {
                this.getMessageList.push(JSON.stringify(e));
                const response = JSON.parse(e.data);
                if (response.code !== 0) {
                    if (this.socket.readyState === 1) {
                        this.socket.close();
                    }
                    this.isLog && console.log(this.requestId, JSON.stringify(response), TAG);
                    this.OnError(response);
                } else {
                    if (!this.isSignSuccess) {
                        this.OnRecognitionStart(response);
                        this.isSignSuccess = true;
                    }
                    if (response.final === 1) {
                        this.OnRecognitionComplete(response);
                        return;
                    }
                    if (response.result) {
                        if (response.result.slice_type === 0) {
                            this.OnSentenceBegin(response);
                            this.isSentenceBegin = true;
                        } else  if (response.result.slice_type === 2) {
                            if (!this.isSentenceBegin) {
                                this.OnSentenceBegin(response);
                            }
                            this.OnSentenceEnd(response);
                        } else {
                            this.OnRecognitionResultChange(response);
                        }
                    }
                    this.isLog && console.log(this.requestId, response, TAG);
                }
            } catch (e) {
                this.isLog && console.log(this.requestId, 'socket.onmessage catch error', JSON.stringify(e), TAG);
            }

        };
        this.socket.onerror = (e) => { // 通信发生错误时触发
            this.isLog && console.log(this.requestId, 'socket error callback', e, TAG);
            this.socket.close();
            this.OnError(e);
        }
        this.socket.onclose = (event) => {
            try {
                if (!this.isRecognizeComplete) {
                    this.isLog && console.log(this.requestId, 'socket is close and error', JSON.stringify(event), TAG);
                    this.OnError(event);
                }
            } catch (e) {
                this.isLog && console.log(this.requestId, 'socket is onclose catch' + this.sendCount, JSON.stringify(e), TAG);
            }
        }
    }
    close() {
        this.socket && this.socket.readyState === 1 && this.socket.close(1000);
    }
    // 发送数据
    write(data) {
        try {
            if (!this.socket || String(this.socket.readyState) !== '1') {
                setTimeout(() => {
                    if (this.socket && this.socket.readyState === 1) {
                        this.socket.send(data);
                    }
                }, 100);
                return false;
            }
            this.sendCount += 1;
            this.socket.send(data);
        } catch (e) {
            this.isLog && console.log(this.requestId , '发送数据 error catch', e,  TAG);
        }
    };
    // 开始识别的时候
    OnRecognitionStart(res) {

    }
    // 一句话开始的时候
    OnSentenceBegin(res) {

    }
    // 识别结果发生变化的时候
    OnRecognitionResultChange() {

    }
    // 一句话结束的时候
    OnSentenceEnd() {

    }
    // 识别结束的时候
    OnRecognitionComplete() {

    }
    // 识别失败
    OnError() {

    }
}

新建webaudiospeechrecognizer.js

import WebRecorder from "./webrecorder.js";
import { SpeechRecognizer, guid } from "./speechrecognizer.js";

export default class WebAudioSpeechRecognizer {
  constructor(params, isLog) {
    this.params = params;
    this.recorder = null;
    this.speechRecognizer = null;
    this.isCanSendData = false;
    this.isNormalEndStop = false;
    this.audioData = [];
    this.isLog = isLog;
    this.requestId = null;
  }
  start() {
    try {
      this.isLog && console.log('start function is click');
      this.requestId = guid();
      this.recorder = new WebRecorder(this.requestId, this.params, this.isLog);
      
      this.recorder.OnReceivedData = (data) => {
        if (this.isCanSendData) {
          this.speechRecognizer && this.speechRecognizer.write(data);
        }
      };
      // 录音失败时
      this.recorder.OnError = (err) => {
        this.speechRecognizer && this.speechRecognizer.close();
        this.stop();
        this.OnError(err);
      }
      this.recorder.OnStop  = (res) => {
        
        if (this.speechRecognizer) {
          this.speechRecognizer.stop();
          // this.speechRecognizer = null;
        }
        this.OnRecorderStop(res);
      }
      this.recorder.start();
      if (!this.speechRecognizer) {        
        this.speechRecognizer = new SpeechRecognizer(this.params, this.requestId, this.isLog);
        console.log(this.speechRecognizer);
        
      }
      // 开始识别
      this.speechRecognizer.OnRecognitionStart = (res) => {
        if (this.recorder) { // 录音正常
          this.OnRecognitionStart(res);
          this.isCanSendData = true;
        } else {
          this.speechRecognizer && this.speechRecognizer.close();
        }
      };
      // 一句话开始
      this.speechRecognizer.OnSentenceBegin = (res) => {        
        this.OnSentenceBegin(res);
      };
      // 识别变化时
      this.speechRecognizer.OnRecognitionResultChange = (res) => {
        this.OnRecognitionResultChange(res);
      };
      // 一句话结束
      this.speechRecognizer.OnSentenceEnd = (res) => {
        this.OnSentenceEnd(res);
      };
      // 识别结束
      this.speechRecognizer.OnRecognitionComplete = (res) => {
        this.OnRecognitionComplete(res);
        this.isCanSendData = false;
        this.isNormalEndStop = true;
      };
      // 识别错误
      this.speechRecognizer.OnError = (res) => {
        if (this.speechRecognizer && !this.isNormalEndStop) {
          this.OnError(res);
        }
        this.speechRecognizer = null;
        this.recorder && this.recorder.stop();
        this.isCanSendData = false;
      };
      // 建立连接
      this.speechRecognizer.start();
    } catch (e) {
      console.log(e);
    }
  }
  stop() {
    this.isLog && console.log('stop function is click');
    
    if (this.recorder) {
      this.recorder.stop();
    }
  }
  destroyStream() {
    this.isLog && console.log('destroyStream function is click', this.recorder);
    if (this.recorder) {
      this.recorder.destroyStream();
    }
  }
  // 开始识别的时候
  OnRecognitionStart(res) {}
  // 一句话开始的时候
  OnSentenceBegin(res) {}
  // 识别结果发生变化的时候
  OnRecognitionResultChange() {}
  // 一句话结束的时候
  OnSentenceEnd() {}
  // 识别结束的时候
  OnRecognitionComplete() {}
  // 识别失败
  OnError() {}
  OnRecorderStop() {}
};

新建webrecorder.js

export function to16BitPCM(input) {
  const dataLength = input.length * (16 / 8);
  const dataBuffer = new ArrayBuffer(dataLength);
  const dataView = new DataView(dataBuffer);
  let offset = 0;
  for (let i = 0; i < input.length; i++, offset += 2) {
    const s = Math.max(-1, Math.min(1, input[i]));
    dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
  }
  return dataView;
}
export function to16kHz(audioData, sampleRate= 44100) {
  const data = new Float32Array(audioData);
  const fitCount = Math.round(data.length * (16000 / sampleRate));
  const newData = new Float32Array(fitCount);
  const springFactor = (data.length - 1) / (fitCount - 1);
  newData[0] = data[0];
  for (let i = 1; i < fitCount - 1; i++) {
    const tmp = i * springFactor;
    const before = Math.floor(tmp).toFixed();
    const after = Math.ceil(tmp).toFixed();
    const atPoint = tmp - before;
    newData[i] = data[before] + (data[after] - data[before]) * atPoint;
  }
  newData[fitCount - 1] = data[data.length - 1];
  return newData;
}

const audioWorkletCode = `
class MyProcessor extends AudioWorkletProcessor {
  constructor(options) {
    super(options);
    this.audioData = [];
    this.sampleCount = 0;
    this.bitCount = 0;
    this.preTime = 0;
  }

  process(inputs) {
    // 去处理音频数据
    // eslint-disable-next-line no-undef
    if (inputs[0][0]) {
      const output = ${to16kHz}(inputs[0][0], sampleRate);
      this.sampleCount += 1;
      const audioData = ${to16BitPCM}(output);
      this.bitCount += 1;
      const data = [...new Int8Array(audioData.buffer)];
      this.audioData = this.audioData.concat(data);
      if (new Date().getTime() - this.preTime > 100) {
        this.port.postMessage({
          audioData: new Int8Array(this.audioData),
          sampleCount: this.sampleCount,
          bitCount: this.bitCount
        });
        this.preTime = new Date().getTime();
        this.audioData = [];
      }
        return true;
      }
  }
}

registerProcessor('my-processor', MyProcessor);
`;
const TAG = 'WebRecorder';
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
    || navigator.mozGetUserMedia || navigator.msGetUserMedia;


export default class WebRecorder {
  constructor(requestId, params, isLog) {
    this.audioData = [];
    this.allAudioData = [];
    this.stream = null;
    this.audioContext = null;
    this.requestId = requestId;
    this.frameTime = [];
    this.frameCount = 0;
    this.sampleCount = 0;
    this.bitCount = 0;
    this.mediaStreamSource = null;
    this.isLog = isLog;
    this.params = params;
  }
  static isSupportMediaDevicesMedia() {
    return !!(navigator.getUserMedia || (navigator.mediaDevices && navigator.mediaDevices.getUserMedia));
  }
  static isSupportUserMediaMedia() {
    return !!navigator.getUserMedia;
  }
  static isSupportAudioContext() {
    return typeof AudioContext !== 'undefined' || typeof webkitAudioContext !== 'undefined';
  }
  static isSupportMediaStreamSource(requestId, audioContext) {
    return typeof audioContext.createMediaStreamSource === 'function';
  }
  static isSupportAudioWorklet(audioContext) {
    return audioContext.audioWorklet && typeof audioContext.audioWorklet.addModule === 'function'
        && typeof AudioWorkletNode !== 'undefined';
  }
  static isSupportCreateScriptProcessor(requestId, audioContext) {
    return typeof audioContext.createScriptProcessor === 'function';
  }
  start() {
    this.frameTime = [];
    this.frameCount = 0;
    this.allAudioData = [];
    this.audioData = [];
    this.sampleCount = 0;
    this.bitCount = 0;
    this.getDataCount = 0;
    this.audioContext = null;
    this.mediaStreamSource = null;
    this.stream = null;
    this.preTime = 0;
    try {
      if (WebRecorder.isSupportAudioContext()) {
        this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
      } else {
        this.isLog && console.log(this.requestId, '浏览器不支持AudioContext', TAG);
        this.OnError('浏览器不支持AudioContext');
      }
    } catch (e) {
      this.isLog && console.log(this.requestId, '浏览器不支持webAudioApi相关接口', e, TAG);
      this.OnError('浏览器不支持webAudioApi相关接口');
    }
    this.getUserMedia(this.requestId, this.getAudioSuccess, this.getAudioFail);
  }
  stop() {
    if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent))){
      this.audioContext && this.audioContext.suspend();
    }
    this.audioContext && this.audioContext.suspend();
    this.isLog && console.log(this.requestId, `webRecorder stop ${this.sampleCount}/${this.bitCount}/${this.getDataCount}` , JSON.stringify(this.frameTime), TAG);
    this.OnStop(this.allAudioData);
  }
  destroyStream() {
    // 关闭通道
    if (this.stream) {
      this.stream.getTracks().map((val) => {
        val.stop();
      });
      this.stream = null;
    }
  }
  async getUserMedia(requestId, getStreamAudioSuccess, getStreamAudioFail) {
    let audioOption = {
      echoCancellation: true,
    };
    if (this.params && String(this.params.echoCancellation) === 'false') { // 关闭回声消除
      audioOption = {
        echoCancellation: false,
      };
    }
    const mediaOption = {
      audio: audioOption,
      video: false,
    };
    // 获取用户的麦克风
    if (WebRecorder.isSupportMediaDevicesMedia()) {
      navigator.mediaDevices
          .getUserMedia(mediaOption)
          .then(stream => {
            this.stream = stream;
            getStreamAudioSuccess.call(this, requestId, stream);
          })
          .catch(e => {
            getStreamAudioFail.call(this, requestId, e);
          });
    } else if (WebRecorder.isSupportUserMediaMedia()) {
      navigator.getUserMedia(mediaOption,
          stream => {
            this.stream = stream;
            getStreamAudioSuccess.call(this, requestId, stream);
          },
          function(err) {
            getStreamAudioFail.call(this, requestId, err);
          }
      );
    } else {
      if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) {
        this.isLog && console.log(this.requestId, 'chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限', TAG);
        this.OnError('chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限');
      } else {
        this.isLog && console.log(this.requestId, '无法获取浏览器录音功能,请升级浏览器或使用chrome', TAG);
        this.OnError('无法获取浏览器录音功能,请升级浏览器或使用chrome');
      }
      this.audioContext && this.audioContext.close();
    }
  }
  async getAudioSuccess(requestId, stream) {
    if (!this.audioContext) {
      return false;
    }
    if (this.mediaStreamSource) {
      this.mediaStreamSource.disconnect();
      this.mediaStreamSource = null;
    }
    this.audioTrack = stream.getAudioTracks()[0];
    const mediaStream = new MediaStream();
    mediaStream.addTrack(this.audioTrack);
    this.mediaStreamSource = this.audioContext.createMediaStreamSource(mediaStream);
    if (WebRecorder.isSupportMediaStreamSource(requestId, this.audioContext)) {
      if (WebRecorder.isSupportAudioWorklet(this.audioContext)) { // 不支持 AudioWorklet 降级
        this.audioWorkletNodeDealAudioData(this.mediaStreamSource, requestId);
      } else {
        this.scriptNodeDealAudioData(this.mediaStreamSource, requestId);
      }
    } else { // 不支持 MediaStreamSource
      this.isLog && console.log(this.requestId, '不支持MediaStreamSource', TAG);
      this.OnError('不支持MediaStreamSource');
    }
  }
  getAudioFail(requestId, err) {
    if (err && err.err && err.err.name === 'NotAllowedError') {
      this.isLog && console.log(requestId,'授权失败', JSON.stringify(err.err), TAG);
    }
    this.isLog && console.log(this.requestId, 'getAudioFail', JSON.stringify(err), TAG);
    this.OnError(err);
    this.stop();
  }
  scriptNodeDealAudioData(mediaStreamSource, requestId) {
    if (WebRecorder.isSupportCreateScriptProcessor(requestId, this.audioContext)) {
      // 创建一个音频分析对象,采样的缓冲区大小为0(自动适配),输入和输出都是单声道
      const scriptProcessor = this.audioContext.createScriptProcessor(1024, 1, 1);
      // 连接
      this.mediaStreamSource && this.mediaStreamSource.connect(scriptProcessor);
      scriptProcessor && scriptProcessor.connect(this.audioContext.destination);
      scriptProcessor.onaudioprocess = (e) => {
        this.getDataCount += 1;
        // 去处理音频数据
        const inputData = e.inputBuffer.getChannelData(0);
        const output = to16kHz(inputData, this.audioContext.sampleRate);
        const audioData = to16BitPCM(output);
        this.audioData.push(...new Int8Array(audioData.buffer));
        this.allAudioData.push(...new Int8Array(audioData.buffer));
        if (new Date().getTime() - this.preTime > 100) {
          this.frameTime.push(`${Date.now()}-${this.frameCount}`);
          this.frameCount += 1;
          this.preTime = new Date().getTime();
          const audioDataArray = new Int8Array(this.audioData);
          this.OnReceivedData(audioDataArray);
          this.audioData = [];
          this.sampleCount += 1;
          this.bitCount += 1;
        }
      };
    } else { // 不支持
      this.isLog && console.log(this.requestId, '不支持createScriptProcessor', TAG);
    }
  }
  async audioWorkletNodeDealAudioData(mediaStreamSource, requestId) {
    try {
      const audioWorkletBlobURL = window.URL.createObjectURL(new Blob([audioWorkletCode], { type: 'text/javascript' }));
      await this.audioContext.audioWorklet.addModule(audioWorkletBlobURL);
      const myNode = new AudioWorkletNode(this.audioContext, 'my-processor', { numberOfInputs: 1, numberOfOutputs: 1, channelCount: 1 });
      myNode.onprocessorerror = (event) => {
        // 降级
        this.scriptNodeDealAudioData(mediaStreamSource, this.requestId);
        return false;
      }
      myNode.port.onmessage = (event) => {
        this.frameTime.push(`${Date.now()}-${this.frameCount}`);
        this.OnReceivedData(event.data.audioData);
        this.frameCount += 1;
        this.allAudioData.push(...event.data.audioData);
        this.sampleCount = event.data.sampleCount;
        this.bitCount = event.data.bitCount;
      };
      myNode.port.onmessageerror = (event) => {
        // 降级
        this.scriptNodeDealAudioData(mediaStreamSource, requestId);
        return false;
      }
      mediaStreamSource &&mediaStreamSource.connect(myNode).connect(this.audioContext.destination);
    } catch (e) {
      this.isLog && console.log(this.requestId, 'audioWorkletNodeDealAudioData catch error', JSON.stringify(e), TAG);
      this.OnError(e);
    }
  }
  // 获取音频数据
  OnReceivedData(data) {}
  OnError(res) {}
  OnStop(res) {}
}

config.js

let config = {
    secretKey: '',
    secretId: '',
    appId: ,
  }

export default config

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注