在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><inputdisabled="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"><viewv-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环境才可以