Browse Source

feat:修复计时器跳得特别快、松开后提示"录音太短"、按住就弹"录音太短"

zhangwl 1 month ago
parent
commit
bc779ab3ac
1 changed files with 38 additions and 11 deletions
  1. 38 11
      chat-ai-ui-main/src/components/ChatInput.vue

+ 38 - 11
chat-ai-ui-main/src/components/ChatInput.vue

@@ -136,10 +136,12 @@ const isTranscribing = ref(false);
 let mediaRecorder: MediaRecorder | null = null;
 let mediaRecorder: MediaRecorder | null = null;
 let audioChunks: Blob[] = [];
 let audioChunks: Blob[] = [];
 let recordingTimer: ReturnType<typeof setInterval> | null = null;
 let recordingTimer: ReturnType<typeof setInterval> | null = null;
+let recordingStartTime = 0; // 实际开始时间戳
 let startY = 0;
 let startY = 0;
-const CANCEL_THRESHOLD = 80; // 上移取消阈值(px)
-const MAX_DURATION = 60;     // 最大录音时长(秒)
-const MIN_DURATION = 1;      // 最短录音时长(秒)
+let isStartingRecording = false; // 防止重复启动锁
+const CANCEL_THRESHOLD = 80;
+const MAX_DURATION = 60;
+const MIN_DURATION = 1;
 
 
 onMounted(() => {
 onMounted(() => {
   // 检测浏览器是否支持录音
   // 检测浏览器是否支持录音
@@ -179,16 +181,30 @@ const formatDuration = (seconds: number): string => {
 };
 };
 
 
 const startRecording = async (event: MouseEvent | TouchEvent) => {
 const startRecording = async (event: MouseEvent | TouchEvent) => {
-  if (isTranscribing.value || props.isLoading) return;
+  // 防止重复启动(mousedown + touchstart 同时触发 / 快速重复点击)
+  if (isStartingRecording || isRecording.value || isTranscribing.value || props.isLoading) return;
+  isStartingRecording = true;
 
 
   startY = 'touches' in event ? event.touches[0].clientY : event.clientY;
   startY = 'touches' in event ? event.touches[0].clientY : event.clientY;
   isCancelling.value = false;
   isCancelling.value = false;
   audioChunks = [];
   audioChunks = [];
   recordingDuration.value = 0;
   recordingDuration.value = 0;
 
 
+  // 确保旧的 timer 被清理
+  if (recordingTimer) {
+    clearInterval(recordingTimer);
+    recordingTimer = null;
+  }
+
   try {
   try {
     const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
     const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
 
 
+    // getUserMedia 是异步的,等待期间用户可能已松手,此时应放弃录音
+    if (!isStartingRecording) {
+      audioStream.getTracks().forEach(track => track.stop());
+      return;
+    }
+
     const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
     const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
       ? 'audio/webm;codecs=opus'
       ? 'audio/webm;codecs=opus'
       : 'audio/webm';
       : 'audio/webm';
@@ -199,22 +215,33 @@ const startRecording = async (event: MouseEvent | TouchEvent) => {
       if (e.data.size > 0) audioChunks.push(e.data);
       if (e.data.size > 0) audioChunks.push(e.data);
     };
     };
 
 
-    mediaRecorder.start(100); // 每 100ms 生成一个 chunk
+    mediaRecorder.start(100);
     isRecording.value = true;
     isRecording.value = true;
+    recordingStartTime = Date.now();
 
 
+    // 使用时间戳计算时长,避免多个 timer 叠加
     recordingTimer = setInterval(() => {
     recordingTimer = setInterval(() => {
-      recordingDuration.value++;
-      if (recordingDuration.value >= MAX_DURATION) {
+      const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
+      recordingDuration.value = elapsed;
+      if (elapsed >= MAX_DURATION) {
         stopRecording();
         stopRecording();
       }
       }
-    }, 1000);
+    }, 200);
   } catch (err: any) {
   } catch (err: any) {
     console.error('无法获取麦克风权限:', err);
     console.error('无法获取麦克风权限:', err);
     alert('请允许麦克风权限以使用语音功能');
     alert('请允许麦克风权限以使用语音功能');
+  } finally {
+    isStartingRecording = false;
   }
   }
 };
 };
 
 
 const stopRecording = async () => {
 const stopRecording = async () => {
+  // 如果正在启动录音(等待麦克风权限),标记取消并返回
+  if (isStartingRecording) {
+    isStartingRecording = false;
+    return;
+  }
+
   if (!isRecording.value || !mediaRecorder) return;
   if (!isRecording.value || !mediaRecorder) return;
 
 
   if (isCancelling.value) {
   if (isCancelling.value) {
@@ -228,8 +255,8 @@ const stopRecording = async () => {
     recordingTimer = null;
     recordingTimer = null;
   }
   }
 
 
-  // 检查录音时长
-  const duration = recordingDuration.value;
+  // 用实际时间戳计算时长(不依赖 timer 累加值)
+  const actualDuration = (Date.now() - recordingStartTime) / 1000;
 
 
   // 停止录音
   // 停止录音
   mediaRecorder.stop();
   mediaRecorder.stop();
@@ -245,7 +272,7 @@ const stopRecording = async () => {
     }
     }
   });
   });
 
 
-  if (duration < MIN_DURATION) {
+  if (actualDuration < MIN_DURATION) {
     alert('录音太短,请至少录制 1 秒');
     alert('录音太短,请至少录制 1 秒');
     audioChunks = [];
     audioChunks = [];
     return;
     return;