uniapp中上传音频只能在app或小程序当中实现,如何使用网页完成语音的录制和上传则成为了困扰前端童鞋的重点。
本文着重解决:
js-audio-recorder报 error:浏览器不支持getUserMedia ! 的问题。
js-audio-recorder报 NotFoundError : Requested device not found 的问题
js-audio-recorder的使用、demo以及文件上传的问题。
本篇文章主要讲如何使用uniapp(vue)在网页中(web模式下)录制声音并上传
js-audio-recorder插件支持在微信公众号(网页)、UC浏览器、Chrome及其内核的浏览器内运行。
文章前半部分先讲操作和demo,后半部分再讲原理和插件来源。
demo编写——前期准备:
- 安装插件
yarn add js-audio-recorder
如果需要转mp3加一句:
yarn add lamejs
- 建立vue文件
由于代码是从网上上复制下来的,个人做了一些修改,所以会看到一些注释。
此代码上传按钮会直接上传文件到后端,请修改为自己的后端地址
<template><div class="home" style="margin:1vw;"><Button type="success" @click="getPermission()" style="margin:1vw;">获取麦克风权限</Button><br/><Button type="info" @click="startRecorder()" style="margin:1vw;">开始录音</Button><Button type="info" @click="resumeRecorder()" style="margin:1vw;">继续录音</Button><Button type="info" @click="pauseRecorder()" style="margin:1vw;">暂停录音</Button><Button type="info" @click="stopRecorder()" style="margin:1vw;">结束录音</Button><br/><Button type="success" @click="playRecorder()" style="margin:1vw;">录音播放</Button><Button type="success" @click="pausePlayRecorder()" style="margin:1vw;">暂停录音播放</Button><Button type="success" @click="resumePlayRecorder()" style="margin:1vw;">恢复录音播放</Button><Button type="success" @click="stopPlayRecorder()" style="margin:1vw;">停止录音播放</Button><br/><Button type="info" @click="getRecorder()" style="margin:1vw;">获取录音信息</Button><Button type="info" @click="downPCM()" style="margin:1vw;">下载PCM</Button><Button type="info" @click="downWAV()" style="margin:1vw;">下载WAV</Button><Button type="info" @click="getMp3Data()" style="margin:1vw;">下载MP3</Button><br/><Button type="error" @click="destroyRecorder()" style="margin:1vw;">销毁录音</Button><br/><Button type="error" @click="uploadWav()" style="margin:1vw;">上传录音</Button><br/><div style="width:100%;height:200px;border:1px solid red;"><canvas id="canvas"></canvas><span style="padding: 0 10%;"></span><canvas id="playChart"></canvas></div></div>
</template><script>import Recorder from 'js-audio-recorder'const lamejs = require('lamejs')const recorder = new Recorder({sampleBits: 16, // 采样位数,支持 8 或 16,默认是16sampleRate: 48000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000numChannels: 1, // 声道,支持 1 或 2, 默认是1// compiling: false,(0.x版本中生效,1.x增加中) // 是否边录边转换,默认是false})// 绑定事件-打印的是当前录音数据recorder.onprogress = function(params) {// console.log('--------------START---------------')// console.log('录音时长(秒)', params.duration);// console.log('录音大小(字节)', params.fileSize);// console.log('录音音量百分比(%)', params.vol);// console.log('当前录音的总数据([DataView, DataView...])', params.data);// console.log('--------------END---------------')}export default {name: 'home',data () {return {//波浪图-录音drawRecordId:null,oCanvas : null,ctx : null,//波浪图-播放drawPlayId:null,pCanvas : null,pCtx : null,}},mounted(){this.startCanvas();},methods: {/*** 波浪图配置* */startCanvas(){//录音波浪// this.oCanvas = document.getElementById('canvas');// this.ctx = this.oCanvas.getContext("2d");// //播放波浪// this.pCanvas = document.getElementById('playChart');// this.pCtx = this.pCanvas.getContext("2d");},/*** 录音的具体操作功能* */// 开始录音startRecorder () {recorder.start().then(() => {// this.drawRecord();//开始绘制图片uni.showToast({title: '开始录音',})}, (error) => {// 出错了uni.showToast({title: `${error.name} : ${error.message}`,})console.log(`${error.name} : ${error.message}`);});},// 继续录音resumeRecorder () {recorder.resume()},// 暂停录音pauseRecorder () {recorder.pause();this.drawRecordId && cancelAnimationFrame(this.drawRecordId);this.drawRecordId = null;},// 结束录音stopRecorder () {recorder.stop()uni.showToast({title: '结束录音',})this.drawRecordId && cancelAnimationFrame(this.drawRecordId);this.drawRecordId = null;},// 录音播放playRecorder () {recorder.play();uni.showToast({title: '录音播放',})// this.drawPlay();//绘制波浪图},// 暂停录音播放pausePlayRecorder () {recorder.pausePlay()},// 恢复录音播放resumePlayRecorder () {recorder.resumePlay();this.drawPlay();//绘制波浪图},// 停止录音播放stopPlayRecorder () {recorder.stopPlay();},// 销毁录音destroyRecorder () {recorder.destroy().then(function() {recorder = null;this.drawRecordId && cancelAnimationFrame(this.drawRecordId);this.drawRecordId = null;});},/*** 获取录音文件* */getRecorder(){let toltime = recorder.duration;//录音总时长let fileSize = recorder.fileSize;//录音总大小//录音结束,获取取录音数据let PCMBlob = recorder.getPCMBlob();//获取 PCM 数据let wav = recorder.getWAVBlob();//获取 WAV 数据let channel = recorder.getChannelData();//获取左声道和右声道音频数据console.log(toltime);console.log(fileSize);// console.log(PCMBlob);console.log(wav);console.log(channel);console.log(recorder);},/*** 下载录音文件* *///下载pcmdownPCM(){//这里传参进去的时文件名recorder.downloadPCM('新文件');},//下载wavdownWAV(){//这里传参进去的时文件名recorder.downloadWAV('新文件');},/*** 获取麦克风权限* */getPermission(){Recorder.getPermission().then(() => {this.$Message.success('获取权限成功')uni.showToast({title: '没有找到您要查询的内容!'})}, (error) => {uni.showToast({title: `${error.name} : ${error.message}`,})console.log(`${error.name} : ${error.message}`);});},/*** 文件格式转换 wav-map3* */getMp3Data(){const mp3Blob = this.convertToMp3(recorder.getWAV());recorder.download(mp3Blob, 'recorder', 'mp3');},blobToFile(theBlob, fileName) { //将blob转换为file let file = new File([theBlob], fileName, {type: theBlob.type.split('/')[1], lastModified: Date.now()}); return file; },convertToMp3(wavDataView) {// 获取wav头信息const wav = lamejs.WavHeader.readHeader(wavDataView); // 此处其实可以不用去读wav头信息,毕竟有对应的config配置const { channels, sampleRate } = wav;const mp3enc = new lamejs.Mp3Encoder(channels, sampleRate, 128);// 获取左右通道数据const result = recorder.getChannelData()const buffer = [];const leftData = result.left && new Int16Array(result.left.buffer, 0, result.left.byteLength / 2);const rightData = result.right && new Int16Array(result.right.buffer, 0, result.right.byteLength / 2);const remaining = leftData.length + (rightData ? rightData.length : 0);const maxSamples = 1152;for (let i = 0; i < remaining; i += maxSamples) {const left = leftData.subarray(i, i + maxSamples);let right = null;let mp3buf = null;if (channels === 2) {right = rightData.subarray(i, i + maxSamples);mp3buf = mp3enc.encodeBuffer(left, right);} else {mp3buf = mp3enc.encodeBuffer(left);}if (mp3buf.length > 0) {buffer.push(mp3buf);}}const enc = mp3enc.flush();if (enc.length > 0) {buffer.push(enc);}return new Blob(buffer, { type: 'audio/mp3' });},/*** 绘制波浪图-录音* */drawRecord () {// // 用requestAnimationFrame稳定60fps绘制// this.drawRecordId = requestAnimationFrame(this.drawRecord);// // 实时获取音频大小数据// let dataArray = recorder.getRecordAnalyseData(),// bufferLength = dataArray.length;// // 填充背景色// this.ctx.fillStyle = 'rgb(200, 200, 200)';// this.ctx.fillRect(0, 0, this.oCanvas.width, this.oCanvas.height);// // 设定波形绘制颜色// this.ctx.lineWidth = 2;// this.ctx.strokeStyle = 'rgb(0, 0, 0)';// this.ctx.beginPath();// var sliceWidth = this.oCanvas.width * 1.0 / bufferLength, // 一个点占多少位置,共有bufferLength个点要绘制// x = 0; // 绘制点的x轴位置// for (var i = 0; i < bufferLength; i++) {// var v = dataArray[i] / 128.0;// var y = v * this.oCanvas.height / 2;// if (i === 0) {// // 第一个点// this.ctx.moveTo(x, y);// } else {// // 剩余的点// this.ctx.lineTo(x, y);// }// // 依次平移,绘制所有点// x += sliceWidth;// }// this.ctx.lineTo(this.oCanvas.width, this.oCanvas.height / 2);// this.ctx.stroke();},/*** 绘制波浪图-播放* */drawPlay () {// // 用requestAnimationFrame稳定60fps绘制// this.drawPlayId = requestAnimationFrame(this.drawPlay);// // 实时获取音频大小数据// let dataArray = recorder.getPlayAnalyseData(),// bufferLength = dataArray.length;// // 填充背景色// this.pCtx.fillStyle = 'rgb(200, 200, 200)';// this.pCtx.fillRect(0, 0, this.pCanvas.width, this.pCanvas.height);// // 设定波形绘制颜色// this.pCtx.lineWidth = 2;// this.pCtx.strokeStyle = 'rgb(0, 0, 0)';// this.pCtx.beginPath();// var sliceWidth = this.pCanvas.width * 1.0 / bufferLength, // 一个点占多少位置,共有bufferLength个点要绘制// x = 0; // 绘制点的x轴位置// for (var i = 0; i < bufferLength; i++) {// var v = dataArray[i] / 128.0;// var y = v * this.pCanvas.height / 2;// if (i === 0) {// // 第一个点// this.pCtx.moveTo(x, y);// } else {// // 剩余的点// this.pCtx.lineTo(x, y);// }// // 依次平移,绘制所有点// x += sliceWidth;// }// this.pCtx.lineTo(this.pCanvas.width, this.pCanvas.height / 2);// this.pCtx.stroke();},uploadWav(){let file = this.blobToFile(recorder.getWAVBlob(),"1.wav")console.log("开始上传");console.log(file);uni.uploadFile({header: {},url: uni.getStorageSync('BaseUrl') +'/file/upload',// filePath: file,file:file,name: 'file',formData: {'user': 'test'},success: (uploadFileRes) => {console.log('/sys/common/static/scott/pic/' +JSON.parse(uploadFileRes.data).result);},complete: () => {},fail: (res) => {console.log(res)}})}},}
</script><style lang='less' scoped></style>
-
运行
-
注意,调试环境这里会报错,所以开始解决报错问题:
报错:error:浏览器不支持getUserMedia !
这里使用的chrome浏览器测试的,所以我们在chrome浏览器当中输入:
chrome://flags/#unsafely-treat-insecure-origin-as-secure
输入你的本地网址,改为enabled,选择重启浏览器按钮【生产环境当中由于是使用域名进行访问,所以就不会报错。】
原因及原理:使用js-audio-recorder报浏览器不支持getUserMedia
- 调试正常:(大前提你的电脑要求插入耳机/喇叭和麦克风【耳麦一体的需要转换为双插头】否则会报找不到设备)
错误指引:
1. NotFoundError : Requested device not found
原因:没插麦克风
解决:插入麦克风并调整为音频主输入
2. 手机扫码显示不出来
原因:使用了localhost:8080
作为url
解决:使用ip地址或者域名访问
3. 手机上访问不到或者无法测试(微信扫码也不行)
原因:只有谷歌内核的浏览器可以如此设置,想在手机上测试或使用请下载安卓版的谷歌浏览器或者使用域名访问。
插件和原理解说
插件官网(git)
https://github.com/2fps/recorder
文档
http://recorder.api.zhuyuntao.cn/Recorder/start.html
demo
https://recorder.zhuyuntao.cn/
uniapp文件上传函数解说
https://uniapp.dcloud.net.cn/api/request/network-file.html