diff --git a/src/main/ipc/live.ts b/src/main/ipc/live.ts index 30e3775..ff95843 100644 --- a/src/main/ipc/live.ts +++ b/src/main/ipc/live.ts @@ -40,6 +40,7 @@ export function setupLiveHandlers() { hasLiveWindow: !!liveState.liveWindow, isVideoInserted: liveState.isVideoInserted, cameraActive: liveState.cameraActive, + sessionId: liveState.sessionId }, }; }); @@ -104,6 +105,9 @@ export function setupLiveHandlers() { } liveState.sessionId = sessionResult.sessionId; + // socket + + // 5. 设置直播状态 liveState.isLiveOn = true; @@ -200,7 +204,6 @@ export function setupLiveHandlers() { type: "echo", interrupt: true, }; - await sendMessage(params); return { success: true }; } catch (error: any) { diff --git a/src/renderer/src/types/socket.ts b/src/renderer/src/types/socket.ts new file mode 100644 index 0000000..497ccaf --- /dev/null +++ b/src/renderer/src/types/socket.ts @@ -0,0 +1,38 @@ +// src/types/socket.ts + +/** + * Socket 配置项接口 + */ +export interface SocketOptions { + /** 心跳间隔(ms),默认 30000 */ + heartbeatInterval?: number; + /** 初始重连间隔(ms),默认 3000 */ + reconnectInterval?: number; + /** 最大重连次数,默认 10 */ + maxReconnectCount?: number; + /** 消息接收回调 */ + onMessage?: (data: string | ArrayBuffer | Blob) => void; + /** 错误回调 */ + onError?: (error: Event) => void; + /** 关闭回调 */ + onClose?: (event: CloseEvent) => void; + /** 连接成功回调 */ + onOpen?: () => void; +} + +/** + * 消息记录类型 + */ +export interface MessageItem { + time: string; + type: 'send' | 'receive' | 'system'; + content: string | object; +} + +/** + * 心跳包类型(可根据后端格式调整) + */ +export interface HeartbeatData { + type: 'ping' | 'pong'; + timestamp?: number; +} \ No newline at end of file diff --git a/src/renderer/src/utils/socket.ts b/src/renderer/src/utils/socket.ts new file mode 100644 index 0000000..2776aa9 --- /dev/null +++ b/src/renderer/src/utils/socket.ts @@ -0,0 +1,175 @@ +// src/utils/socket.ts +import type { SocketOptions, HeartbeatData } from "@renderer/types/socket"; + +/** + * WebSocket 长连接工具类(TS 版) + * @class SocketClient + * @param {string} url - WS/WSS 连接地址 + * @param {SocketOptions} options - 配置项 + */ +class SocketClient { + private url: string; // 连接地址(私有属性) + private ws: WebSocket | null; // WebSocket 实例 + private isConnected: boolean; // 连接状态 + private heartbeatTimer: NodeJS.Timeout | null; // 心跳定时器 + private reconnectTimer: NodeJS.Timeout | null; // 重连定时器 + private reconnectCount: number; // 已重连次数 + private config: Required; // 完整配置(包含默认值) + + constructor(url: string, options: SocketOptions = {}) { + this.url = url; + this.ws = null; + this.isConnected = false; + this.heartbeatTimer = null; + this.reconnectTimer = null; + this.reconnectCount = 0; + + // 合并默认配置与用户配置(确保所有配置项有值) + this.config = { + heartbeatInterval: options.heartbeatInterval || 30000, + reconnectInterval: options.reconnectInterval || 3000, + maxReconnectCount: options.maxReconnectCount || 10, + onMessage: options.onMessage || (() => {}), + onError: options.onError || (() => {}), + onClose: options.onClose || (() => {}), + onOpen: options.onOpen || (() => {}) + }; + + // 初始化连接 + this.init(); + } + + /** + * 初始化 WebSocket 连接 + */ + private init(): void { + // 关闭已有连接 + this.close(); + + try { + this.ws = new WebSocket(this.url); + + // 连接成功 + this.ws.onopen = (): void => { + this.isConnected = true; + this.reconnectCount = 0; + this.config.onOpen(); + this.startHeartbeat(); + }; + + // 接收消息 + this.ws.onmessage = (event: MessageEvent): void => { + this.config.onMessage(event.data); + }; + + // 错误回调 + this.ws.onerror = (error: Event): void => { + this.isConnected = false; + this.config.onError(error); + }; + + // 关闭回调 + this.ws.onclose = (event: CloseEvent): void => { + this.isConnected = false; + this.config.onClose(event); + this.stopHeartbeat(); + + // 自动重连(未超过最大次数) + if (this.reconnectCount < this.config.maxReconnectCount) { + this.reconnect(); + } + }; + } catch (error) { + this.config.onError(error as Event); + this.reconnect(); + } + } + + /** + * 启动心跳保活 + */ + private startHeartbeat(): void { + this.stopHeartbeat(); + + this.heartbeatTimer = setInterval(() => { + if (this.isConnected && this.ws) { + // 发送心跳包(强类型) + const heartbeatData: HeartbeatData = { + type: 'ping', + timestamp: Date.now() + }; + this.send(heartbeatData); + } + }, this.config.heartbeatInterval); + } + + /** + * 停止心跳 + */ + private stopHeartbeat(): void { + if (this.heartbeatTimer) { + clearInterval(this.heartbeatTimer); + this.heartbeatTimer = null; + } + } + + /** + * 发送消息 + * @param {string | object} data - 要发送的消息(字符串/对象) + * @throws {Error} 连接未建立时抛出错误 + */ + public send(data: string | object): void { + if (!this.isConnected || !this.ws) { + throw new Error('WebSocket 未连接,无法发送消息'); + } + + // 类型判断:对象转为 JSON 字符串 + const sendData: string = typeof data === 'object' + ? JSON.stringify(data) + : data; + + this.ws.send(sendData); + } + + /** + * 断线重连(阶梯式间隔) + */ + private reconnect(): void { + this.reconnectCount++; + const currentInterval = this.config.reconnectInterval * this.reconnectCount; + + this.reconnectTimer = setTimeout(() => { + console.log(`WebSocket 第 ${this.reconnectCount} 次重连...`); + this.init(); + }, currentInterval); + } + + /** + * 主动关闭连接 + * @param {number} code - 关闭码(默认 1000:正常关闭) + * @param {string} reason - 关闭原因 + */ + public close(code: number = 1000, reason: string = '主动关闭连接'): void { + if (this.ws) { + this.ws.close(code, reason); + this.ws = null; + } + this.isConnected = false; + this.stopHeartbeat(); + + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + } + + /** + * 获取当前连接状态 + * @returns {boolean} 连接状态 + */ + public getConnectedState(): boolean { + return this.isConnected; + } +} + +export default SocketClient; \ No newline at end of file diff --git a/src/renderer/src/views/Home/index.vue b/src/renderer/src/views/Home/index.vue index 62c63ff..2ec0fe5 100644 --- a/src/renderer/src/views/Home/index.vue +++ b/src/renderer/src/views/Home/index.vue @@ -20,40 +20,50 @@ -
- 岗位列表 +
+ + + + + 岗位列表 + + + + + 添加岗位 + + + + -