flex meeting

red5作服务器的玩家移动信息同步
http://www.cnblogs.com/winnerlan/archive/2008/06/20/1227096.html

基于web的IM简介
http://akalius.垃圾广告.com/blog/192727

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/118838/viewspace-600688/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/118838/viewspace-600688/

使用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>
07-10
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值