|
@@ -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/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"
|
|
"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() {
|
|
mounted() {
|
|
this.playDigitalHumanVideo();
|
|
this.playDigitalHumanVideo();
|
|
|
|
+ this.checkAudioPermission();
|
|
this.initCamera();
|
|
this.initCamera();
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ if (this.cameraStream && !this.useMiniProgramCameraComponent) {
|
|
|
|
+ this.testAudioInput();
|
|
|
|
+ }
|
|
|
|
+ }, 3e3);
|
|
},
|
|
},
|
|
beforeDestroy() {
|
|
beforeDestroy() {
|
|
this.stopUserCamera();
|
|
this.stopUserCamera();
|
|
},
|
|
},
|
|
methods: {
|
|
methods: {
|
|
// 初始化相机
|
|
// 初始化相机
|
|
- initCamera() {
|
|
|
|
|
|
+ async initCamera() {
|
|
const systemInfo = common_vendor.index.getSystemInfoSync();
|
|
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) {
|
|
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 {
|
|
} 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() {
|
|
handleVideoEnded() {
|
|
console.log("视频播放结束");
|
|
console.log("视频播放结束");
|
|
this.videoPlaying = false;
|
|
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++;
|
|
this.currentVideoIndex++;
|
|
if (this.currentVideoIndex < this.videoList.length) {
|
|
if (this.currentVideoIndex < this.videoList.length) {
|
|
this.videoUrl = this.videoList[this.currentVideoIndex];
|
|
this.videoUrl = this.videoList[this.currentVideoIndex];
|
|
@@ -511,6 +966,11 @@ const _sfc_main = {
|
|
}, 2e3);
|
|
}, 2e3);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
+ // 修改 handleAnswerButtonClick 方法
|
|
|
|
+ handleAnswerButtonClick() {
|
|
|
|
+ this.showAnswerButton = false;
|
|
|
|
+ this.proceedToNextQuestion();
|
|
|
|
+ },
|
|
// 处理相机错误
|
|
// 处理相机错误
|
|
handleCameraError(e) {
|
|
handleCameraError(e) {
|
|
console.error("相机错误:", e);
|
|
console.error("相机错误:", e);
|
|
@@ -569,6 +1029,141 @@ const _sfc_main = {
|
|
(sub) => currentTime >= sub.startTime && currentTime < sub.endTime
|
|
(sub) => currentTime >= sub.startTime && currentTime < sub.endTime
|
|
);
|
|
);
|
|
this.currentSubtitle = subtitle ? subtitle.text : "";
|
|
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
|
|
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"]]);
|
|
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-464e78c6"]]);
|
|
wx.createPage(MiniProgramPage);
|
|
wx.createPage(MiniProgramPage);
|