mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 14:55:37 +00:00
perf: optimize application startup
This commit is contained in:
@@ -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,
|
||||
@@ -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/**",
|
||||
@@ -111,4 +116,3 @@
|
||||
"artifactName": "${productName}-windows-${arch}.${ext}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-27
@@ -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") {
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
|
||||
|
||||
+10
-1
@@ -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";
|
||||
@@ -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;
|
||||
@@ -905,7 +907,10 @@ 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);
|
||||
@@ -913,6 +918,10 @@ export function setupAutoUpdates(
|
||||
app.on("before-quit", () => {
|
||||
clearDeferredReminderTimer();
|
||||
clearDevPreviewProgressTimer();
|
||||
if (initialCheckTimer) {
|
||||
clearTimeout(initialCheckTimer);
|
||||
initialCheckTimer = null;
|
||||
}
|
||||
if (periodicCheckTimer) {
|
||||
clearInterval(periodicCheckTimer);
|
||||
periodicCheckTimer = null;
|
||||
|
||||
@@ -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;
|
||||
@@ -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(() => {
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
+44
-48
@@ -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 =
|
||||
@@ -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
|
||||
@@ -103,4 +97,6 @@ export default function App() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <Suspense fallback={null}>{content}</Suspense>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Toaster } from "../ui/sonner";
|
||||
import { LaunchWindow } from "./LaunchWindow";
|
||||
|
||||
export default function HudWindow() {
|
||||
return (
|
||||
<>
|
||||
<LaunchWindow />
|
||||
<Toaster className="pointer-events-auto" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useLayoutEffect, useRef, useState } from "react";
|
||||
|
||||
export function MarqueeText({ text }: { text: string }) {
|
||||
const staticRef = useRef<HTMLSpanElement>(null);
|
||||
const [overflowing, setOverflowing] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const node = staticRef.current;
|
||||
if (!node || node.textContent !== text) return;
|
||||
const checkOverflow = () => {
|
||||
setOverflowing(node.scrollWidth > node.clientWidth + 1);
|
||||
};
|
||||
checkOverflow();
|
||||
const observer = new ResizeObserver(checkOverflow);
|
||||
observer.observe(node);
|
||||
return () => observer.disconnect();
|
||||
}, [text]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full source-selector-marquee"
|
||||
data-overflowing={overflowing ? "true" : "false"}
|
||||
>
|
||||
<span ref={staticRef} className="source-selector-marquee-static">
|
||||
{text}
|
||||
</span>
|
||||
<span className="source-selector-marquee-animated">
|
||||
<span className="source-selector-marquee-track">
|
||||
<span className="source-selector-marquee-segment">{text}</span>
|
||||
<span className="source-selector-marquee-segment source-selector-marquee-duplicate">
|
||||
{text}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AppWindowIcon, CaretUpIcon, MonitorIcon } from "@phosphor-icons/react";
|
||||
import * as React from "react";
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { useScopedT } from "@/contexts/I18nContext";
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import "./launchTheme.css";
|
||||
import "./SourceSelector.css";
|
||||
import { useHudInteraction } from "./contexts/HudInteractionContext";
|
||||
import { MarqueeText } from "./MarqueeText";
|
||||
|
||||
interface SourceSelectorProps {
|
||||
/** List of available screen sources */
|
||||
@@ -36,42 +37,6 @@ interface SourceSelectorProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function MarqueeText({ text }: { text: string }) {
|
||||
const staticRef = useRef<HTMLSpanElement>(null);
|
||||
const [overflowing, setOverflowing] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const node = staticRef.current;
|
||||
if (!node || node.textContent !== text) return;
|
||||
const checkOverflow = () => {
|
||||
setOverflowing(node.scrollWidth > node.clientWidth + 1);
|
||||
};
|
||||
checkOverflow();
|
||||
const observer = new ResizeObserver(checkOverflow);
|
||||
observer.observe(node);
|
||||
return () => observer.disconnect();
|
||||
}, [text]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full source-selector-marquee"
|
||||
data-overflowing={overflowing ? "true" : "false"}
|
||||
>
|
||||
<span ref={staticRef} className="source-selector-marquee-static">
|
||||
{text}
|
||||
</span>
|
||||
<span className="source-selector-marquee-animated">
|
||||
<span className="source-selector-marquee-track">
|
||||
<span className="source-selector-marquee-segment">{text}</span>
|
||||
<span className="source-selector-marquee-segment source-selector-marquee-duplicate">
|
||||
{text}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* SourceSelectorContent - The actual list of sources
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { ReactElement } from "react";
|
||||
import { lazy, type ReactElement, Suspense } from "react";
|
||||
import type { ProjectLibraryEntry } from "../../video-editor/ProjectBrowserDialog";
|
||||
import { useLaunchPopoverCoordinator } from "./LaunchPopoverCoordinator";
|
||||
import { HudPopover } from "./PopoverScaffold";
|
||||
import ProjectBrowserDialog from "../../video-editor/ProjectBrowserDialog";
|
||||
import type { ProjectLibraryEntry } from "../../video-editor/ProjectBrowserDialog";
|
||||
|
||||
const ProjectBrowserDialog = lazy(() => import("../../video-editor/ProjectBrowserDialog"));
|
||||
|
||||
const POPOVER_ID = "projects";
|
||||
|
||||
@@ -31,18 +32,20 @@ export function ProjectPopover({
|
||||
trigger={trigger}
|
||||
align="center"
|
||||
>
|
||||
<ProjectBrowserDialog
|
||||
open={open}
|
||||
onOpenChange={(nextOpen) => {
|
||||
if (!nextOpen) requestClose(POPOVER_ID);
|
||||
}}
|
||||
entries={entries}
|
||||
renderMode="inline"
|
||||
onOpenProject={(path) => {
|
||||
onOpenProject(path);
|
||||
requestClose(POPOVER_ID);
|
||||
}}
|
||||
/>
|
||||
<Suspense fallback={null}>
|
||||
<ProjectBrowserDialog
|
||||
open={open}
|
||||
onOpenChange={(nextOpen) => {
|
||||
if (!nextOpen) requestClose(POPOVER_ID);
|
||||
}}
|
||||
entries={entries}
|
||||
renderMode="inline"
|
||||
onOpenProject={(path) => {
|
||||
onOpenProject(path);
|
||||
requestClose(POPOVER_ID);
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</HudPopover>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useEffect } from "react";
|
||||
import { ShortcutsProvider } from "../../contexts/ShortcutsContext";
|
||||
import { loadAllCustomFonts } from "../../lib/customFonts";
|
||||
import { AnnouncementDialog } from "../announcements/AnnouncementDialog";
|
||||
import { LiveAnnouncementNotifications } from "../announcements/LiveAnnouncementNotifications";
|
||||
import { ShortcutsConfigDialog } from "./ShortcutsConfigDialog";
|
||||
import VideoEditor from "./VideoEditor";
|
||||
|
||||
export default function EditorWindow() {
|
||||
useEffect(() => {
|
||||
loadAllCustomFonts().catch((error) => {
|
||||
console.error("Failed to load custom fonts:", error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ShortcutsProvider>
|
||||
<VideoEditor />
|
||||
<ShortcutsConfigDialog />
|
||||
</ShortcutsProvider>
|
||||
<AnnouncementDialog audience="editor" />
|
||||
<LiveAnnouncementNotifications audience="editor" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1110,6 +1110,8 @@ export function SettingsPanel({
|
||||
);
|
||||
const [experimentalUpdatesEnabled, setExperimentalUpdatesEnabled] = useState(false);
|
||||
const [savingExperimentalUpdates, setSavingExperimentalUpdates] = useState(false);
|
||||
const [internalActiveEffectSection] = useState<EditorEffectSection>("scene");
|
||||
const activeEffectSection = activeEffectSectionProp ?? internalActiveEffectSection;
|
||||
const removeBackgroundStateRef = useRef<{
|
||||
aspectRatio: AspectRatio;
|
||||
padding: Padding;
|
||||
@@ -1166,6 +1168,8 @@ export function SettingsPanel({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBackgroundPanel && activeEffectSection !== "scene") return;
|
||||
|
||||
let mounted = true;
|
||||
(async () => {
|
||||
try {
|
||||
@@ -1196,7 +1200,7 @@ export function SettingsPanel({
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, []);
|
||||
}, [activeEffectSection, isBackgroundPanel]);
|
||||
|
||||
const colorPalette = [
|
||||
"#FF0000",
|
||||
@@ -1231,8 +1235,6 @@ export function SettingsPanel({
|
||||
const customColorInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const cursorClickEffectColorInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const defaultWebcam = initialEditorPreferences.webcam;
|
||||
const [internalActiveEffectSection] = useState<EditorEffectSection>("scene");
|
||||
const activeEffectSection = activeEffectSectionProp ?? internalActiveEffectSection;
|
||||
const [builtInCursorPreviewUrls, setBuiltInCursorPreviewUrls] = useState<
|
||||
Partial<Record<string, string>>
|
||||
>({});
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { useCallback, useRef } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
DEFAULT_MP4_CODEC,
|
||||
type ExportSettings,
|
||||
GifExporter,
|
||||
ModernVideoExporter,
|
||||
VideoExporter,
|
||||
} from "@/lib/exporter";
|
||||
import { getMp4ExportBitrate } from "@/lib/exporter/exportBitrate";
|
||||
import { DEFAULT_MP4_CODEC } from "@/lib/exporter/mp4Support";
|
||||
import type { ExportSettings } from "@/lib/exporter/types";
|
||||
import { calculateMp4ExportDimensions } from "../exportDimensions";
|
||||
import { resolveMp4ExportRouting } from "../mp4ExportRouting";
|
||||
import { resolveMp4ExportSettings } from "../mp4ExportSettings";
|
||||
@@ -116,6 +111,7 @@ export function useExportRunner(input: ExportRunnerInput) {
|
||||
|
||||
if (settings.format === "gif" && settings.gifConfig) {
|
||||
// GIF Export
|
||||
const { GifExporter } = await import("@/lib/exporter/gifExporter");
|
||||
const gifExporter = new GifExporter({
|
||||
videoUrl: videoPath,
|
||||
width: settings.gifConfig.width,
|
||||
@@ -143,7 +139,7 @@ export function useExportRunner(input: ExportRunnerInput) {
|
||||
maxPendingFrames: smokeExportConfig.maxPendingFrames,
|
||||
});
|
||||
|
||||
exporterRef.current = gifExporter as unknown as VideoExporter;
|
||||
exporterRef.current = gifExporter;
|
||||
const result = await gifExporter.export();
|
||||
|
||||
if (result.success && result.blob) {
|
||||
@@ -287,11 +283,15 @@ export function useExportRunner(input: ExportRunnerInput) {
|
||||
|
||||
const exporter =
|
||||
pipelineModel === "modern"
|
||||
? new ModernVideoExporter({
|
||||
? new (
|
||||
await import("@/lib/exporter/modernVideoExporter")
|
||||
).ModernVideoExporter({
|
||||
...exporterConfig,
|
||||
backendPreference,
|
||||
})
|
||||
: new VideoExporter(exporterConfig);
|
||||
: new (await import("@/lib/exporter/videoExporter")).VideoExporter(
|
||||
exporterConfig,
|
||||
);
|
||||
|
||||
exporterRef.current = exporter;
|
||||
const result = await exporter.export();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* biome-ignore-all lint/correctness/useExhaustiveDependencies: grouped editor domain objects contain the thumbnail renderer dependencies. */
|
||||
import { type RefObject, useCallback, useEffect, useRef } from "react";
|
||||
import { FrameRenderer } from "@/lib/exporter";
|
||||
import { FrameRenderer } from "@/lib/exporter/frameRenderer";
|
||||
import { toFileUrl } from "../projectPersistence";
|
||||
import type { useAppearanceState } from "../state/useAppearanceState";
|
||||
import type { useProjectState } from "../state/useProjectState";
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
GifFrameRate,
|
||||
GifSizePreset,
|
||||
} from "@/lib/exporter";
|
||||
import { isValidMp4FrameRate } from "@/lib/exporter";
|
||||
import { isValidMp4FrameRate } from "@/lib/exporter/types";
|
||||
import {
|
||||
TEMPORAL_MOTION_BLUR_DEFAULT_SAMPLE_COUNT,
|
||||
TEMPORAL_MOTION_BLUR_DEFAULT_SHUTTER_FRACTION,
|
||||
|
||||
@@ -126,7 +126,6 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
pixi: ["pixi.js"],
|
||||
"react-vendor": ["react", "react-dom"],
|
||||
"video-processing": ["mediabunny", "mp4box", "@fix-webm-duration/fix"],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user