一、安装依赖
npm i js-audio-recorder
具体类似效果可以借鉴 https://recorder.zhuyuntao.cn/
二、直接上代码
<template>
<div class="audioBox">
<div
class="boxItem"
:class="
recordStatus == 'begin' || recordStatus == 'resume' ? 'active' : ''
"
@click="startRecorder('begin')"
>
<div class="item_icon iconfonts iconrecord-circle-line"></div>
<div class="item_desc">开始录制</div>
</div>
<div
class="boxItem"
v-show="recordStatus !== 'pause'"
@click="pauseRecorder('pause')"
>
<div class="item_icon iconfonts iconzanting1"></div>
<div class="item_desc">暂停录制</div>
</div>
<div
class="boxItem"
v-show="recordStatus == 'pause'"
@click="resumeRecorder('resume')"
>
<div class="item_icon iconfonts iconicon_play"></div>
<div class="item_desc">继续录制</div>
</div>
<div class="boxItem" @click="stopRecorder('stop')">
<div class="item_icon iconfonts iconstop-full"></div>
<div class="item_desc">停止录制</div>
</div>
<div class="boxItem">
<div class="item_dura">{{ formatNum(rm) }}:{{ formatNum(rs) }}</div>
<div class="item_desc">录音时长</div>
</div>
</div>
<div class="audioBox playBox">
<div class="boxItem" @click="playRecorder('play')">
<div class="item_icon iconfonts iconicon_play"></div>
<div class="item_desc">录音播放</div>
</div>
<div class="boxItem" @click="resumePlayRecorder('resume_play')">
<div class="item_icon iconfonts iconicon_play"></div>
<div class="item_desc">恢复播放</div>
</div>
<div class="boxItem" @click="pausePlayRecorder('pause_play')">
<div class="item_icon iconfonts iconzanting1"></div>
<div class="item_desc">暂停播放</div>
</div>
<div class="boxItem">
<div class="item_dura">
{{ formatNum(ym) }}:{{ formatNum(ys) }}/{{ formatNum(rm) }}:{{
formatNum(rs)
}}
</div>
<div class="item_desc">播放时长/录音时长</div>
</div>
<div class="boxItem" @click="resetRecorder('reset')">
<div class="item_icon iconfonts iconzhongzhi"></div>
<div class="item_desc">重新录制</div>
</div>
<div class="boxItem" @click="getRecorder">
<div class="item_icon iconfonts iconbaocun01"></div>
<div class="item_desc">保存</div>
</div>
</div>
</div>
</template>
<script>
// lamejs 版本号为 1.2.0 最新版本会报错 属性找不到
import Recorder from "js-audio-recorder";
const lamejs = require("lamejs");
var 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
});
export default {
name: "audioRecord",
data() {
return {
//波浪图-录音
drawRecordId: null,
oCanvas: null,
ctx: null,
//波浪图-播放
drawPlayId: null,
pCanvas: null,
pCtx: null,
recordStatus: null,
rm: 0, // 录音时长 分
rs: 0, // 录音时长 秒
ym: 0, // 播放时长 分
ys: 0 // 播放时长 秒
};
},
created() {
// 绑定事件-打印的是当前录音数据
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---------------')
};
},
mounted() {
// this.startCanvas();
},
methods: {
formatNum(val) {
return val < 10 ? "0" + val : val;
},
// 将秒数 转为 时间
secondsTotime(seconds) {
var hours = Math.floor(seconds / 3600);
var minutes = Math.floor((seconds - hours * 3600) / 60);
var second = seconds - hours * 3600 - minutes * 60;
second = Math.round(seconds * 100) / 100;
var result =
(minutes < 10 ? "0" + minutes : minutes) +
":" +
(second < 10 ? "0" + second : second);
return result;
},
/**
* 波浪图配置
* */
startCanvas() {
//录音波浪
this.oCanvas = document.getElementById("canvas");
this.ctx = this.oCanvas.getContext("2d");
//播放波浪
this.pCanvas = document.getElementById("playChart");
this.pCtx = this.pCanvas.getContext("2d");
},
/**
* 录音的具体操作功能
* */
// 开始录音
startRecorder(val) {
this.recordStatus = val;
let that = this;
// 获取麦克风权限
Recorder.getPermission().then(
() => {
that.$message.success("获取权限成功,开始录音");
recorder.start();
},
error => {
that.$message.error("请先允许该网页使用麦克风");
// console.log(`${error.name} : ${error.message}`);
}
);
// recorder.start().then(() => {
// this.drawRecord();//开始绘制图片
// }, (error) => {
// // 出错了
// console.log(`${error.name} : ${error.message}`);
// });
},
// 继续录音
resumeRecorder(val) {
this.recordStatus = val;
recorder.resume();
},
// 暂停录音
pauseRecorder(val) {
this.recordStatus = val;
recorder.pause();
// this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
// this.drawRecordId = null;
},
// 结束录音
stopRecorder(val) {
this.recordStatus = val;
recorder.stop();
// this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
// this.drawRecordId = null;
},
// 录音播放
playRecorder(val) {
this.recordStatus = val;
recorder.play();
// this.drawPlay();//绘制波浪图
},
// 暂停录音播放
pausePlayRecorder(val) {
this.recordStatus = val;
recorder.pausePlay();
},
// 恢复录音播放
resumePlayRecorder(val) {
this.recordStatus = val;
recorder.resumePlay();
// this.drawPlay();//绘制波浪图
},
// 停止录音播放
stopPlayRecorder(val) {
this.recordStatus = val;
recorder.stopPlay();
},
// 重新录制
resetRecorder(val) {
this.recordStatus = val;
},
// 销毁录音
destroyRecorder(val) {
this.recordStatus = val;
let that = this;
recorder.destroy().then(function() {
// recorder = null;
that.drawRecordId && cancelAnimationFrame(that.drawRecordId);
that.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(); //获取左声道和右声道音频数据
},
/**
* 下载录音文件
* */
//下载pcm
downPCM() {
//这里传参进去的时文件名
recorder.downloadPCM("新文件");
},
//下载wav
downWAV() {
//这里传参进去的时文件名
recorder.downloadWAV("新文件");
},
/**
* 获取麦克风权限
* */
getPermission() {
Recorder.getPermission().then(
() => {
this.$message.success("获取权限成功");
},
error => {
console.log(`${error.name} : ${error.message}`);
}
);
},
/**
* 文件格式转换 wav-map3
* */
getMp3Data() {
const mp3Blob = this.convertToMp3(recorder.getWAV());
recorder.download(mp3Blob, "recorder", "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 = 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();
}
}
};
</script>
<style lang="less" scoped>
.audioBox {
color: #bec0e0;
width: 6.48rem;
height: 1.3rem;
display: flex;
justify-content: space-around;
align-items: center;
font-size: 0.2rem;
.boxItem {
cursor: pointer;
text-align: center;
.item_icon {
font-size: 0.4rem !important;
}
.item_desc {
margin-top: 0.1rem;
}
&.active {
color: #49f66c;
}
&:hover {
color: #49f66c;
}
}
}
</style>
结束了~~~~~
链接: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/126957202
链接: https://blog.csdn.net/qq_45341411/article/details/132232122?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-13-132232122-blog-126957202.235v43pc_blog_bottom_relevance_base3&spm=1001.2101.3001.4242.8&utm_relevant_index=16