@hyperframes/core 0.9.19 → 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/fonts/deterministicFonts.js +15 -0
- package/dist/generated/runtime-inline.js +1 -1
- package/dist/hyperframe.runtime.iife.js +65 -65
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inline-scripts/pickerApi.d.ts +1 -0
- package/dist/motionBlurFingerprint.d.ts +4 -0
- package/dist/motionBlurFingerprint.js +34 -0
- package/dist/runtime/bridge.js +2 -1
- package/dist/runtime/clock.d.ts +5 -0
- package/dist/runtime/clock.js +49 -3
- package/dist/runtime/wallClock.d.ts +8 -0
- package/dist/runtime/wallClock.js +13 -0
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -68,3 +68,4 @@ export { canaryBucket, evaluateCanary, parseCanaryOverride, type CanaryDecision,
|
|
|
68
68
|
export { CANARIES, canaryEnvVar, findCanary, overdueCanaries, type CanaryDefinition, } from "./canaryRegistry.js";
|
|
69
69
|
export { HF_VFX_ATTR, HF_VFX_CHAIN_VERSION, HF_VFX, chainCapture, getVfxDef, parseVfxChain, serializeVfxChain, normalizeVfxParams, type HfVfxCapture, type HfVfxChain, type HfVfxDef, type HfVfxNode, type HfVfxParam, type HfVfxParamValues, } from "./vfx.js";
|
|
70
70
|
export { PAGE_DEVICE_SCALE_GLOBAL, PAGE_MOTION_BLUR_OFF_GLOBAL, acceleratedCanvasTrackerSource, } from "./pageMotionBlur.js";
|
|
71
|
+
export { MOTION_BLUR_FINGERPRINT_FIELDS, type MotionBlurFingerprint, type MotionBlurFingerprintField, } from "./motionBlurFingerprint.js";
|
package/dist/index.js
CHANGED
|
@@ -71,3 +71,4 @@ export { CANARIES, canaryEnvVar, findCanary, overdueCanaries, } from "./canaryRe
|
|
|
71
71
|
// VFX chain (data-vfx-chain) — defs, chain parse/serialize/normalize
|
|
72
72
|
export { HF_VFX_ATTR, HF_VFX_CHAIN_VERSION, HF_VFX, chainCapture, getVfxDef, parseVfxChain, serializeVfxChain, normalizeVfxParams, } from "./vfx.js";
|
|
73
73
|
export { PAGE_DEVICE_SCALE_GLOBAL, PAGE_MOTION_BLUR_OFF_GLOBAL, acceleratedCanvasTrackerSource, } from "./pageMotionBlur.js";
|
|
74
|
+
export { MOTION_BLUR_FINGERPRINT_FIELDS, } from "./motionBlurFingerprint.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const MOTION_BLUR_FINGERPRINT_FIELDS: readonly ["canvas2d", "webgl", "video", "iframe", "customElement", "svgClip", "mask", "scrollContainer", "threeD", "overflowingMover", "frontOfMover", "blendMode", "fixedInMover", "pseudoOutside"];
|
|
2
|
+
export type MotionBlurFingerprintField = (typeof MOTION_BLUR_FINGERPRINT_FIELDS)[number];
|
|
3
|
+
export type MotionBlurFingerprint = Record<MotionBlurFingerprintField, number>;
|
|
4
|
+
export declare function emptyMotionBlurFingerprint(): MotionBlurFingerprint;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const MOTION_BLUR_FINGERPRINT_FIELDS = [
|
|
2
|
+
"canvas2d",
|
|
3
|
+
"webgl",
|
|
4
|
+
"video",
|
|
5
|
+
"iframe",
|
|
6
|
+
"customElement",
|
|
7
|
+
"svgClip",
|
|
8
|
+
"mask",
|
|
9
|
+
"scrollContainer",
|
|
10
|
+
"threeD",
|
|
11
|
+
"overflowingMover",
|
|
12
|
+
"frontOfMover",
|
|
13
|
+
"blendMode",
|
|
14
|
+
"fixedInMover",
|
|
15
|
+
"pseudoOutside",
|
|
16
|
+
];
|
|
17
|
+
export function emptyMotionBlurFingerprint() {
|
|
18
|
+
return {
|
|
19
|
+
canvas2d: 0,
|
|
20
|
+
webgl: 0,
|
|
21
|
+
video: 0,
|
|
22
|
+
iframe: 0,
|
|
23
|
+
customElement: 0,
|
|
24
|
+
svgClip: 0,
|
|
25
|
+
mask: 0,
|
|
26
|
+
scrollContainer: 0,
|
|
27
|
+
threeD: 0,
|
|
28
|
+
overflowingMover: 0,
|
|
29
|
+
frontOfMover: 0,
|
|
30
|
+
blendMode: 0,
|
|
31
|
+
fixedInMover: 0,
|
|
32
|
+
pseudoOutside: 0,
|
|
33
|
+
};
|
|
34
|
+
}
|
package/dist/runtime/bridge.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wallSetTimeout } from "./wallClock.js";
|
|
1
2
|
import { swallow } from "./diagnostics.js";
|
|
2
3
|
import { inspectRuntimeProtocol, runtimeProtocolFpsToNumber, runtimeProtocolMetadata, } from "./protocol.js";
|
|
3
4
|
let runtimeProtocolFps = 30;
|
|
@@ -134,7 +135,7 @@ function flashElements(selectors, duration) {
|
|
|
134
135
|
const els = document.querySelectorAll(selector);
|
|
135
136
|
els.forEach((el) => {
|
|
136
137
|
el.classList.add("__hf-flash");
|
|
137
|
-
|
|
138
|
+
wallSetTimeout(() => el.classList.remove("__hf-flash"), duration);
|
|
138
139
|
});
|
|
139
140
|
}
|
|
140
141
|
catch (err) {
|
package/dist/runtime/clock.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export declare class TransportClock {
|
|
|
30
30
|
private _lastReadMs;
|
|
31
31
|
private _lastNow;
|
|
32
32
|
private _audioStart;
|
|
33
|
+
private _reading;
|
|
34
|
+
private _ahead;
|
|
33
35
|
constructor(opts?: {
|
|
34
36
|
initialTime?: number;
|
|
35
37
|
rate?: number;
|
|
@@ -38,6 +40,9 @@ export declare class TransportClock {
|
|
|
38
40
|
});
|
|
39
41
|
now(): number;
|
|
40
42
|
private _elementTimeNeverBehind;
|
|
43
|
+
private _betweenUpdates;
|
|
44
|
+
private _runAhead;
|
|
45
|
+
aheadOf(el: HTMLMediaElement): number;
|
|
41
46
|
private _heldUntilAudioOutputStarts;
|
|
42
47
|
private _pendingAudioStart;
|
|
43
48
|
/** Folds a >500ms gap since the last read into `_playStartMs` so it's never reported as played time. */
|
package/dist/runtime/clock.js
CHANGED
|
@@ -6,6 +6,15 @@ const STALL_ADJUSTED_LAG_MS = 33;
|
|
|
6
6
|
const HAVE_FUTURE_DATA = 3;
|
|
7
7
|
const AUDIO_START_WAIT_MS = 1500;
|
|
8
8
|
const MEDIA_SYNC_STEER_TOLERANCE_SECONDS = 0.04;
|
|
9
|
+
const MAX_MEDIA_STEP_SECONDS = 0.25;
|
|
10
|
+
const MEDIA_STEPS_KEPT = 8;
|
|
11
|
+
const furthestAhead = (reading) => reading ? Math.max(0, ...reading.steps) : 0;
|
|
12
|
+
function noteStep(reading, time) {
|
|
13
|
+
const step = time - reading.time;
|
|
14
|
+
if (step > 0 && step <= MAX_MEDIA_STEP_SECONDS)
|
|
15
|
+
reading.steps = [...reading.steps.slice(1 - MEDIA_STEPS_KEPT), step];
|
|
16
|
+
reading.time = time;
|
|
17
|
+
}
|
|
9
18
|
export class TransportClock {
|
|
10
19
|
_baseTime = 0;
|
|
11
20
|
_playStartMs = null;
|
|
@@ -20,6 +29,8 @@ export class TransportClock {
|
|
|
20
29
|
_lastReadMs = null;
|
|
21
30
|
_lastNow = 0;
|
|
22
31
|
_audioStart = null;
|
|
32
|
+
_reading = null;
|
|
33
|
+
_ahead = null;
|
|
23
34
|
constructor(opts) {
|
|
24
35
|
this._baseTime = opts?.initialTime ?? 0;
|
|
25
36
|
this._rate = opts?.rate ?? 1;
|
|
@@ -27,6 +38,7 @@ export class TransportClock {
|
|
|
27
38
|
this._nowMs = opts?.nowMs ?? (() => performance.now());
|
|
28
39
|
}
|
|
29
40
|
now() {
|
|
41
|
+
this._ahead = null;
|
|
30
42
|
if (this._playStartMs === null)
|
|
31
43
|
return this._baseTime;
|
|
32
44
|
// Audio-master: drift is impossible because audio IS the clock. Clearing
|
|
@@ -63,13 +75,42 @@ export class TransportClock {
|
|
|
63
75
|
const spec = rate ?? 1;
|
|
64
76
|
const time = timeAtSourceTime(spec, el.currentTime - mediaStart) + compositionStart;
|
|
65
77
|
const held = this._heldUntilAudioOutputStarts(el.seeking, time);
|
|
66
|
-
if (held !== undefined)
|
|
78
|
+
if (held !== undefined) {
|
|
79
|
+
this._reading = null;
|
|
67
80
|
return held;
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
}
|
|
82
|
+
const between = this._betweenUpdates(el, time);
|
|
83
|
+
if (between !== null)
|
|
84
|
+
return between;
|
|
70
85
|
const wrapped = el.loop && this._lastNow - time > timeAtSourceTime(spec, el.duration) / 2;
|
|
71
86
|
return !wrapped && el.readyState < HAVE_FUTURE_DATA ? this._lastNow : null;
|
|
72
87
|
}
|
|
88
|
+
// Chromium moves currentTime in 10-50 ms steps, so reading it as is holds a frame, then jumps. Run on at the play
|
|
89
|
+
// rate, kept within [element time, element time + its largest recent step], never back; null when time is behind
|
|
90
|
+
// the playhead by more than that step (a late clip, not a step).
|
|
91
|
+
_betweenUpdates(el, time) {
|
|
92
|
+
const nowMs = this._nowMs();
|
|
93
|
+
const last = this._reading?.el === el ? this._reading : null;
|
|
94
|
+
if (time < this._lastNow - furthestAhead(last))
|
|
95
|
+
return null;
|
|
96
|
+
if (!last) {
|
|
97
|
+
this._reading = { el, time, steps: [], from: time, fromMs: nowMs };
|
|
98
|
+
return this._runAhead(el, time, Math.max(time, this._lastNow));
|
|
99
|
+
}
|
|
100
|
+
noteStep(last, time);
|
|
101
|
+
const run = last.from + ((nowMs - last.fromMs) / 1000) * this._rate;
|
|
102
|
+
const held = Math.min(Math.max(run, time), time + furthestAhead(last));
|
|
103
|
+
if (held !== run)
|
|
104
|
+
Object.assign(last, { from: held, fromMs: nowMs });
|
|
105
|
+
return this._runAhead(el, time, Math.max(held, this._lastNow));
|
|
106
|
+
}
|
|
107
|
+
_runAhead(el, time, now) {
|
|
108
|
+
this._ahead = { el, seconds: now - time };
|
|
109
|
+
return now;
|
|
110
|
+
}
|
|
111
|
+
aheadOf(el) {
|
|
112
|
+
return this._ahead?.el === el ? this._ahead.seconds : 0;
|
|
113
|
+
}
|
|
73
114
|
_heldUntilAudioOutputStarts(seeking, time) {
|
|
74
115
|
const pending = this._pendingAudioStart();
|
|
75
116
|
if (seeking)
|
|
@@ -110,6 +151,7 @@ export class TransportClock {
|
|
|
110
151
|
this._playStartMs = this._nowMs();
|
|
111
152
|
this._audioStart = { sinceMs: this._playStartMs, at: this._baseTime, from: null };
|
|
112
153
|
this._lastNow = this._baseTime;
|
|
154
|
+
this._reading = null;
|
|
113
155
|
// Not a stall: the gap since the clock was last read (possibly a long
|
|
114
156
|
// paused idle) says nothing about lost playback time, since none was
|
|
115
157
|
// playing. `_applyStallCorrection` treats null as "nothing to compare
|
|
@@ -122,6 +164,7 @@ export class TransportClock {
|
|
|
122
164
|
return false;
|
|
123
165
|
this._baseTime = this.now();
|
|
124
166
|
this._playStartMs = null;
|
|
167
|
+
this._reading = null;
|
|
125
168
|
return true;
|
|
126
169
|
}
|
|
127
170
|
seek(timeSeconds) {
|
|
@@ -130,6 +173,7 @@ export class TransportClock {
|
|
|
130
173
|
: Math.max(0, timeSeconds);
|
|
131
174
|
this._baseTime = clamped;
|
|
132
175
|
this._lastNow = clamped;
|
|
176
|
+
this._reading = null;
|
|
133
177
|
if (this._playStartMs !== null) {
|
|
134
178
|
this._playStartMs = this._nowMs();
|
|
135
179
|
this._audioStart = { sinceMs: this._playStartMs, at: clamped, from: null };
|
|
@@ -143,6 +187,7 @@ export class TransportClock {
|
|
|
143
187
|
}
|
|
144
188
|
setRate(rate) {
|
|
145
189
|
const safe = Number.isFinite(rate) && rate > 0 ? Math.max(0.1, Math.min(5, rate)) : 1;
|
|
190
|
+
this._reading = null;
|
|
146
191
|
if (this._playStartMs !== null) {
|
|
147
192
|
this._baseTime = this.now();
|
|
148
193
|
this._playStartMs = this._nowMs();
|
|
@@ -189,6 +234,7 @@ export class TransportClock {
|
|
|
189
234
|
this._lastReadMs = null;
|
|
190
235
|
}
|
|
191
236
|
this._audioSource = null;
|
|
237
|
+
this._reading = null;
|
|
192
238
|
}
|
|
193
239
|
/** The element the playhead currently follows, if the source is one. */
|
|
194
240
|
// fallow-ignore-next-line unused-class-member
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type TimeoutId = ReturnType<typeof setTimeout>;
|
|
2
|
+
type IntervalId = ReturnType<typeof setInterval>;
|
|
3
|
+
type Handler = (...args: never[]) => void;
|
|
4
|
+
export declare function wallSetTimeout(handler: Handler, timeout?: number, ...args: unknown[]): TimeoutId;
|
|
5
|
+
export declare function wallClearTimeout(id: TimeoutId | null | undefined): void;
|
|
6
|
+
export declare function wallSetInterval(handler: Handler, timeout?: number, ...args: unknown[]): IntervalId;
|
|
7
|
+
export declare function wallClearInterval(id: IntervalId | null | undefined): void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const shim = () => globalThis.__HF_VIRTUAL_TIME__;
|
|
2
|
+
export function wallSetTimeout(handler, timeout, ...args) {
|
|
3
|
+
return (shim()?.originalSetTimeout ?? setTimeout)(handler, timeout, ...args);
|
|
4
|
+
}
|
|
5
|
+
export function wallClearTimeout(id) {
|
|
6
|
+
(shim()?.originalClearTimeout ?? clearTimeout)(id ?? undefined);
|
|
7
|
+
}
|
|
8
|
+
export function wallSetInterval(handler, timeout, ...args) {
|
|
9
|
+
return (shim()?.originalSetInterval ?? setInterval)(handler, timeout, ...args);
|
|
10
|
+
}
|
|
11
|
+
export function wallClearInterval(id) {
|
|
12
|
+
(shim()?.originalClearInterval ?? clearInterval)(id ?? undefined);
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/core",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.21",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -437,9 +437,9 @@
|
|
|
437
437
|
"linkedom": "^0.18.12",
|
|
438
438
|
"postcss": "^8.5.8",
|
|
439
439
|
"wawoff2": "^2.0.1",
|
|
440
|
-
"@hyperframes/
|
|
441
|
-
"@hyperframes/
|
|
442
|
-
"@hyperframes/studio-server": "0.9.
|
|
440
|
+
"@hyperframes/lint": "0.9.21",
|
|
441
|
+
"@hyperframes/parsers": "0.9.21",
|
|
442
|
+
"@hyperframes/studio-server": "0.9.21"
|
|
443
443
|
},
|
|
444
444
|
"devDependencies": {
|
|
445
445
|
"@fontsource/archivo-black": "^5.2.8",
|