mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 23:05:49 +00:00
fix(export): fallback native lightning errors
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
const videoInfo = {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
duration: 1,
|
||||
streamDuration: 1,
|
||||
frameRate: 30,
|
||||
codec: "h264",
|
||||
hasAudio: false,
|
||||
audioCodec: null,
|
||||
audioSampleRate: null,
|
||||
};
|
||||
|
||||
return {
|
||||
videoInfo,
|
||||
streamingDecoderDestroy: vi.fn(),
|
||||
streamingDecoderCancel: vi.fn(),
|
||||
streamingDecoderDecodeAll: vi.fn(async () => {}),
|
||||
streamingDecoderGetDemuxer: vi.fn(() => null),
|
||||
streamingDecoderGetEffectiveDuration: vi.fn(() => 0),
|
||||
streamingDecoderLoadMetadata: vi.fn(async () => videoInfo),
|
||||
frameRendererDestroy: vi.fn(),
|
||||
frameRendererGetBackend: vi.fn(() => "webgl"),
|
||||
frameRendererInitialize: vi.fn(async () => {}),
|
||||
muxerDestroy: vi.fn(),
|
||||
muxerFinalize: vi.fn(async () => ({
|
||||
mode: "buffer" as const,
|
||||
blob: new Blob([], { type: "video/mp4" }),
|
||||
})),
|
||||
muxerInitialize: vi.fn(async () => {}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./streamingDecoder", () => ({
|
||||
StreamingVideoDecoder: vi.fn().mockImplementation(function () {
|
||||
return {
|
||||
cancel: mocks.streamingDecoderCancel,
|
||||
decodeAll: mocks.streamingDecoderDecodeAll,
|
||||
destroy: mocks.streamingDecoderDestroy,
|
||||
getDemuxer: mocks.streamingDecoderGetDemuxer,
|
||||
getEffectiveDuration: mocks.streamingDecoderGetEffectiveDuration,
|
||||
loadMetadata: mocks.streamingDecoderLoadMetadata,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("./modernFrameRenderer", () => ({
|
||||
FrameRenderer: vi.fn().mockImplementation(function () {
|
||||
return {
|
||||
destroy: mocks.frameRendererDestroy,
|
||||
getRendererBackend: mocks.frameRendererGetBackend,
|
||||
initialize: mocks.frameRendererInitialize,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("./muxer", () => ({
|
||||
VideoMuxer: vi.fn().mockImplementation(function () {
|
||||
return {
|
||||
destroy: mocks.muxerDestroy,
|
||||
finalize: mocks.muxerFinalize,
|
||||
initialize: mocks.muxerInitialize,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("ModernVideoExporter native fallback routing", () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("falls back to WebCodecs instead of surfacing a native error when Breeze is unavailable", async () => {
|
||||
const { ModernVideoExporter } = await import("./modernVideoExporter");
|
||||
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: "breeze",
|
||||
} as never) as unknown as {
|
||||
export: () => Promise<{ success: boolean; blob?: Blob; error?: string }>;
|
||||
initializeEncoder: () => Promise<unknown>;
|
||||
loadNativeStaticLayoutVideoInfo: () => Promise<unknown>;
|
||||
tryExportNativeStaticLayout: () => Promise<unknown>;
|
||||
tryStartNativeVideoExport: () => Promise<boolean>;
|
||||
lastNativeExportError: string | null;
|
||||
};
|
||||
|
||||
vi.spyOn(exporter, "loadNativeStaticLayoutVideoInfo").mockResolvedValue(mocks.videoInfo);
|
||||
vi.spyOn(exporter, "tryExportNativeStaticLayout").mockResolvedValue(null);
|
||||
vi.spyOn(exporter, "tryStartNativeVideoExport").mockImplementation(async () => {
|
||||
exporter.lastNativeExportError = "Breeze native encoder unavailable";
|
||||
return false;
|
||||
});
|
||||
const initializeEncoder = vi.spyOn(exporter, "initializeEncoder").mockResolvedValue({
|
||||
codec: "avc1.640034",
|
||||
hardwareAcceleration: "prefer-hardware",
|
||||
});
|
||||
|
||||
const result = await exporter.export();
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.blob).toBeInstanceOf(Blob);
|
||||
expect(initializeEncoder).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.muxerFinalize).toHaveBeenCalledTimes(1);
|
||||
}, 15_000);
|
||||
});
|
||||
@@ -346,7 +346,7 @@ export class ModernVideoExporter {
|
||||
const runtimePlatform = this.getRuntimePlatform();
|
||||
let useNativeEncoder = false;
|
||||
let triedNativeStaticLayoutWithProbe = false;
|
||||
const shouldDeferNativeEncoderStart = backendPreference === "breeze";
|
||||
let shouldDeferNativeEncoderStart = backendPreference === "breeze";
|
||||
this.lastNativeExportError = null;
|
||||
|
||||
let stageStartedAt = this.getNowMs();
|
||||
@@ -512,10 +512,23 @@ export class ModernVideoExporter {
|
||||
useNativeEncoder = await this.tryStartNativeVideoExport();
|
||||
this.nativeSessionStartTimeMs = this.getNowMs() - stageStartedAt;
|
||||
if (!useNativeEncoder) {
|
||||
throw new Error(
|
||||
const nativeFailure =
|
||||
this.lastNativeExportError ??
|
||||
`${NATIVE_EXPORT_ENGINE_NAME} export is unavailable for this output profile on this system.`,
|
||||
`${NATIVE_EXPORT_ENGINE_NAME} export is unavailable for this output profile on this system.`;
|
||||
console.warn(
|
||||
`[VideoExporter] ${NATIVE_EXPORT_ENGINE_NAME} native export unavailable after static-layout fallback; falling back to WebCodecs.`,
|
||||
nativeFailure,
|
||||
);
|
||||
shouldDeferNativeEncoderStart = false;
|
||||
this.backpressureProfile = getExportBackpressureProfile({
|
||||
encodeBackend: "webcodecs",
|
||||
width: this.config.width,
|
||||
height: this.config.height,
|
||||
frameRate: this.config.frameRate,
|
||||
encodingMode: this.config.encodingMode,
|
||||
});
|
||||
this.maxNativeWriteInFlight = 1;
|
||||
await this.initializeEncoder();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user