ai-clone/frontend-ai/unpackage/dist/build/mp-weixin/pages/video-call/video-call.js
2026-03-06 18:05:51 +08:00

2 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";const e=require("../../common/vendor.js"),i=require("../../config/api.js"),o={data:()=>({API_BASE:i.API_BASE,idleVideoUrl:"",talkingVideoUrl:"",currentVideoUrl:"",isVideoLoading:!0,videoContext:null,hasPlayedInitial:!1,callerName:"对方",voiceId:"",selectedDialect:"",selectedLanguageHint:"",selectedLanguageHintLabel:"",languageHintOptions:["中文(zh)","英文(en)","法语(fr)","德语(de)","日语(ja)","韩语(ko)","俄语(ru)"],dialectOptions:["广东话","东北话","甘肃话","贵州话","河南话","湖北话","江西话","闽南话","宁夏话","山西话","陕西话","山东话","上海话","四川话","天津话","云南话"],callDuration:"00:00",callStartTime:null,durationTimer:null,isIdle:!0,isSpeaking:!1,isRecording:!1,isProcessing:!1,processingText:"处理中...",messages:[],systemPrompt:"你是一位温暖的对话者,用亲切的语气与对方交流。",recorderManager:null,audioFilePath:"",audioContext:null,vadEnabled:!0,vadVoiceThreshold:.004,vadSilenceMs:2500,vadMaxDurationMs:6e4,vadSpeaking:!1,vadSilenceStart:null,vadMaxTimer:null,recordingStartTime:null,vadLastSoundTime:null,vadLastFrameTime:null,vadWatchTimer:null,autoListen:!0,autoLoopTimer:null,scrollTop:0}),onLoad(e){console.log("[VideoCall] 页面参数:",e),this.idleVideoUrl=decodeURIComponent(e.idleVideo||""),this.talkingVideoUrl=decodeURIComponent(e.talkingVideo||this.idleVideoUrl),this.voiceId=e.voiceId||"",this.callerName=decodeURIComponent(e.callerName||"对方"),console.log("[VideoCall] 待机视频URL:",this.idleVideoUrl),console.log("[VideoCall] 说话视频URL:",this.talkingVideoUrl),console.log("[VideoCall] 音色ID:",this.voiceId),this.downloadAndConvertVideo(),this.startCallTimer(),this.initRecorder(),this.initAudioContext(),this.initVideoContext(),this.startAutoLoop()},onUnload(){this.durationTimer&&clearInterval(this.durationTimer),this.vadMaxTimer&&(clearTimeout(this.vadMaxTimer),this.vadMaxTimer=null),this.vadWatchTimer&&(clearInterval(this.vadWatchTimer),this.vadWatchTimer=null),this.autoLoopTimer&&(clearInterval(this.autoLoopTimer),this.autoLoopTimer=null),this.audioContext&&this.audioContext.destroy()},methods:{async downloadAndConvertVideo(){if(!this.idleVideoUrl)return void e.index.showToast({title:"视频URL无效",icon:"none"});console.log("[VideoCall] ========== 视频加载 =========="),console.log("[VideoCall] 原始URL:",this.idleVideoUrl);const o=new URL(i.API_BASE).hostname,t=this.idleVideoUrl.includes("/static/videos/")||this.idleVideoUrl.includes(o);return this.idleVideoUrl.includes("grsai.com")||this.idleVideoUrl.includes("file49")?(console.log("[VideoCall] 检测到外部视频链接直接使用URL播放"),this.currentVideoUrl=this.idleVideoUrl,this.talkingVideoUrl=this.idleVideoUrl,void console.log("[VideoCall] ========== 视频准备完成 ==========")):t?(console.log("[VideoCall] 检测到本地服务器视频直接使用URL播放"),this.currentVideoUrl=this.idleVideoUrl,this.talkingVideoUrl=this.idleVideoUrl,void console.log("[VideoCall] ========== 视频准备完成 ==========")):(console.log("[VideoCall] 本地服务器视频,开始下载..."),e.index.showLoading({title:"视频加载中...",mask:!0}),void e.index.downloadFile({url:this.idleVideoUrl,success:i=>{console.log("[VideoCall] 下载响应状态码:",i.statusCode),200===i.statusCode?(console.log("[VideoCall] ✅ 视频下载成功"),console.log("[VideoCall] 临时路径:",i.tempFilePath),console.log("[VideoCall] 非APP环境使用临时路径"),this.currentVideoUrl=i.tempFilePath,this.idleVideoUrl=i.tempFilePath,this.talkingVideoUrl=i.tempFilePath,e.index.hideLoading()):(console.error("[VideoCall] ❌ 视频下载失败,状态码:",i.statusCode),this.handleDownloadError())},fail:e=>{console.error("[VideoCall] ❌ 视频下载失败:",e),console.error("[VideoCall] 错误详情:",JSON.stringify(e)),this.handleDownloadError()}}))},handleDownloadError(){e.index.hideLoading(),console.log("[VideoCall] 下载失败直接使用原URL播放"),this.currentVideoUrl=this.idleVideoUrl,e.index.showToast({title:"将直接播放网络视频",icon:"none",duration:2e3})},initRecorder(){this.recorderManager=e.index.getRecorderManager(),this.recorderManager.onStart((()=>{console.log("[VideoCall] 开始录音")})),this.recorderManager.onStop((e=>{console.log("[VideoCall] 录音完成:",e.tempFilePath),this.resetVADState();if((e.duration||Date.now()-(this.recordingStartTime||Date.now()))<400)return console.warn("[VideoCall] 录音过短,忽略本次"),void(this.isProcessing=!1);this.audioFilePath=e.tempFilePath,this.processConversation()})),this.recorderManager.onFrameRecorded&&this.recorderManager.onFrameRecorded((e=>{this.vadEnabled&&!this.isProcessing&&this.handleVADFrame(e.frameBuffer)})),this.recorderManager.onError((i=>{console.error("[VideoCall] 录音失败:",i),console.error("[VideoCall] 错误详情:",JSON.stringify(i)),this.isRecording=!1,this.resetVADState();let o="录音失败";i.errMsg&&(o=i.errMsg.includes("permission")?"录音权限被拒绝,请在设置中允许录音权限":i.errMsg.includes("busy")?"录音设备忙,请稍后再试":`录音失败: ${i.errMsg}`),e.index.showModal({title:"录音失败",content:o+"\n\n请检查:\n1. 是否授予录音权限\n2. 麦克风是否被其他应用占用\n3. 设备是否支持录音",showCancel:!1,confirmText:"知道了"})}))},initVideoContext(){this.$nextTick((()=>{this.videoContext=e.index.createVideoContext("videoPlayer",this),console.log("[VideoCall] 视频控制器初始化完成")}))},initAudioContext(){this.audioContext=e.index.createInnerAudioContext(),this.audioContext.onPlay((()=>{console.log("[VideoCall] 开始播放AI回复")})),this.audioContext.onEnded((()=>{console.log("[VideoCall] AI回复播放完成"),this.isSpeaking=!1,this.isIdle=!0})),this.audioContext.onError((e=>{console.error("[VideoCall] 音频播放失败:",e),this.isSpeaking=!1,this.isIdle=!0}))},startCallTimer(){this.callStartTime=Date.now(),this.durationTimer=setInterval((()=>{const e=Math.floor((Date.now()-this.callStartTime)/1e3),i=Math.floor(e/60).toString().padStart(2,"0"),o=(e%60).toString().padStart(2,"0");this.callDuration=`${i}:${o}`}),1e3)},startTalking(){this.isProcessing||(this.resetVADState(),this.recordingStartTime=Date.now(),this.vadLastSoundTime=this.recordingStartTime,this.vadLastFrameTime=this.recordingStartTime,this.isRecording=!0,this.vadMaxTimer=setTimeout((()=>{this.stopTalkingFromVAD("max_duration")}),this.vadMaxDurationMs),this.vadWatchTimer=setInterval((()=>{if(!this.isRecording)return;const e=Date.now(),i=e-(this.recordingStartTime||e);e-(this.vadLastFrameTime||e)>12e3?this.stopTalkingFromVAD("no_frame"):i>1500&&this.vadSpeaking&&e-(this.vadLastSoundTime||e)>this.vadSilenceMs&&this.stopTalkingFromVAD("silence")}),300),this.recorderManager.start({format:"mp3",sampleRate:16e3,numberOfChannels:1,encodeBitRate:48e3,frameSize:32}),e.index.showToast({title:"正在聆听(自动识别说话)",icon:"none",duration:1e4}))},stopTalking(){this.stopTalkingFromVAD("manual")},stopTalkingFromVAD(e="vad"){this.isRecording=!1,this.recorderManager.stop(),this.isProcessing=!0,this.processingText="识别中...",this.resetVADState(),console.log("[VideoCall] 停止录音,原因:",e)},resetVADState(){this.vadSpeaking=!1,this.vadSilenceStart=null,this.vadLastSoundTime=null,this.vadLastFrameTime=null,this.vadMaxTimer&&(clearTimeout(this.vadMaxTimer),this.vadMaxTimer=null),this.vadWatchTimer&&(clearInterval(this.vadWatchTimer),this.vadWatchTimer=null)},onDialectChange(e){this.selectedDialect=this.dialectOptions[e.detail.value]||""},onLanguageHintChange(e){const i=this.languageHintOptions[e.detail.value]||"";this.selectedLanguageHintLabel=i;const o=i.match(/\(([^)]+)\)/);this.selectedLanguageHint=o&&o[1]?o[1]:""},handleVADFrame(e){if(!e||0===e.byteLength)return;this.vadLastFrameTime=Date.now();if(this.calculateRms(e)>this.vadVoiceThreshold)return this.vadSpeaking=!0,this.vadSilenceStart=null,void(this.vadLastSoundTime=Date.now());this.vadSpeaking&&(this.vadSilenceStart?Date.now()-this.vadSilenceStart>this.vadSilenceMs&&this.stopTalkingFromVAD("silence"):this.vadSilenceStart=Date.now())},calculateRms(e){const i=new DataView(e),o=i.byteLength/2;if(0===o)return 0;let t=0;for(let s=0;s<o;s++){const e=i.getInt16(2*s,!0);t+=e*e}return Math.sqrt(t/o)/32768},startAutoLoop(){this.autoListen&&(this.autoLoopTimer&&clearInterval(this.autoLoopTimer),this.autoLoopTimer=setInterval((()=>{this.autoListen&&(this.isRecording||this.isProcessing||this.startTalking())}),2e3))},async processConversation(){try{this.processingText="正在识别...",console.log("[VideoCall] 开始对话请求"),console.log("[VideoCall] 音频文件:",this.audioFilePath),console.log("[VideoCall] 音色ID:",this.voiceId);const i=e.index.getStorageSync("userId")||"",o=e.index.getStorageSync("token")||"";e.index.uploadFile({url:`${this.API_BASE}/api/conversation/talk`,filePath:this.audioFilePath,name:"audio",header:{"X-User-Id":i,Authorization:o?`Bearer ${o}`:""},formData:(()=>{const e=this.voiceId||"",i=(()=>{const i=(e||"").trim();if(!i)return"CLONE";return["Cherry","Kai","Mochi","Bunny","Jada","Dylan","Li","Marcus","Roy","Peter","Sunny","Eric","Rocky","Kiki"].includes(i)||i.startsWith("BV")||i.endsWith("_streaming")||i.endsWith("_offline")||i.endsWith("_bigtts")?"OFFICIAL":"CLONE"})(),o={voiceId:e,voiceType:i};return this.voiceId&&this.voiceId.startsWith("cosyvoice-v3-plus-")&&this.selectedDialect&&(o.dialect=this.selectedDialect),this.voiceId&&this.voiceId.startsWith("cosyvoice-v3-plus-")&&this.selectedLanguageHint&&(o.languageHints=this.selectedLanguageHint),o})(),success:i=>{console.log("[VideoCall] 对话响应状态码:",i.statusCode),console.log("[VideoCall] 对话响应数据:",i.data);try{const o="string"==typeof i.data?JSON.parse(i.data):i.data;o.success?(console.log("[VideoCall] 对话成功"),console.log("[VideoCall] 识别文本:",o.recognizedText),console.log("[VideoCall] AI回复:",o.aiResponse),console.log("[VideoCall] 音频文件:",o.audioFile),this.addMessage("user",o.recognizedText),this.addMessage("ai",o.aiResponse),this.playAIResponse(o.audioFile)):(console.error("[VideoCall] 对话失败:",o.message),e.index.showToast({title:o.message||"对话失败",icon:"none"}),this.isProcessing=!1)}catch(o){console.error("[VideoCall] JSON解析失败:",o),console.error("[VideoCall] 原始响应:",i.data),e.index.showToast({title:"服务器响应格式错误",icon:"none"}),this.isProcessing=!1}},fail:i=>{console.error("[VideoCall] 对话失败:",i),e.index.showToast({title:"对话失败",icon:"none"}),this.isProcessing=!1}})}catch(i){console.error("[VideoCall] 对话失败:",i),e.index.showToast({title:"对话失败: "+i.message,icon:"none"}),this.isProcessing=!1}},playAIResponse(e){this.processingText="正在回复...",this.isIdle=!1,this.isSpeaking=!0,this.audioContext.src=`${this.API_BASE}/api/conversation/audio/${e}`,this.audioContext.play(),this.isProcessing=!1},addMessage(e,i){const o=new Date,t=`${o.getHours()}:${o.getMinutes().toString().padStart(2,"0")}`;this.messages.push({role:e,content:i,time:t}),this.$nextTick((()=>{this.scrollTop=999999}))},onVideoPlay(){console.log("[VideoCall] 视频开始播放"),this.isVideoLoading=!1},onVideoPause(){console.log("[VideoCall] 视频暂停")},onVideoEnded(){console.log("[VideoCall] 视频播放结束"),!this.isIdle&&this.isSpeaking&&console.log("[VideoCall] 视频循环播放")},onVideoLoaded(e){console.log("[VideoCall] 视频元数据加载成功:",e),this.isVideoLoading=!1},onVideoCanPlay(){console.log("[VideoCall] 视频可以播放"),this.isVideoLoading=!1},onVideoWaiting(){console.log("[VideoCall] 视频缓冲中..."),this.isVideoLoading=!0},onVideoTimeUpdate(e){!this.hasPlayedInitial&&e.detail&&e.detail.currentTime>=1&&(console.log("[VideoCall] 视频已播放1秒暂停等待AI回复"),this.hasPlayedInitial=!0,this.videoContext&&this.videoContext.pause())},onVideoError(i){console.error("[VideoCall] 视频加载失败:",i),console.error("[VideoCall] 视频URL:",this.currentVideoUrl),console.error("[VideoCall] 错误详情:",JSON.stringify(i)),this.isVideoLoading=!1;const o=i.detail&&i.detail.errMsg?i.detail.errMsg:"未知错误",t=i.detail&&i.detail.errCode?i.detail.errCode:"N/A";e.index.showModal({title:"视频加载失败",content:`错误代码: ${t}\n错误信息: ${o}\n\n视频URL:\n${this.currentVideoUrl}\n\n请检查:\n1. URL是否有效\n2. 网络连接\n3. 视频格式是否支持\n4. HTTP权限配置`,showCancel:!1,confirmText:"知道了"})},hangUp(){e.index.showModal({title:"结束通话",content:"确定要结束通话吗?",success:i=>{i.confirm&&(this.isRecording&&this.recorderManager.stop(),this.audioContext&&this.audioContext.stop(),e.index.navigateBack())}})}}};const t=e._export_sfc(o,[["render",function(i,o,t,s,a,n){return e.e({a:e.t(a.callerName),b:e.t(a.callDuration),c:a.currentVideoUrl},a.currentVideoUrl?{d:a.currentVideoUrl,e:e.o(((...e)=>n.onVideoPlay&&n.onVideoPlay(...e))),f:e.o(((...e)=>n.onVideoPause&&n.onVideoPause(...e))),g:e.o(((...e)=>n.onVideoEnded&&n.onVideoEnded(...e))),h:e.o(((...e)=>n.onVideoError&&n.onVideoError(...e))),i:e.o(((...e)=>n.onVideoLoaded&&n.onVideoLoaded(...e))),j:e.o(((...e)=>n.onVideoWaiting&&n.onVideoWaiting(...e))),k:e.o(((...e)=>n.onVideoCanPlay&&n.onVideoCanPlay(...e))),l:e.o(((...e)=>n.onVideoTimeUpdate&&n.onVideoTimeUpdate(...e)))}:{},{m:a.isVideoLoading},(a.isVideoLoading,{}),{n:a.isSpeaking},(a.isSpeaking,{}),{o:e.f(a.messages,((i,o,t)=>({a:e.t("user"===i.role?"👤":"🤖"),b:e.t(i.content),c:e.t(i.time),d:o,e:e.n("user"===i.role?"user-message":"ai-message")}))),p:0===a.messages.length},(a.messages.length,{}),{q:a.scrollTop,r:a.voiceId&&a.voiceId.startsWith("cosyvoice-v3-plus-")},a.voiceId&&a.voiceId.startsWith("cosyvoice-v3-plus-")?{s:e.t(a.selectedDialect||"请选择方言(可选)"),t:a.dialectOptions,v:e.o(((...e)=>n.onDialectChange&&n.onDialectChange(...e)))}:{},{w:a.voiceId&&a.voiceId.startsWith("cosyvoice-v3-plus-")},a.voiceId&&a.voiceId.startsWith("cosyvoice-v3-plus-")?{x:e.t(a.selectedLanguageHintLabel||"请选择语言(可选)"),y:a.languageHintOptions,z:e.o(((...e)=>n.onLanguageHintChange&&n.onLanguageHintChange(...e)))}:{},{A:!a.isRecording&&!a.isProcessing},a.isRecording||a.isProcessing?{}:{B:e.o(((...e)=>n.startTalking&&n.startTalking(...e)))},{C:a.isRecording},a.isRecording?{D:e.o(((...e)=>n.stopTalking&&n.stopTalking(...e)))}:{},{E:a.isProcessing},a.isProcessing?{F:e.t(a.processingText)}:{},{G:e.o(((...e)=>n.hangUp&&n.hangUp(...e)))})}],["__scopeId","data-v-0f1482ae"]]);wx.createPage(t);