flat: 暂存
This commit is contained in:
@@ -1,45 +1,124 @@
|
||||
import { ipcMain, BrowserWindow } from "electron";
|
||||
import { preload, indexHtml, ELECTRON_RENDERER_URL } from "../config";
|
||||
import { getSessionId, sendMessage } from "../Api/api";
|
||||
import { showPrompt } from "../utils/tools";
|
||||
let liveWindow: BrowserWindow | null = null;
|
||||
|
||||
// 直播状态管理
|
||||
interface LiveState {
|
||||
sessionId: string | null;
|
||||
userId: string | null;
|
||||
isVideoInserted: boolean;
|
||||
isLiveOn: boolean;
|
||||
jobList: any[];
|
||||
currentJob: any;
|
||||
cameraActive: boolean;
|
||||
audioActive: boolean;
|
||||
liveWindow: BrowserWindow | null;
|
||||
}
|
||||
|
||||
// 全局直播状态
|
||||
const liveState: LiveState = {
|
||||
sessionId: null,
|
||||
userId: null,
|
||||
isVideoInserted: false,
|
||||
isLiveOn: false,
|
||||
jobList: [],
|
||||
currentJob: null,
|
||||
cameraActive: false,
|
||||
audioActive: false,
|
||||
liveWindow: null,
|
||||
};
|
||||
|
||||
// 直播相关的主进程处理
|
||||
export function setupLiveHandlers() {
|
||||
let LiveSessionId = null;
|
||||
let isVideoInserted = false;
|
||||
// 获取直播状态
|
||||
ipcMain.handle("get-live-status", async () => {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
isLiveOn: liveState.isLiveOn,
|
||||
hasLiveWindow: !!liveState.liveWindow,
|
||||
isVideoInserted: liveState.isVideoInserted,
|
||||
cameraActive: liveState.cameraActive,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// 切换摄像头插入状态
|
||||
ipcMain.on("toggle-camera-insert", async () => {
|
||||
if (liveWindow) {
|
||||
isVideoInserted = !isVideoInserted;
|
||||
liveWindow.webContents.send(
|
||||
if (liveState.liveWindow) {
|
||||
liveState.isVideoInserted = !liveState.isVideoInserted;
|
||||
liveState.liveWindow.webContents.send(
|
||||
"toggle-camera-insert",
|
||||
isVideoInserted,
|
||||
liveState.isVideoInserted,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// 插入摄像头视频
|
||||
ipcMain.on("insert-camera-video", async (_) => {
|
||||
if (liveWindow) {
|
||||
liveWindow.webContents.send("insert-camera-video");
|
||||
ipcMain.on("insert-camera-video", async () => {
|
||||
if (liveState.liveWindow) {
|
||||
liveState.liveWindow.webContents.send("insert-camera-video");
|
||||
liveState.cameraActive = true;
|
||||
} else {
|
||||
showPrompt("直播窗口未打开", "error");
|
||||
}
|
||||
});
|
||||
|
||||
// 插入音频
|
||||
ipcMain.on("insert-video-audio", async (_) => {
|
||||
if (liveWindow) {
|
||||
liveWindow.webContents.send("insert-video-audio");
|
||||
ipcMain.on("insert-video-audio", async () => {
|
||||
if (liveState.liveWindow) {
|
||||
liveState.liveWindow.webContents.send("insert-video-audio");
|
||||
liveState.audioActive = true;
|
||||
} else {
|
||||
showPrompt("直播窗口未打开", "error");
|
||||
}
|
||||
});
|
||||
|
||||
// 开始直播
|
||||
ipcMain.handle("start-live", async (_) => {
|
||||
// 开始直播 - 完整的直播启动流程
|
||||
ipcMain.handle("start-live", async (_, { userId } = {}) => {
|
||||
try {
|
||||
console.log("Starting live stream...");
|
||||
// TODO: 实现直播推流逻辑
|
||||
return { success: true };
|
||||
|
||||
// 1. 设置用户ID
|
||||
if (userId) {
|
||||
liveState.userId = userId;
|
||||
}
|
||||
|
||||
// 2. 创建或显示直播窗口
|
||||
if (!liveState.liveWindow) {
|
||||
await createLiveWindow();
|
||||
}
|
||||
|
||||
// 3. 等待网页加载完成
|
||||
// await waitForPageLoad();
|
||||
|
||||
// 4. 获取 sessionId
|
||||
console.log("获取 sessionId");
|
||||
const sessionResult = await getSessionId({
|
||||
userId: liveState.userId,
|
||||
});
|
||||
console.log("sessionid", sessionResult);
|
||||
if (!sessionResult.success) {
|
||||
return { success: false, error: "获取会话ID失败" };
|
||||
}
|
||||
liveState.sessionId = sessionResult.sessionId;
|
||||
|
||||
// 5. 设置直播状态
|
||||
liveState.isLiveOn = true;
|
||||
|
||||
// 6. 发送欢迎消息
|
||||
await sendMessage({
|
||||
sessionid: liveState.sessionId,
|
||||
text: "大家好,欢迎来到我的直播间!",
|
||||
type: "echo",
|
||||
interrupt: true,
|
||||
});
|
||||
|
||||
showPrompt("直播已开始", "info");
|
||||
return { success: true, sessionId: liveState.sessionId };
|
||||
} catch (error: any) {
|
||||
console.error("Start live error:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
@@ -47,151 +126,273 @@ export function setupLiveHandlers() {
|
||||
// 结束直播
|
||||
ipcMain.handle("stop-live", async () => {
|
||||
try {
|
||||
// TODO: 实现结束直播逻辑
|
||||
liveState.isLiveOn = false;
|
||||
|
||||
// 关闭直播窗口
|
||||
if (liveState.liveWindow) {
|
||||
liveState.liveWindow.close();
|
||||
liveState.liveWindow = null;
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
liveState.sessionId = null;
|
||||
liveState.isVideoInserted = false;
|
||||
liveState.cameraActive = false;
|
||||
liveState.audioActive = false;
|
||||
|
||||
showPrompt("直播已结束", "info");
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("explain-position", async (_, content: string) => {
|
||||
// 强制关闭直播窗口
|
||||
ipcMain.handle("force-close-live", async () => {
|
||||
try {
|
||||
let params = {
|
||||
sessionid: LiveSessionId,
|
||||
console.log("Force closing live window...");
|
||||
|
||||
// 通知直播窗口清理所有媒体资源
|
||||
if (liveState.liveWindow) {
|
||||
try {
|
||||
// 发送清理指令到直播窗口
|
||||
liveState.liveWindow.webContents.send(
|
||||
"force-cleanup-media",
|
||||
);
|
||||
|
||||
// 等待一下确保清理指令被处理
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// 强制关闭窗口
|
||||
liveState.liveWindow.removeAllListeners();
|
||||
liveState.liveWindow.destroy();
|
||||
} catch (error) {
|
||||
console.warn("Error destroying window:", error);
|
||||
}
|
||||
liveState.liveWindow = null;
|
||||
}
|
||||
|
||||
// 强制重置所有状态
|
||||
liveState.isLiveOn = false;
|
||||
liveState.sessionId = null;
|
||||
liveState.isVideoInserted = false;
|
||||
liveState.cameraActive = false;
|
||||
liveState.audioActive = false;
|
||||
|
||||
showPrompt("直播窗口已强制关闭", "info");
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
console.error("Force close error:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// 提交消息给数字人平台
|
||||
ipcMain.handle("push-explain-position", async (_, content: string) => {
|
||||
try {
|
||||
if (!liveState.sessionId) {
|
||||
return { success: false, error: "直播未开始,无法发送消息" };
|
||||
}
|
||||
|
||||
const params = {
|
||||
sessionid: liveState.sessionId,
|
||||
text: content,
|
||||
type: "echo",
|
||||
interrupt: true,
|
||||
};
|
||||
|
||||
await sendMessage(params);
|
||||
// TODO: 实现结束直播逻辑
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
console.error("Send message error:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// 打开直播窗口
|
||||
ipcMain.handle("open-live-window", async (_, args) => {
|
||||
try {
|
||||
if (liveWindow) {
|
||||
liveWindow.focus();
|
||||
showPrompt("直播窗口已打开", "info");
|
||||
return { success: true };
|
||||
}
|
||||
const { width, height, path, userId } = args;
|
||||
let liveUrl = `${ELECTRON_RENDERER_URL}/#/${path}`;
|
||||
if (userId) {
|
||||
liveUrl += `?userId=${userId}`;
|
||||
}
|
||||
// TODO: 实现打开直播窗口逻辑
|
||||
liveWindow = new BrowserWindow({
|
||||
title: "直播窗口",
|
||||
width,
|
||||
height,
|
||||
minimizable: false, // 是否可以最小化
|
||||
maximizable: false, // 是否可以最小化
|
||||
closable: true, // 窗口是否可关闭
|
||||
alwaysOnTop: true, // 窗口是否永远在别的窗口的上面
|
||||
webPreferences: {
|
||||
preload,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
// 创建直播窗口
|
||||
async function createLiveWindow() {
|
||||
const width = 375;
|
||||
const height = 690;
|
||||
let liveUrl = `${ELECTRON_RENDERER_URL}/#/live`;
|
||||
if (liveState.userId) {
|
||||
liveUrl += `?userId=${liveState.userId}`;
|
||||
}
|
||||
|
||||
liveState.liveWindow = new BrowserWindow({
|
||||
title: "直播窗口",
|
||||
width,
|
||||
height,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
closable: true,
|
||||
alwaysOnTop: true,
|
||||
webPreferences: {
|
||||
preload,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
webSecurity: false, // 允许访问本地文件
|
||||
allowRunningInsecureContent: true, // 允许运行本地内容
|
||||
},
|
||||
});
|
||||
|
||||
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" });
|
||||
}
|
||||
}
|
||||
|
||||
// 等待页面加载完成
|
||||
// @ts-ignore - 暂时未使用但保留以备将来使用
|
||||
async function waitForPageLoad() {
|
||||
if (!liveState.liveWindow) {
|
||||
throw new Error("直播窗口不存在");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error("页面加载超时"));
|
||||
}, 30000); // 30秒超时
|
||||
|
||||
// 监听页面加载完成事件
|
||||
liveState.liveWindow!.webContents.once("did-finish-load", () => {
|
||||
clearTimeout(timeout);
|
||||
console.log("页面加载完成");
|
||||
|
||||
// 额外等待一下确保iframe也加载完成
|
||||
setTimeout(() => {
|
||||
// 通知所有渲染进程状态已更新
|
||||
const allWindows = BrowserWindow.getAllWindows();
|
||||
allWindows.forEach((window: BrowserWindow) => {
|
||||
if (window.webContents && !window.isDestroyed()) {
|
||||
window.webContents.send("live-status-updated", {
|
||||
hasLiveWindow: true,
|
||||
windowLoaded: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
resolve(true);
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// 监听加载失败事件
|
||||
liveState.liveWindow!.webContents.once(
|
||||
"did-fail-load",
|
||||
(_, errorCode, errorDesc) => {
|
||||
clearTimeout(timeout);
|
||||
reject(
|
||||
new Error(`页面加载失败: ${errorDesc} (${errorCode})`),
|
||||
);
|
||||
},
|
||||
});
|
||||
// liveWindow.webContents.openDevTools();
|
||||
liveWindow.on("closed", () => {
|
||||
liveWindow = null;
|
||||
});
|
||||
if (ELECTRON_RENDERER_URL) {
|
||||
liveWindow.loadURL(liveUrl);
|
||||
setTimeout(async () => {
|
||||
const res = await getSessionId({ userId: userId });
|
||||
if (res.success) {
|
||||
LiveSessionId = res.sessionId;
|
||||
console.log(
|
||||
"Session ID obtained successfully",
|
||||
LiveSessionId,
|
||||
);
|
||||
}
|
||||
}, 3000);
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 文件管理处理器
|
||||
ipcMain.handle("show-files-in-live", async (_, { files }) => {
|
||||
console.log("收到文件", files);
|
||||
try {
|
||||
// 使用当前直播窗口的 webContents
|
||||
if (liveState.liveWindow && !liveState.liveWindow.isDestroyed()) {
|
||||
console.log("发送文件路径到直播窗口:", files.length, "个文件");
|
||||
|
||||
// 直接传递文件路径信息,不读取文件内容
|
||||
const processedFiles = files.map((file) => ({
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
path: file.path,
|
||||
// 为支持的文件类型生成 file:// URL
|
||||
url: file.path
|
||||
? `file://${file.path.replace(/\\/g, "/")}`
|
||||
: null,
|
||||
}));
|
||||
|
||||
console.log(
|
||||
"处理后的文件数据:",
|
||||
processedFiles.map((f) => ({
|
||||
name: f.name,
|
||||
type: f.type,
|
||||
size: f.size,
|
||||
hasPath: !!f.path,
|
||||
hasUrl: !!f.url,
|
||||
})),
|
||||
);
|
||||
|
||||
// 发送文件数据到直播窗口
|
||||
liveState.liveWindow.webContents.send("show-files-display", {
|
||||
files: processedFiles,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `文件已发送到直播窗口 (${files.length}个文件)`,
|
||||
};
|
||||
} else {
|
||||
liveWindow.loadFile(indexHtml, { hash: `/${path}` });
|
||||
throw new Error("直播窗口未找到或已关闭");
|
||||
}
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message };
|
||||
console.error("展示文件失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || "展示文件失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("remove-file-from-live", async (_, { index, fileName }) => {
|
||||
try {
|
||||
if (liveState.liveWindow && !liveState.liveWindow.isDestroyed()) {
|
||||
liveState.liveWindow.webContents.send("remove-file-display", {
|
||||
index,
|
||||
fileName,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "已通知直播窗口移除文件",
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("移除文件展示失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || "移除文件展示失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("clear-all-files-from-live", async () => {
|
||||
try {
|
||||
if (liveState.liveWindow && !liveState.liveWindow.isDestroyed()) {
|
||||
liveState.liveWindow.webContents.send(
|
||||
"clear-all-files-display",
|
||||
{
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "已通知直播窗口清空所有文件",
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("清空文件展示失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || "清空文件展示失败",
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function getSessionId(requestBody: object) {
|
||||
try {
|
||||
const response = await fetch("http://ywpt.hx.cn/dmhx/get_sessionid", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
// 首先检查响应内容类型
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!contentType || !contentType.includes("application/json")) {
|
||||
// 如果不是JSON,读取原始文本进行调试
|
||||
const rawText = await response.text();
|
||||
console.warn("服务器返回非JSON响应:", rawText);
|
||||
|
||||
// 尝试解析可能的JSON响应(即使Content-Type不正确)
|
||||
try {
|
||||
const data = JSON.parse(rawText);
|
||||
if (response.ok && data.sessionid) {
|
||||
return { success: true, sessionId: data.sessionid };
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: data.message || "服务器返回非JSON格式",
|
||||
};
|
||||
}
|
||||
} catch (parseError) {
|
||||
return {
|
||||
success: false,
|
||||
error: `服务器响应格式错误: ${rawText.substring(0, 100)}...`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是JSON,正常解析
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.sessionid) {
|
||||
return { success: true, sessionId: data.sessionid };
|
||||
} else {
|
||||
return { success: false, error: data.message || "未知错误" };
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Error in getSessionId:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function sendMessage(requestBody: object) {
|
||||
try {
|
||||
const response = await fetch(`http://ywpt.hx.cn/dmhx/human`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
return { success: true };
|
||||
} else {
|
||||
return { success: false, error: data.message || "未知错误" };
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Error in sendMessage:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
// 导出状态(用于调试)
|
||||
export const getLiveState = () => ({ ...liveState });
|
||||
|
||||
Reference in New Issue
Block a user