首先是组件
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
发表回复