; (function (window, voice) { "use strict"; if (typeof define === 'function' && define.amd) { define(voice); } else if (typeof exports === 'object') { module.exports = voice(); } else { window.Voice = voice(); }; }(typeof window !== "undefined" ? window : this, () => { "use strict"; return class IatRecorder { constructor(opts = {}) { // 服务接口认证信息(语音听写(流式版)WebAPI) this.appId = opts.appId || ''; this.apiKey = opts.apiKey || ''; this.apiSecret = opts.apiSecret || ''; // 识别监听方法 this.onTextChange = opts.onTextChange || Function(); this.onWillStatusChange = opts.onWillStatusChange || Function(); // 方言/语种 this.status = 'null' this.language = opts.language || 'zh_cn' this.accent = opts.accent || 'mandarin'; // 流媒体 this.streamRef = []; // 记录音频数据 this.audioData = []; // 记录听写结果 this.resultText = ''; // wpgs下的听写结果需要中间状态辅助记录 this.resultTextTemp = ''; // 音频数据多线程 this.init(); }; // WebSocket请求地址鉴权 getWebSocketUrl() { return new Promise((resolve, reject) => { // 请求地址根据语种不同变化 try { const CryptoJS = require('crypto-js'); let url = 'wss://iat-api.xfyun.cn/v2/iat', host = 'iat-api.xfyun.cn', date = new Date().toGMTString(), algorithm = 'hmac-sha256', headers = 'host date request-line', signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`, signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret), signature = CryptoJS.enc.Base64.stringify(signatureSha), authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`, authorization = btoa(authorizationOrigin); resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`); } catch (error) { let url = 'wss://iat-api.xfyun.cn/v2/iat', host = 'iat-api.xfyun.cn', date = new Date().toGMTString(), algorithm = 'hmac-sha256', headers = 'host date request-line', signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`, signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret), signature = CryptoJS.enc.Base64.stringify(signatureSha), authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`, authorization = btoa(authorizationOrigin); resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`); }; }); }; // 操作初始化 init() { const self = this; try { if (!self.appId || !self.apiKey || !self.apiSecret) { alert('请正确配置【迅飞语音听写(流式版)WebAPI】服务接口认证信息!'); } else { self.webWorker = new Worker('./js/transcode.worker.js'); self.webWorker.onmessage = function (event) { self.audioData.push(...event.data); }; } } catch (error) { alert('对不起:请在服务器环境下运行!'); console.error('请在服务器如:WAMP、XAMPP、Phpstudy、http-server、WebServer等环境中运行!', error); }; console.log("%c ❤️使用说明:http://www.muguilin.com/blog/info/609bafc50d572b3fd79b058f", "font-size:32px; color:blue; font-weight: bold;"); }; // 修改录音听写状态 setStatus(status) { this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status); this.status = status; }; // 设置识别结果内容 setResultText({ resultText, resultTextTemp } = {}) { this.onTextChange && this.onTextChange(resultTextTemp || resultText || ''); resultText !== undefined && (this.resultText = resultText); resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp); }; // 修改听写参数 setParams({ language, accent } = {}) { language && (this.language = language) accent && (this.accent = accent) }; // 对处理后的音频数据进行base64编码, toBase64(buffer) { let binary = ''; let bytes = new Uint8Array(buffer); for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); }; // 连接WebSocket connectWebSocket() { return this.getWebSocketUrl().then(url => { let iatWS; if ('WebSocket' in window) { iatWS = new WebSocket(url); } else if ('MozWebSocket' in window) { iatWS = new MozWebSocket(url); } else { alert('浏览器不支持WebSocket!'); return false; } this.webSocket = iatWS; this.setStatus('init'); iatWS.onopen = e => { this.setStatus('ing'); // 重新开始录音 setTimeout(() => { this.webSocketSend(); }, 500); }; iatWS.onmessage = e => { this.webSocketRes(e.data); }; iatWS.onerror = e => { this.recorderStop(e); }; iatWS.onclose = e => { this.recorderStop(e); }; }) }; // 初始化浏览器录音 recorderInit() { // 创建音频环境 try { this.audioContext = this.audioContext ? this.audioContext : new (window.AudioContext || window.webkitAudioContext)(); this.audioContext.resume(); if (!this.audioContext) { alert('浏览器不支持webAudioApi相关接口'); return false; } } catch (e) { if (!this.audioContext) { alert('浏览器不支持webAudioApi相关接口'); return false; } }; // 获取浏览器录音权限成功时回调 let getMediaSuccess = _ => { // 创建一个用于通过JavaScript直接处理音频 this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1); this.scriptProcessor.onaudioprocess = e => { if (this.status === 'ing') { // 多线程音频数据处理 try { this.webWorker.postMessage(e.inputBuffer.getChannelData(0)); } catch (error) { } } } // 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作 this.mediaSource = this.audioContext.createMediaStreamSource(this.streamRef); this.mediaSource.connect(this.scriptProcessor); this.scriptProcessor.connect(this.audioContext.destination); this.connectWebSocket(); }; // 获取浏览器录音权限失败时回调 let getMediaFail = (e) => { alert('对不起:录音权限获取失败!'); this.audioContext && this.audioContext.close(); this.audioContext = undefined; // 关闭websocket if (this.webSocket && this.webSocket.readyState === 1) { this.webSocket.close(); } }; navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; // 获取浏览器录音权限 if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { this.streamRef = stream; getMediaSuccess(); }).catch(e => { getMediaFail(e); }) } else if (navigator.getUserMedia) { navigator.getUserMedia({ audio: true }, (stream) => { this.streamRef = stream; getMediaSuccess(); }, function (e) { getMediaFail(e); }) } else { if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) { console.error('获取浏览器录音功能,因安全性问题,需要在localhost 或 127.0.0.1 或 https 下才能获取权限!'); } else { alert('对不起:未识别到录音设备!'); } this.audioContext && this.audioContext.close(); return false; }; }; // 向webSocket发送数据(音频二进制数据经过Base64处理) webSocketSend() { if (this.webSocket.readyState !== 1) return false; // 音频数据 const audioData = this.audioData.splice(0, 1280); const params = { common: { app_id: this.appId, }, business: { language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用 domain: 'iat', accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用 vad_eos: 5000, dwa: 'wpgs' //为使该功能生效,需到控制台开通动态修正功能(该功能免费) }, data: { status: 0, format: 'audio/L16;rate=16000', encoding: 'raw', audio: this.toBase64(audioData) } }; // 发送数据 this.webSocket.send(JSON.stringify(params)); this.handlerInterval = setInterval(() => { // websocket未连接 if (this.webSocket.readyState !== 1) { this.audioData = []; clearInterval(this.handlerInterval); return false; }; if (this.audioData.length === 0) { if (this.status === 'end') { this.webSocket.send( JSON.stringify({ data: { status: 2, format: 'audio/L16;rate=16000', encoding: 'raw', audio: '' } }) ); this.audioData = []; clearInterval(this.handlerInterval); } return false; }; // 中间帧 this.webSocket.send( JSON.stringify({ data: { status: 1, format: 'audio/L16;rate=16000', encoding: 'raw', audio: this.toBase64(this.audioData.splice(0, 1280)) } }) ); }, 40); }; // 识别结束 webSocket返回数据 webSocketRes(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.setResultText({ resultText: this.resultTextTemp }); } // 将结果存储在resultTextTemp中 this.setResultText({ resultTextTemp: this.resultText + str }); } else { this.setResultText({ resultText: this.resultText + str }); } } if (jsonData.code === 0 && jsonData.data.status === 2) { this.webSocket.close(); } if (jsonData.code !== 0) { this.webSocket.close(); } }; // 启动录音 recorderStart() { if (!this.audioContext) { this.recorderInit(); } else { this.audioContext.resume(); this.connectWebSocket(); } }; // 停止录音 recorderStop() { if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))) { // safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend this.audioContext && this.audioContext.suspend(); } this.setStatus('end'); try { // this.streamRef.getTracks().map(track => track.stop()) || his.streamRef.getAudioTracks()[0].stop(); } catch (error) { console.error('暂停失败!'); } }; // 开始 start() { this.recorderStart(); this.setResultText({ resultText: '', resultTextTemp: '' }); }; // 停止 stop() { this.recorderStop(); }; }; }));