Files
Recordly/electron/editorWindows.ts

293 lines
7.8 KiB
TypeScript

import path from "node:path";
import { BrowserWindow } from "electron";
import { getPackagedRendererBaseUrl } from "./rendererServer";
import {
PRELOAD_PATH,
RENDERER_DIST,
VITE_DEV_SERVER_URL,
WINDOW_ICON_PATH,
getScreen,
loadRendererWindow,
} from "./windowShared";
let countdownWindow: BrowserWindow | null = null;
function getEditorWindowQuery(): Record<string, string> {
const query: Record<string, string> = { windowType: "editor" };
if (process.env.RECORDLY_SMOKE_EXPORT !== "1") {
return query;
}
query.smokeExport = "1";
const mappings: Array<[string, string]> = [
["RECORDLY_SMOKE_EXPORT_INPUT", "smokeInput"],
["RECORDLY_SMOKE_EXPORT_OUTPUT", "smokeOutput"],
["RECORDLY_SMOKE_EXPORT_ENCODING_MODE", "smokeEncodingMode"],
["RECORDLY_SMOKE_EXPORT_SHADOW_INTENSITY", "smokeShadowIntensity"],
["RECORDLY_SMOKE_EXPORT_WEBCAM_INPUT", "smokeWebcamInput"],
["RECORDLY_SMOKE_EXPORT_WEBCAM_SHADOW", "smokeWebcamShadow"],
["RECORDLY_SMOKE_EXPORT_WEBCAM_SIZE", "smokeWebcamSize"],
["RECORDLY_SMOKE_EXPORT_PIPELINE", "smokePipelineModel"],
["RECORDLY_SMOKE_EXPORT_BACKEND", "smokeBackendPreference"],
["RECORDLY_SMOKE_EXPORT_MAX_ENCODE_QUEUE", "smokeMaxEncodeQueue"],
["RECORDLY_SMOKE_EXPORT_MAX_DECODE_QUEUE", "smokeMaxDecodeQueue"],
["RECORDLY_SMOKE_EXPORT_MAX_PENDING_FRAMES", "smokeMaxPendingFrames"],
];
for (const [envKey, queryKey] of mappings) {
const value = process.env[envKey];
if (value) {
query[queryKey] = value;
}
}
if (process.env.RECORDLY_SMOKE_EXPORT_USE_NATIVE === "1") {
query.smokeUseNativeExport = "1";
}
return query;
}
function loadPackagedEditorWindow(window: BrowserWindow) {
const query = getEditorWindowQuery();
const queryString = new URLSearchParams(query).toString();
const indexHtmlPath = path.join(RENDERER_DIST, "index.html");
const packagedRendererBaseUrl = getPackagedRendererBaseUrl();
const webContents = window.webContents;
const loadFromFile = () => {
if (!window.isDestroyed()) {
console.log("[editor-window] load-file", indexHtmlPath);
void window.loadFile(indexHtmlPath, { query });
}
};
if (!packagedRendererBaseUrl) {
loadFromFile();
return;
}
const targetUrl = `${packagedRendererBaseUrl}/?${queryString}`;
let settled = false;
let timeoutId: NodeJS.Timeout | null = setTimeout(() => {
fallbackToFile("load-timeout");
}, 5000);
const clearTimeoutIfNeeded = () => {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
};
const detachLoadListeners = () => {
clearTimeoutIfNeeded();
if (!webContents.isDestroyed()) {
webContents.removeListener("did-fail-load", handleDidFailLoad);
webContents.removeListener("did-finish-load", handleDidFinishLoad);
}
};
const fallbackToFile = (reason: string, details?: Record<string, unknown>) => {
if (settled || window.isDestroyed()) {
return;
}
settled = true;
detachLoadListeners();
console.warn("[editor-window] packaged renderer URL failed, falling back to file", {
reason,
targetUrl,
...details,
});
loadFromFile();
};
const handleDidFailLoad = (
_event: Electron.Event,
errorCode: number,
errorDescription: string,
validatedURL: string,
isMainFrame: boolean,
) => {
if (isMainFrame && validatedURL === targetUrl) {
fallbackToFile("did-fail-load", { errorCode, errorDescription, validatedURL });
}
};
const handleDidFinishLoad = () => {
if (webContents.getURL() === targetUrl) {
settled = true;
detachLoadListeners();
}
};
webContents.on("did-fail-load", handleDidFailLoad);
webContents.on("did-finish-load", handleDidFinishLoad);
window.once("closed", clearTimeoutIfNeeded);
console.log("[editor-window] load-url", targetUrl);
void window.loadURL(targetUrl).catch((error) => {
fallbackToFile("load-url-rejected", {
error: error instanceof Error ? error.message : String(error),
});
});
}
export function createEditorWindow(): BrowserWindow {
const isMac = process.platform === "darwin";
const { workArea, workAreaSize } = getScreen().getPrimaryDisplay();
const initialWidth = isMac ? Math.round(workAreaSize.width * 0.85) : workArea.width;
const initialHeight = isMac ? Math.round(workAreaSize.height * 0.85) : workArea.height;
const window = new BrowserWindow({
width: initialWidth,
height: initialHeight,
...(!isMac && { x: workArea.x, y: workArea.y }),
minWidth: 800,
minHeight: 600,
...(process.platform !== "darwin" && { icon: WINDOW_ICON_PATH }),
...(isMac && {
titleBarStyle: "hiddenInset",
trafficLightPosition: { x: 12, y: 12 },
}),
autoHideMenuBar: !isMac,
transparent: false,
resizable: true,
alwaysOnTop: false,
skipTaskbar: false,
title: "Recordly",
show: false,
backgroundColor: "#000000",
webPreferences: {
preload: PRELOAD_PATH,
nodeIntegration: false,
contextIsolation: true,
webSecurity: false,
backgroundThrottling: false,
},
});
window.once("ready-to-show", () => {
console.log("[editor-window] ready-to-show");
window.show();
});
window.webContents.on("did-finish-load", () => {
console.log("[editor-window] did-finish-load", window.webContents.getURL());
window.webContents.send("main-process-message", new Date().toLocaleString());
});
window.webContents.on("did-fail-load", (_event, errorCode, errorDescription, validatedURL) => {
console.error("[editor-window] did-fail-load", { errorCode, errorDescription, validatedURL });
});
window.webContents.on("render-process-gone", (_event, details) => {
console.error("[editor-window] render-process-gone", details);
});
window.on("show", () => {
console.log("[editor-window] show");
});
window.on("focus", () => {
console.log("[editor-window] focus");
});
if (VITE_DEV_SERVER_URL) {
const query = new URLSearchParams(getEditorWindowQuery());
void window.loadURL(`${VITE_DEV_SERVER_URL}?${query.toString()}`);
} else {
loadPackagedEditorWindow(window);
}
return window;
}
export function createSourceSelectorWindow(): BrowserWindow {
const { width, height } = getScreen().getPrimaryDisplay().workAreaSize;
const window = new BrowserWindow({
width: 620,
height: 420,
minHeight: 350,
maxHeight: 500,
x: Math.round((width - 620) / 2),
y: Math.round((height - 420) / 2),
frame: false,
resizable: false,
alwaysOnTop: true,
transparent: true,
show: false,
...(process.platform !== "darwin" && { icon: WINDOW_ICON_PATH }),
backgroundColor: "#00000000",
webPreferences: {
preload: PRELOAD_PATH,
nodeIntegration: false,
contextIsolation: true,
},
});
window.webContents.on("did-finish-load", () => {
setTimeout(() => {
if (!window.isDestroyed()) {
window.show();
}
}, 100);
});
loadRendererWindow(window, "source-selector");
return window;
}
export function createCountdownWindow(): BrowserWindow {
const { width, height } = getScreen().getPrimaryDisplay().workAreaSize;
const windowSize = 200;
const window = new BrowserWindow({
width: windowSize,
height: windowSize,
x: Math.floor((width - windowSize) / 2),
y: Math.floor((height - windowSize) / 2),
frame: false,
transparent: true,
resizable: false,
alwaysOnTop: true,
skipTaskbar: true,
hasShadow: false,
focusable: true,
show: false,
webPreferences: {
preload: PRELOAD_PATH,
nodeIntegration: false,
contextIsolation: true,
},
});
countdownWindow = window;
window.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
window.webContents.on("did-finish-load", () => {
if (!window.isDestroyed()) {
window.show();
}
});
window.on("closed", () => {
if (countdownWindow === window) {
countdownWindow = null;
}
});
loadRendererWindow(window, "countdown");
return window;
}
export function getCountdownWindow(): BrowserWindow | null {
return countdownWindow;
}
export function closeCountdownWindow(): void {
if (countdownWindow && !countdownWindow.isDestroyed()) {
countdownWindow.close();
countdownWindow = null;
}
}