( serverUrl: string, outputDir: string, options: CaptureOptions, onBeforeCapture: BeforeCaptureHook | null = null, config?: Partial<EngineConfig>, )
| 344 | |
| 345 | // fallow-ignore-next-line unit-size |
| 346 | export async function createCaptureSession( |
| 347 | serverUrl: string, |
| 348 | outputDir: string, |
| 349 | options: CaptureOptions, |
| 350 | onBeforeCapture: BeforeCaptureHook | null = null, |
| 351 | config?: Partial<EngineConfig>, |
| 352 | ): Promise<CaptureSession> { |
| 353 | if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true }); |
| 354 | |
| 355 | // Determine capture mode before building args — BeginFrame flags only apply on Linux. |
| 356 | // BeginFrame's compositor does not preserve alpha; callers that pass |
| 357 | // `options.format === "png"` for transparent capture should also set |
| 358 | // `config.forceScreenshot = true` (the producer's renderOrchestrator does this |
| 359 | // automatically when `RenderConfig.format` is an alpha-capable value). |
| 360 | const headlessShell = resolveHeadlessShellPath(config); |
| 361 | const isLinux = process.platform === "linux"; |
| 362 | const forceScreenshot = config?.forceScreenshot ?? DEFAULT_CONFIG.forceScreenshot; |
| 363 | // BeginFrame's screenshot does not honor a viewport `deviceScaleFactor` |
| 364 | // (the captured surface is sized by the OS window in CSS pixels regardless |
| 365 | // of `Emulation.setDeviceMetricsOverride`'s DPR). When supersampling we |
| 366 | // need explicit clip+scale on `Page.captureScreenshot`, so fall back to |
| 367 | // the screenshot path for any DPR > 1. |
| 368 | const supersampling = (options.deviceScaleFactor ?? 1) > 1; |
| 369 | const preMode: CaptureMode = |
| 370 | headlessShell && isLinux && !forceScreenshot && !supersampling ? "beginframe" : "screenshot"; |
| 371 | const requestedGpuMode = config?.browserGpuMode ?? DEFAULT_CONFIG.browserGpuMode; |
| 372 | const resolvedGpuMode = await resolveBrowserGpuMode(requestedGpuMode, { |
| 373 | chromePath: headlessShell ?? undefined, |
| 374 | browserTimeout: config?.browserTimeout, |
| 375 | }); |
| 376 | const chromeArgs = buildChromeArgs( |
| 377 | { width: options.width, height: options.height, captureMode: preMode }, |
| 378 | { ...config, browserGpuMode: resolvedGpuMode }, |
| 379 | ); |
| 380 | |
| 381 | const { browser, captureMode } = await acquireBrowser(chromeArgs, config); |
| 382 | |
| 383 | const page = await browser.newPage(); |
| 384 | // Polyfill esbuild's keepNames helper inside the page. |
| 385 | // |
| 386 | // The engine is published as raw TypeScript (`packages/engine/package.json` |
| 387 | // points `main`/`exports` at `./src/index.ts`) and downstream consumers |
| 388 | // execute it through transpilers that may inject `__name(fn, "name")` |
| 389 | // wrappers around named functions. Empirically, this happens with: |
| 390 | // - tsx (its esbuild loader runs with keepNames=true), used by the |
| 391 | // producer's parity-harness, ad-hoc dev scripts, and the |
| 392 | // `bun run --filter @hyperframes/engine test` Vitest path. |
| 393 | // - any tsup/esbuild build that explicitly enables keepNames. |
| 394 | // |
| 395 | // The HeyGen CLI (`packages/cli`) bundles this engine via tsup with |
| 396 | // keepNames left at its default (false) — verified by grepping |
| 397 | // `packages/cli/dist/cli.js`, where `__name(...)` call sites are absent. |
| 398 | // Bun's TS loader also does not currently inject `__name`. Even so, |
| 399 | // anything that calls `page.evaluate(fn)` with a nested named function |
| 400 | // under tsx (most local development and tests) will serialize bodies |
| 401 | // like `__name(nested,"nested")` and crash with `__name is not defined` |
| 402 | // in the browser. The shim makes such calls a no-op. |
| 403 | // |
no test coverage detected