@hyperframes/engine 0.7.59 → 0.7.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/dist/config.d.ts +95 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +131 -16
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/services/audioMixer.d.ts.map +1 -1
- package/dist/services/audioMixer.js +38 -3
- package/dist/services/audioMixer.js.map +1 -1
- package/dist/services/browserLeasePool.d.ts +48 -0
- package/dist/services/browserLeasePool.d.ts.map +1 -0
- package/dist/services/browserLeasePool.js +210 -0
- package/dist/services/browserLeasePool.js.map +1 -0
- package/dist/services/browserManager.d.ts +6 -7
- package/dist/services/browserManager.d.ts.map +1 -1
- package/dist/services/browserManager.js +134 -177
- package/dist/services/browserManager.js.map +1 -1
- package/dist/services/chunkEncoder.d.ts.map +1 -1
- package/dist/services/chunkEncoder.js +45 -134
- package/dist/services/chunkEncoder.js.map +1 -1
- package/dist/services/frameCapture.d.ts +38 -1
- package/dist/services/frameCapture.d.ts.map +1 -1
- package/dist/services/frameCapture.js +119 -8
- package/dist/services/frameCapture.js.map +1 -1
- package/dist/services/pageNavigationTimeoutErrorHint.d.ts +79 -0
- package/dist/services/pageNavigationTimeoutErrorHint.d.ts.map +1 -0
- package/dist/services/pageNavigationTimeoutErrorHint.js +112 -0
- package/dist/services/pageNavigationTimeoutErrorHint.js.map +1 -0
- package/dist/services/parallelCoordinator.d.ts +21 -0
- package/dist/services/parallelCoordinator.d.ts.map +1 -1
- package/dist/services/parallelCoordinator.js +40 -1
- package/dist/services/parallelCoordinator.js.map +1 -1
- package/dist/services/protocolTimeoutErrorHint.d.ts +28 -0
- package/dist/services/protocolTimeoutErrorHint.d.ts.map +1 -0
- package/dist/services/protocolTimeoutErrorHint.js +45 -0
- package/dist/services/protocolTimeoutErrorHint.js.map +1 -0
- package/dist/services/streamingEncoder.d.ts.map +1 -1
- package/dist/services/streamingEncoder.js +23 -58
- package/dist/services/streamingEncoder.js.map +1 -1
- package/dist/services/systemMemory.d.ts.map +1 -1
- package/dist/services/systemMemory.js +4 -1
- package/dist/services/systemMemory.js.map +1 -1
- package/dist/services/videoFrameExtractor.d.ts +4 -5
- package/dist/services/videoFrameExtractor.d.ts.map +1 -1
- package/dist/services/videoFrameExtractor.js +57 -112
- package/dist/services/videoFrameExtractor.js.map +1 -1
- package/dist/types.d.ts +28 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/ffmpegBinaries.d.ts +2 -2
- package/dist/utils/ffmpegBinaries.d.ts.map +1 -1
- package/dist/utils/ffmpegBinaries.js +8 -82
- package/dist/utils/ffmpegBinaries.js.map +1 -1
- package/dist/utils/ffprobe.d.ts +3 -1
- package/dist/utils/ffprobe.d.ts.map +1 -1
- package/dist/utils/ffprobe.js +35 -42
- package/dist/utils/ffprobe.js.map +1 -1
- package/dist/utils/gpuEncoder.d.ts.map +1 -1
- package/dist/utils/gpuEncoder.js +32 -52
- package/dist/utils/gpuEncoder.js.map +1 -1
- package/dist/utils/gpuParityDiff.d.ts +142 -0
- package/dist/utils/gpuParityDiff.d.ts.map +1 -0
- package/dist/utils/gpuParityDiff.js +205 -0
- package/dist/utils/gpuParityDiff.js.map +1 -0
- package/dist/utils/managedChildProcess.d.ts +50 -0
- package/dist/utils/managedChildProcess.d.ts.map +1 -0
- package/dist/utils/managedChildProcess.js +148 -0
- package/dist/utils/managedChildProcess.js.map +1 -0
- package/dist/utils/processTracker.js +1 -1
- package/dist/utils/runFfmpeg.d.ts +3 -0
- package/dist/utils/runFfmpeg.d.ts.map +1 -1
- package/dist/utils/runFfmpeg.js +16 -50
- package/dist/utils/runFfmpeg.js.map +1 -1
- package/package.json +3 -2
|
@@ -237,6 +237,23 @@ export function formatRequestFailureDiagnostic(input) {
|
|
|
237
237
|
return (`[Browser:REQUESTFAILED] ${input.method} ${sanitizeDiagnosticUrl(input.url)} ` +
|
|
238
238
|
`resource=${input.resourceType} error=${input.failureText}`);
|
|
239
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Chromium reports media loads that it intentionally cancels during probing as
|
|
242
|
+
* request failures. They are expected when the probe discovers or seeks local
|
|
243
|
+
* audio/video and do not indicate a missing asset.
|
|
244
|
+
*/
|
|
245
|
+
export function shouldIgnoreRequestFailureDiagnostic(input) {
|
|
246
|
+
if (input.failureText !== "net::ERR_ABORTED")
|
|
247
|
+
return false;
|
|
248
|
+
if (input.resourceType === "media")
|
|
249
|
+
return true;
|
|
250
|
+
try {
|
|
251
|
+
return /\.(?:aac|flac|m4a|mp3|mp4|mov|oga|ogg|ogv|wav|webm)$/i.test(new URL(input.url).pathname);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
240
257
|
export function formatHttpErrorDiagnostic(input) {
|
|
241
258
|
const statusText = input.statusText ? ` ${input.statusText}` : "";
|
|
242
259
|
return (`[Browser:HTTP${input.status}] ${input.method} ${sanitizeDiagnosticUrl(input.url)} ` +
|
|
@@ -347,11 +364,13 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
347
364
|
(!forceScreenshot || forceDE);
|
|
348
365
|
if ((session.config?.useDrawElement ?? false) && supersampling) {
|
|
349
366
|
session.deGateReason = "supersampling";
|
|
367
|
+
session.deFallbackTrigger = "supersampling";
|
|
350
368
|
console.log("[engine] --experimental-fast-capture disabled for this render: drawElementImage " +
|
|
351
369
|
"ignores deviceScaleFactor, so supersampled (DPR > 1) output uses screenshot capture.");
|
|
352
370
|
}
|
|
353
371
|
if ((session.config?.useDrawElement ?? false) && !supersampling && forceScreenshot) {
|
|
354
372
|
session.deGateReason = "render_mode_hint";
|
|
373
|
+
session.deFallbackTrigger = "render_mode_hint";
|
|
355
374
|
console.log("[engine] fast capture: falling back to screenshot — render-mode compatibility " +
|
|
356
375
|
"hint forced screenshot capture (e.g. raw requestAnimationFrame composition).");
|
|
357
376
|
}
|
|
@@ -397,6 +416,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
397
416
|
});
|
|
398
417
|
if (!supportsDrawElement) {
|
|
399
418
|
session.deGateReason = "unsupported_chrome";
|
|
419
|
+
session.deFallbackTrigger = "unsupported_chrome";
|
|
400
420
|
console.log(`[engine] fast capture: falling back to ${session.launchCaptureMode} capture — ` +
|
|
401
421
|
"this Chrome build does not implement canvas.drawElementImage (Dev/Canary-only " +
|
|
402
422
|
"feature, ~151+); run `hyperframes browser ensure --force` to fetch a supported " +
|
|
@@ -420,6 +440,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
420
440
|
const mode = resolveDrawElementCaptureMode(session.isSwiftShader, transparent);
|
|
421
441
|
if (mode === "screenshot") {
|
|
422
442
|
session.deGateReason = "swiftshader";
|
|
443
|
+
session.deFallbackTrigger = "swiftshader";
|
|
423
444
|
// Fall back to the browser's LAUNCH mode, not unconditionally to
|
|
424
445
|
// "screenshot": on a BeginFrame-launched browser (Linux fast capture)
|
|
425
446
|
// Page.captureScreenshot hangs for the full protocol timeout, while
|
|
@@ -439,6 +460,11 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
439
460
|
const cssFx = await detectCssEffectRisk(page);
|
|
440
461
|
if (cssFx) {
|
|
441
462
|
session.deGateReason = `css_effect:${(cssFx.split(":")[0] ?? "").replace(/[^a-z-]/gi, "")}`;
|
|
463
|
+
// Full specific effect ("filter:blur" / "filter:drop-shadow" /
|
|
464
|
+
// "backdrop-filter" / "clip-path") — `deGateReason` sanitizes
|
|
465
|
+
// this to the low-cardinality prefix; `deFallbackTrigger` keeps
|
|
466
|
+
// the fine-grained value for the diagnostic profile emission.
|
|
467
|
+
session.deFallbackTrigger = cssFx;
|
|
442
468
|
console.log(`[engine] fast capture: falling back to ${session.launchCaptureMode} capture — ` +
|
|
443
469
|
`${cssFx} detected (drawElementImage cannot reproduce it; see fast-capture-limitations.md)`);
|
|
444
470
|
await routeToFallback();
|
|
@@ -469,6 +495,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
469
495
|
logInitPhase(`timeline at-risk predictor: ${atRisk.size}/${totalFrames} frames (${Math.round(atRiskFraction * 100)}%)`);
|
|
470
496
|
if (atRisk.size > 0 && atRiskFraction > fractionFloor) {
|
|
471
497
|
session.deGateReason = "at_risk_timeline";
|
|
498
|
+
session.deFallbackTrigger = "at_risk_timeline";
|
|
472
499
|
console.log(`[engine] fast capture: falling back to ${session.launchCaptureMode} capture — ` +
|
|
473
500
|
`${atRisk.size}/${totalFrames} frames animate a compositor-incompatible prop ` +
|
|
474
501
|
`(blend/3D/clip/mask); drawElementImage drops these mid-animation ` +
|
|
@@ -484,6 +511,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
484
511
|
const threeD = await initThreeDProjection(page);
|
|
485
512
|
if (!forceDE && !threeD.ok) {
|
|
486
513
|
session.deGateReason = "3d_init_failed";
|
|
514
|
+
session.deFallbackTrigger = "3d_init_failed";
|
|
487
515
|
console.log(`[engine] fast capture: falling back to ${session.launchCaptureMode} capture — ` +
|
|
488
516
|
`3D projection init failed (${threeD.reason ?? "unknown"})`);
|
|
489
517
|
await routeToFallback();
|
|
@@ -642,8 +670,56 @@ export async function createCaptureSession(serverUrl, outputDir, options, onBefo
|
|
|
642
670
|
? "beginframe"
|
|
643
671
|
: "screenshot";
|
|
644
672
|
const chromeArgs = buildChromeArgs({ width: options.width, height: options.height, captureMode: preMode }, { ...config, browserGpuMode: resolvedGpuMode });
|
|
645
|
-
const
|
|
673
|
+
const browserLease = await acquireBrowser(chromeArgs, config);
|
|
674
|
+
return constructCaptureSessionWithRollback({
|
|
675
|
+
browserLease,
|
|
676
|
+
serverUrl,
|
|
677
|
+
outputDir,
|
|
678
|
+
options,
|
|
679
|
+
onBeforeCapture,
|
|
680
|
+
config,
|
|
681
|
+
useDrawElement,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
async function constructCaptureSessionWithRollback(input) {
|
|
685
|
+
let page;
|
|
686
|
+
try {
|
|
687
|
+
return await constructCaptureSession({
|
|
688
|
+
...input,
|
|
689
|
+
onPageCreated: (createdPage) => {
|
|
690
|
+
page = createdPage;
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
catch (error) {
|
|
695
|
+
let pageClosed = true;
|
|
696
|
+
try {
|
|
697
|
+
if (page) {
|
|
698
|
+
const rollbackPage = page;
|
|
699
|
+
pageClosed = await waitForCloseWithTimeout(Promise.resolve().then(() => rollbackPage.close()));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
finally {
|
|
703
|
+
if (!pageClosed) {
|
|
704
|
+
console.warn("[FrameCapture] Timed out closing page during construction rollback; forcing browser process shutdown");
|
|
705
|
+
input.browserLease.forceRelease();
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
const browserClosed = await waitForCloseWithTimeout(input.browserLease.release());
|
|
709
|
+
if (!browserClosed) {
|
|
710
|
+
console.warn("[FrameCapture] Timed out closing browser during construction rollback; forcing browser process shutdown");
|
|
711
|
+
input.browserLease.forceRelease();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
throw error;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
async function constructCaptureSession(input) {
|
|
719
|
+
const { browserLease, serverUrl, outputDir, options, onBeforeCapture, config, useDrawElement, onPageCreated, } = input;
|
|
720
|
+
const { browser, captureMode } = browserLease;
|
|
646
721
|
const page = await browser.newPage();
|
|
722
|
+
onPageCreated(page);
|
|
647
723
|
// Polyfill esbuild's keepNames helper inside the page.
|
|
648
724
|
//
|
|
649
725
|
// The engine is published as raw TypeScript (`packages/engine/package.json`
|
|
@@ -744,6 +820,7 @@ export async function createCaptureSession(serverUrl, outputDir, options, onBefo
|
|
|
744
820
|
// PNG captures after `page.goto(...)` and the `window.__hf` readiness poll.
|
|
745
821
|
return {
|
|
746
822
|
browser,
|
|
823
|
+
browserLease,
|
|
747
824
|
page,
|
|
748
825
|
options: sessionOptions,
|
|
749
826
|
serverUrl,
|
|
@@ -1260,14 +1337,19 @@ export async function initializeSession(session) {
|
|
|
1260
1337
|
appendBrowserDiagnostic(session, text);
|
|
1261
1338
|
});
|
|
1262
1339
|
page.on("requestfailed", (request) => {
|
|
1263
|
-
|
|
1340
|
+
const resourceType = request.resourceType();
|
|
1341
|
+
const url = request.url();
|
|
1342
|
+
const failureText = request.failure()?.errorText ?? "unknown";
|
|
1343
|
+
if (resourceType === "script") {
|
|
1264
1344
|
recordScriptLoadFailure(session, request.url());
|
|
1265
1345
|
}
|
|
1346
|
+
if (shouldIgnoreRequestFailureDiagnostic({ resourceType, url, failureText }))
|
|
1347
|
+
return;
|
|
1266
1348
|
appendBrowserDiagnostic(session, formatRequestFailureDiagnostic({
|
|
1267
1349
|
method: request.method(),
|
|
1268
|
-
resourceType
|
|
1269
|
-
url
|
|
1270
|
-
failureText
|
|
1350
|
+
resourceType,
|
|
1351
|
+
url,
|
|
1352
|
+
failureText,
|
|
1271
1353
|
}));
|
|
1272
1354
|
});
|
|
1273
1355
|
page.on("response", (response) => {
|
|
@@ -2556,16 +2638,22 @@ export async function closeCaptureSession(session) {
|
|
|
2556
2638
|
const pageClosed = await waitForCloseWithTimeout(session.page.close());
|
|
2557
2639
|
if (!pageClosed) {
|
|
2558
2640
|
console.warn("[FrameCapture] Timed out closing page; forcing browser process shutdown");
|
|
2559
|
-
|
|
2641
|
+
if (session.browserLease)
|
|
2642
|
+
session.browserLease.forceRelease();
|
|
2643
|
+
else
|
|
2644
|
+
forceReleaseBrowser(session.browser);
|
|
2560
2645
|
session.browserReleased = true;
|
|
2561
2646
|
}
|
|
2562
2647
|
session.pageReleased = true;
|
|
2563
2648
|
}
|
|
2564
2649
|
if (!session.browserReleased && session.browser) {
|
|
2565
|
-
const browserClosed = await waitForCloseWithTimeout(releaseBrowser(session.browser, session.config));
|
|
2650
|
+
const browserClosed = await waitForCloseWithTimeout(session.browserLease?.release() ?? releaseBrowser(session.browser, session.config));
|
|
2566
2651
|
if (!browserClosed) {
|
|
2567
2652
|
console.warn("[FrameCapture] Timed out closing browser; forcing browser process shutdown");
|
|
2568
|
-
|
|
2653
|
+
if (session.browserLease)
|
|
2654
|
+
session.browserLease.forceRelease();
|
|
2655
|
+
else
|
|
2656
|
+
forceReleaseBrowser(session.browser);
|
|
2569
2657
|
}
|
|
2570
2658
|
session.browserReleased = true;
|
|
2571
2659
|
}
|
|
@@ -2729,6 +2817,26 @@ function medianOf(samples) {
|
|
|
2729
2817
|
const sorted = [...samples].sort((a, b) => a - b);
|
|
2730
2818
|
return Math.round(sorted[Math.floor(sorted.length / 2)] ?? 0);
|
|
2731
2819
|
}
|
|
2820
|
+
/**
|
|
2821
|
+
* Percentile of a positive-real sample set (nearest-rank; matches how the
|
|
2822
|
+
* existing {@link medianOf} p50 helper picks the middle index). `p` is a
|
|
2823
|
+
* fraction in [0, 1]; the sample at `floor(p * n)` (clamped to `[0, n-1]`)
|
|
2824
|
+
* is returned. Sample set is not mutated. Returns 0 for empty input, mirroring
|
|
2825
|
+
* the p50 helper.
|
|
2826
|
+
*
|
|
2827
|
+
* Used for the `capture_fallback_profile` observability checkpoint added in
|
|
2828
|
+
* the fast-capture fallback profiling PR: we already collect `capturePerf.frameMs`
|
|
2829
|
+
* per session, so computing p95/p99 is one sort + two lookups — cheap enough
|
|
2830
|
+
* to always compute alongside the existing p50, no separate opt-in path
|
|
2831
|
+
* needed for the math. The env gate lives at the emission site.
|
|
2832
|
+
*/
|
|
2833
|
+
export function percentileOf(samples, p) {
|
|
2834
|
+
if (samples.length === 0)
|
|
2835
|
+
return 0;
|
|
2836
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
2837
|
+
const idx = Math.min(sorted.length - 1, Math.max(0, Math.floor(p * sorted.length)));
|
|
2838
|
+
return Math.round(sorted[idx] ?? 0);
|
|
2839
|
+
}
|
|
2732
2840
|
export function getCapturePerfSummary(session) {
|
|
2733
2841
|
const frames = Math.max(1, session.capturePerf.frames);
|
|
2734
2842
|
return {
|
|
@@ -2738,6 +2846,8 @@ export function getCapturePerfSummary(session) {
|
|
|
2738
2846
|
avgBeforeCaptureMs: Math.round(session.capturePerf.beforeCaptureMs / frames),
|
|
2739
2847
|
avgScreenshotMs: Math.round(session.capturePerf.screenshotMs / frames),
|
|
2740
2848
|
p50TotalMs: medianOf(session.capturePerf.frameMs),
|
|
2849
|
+
p95TotalMs: percentileOf(session.capturePerf.frameMs, 0.95),
|
|
2850
|
+
p99TotalMs: percentileOf(session.capturePerf.frameMs, 0.99),
|
|
2741
2851
|
subTimelineWaitOutcome: session.subTimelineWaitOutcome,
|
|
2742
2852
|
warnings: session.warnings.map((warning) => ({
|
|
2743
2853
|
...warning,
|
|
@@ -2758,6 +2868,7 @@ export function getCapturePerfSummary(session) {
|
|
|
2758
2868
|
beginFrameHasDamage: session.beginFrameHasDamageCount,
|
|
2759
2869
|
captureMode: session.captureMode,
|
|
2760
2870
|
deGateReason: session.deGateReason,
|
|
2871
|
+
deFallbackTrigger: session.deFallbackTrigger,
|
|
2761
2872
|
deWorkerEncode: session.workerEncodeEnabled ?? false,
|
|
2762
2873
|
deVerifyArmed: session.deVerifyFrames?.size ?? 0,
|
|
2763
2874
|
deVerifyInitMs: session.deVerifyInitMs ?? 0,
|