代码基于vue3,chrome版本122.0.6261.131,未进行其他浏览器兼容性验证。拷贝到工程文件中进行简单调整即可使用。
<template>
<div>
<el-dialog :model-value="props.isAudioRecordDialogShow" width="900" :before-close="handleClose">
<template #header="{ titleId, titleClass }">
<div class="my-header">
<h4 :id="titleId" :class="titleClass">音频录制</h4>
</div>
</template>
<div class="main-content">
<SvgIcon name="audio-wave" v-show="!isRecording"></SvgIcon>
<img v-show="isRecording" src="/wave2.gif" alt="" style="width: 422px; height: 146px" />
<div class="audio-ing" @click="startRecording" v-if="!recordedAudioURL">
<div class="circle"></div>
<div v-if="isRecording">
<div class="pause"></div>
<div class="text-btn">停止录音</div>
</div>
<div v-else>
<div class="play triangle"></div>
<div class="text-btn">开始录音</div>
</div>
<div class="header-timer">耗时:{{ timeStr }}</div>
</div>
<div v-if="!isRecording && recordedAudioURL" class="audio-res">
<audio :src="recordedAudioURL" controls class="audio-self"></audio>
<div class="re-record" @click="resetData">重新录制</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" :disabled="isRecording" @click="toUse" style="width: 140px">使用</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import { ElDialog, ElButton, ElMessage } from 'element-plus';
import SvgIcon from '@/components/SvgIcon';
const props = defineProps({
isAudioRecordDialogShow: {
type: Boolean,
default: false,
},
});
const btnText = computed(() => (isRecording.value ? '停止录音' : '开始录音'));
let recordingTime = ref(0);
const emit = defineEmits(['closeAudioRecordDialog', 'setRecordAudioUrl']);
const handleClose = () => {
emit('closeAudioRecordDialog');
};
let timeStr = computed(() => {
let h = Math.floor(recordingTime.value / 3600);
let m = Math.floor((recordingTime.value % 3600) / 60);
let s = recordingTime.value % 60;
// 进行补0操作
h = h < 10 ? `0${h}` : h;
m = m < 10 ? `0${m}` : m;
s = s < 10 ? `0${s}` : s;
return `${h}:${m}:${s}`;
});
// 音频录制超过5秒后,自动停止录音
watch(
() => recordingTime.value,
(newV) => {
if (newV >= 20) {
stopRecording();
ElMessage.warning('录音时长达到20秒,自动停止录音');
}
},
{
immediate: true,
},
);
const isRecording = ref(false);
let mediaRecorder;
let chunks = [];
let recordedAudioURL = ref(null);
let recordingInterval;
const recorderFile = ref(null);
const startRecording = async () => {
try {
// 如果正在录音,点击按钮则停止录音
if (mediaRecorder && isRecording.value) {
stopRecording();
return;
}
// 开始录音
resetData();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
chunks.push(event.data);
}
};
mediaRecorder.onstop = () => {
recorderFile.value = new Blob(chunks, { type: 'audio/wav' });
recordedAudioURL.value = URL.createObjectURL(recorderFile.value);
chunks = [];
clearInterval(recordingInterval);
recordingTime.value = 0;
};
mediaRecorder.start();
isRecording.value = true;
startRecordingTime();
} catch (error) {
// console.error('Error accessing media devices: ', error);
}
};
const stopRecording = () => {
if (mediaRecorder && isRecording.value) {
mediaRecorder.stop();
isRecording.value = false;
clearInterval(recordingInterval);
recordingTime.value = 0;
}
};
const resetData = () => {
recordedAudioURL.value = null;
recordingTime.value = 0;
timeStr.value = '00:00:00';
};
const startRecordingTime = () => {
recordingInterval = setInterval(() => {
recordingTime.value++;
}, 1000);
};
const toUse = () => {
if (!recordedAudioURL.value) {
ElMessage.warning('请先录制音频');
return;
}
// 拿到录制的音频的文件流数据
let file = new File([recorderFile.value], 'audio.wav', { type: 'audio/wav' });
emit('setRecordAudioUrl', file, recordedAudioURL.value);
handleClose();
};
</script>
<style lang="less" scoped>
.main-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.svg-icon {
width: 480px;
height: 146px;
}
.audio-ing {
width: 480px;
height: 56px;
background-color: var(--el-color-primary);
border-radius: 300px;
cursor: pointer;
}
.circle {
width: 43px;
height: 43px;
background-color: #fff;
border-radius: 50%;
float: left;
margin-left: 7px;
margin-top: 7px;
}
.triangle {
width: 0;
height: 0;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 15px solid var(--el-color-primary);
float: left;
margin-top: 20px;
margin-left: -27px;
}
.pause {
width: 16px;
height: 20px;
position: relative;
float: left;
margin-top: 22px;
margin-left: -30px;
}
.pause::before {
content: '';
position: absolute;
width: 5px;
height: 14px;
background-color: var(--el-color-primary);
left: 0;
}
.pause::after {
content: '';
position: absolute;
width: 5px;
height: 14px;
background-color: var(--el-color-primary);
right: 0;
}
.header-timer {
float: right;
margin-top: 17px;
margin-right: 18px;
color: #fff;
}
.text-btn {
float: left;
margin-left: 10px;
margin-top: 17px;
color: #fff;
}
.dialog-footer {
text-align: center;
margin-top: 40px;
}
.audio-res {
position: relative;
height: 56px;
display: flex;
align-items: center;
.audio-self {
width: 467px;
}
.re-record {
position: absolute;
right: -90px;
top: 15px;
cursor: pointer;
color: var(--el-color-primary);
font-size: 16px;
}
}
</style>