记录使用Recorder实现H5页面录音

记录使用Recorder实现H5页面录音

技术栈:vue3 + element-plus + Recorder

这段代码主要实现了以下功能:

  1. 长按录音,松开发送
  2. 上滑至一定位置取消
  3. 音频blob转成base64格式
  4. 录音音频下载
  5. 模拟音频声波的动画

安装依赖

bash 复制代码
yarn add recorder-core
// 或者
npm i recorder-core

引入依赖

在使用录音的页面文件中添加如下代码引入Recorder 及 音频编码器。

js 复制代码
import Recorder from 'recorder-core'
import 'recorder-core/src/engine/wav.js' // 引入对应格式的编码器

编码器必须引入,否则录音时会报错未加载对应的编码器。我使用的是wav格式,如果使用其他音频格式,可引入对应的编码器文件。下图是依赖中编码器文件所在位置,按需引入即可。

使用

html 复制代码
<template>
    <el-button
      class="audio-btn"
      @touchstart="startRecording"
      @touchmove="handleTouchMove"
      @touchend="sendRecording"
    >
      {{ recording ? '松开发送' : '按住说话' }}
    </el-button>
    <el-drawer
      v-model="dialogVisible"
      direction="btt"
      :show-close="false"
      :with-header="false"
    >
      <div v-if="recording">
        <p v-if="canceling">松手取消发送</p>
        <p v-else-if="seconds && seconds < 10">{{ seconds }}s后将停止录音</p>
        <p v-else>松开发送,上滑取消</p>
        // 模式音频声波的动画
        <div class="audio-animation">
          <span class="audio-wave" :class="'wave' + index" v-for="index in 8" :key="index"></span>
        </div>
      </div>
    </el-drawer>
</template>
js 复制代码
const recording = ref(false);
const cancel = ref(false); // 上滑并松手,确定取消录音(用于判断是否将录音发送给服务端)
const canceling = ref(false); // 正在上滑,也可能还会下滑继续录音(用于在上滑时提示用户上滑会取消)
const dialogVisible = ref(false);
let startY = 0; // 用于判断上滑取消
const rec = ref(null);
let countdownTimer = null;
const seconds = ref(60); // 录音限制60s

function startRecording(event) {
  cancel.value = false;
  recording.value = true;
  dialogVisible.value = true;
  startY = event.touches[0].clientY;
  seconds.value = 60;
  recOpen()
};

// 开启录音
function recOpen() {
  // 传入 音频采样所需的格式,采样频率和采样位数
  rec.value = Recorder({
    type: "wav",
    sampleRate: 8000,
    bitRate: 16,
  });
  rec.value.open(function(){
    // 这个定时器是限制录音60s,最后10s的时候给用户提示
    countdownTimer = setInterval(() => {
      console.log(`倒计时:${seconds.value}秒`);
      seconds.value--;
      if (seconds.value < 0) {
        clearInterval(countdownTimer);
        // 到达录音限制时间,自动停止录音并发送给服务端
        sendRecording();
      }
    }, 1000);
    console.log('开始录音');
    rec.value.start(); // 开始录音

  },function(msg, isUserNotAllow){
    console.log((isUserNotAllow ? "UserNotAllow," : "")+"无法录音:" + msg);
  });
};

// 结束录音
function recStop(){
    rec.value.stop(function(blob, duration){
        // window.URL.createObjectURL(blob) 是blob格式链接,可直接播放音频;duration为音频时长(ms)
        console.log(blob, window.URL.createObjectURL(blob), "时长:" + duration + "ms");
        rec.value.close(); // 释放录音资源,最好释放,否则会导致当前页面一直占用麦克风
        rec.value = null;
        if (countdownTimer) {
          clearTimeout(countdownTimer); // 清除倒计时计时器
        }
        
        // 如果调试时想要下载录音文件,可把这段注释打开。
        // const a = document.createElement('a');
        // a.href = (window.URL).createObjectURL(blob);
        // a.download = 'recording.wav';
        // a.style.display = 'none';
        // document.body.appendChild(a);
        // a.click();
        // document.body.removeChild(a);

        console.log('cancel', cancel.value);
        // 如果没有上滑取消则转成base64发给服务端,否则不做操作
        if (!cancel.value) {
          transBase64(blob)
        }
    },function(msg){
        console.log(msg);
        rec.value.close();
        rec.value = null;
    });
}

// 把blob格式的录音文件转成base64格式
function transBase64(wavBlob) {
    let reader = new FileReader();
    reader.onloadend = function(){
        const base64Str = (/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result)||[])[1];
        // 调用接口,发给服务端
        sendBase64AudioToBackend(base64Str);
    };
    reader.readAsDataURL(wavBlob);
}

// 上滑超过50px则取消录音
function handleTouchMove(event) {
  if (recording.value) {
    const currentY = event.touches[0].clientY;
    const deltaY = startY - currentY;
    console.log(deltaY);

    if (deltaY > 50) {
      cancel.value = true;
      canceling.value = true;
    } else {
      cancel.value = false;
      canceling.value = false;
    }
  }
};

// 结束录音时,恢复各字段默认值并执行recStop方法
function sendRecording() {
  dialogVisible.value = false;
  recording.value = false;
  canceling.value = false;
  recStop()
};

样式部分只包含模拟声波的动画部分,其余省略

css 复制代码
.audio-animation {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 50px;
}

.audio-wave {
  width: 2px;
  height: 6px;
  margin: 0 2px;
  background-color: #0078d4;
  animation: waveAnimation 0.5s ease infinite;
  animation-fill-mode: both; /* 保持动画结束状态 */
}

.audio-wave.wave1 { animation-delay: -.9s; }
.audio-wave.wave2 { animation-delay: -.8s; }
.audio-wave.wave3 { animation-delay: -.7s; }
.audio-wave.wave4 { animation-delay: -.6s; }
.audio-wave.wave5 { animation-delay: -.5s; }
.audio-wave.wave6 { animation-delay: -.4s; }
.audio-wave.wave7 { animation-delay: -.3s; }
.audio-wave.wave8 { animation-delay: -.2s; }

@keyframes waveAnimation {
  0%, 100% {
    transform: scaleY(1);
  }
  50% {
    transform: scaleY(3);
  }
}

其他

录音需要调用navigator.mediaDevices.getUserMedia()获取权限,如果报错navigator.mediaDevices is undefined,请参考文章要实现录音功能,但是navigator.mediaDevices 是 undefined这样解决

相关推荐
2301_765347547 分钟前
Vue3 Day7-全局组件、指令以及pinia
前端·javascript·vue.js
ch_s_t8 分钟前
新峰商城之分类三级联动实现
前端·html
辛-夷15 分钟前
VUE面试题(单页应用及其首屏加载速度慢的问题)
前端·javascript·vue.js
田哥coder16 分钟前
充电桩项目:前端实现
前端
青年有志29 分钟前
Web 服务器介绍 | 通过 Tomcat 说明其作用
服务器·前端·tomcat
dawn1912281 小时前
SpringMVC 中的域对象共享数据
java·前端·servlet
newxtc1 小时前
【爱给网-注册安全分析报告-无验证方式导致安全隐患】
前端·chrome·windows·安全·媒体
dream_ready2 小时前
linux安装nginx+前端部署vue项目(实际测试react项目也可以)
前端·javascript·vue.js·nginx·react·html5
编写美好前程2 小时前
ruoyi-vue若依前端是如何防止接口重复请求
前端·javascript·vue.js
flytam2 小时前
ES5 在 Web 上的现状
前端·javascript