Compare commits
8 Commits
92ec6504f9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd72faa9a6 | ||
|
|
a2808e47ac | ||
|
|
3cd7514066 | ||
|
|
6641e07b8a | ||
|
|
634610ca7e | ||
|
|
9f18b32c7e | ||
|
|
de65b813e1 | ||
|
|
9792c88e53 |
BIN
build/icon.ico
BIN
build/icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 59 KiB |
BIN
build/icon3.ico
Normal file
BIN
build/icon3.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
8307
package-lock.json
generated
8307
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "npm run build && electron-builder --dir",
|
||||
"build:win": "npm run build && electron-builder --win",
|
||||
"build:win:x64": "npm run build && electron-builder --win --x64",
|
||||
"build:mac": "npm run build && electron-builder --mac",
|
||||
"build:linux": "npm run build && electron-builder --linux"
|
||||
},
|
||||
@@ -49,9 +50,9 @@
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-vue": "^9.20.1",
|
||||
"prettier": "^3.2.4",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^5.0.12",
|
||||
"vue": "^3.4.15",
|
||||
"vue-tsc": "^1.8.27"
|
||||
"vue-tsc": "^3.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export function setupLiveHandlers() {
|
||||
hasLiveWindow: !!liveState.liveWindow,
|
||||
isVideoInserted: liveState.isVideoInserted,
|
||||
cameraActive: liveState.cameraActive,
|
||||
sessionId: liveState.sessionId,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -104,6 +105,8 @@ export function setupLiveHandlers() {
|
||||
}
|
||||
liveState.sessionId = sessionResult.sessionId;
|
||||
|
||||
// socket
|
||||
|
||||
// 5. 设置直播状态
|
||||
liveState.isLiveOn = true;
|
||||
|
||||
@@ -200,7 +203,6 @@ export function setupLiveHandlers() {
|
||||
type: "echo",
|
||||
interrupt: true,
|
||||
};
|
||||
|
||||
await sendMessage(params);
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
@@ -238,23 +240,34 @@ export function setupLiveHandlers() {
|
||||
allowRunningInsecureContent: true, // 允许运行本地内容
|
||||
},
|
||||
});
|
||||
// liveState.liveWindow.webContents.openDevTools();
|
||||
liveState.liveWindow.webContents.openDevTools();
|
||||
liveState.liveWindow.on("closed", () => {
|
||||
liveState.liveWindow = null;
|
||||
liveState.isLiveOn = false;
|
||||
});
|
||||
console.log(liveUrl);
|
||||
// if (ELECTRON_RENDERER_URL) {
|
||||
// await liveState.liveWindow.loadURL(liveUrl);
|
||||
// } else {
|
||||
// await liveState.liveWindow.loadFile(indexHtml, { hash: "/live" });
|
||||
// }
|
||||
if (ELECTRON_RENDERER_URL) {
|
||||
let liveUrl = `${ELECTRON_RENDERER_URL}/#/live`;
|
||||
if (liveState.userId) {
|
||||
liveUrl += `?userId=${liveState.userId}`;
|
||||
}
|
||||
await liveState.liveWindow.loadURL(liveUrl);
|
||||
} else {
|
||||
await liveState.liveWindow.loadFile(indexHtml, { hash: "/live" });
|
||||
await liveState.liveWindow.loadFile(indexHtml, {
|
||||
hash: "live",
|
||||
search: `userId=${liveState.userId}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 等待页面加载完成
|
||||
async function waitForIframeVideoPlaying(
|
||||
webContents: Electron.WebContents,
|
||||
timeoutMs = 20000,
|
||||
timeoutMs = 120000, // 2 分钟超时
|
||||
) {
|
||||
console.log("开始检测 iframe 内视频的实际播放状态...");
|
||||
|
||||
|
||||
38
src/renderer/src/types/socket.ts
Normal file
38
src/renderer/src/types/socket.ts
Normal file
@@ -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;
|
||||
}
|
||||
175
src/renderer/src/utils/socket.ts
Normal file
175
src/renderer/src/utils/socket.ts
Normal file
@@ -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<SocketOptions>; // 完整配置(包含默认值)
|
||||
|
||||
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;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -190,15 +190,23 @@ onBeforeUnmount(() => {
|
||||
})
|
||||
|
||||
function startLive() {
|
||||
const paramsUserId = route.query.userId;
|
||||
console.log(paramsUserId)
|
||||
let paramsUserId = route.query.userId;
|
||||
|
||||
if (!paramsUserId) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
paramsUserId = urlParams.get('userId');
|
||||
}
|
||||
|
||||
if (!paramsUserId && window.location.hash.includes('?')) {
|
||||
const hashQuery = window.location.hash.split('?')[1];
|
||||
const searchParams = new URLSearchParams(hashQuery);
|
||||
paramsUserId = searchParams.get('userId');
|
||||
}
|
||||
|
||||
|
||||
if (paramsUserId) {
|
||||
userId.value = paramsUserId
|
||||
console.log(userId.value)
|
||||
// 直播数字人 正式地址
|
||||
userId.value = paramsUserId;
|
||||
liveUrl.value = `https://dmdemo.hx.cn/dashboard.html?userId=${userId.value}`;
|
||||
// 测试地址
|
||||
// liveUrl.value = "https://www.baidu.com/"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user