Files
Recordly/electron/ipc/export/nativeVideo/sourceProxy.timing.test.ts
T
webadderall 088aa0bb94 Fix Recordly 2.0 part 2 review findings
Integrate validated app changes and preserve original stack history.
2026-11-07 14:48:46 +11:00

117 lines
3.3 KiB
TypeScript

import { beforeEach, expect, it, vi } from "vitest";
const state = vi.hoisted(() => ({ run: vi.fn() }));
vi.mock("./process", () => ({ runFfmpegWithMetrics: state.run }));
vi.mock("../../ffmpeg/metadata", () => ({ probeNativeVideoMetadata: vi.fn() }));
import { buildWindowsGpuWebcamProxyArgs, prepareWindowsGpuWebcamInput } from "./sourceProxy";
import type { NativeStaticLayoutExportOptions } from "./types";
const base: NativeStaticLayoutExportOptions = {
inputPath: "/screen.mp4",
webcamInputPath: "/camera.webm",
width: 1920,
height: 1080,
frameRate: 30,
bitrate: 12000000,
durationSec: 10,
contentWidth: 1920,
contentHeight: 1080,
offsetX: 0,
offsetY: 0,
backgroundColor: "#000000",
encodingMode: "balanced",
};
const segment = (start: number, end: number, outputStart = 0, speed = 1) => ({
sourceStartMs: start * 1000,
sourceEndMs: end * 1000,
outputStartMs: outputStart * 1000,
outputEndMs: (outputStart + (end - start) / speed) * 1000,
speed,
});
const flag = (args: string[], name: string) => args[args.indexOf(name) + 1];
beforeEach(() => {
state.run.mockReset().mockResolvedValue({ success: true, elapsedMs: 5 });
});
it.each([
["unedited", {}, "10"],
["later trimmed source", { timelineSegments: [segment(60, 70)] }, "70"],
["speed-up", { timelineSegments: [segment(60, 80, 0, 2)] }, "80"],
["slow-down", { timelineSegments: [segment(60, 65, 0, 0.5)] }, "65"],
["negative webcam offset", { webcamTimeOffsetMs: -500 }, "10.5"],
[
"trim with webcam delay",
{ timelineSegments: [segment(60, 70)], webcamTimeOffsetMs: 3000 },
"67",
],
[
"nonmonotonic source ranges",
{ timelineSegments: [segment(60, 65), segment(10, 15, 5)] },
"65",
],
["original coverage with positive offset", { webcamTimeOffsetMs: 5000 }, "10"],
] as const)("covers required camera samples for %s without changing proxy encoding", (_name, changes, duration) => {
const args = buildWindowsGpuWebcamProxyArgs(
{
...base,
...changes,
timelineSegments: changes.timelineSegments ? [...changes.timelineSegments] : undefined,
},
"/proxy.mp4",
);
expect(flag(args, "-t")).toBe(duration);
const reference = [
"-y",
"-hide_banner",
"-loglevel",
"error",
"-i",
"/camera.webm",
"-map",
"0:v:0",
"-an",
"-t",
duration,
"-vf",
"scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=1,fps=30",
"-c:v",
"libx264",
"-preset",
"ultrafast",
"-tune",
"zerolatency",
"-b:v",
"4000000",
"-maxrate",
"4800000",
"-bufsize",
"8000000",
"-pix_fmt",
"yuv420p",
"-movflags",
"+faststart",
"/proxy.mp4",
];
expect(args).toEqual(reference);
});
it("passes mapped coverage and a matching processing deadline to the actual proxy process", async () => {
const options = { ...base, durationSec: 60, timelineSegments: [segment(60, 660, 0, 10)] };
await prepareWindowsGpuWebcamInput("ffmpeg", options, "/proxy.mp4", {
terminating: false,
currentProcess: null,
});
const call = state.run.mock.calls[0];
expect(flag(call[1], "-t")).toBe("660");
expect(call[2]).toBe(660000);
});
it("keeps supported native webcam files unchanged", async () => {
const result = await prepareWindowsGpuWebcamInput(
"ffmpeg",
{ ...base, webcamInputPath: "/camera.mp4" },
"/proxy.mp4",
{ terminating: false, currentProcess: null },
);
expect(result.inputPath).toBe("/camera.mp4");
expect(state.run).not.toHaveBeenCalled();
});