MagicPot / js /voice.js
zhanglei
fix voice
ab5909a
Raw
History Blame Contribute Delete
31.1 kB
// 语音识别类
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);