|
|
@@ -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>
|