"use strict"; const common_vendor = require("../../common/vendor.js"); const common_config = require("../../common/config.js"); const _sfc_main = { name: "IdentityVerify", data() { return { followUpQuestion: "", // 追问问题 followUpAudioUrl: "", // 追问音频URL audioContext: null, // 音频上下文 followUpQuestions: [], // 追问问题列表 isWaitingForAnswer: false, // 添加新的状态来控制是否在等待用户回答 currentFollowUpIndex: -1, // 当前追问问题索引 showSubtitleText: false, // 是否显示字幕 subtitleText: "", // 字幕文本 parentQuestion: "", // 父问题文本 isFollowUpQuestion: false, // 是否是追问问题 loading: false, responses: [], processedResponses: [], assistantResponse: "", audioTranscript: "", videoPlaying: false, showDebugInfo: false, // 设置为true可以显示调试信息 videoUrl: "", // 修改为空字符串,将通过API获取 showReplayButton: false, cameraStream: null, // 存储摄像头流 cameraError: null, // 存储摄像头错误信息 useMiniProgramCameraComponent: false, // 添加小程序相机组件标志 cameraContext: null, // 添加相机上下文 currentSubtitle: "", currentTranslation: "", // 添加翻译文本 subtitles: [], // 修改为空数组,将通过API获取 // 移除硬编码的字幕数组 // secondVideoSubtitles: [...], // thirdVideoSubtitles: [...], // 等等... showAnswerButton: false, // 控制答题按钮显示 currentVideoIndex: 0, // 当前播放的视频索引 videoList: [], // 修改为空数组,将通过API获取 // 其他属性保持不变... questions: [], // 添加新属性存储API返回的问题数据 introVideoUrl: (() => { try { const configStr = common_vendor.index.getStorageSync("configData"); if (configStr && configStr.trim()) { return JSON.parse(configStr).digital_human_opening_video_url || "https://data.qicai321.com/minlong/ee4d9cce-c3d5-4350-8c6e-684283827897.mp4"; } return "https://data.qicai321.com/minlong/ee4d9cce-c3d5-4350-8c6e-684283827897.mp4"; } catch (error) { console.warn("解析配置数据失败:", error); return {}; } })(), //'https://data.qicai321.com/minlong/ee4d9cce-c3d5-4350-8c6e-684283827897.mp4', // 保留介绍视频 isRecording: false, recordingTimer: null, showStopRecordingButton: false, mediaRecorder: null, recordedChunks: [], recorder: null, lastUploadedVideoUrl: "", showStartRecordingButton: false, showRetryButton: false, // 控制重试按钮显示 lastVideoToRetry: null, // 存储上次失败的视频URL,用于重试 recordingStartTime: null, // 录制开始时间 recordingTimerCount: 0, // 录制计时器计数 recordingTimeDisplay: "00:00", // 格式化的录制时间显示 // 添加上传队列相关数据 uploadQueue: [], // 存储待上传的视频 isUploading: false, // 标记是否正在上传 uploadProgress: {}, // 存储每个视频的上传进度 uploadStatus: {}, // 存储每个视频的上传状态 showUploadStatus: false, // 是否显示上传状态指示器 uploadStatusText: "", // 上传状态文本 mediaRecorderTimeout: null, // 用于存储MediaRecorder的超时机制 maxRecordingTime: 300, // 最大录制时间(秒)- 从60秒改为300秒(5分钟) remainingTime: 300, // 剩余录制时间(秒)- 从60秒改为300秒 countdownValue: 10, // 倒计时数值 showCountdown: false, // 是否显示倒计时蒙层 countdownTimer: null, showGif: false, // 控制是否显示GIF gifUrl: "", // GIF图片的URL globalSocketTask: null, // 添加全局 WebSocket 连接对象 lowScoreVideoUrl: "https://data.qicai321.com/minlong/latentsync/0530e7f5-1957-422d-8f34-ba4a92608081_result.mp4", // 低分提示视频URL showRerecordButton: false, // 控制重新录制按钮显示 isPlayingLowScoreVideo: false, // 标记是否正在播放低分提示视频 lowScoreVideoSubtitles: [ { startTime: 0, endTime: 10, // 假设视频长度为10秒,可根据实际情况调整 text: "我未听清楚您在说什么,请您再说一遍!" } ], // 用于存储低分提示视频的字幕 retryCount: 0, // 添加重试次数计数器 maxRetryAttempts: 2, // 最大重试次数限制 needPlayLowScoreVideo: false, // 是否需要播放低分视频 finalRecordingDuration: 0, // 最终录制时长 historyTime: 0, // 添加历史时间记录 followUpQuestions: [], // 存储追问问题数据 currentParentQuestionId: null, // 当前父问题ID hasPlayedFollowUp: {}, // 记录已播放的追问,格式: {questionId: true} isFollowUpQuestion: false, // 标记当前是否正在播放追问问题 currentFollowUpQuestion: null, // 添加当前追问问题的完整信息 lastQuestionWasFollowUp: false, lastFollowUpQuestionId: null, lastUpdateTime: Date.now(), // 添加最后更新时间戳 subtitleMap: {}, // 用于存储字幕和翻译的映射 progressPercent: 0, // 录制进度百分比 progressColor: "#05dc8b", // 进度条颜色 progressBgColor: "rgba(0, 0, 0,0.3)", // 进度条背景色 parentQuestion: "", // 添加父问题存储 screenCaptureCount: 0, // 添加截屏次数记录 personDetectionSocket: null, // WebSocket对象 personDetectionInterval: null, // 定时器对象 showCameraWarning: false, // 添加新的数据属性 showPageWarning: false, // 添加新的数据属性 followUpQuestion: "", // 追问问题 followUpAudioUrl: "", // 追问音频URL audioContext: null, // 音频上下文 followUpQuestions: [], // 追问问题列表 currentFollowUpIndex: -1, // 当前追问问题索引 showSubtitleText: false, // 是否显示字幕 subtitleText: "", // 字幕文本 isAudioPlaying: false, isWaitingForAnswer: false, // 添加新的状态来控制是否在等待用户回答 currentRecordingDuration: 0, // 添加当前录制时长 parentJobPositionQuestionId: null, // 添加父问题的job_position_question_id isFollowUpMode: false, // 是否处于追问模式 mainQuestionIndex: 0 // 当前主问题的索引 }; }, mounted() { this.fetchQuestions(); this.checkAudioPermission(); this.initCamera(); this.checkIOSCameraRecordPermission(); this.checkAndFixRenderingIssues(); setTimeout(() => { if (this.cameraStream && !this.useMiniProgramCameraComponent) { this.testAudioInput(); } }, 3e3); this.historyTime = 0; common_vendor.index.setKeepScreenOn({ keepScreenOn: true }); common_vendor.index.onUserCaptureScreen(() => { console.log("User captured screen"); this.screenCaptureCount++; if (this.screenCaptureCount === 1) { common_vendor.index.showModal({ title: "Warning", content: "Screen capture detected. If you capture the screen again, your interview will be invalid.", showCancel: false, confirmText: "OK" }); } else if (this.screenCaptureCount >= 2) { common_vendor.index.showModal({ title: "Interview Invalid", content: "Your interview has been invalidated due to multiple screen captures. Please contact the recruiter for assistance.", showCancel: false, confirmText: "OK", success: () => { this.invalidateInterview(); } }); } }); const systemInfo = common_vendor.index.getSystemInfoSync(); const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-"); if (isMiniProgram) { this.initPersonDetectionWebSocket(); } }, beforeDestroy() { common_vendor.index.offUserCaptureScreen(); this.cleanupPersonDetectionWebSocket(); }, methods: { // 处理音频播放完成 handleAudioEnd() { console.log("音频播放完成"); this.isWaitingForAnswer = true; this.showStartRecordingButton = true; setTimeout(() => { this.startCountdown(); }, 100); }, // 开始倒计时 startCountdown() { this.showCountdown = true; this.countdownValue = 5; this.countdownTimer = setInterval(() => { this.countdownValue--; if (this.countdownValue <= 0) { this.clearCountdown(); this.showStartRecordingButton = false; this.startRecordingAnswer(); } }, 1e3); }, // 清除倒计时 clearCountdown() { if (this.countdownTimer) { clearInterval(this.countdownTimer); this.countdownTimer = null; } this.showCountdown = false; }, // 处理追问问题 async handleFollowUpQuestion(questionData) { console.log("处理追问:", questionData); this.enterFollowUpMode(); console.log("处理追问问题数据:", questionData); if (!questionData || !questionData.follow_up_voice_url || !questionData.follow_up_question) { console.error("追问数据不完整:", questionData); common_vendor.index.showToast({ title: "获取追问数据失败", icon: "none" }); return; } this.parentQuestion = questionData.original_question || ""; let audioUrl = ""; if (questionData.follow_up_voice_url.direct_url) { audioUrl = questionData.follow_up_voice_url.direct_url; } else if (questionData.follow_up_voice_url.file_url) { audioUrl = questionData.follow_up_voice_url.file_url; } if (!audioUrl.startsWith("http")) { audioUrl = common_config.apiBaseUrl + audioUrl; } console.log("处理后的音频URL:", audioUrl); const followUpQuestionObj = { id: Date.now(), question: questionData.follow_up_question.trim(), // 去除可能的空白字符 audioUrl, originalQuestion: questionData.original_question, sessionId: questionData.session_id }; console.log("创建的追问问题对象:", followUpQuestionObj); this.followUpQuestions.push(followUpQuestionObj); this.currentFollowUpIndex = this.followUpQuestions.length - 1; this.followUpQuestion = followUpQuestionObj.question; this.followUpAudioUrl = followUpQuestionObj.audioUrl; this.isFollowUpQuestion = true; this.currentFollowUpQuestion = followUpQuestionObj; this.showSubtitle(followUpQuestionObj.question, true); try { await this.playFollowUpAudio(); console.log("音频播放完成,准备录制回答"); this.prepareToAnswer(true, followUpQuestionObj); } catch (error) { console.error("音频播放失败:", error); this.prepareToAnswer(true, followUpQuestionObj); } }, // 准备回答问题 prepareToAnswer(isFollowUp = false, questionData = null) { console.log("准备回答问题:", { isFollowUp, questionData }); this.isRecording = false; this.recordedTime = 0; this.showStopRecordingButton = false; if (isFollowUp && questionData) { this.currentFollowUpQuestion = questionData; this.isFollowUpQuestion = true; } this.isWaitingForAnswer = true; this.handleAudioEnd(); }, // 调用面试互动接口 async callInterviewInteraction(questionId) { const userInfo = JSON.parse(common_vendor.index.getStorageSync("userInfo")); const appId = common_vendor.index.getStorageSync("appId"); const positionConfigId = JSON.parse(common_vendor.index.getStorageSync("configData")).id; try { console.log("开始调用面试互动接口", { questionId, appId }); const res = await common_vendor.index.request({ url: `${common_config.apiBaseUrl}/api/voice_interview_interaction/`, method: "POST", data: { tenant_id: userInfo.tenant_id || 1, question_id: questionId, position_config_id: positionConfigId, application_id: appId }, header: { "content-type": "application/json" } }); console.log("面试互动接口返回数据:", res); if (res.data.success) { await this.handleFollowUpQuestion(res.data); } else { console.error("面试互动接口返回错误:", res.data); common_vendor.index.showToast({ title: "获取追问失败", icon: "none" }); } } catch (error) { console.error("调用面试互动接口失败:", error); } }, // 播放追问音频 async playFollowUpAudio() { return new Promise((resolve, reject) => { console.log("开始播放追问音频, URL:", this.followUpAudioUrl); if (!this.followUpAudioUrl) { console.error("没有音频URL"); common_vendor.index.showToast({ title: "音频URL无效", icon: "none" }); reject(new Error("没有音频URL")); return; } common_vendor.index.showLoading({ title: "加载音频中..." }); this.stopAndDestroyAudio(); try { const innerAudioContext = common_vendor.index.createInnerAudioContext(); this.audioContext = innerAudioContext; innerAudioContext.autoplay = true; innerAudioContext.obeyMuteSwitch = false; innerAudioContext.volume = 1; innerAudioContext.onCanplay(() => { console.log("音频可以播放"); common_vendor.index.hideLoading(); this.isAudioPlaying = true; common_vendor.index.showToast({ title: "正在播放追问", icon: "none", duration: 2e3 }); }); innerAudioContext.onEnded(() => { console.log("追问音频播放完成"); this.isAudioPlaying = false; resolve(); setTimeout(() => { this.stopAndDestroyAudio(); this.prepareToAnswer(true, this.currentFollowUpQuestion); }, 100); }); innerAudioContext.onError((res) => { console.error("音频播放错误:", res); this.isAudioPlaying = false; common_vendor.index.hideLoading(); common_vendor.index.showToast({ title: "音频播放失败", icon: "none" }); reject(res); this.stopAndDestroyAudio(); }); console.log("设置音频源:", this.followUpAudioUrl); innerAudioContext.src = this.followUpAudioUrl; } catch (error) { console.error("创建或播放音频失败:", error); this.isAudioPlaying = false; common_vendor.index.hideLoading(); common_vendor.index.showToast({ title: "音频播放失败", icon: "none" }); this.stopAndDestroyAudio(); reject(error); } }); }, // 停止并销毁音频 stopAndDestroyAudio() { if (!this.audioContext) { return; } const ctx = this.audioContext; this.audioContext = null; try { if (this.isAudioPlaying) { ctx.stop(); } } catch (error) { console.error("停止音频播放失败:", error); } try { ctx.destroy(); } catch (error) { console.error("销毁音频实例失败:", error); } this.isAudioPlaying = false; }, // 绑定音频事件 bindAudioEvents(audioContext, resolve, reject) { audioContext.onCanplay(() => { console.log("音频可以播放"); common_vendor.index.showToast({ title: "正在播放追问", icon: "none", duration: 2e3 }); }); audioContext.onTimeUpdate(() => { console.log("音频播放进度:", audioContext.currentTime); }); audioContext.onEnded(() => { console.log("追问音频播放完成"); this.cleanupAudioContext(); resolve(); }); audioContext.onError((res) => { console.error("音频播放错误:", res); common_vendor.index.showToast({ title: "音频播放失败", icon: "none" }); this.cleanupAudioContext(); reject(res); }); }, // 修改准备回答方法 prepareToAnswer(isFollowUp = false, questionData = null) { this.isRecording = false; this.recordedTime = 0; this.showStopRecordingButton = false; if (isFollowUp && questionData) { this.currentFollowUpQuestion = questionData; this.isFollowUpQuestion = true; } this.isWaitingForAnswer = true; this.handleAudioEnd(); }, // 初始化相机 async initCamera() { const systemInfo = common_vendor.index.getSystemInfoSync(); const isMiniProgram = systemInfo.uniPlatform === "mp-weixin" || systemInfo.uniPlatform === "mp-alipay" || systemInfo.uniPlatform === "mp-baidu"; if (isMiniProgram) { this.useMiniProgramCameraComponent = true; this.cameraContext = common_vendor.index.createCameraContext(); common_vendor.index.getSetting({ success: (res) => { if (!res.authSetting["scope.record"]) { common_vendor.index.authorize({ scope: "scope.record", success: () => { console.log("录音权限已获取"); }, fail: (err) => { console.error("录音权限获取失败:", err); this.showPermissionDialog("录音"); } }); } if (!res.authSetting["scope.camera"]) { common_vendor.index.authorize({ scope: "scope.camera", success: () => { console.log("相机权限已获取"); }, fail: (err) => { console.error("相机权限获取失败:", err); this.showPermissionDialog("相机"); } }); } const systemInfo2 = common_vendor.index.getSystemInfoSync(); if (systemInfo2.platform === "ios") { if (!res.authSetting["scope.camera"] || !res.authSetting["scope.record"]) { console.log("iOS需要同时获取相机和录音权限"); } } } }); } else { try { const constraints = { audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }, video: { width: { ideal: 640, max: 1280 }, // 控制视频宽度 height: { ideal: 480, max: 720 }, // 控制视频高度 frameRate: { ideal: 15, max: 24 }, // 控制帧率 facingMode: "user" } }; const stream = await navigator.mediaDevices.getUserMedia(constraints); this.cameraStream = stream; const audioTracks = stream.getAudioTracks(); console.log("音频轨道数量:", audioTracks.length); if (audioTracks.length > 0) { console.log("音频轨道已获取:", audioTracks[0].label); audioTracks[0].enabled = true; } else { console.warn("未检测到音频轨道,尝试单独获取音频"); this.tryGetAudioOnly(); } const videoElement = this.$refs.userCameraVideo; if (videoElement) { videoElement.srcObject = stream; videoElement.muted = true; } } catch (error) { console.error("获取摄像头失败:", error); this.cameraError = error.message || "无法访问摄像头"; common_vendor.index.showToast({ title: "无法访问摄像头,请检查权限设置", icon: "none" }); } } }, // 停止用户摄像头 stopUserCamera() { if (this.cameraStream) { this.cameraStream.getTracks().forEach((track) => { track.stop(); }); this.cameraStream = null; } }, async fetchData() { this.loading = true; this.assistantResponse = ""; this.audioTranscript = ""; this.processedResponses = []; try { const requestTask = common_vendor.index.request({ url: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", method: "POST", header: { "Content-Type": "application/json", "Authorization": "Bearer sk-9e1ec73a7d97493b8613c63f06b6110c" }, data: { "model": "qwen-omni-turbo", "messages": [ { "role": "user", "content": [ { "type": "input_audio", "input_audio": { "data": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250211/tixcef/cherry.wav", "format": "wav" } }, { "type": "text", "text": "这段音频在说什么" } ] } ], "stream": true, "stream_options": { "include_usage": true }, "modalities": ["text", "audio"], "audio": { "voice": "Cherry", "format": "wav" } }, success: (res) => { console.log("请求成功,响应数据:", res.data); if (typeof res.data === "string" && res.data.includes("data: {")) { const chunks = res.data.split("data: ").filter((chunk) => chunk.trim() !== ""); chunks.forEach((chunk) => { this.handleStreamResponse(chunk); }); } else { this.handleStreamResponse(res.data); } this.playDigitalHumanVideo(); }, fail: (err) => { console.error("请求失败:", err); }, complete: () => { this.loading = false; } }); } catch (error) { console.error("获取数据失败:", error); this.loading = false; } }, handleStreamResponse(data) { if (typeof data === "string") { if (data === "[DONE]") return; try { const cleanData = data.trim(); if (cleanData.startsWith("{") && cleanData.endsWith("}")) { const jsonData = JSON.parse(cleanData); this.processStreamChunk(jsonData); } } catch (e) { console.error("解析JSON失败:", e, "原始数据:", data); } } else { this.processStreamChunk(data); } }, processStreamChunk(chunk) { if (chunk.choices && chunk.choices.length > 0) { const choice = chunk.choices[0]; if (choice.delta && choice.delta.content) { this.assistantResponse += choice.delta.content; } if (choice.delta && choice.delta.audio && choice.delta.audio.transcript) { this.audioTranscript += choice.delta.audio.transcript; } if (choice.delta) { const result = {}; if (choice.delta.role) { result.role = choice.delta.role; } if (choice.delta.audio && choice.delta.audio.transcript) { result.transcript = choice.delta.audio.transcript; } if (Object.keys(result).length > 0) { this.processedResponses.push(result); } } } }, processResponseData() { this.processedResponses = this.responses.map((item) => { const result = {}; if (item.delta && item.delta.role) { result.role = item.delta.role; } if (item.delta && item.delta.audio && item.delta.audio.transcript) { result.transcript = item.delta.audio.transcript; } return result; }).filter((item) => Object.keys(item).length > 0); }, // 播放数字人视频 playDigitalHumanVideo() { if (this.videoList.length === 0) { this.fetchQuestions(); return; } this.videoUrl = this.videoList[this.currentVideoIndex]; this.videoPlaying = true; this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); setTimeout(() => { if (this.videoPlaying && this.$refs.videoPlayer) { console.log("视频应该正在播放"); } else { console.log("视频可能未成功播放,尝试替代方案"); this.tryAlternativeVideoPath(); } }, 1e3); } else { console.error("无法创建视频上下文"); this.tryAlternativeVideoPath(); } }); }, // 修改 tryAlternativeVideoPath 方法 tryAlternativeVideoPath() { console.log("尝试使用替代路径"); const alternativePaths = [ "./static/demo.mp4", "../static/demo.mp4", "static/demo.mp4", "/static/demo.mp4", // 添加绝对路径 `${window.location.origin}/static/demo.mp4` ]; const currentPathIndex = alternativePaths.indexOf(this.videoUrl); const nextPathIndex = (currentPathIndex + 1) % alternativePaths.length; this.videoUrl = alternativePaths[nextPathIndex]; console.log("尝试新路径:", this.videoUrl); this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.stop(); videoContext.play(); setTimeout(() => { if (nextPathIndex === alternativePaths.length - 1 && !this.videoPlaying) { console.log("所有路径均失败,尝试使用uni.getVideoInfo检查视频"); this.checkVideoWithAPI(); } }, 1e3); } }); }, // 添加新方法:使用uni API检查视频 checkVideoWithAPI() { common_vendor.index.getVideoInfo({ src: "/static/demo.mp4", success: (res) => { console.log("视频信息获取成功:", res); this.videoUrl = "/static/demo.mp4"; this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); } }); }, fail: (err) => { console.error("视频信息获取失败:", err); this.fallbackToLocalVideo(); } }); }, // 添加新方法:回退到本地视频 fallbackToLocalVideo() { console.log("尝试使用本地视频资源"); const platform = common_vendor.index.getSystemInfoSync().platform; if (platform === "android" || platform === "ios") { this.videoUrl = platform === "android" ? "android.resource://package_name/raw/demo" : "file:///assets/demo.mp4"; this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); } }); } else { this.videoPlaying = false; common_vendor.index.showToast({ title: "视频加载失败,显示静态图片", icon: "none" }); } }, // 修改 handleVideoError 方法 handleVideoError(e) { console.error("视频加载错误:", e); if (e && e.detail) { console.error("详细错误信息:", e.detail); } common_vendor.index.getFileInfo({ filePath: this.videoUrl.startsWith("/") ? this.videoUrl.substring(1) : this.videoUrl, success: (res) => { console.log("文件存在,大小:", res.size); this.tryDifferentFormat(); }, fail: (err) => { console.error("文件不存在或无法访问:", err); this.tryAlternativeVideoPath(); } }); common_vendor.index.showToast({ title: "视频加载失败,请检查文件是否存在", icon: "none", duration: 2e3 }); }, // 添加新方法:尝试不同格式 tryDifferentFormat() { console.log("尝试不同的视频格式"); const formats = [ { ext: "mp4", mime: "video/mp4" }, { ext: "webm", mime: "video/webm" }, { ext: "ogg", mime: "video/ogg" }, { ext: "mov", mime: "video/quicktime" } ]; const currentPath = this.videoUrl; const basePath = currentPath.substring(0, currentPath.lastIndexOf(".")) || "/static/demo"; let nextFormat = formats.find((f) => !currentPath.endsWith(f.ext)); if (nextFormat) { this.videoUrl = `${basePath}.${nextFormat.ext}`; console.log("尝试新格式:", this.videoUrl); this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.stop(); videoContext.play(); } }); } else { this.useBuiltInResource(); } }, // 添加新方法:使用内置资源 useBuiltInResource() { console.log("尝试使用内置资源"); const platform = common_vendor.index.getSystemInfoSync().platform; if (platform === "windows") { process.env.UNI_INPUT_DIR || ""; this.videoUrl = `./static/demo.mp4`; console.log("Windows平台尝试路径:", this.videoUrl); } else if (platform === "android" || platform === "ios") { this.useNativeVideo(); } else { const baseUrl = window.location.origin; this.videoUrl = `${baseUrl}/static/demo.mp4`; console.log("Web平台尝试URL:", this.videoUrl); } this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); } }); }, // 添加新方法:使用原生视频能力 useNativeVideo() { console.log("尝试使用原生视频能力"); common_vendor.index.chooseVideo({ sourceType: ["album"], success: (res) => { this.videoUrl = res.tempFilePath; this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); } }); }, fail: () => { this.videoPlaying = false; common_vendor.index.showToast({ title: "无法加载视频,显示静态图片", icon: "none" }); } }); }, // 处理视频结束事件 handleVideoEnded() { console.log("视频播放结束事件触发"); const videoElement = this.$refs.videoPlayer; if (videoElement) { const currentTime = videoElement.currentTime || 0; const duration = videoElement.duration || 0; if (duration > 0 && duration - currentTime > 0.5) { console.log("检测到可能的误触发结束事件,继续播放", currentTime, duration); setTimeout(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { if (duration - currentTime > 2) { videoContext.seek(this.historyTime || 0); } else { videoContext.play(); } } }, 100); return; } console.log("视频确认结束,当前时间:", currentTime, "总时长:", duration); } this.videoPlaying = false; if (this.isPlayingLowScoreVideo) { console.log("低分提示视频播放完成"); if (this.retryCount < this.maxRetryAttempts) { console.log(`显示重新录制按钮 (重试次数: ${this.retryCount + 1}/${this.maxRetryAttempts})`); this.showRerecordButton = true; if (this.isFollowUpQuestion && this.currentFollowUpQuestion) { this.currentSubtitle = this.currentFollowUpQuestion.digital_human_video_subtitle || this.currentFollowUpQuestion.question; } } else { console.log("已达到最大重试次数,继续下一题"); this.retryCount = 0; this.proceedToNextQuestion(); } this.isPlayingLowScoreVideo = false; return; } if (this.isFollowUpQuestion) { console.log("追问问题视频播放完成"); this.showStartRecordingButton = true; this.startCountdown(); return; } if (this.currentVideoIndex >= 1) { this.showStartRecordingButton = true; this.startCountdown(); } else { this.showAnswerButton = true; } if (this.isFollowUpQuestion) { this.isFollowUpQuestion = false; this.currentFollowUpQuestion = null; } }, // 添加新方法:开始倒计时 startCountdown() { this.clearCountdown(); this.showCountdown = true; this.countdownValue = 10; this.countdownTimer = setInterval(() => { if (!this.showStartRecordingButton) { this.clearCountdown(); return; } this.countdownValue--; if (this.countdownValue <= 0) { this.clearCountdown(); this.showStartRecordingButton = false; this.startRecordingAnswer(); } }, 1e3); }, // 添加新方法:清除倒计时 clearCountdown() { if (this.countdownTimer) { clearInterval(this.countdownTimer); this.countdownTimer = null; } this.showCountdown = false; }, // 修改 handleStartRecordingClick 方法,使其可以终止倒计时 handleStartRecordingClick() { this.showStartRecordingButton = false; this.clearCountdown(); this.isWaitingForAnswer = false; this.startRecordingAnswer(); }, // 修改 stopRecordingAnswer 方法,添加录制时长检查 stopRecordingAnswer() { this.clearCountdown(); this.isWaitingForAnswer = false; const recordingDuration = this.getRecordingDuration(); const minimumDuration = 3; const lowScoreDuration = 7; this.currentRecordingDuration = recordingDuration; if (recordingDuration < minimumDuration) { common_vendor.index.showToast({ title: "录制时间过短,请至少录制3秒", icon: "none", duration: 2e3 }); return; } this.completeRecordingStop(); if (recordingDuration < lowScoreDuration) { console.log(`录制时间 ${recordingDuration} 秒,少于 ${lowScoreDuration} 秒,将播放低分提示视频`); this.needPlayLowScoreVideo = true; } else { this.needPlayLowScoreVideo = false; } }, // 添加新方法:开始录制用户回答 startRecordingAnswer() { console.log("开始录制用户回答"); this.isRecording = true; this.recordingStartTime = Date.now(); this.recordingTimerCount = 0; this.remainingTime = this.maxRecordingTime; this.recordingTimer = setInterval(() => { this.recordingTimerCount++; this.remainingTime = Math.max(0, this.maxRecordingTime - this.recordingTimerCount); this.recordingTimeDisplay = this.formatTime(this.recordingTimerCount); this.progressPercent = this.recordingTimerCount / this.maxRecordingTime * 100; if (this.recordingTimerCount >= this.maxRecordingTime) { console.log("已达到最大录制时间(5分钟),自动停止录制"); this.stopRecordingAnswer(); } }, 1e3); const systemInfo = common_vendor.index.getSystemInfoSync(); const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-"); if (isMiniProgram) { this.startMiniProgramRecording(); } else { this.startBrowserRecording(); } this.showStopRecordingButton = true; }, // 添加一个新方法:重置相机组件 resetCamera() { console.log("重置相机组件"); this.useMiniProgramCameraComponent = false; if (this.cameraContext) { this.cameraContext = null; } setTimeout(() => { this.useMiniProgramCameraComponent = true; setTimeout(() => { this.cameraContext = common_vendor.index.createCameraContext(); console.log("相机组件已重置"); }, 500); }, 500); }, // 修改 startMiniProgramRecording 方法 startMiniProgramRecording() { console.log("开始小程序录制方法"); const systemInfo = common_vendor.index.getSystemInfoSync(); const isIOS = systemInfo.platform === "ios"; if (isIOS) { this.resetCamera(); setTimeout(() => { this.actualStartRecording(isIOS); }, 1e3); } else { this.actualStartRecording(isIOS); } }, // 添加新方法:实际开始录制 actualStartRecording(isIOS) { if (!this.cameraContext) { this.cameraContext = common_vendor.index.createCameraContext(); console.log("创建新的相机上下文"); } common_vendor.index.getSetting({ success: (res) => { const hasRecordAuth = res.authSetting["scope.record"]; const hasCameraAuth = res.authSetting["scope.camera"]; if (!hasRecordAuth || !hasCameraAuth) { console.warn("缺少必要权限,请求权限"); this.requestMiniProgramPermissions(); return; } if (isIOS) { console.log("iOS: 检查相机状态"); const options = { timeout: 3e5, // 300秒超时 (5分钟) quality: "low", // 降低质量 compressed: true, success: () => { console.log("iOS录制开始成功"); }, fail: (err) => { console.error("iOS录制失败:", err); this.useAlternativeRecordingMethod(); } }; try { console.log("尝试开始录制"); this.recorder = this.cameraContext.startRecord(options); } catch (e) { console.error("开始录制异常:", e); this.useAlternativeRecordingMethod(); } } else { const options = { timeout: 3e5, // 300秒超时 (5分钟) quality: "medium", compressed: true, success: () => { console.log("Android录制开始成功"); }, fail: (err) => { console.error("Android录制失败:", err); common_vendor.index.showToast({ title: "录制失败,请检查相机权限", icon: "none" }); this.proceedToNextQuestion(); } }; this.recorder = this.cameraContext.startRecord(options); } } }); }, // 添加新方法:使用替代录制方法 useAlternativeRecordingMethod() { console.log("使用替代录制方法"); common_vendor.index.showActionSheet({ itemList: ["使用相册中的视频", "跳过此问题"], success: (res) => { if (res.tapIndex === 0) { common_vendor.index.chooseVideo({ sourceType: ["album"], maxDuration: 300, // 从60秒改为300秒 camera: "front", success: (res2) => { console.log("选择视频成功:", res2.tempFilePath); this.isRecording = false; this.showStopRecordingButton = false; this.uploadRecordedVideo(res2.tempFilePath); }, fail: () => { console.log("用户取消选择视频"); this.proceedToNextQuestion(); } }); } else { console.log("用户选择跳过问题"); this.proceedToNextQuestion(); } }, fail: () => { console.log("操作取消"); this.proceedToNextQuestion(); } }); }, // 添加新方法:请求小程序权限 requestMiniProgramPermissions() { common_vendor.index.authorize({ scope: "scope.record", success: () => { console.log("录音权限已获取"); common_vendor.index.authorize({ scope: "scope.camera", success: () => { console.log("相机权限已获取"); this.startMiniProgramRecording(); }, fail: (err) => { console.error("相机权限获取失败:", err); this.showPermissionDialog("相机"); } }); }, fail: (err) => { console.error("录音权限获取失败:", err); this.showPermissionDialog("录音"); } }); }, // 修改浏览器环境下的录制方法 startBrowserRecording() { if (!this.cameraStream) { console.error("没有可用的摄像头流"); common_vendor.index.showToast({ title: "录制失败,摄像头未就绪", icon: "none" }); this.proceedToNextQuestion(); return; } try { const hasAudio = this.cameraStream.getAudioTracks().length > 0; if (!hasAudio) { console.warn("警告:媒体流中没有音频轨道,尝试重新获取带音频的媒体流"); navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }, video: true }).then((newStream) => { const audioTracks = newStream.getAudioTracks(); if (audioTracks.length > 0) { console.log("成功获取音频轨道:", audioTracks[0].label); const videoTrack = this.cameraStream.getVideoTracks()[0]; const audioTrack = newStream.getAudioTracks()[0]; const combinedStream = new MediaStream(); if (videoTrack) combinedStream.addTrack(videoTrack); if (audioTrack) combinedStream.addTrack(audioTrack); this.cameraStream = combinedStream; const videoElement = this.$refs.userCameraVideo; if (videoElement) { videoElement.srcObject = combinedStream; videoElement.muted = true; } this.setupMediaRecorder(combinedStream); } else { console.warn("仍然无法获取音频轨道"); this.setupMediaRecorder(this.cameraStream); } }).catch((err) => { console.error("获取音频失败:", err); this.setupMediaRecorder(this.cameraStream); }); } else { console.log("检测到音频轨道,直接使用"); this.setupMediaRecorder(this.cameraStream); } } catch (error) { console.error("浏览器录制失败:", error); common_vendor.index.showToast({ title: "录制失败,浏览器可能不支持此功能", icon: "none" }); this.proceedToNextQuestion(); } }, // 修改 setupMediaRecorder 方法 setupMediaRecorder(stream) { const videoTracks = stream.getVideoTracks(); const audioTracks = stream.getAudioTracks(); console.log("设置MediaRecorder - 视频轨道:", videoTracks.length, "音频轨道:", audioTracks.length); let mimeType = ""; const supportedTypes = [ "video/webm;codecs=vp9,opus", "video/webm;codecs=vp8,opus", "video/webm;codecs=h264,opus", "video/mp4;codecs=h264,aac", "video/webm", "video/mp4" ]; for (const type of supportedTypes) { if (MediaRecorder.isTypeSupported(type)) { mimeType = type; console.log("使用支持的MIME类型:", mimeType); break; } } const options = { mimeType: mimeType || "", audioBitsPerSecond: 64e3, // 降低音频比特率 videoBitsPerSecond: 1e6 // 降低视频比特率到1Mbps }; try { this.mediaRecorder = new MediaRecorder(stream, options); console.log("MediaRecorder创建成功,使用选项:", options); } catch (e) { console.warn("使用指定选项创建MediaRecorder失败,尝试使用默认选项"); this.mediaRecorder = new MediaRecorder(stream); } this.recordedChunks = []; this.mediaRecorder.ondataavailable = (event) => { if (event.data && event.data.size > 0) { this.recordedChunks.push(event.data); console.log(`收到数据块: ${event.data.size} 字节`); } }; this.mediaRecorder.onstop = async () => { console.log("MediaRecorder停止,数据块数量:", this.recordedChunks.length); if (this.recordedChunks.length === 0) { console.error("没有录制到数据"); common_vendor.index.showToast({ title: "录制失败,未捕获到数据", icon: "none" }); this.proceedToNextQuestion(); return; } const mimeType2 = this.mediaRecorder.mimeType || "video/webm"; const blob = new Blob(this.recordedChunks, { type: mimeType2 }); console.log("创建Blob,原始大小:", blob.size, "类型:", mimeType2); common_vendor.index.showLoading({ title: "正在处理视频...", mask: true }); try { const compressedBlob = await this.compressVideo(blob); const fileName = `answer_${this.currentVideoIndex}_${Date.now()}.webm`; const file = new File([compressedBlob], fileName, { type: mimeType2 }); common_vendor.index.hideLoading(); this.uploadRecordedVideo(file); } catch (error) { console.error("视频处理失败:", error); common_vendor.index.hideLoading(); const fileName = `answer_${this.currentVideoIndex}_${Date.now()}.webm`; const file = new File([blob], fileName, { type: mimeType2 }); this.uploadRecordedVideo(file); } }; this.mediaRecorder.onerror = (event) => { console.error("MediaRecorder错误:", event.error); }; try { this.mediaRecorder.start(1e3); console.log("MediaRecorder开始录制"); this.mediaRecorderTimeout = setTimeout(() => { if (this.mediaRecorder && this.mediaRecorder.state === "recording") { console.log("MediaRecorder备份超时机制触发,停止录制"); this.mediaRecorder.stop(); } }, 3e5); } catch (e) { console.error("开始录制失败:", e); } }, // 添加新方法:停止录制用户回答 stopRecordingAnswer() { console.log("停止录制用户回答"); if (this.countdownTimer) { clearInterval(this.countdownTimer); this.countdownTimer = null; this.showCountdown = false; } this.isWaitingForAnswer = false; const recordingDuration = this.getRecordingDuration(); const minimumDuration = 3; const lowScoreDuration = 7; if (recordingDuration < minimumDuration) { common_vendor.index.showToast({ title: "录制时间过短,请至少录制3秒", icon: "none", duration: 2e3 }); return; } this.completeRecordingStop(); if (recordingDuration < lowScoreDuration) { console.log(`录制时间 ${recordingDuration} 秒,少于 ${lowScoreDuration} 秒,将播放低分提示视频`); this.needPlayLowScoreVideo = true; } else { this.needPlayLowScoreVideo = false; } }, // 添加新方法:获取录制时长 getRecordingDuration() { if (this.recordingStartTime) { return (Date.now() - this.recordingStartTime) / 1e3; } if (this.mediaRecorder && this.$refs.userCameraVideo) { return this.$refs.userCameraVideo.currentTime || 0; } if (this.recordingTimerCount) { return this.recordingTimerCount; } return 0; }, // 添加新方法:重置录制 resetRecording() { if (this.recordingTimer) { clearTimeout(this.recordingTimer); } this.recordingStartTime = Date.now(); this.recordingTimerCount = 0; if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") { this.mediaRecorder.stop(); this.recordedChunks = []; setTimeout(() => { this.startBrowserRecording(); }, 500); } else if (this.cameraContext) { this.cameraContext.stopRecord({ success: () => { console.log("相机录制已停止"); }, fail: (err) => { console.error("停止相机录制失败:", err); } }); } common_vendor.index.showToast({ title: "请重新开始回答", icon: "none", duration: 2e3 }); }, // 修改 completeRecordingStop 方法,确保正确处理追问问题ID completeRecordingStop() { console.log("完成录制停止"); if (this.recordingTimer) { clearInterval(this.recordingTimer); this.recordingTimer = null; } this.finalRecordingDuration = this.recordingTimerCount; this.showStopRecordingButton = false; this.isRecording = false; const systemInfo = common_vendor.index.getSystemInfoSync(); const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-"); if (isMiniProgram) { this.stopMiniProgramRecording(); } else { this.stopBrowserRecording(); } if (this.isFollowUpQuestion && this.currentFollowUpQuestion) { console.log("当前是追问问题,记录追问问题ID:", this.currentFollowUpQuestion.id); this.lastQuestionWasFollowUp = true; this.lastFollowUpQuestionId = this.currentFollowUpQuestion.id; } else { this.lastQuestionWasFollowUp = false; this.lastFollowUpQuestionId = null; } }, // 修改 stopMiniProgramRecording 方法,确保正确传递追问问题ID stopMiniProgramRecording() { console.log("停止小程序录制"); if (!this.cameraContext) { console.error("相机上下文不存在"); this.proceedToNextQuestion(); return; } this.cameraContext.stopRecord({ success: async (res) => { console.log("小程序录制停止成功:", res); const videoPath = res.tempVideoPath; if (videoPath) { await this.uploadRecordedVideo(videoPath); } else { console.error("未获取到录制视频路径"); this.proceedToNextQuestion(); } }, fail: (err) => { console.error("小程序录制停止失败:", err); this.proceedToNextQuestion(); } }); }, // 添加新方法:停止浏览器录制 stopBrowserRecording() { if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") { this.mediaRecorder.stop(); console.log("浏览器录制停止成功"); } else { console.error("MediaRecorder不存在或已经停止"); this.proceedToNextQuestion(); } }, // 修改 uploadRecordedVideo 方法,添加记录父问题ID的逻辑 uploadRecordedVideo(fileOrPath) { console.log("准备上传视频:", typeof fileOrPath === "string" ? fileOrPath : fileOrPath.name); console.log("当前问题ID:", this.currentParentQuestionId); console.log("当前问题类型:", this.isFollowUpQuestion); console.log("当前追问问题:", this.currentFollowUpQuestion); let questionId; let isFollowUpQuestionUpload = false; let questionText = ""; let questionForm = 0; if (this.currentFollowUpQuestion && (this.currentFollowUpQuestion.question_form === 5 || this.isFollowUpQuestion)) { questionId = this.currentFollowUpQuestion.id; isFollowUpQuestionUpload = true; questionText = this.currentFollowUpQuestion.question || ""; questionForm = 5; console.log("正在上传追问问题的回答:", { questionId, questionText, questionForm, isFollowUp: true }); } else { const currentQuestion = this.getCurrentQuestionByIndex(this.currentVideoIndex); if (currentQuestion && currentQuestion.id) { questionId = currentQuestion.id; questionText = currentQuestion.question || ""; console.log("正在上传常规问题的回答:", { questionId, questionText, questionForm: 0 }); } else { questionId = this.getDefaultQuestionId(this.currentVideoIndex); console.log("使用默认问题ID:", questionId); } } const uploadTask = { id: Date.now().toString(), file: fileOrPath, questionId, isFollowUp: isFollowUpQuestionUpload, questionText, questionForm, attempts: 0, maxAttempts: 3, parentQuestionId: this.currentParentQuestionId, videoDuration: this.currentRecordingDuration || 0 // 添加视频时长 }; if (!isFollowUpQuestionUpload) { this.currentParentQuestionId = questionId; } this.uploadQueue.push(uploadTask); this.uploadProgress[uploadTask.id] = 0; this.uploadStatus[uploadTask.id] = "pending"; common_vendor.index.showToast({ title: "正在上传回答...", icon: "loading", duration: 1500 }); this.updateUploadStatusText(); return new Promise((resolve) => { const checkUploadStatus = () => { if (!this.isUploading && this.uploadQueue.length === 0) { this.handlePostUploadActions(uploadTask); resolve(); } else { setTimeout(checkUploadStatus, 500); } }; if (!this.isUploading) { this.processUploadQueue(); } checkUploadStatus(); }); }, // 添加新方法:处理上传后的逻辑 handlePostUploadActions(task) { if (this.needPlayLowScoreVideo && this.retryCount < 1) { this.playLowScoreVideo(); this.needPlayLowScoreVideo = false; } else { if (task.isFollowUp) { console.log("追问问题回答完成,进入下一个问题"); this.retryCount = 0; this.isFollowUpQuestion = false; this.currentFollowUpQuestion = null; this.proceedToNextQuestion(); } else { this.currentParentQuestionId = task.questionId; console.log("检查常规问题的追问,父问题ID:", this.currentParentQuestionId); this.checkAndPlayFollowUpQuestion(this.currentParentQuestionId); } } }, // 修改 processUploadQueue 方法 processUploadQueue() { if (this.uploadQueue.length === 0) { this.isUploading = false; this.showUploadStatus = false; return; } this.isUploading = true; this.showUploadStatus = true; const task = this.uploadQueue[0]; this.uploadStatus[task.id] = "uploading"; this.updateUploadStatusText(); task.attempts++; if (typeof task.file === "string") { this.uploadFileWithUni(task); } else { this.uploadFileWithXHR(task); } }, // 添加新方法:使用XMLHttpRequest上传文件 uploadFileWithXHR(task) { const userInfo = common_vendor.index.getStorageSync("userInfo"); const openid = userInfo ? JSON.parse(userInfo).openid || "" : ""; const tenant_id = JSON.parse(common_vendor.index.getStorageSync("userInfo")).tenant_id || "1"; const formData = new FormData(); formData.append("file", task.file); formData.append("openid", openid); formData.append("tenant_id", tenant_id); formData.append("application_id", common_vendor.index.getStorageSync("appId")); formData.append("question_id", task.questionId); formData.append("video_duration", task.videoDuration); formData.append("has_audio", "true"); const xhr = new XMLHttpRequest(); xhr.open("POST", `${common_config.apiBaseUrl}/api/upload/`, true); xhr.timeout = 12e4; xhr.upload.onprogress = (event) => { if (event.lengthComputable) { const percentComplete = Math.round(event.loaded / event.total * 100); this.uploadProgress[task.id] = percentComplete; this.updateUploadStatusText(); } }; xhr.onload = () => { if (xhr.status === 200) { try { const res = JSON.parse(xhr.responseText); console.log("上传响应:", res); if (res.code === 2e3) { const videoUrl = res.data.url || res.data.photoUrl || ""; if (videoUrl) { this.uploadStatus[task.id] = "success"; this.updateUploadStatusText(); this.submitVideoToInterview(videoUrl, task); } else { this.handleUploadFailure(task, "视频URL获取失败"); } } else { this.handleUploadFailure(task, res.msg || "上传失败"); } } catch (e) { this.handleUploadFailure(task, "解析响应失败"); } } else { this.handleUploadFailure(task, "HTTP状态: " + xhr.status); } }; xhr.onerror = () => { this.handleUploadFailure(task, "网络错误"); }; xhr.ontimeout = () => { this.handleUploadFailure(task, "上传超时"); }; xhr.send(formData); }, // 添加新方法:使用uni.uploadFile上传文件 uploadFileWithUni(task) { const userInfo = common_vendor.index.getStorageSync("userInfo"); const openid = userInfo ? JSON.parse(userInfo).openid || "" : ""; const tenant_id = JSON.parse(common_vendor.index.getStorageSync("userInfo")).tenant_id || "1"; const uploadTask = common_vendor.index.uploadFile({ url: `${common_config.apiBaseUrl}/api/upload/`, filePath: task.file, name: "file", formData: { openid, tenant_id, application_id: common_vendor.index.getStorageSync("appId"), question_id: task.questionId, video_duration: 0, has_audio: "true" }, success: (uploadRes) => { try { const res = JSON.parse(uploadRes.data); console.log("上传响应:", res); if (res.code === 2e3) { const videoUrl = res.data.permanent_link || res.data.url || ""; if (videoUrl) { this.uploadStatus[task.id] = "success"; this.updateUploadStatusText(); this.submitVideoToInterview(videoUrl, task); } else { this.handleUploadFailure(task, "视频URL获取失败"); } } else { this.handleUploadFailure(task, res.msg || "上传失败"); } } catch (e) { this.handleUploadFailure(task, "解析响应失败"); } }, fail: (err) => { this.handleUploadFailure(task, err.errMsg || "上传失败"); } }); uploadTask.onProgressUpdate((res) => { this.uploadProgress[task.id] = res.progress; this.updateUploadStatusText(); }); }, // 添加新方法:处理上传失败 handleUploadFailure(task, errorMsg) { console.error("上传失败:", errorMsg); this.uploadStatus[task.id] = "failed"; this.updateUploadStatusText(); if (task.attempts < task.maxAttempts) { console.log(`将在5秒后重试上传,当前尝试次数: ${task.attempts}/${task.maxAttempts}`); setTimeout(() => { this.uploadProgress[task.id] = 0; if (typeof task.file !== "string") { this.uploadFileWithXHR(task); } else { this.uploadFileWithUni(task); } }, 5e3); } else { console.log("超过最大重试次数,放弃上传"); common_vendor.index.showToast({ title: "视频上传失败,请稍后重试", icon: "none", duration: 2e3 }); this.uploadQueue.shift(); this.processUploadQueue(); } }, // 修改 submitVideoToInterview 方法 submitVideoToInterview(videoUrl, task) { const requestData = { application_id: common_vendor.index.getStorageSync("appId"), question_id: task.questionId, video_url: videoUrl, tenant_id: JSON.parse(common_vendor.index.getStorageSync("userInfo")).tenant_id || "1", is_follow_up: task.isFollowUp ? 1 : 0, question_form: task.questionForm, question_text: task.questionText }; console.log("提交视频到面试接口", requestData); if (task.isFollowUp) { const followUpRequestData = { application_id: common_vendor.index.getStorageSync("appId"), tenant_id: JSON.parse(common_vendor.index.getStorageSync("userInfo")).tenant_id || "1", video_url: videoUrl, original_question_id: this.parentJobPositionQuestionId, // 使用保存的job_position_question_id follow_up_question: task.questionText, video_duration: task.videoDuration, openid: JSON.parse(common_vendor.index.getStorageSync("userInfo")).openid || "" }; console.log("准备提交追问视频:", followUpRequestData); common_vendor.index.request({ url: `${common_config.apiBaseUrl}/voice_interview/upload_follow_up_video/`, method: "POST", data: followUpRequestData, header: { "content-type": "application/x-www-form-urlencoded" }, success: async (res) => { try { if (res.data.code === 200 || res.data.code === 2e3) { console.log("追问视频提交成功"); this.isFollowUpMode = false; this.currentVideoIndex = this.mainQuestionIndex; common_vendor.index.showToast({ title: "回答已提交", icon: "success", duration: 1500 }); this.uploadQueue.shift(); this.processUploadQueue(); this.$nextTick(() => { if (this.currentVideoIndex < this.videoList.length) { this.videoUrl = this.videoList[this.currentVideoIndex]; this.videoPlaying = true; const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); } } }); } else { this.handleSubmitFailure(task, "提交失败: " + (res.data.msg || "未知错误")); } } catch (e) { this.handleSubmitFailure(task, "解析响应失败: " + e.message); } }, fail: (err) => { console.error("提交视频失败:", err); } }); } else { common_vendor.index.request({ url: `${common_config.apiBaseUrl}/api/job/upload_video`, method: "POST", data: requestData, header: { "content-type": "application/x-www-form-urlencoded" }, success: async (res) => { if (res.data.code === 200 || res.data.code === 2e3) { console.log("视频提交成功,准备调用面试互动接口"); try { this.parentJobPositionQuestionId = res.data.data.job_position_question_id; console.log("保存父问题ID:", this.parentJobPositionQuestionId); await this.callInterviewInteraction(res.data.data.job_position_question_id); console.log("面试互动接口调用成功"); } catch (error) { console.error("面试互动接口调用失败:", error); this.proceedToNextQuestion(); } common_vendor.index.showToast({ title: "回答已提交", icon: "success", duration: 1500 }); this.uploadQueue.shift(); this.processUploadQueue(); } else { this.handleSubmitFailure(task, "提交失败: " + (res.data.msg || "未知错误")); } }, fail: (err) => { console.error("提交视频失败:", err); this.handleSubmitFailure(task, "提交失败: " + err.errMsg); } }); } }, // 添加新方法:处理提交失败 handleSubmitFailure(task, errorMsg) { console.error("提交失败:", errorMsg); this.uploadStatus[task.id] = "failed"; this.updateUploadStatusText(); if (task.attempts < task.maxAttempts) { console.log(`将在5秒后重试提交,当前尝试次数: ${task.attempts}/${task.maxAttempts}`); setTimeout(() => { this.submitVideoToInterview(task.videoUrl, task); }, 5e3); } else { console.log("超过最大重试次数,放弃提交"); common_vendor.index.showToast({ title: "视频提交失败,请稍后重试", icon: "none", duration: 2e3 }); this.uploadQueue.shift(); this.processUploadQueue(); } }, // 添加新方法:更新上传状态文本 updateUploadStatusText() { if (this.uploadQueue.length === 0) { this.uploadStatusText = ""; return; } const currentTask = this.uploadQueue[0]; const progress = this.uploadProgress[currentTask.id] || 0; const status = this.uploadStatus[currentTask.id] || "pending"; let statusText = ""; switch (status) { case "pending": statusText = "等待上传"; break; case "uploading": statusText = `上传中 ${progress}%`; break; case "success": statusText = "上传成功,提交中..."; break; case "failed": statusText = `上传失败,${currentTask.attempts < currentTask.maxAttempts ? "即将重试" : "已放弃"}`; break; } const questionTypeText = currentTask.isFollowUp ? "追问" : "问题"; const questionShortText = currentTask.questionText ? currentTask.questionText.length > 10 ? currentTask.questionText.substring(0, 10) + "..." : currentTask.questionText : `${questionTypeText}${currentTask.questionId}`; this.uploadStatusText = `${questionTypeText}「${questionShortText}」:${statusText}`; if (this.uploadQueue.length > 1) { this.uploadStatusText += ` (${this.uploadQueue.length}个视频待处理)`; } }, // 修改 proceedToNextQuestion 方法,确保在切换视频时重置历史时间 proceedToNextQuestion() { console.log("继续下一个问题"); this.currentVideoIndex++; this.historyTime = 0; if (this.currentVideoIndex < this.videoList.length) { this.videoUrl = this.videoList[this.currentVideoIndex]; this.videoPlaying = true; this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); } }); } else { console.log("所有视频已播放完毕"); this.stopUserCamera(); this.clearCountdown(); const currentJobDetail = JSON.parse(common_vendor.index.getStorageSync("selectedJob")); console.log("当前职位ID:", currentJobDetail); const jobId = currentJobDetail ? currentJobDetail.id : null; console.log("当前职位ID:", jobId); setTimeout(() => { if (jobId === 9) { common_vendor.index.navigateTo({ url: "/pages/interview-question/interview-question", success: () => { console.log("成功跳转到interview-question页面"); }, fail: (err) => { console.error("跳转到interview-question页面失败:", err); common_vendor.index.redirectTo({ url: "/pages/interview-question/interview-question", fail: (redirectErr) => { console.error("重定向到interview-question页面也失败:", redirectErr); common_vendor.index.navigateBack({ delta: 1 }); } }); } }); } else { common_vendor.index.navigateTo({ url: "/pages/camera/camera", success: () => { console.log("成功跳转到camera页面"); }, fail: (err) => { console.error("跳转到camera页面失败:", err); common_vendor.index.redirectTo({ url: "/pages/camera/camera", fail: (redirectErr) => { console.error("重定向到camera页面也失败:", redirectErr); common_vendor.index.switchTab({ url: "/pages/camera/camera", fail: (switchErr) => { console.error("所有跳转方式都失败:", switchErr); common_vendor.index.navigateBack({ delta: 1 }); } }); } }); } }); } }, 200); } }, // 修改 handleAnswerButtonClick 方法,确保在切换视频时重置历史时间 handleAnswerButtonClick() { console.log("点击开始回答按钮"); this.showAnswerButton = false; this.currentVideoIndex++; this.historyTime = 0; if (this.currentVideoIndex < this.videoList.length) { this.videoUrl = this.videoList[this.currentVideoIndex]; this.videoPlaying = true; this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); } }); } }, // 处理相机错误 handleCameraError(e) { console.error("相机错误:", e); const systemInfo = common_vendor.index.getSystemInfoSync(); const isIOS = systemInfo.platform === "ios"; if (isIOS) { console.log("iOS相机错误,尝试重新初始化"); common_vendor.index.showToast({ title: "相机初始化中...", icon: "loading", duration: 2e3 }); this.resetCamera(); if (this.isRecording) { this.isRecording = false; this.showStopRecordingButton = false; setTimeout(() => { this.useAlternativeRecordingMethod(); }, 1e3); } } else { common_vendor.index.showToast({ title: "相机初始化失败,请检查权限设置", icon: "none" }); this.tryFallbackOptions(); } }, // 添加新方法:尝试备用选项 tryFallbackOptions() { const systemInfo = common_vendor.index.getSystemInfoSync(); if (systemInfo.uniPlatform === "mp-weixin" || systemInfo.uniPlatform === "mp-alipay") { this.useMiniProgramCamera(); } else { this.showStaticCameraPlaceholder(); } }, // 添加新方法:使用小程序相机API useMiniProgramCamera() { console.log("尝试使用小程序相机组件"); this.useMiniProgramCameraComponent = true; }, // 添加新方法:显示静态图像 showStaticCameraPlaceholder() { console.log("显示静态摄像头占位图"); const img = document.createElement("img"); img.src = "/static/images/camera-placeholder.png"; img.className = "static-camera-image"; img.style.width = "100%"; img.style.height = "100%"; img.style.objectFit = "cover"; const container = this.$refs.userCameraVideo.parentNode; container.appendChild(img); }, // 修改 handleTimeUpdate 方法 handleTimeUpdate(e) { const currentTime = e.detail.currentTime || e.target.currentTime; if (this.isRecording && this.recordingTimerCount) { this.recordingTimeDisplay = this.formatTime(this.recordingTimerCount); } if (this.isPlayingLowScoreVideo) { if (this.lowScoreVideoSubtitles && this.lowScoreVideoSubtitles.length > 0) { const subtitle = this.lowScoreVideoSubtitles[0]; this.currentSubtitle = subtitle.text; } return; } let currentSubtitles = null; if (this.isFollowUpQuestion && this.currentFollowUpQuestion) { const subtitleKey = `followUpSubtitles_${this.currentFollowUpQuestion.id}`; currentSubtitles = this[subtitleKey] || [{ startTime: 0, endTime: 30, text: this.currentFollowUpQuestion.digital_human_video_subtitle || this.currentFollowUpQuestion.question }]; } else { if (this.currentVideoIndex === 0) { currentSubtitles = this.subtitles; } else { const subtitleArrayName = `question${this.currentVideoIndex}Subtitles`; currentSubtitles = this[subtitleArrayName]; } } if (currentSubtitles && currentSubtitles.length > 0) { const subtitle = currentSubtitles.find( (sub) => currentTime >= sub.startTime && currentTime <= sub.endTime ) || currentSubtitles[0]; if (subtitle) { this.currentSubtitle = subtitle.text; } } this.historyTime = currentTime; this.lastUpdateTime = Date.now(); }, // Add a new method to handle the "Start Recording" button click handleStartRecordingClick() { this.showStartRecordingButton = false; this.clearCountdown(); this.isWaitingForAnswer = false; this.startRecordingAnswer(); }, // 修改 checkAudioPermission 方法,确保在录制前获取音频权限 checkAudioPermission() { const systemInfo = common_vendor.index.getSystemInfoSync(); const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-"); if (isMiniProgram) { common_vendor.index.getSetting({ success: (res) => { if (!res.authSetting["scope.record"]) { common_vendor.index.authorize({ scope: "scope.record", success: () => { console.log("录音权限已获取"); }, fail: (err) => { console.error("录音权限获取失败:", err); this.showPermissionDialog("录音"); } }); } if (!res.authSetting["scope.camera"]) { common_vendor.index.authorize({ scope: "scope.camera", success: () => { console.log("相机权限已获取"); }, fail: (err) => { console.error("相机权限获取失败:", err); this.showPermissionDialog("相机"); } }); } } }); } }, // 添加新方法:测试音频输入 testAudioInput() { if (!this.cameraStream) { console.warn("没有可用的媒体流,无法测试音频"); return; } const audioTracks = this.cameraStream.getAudioTracks(); if (audioTracks.length === 0) { console.warn("没有检测到音频轨道,尝试重新获取"); this.tryGetAudioOnly(); return; } console.log("音频轨道信息:", audioTracks[0].getSettings()); try { const AudioContext = window.AudioContext || window.webkitAudioContext; if (!AudioContext) { console.warn("浏览器不支持AudioContext"); return; } const audioContext = new AudioContext(); const analyser = audioContext.createAnalyser(); const microphone = audioContext.createMediaStreamSource(this.cameraStream); microphone.connect(analyser); analyser.fftSize = 256; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); let silenceCounter = 0; const checkAudio = () => { if (this.isRecording) return; analyser.getByteFrequencyData(dataArray); let sum = 0; for (let i = 0; i < bufferLength; i++) { sum += dataArray[i]; } const average = sum / bufferLength; if (average > 10) { console.log("检测到音频输入,音量:", average); silenceCounter = 0; } else { silenceCounter++; if (silenceCounter > 10) { console.warn("持续检测不到音频输入,可能麦克风未正常工作"); silenceCounter = 0; } } requestAnimationFrame(checkAudio); }; checkAudio(); } catch (e) { console.error("音频测试失败:", e); } }, // 添加新方法:尝试单独获取音频 tryGetAudioOnly() { navigator.mediaDevices.getUserMedia({ audio: true }).then((audioStream) => { if (this.cameraStream) { const videoTrack = this.cameraStream.getVideoTracks()[0]; const audioTrack = audioStream.getAudioTracks()[0]; const combinedStream = new MediaStream(); if (videoTrack) combinedStream.addTrack(videoTrack); if (audioTrack) combinedStream.addTrack(audioTrack); this.cameraStream = combinedStream; const videoElement = this.$refs.userCameraVideo; if (videoElement) { videoElement.srcObject = combinedStream; videoElement.muted = true; } console.log("成功合并音频和视频轨道"); } else { console.warn("没有视频流可合并"); } }).catch((err) => { console.error("单独获取音频失败:", err); }); }, // 添加新方法:显示权限对话框 showPermissionDialog(permissionType) { common_vendor.index.showModal({ title: "需要权限", content: `请允许使用${permissionType}权限,否则可能影响面试功能`, confirmText: "去设置", success: (res) => { if (res.confirm) { common_vendor.index.openSetting({ success: (settingRes) => { console.log("设置页面打开成功", settingRes); } }); } } }); }, // 添加重试上传方法 retryVideoUpload() { if (this.lastVideoToRetry) { this.showRetryButton = false; common_vendor.index.showLoading({ title: "正在重新提交...", mask: true }); this.submitVideoToInterview(this.lastVideoToRetry); } else { common_vendor.index.showToast({ title: "没有可重试的视频", icon: "none" }); } }, // 添加一个新方法用于压缩视频 async compressVideo(videoBlob) { if (videoBlob.size < 5 * 1024 * 1024) { return videoBlob; } console.log("开始压缩视频,原始大小:", videoBlob.size); const videoElement = document.createElement("video"); videoElement.muted = true; videoElement.autoplay = false; const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); videoElement.src = URL.createObjectURL(videoBlob); return new Promise((resolve) => { videoElement.onloadedmetadata = () => { const width = Math.floor(videoElement.videoWidth / 2); const height = Math.floor(videoElement.videoHeight / 2); canvas.width = width; canvas.height = height; const stream = canvas.captureStream(15); if (videoElement.captureStream) { const originalStream = videoElement.captureStream(); const audioTracks = originalStream.getAudioTracks(); if (audioTracks.length > 0) { stream.addTrack(audioTracks[0]); } } const options = { mimeType: "video/webm;codecs=vp8,opus", audioBitsPerSecond: 64e3, videoBitsPerSecond: 8e5 // 800kbps }; const mediaRecorder = new MediaRecorder(stream, options); const chunks = []; mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0) { chunks.push(e.data); } }; mediaRecorder.onstop = () => { const compressedBlob = new Blob(chunks, { type: "video/webm" }); console.log("视频压缩完成,压缩后大小:", compressedBlob.size); resolve(compressedBlob); }; videoElement.onplay = () => { mediaRecorder.start(10); const drawFrame = () => { if (videoElement.paused || videoElement.ended) { mediaRecorder.stop(); return; } ctx.drawImage(videoElement, 0, 0, width, height); requestAnimationFrame(drawFrame); }; drawFrame(); }; videoElement.play(); }; }); }, // 修改 checkIOSCameraRecordPermission 方法 checkIOSCameraRecordPermission() { const systemInfo = common_vendor.index.getSystemInfoSync(); if (systemInfo.platform !== "ios") return; common_vendor.index.getSetting({ success: (res) => { if (!res.authSetting["scope.camera"]) { common_vendor.index.authorize({ scope: "scope.camera", success: () => { console.log("iOS相机权限已获取"); }, fail: (err) => { console.error("iOS相机权限获取失败:", err); this.showPermissionDialog("相机"); } }); } if (!res.authSetting["scope.record"]) { common_vendor.index.authorize({ scope: "scope.record", success: () => { console.log("iOS录音权限已获取"); }, fail: (err) => { console.error("iOS录音权限获取失败:", err); this.showPermissionDialog("录音"); } }); } } }); }, // 添加新方法:检查并修复渲染问题 checkAndFixRenderingIssues() { try { if (typeof u !== "undefined" && u) { if (!u.currentQuestion) { console.log("修复: 创建缺失的currentQuestion对象"); u.currentQuestion = {}; } if (u.currentQuestion && typeof u.currentQuestion.isImportant === "undefined") { console.log("修复: 设置缺失的isImportant属性"); u.currentQuestion.isImportant = false; } } } catch (e) { console.log("防御性检查异常:", e); } }, // 添加格式化时间的辅助方法 formatTime(seconds) { if (!seconds && seconds !== 0) return "03:30"; const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`; }, // 添加获取问题数据的方法 fetchQuestions() { this.loading = true; const tenant_id = JSON.parse(common_vendor.index.getStorageSync("userInfo")).tenant_id || "1"; let position_id = 1; try { const selectedJob = JSON.parse(common_vendor.index.getStorageSync("selectedJob") || "{}"); if (selectedJob && selectedJob.id) { position_id = selectedJob.id; console.log("使用选择的职位ID获取问题:", position_id); } else { console.warn("未找到选择的职位信息,使用默认职位ID:", position_id); } } catch (e) { console.error("解析职位信息失败:", e); } const timestamp = Date.now(); const requestUrl = `${common_config.apiBaseUrl}/api/wechat/open_questions/?position_id=${position_id}&tenant_id=${tenant_id}&question_form=0&_t=${timestamp}`; console.log("完整的请求URL:", requestUrl); common_vendor.index.request({ url: requestUrl, method: "GET", success: (res) => { if (res.data && res.data.code === 2e3 && res.data.data && res.data.data.items) { console.log("获取问题数据成功:", res.data.data.items); this.questions = res.data.data.items; this.processQuestionData(); this.playIntroVideo(); } else { console.error("获取问题数据失败:", res.data); common_vendor.index.showToast({ title: "获取问题数据失败", icon: "none" }); } }, fail: (err) => { console.error("请求问题数据失败:", err); common_vendor.index.showToast({ title: "网络错误,请检查网络连接", icon: "none" }); }, complete: () => { this.loading = false; } }); }, // 处理问题数据,提取视频URL和字幕 processQuestionData() { this.videoList = []; this.videoList.push(this.introVideoUrl); this.subtitles = [ { startTime: 0, endTime: 5, text: "你好,我是本次面试的面试官,欢迎参加本公司的线上面试!" }, { startTime: 5, endTime: 13, text: "面试预计需要15分钟,请你提前安排在网络良好、光线亮度合适、且相对安静的环境参加这次面试" }, { startTime: 13, endTime: 20, text: "以免影响本次面试的结果。如果你在面试过程中遇到问题,请与我们的招聘人员联系。" } ]; const sortedQuestions = [...this.questions].sort((a, b) => a.sequence_number - b.sequence_number); const questionsToUse = sortedQuestions.slice(0, 5); questionsToUse.forEach((question, index) => { if (question.digital_human_video_url) { this.videoList.push(question.digital_human_video_url); const subtitleArray = [{ startTime: 0, endTime: 30, // 延长字幕显示时间到30秒 text: question.digital_human_video_subtitle || question.question }]; const videoIndex = this.videoList.length - 1; this[`question${videoIndex}Subtitles`] = subtitleArray; this.subtitleMap[question.digital_human_video_url] = subtitleArray; } }); if (this.videoList.length <= 1) ; else { console.log("处理后的视频列表:", this.videoList); console.log("处理后的字幕:", this.subtitles); } }, // 使用默认视频和字幕(作为备用) useDefaultVideosAndSubtitles() { console.log("使用默认视频和字幕"); this.subtitles = [ { startTime: 0, endTime: 5, text: "你好,我是本次面试的面试官,欢迎参加本公司的线上面试!" }, { startTime: 5, endTime: 13, text: "面试预计需要15分钟,请你提前安排在网络良好、光线亮度合适、且相对安静的环境参加这次面试" }, { startTime: 13, endTime: 20, text: "以免影响本次面试的结果。如果你在面试过程中遇到问题,请与我们的招聘人员联系。" } ]; this.question1Subtitles = [{ startTime: 0, endTime: 10, text: "请结合您的基本信息与过往履历进行简单的自我介绍,并讲一讲您有哪些优势胜任本岗位:" }]; this.question2Subtitles = [{ startTime: 0, endTime: 4, text: "在工作中,你如何确保个人防护装备的正确使用?" }]; this.question3Subtitles = [{ startTime: 0, endTime: 4, text: "描述一次你与团队合作改善生产流程的经历。" }]; this.question4Subtitles = [{ startTime: 0, endTime: 6, text: "你在团队合作中曾遇到过哪些挑战?如何解决团队内部的分歧?" }]; this.question5Subtitles = [{ startTime: 0, endTime: 5, text: "您已完成本次面试全部题目,请问您对于这个岗位还有什么想要了解的吗?" }]; }, // 播放介绍视频 playIntroVideo() { this.videoUrl = this.videoList[0]; this.videoPlaying = true; this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); } }); }, // 添加新方法:获取默认问题ID getDefaultQuestionId(videoIndex) { switch (videoIndex) { case 1: return 10; case 2: return 11; case 3: return 12; case 4: return 13; default: return 10; } }, // 添加新方法:根据索引获取当前问题 getCurrentQuestionByIndex(videoIndex) { const questionIndex = videoIndex - 1; const sortedQuestions = [...this.questions].sort((a, b) => a.sequence_number - b.sequence_number); if (questionIndex >= 0 && questionIndex < sortedQuestions.length) { return sortedQuestions[questionIndex]; } return null; }, // 添加新方法:重置录制状态,准备重新回答 resetForRerecording() { console.log("重置录制状态,准备重新回答"); this.isRecording = false; this.showStopRecordingButton = false; this.showStartRecordingButton = true; this.clearCountdown(); if (this.recordingTimer) { clearInterval(this.recordingTimer); this.recordingTimer = null; } this.recordingTimerCount = 0; this.recordingTimeDisplay = "00:00 "; this.remainingTime = this.maxRecordingTime; if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") { this.mediaRecorder.stop(); this.recordedChunks = []; } if (this.cameraContext) { this.cameraContext.stopRecord({ success: () => { console.log("相机录制已停止"); }, fail: (err) => { console.error("停止相机录制失败:", err); } }); } }, // 修改 playLowScoreVideo 方法,优化字幕显示 playLowScoreVideo() { console.log("播放低分提示视频"); this.isPlayingLowScoreVideo = true; this.showStartRecordingButton = false; this.showStopRecordingButton = false; this.showRerecordButton = false; this.videoUrl = this.lowScoreVideoUrl; this.videoPlaying = true; const originalSubtitle = this.currentSubtitle; this.currentSubtitle = ""; this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); setTimeout(() => { this.currentSubtitle = "我未听清楚您在说什么,请您再说一遍!"; this.lowScoreVideoSubtitles = [ { startTime: 0, endTime: 30, text: "我未听清楚您在说什么,请您再说一遍!", translation: "I didn't quite catch what you said, could you please repeat it?" } ]; setTimeout(() => { if (this.isFollowUpQuestion && this.currentFollowUpQuestion) { this.currentSubtitle = this.currentFollowUpQuestion.digital_human_video_subtitle || this.currentFollowUpQuestion.question; } else { this.currentSubtitle = originalSubtitle; } }, 3e3); }, 500); } }); }, // 添加新方法:处理重新录制按钮点击 handleRerecordButtonClick() { console.log("点击重新录制按钮"); this.retryCount++; console.log(`当前重试次数: ${this.retryCount}/${this.maxRetryAttempts}`); this.showRerecordButton = false; this.handleStartRecordingClick(); }, // 页面卸载时关闭WebSocket连接 onUnload() { console.log("页面卸载,关闭WebSocket连接"); this.cleanupPersonDetectionWebSocket(); this.stopUserCamera(); this.clearCountdown(); if (this.recordingTimer) { clearInterval(this.recordingTimer); this.recordingTimer = null; } if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") { this.mediaRecorder.stop(); } common_vendor.index.setKeepScreenOn({ keepScreenOn: false }); }, // 页面隐藏时关闭WebSocket连接 onHide() { console.log("页面隐藏,关闭WebSocket连接"); this.cleanupPersonDetectionWebSocket(); if (this.isRecording) { console.log("页面隐藏时正在录制,录制将继续"); common_vendor.index.showToast({ title: "请不要离开面试页面", icon: "none", duration: 2e3 }); } }, // 页面显示时重新初始化WebSocket连接 onShow() { console.log("identity-verify页面onShow"); console.log("当前本地存储中的职位信息:", common_vendor.index.getStorageSync("selectedJob")); this.fetchQuestions(); const systemInfo = common_vendor.index.getSystemInfoSync(); const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-"); if (isMiniProgram) { if (!this.personDetectionSocket) { console.log("重新初始化WebSocket连接"); this.initPersonDetectionWebSocket(); } else if (!this.personDetectionInterval) { console.log("重新启动人脸检测定时器"); this.startPersonDetectionInterval(); } } }, // 添加新方法:阻止视频控制 preventVideoControl(e) { e.preventDefault(); e.stopPropagation(); console.log("阻止视频控制操作"); return false; }, // 添加获取追问问题的方法 fetchFollowUpQuestions() { const tenant_id = JSON.parse(common_vendor.index.getStorageSync("userInfo")).tenant_id || "1"; let position_id = 1; try { const selectedJob = JSON.parse(common_vendor.index.getStorageSync("selectedJob") || "{}"); if (selectedJob && selectedJob.id) { position_id = selectedJob.id; console.log("使用选择的职位ID获取追问问题:", position_id); } else { console.warn("未找到选择的职位信息,使用默认职位ID:", position_id); } } catch (e) { console.error("解析职位信息失败:", e); } const timestamp = Date.now(); const requestUrl = `${common_config.apiBaseUrl}/api/wechat/open_questions/?position_id=${position_id}&tenant_id=${tenant_id}&question_form=5&_t=${timestamp}`; console.log("获取追问问题的请求URL:", requestUrl); common_vendor.index.request({ url: requestUrl, method: "GET", success: (res) => { if (res.data && res.data.code === 2e3 && res.data.data && res.data.data.items) { console.log("获取追问问题数据成功:", res.data.data.items); this.followUpQuestions = res.data.data.items.map((item) => ({ ...item, question_form: 5 // 确保每个追问问题都有正确的question_form值 })); this.processFollowUpQuestions(); } else { console.error("获取追问问题数据失败:", res.data); this.followUpQuestions = []; } }, fail: (err) => { console.error("请求追问问题数据失败:", err); this.followUpQuestions = []; } }); }, // 改进处理追问问题数据的方法 processFollowUpQuestions() { if (!this.followUpQuestions || this.followUpQuestions.length === 0) { console.log("没有追问问题数据"); this.followUpQuestions = []; return; } console.log("处理追问问题数据,总数:", this.followUpQuestions.length); this.followUpQuestions = this.followUpQuestions.filter((q) => q.parent_question_id); const parentQuestionIds = /* @__PURE__ */ new Set(); this.followUpQuestions.forEach((question) => { if (question.parent_question_id) { parentQuestionIds.add(question.parent_question_id); } }); console.log("有追问的父问题ID:", Array.from(parentQuestionIds)); this.hasPlayedFollowUp = {}; }, // 修改检查并播放追问问题的方法 checkAndPlayFollowUpQuestion(parentQuestionId) { console.log("检查是否有追问问题,父问题ID:", parentQuestionId); if (this.isWaitingForAnswer) { console.log("正在等待用户回答,暂不处理追问"); return; } if (!parentQuestionId) { console.warn("没有父问题ID,无法检查追问问题"); this.proceedToNextQuestion(); return; } if (this.hasPlayedFollowUp[parentQuestionId]) { console.log("已经播放过此问题的追问,直接进入下一个问题"); this.retryCount = 0; this.proceedToNextQuestion(); return; } const followUpQuestion = this.followUpQuestions.find( (q) => q.parent_question_id === parentQuestionId && q.digital_human_video_url && q.question_form === 5 ); if (followUpQuestion) { console.log("找到追问问题:", followUpQuestion); const recordingDuration = this.getRecordingDuration(); const lowScoreDuration = 7; if (recordingDuration < lowScoreDuration) { console.log(`录制时间 ${recordingDuration} 秒,少于 ${lowScoreDuration} 秒,将播放低分提示视频`); this.retryCount++; this.needPlayLowScoreVideo = true; this.playLowScoreVideo(); return; } this.retryCount = 0; this.hasPlayedFollowUp[parentQuestionId] = true; this.isFollowUpQuestion = true; this.currentParentQuestionId = parentQuestionId; this.currentFollowUpQuestion = followUpQuestion; this.playFollowUpQuestionVideo(followUpQuestion); } else { console.log("没有找到对应的追问问题,继续下一个问题"); this.retryCount = 0; this.proceedToNextQuestion(); } }, // 修改播放追问问题视频的方法 playFollowUpQuestionVideo(followUpQuestion) { console.log("播放追问问题视频:", followUpQuestion.digital_human_video_url); this.parentQuestion = this.currentSubtitle; this.videoUrl = followUpQuestion.digital_human_video_url; this.videoPlaying = true; this.isFollowUpQuestion = true; this.currentFollowUpQuestion = followUpQuestion; const followUpSubtitles = [{ startTime: 0, endTime: 60, // 延长显示时间到60秒 text: followUpQuestion.digital_human_video_subtitle || followUpQuestion.question }]; const subtitleKey = `followUpSubtitles_${followUpQuestion.id}`; this[subtitleKey] = followUpSubtitles; this.$nextTick(() => { const videoContext = common_vendor.index.createVideoContext("myVideo", this); if (videoContext) { videoContext.play(); this.currentSubtitle = followUpSubtitles[0].text; } }); }, // 添加作废面试的方法 invalidateInterview() { if (this.isRecording) { this.stopRecordingAnswer(); } if (this.recordingTimer) { clearInterval(this.recordingTimer); } this.isRecording = false; this.showStopRecordingButton = false; this.showStartRecordingButton = false; setTimeout(() => { common_vendor.index.switchTab({ url: "/pages/index/index" }); }, 500); }, initPersonDetectionWebSocket() { if (this.personDetectionSocket) { this.cleanupPersonDetectionWebSocket(); } try { this.personDetectionSocket = common_vendor.index.connectSocket({ url: `${common_config.personDetectionWsUrl}/ws/interview-room/room_${common_vendor.index.getStorageSync("appId")}/${common_vendor.index.getStorageSync("appId")}/`, success: () => { console.log("WebSocket connection initiated"); }, fail: (error) => { console.error("WebSocket connection failed:", error); } }); this.personDetectionSocket.onOpen(() => { console.log("WebSocket connection opened"); this.startPersonDetectionInterval(); }); this.personDetectionSocket.onError((error) => { console.error("WebSocket error:", error); this.cleanupPersonDetectionWebSocket(); }); this.personDetectionSocket.onClose(() => { console.log("WebSocket connection closed"); this.cleanupPersonDetectionWebSocket(); }); this.personDetectionSocket.onMessage((res) => { try { const data = JSON.parse(res.data); if (data.type === "person_detection_result") { this.handlePersonDetectionResult(data); } } catch (error) { console.error("Error parsing WebSocket message:", error); } }); } catch (error) { console.error("Error initializing WebSocket:", error); this.cleanupPersonDetectionWebSocket(); } }, startPersonDetectionInterval() { if (this.personDetectionInterval) { clearInterval(this.personDetectionInterval); } this.personDetectionInterval = setInterval(() => { if (this.personDetectionSocket && this.cameraContext) { this.cameraContext.takePhoto({ quality: "low", success: (res) => { const tempFilePath = res.tempImagePath; common_vendor.index.getFileSystemManager().readFile({ filePath: tempFilePath, encoding: "base64", success: (res2) => { const base64Image = res2.data; this.personDetectionSocket.send({ data: JSON.stringify({ type: "person_detection", image_data: base64Image }) }); }, fail: (error) => { console.error("Error reading image file:", error); } }); }, fail: (error) => { console.error("Error taking photo:", error); } }); } }, 3e3); }, cleanupPersonDetectionWebSocket() { if (this.personDetectionInterval) { clearInterval(this.personDetectionInterval); this.personDetectionInterval = null; } if (this.personDetectionSocket) { try { this.personDetectionSocket.close(); } catch (error) { console.error("Error closing WebSocket:", error); } this.personDetectionSocket = null; } }, handlePersonDetectionResult(data) { if (data.data.identity.status !== "identity_verified") { this.showPageWarning = true; common_vendor.index.showToast({ title: data.data.identity.message, icon: "none", duration: 3e3 }); common_vendor.index.vibrateLong({ success: function() { console.log("Vibration successful"); }, fail: function(err) { console.error("Vibration failed:", err); } }); setTimeout(() => { this.showPageWarning = false; }, 3e3); } }, // 进入追问模式 enterFollowUpMode() { this.isFollowUpMode = true; this.mainQuestionIndex = this.currentVideoIndex; console.log("进入追问模式,保存主问题索引:", this.mainQuestionIndex); }, // 修改 handleFollowUpQuestion 方法 async handleFollowUpQuestion(questionData) { console.log("处理追问:", questionData); this.enterFollowUpMode(); console.log("处理追问问题数据:", questionData); if (!questionData || !questionData.follow_up_voice_url || !questionData.follow_up_question) { console.error("追问数据不完整:", questionData); common_vendor.index.showToast({ title: "获取追问数据失败", icon: "none" }); return; } this.parentQuestion = questionData.original_question || ""; let audioUrl = ""; if (questionData.follow_up_voice_url.direct_url) { audioUrl = questionData.follow_up_voice_url.direct_url; } else if (questionData.follow_up_voice_url.file_url) { audioUrl = questionData.follow_up_voice_url.file_url; } if (!audioUrl.startsWith("http")) { audioUrl = common_config.apiBaseUrl + audioUrl; } console.log("处理后的音频URL:", audioUrl); const followUpQuestionObj = { id: Date.now(), question: questionData.follow_up_question.trim(), // 去除可能的空白字符 audioUrl, originalQuestion: questionData.original_question, sessionId: questionData.session_id }; console.log("创建的追问问题对象:", followUpQuestionObj); this.followUpQuestions.push(followUpQuestionObj); this.currentFollowUpIndex = this.followUpQuestions.length - 1; this.followUpQuestion = followUpQuestionObj.question; this.followUpAudioUrl = followUpQuestionObj.audioUrl; this.isFollowUpQuestion = true; this.currentFollowUpQuestion = followUpQuestionObj; this.showSubtitle(followUpQuestionObj.question, true); try { await this.playFollowUpAudio(); console.log("音频播放完成,准备录制回答"); this.prepareToAnswer(true, followUpQuestionObj); } catch (error) { console.error("音频播放失败:", error); this.prepareToAnswer(true, followUpQuestionObj); } }, // 播放追问音频 async playFollowUpAudio() { return new Promise((resolve, reject) => { console.log("开始播放追问音频, URL:", this.followUpAudioUrl); if (!this.followUpAudioUrl) { console.error("没有音频URL"); reject(new Error("没有音频URL")); return; } common_vendor.index.showLoading({ title: "加载音频中..." }); this.stopAndDestroyAudio(); try { const innerAudioContext = common_vendor.index.createInnerAudioContext(); this.audioContext = innerAudioContext; innerAudioContext.autoplay = true; innerAudioContext.obeyMuteSwitch = false; innerAudioContext.volume = 1; innerAudioContext.onCanplay(() => { console.log("音频可以播放"); common_vendor.index.hideLoading(); this.isAudioPlaying = true; common_vendor.index.showToast({ title: "正在播放追问", icon: "none", duration: 2e3 }); }); innerAudioContext.onEnded(() => { console.log("追问音频播放完成"); this.isAudioPlaying = false; resolve(); setTimeout(() => { this.stopAndDestroyAudio(); this.prepareToAnswer(true, this.currentFollowUpQuestion); }, 100); }); innerAudioContext.onError((res) => { console.error("音频播放错误:", res); this.isAudioPlaying = false; common_vendor.index.hideLoading(); common_vendor.index.showToast({ title: "音频播放失败", icon: "none" }); reject(res); this.stopAndDestroyAudio(); }); console.log("设置音频源:", this.followUpAudioUrl); innerAudioContext.src = this.followUpAudioUrl; } catch (error) { console.error("创建或播放音频失败:", error); this.isAudioPlaying = false; common_vendor.index.hideLoading(); common_vendor.index.showToast({ title: "音频播放失败", icon: "none" }); this.stopAndDestroyAudio(); reject(error); } }); }, // 停止并销毁音频 stopAndDestroyAudio() { if (!this.audioContext) { return; } const ctx = this.audioContext; this.audioContext = null; try { if (this.isAudioPlaying) { ctx.stop(); } } catch (error) { console.error("停止音频播放失败:", error); } try { ctx.destroy(); } catch (error) { console.error("销毁音频实例失败:", error); } this.isAudioPlaying = false; }, // 显示字幕 showSubtitle(text, isFollowUp = true) { console.log("显示字幕:", text); this.subtitleText = text; this.showSubtitleText = true; this.isFollowUpQuestion = isFollowUp; this.$nextTick(() => { setTimeout(() => { this.showSubtitleText = false; }, 8e3); }); }, // 显示问题对话框 showQuestionDialog() { if (this.followUpQuestion) { common_vendor.index.showModal({ title: "追问问题", content: this.followUpQuestion, showCancel: false, success: () => { this.prepareToAnswer(true, this.followUpQuestions[this.currentFollowUpIndex]); } }); } } }, computed: { // 计算进度比例 progressRatio() { return this.recordingTimerCount / this.maxRecordingTime; }, // 右半圆旋转角度 half1Turn() { if (this.progressRatio <= 0.5) { return this.progressRatio * 2 * 180; } else { return 180; } }, // 左半圆旋转角度 half2Turn() { if (this.progressRatio <= 0.5) { return 0; } else { return (this.progressRatio - 0.5) * 2 * 180; } }, // 修改进度百分比的计算方式 progressPercent() { return Math.min(Math.round(this.recordingTimerCount / this.maxRecordingTime * 100), 100); }, // 添加总题数计算属性 totalQuestions() { return this.questions.length || 5; } } }; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return common_vendor.e({ a: $data.videoUrl, b: common_vendor.o((...args) => $options.handleVideoError && $options.handleVideoError(...args)), c: common_vendor.o((...args) => $options.handleVideoEnded && $options.handleVideoEnded(...args)), d: common_vendor.o((...args) => $options.handleTimeUpdate && $options.handleTimeUpdate(...args)), e: common_vendor.o((...args) => $options.preventVideoControl && $options.preventVideoControl(...args)), f: common_vendor.o((...args) => $options.preventVideoControl && $options.preventVideoControl(...args)), g: $data.showAnswerButton }, $data.showAnswerButton ? { h: common_vendor.o((...args) => $options.handleAnswerButtonClick && $options.handleAnswerButtonClick(...args)) } : {}, { i: $data.currentSubtitle || $data.showSubtitleText && $data.subtitleText }, $data.currentSubtitle || $data.showSubtitleText && $data.subtitleText ? common_vendor.e({ j: $data.currentVideoIndex > 0 }, $data.currentVideoIndex > 0 ? { k: common_vendor.t($data.currentVideoIndex), l: common_vendor.t($options.totalQuestions) } : {}, { m: !$data.isFollowUpQuestion }, !$data.isFollowUpQuestion ? { n: common_vendor.t($data.currentSubtitle) } : { o: common_vendor.t($data.parentQuestion), p: common_vendor.t($data.subtitleText || $data.currentSubtitle) }) : {}, { q: $data.useMiniProgramCameraComponent }, $data.useMiniProgramCameraComponent ? { r: common_vendor.o((...args) => $options.handleCameraError && $options.handleCameraError(...args)) } : {}, { s: $data.showPageWarning ? 1 : "", t: $data.loading }, $data.loading ? {} : {}, { v: $data.showDebugInfo }, $data.showDebugInfo ? common_vendor.e({ w: $data.assistantResponse }, $data.assistantResponse ? { x: common_vendor.t($data.assistantResponse) } : {}, { y: $data.audioTranscript }, $data.audioTranscript ? { z: common_vendor.t($data.audioTranscript) } : {}, { A: common_vendor.f($data.processedResponses, (item, index, i0) => { return common_vendor.e({ a: item.role }, item.role ? { b: common_vendor.t(item.role) } : {}, { c: item.transcript }, item.transcript ? { d: common_vendor.t(item.transcript) } : {}, { e: index }); }) }) : {}, { B: $data.showStopRecordingButton }, $data.showStopRecordingButton ? { C: common_vendor.o((...args) => $options.stopRecordingAnswer && $options.stopRecordingAnswer(...args)) } : {}, { D: $data.isRecording }, $data.isRecording ? { E: common_vendor.t($data.recordingTimeDisplay), F: `conic-gradient(${$data.progressColor} ${$options.progressPercent}%, ${$data.progressBgColor} 0%)` } : {}, { G: $data.showStartRecordingButton }, $data.showStartRecordingButton ? { H: common_vendor.o((...args) => $options.handleStartRecordingClick && $options.handleStartRecordingClick(...args)) } : {}, { I: $data.showRetryButton }, $data.showRetryButton ? { J: common_vendor.o((...args) => $options.retryVideoUpload && $options.retryVideoUpload(...args)) } : {}, { K: $data.showCountdown }, $data.showCountdown ? { L: common_vendor.t($data.countdownValue) } : {}, { M: $data.showRerecordButton }, $data.showRerecordButton ? { N: common_vendor.o((...args) => $options.handleRerecordButtonClick && $options.handleRerecordButtonClick(...args)) } : {}, { O: $data.showPageWarning ? 1 : "" }); } const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-464e78c6"]]); wx.createPage(MiniProgramPage);