399 lines
13 KiB
TypeScript
399 lines
13 KiB
TypeScript
import { ipcMain, BrowserWindow } from "electron";
|
|
import { preload, indexHtml, ELECTRON_RENDERER_URL } from "../config";
|
|
import { getSessionId, sendMessage } from "../Api/api";
|
|
import { showPrompt } from "../utils/tools";
|
|
|
|
// 直播状态管理
|
|
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() {
|
|
// 获取直播状态
|
|
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 (liveState.liveWindow) {
|
|
liveState.isVideoInserted = !liveState.isVideoInserted;
|
|
liveState.liveWindow.webContents.send(
|
|
"toggle-camera-insert",
|
|
liveState.isVideoInserted,
|
|
);
|
|
}
|
|
});
|
|
|
|
// 插入摄像头视频
|
|
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 (liveState.liveWindow) {
|
|
liveState.liveWindow.webContents.send("insert-video-audio");
|
|
liveState.audioActive = true;
|
|
} else {
|
|
showPrompt("直播窗口未打开", "error");
|
|
}
|
|
});
|
|
|
|
// 开始直播 - 完整的直播启动流程
|
|
ipcMain.handle("start-live", async (_, { userId } = {}) => {
|
|
try {
|
|
console.log("Starting live stream...");
|
|
|
|
// 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 };
|
|
}
|
|
});
|
|
|
|
// 结束直播
|
|
ipcMain.handle("stop-live", async () => {
|
|
try {
|
|
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("force-close-live", async () => {
|
|
try {
|
|
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);
|
|
return { success: true };
|
|
} catch (error: any) {
|
|
console.error("Send message error:", error);
|
|
return { success: false, error: error.message };
|
|
}
|
|
});
|
|
|
|
// 创建直播窗口
|
|
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})`),
|
|
);
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
// 文件管理处理器
|
|
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 {
|
|
throw new Error("直播窗口未找到或已关闭");
|
|
}
|
|
} catch (error: any) {
|
|
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 || "清空文件展示失败",
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
// 导出状态(用于调试)
|
|
export const getLiveState = () => ({ ...liveState });
|