@hyperframes/engine 0.9.1 → 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.1",
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.1",
63
- "@hyperframes/parsers": "^0.9.1"
62
+ "@hyperframes/core": "^0.9.2",
63
+ "@hyperframes/parsers": "^0.9.2"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/node": "^25.0.10",