@hyperframes/engine 0.9.0 → 0.9.2
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.
|
@@ -11,6 +11,7 @@ import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
|
11
11
|
import { join } from "path";
|
|
12
12
|
import { COMPOSITION_SOURCE_URL, MEDIA_RENDER_ID_ATTR, quantizeSeekTime, quantizeTimeToFrame, fpsToNumber, resolveAuthoredTimingWindow, AUTHORED_SOURCE_BEGIN, AUTHORED_SOURCE_END, RUNTIME_BOOTSTRAP_ATTR, SCENE_CALLBACK_ERROR_PREFIX, } from "@hyperframes/core";
|
|
13
13
|
import { holdRenderFrameVideos } from "./renderFrameVideoHold.js";
|
|
14
|
+
import { recordWebglContexts, releaseEndedWebglContexts } from "./webglRelease.js";
|
|
14
15
|
import { asDrawElementCaptureFailure, DrawElementCaptureError } from "./drawElementCaptureError.js";
|
|
15
16
|
import { encodePng } from "../utils/alphaBlit.js";
|
|
16
17
|
import { DEFAULT_SAMPLES_PER_FRAME, MotionBlurAccumulator, SPATIAL_TWEEN_PROPERTIES, adaptiveSampleCount, motionBlurProbeTimes, motionBlurSampleTimes, motionBlurWindowIsStatic, probeDiffMagnitude, resolveMotionBlurPlan, } from "./motionBlur.js";
|
|
@@ -1004,6 +1005,7 @@ async function constructCaptureSession(input) {
|
|
|
1004
1005
|
}
|
|
1005
1006
|
});
|
|
1006
1007
|
await installMediaPageHooks(page, options);
|
|
1008
|
+
await page.evaluateOnNewDocument(recordWebglContexts);
|
|
1007
1009
|
// Fast capture: record accelerated canvases (webgl/webgl2/webgpu) and force
|
|
1008
1010
|
// preserveDrawingBuffer before any page script can create a context — their
|
|
1009
1011
|
// paint records freeze at the first frame, so captureDrawElementFrame
|
|
@@ -2345,6 +2347,7 @@ async function prepareFrameForCapture(session, frameIndex, time, seekOptions, he
|
|
|
2345
2347
|
const seekStart = Date.now();
|
|
2346
2348
|
session.captureStep = "seeking the timeline";
|
|
2347
2349
|
const hasPendingComposite = await seekPageTimeline(page, quantizedTime, seekOptions);
|
|
2350
|
+
await releaseEndedWebglContexts(page, quantizedTime);
|
|
2348
2351
|
session.captureStep = "decoding CSS background images";
|
|
2349
2352
|
await decodeDynamicCssBackgroundImages(page);
|
|
2350
2353
|
const seekMs = Date.now() - seekStart;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Page } from "puppeteer-core";
|
|
2
|
+
/** Records each WebGL context a page creates. Register with page.evaluateOnNewDocument before navigation. */
|
|
3
|
+
export declare function recordWebglContexts(): void;
|
|
4
|
+
/** Releases the WebGL context of each canvas in a hidden clip whose runtime-resolved window ended by `time`. */
|
|
5
|
+
export declare function releaseEndedWebglContexts(page: Page, time: number): Promise<number>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Records each WebGL context a page creates. Register with page.evaluateOnNewDocument before navigation. */
|
|
2
|
+
export function recordWebglContexts() {
|
|
3
|
+
const w = window;
|
|
4
|
+
w.__hf_gl_contexts = [];
|
|
5
|
+
const canvasProto = HTMLCanvasElement.prototype;
|
|
6
|
+
const orig = canvasProto.getContext;
|
|
7
|
+
canvasProto.getContext = function (type, attrs) {
|
|
8
|
+
const ctx = orig.call(this, type, attrs);
|
|
9
|
+
if (ctx && (type === "webgl" || type === "webgl2" || type === "experimental-webgl")) {
|
|
10
|
+
const list = w.__hf_gl_contexts ?? [];
|
|
11
|
+
if (!list.some((r) => r.ctx === ctx))
|
|
12
|
+
list.push({ canvas: this, ctx });
|
|
13
|
+
w.__hf_gl_contexts = list;
|
|
14
|
+
}
|
|
15
|
+
return ctx;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Releases the WebGL context of each canvas in a hidden clip whose runtime-resolved window ended by `time`. */
|
|
19
|
+
export async function releaseEndedWebglContexts(page, time) {
|
|
20
|
+
return page.evaluate((t) => {
|
|
21
|
+
const clipWindow = window.__hf?.clipWindow;
|
|
22
|
+
if (!clipWindow)
|
|
23
|
+
return 0;
|
|
24
|
+
const hasEnded = (el) => {
|
|
25
|
+
for (let up = el; up; up = up.parentElement) {
|
|
26
|
+
const span = up.hasAttribute("data-hf-clip-hidden") ? clipWindow(up) : null;
|
|
27
|
+
if (span && Number.isFinite(span.end) && span.end <= t)
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
};
|
|
32
|
+
const live = (window.__hf_gl_contexts ?? []).filter((r) => !r.lost && hasEnded(r.canvas));
|
|
33
|
+
for (const rec of live) {
|
|
34
|
+
rec.lost = true;
|
|
35
|
+
rec.ctx.getExtension("WEBGL_lose_context")?.loseContext();
|
|
36
|
+
}
|
|
37
|
+
return live.length;
|
|
38
|
+
}, time);
|
|
39
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -123,6 +123,10 @@ export interface HfProtocol {
|
|
|
123
123
|
media?: HfMediaElement[];
|
|
124
124
|
/** Optional: shader transition metadata, populated by @hyperframes/shader-transitions */
|
|
125
125
|
transitions?: HfTransitionMeta[];
|
|
126
|
+
clipWindow?(el: Element): {
|
|
127
|
+
start: number;
|
|
128
|
+
end: number;
|
|
129
|
+
} | null;
|
|
126
130
|
}
|
|
127
131
|
export interface CaptureOptions {
|
|
128
132
|
width: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"puppeteer": "~25.11.0",
|
|
60
60
|
"puppeteer-core": "^25.11.0",
|
|
61
61
|
"undici": "7.25.0",
|
|
62
|
-
"@hyperframes/core": "^0.9.
|
|
63
|
-
"@hyperframes/parsers": "^0.9.
|
|
62
|
+
"@hyperframes/core": "^0.9.2",
|
|
63
|
+
"@hyperframes/parsers": "^0.9.2"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/node": "^25.0.10",
|