封装组件(soundRecording.vue):
<template>
<view class="recorder"> </view>
</template>
<script>
export default {
data() {
return {
isUserMedia: false,
stream: null,
audio: null,
recorder: null,
chunks: []
};
},
mounted() {
/**
* error 事件的返回状态
* 100: 请在HTTPS环境中使用
* 101: 浏览器不支持
* 201: 用户拒绝授权
* 500: 未知错误
* */
if (origin.indexOf('https') === -1) {
this.$emit('error', '100');
uni.showModal({
title: '提示',
content: '请在https环境中使用录音功能,确认返回上一个页面',
showCancel: false,
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
}
});
}
if (!navigator.mediaDevices || !window.MediaRecorder) {
this.$emit('error', '101');
uni.showModal({
title: '提示',
content: '当前浏览器不支持录音功能,确认返回上一个页面',
showCancel: false,
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
}
});
}
this.getRecorderManager();
},
methods: {
getRecorderManager() {
this.audio = document.createElement('audio');
navigator.mediaDevices
.getUserMedia({ audio: true })
.then((stream) => {
this.isUserMedia = true;
stream.getTracks().forEach((track) => {
track.stop();
});
})
.catch((err) => {
this.onErrorHandler(err);
});
},
start() {
if (!this.isUserMedia) {
uni.showModal({
title: '提示',
content: '当前设备不支持,确认返回上一个页面',
showCancel: false,
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
}
});
}
navigator.mediaDevices
.getUserMedia({ audio: true })
.then((stream) => {
this.stream = stream;
this.recorder = new MediaRecorder(stream);
this.recorder.ondataavailable = this.getRecordingData;
this.recorder.onstop = this.saveRecordingData;
this.recorder.start();
})
.catch((err) => {
this.onErrorHandler(err);
});
},
stop() {
if (this.recorder) {
this.recorder.stop();
this.stream.getTracks().forEach((track) => {
track.stop();
});
}
},
getRecordingData(e) {
this.chunks.push(e.data);
},
saveRecordingData() {
const blob = new Blob(this.chunks, { type: 'audio/mpeg' }),
localUrl = URL.createObjectURL(blob);
this.chunks = [];
let lock = true;
const temporaryAudio = document.createElement('audio');
temporaryAudio.src = localUrl;
temporaryAudio.muted = true;
temporaryAudio.load();
temporaryAudio.play();
temporaryAudio.addEventListener('timeupdate', (e) => {
if (!Number.isFinite(temporaryAudio.duration)) {
temporaryAudio.currentTime = Number.MAX_SAFE_INTEGER;
temporaryAudio.currentTime = 0;
} else {
document.body.append(temporaryAudio);
document.body.removeChild(temporaryAudio);
if (lock) {
lock = false;
const recorder = {
data: blob,
duration: temporaryAudio.duration,
localUrl: localUrl
};
this.$emit('success', recorder);
}
}
});
},
onErrorHandler(err) {
console.log(err);
if (err.name === 'NotAllowedError') {
this.$emit('error', '201');
uni.showModal({
title: '提示',
content: '用户拒绝了当前浏览器的访问请求,确认返回上一个页面',
showCancel: false,
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
}
});
}
if (err.name === 'NotReadableError') {
this.$emit('error', '101');
uni.showModal({
title: '提示',
content: '当前浏览器不支持,确认返回上一个页面',
showCancel: false,
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
}
});
}
this.$emit('error', '500');
uni.showModal({
title: '提示',
content: '调用失败,确认返回上一个页面',
showCancel: false,
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
}
});
}
},
destroyed() {
this.stop();
}
};
</script>
使用组件:
<template>
<view>
<view class="audio" v-if="recorder">
<audio :src="recorder.localUrl" name="本地录音" controls="true"></audio>
<br />
<button type="primary" @click="handlerSave">保存录音</button>
</view>
<h3 v-else>点击下方按钮录音</h3>
<div class="container" v-if="status">
<div class="wave0"></div>
<div class="wave1"></div>
</div>
<view @click="handlerOnCahnger" class="statusBox" :class="{ active: status }">
<view class="status"></view>
</view>
<sound-recording ref="recorder" @success="handlerSuccess" @error="handlerError"></sound-recording>
</view>
</template>
<script>
import SoundRecording from '@/components/soundRecording/soundRecording.vue';
export default {
components: {
SoundRecording
},
data() {
return {
status: false,
recorder: null
};
},
methods: {
handlerSave() {
uni.downloadFile({
url: this.recorder.localUrl, //仅为示例,并非真实的资源
success: (res) => {
if (res.statusCode === 200) {
var oA = document.createElement('a');
oA.download = ''; // 设置下载的文件名,默认是'下载'
oA.href = res.tempFilePath; //临时路径再保存到本地
document.body.appendChild(oA);
oA.click();
oA.remove(); // 下载之后把创建的元素删除
}
},
fail: (err) => {
uni.showToast({
title: `下载失败${err}`
});
}
});
},
handlerOnCahnger() {
if (this.status) {
this.$refs.recorder.stop();
} else {
this.$refs.recorder.start();
}
this.status = !this.status;
},
handlerSuccess(res) {
console.log(res);
this.recorder = res;
},
handlerError(code) {
switch (code) {
case '101':
uni.showModal({
content: '当前浏览器版本较低,请更换浏览器使用,推荐在微信中打开。'
});
break;
case '201':
uni.showModal({
content: '麦克风权限被拒绝,请刷新页面后授权麦克风权限。'
});
break;
default:
uni.showModal({
content: '未知错误,请刷新页面重试'
});
break;
}
}
}
};
</script>
<style lang="scss" scoped>
.audio {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20rpx;
}
h3 {
text-align: center;
padding: 20rpx;
}
.statusBox {
z-index: 11;
width: 120rpx;
height: 120rpx;
background-color: aliceblue;
border-radius: 50%;
box-shadow: 5rpx 5rpx 10rpx rgba(000, 000, 000, 0.35);
display: flex;
align-items: center;
justify-content: center;
position: fixed;
left: 50%;
bottom: 120rpx;
transform: translateX(-50%);
.status {
width: 60rpx;
height: 60rpx;
background-color: aliceblue;
border-radius: 50%;
border: 10rpx solid #5c5c66;
}
&.active {
background-color: rgba(118, 218, 255, 0.45);
.status {
background-color: rgba(118, 218, 255, 0.45);
border: 10rpx solid rgba(255, 255, 255, 0.75);
}
}
}
.container {
z-index: 10;
position: fixed;
bottom: -200rpx;
padding: 0;
border: 0;
width: 750rpx;
height: 750rpx;
background-color: rgb(118, 218, 255);
}
.wave0,
.wave1,
.wave2 {
position: absolute;
width: 750rpx * 2;
height: 750rpx * 2;
margin-top: -150%;
margin-left: -50%;
background-color: rgba(255, 255, 255, 0.4);
border-radius: 45%;
animation: spin 15s linear -0s infinite;
z-index: 1;
/* border: 1px solid;*/
}
.wave1 {
margin-top: -152%;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 47%;
animation: spin 30s linear -15s infinite;
z-index: 2;
}
@keyframes spin {
0% {
transform: translate(-0%, -0%) rotate(0deg) scale(1);
}
25% {
transform: translate(-1%, -1%) rotate(90deg) scale(1);
}
50% {
transform: translate(-0%, -2%) rotate(180deg) scale(1);
}
75% {
transform: translate(1%, -1%) rotate(270deg) scale(1);
}
100% {
transform: translate(-0%, -0%) rotate(360deg) scale(1);
}
}
</style>
效果:

注意:需要调用录音功能的域名必须是https,否则无法调用!