├── static ├── .gitkeep └── image │ ├── run.png │ └── timg.gif ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── src ├── assets │ ├── logo.png │ └── js │ │ ├── transcode.worker.js │ │ └── IatRecorder.js ├── main.js ├── App.vue └── components │ └── HelloWorld.vue ├── .editorconfig ├── .gitignore ├── .babelrc ├── .postcssrc.js ├── index.html ├── README.md └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ma-Tao007/xunfei-vioceAl-vueSDK/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /static/image/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ma-Tao007/xunfei-vioceAl-vueSDK/HEAD/static/image/run.png -------------------------------------------------------------------------------- /static/image/timg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ma-Tao007/xunfei-vioceAl-vueSDK/HEAD/static/image/timg.gif -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx"] 12 | } 13 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | xunfei-vioce-demo 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | 6 | Vue.config.productionTip = false 7 | 8 | /* eslint-disable no-new */ 9 | new Vue({ 10 | el: '#app', 11 | components: { App }, 12 | template: '' 13 | }) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xunfei-vioce-demo 2 | 3 | > 一个基于科大讯飞语音识别接口的vue案例[java版本案例](https://github.com/Ma-Tao007/xunfei-vioceAl-javaSDK) 4 | 5 | ## Build Setup 6 | 7 | # 下载依赖 8 | npm install 9 | 10 | # 运行项目 11 | npm run dev(如果报错请使用yarn run serve运行) 12 | 13 | # 运行说明 14 | 1.电脑要能支持语音录入(台式机没有外设的需要插入耳机,不然无法运行)
15 | 2.案例中使用的科大讯飞接口控制台的参数修改成自己的参数使用,在src/assets/js/IatRecorder.js中的前三个参数设置为对应 16 | 你自己应用的参数 17 | 18 | # 效果截图 19 | ![image](https://github.com/Ma-Tao007/xunfei-vioceAl-vueSDK/blob/master/static/image/run.png) 20 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /src/assets/js/transcode.worker.js: -------------------------------------------------------------------------------- 1 | 2 | self.onmessage = function(e){ 3 | transAudioData.transcode(e.data) 4 | } 5 | let transAudioData = { 6 | transcode(audioData) { 7 | let output = transAudioData.to16kHz(audioData) 8 | output = transAudioData.to16BitPCM(output) 9 | output = Array.from(new Uint8Array(output.buffer)) 10 | self.postMessage(output) 11 | // return output 12 | }, 13 | to16kHz(audioData) { 14 | var data = new Float32Array(audioData) 15 | var fitCount = Math.round(data.length * (16000 / 44100)) 16 | var newData = new Float32Array(fitCount) 17 | var springFactor = (data.length - 1) / (fitCount - 1) 18 | newData[0] = data[0] 19 | for (let i = 1; i < fitCount - 1; i++) { 20 | var tmp = i * springFactor 21 | var before = Math.floor(tmp).toFixed() 22 | var after = Math.ceil(tmp).toFixed() 23 | var atPoint = tmp - before 24 | newData[i] = data[before] + (data[after] - data[before]) * atPoint 25 | } 26 | newData[fitCount - 1] = data[data.length - 1] 27 | return newData 28 | }, 29 | to16BitPCM(input) { 30 | var dataLength = input.length * (16 / 8) 31 | var dataBuffer = new ArrayBuffer(dataLength) 32 | var dataView = new DataView(dataBuffer) 33 | var offset = 0 34 | for (var i = 0; i < input.length; i++, offset += 2) { 35 | var s = Math.max(-1, Math.min(1, input[i])) 36 | dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true) 37 | } 38 | return dataView 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xunfei-vioce-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "mt", 6 | "private": true, 7 | "scripts": { 8 | "serve": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "test": "npm run unit", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.19.2", 15 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 16 | "fastclick": "^1.0.6", 17 | "nprogress": "^0.2.0", 18 | "recorderx": "^2.0.2", 19 | "vue": "^2.5.2", 20 | "vue-lazyload": "^1.3.3", 21 | "vue-router": "^3.0.1", 22 | "webpack-dev-server": "^3.11.0", 23 | "worker-loader": "^3.0.1" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer": "^7.1.2", 27 | "babel-core": "^6.22.1", 28 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 29 | "babel-loader": "^7.1.1", 30 | "babel-plugin-component": "^1.1.1", 31 | "babel-plugin-syntax-jsx": "^6.18.0", 32 | "babel-plugin-transform-runtime": "^6.22.0", 33 | "babel-plugin-transform-vue-jsx": "^3.5.0", 34 | "babel-preset-env": "^1.3.2", 35 | "babel-preset-stage-2": "^6.22.0", 36 | "chalk": "^2.0.1", 37 | "copy-webpack-plugin": "^4.0.1", 38 | "crypto-js": "^4.0.0", 39 | "css-loader": "^0.28.0", 40 | "enc": "^0.4.0", 41 | "extract-text-webpack-plugin": "^3.0.0", 42 | "file-loader": "^1.1.4", 43 | "friendly-errors-webpack-plugin": "^1.6.1", 44 | "html-webpack-plugin": "^3.2.0", 45 | "node-notifier": "^5.1.2", 46 | "optimize-css-assets-webpack-plugin": "^3.2.0", 47 | "ora": "^1.2.0", 48 | "popper.js": "^1.16.1", 49 | "portfinder": "^1.0.13", 50 | "postcss-import": "^11.0.0", 51 | "postcss-loader": "^2.0.8", 52 | "postcss-url": "^7.2.1", 53 | "rimraf": "^2.6.0", 54 | "semver": "^5.3.0", 55 | "shelljs": "^0.7.6", 56 | "style-loader": "^1.2.1", 57 | "uglifyjs-webpack-plugin": "^1.1.1", 58 | "url-loader": "^0.5.8", 59 | "vconsole": "^3.3.4", 60 | "vue-loader": "^15.9.3", 61 | "vue-style-loader": "^3.0.1", 62 | "vue-template-compiler": "^2.5.2", 63 | "webpack": "^4.44.1", 64 | "webpack-bundle-analyzer": "^2.9.0", 65 | "webpack-cli": "^3.3.12", 66 | "webpack-merge": "^4.1.0" 67 | }, 68 | "engines": { 69 | "node": ">= 6.0.0", 70 | "npm": ">= 3.0.0" 71 | }, 72 | "browserslist": [ 73 | "> 1%", 74 | "last 2 versions", 75 | "not ie <= 8" 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | configureWebpack: config => { 9 | config.module.rules.push({ 10 | test: /\.worker.js$/, 11 | use: { 12 | loader: 'worker-loader', 13 | options: { inline: true, name: 'workerName.[hash].js' } 14 | } 15 | }) 16 | }, 17 | dev: { 18 | 19 | // Paths 20 | assetsSubDirectory: 'static', 21 | assetsPublicPath: '/', 22 | proxyTable: {}, 23 | 24 | // Various Dev Server settings 25 | host: 'localhost', // can be overwritten by process.env.HOST 26 | port: 8085, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 27 | autoOpenBrowser: false, 28 | errorOverlay: true, 29 | notifyOnErrors: true, 30 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 31 | 32 | 33 | /** 34 | * Source Maps 35 | */ 36 | 37 | // https://webpack.js.org/configuration/devtool/#development 38 | devtool: 'cheap-module-eval-source-map', 39 | 40 | // If you have problems debugging vue-files in devtools, 41 | // set this to false - it *may* help 42 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 43 | cacheBusting: true, 44 | 45 | cssSourceMap: true 46 | }, 47 | 48 | build: { 49 | // Template for index.html 50 | index: path.resolve(__dirname, '../dist/index.html'), 51 | 52 | // Paths 53 | assetsRoot: path.resolve(__dirname, '../dist'), 54 | assetsSubDirectory: 'static', 55 | assetsPublicPath: '/', 56 | 57 | /** 58 | * Source Maps 59 | */ 60 | 61 | productionSourceMap: true, 62 | // https://webpack.js.org/configuration/devtool/#production 63 | devtool: '#source-map', 64 | 65 | // Gzip off by default as many popular static hosts such as 66 | // Surge or Netlify already gzip all static assets for you. 67 | // Before setting to `true`, make sure to: 68 | // npm install --save-dev compression-webpack-plugin 69 | productionGzip: false, 70 | productionGzipExtensions: ['js', 'css'], 71 | 72 | // Run the build command with an extra argument to 73 | // View the bundle analyzer report after build finishes: 74 | // `npm run build --report` 75 | // Set to `true` or `false` to always turn it on or off 76 | bundleAnalyzerReport: process.env.npm_config_report 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 42 | 141 | -------------------------------------------------------------------------------- /src/assets/js/IatRecorder.js: -------------------------------------------------------------------------------- 1 | 2 | const APPID = '' 3 | const API_SECRET = '' 4 | const API_KEY = '' 5 | import CryptoJS from 'crypto-js' 6 | import Worker from './transcode.worker.js' 7 | const transWorker = new Worker() 8 | console.log(transWorker) 9 | var startTime = "" 10 | var endTime = "" 11 | 12 | function getWebSocketUrl(){ 13 | return new Promise((resolve, reject) => { 14 | // 请求地址根据语种不同变化 15 | var url = 'wss://iat-api.xfyun.cn/v2/iat' 16 | var host = 'iat-api.xfyun.cn' 17 | var apiKey = API_KEY 18 | var apiSecret = API_SECRET 19 | var date = new Date().toGMTString() 20 | var algorithm = 'hmac-sha256' 21 | var headers = 'host date request-line' 22 | var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1` 23 | var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret) 24 | var signature = CryptoJS.enc.Base64.stringify(signatureSha) 25 | var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"` 26 | var authorization = btoa(authorizationOrigin) 27 | url = `${url}?authorization=${authorization}&date=${date}&host=${host}` 28 | resolve(url) 29 | }) 30 | } 31 | const IatRecorder = class { 32 | constructor({ language, accent, appId } = {}) { 33 | let self = this 34 | this.status = 'null' 35 | this.language = language || 'zh_cn' 36 | this.accent = accent || 'mandarin' 37 | this.appId = appId || APPID 38 | // 记录音频数据 39 | this.audioData = [] 40 | // 记录听写结果 41 | this.resultText = '' 42 | // wpgs下的听写结果需要中间状态辅助记录 43 | this.resultTextTemp = '' 44 | transWorker.onmessage = function (event) { 45 | // console.log("构造方法中",self.audioData) 46 | self.audioData.push(...event.data) 47 | } 48 | } 49 | 50 | // 修改录音听写状态 51 | setStatus(status) { 52 | this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status) 53 | this.status = status 54 | } 55 | setResultText({ resultText, resultTextTemp } = {}) { 56 | this.onTextChange && this.onTextChange(resultTextTemp || resultText || '') 57 | resultText !== undefined && (this.resultText = resultText) 58 | resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp) 59 | } 60 | // 修改听写参数 61 | setParams({ language, accent } = {}) { 62 | language && (this.language = language) 63 | accent && (this.accent = accent) 64 | } 65 | // 连接websocket 66 | connectWebSocket() { 67 | return getWebSocketUrl().then(url => { 68 | let iatWS 69 | if ('WebSocket' in window) { 70 | iatWS = new WebSocket(url) 71 | } else if ('MozWebSocket' in window) { 72 | iatWS = new MozWebSocket(url) 73 | } else { 74 | alert('浏览器不支持WebSocket') 75 | return 76 | } 77 | this.webSocket = iatWS 78 | this.setStatus('init') 79 | iatWS.onopen = e => { 80 | this.setStatus('ing') 81 | // 重新开始录音 82 | setTimeout(() => { 83 | this.webSocketSend() 84 | }, 500) 85 | } 86 | iatWS.onmessage = e => { 87 | this.result(e.data) 88 | } 89 | iatWS.onerror = e => { 90 | this.recorderStop() 91 | } 92 | iatWS.onclose = e => { 93 | endTime = Date.parse(new Date()) 94 | console.log("持续时间",endTime-startTime) 95 | this.recorderStop() 96 | } 97 | }) 98 | } 99 | // 初始化浏览器录音 100 | recorderInit() { 101 | navigator.getUserMedia = 102 | navigator.getUserMedia || 103 | navigator.webkitGetUserMedia || 104 | navigator.mozGetUserMedia || 105 | navigator.msGetUserMedia 106 | 107 | // 创建音频环境 108 | try { 109 | this.audioContext = new (window.AudioContext || window.webkitAudioContext)() 110 | this.audioContext.resume() 111 | if (!this.audioContext) { 112 | alert('浏览器不支持webAudioApi相关接口') 113 | return 114 | } 115 | } catch (e) { 116 | if (!this.audioContext) { 117 | alert('浏览器不支持webAudioApi相关接口') 118 | return 119 | } 120 | } 121 | 122 | // 获取浏览器录音权限 123 | if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { 124 | navigator.mediaDevices 125 | .getUserMedia({ 126 | audio: true, 127 | video: false, 128 | }) 129 | .then(stream => { 130 | getMediaSuccess(stream) 131 | }) 132 | .catch(e => { 133 | getMediaFail(e) 134 | }) 135 | } else if (navigator.getUserMedia) { 136 | navigator.getUserMedia( 137 | { 138 | audio: true, 139 | video: false, 140 | }, 141 | stream => { 142 | getMediaSuccess(stream) 143 | }, 144 | function(e) { 145 | getMediaFail(e) 146 | } 147 | ) 148 | } else { 149 | if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) { 150 | alert('chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限') 151 | } else { 152 | alert('无法获取浏览器录音功能,请升级浏览器或使用chrome') 153 | } 154 | this.audioContext && this.audioContext.close() 155 | return 156 | } 157 | // 获取浏览器录音权限成功的回调 158 | let getMediaSuccess = stream => { 159 | // 创建一个用于通过JavaScript直接处理音频 160 | this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1) 161 | this.scriptProcessor.onaudioprocess = e => { 162 | // 去处理音频数据 163 | if (this.status === 'ing') { 164 | transWorker.postMessage(e.inputBuffer.getChannelData(0)) 165 | // this.audioData.push(e.inputBuffer.getChannelData(0)) 166 | } 167 | } 168 | // 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作 169 | this.mediaSource = this.audioContext.createMediaStreamSource(stream) 170 | // 连接 171 | this.mediaSource.connect(this.scriptProcessor) 172 | this.scriptProcessor.connect(this.audioContext.destination) 173 | this.connectWebSocket() 174 | } 175 | 176 | let getMediaFail = (e) => { 177 | this.audioContext && this.audioContext.close() 178 | this.audioContext = undefined 179 | // 关闭websocket 180 | if (this.webSocket && this.webSocket.readyState === 1) { 181 | this.webSocket.close() 182 | } 183 | } 184 | } 185 | recorderStart() { 186 | if (!this.audioContext) { 187 | this.recorderInit() 188 | } else { 189 | this.audioContext.resume() 190 | this.connectWebSocket() 191 | } 192 | } 193 | // 暂停录音 194 | recorderStop() { 195 | // safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend 196 | if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))){ 197 | this.audioContext && this.audioContext.suspend() 198 | } 199 | this.setStatus('end') 200 | } 201 | // 处理音频数据 202 | // transAudioData(audioData) { 203 | // audioData = transAudioData.transaction(audioData) 204 | // this.audioData.push(...audioData) 205 | // } 206 | // 对处理后的音频数据进行base64编码, 207 | toBase64(buffer) { 208 | var binary = '' 209 | var bytes = new Uint8Array(buffer) 210 | var len = bytes.byteLength 211 | for (var i = 0; i < len; i++) { 212 | binary += String.fromCharCode(bytes[i]) 213 | } 214 | return window.btoa(binary) 215 | } 216 | // 向webSocket发送数据 217 | webSocketSend() { 218 | if (this.webSocket.readyState !== 1) { 219 | return 220 | } 221 | let audioData = this.audioData.splice(0, 1280) 222 | var params = { 223 | common: { 224 | app_id: this.appId, 225 | }, 226 | business: { 227 | language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用 228 | domain: 'iat', 229 | accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用 230 | }, 231 | data: { 232 | status: 0, 233 | format: 'audio/L16;rate=16000', 234 | encoding: 'raw', 235 | audio: this.toBase64(audioData), 236 | }, 237 | } 238 | console.log("参数language:",this.language) 239 | console.log("参数accent:",this.accent) 240 | this.webSocket.send(JSON.stringify(params)) 241 | startTime = Date.parse(new Date()) 242 | this.handlerInterval = setInterval(() => { 243 | // websocket未连接 244 | if (this.webSocket.readyState !== 1) { 245 | console.log("websocket未连接") 246 | this.audioData = [] 247 | clearInterval(this.handlerInterval) 248 | return 249 | } 250 | if (this.audioData.length === 0) { 251 | console.log("自动关闭",this.status) 252 | if (this.status === 'end') { 253 | this.webSocket.send( 254 | JSON.stringify({ 255 | data: { 256 | status: 2, 257 | format: 'audio/L16;rate=16000', 258 | encoding: 'raw', 259 | audio: '', 260 | }, 261 | }) 262 | ) 263 | this.audioData = [] 264 | clearInterval(this.handlerInterval) 265 | } 266 | return false 267 | } 268 | audioData = this.audioData.splice(0, 1280) 269 | // 中间帧 270 | this.webSocket.send( 271 | JSON.stringify({ 272 | data: { 273 | status: 1, 274 | format: 'audio/L16;rate=16000', 275 | encoding: 'raw', 276 | audio: this.toBase64(audioData), 277 | }, 278 | }) 279 | ) 280 | }, 40) 281 | } 282 | result(resultData) { 283 | // 识别结束 284 | let jsonData = JSON.parse(resultData) 285 | if (jsonData.data && jsonData.data.result) { 286 | let data = jsonData.data.result 287 | let str = '' 288 | let resultStr = '' 289 | let ws = data.ws 290 | for (let i = 0; i < ws.length; i++) { 291 | str = str + ws[i].cw[0].w 292 | } 293 | console.log("识别的结果为:",str) 294 | // 开启wpgs会有此字段(前提:在控制台开通动态修正功能) 295 | // 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段 296 | if (data.pgs) { 297 | if (data.pgs === 'apd') { 298 | // 将resultTextTemp同步给resultText 299 | this.setResultText({ 300 | resultText: this.resultTextTemp, 301 | }) 302 | } 303 | // 将结果存储在resultTextTemp中 304 | this.setResultText({ 305 | resultTextTemp: this.resultText + str, 306 | }) 307 | } else { 308 | this.setResultText({ 309 | resultText: this.resultText + str, 310 | }) 311 | } 312 | } 313 | if (jsonData.code === 0 && jsonData.data.status === 2) { 314 | this.webSocket.close() 315 | } 316 | if (jsonData.code !== 0) { 317 | this.webSocket.close() 318 | console.log(`${jsonData.code}:${jsonData.message}`) 319 | } 320 | } 321 | start() { 322 | this.recorderStart() 323 | this.setResultText({ resultText: '', resultTextTemp: '' }) 324 | } 325 | stop() { 326 | this.recorderStop() 327 | } 328 | } 329 | 330 | export default IatRecorder 331 | --------------------------------------------------------------------------------