Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions electron-builder.json5
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"node_modules/ffmpeg-static/**",
"node_modules/ffprobe-static/**",
"node_modules/uiohook-napi/**",
"electron/native/**"
"electron/native/bin/**"
],
"productName": "Recordly",
"npmRebuild": true,
Expand All @@ -19,8 +19,13 @@
},
"files": [
"dist",
"!dist/wallpapers/**",
"dist-electron",
"electron/native",
"electron/native/bin",
"!electron/native/bin/**/whisper-bench*",
"!electron/native/bin/**/whisper-quantize*",
"!electron/native/bin/**/whisper-server*",
"!electron/native/bin/**/whisper-vad-speech-segments*",
"!node_modules/ffprobe-static/bin/darwin/**",
"!node_modules/ffprobe-static/bin/linux/**",
"!node_modules/ffprobe-static/bin/win32/ia32/**",
Expand Down Expand Up @@ -111,4 +116,3 @@
"artifactName": "${productName}-windows-${arch}.${ext}"
}
}

8 changes: 6 additions & 2 deletions electron/ipc/register/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { ipcMain } from "electron";
import { app, ipcMain } from "electron";
import { USER_DATA_PATH } from "../../appPaths";
import { normalizePath } from "../utils";
import { getAssetRootPath } from "../project/manager";
import { normalizePath } from "../utils";

export function registerAssetHandlers() {
async function resolveReadableLocalFilePath(filePath: string) {
Expand Down Expand Up @@ -73,6 +73,10 @@ export function registerAssetHandlers() {

// Return base path for assets so renderer can resolve file:// paths in production
ipcMain.handle("get-asset-base-path", () => {
if (!app.isPackaged) {
return null;
}

try {
const assetPath = getAssetRootPath();
return pathToFileURL(`${assetPath}${path.sep}`).toString();
Expand Down
46 changes: 19 additions & 27 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -935,17 +935,11 @@ app.whenReady().then(async () => {
// Recordly does not use WebHID, Web Serial, or WebUSB. Do not grant devices by default.
session.defaultSession.setDevicePermissionHandler(() => false);

if (process.platform === "darwin") {
const cameraStatus = systemPreferences.getMediaAccessStatus("camera");
if (cameraStatus !== "granted") {
await systemPreferences.askForMediaAccess("camera");
}

const micStatus = systemPreferences.getMediaAccessStatus("microphone");
if (micStatus !== "granted") {
await systemPreferences.askForMediaAccess("microphone");
}
} else if (process.platform === "win32") {
// macOS prompts for camera and microphone access at the point of use. Asking
// here blocks the first window behind two modal OS permission flows and makes
// a fresh install look hung. Windows has no equivalent request API, so retain
// its diagnostic warnings.
if (process.platform === "win32") {
const cameraStatus = systemPreferences.getMediaAccessStatus("camera");
const micStatus = systemPreferences.getMediaAccessStatus("microphone");
if (cameraStatus !== "granted") {
Expand Down Expand Up @@ -986,22 +980,20 @@ app.whenReady().then(async () => {
updateTrayMenu();
}
setupApplicationMenu();
// Ensure recordings directory exists
await ensureRecordingsDir();

if (!VITE_DEV_SERVER_URL) {
try {
await ensurePackagedRendererServer(RENDERER_DIST);
} catch (error) {
console.warn("[renderer-server] Failed to start packaged renderer server:", error);
}
}

try {
await ensureMediaServer();
} catch (error) {
console.warn("[media-server] Failed to start media server:", error);
}
await Promise.all([
ensureRecordingsDir(),
!VITE_DEV_SERVER_URL
? ensurePackagedRendererServer(RENDERER_DIST).catch((error) => {
console.warn(
"[renderer-server] Failed to start packaged renderer server:",
error,
);
})
: Promise.resolve(),
ensureMediaServer().catch((error) => {
console.warn("[media-server] Failed to start media server:", error);
}),
]);

registerIpcHandlers(
createEditorWindowWrapper,
Expand Down
11 changes: 10 additions & 1 deletion electron/rendererServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ function getContentType(filePath: string): string {
return MIME_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
}

function getCacheControl(filePath: string): string {
// Vite fingerprints production assets, so they can be reused across the HUD,
// picker, toast, and editor windows without revalidation. Keep index.html
// uncached because it points at the current fingerprinted files.
return /-[A-Za-z0-9_-]{8,}\.[^.]+$/.test(path.basename(filePath))
? "public, max-age=31536000, immutable"
: "no-cache";
}

function resolveRequestedFilePath(rootDir: string, requestPathname: string): string | null {
const trimmedPathname = requestPathname === "/" ? "/index.html" : requestPathname;

Expand Down Expand Up @@ -72,7 +81,7 @@ async function servePackagedRendererRequest(
const fileContents = await fs.readFile(filePath);

response.writeHead(200, {
"Cache-Control": "no-cache",
"Cache-Control": getCacheControl(filePath),
"Content-Type": getContentType(filePath),
});

Expand Down
11 changes: 10 additions & 1 deletion electron/updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { readAppSetting, writeAppSetting } from "./appSettingsStore";
import { EXPERIMENTAL_UPDATE_DESCRIPTION, getUpdateChannelConfiguration } from "./updateChannel";

const UPDATE_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
const INITIAL_UPDATE_CHECK_DELAY_MS = 15 * 1000;
export const UPDATE_REMINDER_DELAY_MS = 3 * 60 * 60 * 1000;
const DISMISSED_READY_REMINDER_DELAY_MS = 5 * 60 * 1000;
const AUTO_UPDATES_DISABLED = process.env.RECORDLY_DISABLE_AUTO_UPDATES === "1";
Expand Down Expand Up @@ -71,6 +72,7 @@ let updaterInitialized = false;
let updateCheckInProgress = false;
let manualCheckRequested = false;
let periodicCheckTimer: NodeJS.Timeout | null = null;
let initialCheckTimer: NodeJS.Timeout | null = null;
let deferredReminderTimer: NodeJS.Timeout | null = null;
let devPreviewProgressTimer: NodeJS.Timeout | null = null;
let currentToastPayload: UpdateToastPayload | null = null;
Expand Down Expand Up @@ -905,14 +907,21 @@ export function setupAutoUpdates(
void showDownloadedUpdateDialog(getMainWindow, info.version);
});

void checkForAppUpdates(getMainWindow);
initialCheckTimer = setTimeout(() => {
initialCheckTimer = null;
void checkForAppUpdates(getMainWindow);
}, INITIAL_UPDATE_CHECK_DELAY_MS);
periodicCheckTimer = setInterval(() => {
void checkForAppUpdates(getMainWindow);
}, UPDATE_CHECK_INTERVAL_MS);

app.on("before-quit", () => {
clearDeferredReminderTimer();
clearDevPreviewProgressTimer();
if (initialCheckTimer) {
clearTimeout(initialCheckTimer);
initialCheckTimer = null;
}
if (periodicCheckTimer) {
clearInterval(periodicCheckTimer);
periodicCheckTimer = null;
Expand Down
3 changes: 3 additions & 0 deletions electron/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ ipcMain.handle("set-hud-overlay-capture-protection", (_event, enabled: boolean)
});

export function createHudOverlayWindow(): BrowserWindow {
const perfStart = Date.now();
loadHudOverlayCaptureProtectionSetting();
hudOverlayFallbackExpanded = false;
hudOverlayWebcamPreviewVisible = false;
Expand Down Expand Up @@ -557,6 +558,7 @@ export function createHudOverlayWindow(): BrowserWindow {
}

win.webContents.on("did-finish-load", () => {
console.log(`[PERF:MAIN] HUD Window: did-finish-load in ${Date.now() - perfStart}ms`);
win?.webContents.send("main-process-message", new Date().toLocaleString());
// Safety fallback if renderer-ready signal never arrives.
setTimeout(() => {
Expand All @@ -577,6 +579,7 @@ export function createHudOverlayWindow(): BrowserWindow {

const handleHudRendererReady = () => {
if (!win.isDestroyed()) {
console.log(`[PERF:MAIN] HUD Window: renderer-ready in ${Date.now() - perfStart}ms`);
showHudWindow();
}
};
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"url": "https://github.com/webadderallorg/Recordly/issues"
},
"private": true,
"version": "1.3.5-beta.2",
"version": "1.3.5-beta.3",
"type": "module",
"scripts": {
"dev": "vite --config vite.config.ts",
Expand Down
92 changes: 44 additions & 48 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,56 @@
import { useEffect, useState } from "react";
import { AnnouncementDialog } from "./components/announcements/AnnouncementDialog";
import { LiveAnnouncementNotifications } from "./components/announcements/LiveAnnouncementNotifications";
import { CountdownOverlay } from "./components/countdown/CountdownOverlay";
import { LaunchWindow } from "./components/launch/LaunchWindow";
import { SourceSelector } from "./components/launch/SourceSelector";
import { UpdateToastWindow } from "./components/launch/UpdateToastWindow";
import { Toaster } from "./components/ui/sonner";
import { ShortcutsConfigDialog } from "./components/video-editor/ShortcutsConfigDialog";
import VideoEditor from "./components/video-editor/VideoEditor";
import { lazy, Suspense, useEffect, useState } from "react";
import { useI18n } from "./contexts/I18nContext";
import { ShortcutsProvider } from "./contexts/ShortcutsContext";
import { loadAllCustomFonts } from "./lib/customFonts";

const HudWindow = lazy(() => import("./components/launch/HudWindow"));
const SourceSelector = lazy(() =>
import("./components/launch/SourceSelector").then((module) => ({
default: module.SourceSelector,
})),
);
const CountdownOverlay = lazy(() =>
import("./components/countdown/CountdownOverlay").then((module) => ({
default: module.CountdownOverlay,
})),
);
const UpdateToastWindow = lazy(() =>
import("./components/launch/UpdateToastWindow").then((module) => ({
default: module.UpdateToastWindow,
})),
);
const EditorWindow = lazy(() => import("./components/video-editor/EditorWindow"));

export default function App() {
const [windowType, setWindowType] = useState("");
const [windowType] = useState(
() => new URLSearchParams(window.location.search).get("windowType") || "",
);
const { t } = useI18n();
const appIconSrc = "/app-icons/recordly-128.png";

useEffect(() => {
const params = new URLSearchParams(window.location.search);
const type = params.get("windowType") || "";
setWindowType(type);
document.documentElement.dataset.windowType = type;
document.documentElement.dataset.windowType = windowType;

if (
type === "hud-overlay" ||
type === "source-selector" ||
type === "countdown" ||
type === "update-toast"
windowType === "hud-overlay" ||
windowType === "source-selector" ||
windowType === "countdown" ||
windowType === "update-toast"
) {
document.body.style.background = "transparent";
document.documentElement.style.background = "transparent";
document.getElementById("root")?.style.setProperty("background", "transparent");
}

if (type === "hud-overlay") {
if (windowType === "hud-overlay") {
document.documentElement.classList.add("hud-overlay-window");
document.body.classList.add("hud-overlay-window");
document.getElementById("root")?.classList.add("hud-overlay-window");
window.electronAPI?.hudOverlaySetIgnoreMouse?.(true);
} else if (type === "update-toast") {
} else if (windowType === "update-toast") {
document.documentElement.style.overflow = "visible";
document.body.style.overflow = "visible";
document.getElementById("root")?.style.setProperty("overflow", "visible");
}

loadAllCustomFonts().catch((error) => {
console.error("Failed to load custom fonts:", error);
});
}, []);
}, [windowType]);

useEffect(() => {
document.title =
Expand All @@ -57,33 +59,25 @@ export default function App() {
: t("app.name", "Recordly");
}, [windowType, t]);

let content;
switch (windowType) {
case "hud-overlay":
return (
<>
<LaunchWindow />
<Toaster className="pointer-events-auto" />
</>
);
content = <HudWindow />;
break;
case "source-selector":
return <SourceSelector />;
content = <SourceSelector />;
break;
case "countdown":
return <CountdownOverlay />;
content = <CountdownOverlay />;
break;
case "update-toast":
return <UpdateToastWindow />;
content = <UpdateToastWindow />;
break;
case "editor":
return (
<>
<ShortcutsProvider>
<VideoEditor />
<ShortcutsConfigDialog />
</ShortcutsProvider>
<AnnouncementDialog audience="editor" />
<LiveAnnouncementNotifications audience="editor" />
</>
);
content = <EditorWindow />;
break;
default:
return (
content = (
<div className="flex h-full w-full items-center justify-center bg-editor-bg text-foreground">
<div className="flex items-center gap-4 rounded-2xl border border-foreground/10 bg-foreground/5 px-6 py-5 shadow-2xl shadow-black/30 backdrop-blur-xl">
<img
Expand All @@ -103,4 +97,6 @@ export default function App() {
</div>
);
}

return <Suspense fallback={null}>{content}</Suspense>;
}
11 changes: 11 additions & 0 deletions src/components/launch/HudWindow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Toaster } from "../ui/sonner";
import { LaunchWindow } from "./LaunchWindow";

export default function HudWindow() {
return (
<>
<LaunchWindow />
<Toaster className="pointer-events-auto" />
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</>
);
}
2 changes: 1 addition & 1 deletion src/components/launch/LaunchWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { useLaunchWindowSystemState } from "./hooks/useLaunchWindowSystemState";
import { useRecordingTimer } from "./hooks/useRecordingTimer";
import { useWebcamPreviewOverlay } from "./hooks/useWebcamPreviewOverlay";
import styles from "./LaunchWindow.module.css";
import { MarqueeText } from "./MarqueeText";
import { CountdownPopover } from "./popovers/CountdownPopover";
import {
LaunchPopoverCoordinatorProvider,
Expand All @@ -41,7 +42,6 @@ import { ProjectPopover } from "./popovers/ProjectPopover";
import { SourcePopover } from "./popovers/SourcePopover";
import { WebcamPopover } from "./popovers/WebcamPopover";
import { RecordingControls } from "./RecordingControls";
import { MarqueeText } from "./SourceSelector";

const SHOW_DEV_UPDATE_PREVIEW = import.meta.env.DEV;

Expand Down
Loading