From 90ad8a694b051932323c09dcbdb124b0a23c621f Mon Sep 17 00:00:00 2001 From: wiiiii123 Date: Fri, 22 May 2026 18:47:30 +0700 Subject: [PATCH] refactor(export): wire lightning route policy --- .../modernVideoExporter.fallback.test.ts | 58 +++++++++++++++++++ src/lib/exporter/modernVideoExporter.ts | 19 +++--- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/lib/exporter/modernVideoExporter.fallback.test.ts b/src/lib/exporter/modernVideoExporter.fallback.test.ts index 95f06a99..7b491b00 100644 --- a/src/lib/exporter/modernVideoExporter.fallback.test.ts +++ b/src/lib/exporter/modernVideoExporter.fallback.test.ts @@ -118,6 +118,64 @@ describe("ModernVideoExporter native fallback routing", () => { expect(mocks.muxerFinalize).toHaveBeenCalledTimes(1); }, 15_000); + it("tries Windows auto static-layout before starting the streaming native encoder", async () => { + vi.stubGlobal("navigator", { + platform: "Win32", + userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", + }); + + const { ModernVideoExporter } = await import("./modernVideoExporter"); + const staticLayoutResult = { + success: true, + blob: new Blob([], { type: "video/mp4" }), + }; + const exporter = new ModernVideoExporter({ + videoUrl: "file:///recording.mp4", + width: 1920, + height: 1080, + frameRate: 30, + bitrate: 8_000_000, + wallpaper: "#101010", + padding: 0, + borderRadius: 0, + backgroundBlur: 0, + shadowIntensity: 0, + showShadow: false, + cropRegion: { x: 0, y: 0, width: 1, height: 1 }, + experimentalNativeExport: true, + backendPreference: "auto", + } as never) as unknown as { + export: () => Promise<{ success: boolean; blob?: Blob; error?: string }>; + initializeEncoder: () => Promise; + loadNativeStaticLayoutVideoInfo: () => Promise; + tryExportNativeStaticLayout: () => Promise; + tryStartNativeVideoExport: () => Promise; + }; + + const initializeEncoder = vi.spyOn(exporter, "initializeEncoder").mockResolvedValue({ + codec: "avc1.640034", + hardwareAcceleration: "prefer-hardware", + }); + const loadNativeStaticLayoutVideoInfo = vi + .spyOn(exporter, "loadNativeStaticLayoutVideoInfo") + .mockResolvedValue(mocks.videoInfo); + const tryExportNativeStaticLayout = vi + .spyOn(exporter, "tryExportNativeStaticLayout") + .mockResolvedValue(staticLayoutResult); + const tryStartNativeVideoExport = vi + .spyOn(exporter, "tryStartNativeVideoExport") + .mockResolvedValue(true); + + const result = await exporter.export(); + + expect(result).toBe(staticLayoutResult); + expect(loadNativeStaticLayoutVideoInfo).toHaveBeenCalledTimes(1); + expect(tryExportNativeStaticLayout).toHaveBeenCalledTimes(1); + expect(tryStartNativeVideoExport).not.toHaveBeenCalled(); + expect(initializeEncoder).not.toHaveBeenCalled(); + expect(mocks.streamingDecoderLoadMetadata).not.toHaveBeenCalled(); + }, 15_000); + it("retries the main decode path once with a readable file-backed source", async () => { const { ModernVideoExporter } = await import("./modernVideoExporter"); mocks.streamingDecoderGetEffectiveDuration.mockReturnValue(1); diff --git a/src/lib/exporter/modernVideoExporter.ts b/src/lib/exporter/modernVideoExporter.ts index 679c9a9d..3f9f39c5 100644 --- a/src/lib/exporter/modernVideoExporter.ts +++ b/src/lib/exporter/modernVideoExporter.ts @@ -8,8 +8,8 @@ import type { CursorStyle, CursorTelemetryPoint, Padding, - SpeedRegion, SourceAudioTrackSettings, + SpeedRegion, TrimRegion, WebcamOverlaySettings, ZoomMotionBlurTuning, @@ -49,7 +49,11 @@ import { isVideoWallpaperSource, } from "@/lib/wallpapers"; import { AudioProcessor, isAacAudioEncodingSupported } from "./audioEncoder"; -import { normalizeLightningRuntimePlatform, shouldPreferNativeAutoBackend } from "./backendPolicy"; +import { + normalizeLightningRuntimePlatform, + shouldPreferNativeAutoBackend, + shouldPreferNativeStaticLayoutBeforeBreeze, +} from "./backendPolicy"; import { buildEditedTrackSourceSegments, classifyEditedTrackStrategy } from "./editedTrackStrategy"; import { type ExportBackpressureProfile, @@ -376,14 +380,15 @@ export class ModernVideoExporter { const runtimePlatform = this.getRuntimePlatform(); let useNativeEncoder = false; let triedNativeStaticLayoutWithProbe = false; - let shouldDeferNativeEncoderStart = backendPreference === "breeze"; + let shouldDeferNativeEncoderStart = + backendPreference === "breeze" || + shouldPreferNativeStaticLayoutBeforeBreeze(runtimePlatform, backendPreference); this.lastNativeExportError = null; let stageStartedAt = this.getNowMs(); - if (backendPreference === "breeze") { - // Defer the streaming native encoder until after metadata is known. - // Static-layout exports can then use the faster Windows D3D compositor - // instead of unnecessarily rendering every frame through JS first. + if (shouldDeferNativeEncoderStart) { + // Defer the streaming native encoder until after metadata is known so + // static-layout exports can use the fastest compatible compositor first. } else if ( backendPreference === "auto" && shouldPreferNativeAutoBackend(runtimePlatform)