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",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"build:unpack": "npm run build && electron-builder --dir",
|
"build:unpack": "npm run build && electron-builder --dir",
|
||||||
"build:win": "npm run build && electron-builder --win",
|
"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:mac": "npm run build && electron-builder --mac",
|
||||||
"build:linux": "npm run build && electron-builder --linux"
|
"build:linux": "npm run build && electron-builder --linux"
|
||||||
},
|
},
|
||||||
@@ -49,9 +50,9 @@
|
|||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-plugin-vue": "^9.20.1",
|
"eslint-plugin-vue": "^9.20.1",
|
||||||
"prettier": "^3.2.4",
|
"prettier": "^3.2.4",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
"vue": "^3.4.15",
|
"vue": "^3.4.15",
|
||||||
"vue-tsc": "^1.8.27"
|
"vue-tsc": "^3.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export function setupLiveHandlers() {
|
|||||||
hasLiveWindow: !!liveState.liveWindow,
|
hasLiveWindow: !!liveState.liveWindow,
|
||||||
isVideoInserted: liveState.isVideoInserted,
|
isVideoInserted: liveState.isVideoInserted,
|
||||||
cameraActive: liveState.cameraActive,
|
cameraActive: liveState.cameraActive,
|
||||||
|
sessionId: liveState.sessionId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -104,6 +105,8 @@ export function setupLiveHandlers() {
|
|||||||
}
|
}
|
||||||
liveState.sessionId = sessionResult.sessionId;
|
liveState.sessionId = sessionResult.sessionId;
|
||||||
|
|
||||||
|
// socket
|
||||||
|
|
||||||
// 5. 设置直播状态
|
// 5. 设置直播状态
|
||||||
liveState.isLiveOn = true;
|
liveState.isLiveOn = true;
|
||||||
|
|
||||||
@@ -200,7 +203,6 @@ export function setupLiveHandlers() {
|
|||||||
type: "echo",
|
type: "echo",
|
||||||
interrupt: true,
|
interrupt: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
await sendMessage(params);
|
await sendMessage(params);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -238,23 +240,34 @@ export function setupLiveHandlers() {
|
|||||||
allowRunningInsecureContent: true, // 允许运行本地内容
|
allowRunningInsecureContent: true, // 允许运行本地内容
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// liveState.liveWindow.webContents.openDevTools();
|
liveState.liveWindow.webContents.openDevTools();
|
||||||
liveState.liveWindow.on("closed", () => {
|
liveState.liveWindow.on("closed", () => {
|
||||||
liveState.liveWindow = null;
|
liveState.liveWindow = null;
|
||||||
liveState.isLiveOn = false;
|
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) {
|
if (ELECTRON_RENDERER_URL) {
|
||||||
|
let liveUrl = `${ELECTRON_RENDERER_URL}/#/live`;
|
||||||
|
if (liveState.userId) {
|
||||||
|
liveUrl += `?userId=${liveState.userId}`;
|
||||||
|
}
|
||||||
await liveState.liveWindow.loadURL(liveUrl);
|
await liveState.liveWindow.loadURL(liveUrl);
|
||||||
} else {
|
} else {
|
||||||
await liveState.liveWindow.loadFile(indexHtml, { hash: "/live" });
|
await liveState.liveWindow.loadFile(indexHtml, {
|
||||||
|
hash: "live",
|
||||||
|
search: `userId=${liveState.userId}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待页面加载完成
|
// 等待页面加载完成
|
||||||
async function waitForIframeVideoPlaying(
|
async function waitForIframeVideoPlaying(
|
||||||
webContents: Electron.WebContents,
|
webContents: Electron.WebContents,
|
||||||
timeoutMs = 20000,
|
timeoutMs = 120000, // 2 分钟超时
|
||||||
) {
|
) {
|
||||||
console.log("开始检测 iframe 内视频的实际播放状态...");
|
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() {
|
function startLive() {
|
||||||
const paramsUserId = route.query.userId;
|
let paramsUserId = route.query.userId;
|
||||||
console.log(paramsUserId)
|
|
||||||
|
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) {
|
if (paramsUserId) {
|
||||||
userId.value = paramsUserId
|
userId.value = paramsUserId;
|
||||||
console.log(userId.value)
|
|
||||||
// 直播数字人 正式地址
|
|
||||||
liveUrl.value = `https://dmdemo.hx.cn/dashboard.html?userId=${userId.value}`;
|
liveUrl.value = `https://dmdemo.hx.cn/dashboard.html?userId=${userId.value}`;
|
||||||
// 测试地址
|
|
||||||
// liveUrl.value = "https://www.baidu.com/"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user