在h5端实现录音发送功能(兼容内嵌微信小程序) recorder-core

本文将通过一个实际的 Vue3 组件示例,带你一步步实现"按住录音,松开发送,上滑取消"的语音录制功能。

我们将使用强大且小巧的开源库 recorder-core,支持 MP3、WAV、AAC 等编码格式,兼容性较好。

🔧 项目依赖

复制代码
pnpm add recorder-core dayjs
# 或
npm install recorder-core dayjs

我们实现的组件是一个 input 输入框,按下开始录音,松开结束录音,上滑取消录音。核心逻辑全部由 recorder-core 管理。

✅ 权限处理机制

第一次调用 rec.open() 时会触发麦克风授权窗口,用户点击「允许」后才能真正录音。所以我们用 isAuthorized 标记避免重复弹窗。


✅ 录音时间和状态展示

我们通过 onProcess() 回调实时拿到录音时间和音量等级,再结合 dayjs 把时间格式化展示在 UI 上(audioLoading.vue 可以自定义成动画弹窗或语音时长条等)。


✅ 录音取消(上滑手势)

录音时用户可能不想发送,我们监听 @touchmove 来模拟"上滑取消"操作,直接关闭并丢弃录音。

完整代码如下

复制代码
<template>
  <input
    disabled="true"
    placeholder="按住 说话"
    @touchstart="handleTouchStart"
    @touchmove="handleTouchMove"
    @touchend="handleTouchEnd"
  />
  <AudioLoading :audioLoading="audioLoading" :audioTime="audioTime" />
</template>

<script setup>
  import { ref } from 'vue';
  import dayjs from 'dayjs';
  import Recorder from 'recorder-core';
  import 'recorder-core/src/engine/mp3'; // mp3 封装
  import 'recorder-core/src/engine/mp3-engine'; // mp3 编码核心模块
  const audioLoading = ref(false); //语音弹框
  const audioTime = ref(0); //语音时间
  const isAuthorized = ref(false); // 是否授权
  import AudioLoading from './audioLoading.vue';
function formatDateToss(inputStr) {
    return dayjs(inputStr).format('mm:ss');
  }
  let rec = null;
  /*长按开始录制语音*/
  const handleTouchStart = e => {
    audioTime.value = 0;
    rec = Recorder({
      type: 'mp3',
      sampleRate: 16000,
      bitRate: 16,
      onProcess(buffers, powerLevel, duration, sampleRate) {
        audioLoading.value = true;
        audioTime.value = formatDateToss(duration);
      },
    });
    rec.open(
      () => {
        if (isAuthorized.value) {
          rec.start();
        }
        isAuthorized.value = true;
      },
      (msg, isUserNotAllow) => {
        audioLoading.value = false;
        console.log('停止录音失败: ' + msg);
      },
    );
  };
  /*语音录制结束*/
  const handleTouchEnd = () => {
    audioLoading.value = false;
    rec.stop(
      (blob, duration) => {
        rec.close();
        const url = URL.createObjectURL(blob);
        console.log(url);
      },
      msg => {
        rec.close();
        console.log('停止录音失败: ' + msg);
      },
    );
  };
  //上滑取消
  const handleTouchMove = () => {
    rec.close();
    rec.stop();
    audioLoading.value = false;
  };
</script>

AudioLoading加载组件

复制代码
<template>
  <view class="modal-body" v-if="audioLoading">
    <view class="time">{{ audioTime }}</view>
    <view class="sound-waves">
      <view
        v-for="(item, index) in radomHeight"
        :key="index"
        :style="`height: ${item}rpx; margin-top: -${item / 2}rpx;`"
      ></view>
      <view style="clear: both; width: 0; height: 0"></view>
    </view>
    <view class="desc">松开发送,上滑取消</view>
  </view>
</template>

<script setup>
  import { watch, ref } from 'vue';

  import { onLoad } from '@dcloudio/uni-app';
  const props = defineProps({
    audioTime: {
      type: Number,
    },
    audioLoading: {
      type: Boolean,
      default: false,
    },
  });
  const radomHeight = ref([
    50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
    50, 50, 50,
  ]);
  onLoad(() => {});
  let timer;
  watch(
    () => props.audioLoading,
    val => {
      if (val) {
        timer = setInterval(() => {
          myradom();
        }, 500);
      } else {
        clearInterval(timer);
      }
    },
  );

  const myradom = () => {
    let _radomheight = radomHeight.value;
    for (var i = 0; i < radomHeight.value.length; i++) {
      //+1是为了避免为0
      _radomheight[i] = 100 * Math.random().toFixed(2) + 10;
    }
    radomHeight.value = _radomheight;
  };
</script>

<style scoped lang="scss">
  .modal-body {
    position: fixed;
    top: 500rpx;
    left: 235rpx;
    width: 280rpx;
    height: 280rpx;
    background: rgba(0, 0, 0, 0.75);
    border-radius: 16rpx;
    backdrop-filter: blur(20rpx);
    box-sizing: border-box;
    padding-top: 40rpx;
  }
  .time {
    width: 100%;
    text-align: center;
    font-size: 28rpx;
    font-family: PingFangSC-Regular, PingFang SC;
    font-weight: 400;
    color: #ffffff;
  }
  .sound-waves {
    width: 100%;
    box-sizing: border-box;
    padding-left: 10%;
    margin-top: 70rpx;
    height: 50rpx;
    text-align: center;
  }

  .sound-waves view {
    transition: all 0.5s;
    width: 1%;
    margin-left: 1.5%;
    margin-right: 1.5%;
    height: 100rpx;
    background-color: #ffffff;
    float: left;
  }
  .desc {
    width: 100%;
    font-size: 30rpx;
    font-family: PingFangSC-Regular, PingFang SC;
    font-weight: 400;
    color: #ffffff;
    line-height: 42rpx;
    text-align: center;
    margin-top: 20rpx;
  }
  .record-btn {
    width: 584rpx;
    height: 74rpx;
    line-height: 74rpx;
    text-align: center;
    background: #ffffff;
    border-radius: 16rpx;
    font-size: 32rpx;
    font-family: PingFangSC-Semibold, PingFang SC;
    font-weight: 600;
    color: #000000;
  }
  .record-btn::after {
    border: none;
  }
</style>

注意如果内嵌到微信小程序中开发环境 会直接拒绝权限

必须部署到http环境才可以

相关推荐
2601_949950631 小时前
错题总是反复错?用练题簿在线刷题,把复习重点找出来
人工智能·小程序·刷题·练习·小程序推荐
卓怡学长2 小时前
w194基于ssm新能源汽车充电系统小程序
java·小程序·intellij-idea
Ai-_Man3 小时前
您您这可以把Google AI Studio的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑
m0_5873830016 小时前
深度解析24小时自助健身房系统开发:从架构设计到落地部署
人工智能·小程序·数据挖掘·系统架构·需求分析
镭封1 天前
基于微信小程序的移动端AI配音工作流设计
人工智能·小程序·媒体
FX1317887KP_1 天前
PET口语备考攻略:发音、语法、表达,三个问题怎么同时解决
小程序·ket口语·pet口语·fce备考·剑桥英语
地球@+jdhb441 天前
企业私域运营新趋势:快手短视频平台跳转小程序企微卡片链路搭建成为商家运营重点
小程序·企业微信
程序鉴定师1 天前
2026年深圳小程序/App开发公司技术选型指南:从架构设计到交付标准全维度评估
java·小程序·uni-app·php·objective-c
Q26433650231 天前
【有源码】基于uni-app的旅游行程规划小程序-基于微信小程序的智慧文旅综合服务平台
java·微信小程序·小程序·uni-app·毕业设计·springboot·源代码
网硕互联的小客服1 天前
微信小程序多种跳转页面方式教学
微信小程序·小程序·notepad++