Merge pull request #582 from webadderallorg/fix/v130-lightning-auto-route-fallback

fix(export): keep Windows auto off static layout first
This commit is contained in:
Phạm Thị Minh Hồng
2026-05-24 19:59:07 +07:00
committed by GitHub
4 changed files with 87 additions and 17 deletions
+8 -8
View File
@@ -27,8 +27,8 @@ describe("backendPolicy", () => {
expect(getDefaultLightningRenderBackend()).toBe("webgl");
});
it("puts visually compatible Windows auto exports on native static layout before Breeze", () => {
expect(shouldPreferNativeStaticLayoutBeforeBreeze("win32", "auto")).toBe(true);
it("keeps Windows auto exports on the streaming route by default", () => {
expect(shouldPreferNativeStaticLayoutBeforeBreeze("win32", "auto")).toBe(false);
expect(shouldPreferNativeStaticLayoutBeforeBreeze("darwin", "auto")).toBe(false);
expect(
@@ -38,16 +38,16 @@ describe("backendPolicy", () => {
nativeStaticLayoutAvailable: true,
}),
).toMatchObject({
selectedRoute: "native-static-layout",
selectedRoute: "breeze-stream",
decisions: [
{ route: "native-static-layout", status: "selected" },
{ route: "breeze-stream", status: "fallback" },
{ route: "native-static-layout", status: "rejected" },
{ route: "breeze-stream", status: "selected" },
{ route: "webcodecs", status: "fallback" },
],
});
});
it("documents the Breeze fallback when Windows static native is rejected", () => {
it("ignores native static skip reasons for Windows auto routing", () => {
expect(
planLightningExportRoutes({
backendPreference: "auto",
@@ -61,12 +61,12 @@ describe("backendPolicy", () => {
{
route: "native-static-layout",
status: "rejected",
reasons: ["unsupported-frame-overlay"],
reasons: ["platform-does-not-use-native-static-layout"],
},
{
route: "breeze-stream",
status: "selected",
reasons: ["windows-native-static-fallback"],
reasons: ["platform-prefers-native-streaming"],
},
{
route: "webcodecs",
+10 -2
View File
@@ -41,11 +41,19 @@ export interface LightningExportRoutePlan {
decisions: LightningExportRouteDecision[];
}
// Disabled while stabilizing v1.3.0: the Windows auto static-layout probe can
// leave Lightning at "Preparing export..." before the stable streaming fallback.
const WINDOWS_AUTO_STATIC_LAYOUT_FIRST_ENABLED = false;
export function shouldPreferNativeStaticLayoutBeforeBreeze(
platform: LightningRuntimePlatform,
backendPreference: ExportBackendPreference,
): boolean {
return backendPreference === "auto" && platform === "win32";
return (
WINDOWS_AUTO_STATIC_LAYOUT_FIRST_ENABLED &&
backendPreference === "auto" &&
platform === "win32"
);
}
export function planLightningExportRoutes(options: {
@@ -92,7 +100,7 @@ export function planLightningExportRoutes(options: {
reasons: [
options.backendPreference === "breeze"
? "user-selected-breeze"
: "windows-native-static-fallback",
: "native-static-layout-not-auto-default",
],
});
decisions.push({
@@ -118,7 +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 () => {
it("keeps Windows auto exports on the streaming native route before static layout", async () => {
vi.stubGlobal("navigator", {
platform: "Win32",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
});
const { ModernVideoExporter } = await import("./modernVideoExporter");
const nativeResult = {
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 }>;
finishNativeVideoExport: () => Promise<unknown>;
loadNativeStaticLayoutVideoInfo: () => Promise<unknown>;
tryExportNativeStaticLayout: () => Promise<unknown>;
tryStartNativeVideoExport: () => Promise<boolean>;
};
const loadNativeStaticLayoutVideoInfo = vi.spyOn(
exporter,
"loadNativeStaticLayoutVideoInfo",
);
const tryExportNativeStaticLayout = vi.spyOn(exporter, "tryExportNativeStaticLayout");
const tryStartNativeVideoExport = vi
.spyOn(exporter, "tryStartNativeVideoExport")
.mockResolvedValue(true);
const finishNativeVideoExport = vi
.spyOn(exporter, "finishNativeVideoExport")
.mockResolvedValue(nativeResult);
const result = await exporter.export();
expect(result.success).toBe(true);
expect(result.blob).toBe(nativeResult.blob);
expect(tryStartNativeVideoExport).toHaveBeenCalledTimes(1);
expect(loadNativeStaticLayoutVideoInfo).not.toHaveBeenCalled();
expect(tryExportNativeStaticLayout).not.toHaveBeenCalled();
expect(mocks.streamingDecoderLoadMetadata).toHaveBeenCalledTimes(1);
expect(finishNativeVideoExport).toHaveBeenCalledTimes(1);
}, 15_000);
it("tries Windows auto static-layout first when NVIDIA CUDA is opted in", async () => {
vi.stubGlobal("navigator", {
platform: "Win32",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
@@ -143,6 +200,7 @@ describe("ModernVideoExporter native fallback routing", () => {
showShadow: false,
cropRegion: { x: 0, y: 0, width: 1, height: 1 },
experimentalNativeExport: true,
experimentalNvidiaCudaExport: true,
backendPreference: "auto",
} as never) as unknown as {
export: () => Promise<{ success: boolean; blob?: Blob; error?: string }>;
+10 -6
View File
@@ -382,9 +382,16 @@ export class ModernVideoExporter {
const runtimePlatform = this.getRuntimePlatform();
let useNativeEncoder = false;
let triedNativeStaticLayoutWithProbe = false;
const prefersNativeStaticLayoutBeforeBreeze =
shouldPreferNativeStaticLayoutBeforeBreeze(runtimePlatform, backendPreference);
const shouldTryNativeStaticLayout =
backendPreference === "breeze" ||
this.config.experimentalNvidiaCudaExport === true ||
prefersNativeStaticLayoutBeforeBreeze;
let shouldDeferNativeEncoderStart =
backendPreference === "breeze" ||
shouldPreferNativeStaticLayoutBeforeBreeze(runtimePlatform, backendPreference);
this.config.experimentalNvidiaCudaExport === true ||
prefersNativeStaticLayoutBeforeBreeze;
this.lastNativeExportError = null;
let stageStartedAt = this.getNowMs();
@@ -478,10 +485,7 @@ export class ModernVideoExporter {
maxInFlightNativeWrites: this.maxNativeWriteInFlight,
});
if (
(backendPreference === "auto" || backendPreference === "breeze") &&
!useNativeEncoder
) {
if (shouldTryNativeStaticLayout && !useNativeEncoder) {
const nativeVideoInfo = await this.loadNativeStaticLayoutVideoInfo();
if (nativeVideoInfo) {
triedNativeStaticLayoutWithProbe = true;
@@ -530,7 +534,7 @@ export class ModernVideoExporter {
const totalFrames = Math.ceil(effectiveDuration * this.config.frameRate);
if (
(backendPreference === "auto" || backendPreference === "breeze") &&
shouldTryNativeStaticLayout &&
!useNativeEncoder &&
!triedNativeStaticLayoutWithProbe
) {