Vue H5 音频/语音录入
这里写自定义目录标题依赖安装封装组件依赖js-audio-plugin 在线文档js-audio-plugin Github地址安装npm i js-audio-recorder// 利用lamejs将pcm(wav)音频文件转化为mp3格式,详情见在线文档npm install lamejs封装组件提示:按需微调,组件代码如下:<template><div class='you
·
依赖
js-audio-plugin 在线文档
js-audio-plugin Github地址
安装
npm i js-audio-recorder
// 利用lamejs将pcm(wav)音频文件转化为mp3格式,详情见在线文档
npm install lamejs
封装组件
提示:按需微调,代码如下:
<template>
<div class='yourClass'>
<audio-enter :isShow='isShow' @closeAudio='closeAudio'></audio-enter>
</div>
</template>
<script>
import ......AudioEnter......
export default {
components: { AudioEnter },
data() {
return {
isShow: false
}
},
methods: {
closeAudio(data) {
this.isShow = false;
// 处理其他逻辑......
}
}
}
</script>
组件代码如下:
<template>
<div v-show="isShow" class="audio-enter">
<van-overlay :show="isShow" @click="close" class-name="custom-overlay">
<div class="audio-pie" @click.stop>
<!-- 录音时显示音波图和时长 -->
<div v-if="beginRecoding">
<div class="audio-pie_audio--times" v-if="nowDuration">{{nowDuration}}</div>
<div v-show="false" class="audio-pie_audio--osc"><canvas id="canvas"></canvas></div>
<div v-if="drawColuList.length > 0" class="audio-pie_audio--osc">
<div class="audio-pie_audio--osc_item" v-for="(item, idx) in drawColuList" :key="idx" :style="{height: item + 'px'}"></div>
</div>
</div>
<div class="audio-pie_btn" @touchstart.prevent="touchstart" @touchend.prevent="touchend" @touchmove.prevent="touchmove">
<div class="audio-pie_btn-icon">
<template v-if="beginRecoding">
<div class="audio-pie_btn-icon1"></div>
<div class="audio-pie_btn-icon2"></div>
</template>
<div v-else class="iconfont icon-Voice"></div>
</div>
<div class="audio-pie_txt">{{beginRecoding ? '松开发送,上滑取消' : '按住说话'}}</div>
</div>
</div>
</van-overlay>
</div>
</template>
<script>
import Recorder from 'js-audio-recorder'
const lamejs = require('lamejs')
/**
* @param isShow 是否显示音频录入组件
* @param closeAudio 关闭音频录入组件或录音完毕的事件
*/
export default {
props: {
isShow: {
type: Boolean,
default: false
}
},
data() {
return {
beginRecoding: false, // 开始录音
blackBoxSpeak: false, // 是否上滑取消
startY: '', // 手指滑动开始Y轴坐标
timeOutEvent: 0, // 定时器
waveCanvas: null,
ctx: null,
recorder: null, // 音频录入的实例
drawRecordId: null, // 音频绘画波动图时的音频id
nowDuration: null, // 当前时长
limitDuration: 60, // 限制时长,单位-秒
drawColuList: [] // 录音时的柱状波动图,数据集合
}
},
watch: {
'isShow': {
handler(nv, ov) {
if (nv) {
this.getPermission()
}
},
deep: true
}
},
mounted() {
// 创建录音实例
this.recorder = new Recorder({
sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
sampleRate: 48000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值
numChannels: 1, // 声道,支持 1 或 2, 默认是1
// compiling: false,(0.x版本中生效,1.x增加中) // 是否边录边转换,默认是false
})
// 监听录音变化
const vm = this;
this.recorder.onprogress = function(params) {
if (Math.floor(params.duration) === this.limitDuration) {
vm.touchend();
}
let d = Math.floor(params.duration);
d = Number(d) < 10 ? '0' + d : d;
d = '0:' + d;
vm.$set(vm, 'nowDuration', d)
// console.log('--------------START---------------')
// console.log('录音时长(秒)', params.duration);
// console.log('录音大小(字节)', params.fileSize);
// console.log('录音音量百分比(%)', params.vol);
// console.log('当前录音的总数据([DataView, DataView...])', params.data);
// console.log('--------------END---------------')
}
//this.startCanvas();
},
methods: {
// 获取麦克风权限
getPermission(){
Recorder.getPermission().then(() => {
console.log('录音给权限了');
}, (error) => {
console.log(`${error.name} : ${error.message}`);
});
},
// 波浪图canvas 配置
startCanvas(){
//录音波浪
this.waveCanvas = document.getElementById('canvas');
this.waveCanvas.width = 145;
this.waveCanvas.height = 44;
this.ctx = this.waveCanvas.getContext("2d");
},
// 手指开始触发
touchstart(e) {
this.blackBoxSpeak = true
this.startY = e.clientY
this.timeOutEvent = 0
// 长按1000毫秒后执行
this.timeOutEvent = setTimeout(()=>{
this.startRecorder();
this.beginRecoding = true
},500)
return false
},
// 手指离开屏幕触发
touchend() {
// 清空定时器
clearTimeout(this.timeOutEvent)
if (this.timeOutEvent !== 0) {
this.beginRecoding = false
this.stopRecorder() // 停止录音
// 长按结束调用保存录音或者返回录音数据
if (this.blackBoxSpeak) { // 未上滑取消
this.$emit('closeAudio', this.recorder.getWAVBlob())
}
//console.log('松开>>>', this.getRecorder())
this.nowDuration = null;
this.drawColuList = [];
}
},
// 滑动触发
touchmove(e) {
const endY = e.clientY
this.blackBoxSpeak = this.startY < endY
},
//长按超过x毫秒-- 开始录音
startRecorder () {
this.recorder.start().then(() => {
this.beginRecoding = true
//this.drawRecordWave();//开始绘制
this.drawRecordColu(); // 开始绘制
}, (error) => {
console.log(`${error.name} : ${error.message}`);
});
},
// 继续录音
resumeRecorder () {
this.recorder.resume()
},
// 暂停录音
pauseRecorder () {
this.recorder.pause();
this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
this.drawRecordId = null;
},
// 结束录音
stopRecorder () {
this.recorder.stop()
this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
this.drawRecordId = null;
},
// 录音播放
playRecorder () {
this.recorder.play();
//this.drawPlay();//绘制波浪图
},
// 暂停录音播放
pausePlayRecorder () {
this.recorder.pausePlay()
},
// 恢复录音播放
resumePlayRecorder () {
this.recorder.resumePlay();
//this.drawPlay();//绘制波浪图
},
// 停止录音播放
stopPlayRecorder () {
this.recorder.stopPlay();
},
// 销毁录音
destroyRecorder () {
const vm = this;
if (vm.recorder) {
vm.recorder.destroy().then(function() {
vm.recorder = null;
vm.drawRecordId && cancelAnimationFrame(vm.drawRecordId);
vm.drawRecordId = null;
});
}
},
// 获取录音文件
getRecorder(){
let map = new Map();
map.set('duration', this.recorder.duration);//录音总时长
map.set('fileSize', this.recorder.fileSize);//录音总大小
//录音结束,获取取录音数据
map.set('PCMBlob', this.recorder.getPCMBlob());//获取 PCM 数据
map.set('wavBlob', this.recorder.getWAVBlob());//获取 WAV 数据
map.set('mp3Blob', this.convertToMp3(this.recorder.getWAV()));
map.set('channel', this.recorder.getChannelData());//获取左声道和右声道音频数据
return map;
},
// 下载pcm格式
downPCM(){
this.recorder.downloadPCM('新文件');//这里传参进去的时文件名
},
// 下载wav格式
downWAV(){
this.recorder.downloadWAV('新文件');//这里传参进去的时文件名
},
// 文件格式转换 wav-map3
downMP3(){
const mp3Blob = this.convertToMp3(this.recorder.getWAV());
this.recorder.download(mp3Blob, 'recorder', 'mp3');
},
// wav转换成mp3格式
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 = this.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' });
},
// 录音绘制波浪图
drawRecordWave () {
// 用requestAnimationFrame稳定60fps绘制
this.drawRecordId = requestAnimationFrame(this.drawRecordWave);
// 实时获取音频大小数据
let dataArray = this.recorder.getRecordAnalyseData()
//console.log('>>>',dataArray)
let bufferLength = dataArray.length;
// 填充背景色
this.ctx.fillStyle = 'rgb(0,0,0)';
this.ctx.fillRect(0, 0, this.waveCanvas.width, this.waveCanvas.height);
// 设定波形绘制颜色
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = 'rgb(109, 212, 0)';
this.ctx.beginPath();
let sliceWidth = this.waveCanvas.width * 1.0 / bufferLength; // 一个点占多少位置,共有bufferLength个点要绘制
let x = 0; // 绘制点的x轴位置
for (var i = 0; i < bufferLength; i++) {
var v = dataArray[i] / 128.0;
var y = v * this.waveCanvas.height / 2;
if (i === 0) {
// 第一个点
this.ctx.moveTo(x, y);
} else {
// 剩余的点
this.ctx.lineTo(x, y);
}
// 依次平移,绘制所有点
x += sliceWidth;
}
this.ctx.lineTo(this.waveCanvas.width, this.waveCanvas.height / 2);
this.ctx.stroke();
},
// 录音绘制柱状图
drawRecordColu() {
// 用requestAnimationFrame稳定60fps绘制(官方写法,录音过程中,会一直自我调用,因此能得到持续的数据,然后根据数据绘制音波图)
this.drawRecordId = requestAnimationFrame(this.drawRecordColu);
// 实时获取音频大小数据
let dataArray = this.recorder.getRecordAnalyseData()
let transit = [];
this.splitArr([...dataArray], transit)
let rstArr = [];
for (let i = 0; i < transit.length; i++) {
rstArr.push(Math.max(...transit[i]))
}
this.drawColuList = [];
for (var i = 0; i < rstArr.length; i++) {
// var v = rstArr[i] / 128.0;
// var h = v * this.waveCanvas.height / 3;
// this.drawColuList.push(h)
// 根据数值大小,设置音波柱状的高度
let newDb = rstArr[i];
let waveH = 10;
if (newDb >= 128 && newDb <= 140) {
waveH = 15;
} else if (newDb >= 141 && newDb <= 160) {
waveH = 20
} else if (newDb >= 161 && newDb <= 180) {
waveH = 25;
} else if (newDb >= 181 && newDb <= 200) {
waveH = 30;
} else if (newDb > 200) {
waveH = 35;
}
this.drawColuList.push(waveH)
}
// console.log(this.drawColuList)
this.$forceUpdate();
},
// 拆分数组
splitArr(arr, rst, idx) {
if (!arr || arr.length === 0) {
return;
}
rst.push(arr.splice(0, idx ? idx : 32))
this.splitArr(arr, rst)
},
// 取数组平均数
arrAverageNum2(arr) {
let sum = eval(arr.join("+"));
return ~~(sum / arr.length * 100) / 100;
},
// 关闭组件
close() {
this.$emit('closeAudio')
},
// 销毁实例
beforeDestroy() {
this.destroyRecorder();
}
}
}
</script>
<style scoped lang="scss">
.audio-enter {
position: fixed;
left: 0;
top: 0;
height: 100%;
width: 100%;
z-index: 999999999;
background-color: #FFFFFF;
.custom-overlay {
// background-color: rgba(0, 0, 0, 0.8);
}
.audio-pie {
z-index: 999;
position: absolute;
left: 0;
right: 0;
bottom: 74px;
text-align: center;
.audio-pie_txt {
height: 20px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FFFFFF;
line-height: 20px;
margin-top: 20px;
}
.audio-pie_audio--times {
height: 20px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FFFFFF;
line-height: 20px;
}
.audio-pie_audio--osc {
width: 157px;
height: 44px;
// background: #6DD400;
margin: 24px auto 0;
display: flex;align-items: center;
.audio-pie_audio--osc_item {
width: 2px;
background-color: rgba(109, 212, 0, 1);
border-radius: 1000px;
&:not(:first-child) {
margin-left: 3px;
}
}
}
.audio-pie_btn {
margin-top: 60px;
.audio-pie_btn-icon {
position: relative;
width: 60px;
height: 60px;
background: #6DD400;
border-radius: 100%;
margin: 0 auto;
.audio-pie_btn-icon1 {
position: absolute;
top: 12px;
left: 0;
right: 0;
margin: 0 auto;
width: 36px;
height: 36px;
border: 4px solid #fff;
border-radius: 100%;
}
.audio-pie_btn-icon2 {
position: absolute;
top: 22px;
left: 0;
right: 0;
margin: 0 auto;
width: 16px;
height: 16px;
background-color: #fff;
border-radius: 100%;
}
.iconfont {
color: #FFFFFF;font-size: 35px;line-height: 60px
}
}
}
}
}
</style>
更多推荐
已为社区贡献6条内容
所有评论(0)