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,后半部分再讲原理和插件来源。
yarn add js-audio-recorder
如果需要转mp3加一句:
yarn add lamejs
由于代码是从网上上复制下来的,个人做了一些修改,所以会看到一些注释。
此代码上传按钮会直接上传文件到后端,请修改为自己的后端地址
<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,默认是16
sampleRate: 48000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000
numChannels: 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);
},
/**
* 下载录音文件
* */
//下载pcm
downPCM(){
//这里传参进去的时文件名
recorder.downloadPCM('新文件');
},
//下载wav
downWAV(){
//这里传参进去的时文件名
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>
这里使用的chrome浏览器测试的,所以我们在chrome浏览器当中输入:
chrome://flags/#unsafely-treat-insecure-origin-as-secure
输入你的本地网址,改为enabled,选择重启浏览器按钮【生产环境当中由于是使用域名进行访问,所以就不会报错。】
原因及原理:使用js-audio-recorder报浏览器不支持getUserMedia
原因:使用了localhost:8080
作为url
解决:使用ip地址或者域名访问
原因:只有谷歌内核的浏览器可以如此设置,想在手机上测试或使用请下载安卓版的谷歌浏览器或者使用域名访问。
插件官网(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