Files
live-stream-app/src/main/ipc/live.ts
2025-11-16 18:11:30 +08:00

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 });