|
|
@@ -0,0 +1,386 @@
|
|
|
+<template>
|
|
|
+ <div class="audioBoxContent">
|
|
|
+ <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="scss" scoped>
|
|
|
+.audioBoxContent {
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+.audioBox {
|
|
|
+ color: #bec0e0;
|
|
|
+ // width: 6.48rem;
|
|
|
+ // height: 1.3rem;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-around;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 14px;
|
|
|
+ .boxItem {
|
|
|
+ cursor: pointer;
|
|
|
+ text-align: center;
|
|
|
+ margin: 0 5px;
|
|
|
+ .item_icon {
|
|
|
+ font-size: 14px !important;
|
|
|
+ }
|
|
|
+ .item_desc {
|
|
|
+ margin-top: 0.1rem;
|
|
|
+ }
|
|
|
+ &.active {
|
|
|
+ color: #49f66c;
|
|
|
+ }
|
|
|
+ &:hover {
|
|
|
+ color: #49f66c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|