|
|
@@ -1,412 +0,0 @@
|
|
|
-<template>
|
|
|
- <div class="audioRecord">
|
|
|
- <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',
|
|
|
- props: {
|
|
|
- audioUrl: {
|
|
|
- type: String,
|
|
|
- default: '',
|
|
|
- },
|
|
|
- },
|
|
|
- data() {
|
|
|
- return {
|
|
|
- //波浪图-录音
|
|
|
- drawRecordId: null,
|
|
|
- oCanvas: null,
|
|
|
- ctx: null,
|
|
|
- //波浪图-播放
|
|
|
- drawPlayId: null,
|
|
|
- pCanvas: null,
|
|
|
- pCtx: null,
|
|
|
- recordStatus: null,
|
|
|
- rm: 0, // 录音时长 分
|
|
|
- rs: 0, // 录音时长 秒
|
|
|
- ym: 0, // 播放时长 分
|
|
|
- ys: 0, // 播放时长 秒
|
|
|
- };
|
|
|
- },
|
|
|
- watch: {
|
|
|
- audioUrl: {
|
|
|
- handler() {
|
|
|
- debugger;
|
|
|
- this.getMp3Data();
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
- 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(
|
|
|
- () => {
|
|
|
- console.log('获取权限成功,开始录音');
|
|
|
- recorder.start();
|
|
|
- },
|
|
|
- (error) => {
|
|
|
- console.log('请先允许该网页使用麦克风');
|
|
|
- // 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(
|
|
|
- () => {
|
|
|
- console.log('获取权限成功');
|
|
|
- },
|
|
|
- (error) => {
|
|
|
- console.log(`${error.name} : ${error.message}`);
|
|
|
- }
|
|
|
- );
|
|
|
- },
|
|
|
- /**
|
|
|
- * 文件格式转换 wav-map3
|
|
|
- * */
|
|
|
- getMp3Data() {
|
|
|
- debugger;
|
|
|
- let url = this.audioUrl;
|
|
|
- var xhr = new XMLHttpRequest();
|
|
|
- xhr.open('GET', url, true);
|
|
|
- xhr.responseType = 'arraybuffer';
|
|
|
- xhr.onload = function () {
|
|
|
- var samples = new Int16Array(xhr.response);
|
|
|
- const mp3Blob = this.convertToMp3(samples);
|
|
|
- recorder.download(mp3Blob, 'recorder', 'mp3');
|
|
|
- };
|
|
|
- xhr.send();
|
|
|
- },
|
|
|
- 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>
|
|
|
-.audioRecord {
|
|
|
- background: #fff;
|
|
|
- position: fixed;
|
|
|
- bottom: 120px;
|
|
|
- width: 100%;
|
|
|
-}
|
|
|
-.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>
|