使用recorder-core出现警告:SampleData似乎传入了未重置chunk 71>18,我的代码:<template>
<div class="meeting-container">
<!-- 顶部导航栏 -->
<div style="height: 24%">
<van-nav-bar
left-arrow
border
style="background: dodgerblue"
@click-left="$router.back()"
>
<template #left>
<van-icon @click="this.$router.go(-1)" name="arrow-left" size="18" color="#fff"/>
</template>
</van-nav-bar>
<div class="meeting-time">
<h2>网格晨会</h2>
<van-tag plain style="border: white" color="#1E90FF" size="large">{{this.agenda}}
</van-tag>
</div>
<div style="background: dodgerblue;;height: 50px"></div>
</div>
<div v-if="meetingOverFlag" style="height: 76%;width: 100%">
<!-- 主要内容区域 -->
<div style="height:80%;width: 100%;">
<div class="recording-content">
<div style="width: 64px">
<div style="position: relative;margin-top: 40px;margin-bottom:15px;left: 7px;">
<div class="recording-content-top" :class="{'recording-content-top-animate':startRecordFlag}" style="z-index: 0;position: absolute;">
</div>
<div class="recording-content-top" style="z-index: 4">
<van-icon size="25" name="volume" />
</div>
</div>
<span style="color: dodgerblue"> {{this.agendaOrder}}</span>
</div>
</div>
<div class="recording-show">
<div style="padding-top: 30px">
<!-- 镜像模式频率直方图绘制区域 -->
<div style=" display: inline-block; vertical-align: bottom">
<div style="height: 200px; width: 300px" ref="freqHistogram"></div>
</div>
</div>
</div>
<div class="time-container">
<div :style="{background:recordingBitColor}" style="width:25px;height: 25px;background: #63a35c;border-radius: 50%;margin-top: 18px;margin-right: 15px "></div>
<div>{{ time }}</div>
</div>
</div>
<span style="flex: 1"></span>
<div class="record-bottom">
<div style="flex: 1"></div>
<van-button
v-if="flag"
round
block
type="info"
class="record-btn"
@click="end"
>
暂停
</van-button>
<van-button
id="startRecordingBtn"
v-else
round
block
type="info"
class="record-btn"
@click="start"
>
继续
</van-button>
<div style="flex: 1"></div>
<van-button
round
block
type="info"
class="record-btn"
@click="reset"
>
结束会议
</van-button>
<div style="flex: 1"></div>
</div>
</div>
<span style="flex: 1"></span>
<div v-if="!meetingOverFlag" style="color: red">
<h4>会议已结束</h4>
<span>会议时长:{{time}}</span>
</div>
</div>
</template>
<script>
import {NavBar, Icon, Panel, Button, Toast} from 'vant';
//必须引入的核心
import Recorder from 'recorder-core';
//引入mp3格式支持文件
import 'recorder-core/src/engine/mp3';
import 'recorder-core/src/engine/mp3-engine';
//录制wav格式支持
import 'recorder-core/src/engine/wav';
//频率直方图可视化插件
import 'recorder-core/src/extensions/frequency.histogram.view';
//FFT库,FrequencyHistogramView依赖
import 'recorder-core/src/extensions/lib.fft';
import meeting from "@/api/meeting";
import meetingApi from "@/api/meeting";
import {mapState} from "vuex";
export default {
name: 'MeetingAgenda',
data(){
return{
agenda:"",
agendaOrder:"",
flag: null,
hour: 0,
minute: 0,
second: 0,
time: "00:00:00",
startRecordFlag:true,//开始录音标志位
recordingBitColor:'green',//录音显示标识
meetingOverFlag:true,
//录音插件配置
rec: null,
recBlob: null,
freqHistogram: null,
//录音上传
uploadChunks: [], // 存储上传的分块
currentChunkIndex: 0, // 当前上传的分块索引
meetingId: '', // 会议唯一标识
chunkSize: 1024 * 1024, // 分块大小1MB
uploadInterval: null // 定时上传的定时器,
}
},
computed:{
...mapState('baseInfo', ['userInfo', 'userRegions'])
},
mounted() {
console.log("userRegions",this.userRegions)
this.meetingId = this.generateMeetingId();
this.agenda="随销"
this.agendaOrder="第一议程"
this.meetingTimer()
console.log("user",this.userInfo)
this.recOpen()
},
methods: {
// 生成会议唯一ID
generateMeetingId() {
return 'meeting_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
},
meetingTimer() {
setTimeout(() => {
this.agenda = "直销"
this.agendaOrder = "第二议程"
}, 3000)
setTimeout(() => this.toToast(20), 3000)
setTimeout(() => this.toToast(30), 5000)
setTimeout(() => this.toToast(50), 7000)
},
//提示
toToast(minute) {
let message = ''
if (minute === 20) {
message = '请开始第二议程,若已开始,则忽略该提示'
} else if (minute === 30) {
message = '请开始第二议程,若已开始,则忽略该提示'
} else {
message = '第二议程还剩<span style="color: yellow;">10</span>,请加快会议进度。若已议程进度,则忽略该提示。'
}
Toast({
type: "html",
message: message,
dangerouslyUseHTMLString: true
});
},
//开始计时
start() {
this.recStart()
this.startRecordFlag = true
this.recordingBitColor = 'green'
// 开始定时上传
this.startUploadInterval();
this.flag = setInterval(() => {
this.second = this.second + 1;
if (this.second >= 60) {
this.second = 0;
this.minute = this.minute + 1;
}
if (this.minute >= 60) {
this.minute = 0;
this.hour = this.hour + 1;
}
this.time =
this.complZero(this.hour) +
":" +
this.complZero(this.minute) +
":" +
this.complZero(this.second);
}, 1000);
},
// 开始定时上传
startUploadInterval() {
// 每3秒上传一次
this.uploadInterval = setInterval(() => {
this.uploadCurrentChunk();
}, 3000);
},
// 上传当前录音数据
async uploadCurrentChunk() {
if (!this.rec) return;
// 获取当前录音数据(不停止录音)
const buffers = this.rec.buffers;
if (buffers.length === 0) return;
// 改进的合并方法
const merged = this.mergeBuffers(buffers);
// 转换为Blob
const blob = new Blob([merged], {type: 'audio/wav'});
// 只有当数据足够大时才上传(例如>1mb)
if (blob.size > this.chunkSize) {
const base64Data = await this.blobToBase64(blob);
this.uploadChunk(base64Data);
// 清空已上传的缓冲区
this.rec.buffers = [];
}
},
blobToBase64(blob) {
const reader = new FileReader();
reader.readAsDataURL(blob);
return new Promise((resolve) => {
reader.onloadend = () => {
const dataUrl = reader.result;
const base64Data = dataUrl.split(',')[1]; // 去除DataURL前缀
resolve(base64Data);
};
});
},
// 合并缓冲区数据
mergeBuffers(buffers) {
// 计算总长度
let totalLength = 0;
buffers.forEach(buf => {
totalLength += buf.length;
});
// 创建合并后的缓冲区
const result = new Float32Array(totalLength);
let offset = 0;
// 合并所有缓冲区
buffers.forEach(buf => {
result.set(buf, offset);
offset += buf.length;
});
return result;
},
// 上传分块
uploadChunk(base64Data) {
try {
let userInfo={
opPhone:this.userInfo.user_mobile,
cityId:"",
areaLevel:""
}
const payload = {
meetingId: this.meetingId,
chunkIndex: this.currentChunkIndex++,
audioData: base64Data,
};
const response = meetingApi.uploadRecordFile(payload);
console.log('上传成功:', response);
return response;
} catch (error) {
console.error('上传失败:', error);
throw error;
}
},
//重新计时
reset() {
this.recStop()
this.meetingOverFlag=false
window.clearInterval(this.flag);
},
//暂停计时
end() {
this.rec.pause();
this.flag = clearInterval(this.flag);
if (this.uploadInterval) {
clearInterval(this.uploadInterval);
this.uploadInterval = null;
}
this.startRecordFlag = false
this.recordingBitColor = '#777777'
},
//补零
complZero(n) {
return n < 10 ? "0" + n : "" + n;
},
// 打开录音
recOpen() {
return new Promise((resolve, reject) => {
this.rec = Recorder({
type: 'mp3',
bufferSampleRate: 16000,
bitRate: 16,
onProcess: (buffers, powerLevel, bufferDuration, bufferSampleRate) => {
if (this.freqHistogram) {
this.freqHistogram.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
}
}
});
if (!this.rec) {
alert('当前浏览器不支持录音功能!');
reject();
return;
}
this.rec.open(() => {
console.log('录音已打开');
this.$nextTick(() => {
if (this.$refs.freqHistogram) {
this.freqHistogram = Recorder.FrequencyHistogramView({
elem: this.$refs.freqHistogram,
width: 320,
height: 200,
lineCount: 20,
position: 0,
minHeight: 1,
stripeEnable: false,
mirrorEnable: true,
mirrorHeight: 0.5,
linear: [0, "#1a73e8", 0.5, "#182374", 1, "rgba(255,102,0,1)"],
fallDuration: 1000,
stripeFallDuration: 1000,
fontSize: 10,
scale: 1,
fadeIn: 0.3,
fadeOut: 0.7
});
}
});
resolve();
this.start();
}, (msg, isUserNotAllow) => {
if (isUserNotAllow) {
alert('请允许录音权限后重试!');
reject();
}
});
});
},
//开始录音
recStart() {
if (!this.rec) {
console.error('未打开录音');
return;
}
// 确保可视化容器已初始化
if (!this.freqHistogram && this.$refs.freqHistogram) {
this.initVisualizer();
}
this.rec.start();
console.log('已开始录音');
},
// 提取可视化初始化逻辑
initVisualizer() {
this.freqHistogram = Recorder.FrequencyHistogramView({
elem: this.$refs.freqHistogram,
// ...保持原有配置
})
},
//结束录音
recStop() {
if (!this.rec) {
console.error('未打开录音');
return;
}
this.rec.stop((blob, duration) => {
this.recBlob = blob;
const localUrl = (window.URL || window.webkitURL).createObjectURL(blob);
console.log('录音成功', blob, localUrl, '时长:' + duration + 'ms');
// 保存到本地
this.download(blob);
this.rec.close();
this.rec = null;
}, (err) => {
console.error('结束录音出错:' + err);
this.rec.close();
this.rec = null;
});
},
// 保存录音文件到本地
download(blob) {
// 创建下载链接
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// 使用时间戳生成唯一文件名
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
a.download = `recording_${timestamp}.mp3`;
document.body.appendChild(a);
a.click();
// 清理资源
setTimeout(() => {
URL.revokeObjectURL(url);
document.body.removeChild(a);
}, 100);
},
},
beforeDestroy() {
if (this.rec) {
this.rec.close();
this.rec = null;
}
if (this.uploadInterval) {
clearInterval(this.uploadInterval);
this.uploadInterval = null;
}
}
}
</script>
<style scoped>
.meeting-container {
margin: 0;
padding: 0;
box-sizing: border-box; /* 确保边框和内边距计入宽高 */
height: 100vh;
width: 100vw;
color: #fff;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
/* 自定义导航栏样式 */
.van-nav-bar {
background-color: transparent;
}
.van-nav-bar__title {
color: #fff;
font-size: 18px;
}
.meeting-time {
margin: 0;
float: none;
padding-top: 20px;
text-align: center;
background: dodgerblue;
}
.meeting-time h3 {
font-size: 16px;
margin-bottom: 8px;
font-weight: normal;
}
.meeting-time p {
font-size: 14px;
margin: 0;
}
.recording-content{
display: flex;
justify-content: center;
.recording-content-top{
width: 50px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
background:dodgerblue;
border-radius: 50%;
}
.recording-content-top-animate{
animation-name: recording-start;
animation-duration: 2s;
animation-iteration-count: infinite;
background: dodgerblue;
}
}
@keyframes recording-start {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(2);
opacity: 0;
}
}
.recording-show{
width: 100%;
height: 55%;
text-align: center;
}
.time-container{
height: 20%;
width: 100%;
color: #1a73e8;
font-size: 50px;
display: flex;
justify-content: center;
}
/* 录音按钮样式 */
.record-bottom {
height: 20%;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
.record-btn {
background-color: #1a73e8;
border: none;
width: 30%;
height: 44px;
line-height: 44px;
font-size: 16px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(26, 115, 232, 0.3);
}
}
.van-hairline--bottom:after {
border-bottom-width: 0;
}
</style>