import { uiController } from '../../ui/controller.js?v=0205'; import { log } from '../../utils/logger.js?v=0205'; let wakewordSocket = null; let reconnectTimer = null; let reconnectAttempts = 0; let shouldReconnect = true; let wakewordRequestSeq = 0; let onNextBridgeConnectedCallback = null; const pendingWakewordRequests = new Map(); export function startWakewordBridgeListener() { if (wakewordSocket) { return wakewordSocket; } shouldReconnect = true; log('正在连接本地唤醒事件桥...', 'info'); tryConnect(); return wakewordSocket; } function tryConnect() { const bridgeUrl = buildWakewordBridgeUrl(); try { wakewordSocket = new WebSocket(bridgeUrl); wakewordSocket.onopen = () => { reconnectAttempts = 0; log(`本地唤醒事件桥已连接: ${bridgeUrl}`, 'success'); // 连接成功后自动保存地址,刷新后仍能记住 localStorage.setItem('xz_tester_wakewordWsUrl', bridgeUrl); const urlInput = document.getElementById('wakewordWsUrl'); if (urlInput) urlInput.value = bridgeUrl; }; wakewordSocket.onerror = () => { log(`本地唤醒事件桥连接失败: ${bridgeUrl}`, 'error'); }; wakewordSocket.onmessage = async (event) => { try { const message = parseWakewordBridgeMessage(event.data); if (message.requestId && pendingWakewordRequests.has(message.requestId)) { settleWakewordRequest(message); return; } if (message.success === false) { log(`本地唤醒事件桥返回错误: ${message.error || '未知错误'}`, 'error'); return; } if (message.type === 'bridge_connected') { log('本地唤醒监听已就绪', 'info'); if (onNextBridgeConnectedCallback) { const cb = onNextBridgeConnectedCallback; onNextBridgeConnectedCallback = null; cb(message); } return; } if (message.type === 'service_ready') { log('本地唤醒服务已启动', 'info'); return; } if (message.type === 'wakeword_config') { uiController.applyWakewordConfig(message.payload || {}); log('已同步本地唤醒词配置', 'info'); return; } if (message.type === 'service_stopping') { log('本地唤醒服务正在停止', 'warning'); return; } if (message.type === 'wake_word_detected') { const wakeWord = message.payload?.wake_word || '唤醒词'; log(`检测到本地唤醒事件: ${wakeWord}`, 'info'); await uiController.triggerWakewordDial(wakeWord); } } catch (error) { log(`解析本地唤醒事件失败: ${error.message}`, 'error'); } }; wakewordSocket.onclose = () => { if (wakewordSocket) { wakewordSocket = null; } rejectAllWakewordRequests('本地唤醒事件桥已断开'); if (!shouldReconnect) { return; } if (reconnectTimer) { return; } reconnectAttempts += 1; const delay = Math.min(1000 * reconnectAttempts, 5000); log(`本地唤醒事件桥将在 ${delay}ms 后重连: ${bridgeUrl}`, 'warning'); reconnectTimer = window.setTimeout(() => { reconnectTimer = null; tryConnect(); }, delay); }; return wakewordSocket; } catch (error) { log(`启动本地唤醒监听失败: ${error.message}`, 'error'); return null; } } export function stopWakewordBridgeListener() { shouldReconnect = false; if (reconnectTimer) { window.clearTimeout(reconnectTimer); reconnectTimer = null; } if (!wakewordSocket) { return; } wakewordSocket.onclose = null; wakewordSocket.close(); wakewordSocket = null; } export function sendWakewordBridgeMessage(type, payload = {}, requestId = null) { if (!wakewordSocket || wakewordSocket.readyState !== WebSocket.OPEN) { log('本地唤醒事件桥未连接,无法发送消息', 'warning'); return false; } wakewordSocket.send(JSON.stringify({ type, requestId, payload, })); return true; } export function requestWakewordBridge(type, payload = {}, timeout = 5000) { const requestId = `wakeword-${Date.now()}-${++wakewordRequestSeq}`; return new Promise((resolve, reject) => { const timer = window.setTimeout(() => { pendingWakewordRequests.delete(requestId); reject(new Error('本地唤醒服务响应超时')); }, timeout); pendingWakewordRequests.set(requestId, { resolve, reject, timer }); if (!sendWakewordBridgeMessage(type, payload, requestId)) { window.clearTimeout(timer); pendingWakewordRequests.delete(requestId); reject(new Error('本地唤醒事件桥未连接')); } }); } export function getWakewordBridgeUrl() { if (wakewordSocket && wakewordSocket.url) { return wakewordSocket.url; } return buildWakewordBridgeUrl(); } export function onNextBridgeConnected(callback) { onNextBridgeConnectedCallback = callback; } function buildWakewordBridgeUrl() { const configured = localStorage.getItem('xz_tester_wakewordWsUrl'); if (configured && configured.trim()) { return configured.trim(); } return 'ws://127.0.0.1:8006/wakeword-ws'; } function parseWakewordBridgeMessage(rawData) { const message = JSON.parse(rawData); return { type: message.type || '', requestId: message.requestId || null, success: message.success !== false, payload: message.payload || {}, error: message.error || null, }; } function settleWakewordRequest(message) { const pendingRequest = pendingWakewordRequests.get(message.requestId); if (!pendingRequest) { return; } window.clearTimeout(pendingRequest.timer); pendingWakewordRequests.delete(message.requestId); if (message.success === false) { pendingRequest.reject(new Error(message.error || '本地唤醒服务返回失败')); return; } pendingRequest.resolve(message); } function rejectAllWakewordRequests(errorMessage) { pendingWakewordRequests.forEach((pendingRequest) => { window.clearTimeout(pendingRequest.timer); pendingRequest.reject(new Error(errorMessage)); }); pendingWakewordRequests.clear(); }