浏览代码

录制视频并上传

yangg 2 月之前
父节点
当前提交
3b00529cd1

文件差异内容过多而无法显示
+ 858 - 83
pages/identity-verify/identity-verify.vue


二进制
static/demo.mp4


+ 666 - 61
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.js

@@ -93,79 +93,104 @@ const _sfc_main = {
         "http://121.36.251.245:9000/minlong/5a9ad6b2-0de8-48e3-8eb7-141a9bee4a9b.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T145623Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=8a2120a4c6b059d64f90620377fdda2536f9fff9c455d5140210ef35990758c6",
         "http://121.36.251.245:9000/minlong/7aafb07e-ab0d-477e-9124-3263d0b7bf6f.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T145857Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=54ebe67d751c7e44a0f608b26175c9d076685a0e647e7e134cda22bbca2639eb"
         //结束
-      ]
+      ],
+      isRecording: false,
+      recordingTimer: null,
+      showStopRecordingButton: false,
+      mediaRecorder: null,
+      recordedChunks: [],
+      recorder: null,
+      lastUploadedVideoUrl: "",
+      showStartRecordingButton: false
     };
   },
   mounted() {
     this.playDigitalHumanVideo();
+    this.checkAudioPermission();
     this.initCamera();
+    setTimeout(() => {
+      if (this.cameraStream && !this.useMiniProgramCameraComponent) {
+        this.testAudioInput();
+      }
+    }, 3e3);
   },
   beforeDestroy() {
     this.stopUserCamera();
   },
   methods: {
     // 初始化相机
-    initCamera() {
+    async initCamera() {
       const systemInfo = common_vendor.index.getSystemInfoSync();
-      const isMiniProgram = systemInfo.uniPlatform === "mp-weixin" || systemInfo.uniPlatform === "mp-alipay" || systemInfo.uniPlatform === "mp-baidu" || systemInfo.uniPlatform === "mp-toutiao";
-      this.useMiniProgramCameraComponent = isMiniProgram;
+      const isMiniProgram = systemInfo.uniPlatform === "mp-weixin" || systemInfo.uniPlatform === "mp-alipay" || systemInfo.uniPlatform === "mp-baidu";
       if (isMiniProgram) {
-        this.$nextTick(() => {
-          this.cameraContext = common_vendor.index.createCameraContext();
+        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("相机");
+                }
+              });
+            }
+          }
         });
       } else {
-        this.startUserCamera();
-      }
-    },
-    // 启动用户摄像头
-    async startUserCamera() {
-      try {
-        const systemInfo = common_vendor.index.getSystemInfoSync();
-        const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-");
-        if (isMiniProgram) {
-          console.log("小程序环境不支持 getUserMedia API");
-          return;
-        }
-        if (!navigator || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
-          throw new Error("您的浏览器不支持摄像头访问");
-        }
-        const stream = await navigator.mediaDevices.getUserMedia({
-          video: {
-            width: { ideal: 200 },
-            height: { ideal: 150 },
-            facingMode: "user"
-            // 使用前置摄像头
-          },
-          audio: true
-          // 不需要音频
-        });
-        this.cameraStream = stream;
-        this.$nextTick(() => {
-          if (this.$refs.userCameraVideo) {
-            this.$refs.userCameraVideo.srcObject = stream;
+        try {
+          const constraints = {
+            audio: {
+              echoCancellation: true,
+              noiseSuppression: true,
+              autoGainControl: true
+            },
+            video: {
+              width: { ideal: 640 },
+              height: { ideal: 480 },
+              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();
           }
-        });
-      } catch (error) {
-        console.error("无法访问摄像头:", error);
-        this.cameraError = error.message;
-        let errorMessage = "无法访问摄像头,请检查权限设置";
-        if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
-          errorMessage = "摄像头访问被拒绝,请在浏览器设置中允许摄像头访问";
-        } else if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
-          errorMessage = "未检测到摄像头设备";
-        } else if (error.name === "NotReadableError" || error.name === "TrackStartError") {
-          errorMessage = "摄像头可能被其他应用占用";
-        } else if (error.name === "OverconstrainedError") {
-          errorMessage = "摄像头不满足指定的要求";
-        } else if (error.name === "TypeError" || error.message.includes("SSL")) {
-          errorMessage = "请确保在HTTPS环境下访问";
+          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"
+          });
         }
-        common_vendor.index.showToast({
-          title: errorMessage,
-          icon: "none",
-          duration: 3e3
-        });
-        this.handleCameraError(errorMessage);
       }
     },
     // 停止用户摄像头
@@ -482,11 +507,441 @@ const _sfc_main = {
     handleVideoEnded() {
       console.log("视频播放结束");
       this.videoPlaying = false;
-      this.showAnswerButton = true;
+      if (this.currentVideoIndex >= 1) {
+        this.showStartRecordingButton = true;
+      } else {
+        this.showAnswerButton = true;
+      }
     },
-    // 处理答题按钮点击
-    handleAnswerButtonClick() {
-      this.showAnswerButton = false;
+    // 添加新方法:开始录制用户回答
+    startRecordingAnswer() {
+      console.log("开始录制用户回答");
+      this.isRecording = true;
+      const systemInfo = common_vendor.index.getSystemInfoSync();
+      const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-");
+      if (isMiniProgram) {
+        this.startMiniProgramRecording();
+      } else {
+        this.startBrowserRecording();
+      }
+      this.showStopRecordingButton = true;
+    },
+    // 修改小程序环境下的录制方法
+    startMiniProgramRecording() {
+      if (!this.cameraContext) {
+        this.cameraContext = common_vendor.index.createCameraContext();
+      }
+      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;
+          }
+          const options = {
+            timeout: 6e4,
+            // 最长录制60秒
+            success: () => {
+              console.log("小程序录像开始成功");
+            },
+            fail: (err) => {
+              console.error("小程序录像开始失败:", err);
+              common_vendor.index.showToast({
+                title: "录制失败,请检查相机权限",
+                icon: "none"
+              });
+              this.proceedToNextQuestion();
+            },
+            timeoutCallback: () => {
+              console.log("录制超时自动停止");
+              this.stopRecordingAnswer();
+            }
+          };
+          this.recorder = this.cameraContext.startRecord(options);
+        }
+      });
+    },
+    // 添加新方法:请求小程序权限
+    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: 128e3,
+        videoBitsPerSecond: 25e5
+      };
+      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 = () => {
+        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);
+        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开始录制");
+      } catch (e) {
+        console.error("开始录制失败:", e);
+      }
+    },
+    // 添加新方法:停止录制用户回答
+    stopRecordingAnswer() {
+      console.log("停止录制用户回答");
+      this.isRecording = false;
+      if (this.recordingTimer) {
+        clearTimeout(this.recordingTimer);
+        this.recordingTimer = null;
+      }
+      common_vendor.index.hideLoading();
+      this.showStopRecordingButton = false;
+      const systemInfo = common_vendor.index.getSystemInfoSync();
+      const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-");
+      if (isMiniProgram) {
+        this.stopMiniProgramRecording();
+      } else {
+        this.stopBrowserRecording();
+      }
+    },
+    // 添加新方法:停止小程序录制
+    stopMiniProgramRecording() {
+      if (!this.cameraContext) {
+        console.error("相机上下文不存在");
+        this.proceedToNextQuestion();
+        return;
+      }
+      this.cameraContext.stopRecord({
+        success: (res) => {
+          console.log("小程序录像停止成功:", res);
+          const tempFilePath = res.tempVideoPath;
+          this.uploadRecordedVideo(tempFilePath);
+        },
+        fail: (err) => {
+          console.error("小程序录像停止失败:", err);
+          common_vendor.index.showToast({
+            title: "录制失败",
+            icon: "none"
+          });
+          this.proceedToNextQuestion();
+        }
+      });
+    },
+    // 添加新方法:停止浏览器录制
+    stopBrowserRecording() {
+      if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") {
+        this.mediaRecorder.stop();
+        console.log("浏览器录制停止成功");
+      } else {
+        console.error("MediaRecorder不存在或已经停止");
+        this.proceedToNextQuestion();
+      }
+    },
+    // 修改上传录制的视频方法
+    uploadRecordedVideo(fileOrPath) {
+      console.log("准备上传视频:", typeof fileOrPath === "string" ? fileOrPath : fileOrPath.name);
+      common_vendor.index.showLoading({
+        title: "正在上传...",
+        mask: true
+      });
+      const userInfo = common_vendor.index.getStorageSync("userInfo");
+      const openid = userInfo ? JSON.parse(userInfo).openid || "" : "";
+      const tenant_id = common_vendor.index.getStorageSync("tenant_id") || "1";
+      if (typeof fileOrPath !== "string") {
+        const formData = new FormData();
+        formData.append("file", fileOrPath);
+        formData.append("openid", openid);
+        formData.append("tenant_id", tenant_id);
+        formData.append("application_id", 1);
+        formData.append("question_id", this.currentVideoIndex);
+        formData.append("video_duration", 0);
+        formData.append("has_audio", "true");
+        const xhr = new XMLHttpRequest();
+        xhr.open("POST", "http://192.168.66.187:8083/api/system/upload/", true);
+        xhr.onload = () => {
+          common_vendor.index.hideLoading();
+          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.submitVideoToInterview(videoUrl);
+                } else {
+                  common_vendor.index.showToast({
+                    title: "视频URL获取失败",
+                    icon: "none"
+                  });
+                }
+              } else {
+                common_vendor.index.showToast({
+                  title: res.msg || "上传失败,请重试",
+                  icon: "none"
+                });
+              }
+            } catch (e) {
+              console.error("解析响应失败:", e);
+              common_vendor.index.showToast({
+                title: "解析响应失败",
+                icon: "none"
+              });
+            }
+          } else {
+            common_vendor.index.showToast({
+              title: "上传失败,HTTP状态: " + xhr.status,
+              icon: "none"
+            });
+          }
+        };
+        xhr.onerror = () => {
+          common_vendor.index.hideLoading();
+          console.error("上传网络错误");
+          common_vendor.index.showToast({
+            title: "网络错误,请重试",
+            icon: "none"
+          });
+        };
+        xhr.upload.onprogress = (event) => {
+          if (event.lengthComputable) {
+            const percentComplete = Math.round(event.loaded / event.total * 100);
+            console.log("上传进度:", percentComplete + "%");
+          }
+        };
+        xhr.send(formData);
+        return;
+      }
+      common_vendor.index.uploadFile({
+        url: "http://192.168.66.187:8083/api/system/upload/",
+        filePath: fileOrPath,
+        name: "file",
+        formData: {
+          openid,
+          tenant_id,
+          application_id: 1,
+          question_id: this.currentVideoIndex,
+          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.url || res.data.photoUrl || "";
+              if (videoUrl) {
+                this.submitVideoToInterview(videoUrl);
+              } else {
+                common_vendor.index.showToast({
+                  title: "视频URL获取失败",
+                  icon: "none"
+                });
+              }
+            } else {
+              common_vendor.index.showToast({
+                title: res.msg || "上传失败,请重试",
+                icon: "none"
+              });
+            }
+          } catch (e) {
+            console.error("解析响应失败:", e);
+            common_vendor.index.showToast({
+              title: "解析响应失败",
+              icon: "none"
+            });
+          }
+        },
+        fail: (err) => {
+          console.error("上传失败:", err);
+          common_vendor.index.showToast({
+            title: "上传失败,请重试",
+            icon: "none"
+          });
+        },
+        complete: () => {
+          common_vendor.index.hideLoading();
+        }
+      });
+    },
+    // 添加新方法:将视频URL提交到面试接口
+    submitVideoToInterview(videoUrl) {
+      console.log("提交视频URL到面试接口:", videoUrl);
+      const requestData = {
+        application_id: 1,
+        question_id: this.currentVideoIndex,
+        video_url: videoUrl,
+        video_duration: 0,
+        tenant_id: common_vendor.index.getStorageSync("tenant_id") || "1"
+      };
+      common_vendor.index.request({
+        url: "http://192.168.66.187:8083/api/job/upload_video",
+        method: "POST",
+        data: requestData,
+        header: {
+          "content-type": "application/json"
+        },
+        success: (res) => {
+          console.log("面试接口提交成功:", res);
+          common_vendor.index.hideLoading();
+          if (res.data.code === 0 || res.data.code === 2e3) {
+            common_vendor.index.showToast({
+              title: "回答已提交",
+              icon: "success"
+            });
+            this.lastUploadedVideoUrl = videoUrl;
+            setTimeout(() => {
+              this.proceedToNextQuestion();
+            }, 1500);
+          } else {
+            common_vendor.index.showToast({
+              title: res.data.msg || "提交失败,请重试",
+              icon: "none"
+            });
+          }
+        },
+        fail: (err) => {
+          console.error("面试接口提交失败:", err);
+          common_vendor.index.hideLoading();
+          common_vendor.index.showToast({
+            title: "网络错误,请重试",
+            icon: "none"
+          });
+        }
+      });
+    },
+    // 添加新方法:进入下一个问题
+    proceedToNextQuestion() {
       this.currentVideoIndex++;
       if (this.currentVideoIndex < this.videoList.length) {
         this.videoUrl = this.videoList[this.currentVideoIndex];
@@ -511,6 +966,11 @@ const _sfc_main = {
         }, 2e3);
       }
     },
+    // 修改 handleAnswerButtonClick 方法
+    handleAnswerButtonClick() {
+      this.showAnswerButton = false;
+      this.proceedToNextQuestion();
+    },
     // 处理相机错误
     handleCameraError(e) {
       console.error("相机错误:", e);
@@ -569,6 +1029,141 @@ const _sfc_main = {
         (sub) => currentTime >= sub.startTime && currentTime < sub.endTime
       );
       this.currentSubtitle = subtitle ? subtitle.text : "";
+    },
+    // Add a new method to handle the "Start Recording" button click
+    handleStartRecordingClick() {
+      this.showStartRecordingButton = 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);
+              }
+            });
+          }
+        }
+      });
     }
   }
 };
@@ -615,7 +1210,17 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
         e: index
       });
     })
-  }) : {});
+  }) : {}, {
+    r: $data.showStopRecordingButton
+  }, $data.showStopRecordingButton ? {
+    s: common_vendor.o((...args) => $options.stopRecordingAnswer && $options.stopRecordingAnswer(...args))
+  } : {}, {
+    t: $data.isRecording
+  }, $data.isRecording ? {} : {}, {
+    v: $data.showStartRecordingButton
+  }, $data.showStartRecordingButton ? {
+    w: common_vendor.o((...args) => $options.handleStartRecordingClick && $options.handleStartRecordingClick(...args))
+  } : {});
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-464e78c6"]]);
 wx.createPage(MiniProgramPage);

文件差异内容过多而无法显示
+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.wxml


+ 93 - 0
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.wxss

@@ -176,3 +176,96 @@ video.data-v-464e78c6::-webkit-media-controls-fullscreen-button {
     box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
 }
 }
+.stop-recording-button-container.data-v-464e78c6 {
+  position: absolute;
+  top: 75%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 20;
+}
+.stop-recording-button.data-v-464e78c6 {
+  width: 120px;
+  height: 120px;
+  border-radius: 50%;
+  background-color: #e74c3c; /* 红色背景 */
+  color: white;
+  font-size: 18px;
+  border: none;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+  animation: pulse-464e78c6 2s infinite;
+}
+@keyframes pulse-464e78c6 {
+0% {
+    transform: scale(1);
+    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+}
+50% {
+    transform: scale(1.05);
+    box-shadow: 0 8px 15px rgba(0, 0, 0, 0.4);
+}
+100% {
+    transform: scale(1);
+    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+}
+}
+
+/* 录制中的指示器 */
+.recording-indicator.data-v-464e78c6 {
+  position: absolute;
+  top: 20px;
+  left: 20px;
+  display: flex;
+  align-items: center;
+  background-color: rgba(0, 0, 0, 0.6);
+  padding: 5px 10px;
+  border-radius: 15px;
+  z-index: 20;
+}
+.recording-dot.data-v-464e78c6 {
+  width: 12px;
+  height: 12px;
+  background-color: #e74c3c;
+  border-radius: 50%;
+  margin-right: 8px;
+  animation: blink-464e78c6 1s infinite;
+}
+.recording-text.data-v-464e78c6 {
+  color: white;
+  font-size: 14px;
+}
+@keyframes blink-464e78c6 {
+0% { opacity: 1;
+}
+50% { opacity: 0.3;
+}
+100% { opacity: 1;
+}
+}
+.start-recording-button-container.data-v-464e78c6 {
+  position: absolute;
+  top: 75%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 20;
+}
+.start-recording-button.data-v-464e78c6 {
+  width: 120px;
+  height: 120px;
+  border-radius: 50%;
+  background-color: #6c5ce7; /* 与开始面试按钮颜色保持一致 */
+  color: white;
+  font-size: 18px;
+  border: none;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+  animation: pulse-464e78c6 2s infinite; /* 使用相同的动画 */
+}
+
+/* 可以删除pulse-green动画,直接使用与开始面试按钮相同的pulse动画 */

二进制
unpackage/dist/dev/mp-weixin/static/demo.mp4


部分文件因为文件数量过多而无法显示