wangzhibo
2026-08-30 c8515170fd399b4d2dfaebb59128d4674e58915e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
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();
}