1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Vue js前端实现语音实时转换文字 前端实现浏览器语音实时转换为文字 vue阿里云语音转文字

Vue js前端实现语音实时转换文字 前端实现浏览器语音实时转换为文字 vue阿里云语音转文字

时间:2021-04-06 04:54:16

相关推荐

Vue js前端实现语音实时转换文字 前端实现浏览器语音实时转换为文字 vue阿里云语音转文字

Vue,js前端实现浏览器语音实时转换文字功能详解

1.首先总结一下,前端使用实时语音需要使用到HZRecorder.js这个JS文件来实现获取浏览器麦克风话筒权限

大注意:HZRecorder.js获取话筒权限如果你在本地运行会发现正常可以使用,在发布线上不能用,那是因为线上地址不是https域名,不安全,所以浏览器默认不会给话筒权限

2.HZRecorder.js文件

;(function (window) {//兼容window.URL = window.URL || window.webkitURLnavigator.getUserMedia =navigator.getUserMedia ||navigator.webkitGetUserMedia ||navigator.mozGetUserMedia ||navigator.msGetUserMediavar HZRecorder = function (stream, config) {config = config || {}config.sampleBits = 16 //采样数位 8, 16config.sampleRate = 16000 //采样率(1/6 44100)var context = new (window.webkitAudioContext || window.AudioContext)()var audioInput = context.createMediaStreamSource(stream)var createScript =context.createScriptProcessor || context.createJavaScriptNodevar recorder = createScript.apply(context, [4096, 1, 1])var audioData = {size: 0, //录音文件长度buffer: [], //录音缓存inputSampleRate: context.sampleRate, //输入采样率inputSampleBits: 16, //输入采样数位 8, 16outputSampleRate: config.sampleRate, //输出采样率oututSampleBits: config.sampleBits, //输出采样数位 8, 16input: function (data) {this.buffer.push(new Float32Array(data))this.size += data.length},compress: function () {//合并压缩//合并var data = new Float32Array(this.size)var offset = 0for (var i = 0; i < this.buffer.length; i++) {data.set(this.buffer[i], offset)offset += this.buffer[i].length}//压缩var compression = parseInt(this.inputSampleRate / this.outputSampleRate)var length = data.length / compressionvar result = new Float32Array(length)var index = 0,j = 0while (index < length) {result[index] = data[j]j += compressionindex++}return result},encodeWAV: function () {var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate)var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits)var bytes = press()var dataLength = bytes.length * (sampleBits / 8)var buffer = new ArrayBuffer(44 + dataLength)var data = new DataView(buffer)var channelCount = 1 //单声道var offset = 0var writeString = function (str) {for (var i = 0; i < str.length; i++) {data.setUint8(offset + i, str.charCodeAt(i))}}// 资源交换文件标识符writeString('RIFF')offset += 4// 下个地址开始到文件尾总字节数,即文件大小-8data.setUint32(offset, 36 + dataLength, true)offset += 4// WAV文件标志writeString('WAVE')offset += 4// 波形格式标志writeString('fmt ')offset += 4// 过滤字节,一般为 0x10 = 16data.setUint32(offset, 16, true)offset += 4// 格式类别 (PCM形式采样数据)data.setUint16(offset, 1, true)offset += 2// 通道数data.setUint16(offset, channelCount, true)offset += 2// 采样率,每秒样本数,表示每个通道的播放速度data.setUint32(offset, sampleRate, true)offset += 4// 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8data.setUint32(offset,channelCount * sampleRate * (sampleBits / 8),true)offset += 4// 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8data.setUint16(offset, channelCount * (sampleBits / 8), true)offset += 2// 每样本数据位数data.setUint16(offset, sampleBits, true)offset += 2// 数据标识符writeString('data')offset += 4// 采样数据总数,即数据总大小-44data.setUint32(offset, dataLength, true)offset += 4// 写入采样数据if (sampleBits === 8) {for (var i = 0; i < bytes.length; i++, offset++) {var s = Math.max(-1, Math.min(1, bytes[i]))var val = s < 0 ? s * 0x8000 : s * 0x7fffval = parseInt(255 / (65535 / (val + 32768)))data.setInt8(offset, val, true)}} else {for (var i = 0; i < bytes.length; i++, offset += 2) {var s = Math.max(-1, Math.min(1, bytes[i]))data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)}}return new Blob([data], { type: 'audio/wav' })},}//开始录音this.start = function () {audioInput.connect(recorder)recorder.connect(context.destination)}//停止this.stop = function () {recorder.disconnect()}//获取音频文件this.getBlob = function () {this.stop()return audioData.encodeWAV()}//回放this.play = function (audio) {audio.src = window.URL.createObjectURL(this.getBlob())}//上传this.upload = function (url, callback) {var fd = new FormData()fd.append('audioData', this.getBlob())var xhr = new XMLHttpRequest()if (callback) {xhr.upload.addEventListener('progress',function (e) {callback('uploading', e)},false)xhr.addEventListener('load',function (e) {callback('ok', e)},false)xhr.addEventListener('error',function (e) {callback('error', e)},false)xhr.addEventListener('abort',function (e) {callback('cancel', e)},false)}xhr.open('POST', url)xhr.send(fd)}//音频采集recorder.onaudioprocess = function (e) {audioData.input(e.inputBuffer.getChannelData(0))//record(e.inputBuffer.getChannelData(0));}}//抛出异常HZRecorder.throwError = function (message) {alert(message)throw new (function () {this.toString = function () {return message}})()}//是否支持录音HZRecorder.canRecording = navigator.getUserMedia != null//获取录音机HZRecorder.get = function (callback, config) {console.log('callback')console.log('callback', callback)if (callback) {console.log('callback', callback)if (navigator.getUserMedia) {console.log('navigator.getUserMedia', navigator.getUserMedia)navigator.getUserMedia({ audio: true }, //只启用音频function (stream) {var rec = new HZRecorder(stream, config)console.log('rec', rec)callback(rec)},function (error) {switch (error.code || error.name) {case 'PERMISSION_DENIED':case 'PermissionDeniedError':HZRecorder.throwError('用户拒绝提供信息。')breakcase 'NOT_SUPPORTED_ERROR':case 'NotSupportedError':HZRecorder.throwError('浏览器不支持硬件设备。')breakcase 'MANDATORY_UNSATISFIED_ERROR':case 'MandatoryUnsatisfiedError':HZRecorder.throwError('无法发现指定的硬件设备。')breakdefault:HZRecorder.throwError('无法打开麦克风。异常信息:' + (error.code || error.name))break}})} else {HZRecorder.throwErr('当前浏览器不支持录音功能。')console.log('当前浏览器不支持录音功能。')return}}}window.HZRecorder = HZRecorder})(window)

前端使用websocket来实时和后端接口交互,将语音文件实时传给后端,后端使用阿里云API解析,然后返回汉字即可,英语也可以识别!**

代码:

<template><div><div class="section"><div class="fromLeft"><div class="task"><span class="titItems">内容:</span><div class="taskbox">{{ taskContent }}</div></div><div class="mainBox"><div class="doTask"><span class="titItems">上传:</span><div class="resourcesBox"><!-- {{ productionRepresentation }} --><el-inputv-model="productionRepresentation"type="textarea"placeholder="请输入内容":rows="20"show-word-limitstyle="width: 100%; height: 100px; border: none"></el-input></div><div class="buttons"><div class="buttonLeft"><el-buttontype="primary"@mousedown.native="startRecording"@mouseup.native="stopRecording"><vab-icon v-if="viceBtn" :icon="['fas', 'microphone']" /><vab-icon v-else :icon="['fas', 'blog']"></vab-icon><span v-if="viceBtn">开始识别</span><span v-else>识别中...</span></el-button><!-- <el-button type="primary"><vab-icon :icon="['fas', 'blog']"></vab-icon>识别中...</el-button><el-button type="primary" @click="stopRecording"><vab-icon :icon="['fas', 'microphone-alt-slash']" />停止识别</el-button> --></div><div class="buttonRight"><el-button type="primary" @click="updateProduction"><vab-icon :icon="['fas', 'arrow-up']"></vab-icon>上传</el-button></div></div></div></div></div><div class="fromRight"><div class="fromRightbox01"><div id="messageList"><divv-for="(item, index) in listerDatas":key="index"class="lister"><div class="listerLeft"><div class="header"></div><div class="userName">{{ item.userName }}</div></div><div class="listerRight"><div class="messageDiv">{{ item.content }}</div><div class="messageTime">{{ item.createTime | formatDate }}</div></div></div></div><div id="messageBox"><el-inputid="input1"v-model="content"type="textarea"placeholder="请输入内容":rows="5"maxlength="300"show-word-limitstyle="width: 100%; height: 100px; border: none"></el-input></div><div style="margin-top: 10px"><el-button size="mini" type="primary" round @click="sendMessage()">发 送</el-button></div></div></div></div><!-- 弹框--></div></template><script>import './js/HZRecorder'//引入JS文件,用于获取麦开风话筒权限import { baseURL } from '../../../config'var OSS = require('ali-oss') //请求阿里云对象var recorderimport {savaChatContent,getChatContentByProjectId,updateProduction,} from '@/api/manage' //封装的接口文件export default {name: 'ResDetails',filters: {//处理日期formatDate: function (value) {let date = new Date(parseInt(value))//let date = new Date(value / 1000000);//这个是纳秒的,想要毫秒的可以不用除以1000000let y = date.getFullYear()let MM = date.getMonth() + 1MM = MM < 10 ? '0' + MM : MMlet d = date.getDate()d = d < 10 ? '0' + d : dlet h = date.getHours()h = h < 10 ? '0' + h : hlet m = date.getMinutes()m = m < 10 ? '0' + m : mlet s = date.getSeconds()s = s < 10 ? '0' + s : s//return y + '-' + MM + '-' + d + ' ' + h + ':' + m + ':' + s;return MM + '-' + d + ' ' + h + ':' + m + ':' + s},},data() {return {taskContent: '',content: '',listerDatas: [],productionRepresentation: '', //作品展示描述region: 'oss-cn-beijing',bucket: 'book-2d-code',accessKeyId: '',accessKeySecret: '',securityToken: '',percentage: 0,leadHtml: [],id: '',viceBtn: true,}},created() {this.initWebSocket() //初始化WebSocket,页面创建建立this.taskContent = this.$route.query.objdata.descriptionthis.taskGroupsData()},mounted() {},methods: {//首次加载taskGroupsData() {let params = {projectId: this.$route.query.objdata.id,content: this.content,}this.getChatContentByProjectId(params)},//右侧留言板sendMessage() {if (this.content == '') {this.$message({type: 'warning',message: '评价内容不能为空哦',})} else {let params = {projectId: this.$route.query.objdata.id,content: this.content,}savaChatContent(params).then((val) => {if (val.code == 200) {this.$message({type: 'success',message: '评价成功',})this.content = ''this.getChatContentByProjectId(params)}})}},//右侧留言板查询sgetChatContentByProjectId(obj) {getChatContentByProjectId(obj).then((res) => {this.listerDatas = res.data})},//语音录入startRecording() {console.log('录音前')this.viceBtn = falseHZRecorder.get(function (rec) {console.log('录音中.........', rec)recorder = recrecorder.start()})console.log('录音后')},// 初始化websocketinitWebSocket() {const wsuri = baseURL.replace('http', 'ws') + '/audioWebsocket' // ws地址this.websock = new WebSocket(wsuri)this.websock.onopen = this.websocketopenthis.websock.onerror = this.websocketerrorthis.websock.onmessage = this.websocketonmessagethis.websock.onclose = this.websocketclose},//停止录音stopRecording(index, type) {// console.log('stop', recorder.getBlob())// recorder.stop()this.viceBtn = truethis.uploadAudio()},//上传录音uploadAudio(tid, sid, qid, index, type) {const _this = thisconst client = new OSS({secure: true,region: 'oss-cn-hangzhou',accessKeyId: 'LTAIs84XXBXiaCXN',accessKeySecret: 'btOBcvQZOBpsjxshHA4l5QSKkCeUyh',bucket: 'oraltest',timeout: '10000000',})console.log(client, 9999999999)_this.percentage = 0// 随机命名let random_name = 'video/' + new Date().getTime() + '.wav'// 上传var reader = new FileReader()console.log('reader', reader)let blob = recorder.getBlob()console.log('blob', blob)reader.readAsArrayBuffer(blob)reader.onload = function (event) {var buffer = new OSS.Buffer(event.target.result)client.put(random_name, buffer, {progress: function* (percentage, cpt) {// 上传进度_this.percentage = percentage},}).then((results) => {// 上传完成_this.$message.success('上传成功')const url ='https://oraltest.oss-cn-/' +results.name_this.url = urlconsole.log(url)_this.websock.send(url)}).catch((err) => {console.log(err)})}},websocketopen() {this.leadHtml = []},websocketerror(e) {console.log('WebSocket连接发生错误')},websocketclose(e) {console.log('WebSocket连接关闭')},websocketonmessage(e) {console.log('websocket receive data', e)this.productionRepresentation += e.data},websocketsend(agentData) {this.websock.send(agentData)},//上传语音updateProduction() {let obj = {id: this.id,productionRepresentation: this.productionRepresentation,}updateProduction(obj).then((res) => {if (res.code == 200) {this.$message({type: 'success',message: '上传成功!',})}})},},}</script><style lang="scss" scoped>.section {width: 100%;display: flex;flex-direction: row;box-sizing: border-box;padding: 20px;background-color: #ffffff;height: 800px;}.fromLeft {flex: 1;height: 760px;}.fromRight {margin-left: 20px;min-width: 300px;height: 760px;}.rightHead {display: flex;flex-direction: row;height: 50px;line-height: 50px;width: 100%;margin-bottom: 10px;}.icons01 {flex: 1;}.icons01 {width: 80px;}.fromRightbox01 {padding: 10px;border: 1px solid #41b584;height: 755px;width: 100%;box-sizing: border-box;border-radius: 10px;}#messageList {width: 100%;height: 560px;overflow: hidden;overflow-y: auto;box-sizing: border-box;}#messageBox {width: 100%;height: 110px;margin-top: 10px;border: 1px solid #41b584;}.task {width: 100%;overflow: hidden;overflow-y: auto;padding: 20px;box-sizing: border-box;height: 130px;border: 1px solid #41b584;border-radius: 10px;}.mainBox {width: 100%;height: 605px;padding: 20px;box-sizing: border-box;margin-top: 20px;border: 1px solid #41b584;border-radius: 10px;overflow: hidden;overflow-y: auto;}.titItems {color: #41b584;font-size: 18px;font-weight: 550;}.tabsdiv {width: 100%;height: 150px;margin-top: 15px;}.doTask {padding-bottom: 20px;height: auto;}.taskEval {width: 100%;height: 150px;overflow: hidden;overflow-y: auto;padding: 20px;margin-top: 10px;box-sizing: border-box;border: 1px solid #41b584;border-radius: 10px;}.taskEval >>> .el-textarea__inner {border: 0;resize: none; /* 这个是去掉 textarea 下面拉伸的那个标志,如下图 */}#messageBox >>> .el-textarea__inner {border: 0;resize: none; /* 这个是去掉 textarea 下面拉伸的那个标志,如下图 */}.tasktit {margin-top: 15px;}.radar {margin-top: 20px;}.rardrCharts {width: 100%;height: 100%;margin: 0 auto;}.taskbox {width: 95%;line-height: 25px;height: auto;text-indent: 2em;font-size: 17px;color: #000000;padding: 10px;margin: 0 auto;}.evaluatehead {height: 40px;}.head {height: 35px;margin-bottom: 10px;color: #000000;background: #41b584;border-radius: 10px 10px 0px 0px;}#mytable {border-collapse: collapse;}/* .tableStyle tr:nth-child(even) {background: #006AFF;} */.actives {background-color: #41b58417;}.contrs {border-bottom: 1px solid #41b58417;border-left: 1px solid #41b58417;border-right: 1px solid #41b58417;}td {height: 40px;padding: 1px 0;text-align: center;border: none;}.groupsBox {display: flex;flex-direction: row;}.active {color: #41b584;}.active2 {color: #41b584;}.groupList {width: 100px;height: 35px;cursor: pointer;font-size: 15px;text-align: center;line-height: 35px;margin-left: 15px;border-radius: 10px;border: 1px solid #41b584;}.groupStuds {margin-top: 20px;/* display: flex;flex-direction: row;justify-content:space-between; */}.stuNames2 {cursor: pointer;width: 80px;float: left;height: 35px;line-height: 35px;font-weight: 550;margin-left: 15px;}.stuNames {cursor: pointer;width: 80px;float: left;height: 35px;color: #000000;line-height: 35px;font-weight: 550;margin-left: 15px;}.echarts1 {margin: 0 auto;width: 900px;height: 800px;}.commentBox {width: 100%;height: 100px;}.lister {width: 100%;height: auto;box-sizing: border-box;border: 1px dashed #00bfff;margin-bottom: 10px;display: flex;}.listerLeft {width: 80px;height: 100px;}.listerRight {width: 200px;height: auto;padding: 10px;}.header {width: 40px;margin: 5px auto;height: 40px;cursor: pointer;border-radius: 50%;background: url(../../../assets/image/details4.png);background-size: 100% 100%;}.userName {margin-top: 20px;color: #000000;font-size: 12px;font-weight: 600;text-align: center;}.messageDiv {width: 100%;height: auto;box-sizing: border-box;white-space: normal;word-break: break-all;}.messageTime {width: 100%;height: 20px;margin-top: 20px;color: #000000;font-size: 12px;font-weight: 550;}.resourcesBox {width: 100%;height: 430px;margin-bottom: 25px;margin-top: 10px;box-sizing: border-box;font-size: 16px;}.reslister {width: 550px;margin-top: 20px;height: 80px;padding: 15px;box-sizing: border-box;border: 1px solid #ead2d0;box-shadow: -1px 2px 10px #63d6e4;border-radius: 10px;display: flex;background-color: rgba(182, 211, 245, 0.3);}.resourse1 {width: 50px;height: 50px;background: url(../../../assets/image/taskfiles.jpeg);background-size: 100% 100%;border-radius: 25px;}.resourse2 {width: 330px;box-sizing: border-box;height: 50px;color: #000000;font-weight: 550;font-size: 15px;line-height: 50px;margin-left: 10px;text-overflow: ellipsis; /* ellipsis:显示省略符号来代表被修剪的文本 string:使用给定的字符串来代表被修剪的文本*/white-space: nowrap; /* nowrap:规定段落中的文本不进行换行 */overflow: hidden;}.resourse3 {width: 100px;margin-left: 10px;padding-top: 10px;padding-left: 10px;}.secondHead {border-left: 1px solid #41b58417;border-right: 1px solid #41b58417;}.secondHead p {/* width: 20px; */padding-top: 5px;padding-bottom: 5px;display: inline-block;writing-mode: vertical-rl;}.buttons {display: flex;}.buttonLeft {flex: 1;}.buttonLeft ::v-deep .el-button--small {font-size: 20px;}.buttonRight ::v-deep .el-button--small {font-size: 20px;}.buttonRight {width: 200px;text-align: right;}</style>

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。