记录使用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这样解决

相关推荐
BBB努力学习程序设计13 小时前
CSS Sprite技术:用“雪碧图”提升网站性能的魔法
前端·html
BBB努力学习程序设计13 小时前
CSS3渐变:用代码描绘色彩的流动之美
前端·html
冰暮流星14 小时前
css之动画
前端·css
jump68014 小时前
axios
前端
spionbo14 小时前
前端解构赋值避坑指南基础到高阶深度解析技巧
前端
用户40993225021214 小时前
Vue响应式声明的API差异、底层原理与常见陷阱你都搞懂了吗
前端·ai编程·trae
开发者小天14 小时前
React中的componentWillUnmount 使用
前端·javascript·vue.js·react.js
永远的个初学者15 小时前
图片优化 上传图片压缩 npm包支持vue(react)框架开源插件 支持在线与本地
前端·vue.js·react.js
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ15 小时前
npm i / npm install 卡死不动解决方法
前端·npm·node.js
Kratzdisteln15 小时前
【Cursor _RubicsCube Diary 1】Node.js;npm;Vite
前端·npm·node.js