// 语音识别类 class VoiceRecognition { constructor() { this.recognition = null; this.isListening = false; this.isSupported = false; this.onResult = null; this.onError = null; this.onStatusChange = null; // 错误重试控制 this.retryCount = 0; this.maxRetries = 3; this.retryDelay = 2000; // 2秒延迟 this.lastErrorTime = 0; this.consecutiveErrors = 0; this.isDisabled = false; // 新增:防止并发启动的锁机制 this.isStarting = false; this.isStopping = false; this.restartTimer = null; this.startupPromise = null; // 双击恢复功能标志 this.doubleClickRecoveryAdded = false; this.init(); } init() { // 检查浏览器支持 if ('webkitSpeechRecognition' in window) { this.recognition = new webkitSpeechRecognition(); this.isSupported = true; } else if ('SpeechRecognition' in window) { this.recognition = new SpeechRecognition(); this.isSupported = true; } else { console.warn('浏览器不支持语音识别'); this.handleError('浏览器不支持语音识别'); return; } this.setupRecognition(); // 使用更安全的异步启动 setTimeout(async () => { try { console.log('初始化语音识别...'); await this.startListening(); } catch (error) { console.error('初始化启动失败:', error); // 如果初始化失败,给用户提供恢复选项 setTimeout(() => { this.showNotification('语音识别初始化遇到问题,双击页面重新尝试', 'warning'); this.addDoubleClickRecovery(); }, 2000); } }, 500); // 增加初始化延迟 } setupRecognition() { // 配置语音识别 this.recognition.continuous = true; this.recognition.interimResults = false; this.recognition.lang = 'en-US'; // 使用英文识别食物名称 this.recognition.maxAlternatives = 3; // 事件监听 this.recognition.onstart = () => { this.isListening = true; this.updateStatus('listening'); console.log('语音识别开始'); }; this.recognition.onresult = (event) => { this.handleResult(event); }; this.recognition.onerror = (event) => { this.handleError(event.error); }; this.recognition.onend = () => { this.isListening = false; this.isStopping = false; this.updateStatus('ready'); console.log('语音识别结束'); // 清除之前的重启定时器 if (this.restartTimer) { clearTimeout(this.restartTimer); this.restartTimer = null; } // 智能重启逻辑 - 避免在错误频发时无限重试 if (this.shouldAttemptRestart()) { const delay = this.calculateRestartDelay(); console.log(`准备在 ${delay}ms 后重新启动语音识别...`); this.restartTimer = setTimeout(async () => { if (this.isSupported && !this.isListening && !this.isDisabled && !this.isStarting) { await this.startListening(); } }, delay); } else { console.log('由于频繁错误,暂停自动重启语音识别'); this.showNotification('语音识别暂停,请点击页面重新激活或使用键盘输入(F9)', 'warning'); } }; } handleResult(event) { this.updateStatus('processing'); let finalTranscript = ''; let interimTranscript = ''; let confidence = 0; // 处理识别结果 for (let i = event.resultIndex; i < event.results.length; i++) { const transcript = event.results[i][0].transcript; confidence = event.results[i][0].confidence || 0; if (event.results[i].isFinal) { finalTranscript += transcript; } else { interimTranscript += transcript; } } // 显示实时语音输入 const currentText = finalTranscript || interimTranscript; if (currentText) { this.showVoiceInput(currentText, confidence, !finalTranscript); } // 如果有最终结果,触发回调 if (finalTranscript && this.onResult) { console.log('语音识别结果:', finalTranscript); this.onResult(finalTranscript.trim()); // 短暂显示识别成功状态 setTimeout(() => { this.hideVoiceInput(); }, 2000); } this.updateStatus('ready'); } handleError(error) { console.error('语音识别错误:', error); this.updateStatus('error'); // no-speech不算作真正的错误,不影响错误计数 if (error !== 'no-speech') { // 记录错误时间和连续错误次数 const now = Date.now(); if (now - this.lastErrorTime < 5000) { // 5秒内的错误视为连续错误 this.consecutiveErrors++; } else { this.consecutiveErrors = 1; } this.lastErrorTime = now; } if (this.onError) { this.onError(error); } // 根据错误类型处理 switch (error) { case 'not-allowed': this.showPermissionError(); this.isDisabled = true; // 禁用自动重启 break; case 'no-speech': // 没有检测到语音是正常现象,不需要任何特殊处理 console.log('没有检测到语音(正常状态)- 继续监听'); // no-speech是正常状态,语音识别会自动继续运行 // 不需要重启、不需要延迟、不需要任何额外操作 break; case 'network': this.showNetworkError(); console.log('网络错误,准备重试...'); if (this.consecutiveErrors < 5) { // 网络问题容忍度适中 const delay = Math.min(3000 + (this.consecutiveErrors * 2000), 15000); setTimeout(async () => { if (this.isSupported && !this.isDisabled && !this.isListening) { console.log('网络错误重试中...'); await this.safeRestart(); } }, delay); } else { this.showNotification('网络连接持续异常,语音识别已暂停。请检查网络后双击页面重启', 'error'); this.addDoubleClickRecovery(); this.isDisabled = true; } break; case 'aborted': this.handleAbortedError(); break; default: // 其他错误,谨慎重试 console.log(`未知错误类型: ${error},准备重试...`); if (this.consecutiveErrors < 4) { const delay = this.retryDelay * Math.pow(1.5, this.consecutiveErrors - 1); setTimeout(async () => { if (this.isSupported && !this.isDisabled && !this.isListening) { console.log(`未知错误重试中(第${this.consecutiveErrors}次)...`); await this.safeRestart(); } }, delay); } else { this.showNotification(`语音识别遇到未知错误:${error}。已暂停,双击页面重启`, 'error'); this.addDoubleClickRecovery(); } break; } } handleAbortedError() { console.log('处理 aborted 错误,连续错误次数:', this.consecutiveErrors); // 更宽松的错误处理策略 if (this.consecutiveErrors >= 5) { this.handleAbortedErrorLoop(); } else { // 先完全清理当前状态 this.forceCleanup(); // 使用指数退避算法计算延迟时间 const baseDelay = 2000; const delay = Math.min(baseDelay * Math.pow(2, this.consecutiveErrors - 1), 30000); console.log(`Aborted 错误(第${this.consecutiveErrors}次),${delay}ms 后重试`); // 添加状态检查的重试逻辑 setTimeout(async () => { if (this.isSupported && !this.isDisabled && !this.isListening && !this.isStarting) { console.log('重试启动语音识别...'); await this.safeRestart(); } else { console.log('跳过重试,当前状态不允许启动'); } }, delay); } } handleAbortedErrorLoop() { console.warn('检测到 aborted 错误循环,暂停自动重启'); this.isDisabled = true; // 显示更详细的错误信息和解决方案 this.showNotification( '语音识别遇到连续错误已暂停。解决方案:\n' + '1. 按Ctrl+R刷新页面\n' + '2. 使用键盘输入(F9键)\n' + '3. 检查麦克风权限\n' + '4. 双击页面手动重启', 'warning' ); // 添加页面双击恢复功能 this.addDoubleClickRecovery(); // 减少自动恢复时间到2分钟,但保持更保守的重启策略 setTimeout(() => { this.resetErrorState(); this.showNotification('语音识别已重新启用,双击页面激活', 'info'); }, 120000); // 2分钟 } // 添加双击恢复功能 addDoubleClickRecovery() { if (this.doubleClickRecoveryAdded) return; const handleDoubleClick = async (event) => { if (this.isDisabled) { console.log('用户双击恢复语音识别'); this.showNotification('正在重启语音识别...', 'info'); // 完全重置并重启 this.resetErrorState(); await this.forceCleanup(); await new Promise(resolve => setTimeout(resolve, 1000)); if (this.isSupported) { try { await this.startListening(); this.showNotification('语音识别已成功重启!', 'info'); } catch (error) { this.showNotification('重启失败,请稍后再试或刷新页面', 'error'); } } } }; document.addEventListener('dblclick', handleDoubleClick); this.doubleClickRecoveryAdded = true; console.log('已添加双击恢复功能'); } shouldAttemptRestart() { // 判断是否应该尝试重启 if (this.isDisabled) return false; if (this.consecutiveErrors >= 5) return false; return true; } calculateRestartDelay() { // 计算重启延迟时间 let baseDelay = this.retryDelay; // 根据连续错误次数增加延迟 const multiplier = Math.min(this.consecutiveErrors, 5); baseDelay *= multiplier; return Math.min(baseDelay, 30000); // 最大30秒 } resetErrorState() { // 重置错误状态 this.consecutiveErrors = 0; this.retryCount = 0; this.isDisabled = false; this.lastErrorTime = 0; console.log('错误状态已重置'); } // 强制清理所有状态和资源 forceCleanup() { console.log('执行强制清理...'); // 清除所有定时器 if (this.restartTimer) { clearTimeout(this.restartTimer); this.restartTimer = null; } if (this.hideTimer) { clearTimeout(this.hideTimer); this.hideTimer = null; } // 强制停止语音识别 if (this.recognition) { try { this.recognition.abort(); } catch (error) { console.log('强制停止时出错:', error); } } // 重置所有状态 this.isListening = false; this.isStarting = false; this.isStopping = false; console.log('强制清理完成'); } // 安全重启 - 带有更完善的状态检查 async safeRestart() { console.log('执行安全重启...'); try { // 1. 强制清理 this.forceCleanup(); // 2. 等待一段时间确保清理完成 await new Promise(resolve => setTimeout(resolve, 1000)); // 3. 状态验证 if (this.isDisabled) { console.log('语音识别已禁用,取消重启'); return; } if (this.isListening || this.isStarting) { console.log('语音识别状态异常,取消重启'); return; } // 4. 重新初始化并启动 if (this.recognition) { console.log('重新配置语音识别...'); this.setupRecognition(); await new Promise(resolve => setTimeout(resolve, 500)); console.log('启动语音识别...'); this.recognition.start(); console.log('安全重启成功'); } } catch (error) { console.error('安全重启失败:', error); this.consecutiveErrors++; // 如果安全重启也失败,增加更长的延迟后再试 if (this.consecutiveErrors < 5) { setTimeout(() => this.safeRestart(), 5000); } } } async startListening() { if (!this.isSupported) { this.showUnsupportedError(); return; } if (this.isDisabled) { console.log('语音识别已禁用,需要手动重启'); return; } // 增强的并发控制 if (this.isStarting) { console.log('语音识别正在启动中,等待完成...'); // 等待当前启动完成 let attempts = 0; while (this.isStarting && attempts < 10) { await new Promise(resolve => setTimeout(resolve, 500)); attempts++; } if (this.isStarting) { console.error('启动超时,强制清理'); this.forceCleanup(); } return; } if (this.isListening) { console.log('语音识别已在运行中,跳过启动'); return; } // 如果正在停止,等待停止完成 if (this.isStopping) { console.log('等待停止完成后重新启动...'); let stopAttempts = 0; while (this.isStopping && stopAttempts < 6) { await new Promise(resolve => setTimeout(resolve, 500)); stopAttempts++; } if (this.isStopping) { console.log('停止超时,强制清理'); this.forceCleanup(); } } this.isStarting = true; console.log('开始启动语音识别...'); try { // 1. 完全清理之前的状态 await this.ensureFullyStoppedAsync(); // 2. 额外的清理延迟 await new Promise(resolve => setTimeout(resolve, 800)); // 3. 最终状态检查 if (this.isDisabled) { console.log('启动被取消:语音识别已禁用'); return; } if (this.isListening) { console.log('启动被取消:语音识别已在运行'); return; } // 4. 尝试启动 console.log('尝试启动语音识别...'); this.recognition.start(); console.log('语音识别启动命令已发送'); } catch (error) { console.error('启动语音识别失败:', error); // 特别处理常见错误 if (error.toString().includes('already started')) { console.log('检测到重复启动错误,清理状态'); this.forceCleanup(); } else { this.handleError('start-failed'); } } finally { // 延迟清除启动标志,确保onstart事件有时间触发 setTimeout(() => { this.isStarting = false; }, 1000); } } // 确保语音识别完全停止 async ensureFullyStoppedAsync() { if (!this.recognition) return; return new Promise((resolve) => { if (!this.isListening) { resolve(); return; } this.isStopping = true; // 设置停止完成监听器 const onStopComplete = () => { this.recognition.removeEventListener('end', onStopComplete); this.isStopping = false; this.isListening = false; console.log('语音识别已完全停止'); resolve(); }; this.recognition.addEventListener('end', onStopComplete); // 强制停止 try { this.recognition.abort(); } catch (error) { console.log('停止时出现错误:', error); // 即使出错也要继续 setTimeout(() => { this.isStopping = false; this.isListening = false; resolve(); }, 1000); } // 超时保护 setTimeout(() => { if (this.isStopping) { console.log('停止超时,强制完成'); this.recognition.removeEventListener('end', onStopComplete); this.isStopping = false; this.isListening = false; resolve(); } }, 3000); }); } async stopListening() { if (this.recognition && this.isListening) { console.log('停止语音识别...'); // 清除重启定时器 if (this.restartTimer) { clearTimeout(this.restartTimer); this.restartTimer = null; } await this.ensureFullyStoppedAsync(); } } // 重启语音识别 async restart() { console.log('手动重启语音识别系统...'); try { // 显示重启进度 this.showNotification('正在重启语音识别...', 'info'); // 1. 重置错误状态 this.resetErrorState(); // 2. 强制清理所有状态 this.forceCleanup(); // 3. 等待清理完成 await new Promise(resolve => setTimeout(resolve, 1500)); // 4. 检查支持状态 if (!this.isSupported) { this.showNotification('当前浏览器不支持语音识别', 'error'); return; } // 5. 重新初始化 this.setupRecognition(); await new Promise(resolve => setTimeout(resolve, 500)); // 6. 启动语音识别 if (!this.isDisabled) { console.log('执行重启...'); await this.startListening(); this.showNotification('语音识别已成功重启!', 'info'); } else { this.showNotification('语音识别重启完成,但仍处于禁用状态', 'warning'); } } catch (error) { console.error('重启失败:', error); this.showNotification('重启失败,建议刷新页面重试', 'error'); // 重启失败时的降级方案 this.resetErrorState(); } } // 手动激活语音识别(用于用户点击按钮) async manualActivate() { console.log('手动激活语音识别'); this.resetErrorState(); if (this.isSupported) { await this.startListening(); this.showNotification('语音识别已激活', 'info'); } else { this.showNotification('当前浏览器不支持语音识别', 'error'); } } // 永久禁用语音识别 disable() { console.log('禁用语音识别'); this.isDisabled = true; if (this.recognition && this.isListening) { this.recognition.abort(); } this.showNotification('语音识别已禁用', 'warning'); } // 启用语音识别 async enable() { console.log('启用语音识别'); this.resetErrorState(); if (this.isSupported) { this.showNotification('语音识别已启用', 'info'); await this.startListening(); } } updateStatus(status) { if (this.onStatusChange) { this.onStatusChange(status); } } showVoiceInput(text, confidence = 0, isInterim = false) { const voiceDisplay = document.getElementById('voiceInputDisplay'); const speechText = document.getElementById('speechText'); const speechConfidence = document.getElementById('speechConfidence'); const speechBubble = voiceDisplay.querySelector('.speech-bubble'); if (!voiceDisplay || !speechText) return; // 显示语音输入框 voiceDisplay.style.display = 'block'; // 更新文本内容 speechText.textContent = text; // 显示置信度 if (confidence > 0) { speechConfidence.textContent = `置信度: ${Math.round(confidence * 100)}%`; } else { speechConfidence.textContent = isInterim ? '正在识别...' : ''; } // 更新样式状态 speechBubble.className = 'speech-bubble'; if (isInterim) { speechBubble.classList.add('listening'); } else { speechBubble.classList.add('recognized'); } // 清除之前的隐藏定时器 if (this.hideTimer) { clearTimeout(this.hideTimer); } // 如果是临时结果,设置自动隐藏 if (isInterim) { this.hideTimer = setTimeout(() => { this.hideVoiceInput(); }, 3000); } } hideVoiceInput() { const voiceDisplay = document.getElementById('voiceInputDisplay'); if (voiceDisplay) { voiceDisplay.style.display = 'none'; } if (this.hideTimer) { clearTimeout(this.hideTimer); this.hideTimer = null; } } showPermissionError() { this.showNotification('需要麦克风权限才能使用语音功能', 'error'); } showNetworkError() { this.showNotification('网络连接错误,语音识别暂时不可用', 'error'); } showUnsupportedError() { this.showNotification('您的浏览器不支持语音识别功能', 'error'); } showNotification(message, type = 'info') { // 创建通知元素 const notification = document.createElement('div'); notification.className = `voice-notification ${type}`; notification.textContent = message; // 样式 Object.assign(notification.style, { position: 'fixed', top: '20px', right: '20px', background: type === 'error' ? '#e74c3c' : '#4ecdc4', color: 'white', padding: '15px 20px', borderRadius: '10px', boxShadow: '0 4px 15px rgba(0,0,0,0.2)', zIndex: '10000', fontSize: '14px', maxWidth: '300px', animation: 'slideInRight 0.3s ease' }); document.body.appendChild(notification); // 自动移除 setTimeout(() => { notification.style.animation = 'slideOutRight 0.3s ease'; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }, 5000); } // 测试语音识别功能 test() { if (!this.isSupported) { console.log('语音识别不支持'); return false; } console.log('语音识别测试开始...'); // 设置测试回调 const originalOnResult = this.onResult; this.onResult = (transcript) => { console.log('测试结果:', transcript); this.onResult = originalOnResult; }; return true; } // 获取支持的语言列表 getSupportedLanguages() { return [ { code: 'en-US', name: 'English (US)' }, { code: 'zh-CN', name: '中文 (普通话)' }, { code: 'ja-JP', name: '日本語' }, { code: 'ko-KR', name: '한국어' }, { code: 'fr-FR', name: 'Français' }, { code: 'de-DE', name: 'Deutsch' }, { code: 'es-ES', name: 'Español' }, { code: 'it-IT', name: 'Italiano' } ]; } // 切换语言 setLanguage(langCode) { if (this.recognition) { this.recognition.lang = langCode; console.log(`语音识别语言切换为: ${langCode}`); } } // 获取当前状态 getStatus() { return { isSupported: this.isSupported, isListening: this.isListening, isDisabled: this.isDisabled, isStarting: this.isStarting, isStopping: this.isStopping, consecutiveErrors: this.consecutiveErrors, hasRestartTimer: !!this.restartTimer, currentLanguage: this.recognition ? this.recognition.lang : null, doubleClickRecoveryEnabled: this.doubleClickRecoveryAdded, healthStatus: this.getHealthStatus() }; } // 获取系统健康状况 getHealthStatus() { if (!this.isSupported) return 'unsupported'; if (this.isDisabled) return 'disabled'; if (this.consecutiveErrors >= 5) return 'critical'; if (this.consecutiveErrors >= 3) return 'warning'; if (this.isListening) return 'healthy'; if (this.isStarting) return 'starting'; return 'ready'; } // 快速诊断和修复 async quickFix() { console.log('执行快速诊断和修复...'); const status = this.getHealthStatus(); console.log('当前健康状况:', status); switch (status) { case 'unsupported': this.showNotification('浏览器不支持语音识别功能', 'error'); return false; case 'disabled': case 'critical': case 'warning': console.log('检测到问题,执行完整重启...'); await this.restart(); return true; case 'starting': console.log('正在启动中,等待完成...'); return true; case 'ready': console.log('系统就绪,尝试启动...'); await this.startListening(); return true; case 'healthy': console.log('系统运行正常'); return true; default: console.log('未知状态,执行重启...'); await this.restart(); return true; } } // 清理所有资源和状态 cleanup() { console.log('清理语音识别资源...'); // 清除所有定时器 if (this.restartTimer) { clearTimeout(this.restartTimer); this.restartTimer = null; } if (this.hideTimer) { clearTimeout(this.hideTimer); this.hideTimer = null; } // 停止语音识别 if (this.recognition) { try { this.recognition.abort(); } catch (error) { console.log('清理时停止识别出错:', error); } } // 重置状态 this.isListening = false; this.isStarting = false; this.isStopping = false; this.isDisabled = true; console.log('语音识别资源清理完成'); } } // 添加CSS动画 const style = document.createElement('style'); style.textContent = ` @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } .voice-notification { font-family: 'Comic Neue', cursive; font-weight: bold; word-wrap: break-word; } `; document.head.appendChild(style);