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
60 changes: 11 additions & 49 deletions src/components/video-editor/VideoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
import { Toaster } from "@/components/ui/sonner";
import { useI18n } from "@/contexts/I18nContext";
import { useShortcuts } from "@/contexts/ShortcutsContext";
import { loadAppSetting, saveAppSetting } from "@/lib/appSettings";
import {
calculateOutputDimensions,
DEFAULT_MP4_CODEC,
Expand Down Expand Up @@ -89,6 +88,7 @@ import {
} from "@/utils/aspectRatioUtils";
import { ExtensionIcon } from "./ExtensionIcon";
import { calculateMp4ExportDimensions, calculateMp4SourceDimensions } from "./exportDimensions";
import { useNvidiaCudaExportOptIn } from "./useNvidiaCudaExportOptIn";

const PhCursorFill = (props: { className?: string; weight?: "fill" | "regular" }) => (
<Cursor weight="fill" className={props.className} />
Expand All @@ -109,8 +109,6 @@ const PhSettings = (props: { className?: string; weight?: "fill" | "regular" })
<Gear weight={props.weight ?? "regular"} className={props.className} />
);

const NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY = "recordly.export.experimentalNvidiaCuda";

import type { SourceAudioTrackSettings } from "@/components/video-editor/audio/audioTypes";
import { extensionHost } from "@/lib/extensions";
import { useVideoEditorAudio } from "./audio/useVideoEditorAudio";
Expand Down Expand Up @@ -554,10 +552,16 @@ export default function VideoEditor() {
const [exportPipelineModel, setExportPipelineModel] = useState<ExportPipelineModel>(
initialEditorPreferences.exportPipelineModel,
);
const [nvidiaCudaExportAvailable, setNvidiaCudaExportAvailable] = useState(false);
const [experimentalNvidiaCudaExport, setExperimentalNvidiaCudaExportState] = useState(
() => loadAppSetting<boolean>(NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY) === true,
);
const enableModernExportPipeline = useCallback(() => {
setExportPipelineModel("modern");
}, []);
const {
nvidiaCudaExportAvailable,
experimentalNvidiaCudaExport,
setExperimentalNvidiaCudaExport,
} = useNvidiaCudaExportOptIn({
onEnabled: enableModernExportPipeline,
});
const [mp4FrameRate, setMp4FrameRate] = useState<ExportMp4FrameRate>(
initialEditorPreferences.mp4FrameRate ?? DEFAULT_MP4_EXPORT_FRAME_RATE,
);
Expand Down Expand Up @@ -628,48 +632,6 @@ export default function VideoEditor() {
});
}, []);

useEffect(() => {
let cancelled = false;

void (async () => {
try {
const result = await window.electronAPI?.getNativeExportCapabilities?.();
if (cancelled) {
return;
}

const available = result?.capabilities?.nvidiaCuda.available === true;
setNvidiaCudaExportAvailable(available);
if (!available) {
setExperimentalNvidiaCudaExportState(false);
}
} catch (error) {
if (cancelled) {
return;
}
console.warn("[export] Failed to load native export capabilities", error);
setNvidiaCudaExportAvailable(false);
setExperimentalNvidiaCudaExportState(false);
}
})();

return () => {
cancelled = true;
};
}, []);

const setExperimentalNvidiaCudaExport = useCallback(
(enabled: boolean) => {
const nextEnabled = Boolean(enabled && nvidiaCudaExportAvailable);
setExperimentalNvidiaCudaExportState(nextEnabled);
saveAppSetting(NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY, nextEnabled);
if (nextEnabled) {
setExportPipelineModel("modern");
}
},
[nvidiaCudaExportAvailable],
);

useEffect(() => {
autoSuggestedVideoPathRef.current = null;
pendingFreshRecordingAutoSuggestTelemetryCountRef.current = 0;
Expand Down
68 changes: 68 additions & 0 deletions src/components/video-editor/useNvidiaCudaExportOptIn.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { afterEach, describe, expect, it } from "vitest";

import {
isNvidiaCudaExportAvailable,
loadInitialNvidiaCudaExportOptIn,
NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY,
resolveNvidiaCudaExportOptIn,
saveNvidiaCudaExportOptIn,
} from "./useNvidiaCudaExportOptIn";

function stubElectronSettings(initialValues: Record<string, unknown> = {}) {
const store = new Map(Object.entries(initialValues));

Object.defineProperty(globalThis, "electronAPI", {
configurable: true,
value: {
getAppSetting: (key: string) => (store.has(key) ? store.get(key) : null),
setAppSetting: (key: string, value: unknown) => {
store.set(key, value);
return true;
},
} as Pick<Window["electronAPI"], "getAppSetting" | "setAppSetting">,
});

return store;
}

describe("nvidiaCudaExportOptIn", () => {
afterEach(() => {
Reflect.deleteProperty(globalThis, "electronAPI");
});

it("only treats explicit native CUDA availability as available", () => {
expect(
isNvidiaCudaExportAvailable({
capabilities: { nvidiaCuda: { available: true } },
}),
).toBe(true);
expect(
isNvidiaCudaExportAvailable({
capabilities: { nvidiaCuda: { available: false } },
}),
).toBe(false);
expect(isNvidiaCudaExportAvailable({ capabilities: {} })).toBe(false);
expect(isNvidiaCudaExportAvailable(null)).toBe(false);
});

it("requires both user request and runtime availability before enabling", () => {
expect(resolveNvidiaCudaExportOptIn(true, true)).toBe(true);
expect(resolveNvidiaCudaExportOptIn(true, false)).toBe(false);
expect(resolveNvidiaCudaExportOptIn(false, true)).toBe(false);
expect(resolveNvidiaCudaExportOptIn(false, false)).toBe(false);
});

it("loads and saves the local opt-in flag through app settings", () => {
const store = stubElectronSettings({
[NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY]: true,
});

expect(loadInitialNvidiaCudaExportOptIn()).toBe(true);
expect(saveNvidiaCudaExportOptIn(false)).toBe(true);
expect(store.get(NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY)).toBe(false);
});

it("defaults to disabled when the app setting is unavailable", () => {
expect(loadInitialNvidiaCudaExportOptIn()).toBe(false);
});
});
96 changes: 96 additions & 0 deletions src/components/video-editor/useNvidiaCudaExportOptIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useCallback, useEffect, useState } from "react";
import { loadAppSetting, saveAppSetting } from "@/lib/appSettings";

export const NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY =
"recordly.export.experimentalNvidiaCuda";

type NativeExportCapabilitiesResult = {
capabilities?: {
nvidiaCuda?: {
available?: boolean;
};
};
} | null;

export function isNvidiaCudaExportAvailable(
result: NativeExportCapabilitiesResult | undefined,
) {
return result?.capabilities?.nvidiaCuda?.available === true;
}

export function resolveNvidiaCudaExportOptIn(
requested: boolean,
nvidiaCudaExportAvailable: boolean,
) {
return Boolean(requested && nvidiaCudaExportAvailable);
}

export function loadInitialNvidiaCudaExportOptIn() {
return loadAppSetting<boolean>(NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY) === true;
}

export function saveNvidiaCudaExportOptIn(enabled: boolean) {
return saveAppSetting(NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY, enabled);
}

export function useNvidiaCudaExportOptIn({
onEnabled,
}: {
onEnabled?: () => void;
} = {}) {
const [nvidiaCudaExportAvailable, setNvidiaCudaExportAvailable] = useState(false);
const [experimentalNvidiaCudaExport, setExperimentalNvidiaCudaExportState] = useState(
loadInitialNvidiaCudaExportOptIn,
);

useEffect(() => {
let cancelled = false;

void (async () => {
try {
const result = await window.electronAPI?.getNativeExportCapabilities?.();
if (cancelled) {
return;
}

const available = isNvidiaCudaExportAvailable(result);
setNvidiaCudaExportAvailable(available);
if (!available) {
setExperimentalNvidiaCudaExportState(false);
}
} catch (error) {
if (cancelled) {
return;
}
console.warn("[export] Failed to load native export capabilities", error);
setNvidiaCudaExportAvailable(false);
setExperimentalNvidiaCudaExportState(false);
}
})();

return () => {
cancelled = true;
};
}, []);

const setExperimentalNvidiaCudaExport = useCallback(
(enabled: boolean) => {
const nextEnabled = resolveNvidiaCudaExportOptIn(
enabled,
nvidiaCudaExportAvailable,
);
setExperimentalNvidiaCudaExportState(nextEnabled);
saveNvidiaCudaExportOptIn(nextEnabled);
if (nextEnabled) {
onEnabled?.();
}
},
[nvidiaCudaExportAvailable, onEnabled],
);

return {
nvidiaCudaExportAvailable,
experimentalNvidiaCudaExport,
setExperimentalNvidiaCudaExport,
};
}