Browse Source

fix: 修复录音停止流程的多个异常问题

优化录音停止逻辑,解决以下问题:
1. 处理浏览器不支持permissions的情况,避免流程中断
2. 捕获Firefox/Safari等浏览器的权限查询异常
3. 修正权限判断逻辑,避免误判prompt状态为无权限
4. 先绑定回调再触发停止按钮,防止回调丢失
5. 添加超时兜底机制,避免录音状态卡住
6. 组件销毁时清理定时器和iframe回调,防止内存泄漏
SanHQin 11 hours ago
parent
commit
3dfeccac68

+ 1 - 1
dist/index.html

@@ -37,7 +37,7 @@
       width: 100%;
       background: #e6eaf0;
       font-family: '黑体';
-    }</style><link href=./static/css/app.50eb0108549c9b6fed7c1787589cc36e.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.23ea04dc469b57e2b4f8.js></script><script type=text/javascript src=./static/js/vendor.2c492f3d5a39df08f4a3.js></script><script type=text/javascript src=./static/js/app.626a3d4b754841344091.js></script></body></html><script>function stopSafari() {
+    }</style><link href=./static/css/app.dc72677ad20e3d9fb6fb1ab3034466a7.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.23ea04dc469b57e2b4f8.js></script><script type=text/javascript src=./static/js/vendor.2c492f3d5a39df08f4a3.js></script><script type=text/javascript src=./static/js/app.87d6783430ffbcd96716.js></script></body></html><script>function stopSafari() {
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

File diff suppressed because it is too large
+ 0 - 0
dist/static/css/app.dc72677ad20e3d9fb6fb1ab3034466a7.css


File diff suppressed because it is too large
+ 0 - 0
dist/static/css/app.dc72677ad20e3d9fb6fb1ab3034466a7.css.map


File diff suppressed because it is too large
+ 0 - 0
dist/static/css/workPage.8b1a8a47079e74f932d0137c9a9059c1.css


File diff suppressed because it is too large
+ 0 - 0
dist/static/css/workPage.8b1a8a47079e74f932d0137c9a9059c1.css.map


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/app.87d6783430ffbcd96716.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/app.87d6783430ffbcd96716.js.map


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/manifest.23ea04dc469b57e2b4f8.js.map


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/workPage-manifest.2ece51fa34be51c8610a.js.map


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/workPage.50a119c2f75871f523f1.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/workPage.50a119c2f75871f523f1.js.map


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/workPage.d507d4f8ac0be564a697.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/workPage.d507d4f8ac0be564a697.js.map


+ 1 - 1
dist/workPage.html

@@ -1,4 +1,4 @@
-<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>工作页面 - CocoFlow</title><link rel=icon href=../static/logo.ico><link href=./static/css/workPage.3ef009ac6e432afb5e94dd7d6bfda3e9.css rel=stylesheet></head><body><noscript><strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script type=text/javascript src=./static/js/workPage-manifest.2ece51fa34be51c8610a.js></script><script type=text/javascript src=./static/js/workPage-vendor.7b6275e4cf468b42a92b.js></script><script type=text/javascript src=./static/js/workPage.50a119c2f75871f523f1.js></script></body></html><script>function stopSafari() {
+<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>工作页面 - CocoFlow</title><link rel=icon href=../static/logo.ico><link href=./static/css/workPage.8b1a8a47079e74f932d0137c9a9059c1.css rel=stylesheet></head><body><noscript><strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script type=text/javascript src=./static/js/workPage-manifest.2ece51fa34be51c8610a.js></script><script type=text/javascript src=./static/js/workPage-vendor.7b6275e4cf468b42a92b.js></script><script type=text/javascript src=./static/js/workPage.d507d4f8ac0be564a697.js></script></body></html><script>function stopSafari() {
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

+ 129 - 64
src/components/pages/workPage/components/questionsAndAnswers.vue

@@ -121,8 +121,75 @@ export default {
   methods: {
     // 清理 iframe 录音会话(统一清理入口)
     _cleanupTranscriber(iframe) {
-      iframe.contentWindow.onSessionStopped = null;
-      iframe.contentWindow.window.onRecognizedResult = null;
+      const win = iframe && iframe.contentWindow;
+      if (!win) return;
+      try {
+        win.onSessionStopped = null;
+        win.onSessionStarted = null;
+        win.window.onRecognizedResult = null;
+      } catch (e) {
+        console.warn("[cleanupTranscriber] 清理 iframe 回调失败", e);
+      }
+    },
+    // 查询麦克风权限状态:永不抛错、永不挂起
+    // 不支持/查询失败/超时 统一返回 'unknown',避免整个停止流程被卡死
+    _queryMicPermission(timeout = 1500) {
+      return new Promise(resolve => {
+        let settled = false;
+        let timer = null;
+        const finish = state => {
+          if (settled) return;
+          settled = true;
+          if (timer) clearTimeout(timer);
+          resolve(state || 'unknown');
+        };
+        timer = setTimeout(() => finish('unknown'), timeout);
+        try {
+          if (!navigator.permissions || typeof navigator.permissions.query !== 'function') {
+            return finish('unknown');
+          }
+          const result = navigator.permissions.query({ name: 'microphone' });
+          if (result && typeof result.then === 'function') {
+            result
+              .then(status => finish(status && status.state))
+              .catch(e => {
+                console.warn("[stopRecord] permissions.query 失败,按未知权限处理", e);
+                finish('unknown');
+              });
+          } else if (result && result.state) {
+            finish(result.state);
+          } else {
+            finish('unknown');
+          }
+        } catch (e) {
+          // Firefox/Safari 等对 { name: 'microphone' } 直接抛 TypeError
+          console.warn("[stopRecord] permissions.query 不支持,按未知权限处理", e);
+          finish('unknown');
+        }
+      });
+    },
+    // 绑定 iframe 停止回调(必须在点击停止按钮之前绑定,避免回调先于监听到达)
+    _bindSessionStopped(iframe) {
+      const win = iframe && iframe.contentWindow;
+      if (!win) return;
+      win.onSessionStopped = (s, e) => {
+        console.log("onSessionStopped", s, e);
+        this._finishStop(iframe, this.lang.ssStoppedRec);
+      };
+    },
+    // 统一收尾:复位状态、解绑回调、清理 iframe、给出提示
+    _finishStop(iframe, tipMsg) {
+      if (this._stopFallbackTimer) {
+        clearTimeout(this._stopFallbackTimer);
+        this._stopFallbackTimer = null;
+      }
+      this.recordObj.status = "0";
+      this.recordObj.loading = false;
+      this.recordObj.startTime = 0;
+      this._cleanupTranscriber(iframe);
+      if (tipMsg) {
+        this.$message.success(tipMsg);
+      }
     },
     // 更新作业
     changeWorkData(newValue) {
@@ -213,74 +280,64 @@ export default {
       //  })
       //}, 2000)
     },
-    stopRecord() {
-      console.log("测试测试")
-      alert("点击停止录音1")
-      if (new Date().getTime() - this.recordObj.startTime < 2000) {
-        alert("点击停止录音2")
-        return this.$message.info(this.lang.ssRecordShort)
+    // 停止录音
+    // 旧实现的问题:
+    // 1. navigator.permissions 不存在时,`permissions && permissions.query(...).then(...)`
+    //    整段短路成 undefined,回调一个都不执行,函数直接走到 alert("...end"),录音实际没停;
+    // 2. permissions.query 在 Firefox/Safari 上会直接 throw,未捕获导致停止流程中断;
+    // 3. 只有 "granted" 才继续,导致 state 为 "prompt"(用户一直没回应权限弹窗)时误判为无权限;
+    // 4. onSessionStopped 在点击停止按钮「之后」才绑定,回调可能先于绑定到达,导致状态永远不复位。
+    async stopRecord() {
+      // 防重复点击:卸载中或已停止则直接忽略
+      if (this.recordObj.loading || this.recordObj.status != '1') {
+        return;
+      }
+      if (this.recordObj.startTime && new Date().getTime() - this.recordObj.startTime < 2000) {
+        return this.$message.info(this.lang.ssRecordShort);
       }
-      alert("点击停止录音3")
+
       const iframe = this.$refs["iframeRef"];
-      // 判断浏览器是否有开启录音权限
-      navigator.permissions && navigator.permissions.query({ name: 'microphone' }).then(permissionStatus => {
-        alert("点击停止录音4")
-        if (permissionStatus.state !== "granted") {
-          // 没有开启录音权限,直接确定停止录音
-          alert("点击停止录音5")
-          this.recordObj.status = "0";
-          this.recordObj.loading = false;
-
-          this._cleanupTranscriber(iframe);
-          alert("点击停止录音6")
-          this.$message.success(this.lang.ssNoPermStop);
+      if (!iframe || !iframe.contentWindow) {
+        // iframe 已不存在,直接复位本地状态
+        this._finishStop(iframe);
+        return;
+      }
 
+      this.recordObj.loading = true;
+      try {
+        // 权限查询失败/不支持/超时都返回 'unknown',只有明确 denied 才算无权限,
+        // 避免因浏览器差异把「正常录音」误判成「无权限」
+        const permissionState = await this._queryMicPermission(1500);
+        if (permissionState === 'denied') {
+          this._finishStop(iframe, this.lang.ssNoPermStop);
           return;
         }
-        console.log("测试测试222")
-        // 有权限再判断状态
-        alert("点击停止录音7")
-        try {
-          if (this.recordObj.status == '1' && !this.recordObj.loading) {
-            alert("点击停止录音8")
-            console.log("测试测3333")
-            this.recordObj.loading = true;
-            iframe.contentWindow.window.document
-              .getElementById("scenarioStopButton")
-              .click();
-            alert("点击停止录音9")
-            iframe.contentWindow.onSessionStopped = (s, e) => {
-              try {
-                alert("点击停止录音10")
-                this.recordObj.status = "0"
-                this.recordObj.loading = false;
-                this.$message.success(this.lang.ssStoppedRec)
-                iframe.contentWindow.onSessionStopped = null;
-                this._cleanupTranscriber(iframe);
-              } catch (error) {
-                alert(JSON.stringify(error))
-              }
-            };
-          } else {
-            alert("点击停止录音11")
-            this.recordObj.status = "0";
-            this.recordObj.loading = false;
-            this._cleanupTranscriber(iframe);
-          }
-        } catch (error) {
-          alert(JSON.stringify(error))
+
+        // 关键:先绑定回调,再触发 iframe 内的停止按钮
+        this._bindSessionStopped(iframe);
+
+        const stopBtn = iframe.contentWindow.window.document.getElementById("scenarioStopButton");
+        if (!stopBtn) {
+          // iframe 内页面尚未就绪,无法优雅停止,直接本地收尾
+          console.warn("[stopRecord] 未找到 scenarioStopButton,直接结束录音会话");
+          this._finishStop(iframe, this.lang.ssMicCheckStop);
+          return;
         }
-      }).catch((e) => {
-        alert(JSON.stringify(e))
-        alert(JSON.stringify(e))
-        console.log("测试测4444试")
-        // 浏览器不支持或异常,默认直接停止
-        this.recordObj.status = "0";
-        this.recordObj.loading = false;
-        this._cleanupTranscriber(this.$refs["iframeRef"]);
-        this.$message.success(this.lang.ssMicCheckStop);
-      });
-      alert("点击停止录音end")
+        stopBtn.click();
+
+        // 兜底:iframe 若因故不再回调 onSessionStopped,超时后强制复位,避免一直卡在「录音中」
+        if (this._stopFallbackTimer) clearTimeout(this._stopFallbackTimer);
+        this._stopFallbackTimer = setTimeout(() => {
+          this._stopFallbackTimer = null;
+          if (this.recordObj.status == '1') {
+            console.warn("[stopRecord] 等待 onSessionStopped 超时,强制结束录音会话");
+            this._finishStop(iframe, this.lang.ssStoppedRec);
+          }
+        }, 3000);
+      } catch (error) {
+        console.error("[stopRecord] 停止录音异常", error);
+        this._finishStop(iframe, this.lang.ssMicCheckStop);
+      }
     },
     setWork() {
       this.work = JSON.parse(JSON.stringify(this.workData.json));
@@ -288,6 +345,14 @@ export default {
   },
   mounted() {
 
+  },
+  beforeDestroy() {
+    // 组件销毁时清掉停止录音的兜底定时器与 iframe 回调,避免内存泄漏
+    if (this._stopFallbackTimer) {
+      clearTimeout(this._stopFallbackTimer);
+      this._stopFallbackTimer = null;
+    }
+    this._cleanupTranscriber(this.$refs["iframeRef"]);
   }
 };
 </script>

Some files were not shown because too many files changed in this diff