diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx
index 6308efc6..3e53a29c 100644
--- a/src/components/video-editor/VideoEditor.tsx
+++ b/src/components/video-editor/VideoEditor.tsx
@@ -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,
@@ -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" }) => (
@@ -109,8 +109,6 @@ const PhSettings = (props: { className?: string; weight?: "fill" | "regular" })
);
-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";
@@ -554,10 +552,16 @@ export default function VideoEditor() {
const [exportPipelineModel, setExportPipelineModel] = useState(
initialEditorPreferences.exportPipelineModel,
);
- const [nvidiaCudaExportAvailable, setNvidiaCudaExportAvailable] = useState(false);
- const [experimentalNvidiaCudaExport, setExperimentalNvidiaCudaExportState] = useState(
- () => loadAppSetting(NVIDIA_CUDA_EXPORT_OPT_IN_SETTING_KEY) === true,
- );
+ const enableModernExportPipeline = useCallback(() => {
+ setExportPipelineModel("modern");
+ }, []);
+ const {
+ nvidiaCudaExportAvailable,
+ experimentalNvidiaCudaExport,
+ setExperimentalNvidiaCudaExport,
+ } = useNvidiaCudaExportOptIn({
+ onEnabled: enableModernExportPipeline,
+ });
const [mp4FrameRate, setMp4FrameRate] = useState(
initialEditorPreferences.mp4FrameRate ?? DEFAULT_MP4_EXPORT_FRAME_RATE,
);
@@ -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;
diff --git a/src/components/video-editor/useNvidiaCudaExportOptIn.test.ts b/src/components/video-editor/useNvidiaCudaExportOptIn.test.ts
new file mode 100644
index 00000000..d63ba97c
--- /dev/null
+++ b/src/components/video-editor/useNvidiaCudaExportOptIn.test.ts
@@ -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 = {}) {
+ 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,
+ });
+
+ 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);
+ });
+});
diff --git a/src/components/video-editor/useNvidiaCudaExportOptIn.ts b/src/components/video-editor/useNvidiaCudaExportOptIn.ts
new file mode 100644
index 00000000..a674b4ae
--- /dev/null
+++ b/src/components/video-editor/useNvidiaCudaExportOptIn.ts
@@ -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(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,
+ };
+}