@hyperframes/producer 0.9.20 → 0.9.21
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/dist/distributed.js +87 -4
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +73 -73
- package/dist/index.js +90 -4
- package/dist/public-server.js +66 -1
- package/dist/renderRequest.d.ts +1 -0
- package/dist/services/distributed/plan.d.ts +7 -5
- package/dist/services/fileServer.d.ts +3 -3
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -410,6 +410,68 @@ ${seededRandomBlock}
|
|
|
410
410
|
}
|
|
411
411
|
};
|
|
412
412
|
|
|
413
|
+
// Timers set once rendering has started run on film time: each seek fires every timer due by then, in
|
|
414
|
+
// due order. Until a seek after the page reports ready, timers keep the wall clock: setup code waits on
|
|
415
|
+
// them while the renderer may already be seeking.
|
|
416
|
+
var rendering = false;
|
|
417
|
+
var timerQueue = [];
|
|
418
|
+
var nextTimerId = 1000000000;
|
|
419
|
+
var firingDepth = 0;
|
|
420
|
+
function addTimer(callback, delay, args, repeats) {
|
|
421
|
+
// A timer set from a firing timer waits at least 1 ms, so a zero-delay chain cannot stall a seek.
|
|
422
|
+
var ms = Math.max(firingDepth > 0 ? 1 : 0, Number(delay) || 0);
|
|
423
|
+
var entry = { id: nextTimerId++, callback: callback, args: args, due: virtualNowMs + ms, every: repeats ? Math.max(1, ms) : 0, cancelled: false };
|
|
424
|
+
timerQueue.push(entry);
|
|
425
|
+
return entry.id;
|
|
426
|
+
}
|
|
427
|
+
function cancelTimer(id) {
|
|
428
|
+
for (var i = 0; i < timerQueue.length; i++) {
|
|
429
|
+
if (timerQueue[i].id === id) {
|
|
430
|
+
timerQueue[i].cancelled = true;
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
function fireDueTimers(untilMs) {
|
|
437
|
+
// Frame times are fractional, so a timer due on a frame lands a rounding error past it; it still fires on it.
|
|
438
|
+
untilMs += 0.000001;
|
|
439
|
+
while (true) {
|
|
440
|
+
var next = null;
|
|
441
|
+
for (var i = 0; i < timerQueue.length; i++) {
|
|
442
|
+
var candidate = timerQueue[i];
|
|
443
|
+
if (!candidate.cancelled && candidate.due <= untilMs && (next === null || candidate.due < next.due)) next = candidate;
|
|
444
|
+
}
|
|
445
|
+
if (next === null) break;
|
|
446
|
+
var dueMs = next.due;
|
|
447
|
+
if (next.every) next.due += next.every;
|
|
448
|
+
else next.cancelled = true;
|
|
449
|
+
virtualNowMs = dueMs;
|
|
450
|
+
firingDepth++;
|
|
451
|
+
try {
|
|
452
|
+
next.callback.apply(window, next.args);
|
|
453
|
+
} catch (err) {
|
|
454
|
+
originalSetTimeout(function() { throw err; }, 0);
|
|
455
|
+
}
|
|
456
|
+
firingDepth--;
|
|
457
|
+
}
|
|
458
|
+
timerQueue = timerQueue.filter(function(entry) { return !entry.cancelled; });
|
|
459
|
+
}
|
|
460
|
+
window.setTimeout = function(callback, delay) {
|
|
461
|
+
if (!rendering || typeof callback !== "function") return originalSetTimeout.apply(null, arguments);
|
|
462
|
+
return addTimer(callback, delay, Array.prototype.slice.call(arguments, 2), false);
|
|
463
|
+
};
|
|
464
|
+
window.setInterval = function(callback, delay) {
|
|
465
|
+
if (!rendering || typeof callback !== "function") return originalSetInterval.apply(null, arguments);
|
|
466
|
+
return addTimer(callback, delay, Array.prototype.slice.call(arguments, 2), true);
|
|
467
|
+
};
|
|
468
|
+
window.clearTimeout = function(id) {
|
|
469
|
+
if (!cancelTimer(id)) originalClearTimeout(id);
|
|
470
|
+
};
|
|
471
|
+
window.clearInterval = function(id) {
|
|
472
|
+
if (!cancelTimer(id)) originalClearInterval(id);
|
|
473
|
+
};
|
|
474
|
+
|
|
413
475
|
window.__HF_VIRTUAL_TIME__ = {
|
|
414
476
|
originalSetTimeout: originalSetTimeout,
|
|
415
477
|
originalClearTimeout: originalClearTimeout,
|
|
@@ -419,8 +481,10 @@ ${seededRandomBlock}
|
|
|
419
481
|
originalCancelAnimationFrame: originalCancelAnimationFrame,
|
|
420
482
|
seekToTime: function(nextTimeMs) {
|
|
421
483
|
var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
|
|
484
|
+
if (window.__renderReady === true) rendering = true;
|
|
485
|
+
${seekToTimeReseedCall}fireDueTimers(safeTimeMs);
|
|
422
486
|
virtualNowMs = safeTimeMs;
|
|
423
|
-
|
|
487
|
+
flushAnimationFrame();
|
|
424
488
|
return virtualNowMs;
|
|
425
489
|
},
|
|
426
490
|
getTime: function() {
|
|
@@ -18006,7 +18070,7 @@ function assertPlanSizeWithinLimit(input) {
|
|
|
18006
18070
|
});
|
|
18007
18071
|
throw new PlanTooLargeError(breakdown.totalBytes, input.limitBytes, breakdown, input.observedAt);
|
|
18008
18072
|
}
|
|
18009
|
-
function resolveChunkPlan(totalFrames, configChunkSize, maxParallelChunks, targetChunkFrames) {
|
|
18073
|
+
function resolveChunkPlan(totalFrames, configChunkSize, maxParallelChunks, targetChunkFrames, minParallelChunks = 1) {
|
|
18010
18074
|
assertPositiveInteger2("totalFrames", totalFrames);
|
|
18011
18075
|
assertPositiveInteger2("maxParallelChunks", maxParallelChunks);
|
|
18012
18076
|
if (configChunkSize !== void 0) {
|
|
@@ -18015,7 +18079,11 @@ function resolveChunkPlan(totalFrames, configChunkSize, maxParallelChunks, targe
|
|
|
18015
18079
|
if (targetChunkFrames !== void 0) {
|
|
18016
18080
|
assertPositiveInteger2("targetChunkFrames", targetChunkFrames);
|
|
18017
18081
|
}
|
|
18018
|
-
|
|
18082
|
+
assertPositiveInteger2("minParallelChunks", minParallelChunks);
|
|
18083
|
+
const autoSizeParallel = targetChunkFrames === void 0 ? maxParallelChunks : Math.min(
|
|
18084
|
+
maxParallelChunks,
|
|
18085
|
+
Math.max(minParallelChunks, Math.ceil(totalFrames / targetChunkFrames))
|
|
18086
|
+
);
|
|
18019
18087
|
const resolvedChunkSize = configChunkSize ?? Math.max(MIN_CHUNK_SIZE, Math.ceil(totalFrames / autoSizeParallel));
|
|
18020
18088
|
const naiveCount = Math.ceil(totalFrames / resolvedChunkSize);
|
|
18021
18089
|
const chunkCount = Math.min(maxParallelChunks, Math.max(1, naiveCount));
|
|
@@ -18594,7 +18662,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18594
18662
|
totalFrames,
|
|
18595
18663
|
config.chunkSize,
|
|
18596
18664
|
maxParallel,
|
|
18597
|
-
config.targetChunkFrames
|
|
18665
|
+
config.targetChunkFrames,
|
|
18666
|
+
config.minParallelChunks
|
|
18598
18667
|
);
|
|
18599
18668
|
const chunks = buildPlanChunkSlices(
|
|
18600
18669
|
frameWeights,
|
|
@@ -20598,6 +20667,20 @@ function validateDistributedRenderConfig(config) {
|
|
|
20598
20667
|
);
|
|
20599
20668
|
}
|
|
20600
20669
|
}
|
|
20670
|
+
if (config.minParallelChunks !== void 0) {
|
|
20671
|
+
if (!Number.isInteger(config.minParallelChunks) || config.minParallelChunks < 1) {
|
|
20672
|
+
throw new InvalidConfigError(
|
|
20673
|
+
"config.minParallelChunks",
|
|
20674
|
+
`must be a positive integer; got ${config.minParallelChunks}`
|
|
20675
|
+
);
|
|
20676
|
+
}
|
|
20677
|
+
if (config.minParallelChunks > MAX_PARALLEL_CHUNKS_CEILING) {
|
|
20678
|
+
throw new InvalidConfigError(
|
|
20679
|
+
"config.minParallelChunks",
|
|
20680
|
+
`must be <= ${MAX_PARALLEL_CHUNKS_CEILING}; got ${config.minParallelChunks}`
|
|
20681
|
+
);
|
|
20682
|
+
}
|
|
20683
|
+
}
|
|
20601
20684
|
for (const field of ["workerTimeoutSeconds", "workerConcurrency"]) {
|
|
20602
20685
|
const value = config[field];
|
|
20603
20686
|
if (value !== void 0 && (!Number.isInteger(value) || value < 1)) {
|
|
@@ -20767,6 +20850,7 @@ function assertDistributedOptions(value) {
|
|
|
20767
20850
|
"chunkSize",
|
|
20768
20851
|
"maxParallelChunks",
|
|
20769
20852
|
"targetChunkFrames",
|
|
20853
|
+
"minParallelChunks",
|
|
20770
20854
|
"planDirSizeLimitBytes"
|
|
20771
20855
|
]) {
|
|
20772
20856
|
assertOptionalInteger(value, field, 1);
|
|
@@ -20906,6 +20990,7 @@ function distributedConfigFromRequest(request, runtime = {}) {
|
|
|
20906
20990
|
chunkSize: distributed.chunkSize,
|
|
20907
20991
|
maxParallelChunks: distributed.maxParallelChunks,
|
|
20908
20992
|
targetChunkFrames: distributed.targetChunkFrames,
|
|
20993
|
+
minParallelChunks: distributed.minParallelChunks,
|
|
20909
20994
|
runtimeCap: distributed.runtimeCap,
|
|
20910
20995
|
rejectOnSystemFonts: distributed.rejectOnSystemFonts,
|
|
20911
20996
|
failClosedFontFetch: distributed.failClosedFontFetch,
|
|
@@ -20933,6 +21018,7 @@ function renderRequestFromDistributedConfig(input) {
|
|
|
20933
21018
|
...optionalProperty("chunkSize", config.chunkSize),
|
|
20934
21019
|
...optionalProperty("maxParallelChunks", config.maxParallelChunks),
|
|
20935
21020
|
...optionalProperty("targetChunkFrames", config.targetChunkFrames),
|
|
21021
|
+
...optionalProperty("minParallelChunks", config.minParallelChunks),
|
|
20936
21022
|
...optionalProperty("runtimeCap", config.runtimeCap),
|
|
20937
21023
|
...optionalProperty("rejectOnSystemFonts", config.rejectOnSystemFonts),
|
|
20938
21024
|
...optionalProperty("failClosedFontFetch", config.failClosedFontFetch),
|
package/dist/public-server.js
CHANGED
|
@@ -427,6 +427,68 @@ ${seededRandomBlock}
|
|
|
427
427
|
}
|
|
428
428
|
};
|
|
429
429
|
|
|
430
|
+
// Timers set once rendering has started run on film time: each seek fires every timer due by then, in
|
|
431
|
+
// due order. Until a seek after the page reports ready, timers keep the wall clock: setup code waits on
|
|
432
|
+
// them while the renderer may already be seeking.
|
|
433
|
+
var rendering = false;
|
|
434
|
+
var timerQueue = [];
|
|
435
|
+
var nextTimerId = 1000000000;
|
|
436
|
+
var firingDepth = 0;
|
|
437
|
+
function addTimer(callback, delay, args, repeats) {
|
|
438
|
+
// A timer set from a firing timer waits at least 1 ms, so a zero-delay chain cannot stall a seek.
|
|
439
|
+
var ms = Math.max(firingDepth > 0 ? 1 : 0, Number(delay) || 0);
|
|
440
|
+
var entry = { id: nextTimerId++, callback: callback, args: args, due: virtualNowMs + ms, every: repeats ? Math.max(1, ms) : 0, cancelled: false };
|
|
441
|
+
timerQueue.push(entry);
|
|
442
|
+
return entry.id;
|
|
443
|
+
}
|
|
444
|
+
function cancelTimer(id) {
|
|
445
|
+
for (var i = 0; i < timerQueue.length; i++) {
|
|
446
|
+
if (timerQueue[i].id === id) {
|
|
447
|
+
timerQueue[i].cancelled = true;
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
function fireDueTimers(untilMs) {
|
|
454
|
+
// Frame times are fractional, so a timer due on a frame lands a rounding error past it; it still fires on it.
|
|
455
|
+
untilMs += 0.000001;
|
|
456
|
+
while (true) {
|
|
457
|
+
var next = null;
|
|
458
|
+
for (var i = 0; i < timerQueue.length; i++) {
|
|
459
|
+
var candidate = timerQueue[i];
|
|
460
|
+
if (!candidate.cancelled && candidate.due <= untilMs && (next === null || candidate.due < next.due)) next = candidate;
|
|
461
|
+
}
|
|
462
|
+
if (next === null) break;
|
|
463
|
+
var dueMs = next.due;
|
|
464
|
+
if (next.every) next.due += next.every;
|
|
465
|
+
else next.cancelled = true;
|
|
466
|
+
virtualNowMs = dueMs;
|
|
467
|
+
firingDepth++;
|
|
468
|
+
try {
|
|
469
|
+
next.callback.apply(window, next.args);
|
|
470
|
+
} catch (err) {
|
|
471
|
+
originalSetTimeout(function() { throw err; }, 0);
|
|
472
|
+
}
|
|
473
|
+
firingDepth--;
|
|
474
|
+
}
|
|
475
|
+
timerQueue = timerQueue.filter(function(entry) { return !entry.cancelled; });
|
|
476
|
+
}
|
|
477
|
+
window.setTimeout = function(callback, delay) {
|
|
478
|
+
if (!rendering || typeof callback !== "function") return originalSetTimeout.apply(null, arguments);
|
|
479
|
+
return addTimer(callback, delay, Array.prototype.slice.call(arguments, 2), false);
|
|
480
|
+
};
|
|
481
|
+
window.setInterval = function(callback, delay) {
|
|
482
|
+
if (!rendering || typeof callback !== "function") return originalSetInterval.apply(null, arguments);
|
|
483
|
+
return addTimer(callback, delay, Array.prototype.slice.call(arguments, 2), true);
|
|
484
|
+
};
|
|
485
|
+
window.clearTimeout = function(id) {
|
|
486
|
+
if (!cancelTimer(id)) originalClearTimeout(id);
|
|
487
|
+
};
|
|
488
|
+
window.clearInterval = function(id) {
|
|
489
|
+
if (!cancelTimer(id)) originalClearInterval(id);
|
|
490
|
+
};
|
|
491
|
+
|
|
430
492
|
window.__HF_VIRTUAL_TIME__ = {
|
|
431
493
|
originalSetTimeout: originalSetTimeout,
|
|
432
494
|
originalClearTimeout: originalClearTimeout,
|
|
@@ -436,8 +498,10 @@ ${seededRandomBlock}
|
|
|
436
498
|
originalCancelAnimationFrame: originalCancelAnimationFrame,
|
|
437
499
|
seekToTime: function(nextTimeMs) {
|
|
438
500
|
var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
|
|
501
|
+
if (window.__renderReady === true) rendering = true;
|
|
502
|
+
${seekToTimeReseedCall}fireDueTimers(safeTimeMs);
|
|
439
503
|
virtualNowMs = safeTimeMs;
|
|
440
|
-
|
|
504
|
+
flushAnimationFrame();
|
|
441
505
|
return virtualNowMs;
|
|
442
506
|
},
|
|
443
507
|
getTime: function() {
|
|
@@ -16537,6 +16601,7 @@ function assertDistributedOptions(value) {
|
|
|
16537
16601
|
"chunkSize",
|
|
16538
16602
|
"maxParallelChunks",
|
|
16539
16603
|
"targetChunkFrames",
|
|
16604
|
+
"minParallelChunks",
|
|
16540
16605
|
"planDirSizeLimitBytes"
|
|
16541
16606
|
]) {
|
|
16542
16607
|
assertOptionalInteger(value, field, 1);
|
package/dist/renderRequest.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface DistributedRenderOptions {
|
|
|
12
12
|
chunkSize?: number;
|
|
13
13
|
maxParallelChunks?: number;
|
|
14
14
|
targetChunkFrames?: number;
|
|
15
|
+
minParallelChunks?: number;
|
|
15
16
|
runtimeCap?: DistributedRenderConfig["runtimeCap"];
|
|
16
17
|
rejectOnSystemFonts?: boolean;
|
|
17
18
|
failClosedFontFetch?: boolean;
|
|
@@ -104,17 +104,19 @@ export interface DistributedRenderConfig extends Pick<RenderRequestOptions, "pag
|
|
|
104
104
|
* chunks whose per-chunk frame count stays at or below this bound, still
|
|
105
105
|
* capped by `maxParallelChunks`:
|
|
106
106
|
*
|
|
107
|
-
* chunkCount = clamp(ceil(totalFrames / targetChunkFrames),
|
|
107
|
+
* chunkCount = clamp(ceil(totalFrames / targetChunkFrames), minParallelChunks, maxParallelChunks)
|
|
108
108
|
*
|
|
109
109
|
* This bounds per-chunk render *time* (which scales with frames-per-chunk) so
|
|
110
110
|
* a single chunk can't exceed a downstream per-chunk timeout on a long video,
|
|
111
|
-
* while short videos
|
|
111
|
+
* while short videos drop to `minParallelChunks` (default 1). It is a ceiling, not a
|
|
112
112
|
* fixed size: a video short enough to fit in fewer chunks gets fewer. Ignored
|
|
113
113
|
* when `chunkSize` is set (an explicit fixed size already pins per-chunk
|
|
114
114
|
* frames). Mutually exclusive with `chunkSize` in intent; if both are passed,
|
|
115
115
|
* `chunkSize` wins and `targetChunkFrames` is a no-op.
|
|
116
116
|
*/
|
|
117
117
|
targetChunkFrames?: number;
|
|
118
|
+
/** Fewest chunks a `targetChunkFrames`-sized plan uses, so a short video keeps this fan-out. */
|
|
119
|
+
minParallelChunks?: number;
|
|
118
120
|
/** Runtime hint; consumed by future per-runtime budget checks. The current implementation records the value but does not enforce. */
|
|
119
121
|
runtimeCap?: "lambda" | "temporal" | "cloud-run-job" | "k8s-job" | "none";
|
|
120
122
|
/**
|
|
@@ -397,12 +399,12 @@ export declare function measurePlanDirBytes(planDir: string): number;
|
|
|
397
399
|
* on tiny renders). Explicit numbers, including `240`, take precedence.
|
|
398
400
|
*
|
|
399
401
|
* Optional `targetChunkFrames` caps per-chunk frames in the auto-sized path:
|
|
400
|
-
* the auto-sizer then targets `clamp(ceil(totalFrames / targetChunkFrames),
|
|
401
|
-
* maxParallelChunks)` chunks, so short videos
|
|
402
|
+
* the auto-sizer then targets `clamp(ceil(totalFrames / targetChunkFrames), minParallelChunks,
|
|
403
|
+
* maxParallelChunks)` chunks, so short videos drop to `minParallelChunks` and long
|
|
402
404
|
* videos add chunks (up to the cap) to keep each one under the bound. It is a
|
|
403
405
|
* no-op when omitted, and ignored when `configChunkSize` is set.
|
|
404
406
|
*/
|
|
405
|
-
export declare function resolveChunkPlan(totalFrames: number, configChunkSize: number | undefined, maxParallelChunks: number, targetChunkFrames?: number): {
|
|
407
|
+
export declare function resolveChunkPlan(totalFrames: number, configChunkSize: number | undefined, maxParallelChunks: number, targetChunkFrames?: number, minParallelChunks?: number): {
|
|
406
408
|
chunkCount: number;
|
|
407
409
|
effectiveChunkSize: number;
|
|
408
410
|
};
|
|
@@ -95,9 +95,9 @@ export interface VirtualTimeShimOptions {
|
|
|
95
95
|
/**
|
|
96
96
|
* Build the page-side virtual-time shim script.
|
|
97
97
|
*
|
|
98
|
-
* The shim freezes `Date.now`, `performance.now`, and the
|
|
99
|
-
*
|
|
100
|
-
* notion of "now". The renderer issues `__HF_VIRTUAL_TIME__.seekToTime(ms)`
|
|
98
|
+
* The shim freezes `Date.now`, `performance.now`, rAF, and the timers set
|
|
99
|
+
* after the first seek, so a render seek can deterministically advance the
|
|
100
|
+
* page's notion of "now". The renderer issues `__HF_VIRTUAL_TIME__.seekToTime(ms)`
|
|
101
101
|
* before every frame capture; everything timing-related on the page sees
|
|
102
102
|
* exactly `ms` until the next seek.
|
|
103
103
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/producer",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.21",
|
|
4
4
|
"description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"postcss": "^8.4.0",
|
|
36
36
|
"puppeteer": "~25.11.0",
|
|
37
37
|
"puppeteer-core": "^25.11.0",
|
|
38
|
-
"@hyperframes/
|
|
39
|
-
"@hyperframes/
|
|
40
|
-
"@hyperframes/
|
|
41
|
-
"@hyperframes/
|
|
42
|
-
"@hyperframes/
|
|
38
|
+
"@hyperframes/engine": "0.9.21",
|
|
39
|
+
"@hyperframes/parsers": "0.9.21",
|
|
40
|
+
"@hyperframes/studio-server": "0.9.21",
|
|
41
|
+
"@hyperframes/lint": "0.9.21",
|
|
42
|
+
"@hyperframes/core": "0.9.21"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^25.0.10",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"tsx": "^4.21.0",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^4.1.11",
|
|
51
|
-
"@hyperframes/aws-lambda": "0.9.
|
|
51
|
+
"@hyperframes/aws-lambda": "0.9.21"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=22"
|