@hyperframes/core 0.8.200 → 0.8.201

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/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export { AUDIO_GROUP_RENDER_ID_ATTR, MEDIA_RENDER_ID_ATTR, assignMediaRenderIds,
22
22
  export { RENDER_FRAME_ID_PREFIX, RENDER_FRAME_ID_SUFFIX, renderFrameIdForRenderId, } from "./runtime/renderFrameSibling.js";
23
23
  export { rewriteAssetPaths, rewriteAssetPath, rewriteCssAssetUrls, rewriteInlineStyleAssetUrls, } from "./compiler/rewriteSubCompPaths.js";
24
24
  export { CSS_URL_RE, isNonRelativeUrl, isPathInside } from "./compiler/assetPaths.js";
25
+ export { rewriteSrcsetUrls } from "./compiler/srcset.js";
25
26
  export { checkSubCompositionUsability, type ParsableDocumentLike, type SubCompositionValidity, type SubCompositionValidityReason, } from "./compiler/subCompositionValidity.js";
26
27
  export { RUNTIME_BOOTSTRAP_ATTR, stripEmbeddedRuntimeScripts } from "./compiler/htmlDocument.js";
27
28
  export { COMPOSITION_SOURCE_URL } from "./compiler/scriptRuns.js";
@@ -32,7 +33,7 @@ export { clearGifFramesBeforeNext, gifClearsAfterLeavingFrameInPlace, parseAnima
32
33
  export { HF_COLOR_GRADING_ATTR, HF_COLOR_GRADING_ADJUST_KEYS, HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS, HF_COLOR_GRADING_ANIMATABLE_PROPERTIES, HF_COLOR_GRADING_CANVAS_ID_PREFIX, HF_COLOR_GRADING_COLOR_SPACE, HF_COLOR_GRADING_CURVE_KEYS, HF_COLOR_GRADING_DETAIL_KEYS, HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS, HF_COLOR_GRADING_EFFECT_KEYS, HF_COLOR_GRADING_EFFECT_PRESETS, HF_COLOR_GRADING_GRADE_PRESETS, HF_COLOR_GRADING_HUE_CURVE_KEYS, HF_COLOR_GRADING_LUT_KEYS, HF_COLOR_GRADING_PALETTES, HF_COLOR_GRADING_PRESETS, HF_COLOR_GRADING_TOP_LEVEL_KEYS, HF_COLOR_GRADING_WHEEL_KEYS, getHfColorGradingCapabilities, hasHfColorGradingAuthoredValues, isHfColorGradingActive, normalizeHfColorGrading, normalizeHfColorGradingWithVariables, resolveHfColorGradingVariables, serializeHfColorGrading, type HfColorGrading, type HfColorGradingAdjust, type HfColorGradingAdjustKey, type HfColorGradingAnimatablePath, type HfColorGradingActiveEffectKey, type HfColorGradingDetailKey, type HfColorGradingDetails, type HfColorGradingEffectKey, type HfColorGradingEffects, type HfColorGradingLutRef, type HfColorGradingPreset, type HfColorGradingPresetId, type HfColorGradingTarget, type HfColorGradingVariableMap, type HfColorGradingCapabilities, type HfColorGradingCurveKey, type HfColorGradingCurves, type HfColorGradingHueCurveKey, type HfColorGradingHueCurves, type HfColorGradingHueRange, type HfColorGradingSecondary, type HfColorGradingSecondaryCorrection, type HfColorGradingSoftRange, type HfColorGradingTonalWheel, type HfColorGradingWheelKey, type HfColorGradingWheels, type HfColorCurvePoint, type HfHueCurvePoint, type NormalizedHfColorGrading, type NormalizedHfColorGradingCurves, type NormalizedHfColorGradingHueCurves, type NormalizedHfColorGradingSecondary, type NormalizedHfColorGradingWheels, type ResolvedHfColorGrading, } from "./colorGrading.js";
33
34
  export { parseCubeLut, CubeLutParseError, type ParseCubeLutOptions } from "./colorLuts.js";
34
35
  export { firstFrameColourArgs, hdrToSdrToneMapFilter, parseFirstFrameColour, type ToneMapSourceColour, } from "./hdrToneMap.js";
35
- export { PREVIEW_PROXY_BOX_PARAM, formatPreviewProxyBox, parsePreviewProxyBox, quantizePreviewProxyBox, type PreviewProxyBox, } from "./previewProxyBox.js";
36
+ export { HEAVY_BITRATE, HEAVY_PIXEL_SHARE, PREVIEW_PROXY_BOX_PARAM, PREVIEW_PROXY_LIGHT_PARAM, PREVIEW_QUALITY_LEVELS, formatPreviewProxyBox, isHeavyForPreview, isPreviewQuality, parsePreviewProxyBox, quantizePreviewProxyBox, scalePreviewProxyBox, type PreviewProxyBox, type PreviewQuality, type PreviewSourceFacts, } from "./previewProxyBox.js";
36
37
  export { HYPERFRAME_RUNTIME_ARTIFACTS, HYPERFRAME_RUNTIME_CONTRACT, loadHyperframeRuntimeSource, type HyperframeRuntimeContract, } from "./inline-scripts/hyperframe.js";
37
38
  export { HYPERFRAME_RUNTIME_GLOBALS, HYPERFRAME_BRIDGE_SOURCES, HYPERFRAME_CONTROL_ACTIONS, type HyperframeControlAction, } from "./inline-scripts/runtimeContract.js";
38
39
  export { getHyperframeRuntimeScript } from "./generated/runtime-inline.js";
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ export { RENDER_FRAME_ID_PREFIX, RENDER_FRAME_ID_SUFFIX, renderFrameIdForRenderI
18
18
  // package (which imports core utilities back).
19
19
  export { rewriteAssetPaths, rewriteAssetPath, rewriteCssAssetUrls, rewriteInlineStyleAssetUrls, } from "./compiler/rewriteSubCompPaths.js";
20
20
  export { CSS_URL_RE, isNonRelativeUrl, isPathInside } from "./compiler/assetPaths.js";
21
+ export { rewriteSrcsetUrls } from "./compiler/srcset.js";
21
22
  export { checkSubCompositionUsability, } from "./compiler/subCompositionValidity.js";
22
23
  export { RUNTIME_BOOTSTRAP_ATTR, stripEmbeddedRuntimeScripts } from "./compiler/htmlDocument.js";
23
24
  export { COMPOSITION_SOURCE_URL } from "./compiler/scriptRuns.js";
@@ -28,7 +29,7 @@ export { clearGifFramesBeforeNext, gifClearsAfterLeavingFrameInPlace, parseAnima
28
29
  export { HF_COLOR_GRADING_ATTR, HF_COLOR_GRADING_ADJUST_KEYS, HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS, HF_COLOR_GRADING_ANIMATABLE_PROPERTIES, HF_COLOR_GRADING_CANVAS_ID_PREFIX, HF_COLOR_GRADING_COLOR_SPACE, HF_COLOR_GRADING_CURVE_KEYS, HF_COLOR_GRADING_DETAIL_KEYS, HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS, HF_COLOR_GRADING_EFFECT_KEYS, HF_COLOR_GRADING_EFFECT_PRESETS, HF_COLOR_GRADING_GRADE_PRESETS, HF_COLOR_GRADING_HUE_CURVE_KEYS, HF_COLOR_GRADING_LUT_KEYS, HF_COLOR_GRADING_PALETTES, HF_COLOR_GRADING_PRESETS, HF_COLOR_GRADING_TOP_LEVEL_KEYS, HF_COLOR_GRADING_WHEEL_KEYS, getHfColorGradingCapabilities, hasHfColorGradingAuthoredValues, isHfColorGradingActive, normalizeHfColorGrading, normalizeHfColorGradingWithVariables, resolveHfColorGradingVariables, serializeHfColorGrading, } from "./colorGrading.js";
29
30
  export { parseCubeLut, CubeLutParseError } from "./colorLuts.js";
30
31
  export { firstFrameColourArgs, hdrToSdrToneMapFilter, parseFirstFrameColour, } from "./hdrToneMap.js";
31
- export { PREVIEW_PROXY_BOX_PARAM, formatPreviewProxyBox, parsePreviewProxyBox, quantizePreviewProxyBox, } from "./previewProxyBox.js";
32
+ export { HEAVY_BITRATE, HEAVY_PIXEL_SHARE, PREVIEW_PROXY_BOX_PARAM, PREVIEW_PROXY_LIGHT_PARAM, PREVIEW_QUALITY_LEVELS, formatPreviewProxyBox, isHeavyForPreview, isPreviewQuality, parsePreviewProxyBox, quantizePreviewProxyBox, scalePreviewProxyBox, } from "./previewProxyBox.js";
32
33
  // Inline scripts
33
34
  export { HYPERFRAME_RUNTIME_ARTIFACTS, HYPERFRAME_RUNTIME_CONTRACT, loadHyperframeRuntimeSource, } from "./inline-scripts/hyperframe.js";
34
35
  export { HYPERFRAME_RUNTIME_GLOBALS, HYPERFRAME_BRIDGE_SOURCES, HYPERFRAME_CONTROL_ACTIONS, } from "./inline-scripts/runtimeContract.js";
@@ -56,6 +56,7 @@ type NumericStats = Readonly<Record<string, number | undefined>>;
56
56
  export declare function parseMediaTreatmentSignalStats(raw: string): GradeSignalFrame[];
57
57
  export declare function statsToAdjust(stats: NumericStats): Pick<MediaTreatmentAnalysis, "adjust" | "measured">;
58
58
  export declare function summarizeMediaTreatmentAnalysis(probe: GradeMediaProbe, frames: readonly GradeSignalFrame[]): MediaTreatmentAnalysis;
59
+ export declare function gradeSampleTimes(duration: number): number[];
59
60
  export declare function analyzeMediaGrade(mediaPath: string, options?: {
60
61
  ffmpegPath?: string;
61
62
  ffprobePath?: string;
@@ -212,43 +212,55 @@ export function summarizeMediaTreatmentAnalysis(probe, frames) {
212
212
  warnings,
213
213
  };
214
214
  }
215
- export function analyzeMediaGrade(mediaPath, options = {}) {
216
- const ffmpegPath = options.ffmpegPath ?? "ffmpeg";
217
- const ffprobePath = options.ffprobePath ?? "ffprobe";
215
+ export function gradeSampleTimes(duration) {
216
+ return Array.from({ length: SAMPLE_FRAMES }, (_, i) => (duration * (i + 0.5)) / SAMPLE_FRAMES);
217
+ }
218
+ function readSignalStats(ffmpegPath, mediaPath, seekSeconds, frames) {
219
+ const timeoutMs = Number(process.env.HYPERFRAMES_ANALYZE_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS;
220
+ const keyframeSeek = seekSeconds === null ? [] : ["-noaccurate_seek", "-ss", seekSeconds.toFixed(3)];
218
221
  try {
219
- const probe = probeMedia(mediaPath, ffprobePath);
220
- const isImage = IMAGE_EXTENSIONS.has(extname(mediaPath).toLowerCase());
221
- const fps = !isImage && probe.duration
222
- ? Math.max(0.1, Math.min(2, SAMPLE_FRAMES / probe.duration))
223
- : null;
224
- const filters = [
225
- fps ? `fps=${fps.toFixed(4)}` : null,
226
- "format=yuv444p",
227
- "signalstats",
228
- "metadata=print:file=-",
229
- ]
230
- .filter(Boolean)
231
- .join(",");
232
- const raw = execFileSync(ffmpegPath, [
222
+ return execFileSync(ffmpegPath, [
233
223
  "-hide_banner",
234
224
  "-nostdin",
235
225
  "-v",
236
226
  "error",
227
+ ...keyframeSeek,
237
228
  "-i",
238
229
  mediaPath,
239
230
  "-vf",
240
- filters,
231
+ `trim=end_frame=${frames},format=yuv444p,signalstats,metadata=print:file=-`,
241
232
  "-frames:v",
242
- String(SAMPLE_FRAMES),
233
+ String(frames),
243
234
  "-f",
244
235
  "null",
245
236
  "-",
246
237
  ], {
247
238
  encoding: "utf8",
248
- timeout: Number(process.env.HYPERFRAMES_ANALYZE_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS,
239
+ timeout: timeoutMs,
249
240
  stdio: ["ignore", "pipe", "pipe"],
250
241
  windowsHide: true,
251
242
  });
243
+ }
244
+ catch (error) {
245
+ if (error.code !== "ETIMEDOUT")
246
+ throw error;
247
+ const target = seekSeconds === null
248
+ ? `the first ${frames} frames`
249
+ : `the frame at ${seekSeconds.toFixed(1)}s`;
250
+ throw new Error(`FFmpeg took longer than ${timeoutMs / 1000}s to measure ${target}; set HYPERFRAMES_ANALYZE_TIMEOUT_MS to allow more`);
251
+ }
252
+ }
253
+ export function analyzeMediaGrade(mediaPath, options = {}) {
254
+ const ffmpegPath = options.ffmpegPath ?? "ffmpeg";
255
+ const ffprobePath = options.ffprobePath ?? "ffprobe";
256
+ try {
257
+ const probe = probeMedia(mediaPath, ffprobePath);
258
+ const isImage = IMAGE_EXTENSIONS.has(extname(mediaPath).toLowerCase());
259
+ const raw = !isImage && probe.duration
260
+ ? gradeSampleTimes(probe.duration)
261
+ .map((time) => readSignalStats(ffmpegPath, mediaPath, time, 1))
262
+ .join("\n")
263
+ : readSignalStats(ffmpegPath, mediaPath, null, SAMPLE_FRAMES);
252
264
  return summarizeMediaTreatmentAnalysis(probe, parseMediaTreatmentSignalStats(raw));
253
265
  }
254
266
  catch (error) {
@@ -1,5 +1,6 @@
1
1
  /** Query parameter carrying the largest box, in device pixels, a preview shows a proxied video at. */
2
2
  export declare const PREVIEW_PROXY_BOX_PARAM = "hf-proxy-box";
3
+ export declare const PREVIEW_PROXY_LIGHT_PARAM = "hf-proxy-light";
3
4
  export type PreviewProxyBox = {
4
5
  width: number;
5
6
  height: number;
@@ -9,3 +10,16 @@ export declare function quantizePreviewProxyBox(width: number, height: number):
9
10
  export declare function formatPreviewProxyBox(box: PreviewProxyBox): string;
10
11
  /** Accepts only ladder rungs, so a client cannot make one cached copy per pixel. */
11
12
  export declare function parsePreviewProxyBox(raw: string): PreviewProxyBox | null;
13
+ export declare const PREVIEW_QUALITY_LEVELS: readonly [1, 0.5, 0.25, 0.125];
14
+ export type PreviewQuality = (typeof PREVIEW_QUALITY_LEVELS)[number];
15
+ export declare function isPreviewQuality(value: unknown): value is PreviewQuality;
16
+ export type PreviewSourceFacts = {
17
+ width: number;
18
+ height: number;
19
+ bitDepth: number;
20
+ bitrate: number;
21
+ };
22
+ export declare const HEAVY_PIXEL_SHARE = 0.5;
23
+ export declare const HEAVY_BITRATE = 40000000;
24
+ export declare function scalePreviewProxyBox(box: PreviewProxyBox, level: PreviewQuality): PreviewProxyBox;
25
+ export declare function isHeavyForPreview(facts: PreviewSourceFacts, copyBox: PreviewProxyBox): boolean;
@@ -1,5 +1,6 @@
1
1
  /** Query parameter carrying the largest box, in device pixels, a preview shows a proxied video at. */
2
2
  export const PREVIEW_PROXY_BOX_PARAM = "hf-proxy-box";
3
+ export const PREVIEW_PROXY_LIGHT_PARAM = "hf-proxy-light";
3
4
  // 256 * 2^(k/4), even: nearby boxes share one cached copy, and a rung overshoots by at most ~19%.
4
5
  const RUNGS = [
5
6
  256, 304, 362, 430, 512, 608, 724, 862, 1024, 1218, 1448, 1722, 2048, 2436, 2896, 3444, 4096,
@@ -28,3 +29,17 @@ export function parsePreviewProxyBox(raw) {
28
29
  const height = Number(match[2]);
29
30
  return RUNGS.includes(width) && RUNGS.includes(height) ? { width, height } : null;
30
31
  }
32
+ export const PREVIEW_QUALITY_LEVELS = [1, 0.5, 0.25, 0.125];
33
+ export function isPreviewQuality(value) {
34
+ return PREVIEW_QUALITY_LEVELS.some((level) => level === value);
35
+ }
36
+ export const HEAVY_PIXEL_SHARE = 0.5;
37
+ export const HEAVY_BITRATE = 40_000_000;
38
+ export function scalePreviewProxyBox(box, level) {
39
+ return quantizePreviewProxyBox(box.width * level, box.height * level) ?? box;
40
+ }
41
+ export function isHeavyForPreview(facts, copyBox) {
42
+ return (copyBox.width * copyBox.height <= HEAVY_PIXEL_SHARE * facts.width * facts.height ||
43
+ facts.bitDepth > 8 ||
44
+ facts.bitrate >= HEAVY_BITRATE);
45
+ }
@@ -22,6 +22,7 @@ type BridgeDeps = {
22
22
  onSetRuntimeData?: (channel: string, payload: unknown, requestId?: number) => void;
23
23
  onClearRuntimeData?: (channel: string, requestId?: number) => void;
24
24
  onSetDisplayScale?: (scale: number) => void;
25
+ onSetPreviewQuality?: (level: number) => void;
25
26
  getCanonicalFps: () => number;
26
27
  };
27
28
  export declare function setRuntimeProtocolFps(fps: number): void;
@@ -30,6 +30,7 @@ const CONTROL_HANDLERS = new Map(Object.entries({
30
30
  "set-playback-rate": (data, deps) => deps.onSetPlaybackRate(Number(data.playbackRate ?? 1)),
31
31
  "set-idle-heartbeat": (data, deps) => deps.onSetIdleHeartbeat(Boolean(data.slow)),
32
32
  "set-display-scale": (data, deps) => deps.onSetDisplayScale?.(Number(data.scale)),
33
+ "set-preview-quality": (data, deps) => deps.onSetPreviewQuality?.(Number(data.level)),
33
34
  "set-root-duration": (data, deps) => deps.onSetRootDuration(Number(data.durationSeconds ?? 0)),
34
35
  "set-play-range": (data, deps) => deps.onSetPlayRange(Number(data.startSeconds ?? 0), data.endSeconds == null ? null : Number(data.endSeconds)),
35
36
  "set-color-grading": (data, deps) => deps.onSetColorGrading(data.target ?? null, data.grading ?? null),
@@ -32,11 +32,18 @@ import { getDebugSurface } from "./globals.js";
32
32
  export function recordStartupError(error) {
33
33
  let detail = "unknown error";
34
34
  try {
35
- detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
35
+ if (error instanceof Error) {
36
+ detail = `${error.name}: ${error.message}`;
37
+ detail += startupErrorStack(error, detail);
38
+ }
39
+ else {
40
+ detail = String(error);
41
+ }
36
42
  }
37
43
  catch (err) {
38
44
  swallow("runtime.startupError", err);
39
45
  }
46
+ detail = detail.replace(/\s+/g, " ").trim();
40
47
  window.__hfStartupError =
41
48
  `HyperFrames runtime failed: ${detail}. Check the composition's scripts and window.__timelines ` +
42
49
  `entries; if they look right, report this as a HyperFrames bug.`;
@@ -65,3 +72,33 @@ export function swallow(label, error) {
65
72
  console.debug(`[hyperframes] ${label} swallowed:`, error);
66
73
  }
67
74
  }
75
+ function isCompositionFrame(frame) {
76
+ const location = /(?:\(|@|\bat )(\S+?):\d+(?::\d+)?\)?$/.exec(frame)?.[1];
77
+ if (!location || location.startsWith("hyperframes://"))
78
+ return false;
79
+ return (location.startsWith("hyperframes-composition://") ||
80
+ /\.html?(?:[?#]|$)/i.test(location) ||
81
+ (typeof document !== "undefined" && location === document.URL));
82
+ }
83
+ function startupErrorStack(error, header) {
84
+ try {
85
+ const stack = error.stack;
86
+ if (typeof stack !== "string")
87
+ return "";
88
+ const hasHeader = stack === header || stack.startsWith(`${header}\n`) || stack.startsWith(`${header}\r`);
89
+ const body = hasHeader ? stack.slice(header.length) : stack;
90
+ const frames = body
91
+ .split(/[\r\n]+/)
92
+ .map((line) => line.trim().replace(/\s+/g, " "))
93
+ .filter((line) => /^at\s+\S/.test(line) || /^[^@]*@.+:\d+(?::\d+)?$/.test(line));
94
+ const selected = frames.slice(0, 3);
95
+ const composition = frames.find(isCompositionFrame);
96
+ if (composition && !selected.includes(composition))
97
+ selected.push(composition);
98
+ return selected.length ? ` [thrown at: ${selected.join(" <- ")}]` : "";
99
+ }
100
+ catch (err) {
101
+ swallow("runtime.startupError.stack", err);
102
+ return "";
103
+ }
104
+ }
@@ -81,6 +81,7 @@ export declare function evictMediaSyncState(el: HTMLMediaElement): void;
81
81
  export declare function hasMediaSyncStateForTest(el: HTMLMediaElement): boolean;
82
82
  export declare const MEDIA_HARD_SYNC_SECONDS = 0.5;
83
83
  export declare const VIDEO_PRESEEK_SECONDS = 1.5;
84
+ export declare function mediaSourceTimeAt(clip: RuntimeMediaClip, timeSeconds: number): number;
84
85
  /** Drift a playing audio element may carry before sync pulls it back onto the playhead. */
85
86
  export declare const MEDIA_SYNC_TOLERANCE_SECONDS = 0.04;
86
87
  export declare function syncRuntimeMedia(params: {
@@ -193,6 +193,19 @@ export function hasMediaSyncStateForTest(el) {
193
193
  }
194
194
  export const MEDIA_HARD_SYNC_SECONDS = 0.5;
195
195
  export const VIDEO_PRESEEK_SECONDS = 1.5;
196
+ export function mediaSourceTimeAt(clip, timeSeconds) {
197
+ const elapsed = Math.max(0, Math.min(timeSeconds, clip.end) - (clip.origin ?? clip.start));
198
+ const target = clip.mediaStart + sourceTimeAt(clip.rate ?? clip.playbackRate, elapsed);
199
+ if (clip.sourceDuration == null)
200
+ return target;
201
+ const loopLength = clip.sourceDuration - clip.mediaStart;
202
+ if (clip.loop && loopLength > 0 && target >= clip.sourceDuration)
203
+ return clip.mediaStart + ((target - clip.mediaStart) % loopLength);
204
+ // A non-loop video past its source holds the last frame.
205
+ if (!clip.loop && clip.el.tagName === "VIDEO")
206
+ return Math.min(target, clip.sourceDuration);
207
+ return target;
208
+ }
196
209
  function prepareUpcomingVideo(clip, time, playing, rate) {
197
210
  const { el } = clip;
198
211
  const dueIn = clip.start - time;
@@ -206,15 +219,7 @@ function prepareUpcomingVideo(clip, time, playing, rate) {
206
219
  findInjectedRenderFrame(el) ||
207
220
  ("__HF_EXPORT_RENDER_SEEK_CONFIG" in window && window.__HF_EXPORT_RENDER_SEEK_CONFIG))
208
221
  return;
209
- let target = clip.mediaStart +
210
- sourceTimeAt(clip.rate ?? clip.playbackRate, clip.start - (clip.origin ?? clip.start));
211
- if (clip.sourceDuration != null) {
212
- const loopLength = clip.sourceDuration - clip.mediaStart;
213
- if (clip.loop && loopLength > 0 && target >= clip.sourceDuration)
214
- target = clip.mediaStart + ((target - clip.mediaStart) % loopLength);
215
- else if (!clip.loop)
216
- target = Math.min(target, clip.sourceDuration);
217
- }
222
+ const target = mediaSourceTimeAt(clip, clip.start);
218
223
  if (!Number.isFinite(target) || target < 0 || Math.abs(el.currentTime - target) <= 0.001)
219
224
  return;
220
225
  try {
@@ -282,13 +287,9 @@ export function syncRuntimeMedia(params) {
282
287
  !inWindow &&
283
288
  (params.timeSeconds >= clip.end || sameInstant(params.timeSeconds, clip.end)) &&
284
289
  isClipVisibleAt(params.timeSeconds, clip.start, clip.end, params.getCompositionDuration());
285
- let relTime = sourceTimeAt(clipRate, Math.max(0, Math.min(params.timeSeconds, clip.end) - origin)) +
286
- clip.mediaStart;
290
+ const relTime = mediaSourceTimeAt(clip, params.timeSeconds);
287
291
  const isHeldVideoTail = isTerminalVideo ||
288
292
  (isNonLoopVideo && clip.sourceDuration != null && relTime >= clip.sourceDuration && inWindow);
289
- if (isHeldVideoTail && clip.sourceDuration != null) {
290
- relTime = Math.min(relTime, clip.sourceDuration);
291
- }
292
293
  const previousRelativeTime = lastRelativeTime.get(el);
293
294
  const audioReenteredAfterBackwardSeek = el.tagName === "AUDIO" &&
294
295
  (previousRelativeTime === undefined || relTime < previousRelativeTime - 0.04);
@@ -308,13 +309,6 @@ export function syncRuntimeMedia(params) {
308
309
  (!el.ended || clip.loop || isHeldVideoTail || canSeekEndedMediaBackward);
309
310
  if (isActive) {
310
311
  lastRelativeTime.set(el, relTime);
311
- // Loop wrapping: when media reaches end, restart from mediaStart
312
- if (clip.loop && clip.sourceDuration != null && clip.sourceDuration > 0) {
313
- const loopLength = clip.sourceDuration - clip.mediaStart;
314
- if (loopLength > 0 && relTime >= clip.sourceDuration) {
315
- relTime = clip.mediaStart + ((relTime - clip.mediaStart) % loopLength);
316
- }
317
- }
318
312
  const userVol = clampVolume(params.userVolume ?? 1);
319
313
  const fallbackAuthorVolume = clampAudioGain(clip.volume ?? 1);
320
314
  const previousRuntimeVolume = lastRuntimeAppliedVolume.get(el);
@@ -27,8 +27,10 @@ export function showHiddenClips(only = () => true) {
27
27
  export function showClips(hidden) {
28
28
  const putMarksBack = hidden.map((el) => revealClip(el));
29
29
  return () => {
30
- for (const el of hidden)
31
- el.style.visibility = "hidden";
30
+ for (const el of hidden) {
31
+ if (el.style.getPropertyValue("visibility") !== "hidden")
32
+ el.style.setProperty("visibility", "hidden", "important");
33
+ }
32
34
  for (const putMarkBack of putMarksBack)
33
35
  putMarkBack();
34
36
  };
@@ -40,9 +40,10 @@ export function runtimeElementWindow(rawNode, options) {
40
40
  duration = liveDuration;
41
41
  }
42
42
  }
43
+ const isRoot = compId !== null && rawNode === findRootCompositionElement(rawNode.ownerDocument);
43
44
  return {
44
45
  start,
45
- end: duration != null && duration > 0 ? start + duration : Number.POSITIVE_INFINITY,
46
+ end: !isRoot && duration != null && duration > 0 ? start + duration : Number.POSITIVE_INFINITY,
46
47
  };
47
48
  }
48
49
  export function isRuntimeElementVisibleAt(rawNode, options) {
@@ -6,7 +6,7 @@ export type RuntimeJson = string | number | boolean | null | RuntimeJson[] | {
6
6
  import type { HyperframeControlAction } from "../inline-scripts/runtimeContract.js";
7
7
  import type { HyperframePickerElementInfo } from "../inline-scripts/pickerApi.js";
8
8
  import type { RuntimeProtocolV1 } from "./protocol.js";
9
- type RuntimeBridgeControlActionBase = HyperframeControlAction | "tick" | "set-volume" | "set-media-output-muted" | "set-native-media-sync-disabled" | "set-web-audio-media-disabled" | "set-idle-heartbeat" | "set-display-scale" | "set-root-duration" | "set-play-range" | "stop-media" | "flash-elements";
9
+ type RuntimeBridgeControlActionBase = HyperframeControlAction | "tick" | "set-volume" | "set-media-output-muted" | "set-native-media-sync-disabled" | "set-web-audio-media-disabled" | "set-idle-heartbeat" | "set-display-scale" | "set-preview-quality" | "set-root-duration" | "set-play-range" | "stop-media" | "flash-elements";
10
10
  type RuntimeBridgeControlMessageBase = {
11
11
  source: "hf-parent";
12
12
  type: "control";
@@ -21,6 +21,7 @@ type RuntimeBridgeControlMessageBase = {
21
21
  disabled?: boolean;
22
22
  slow?: boolean;
23
23
  scale?: number;
24
+ level?: number;
24
25
  playbackRate?: number;
25
26
  target?: HfColorGradingTarget | string | null;
26
27
  grading?: RuntimeJson;
@@ -1,3 +1,4 @@
1
+ import { isFontValue, isImageValue } from "@hyperframes/parsers/composition";
1
2
  /**
2
3
  * Compare a flat values map (from `--variables` / `data-variable-values`) to
3
4
  * the declared schema (`data-composition-variables`). Returns issues for keys
@@ -86,10 +87,7 @@ function checkType(value, decl) {
86
87
  actual: jsTypeOf(value),
87
88
  };
88
89
  }
89
- // Object form: require the discriminant fields so a malformed brand-kit
90
- // value ({name: 42} / {}) is caught here rather than surfacing as a bogus
91
- // font-family at render time.
92
- if (typeof value.name !== "string" || typeof value.source !== "string") {
90
+ if (!isFontValue(value)) {
93
91
  return {
94
92
  kind: "type-mismatch",
95
93
  variableId: decl.id,
@@ -111,13 +109,14 @@ function checkType(value, decl) {
111
109
  actual: jsTypeOf(value),
112
110
  };
113
111
  }
114
- // Object form: require the discriminant field.
115
- if (typeof value.url !== "string") {
112
+ if (!isImageValue(value)) {
116
113
  return {
117
114
  kind: "type-mismatch",
118
115
  variableId: decl.id,
119
116
  expected: "image object {url: string}",
120
- actual: "object missing string url",
117
+ actual: typeof value.url !== "string"
118
+ ? "object missing string url"
119
+ : "object with invalid alt/fit",
121
120
  };
122
121
  }
123
122
  return null;
@@ -5,7 +5,7 @@
5
5
  * the Node-only modules reachable from the core/parsers root entries, so
6
6
  * browser consumers (SDK, Studio, embedders) can bundle it.
7
7
  */
8
- export type { CompositionVariable, CompositionVariableType, CompositionVariableBase, StringVariable, NumberVariable, ColorVariable, BooleanVariable, EnumVariable, FontVariable, ImageVariable, } from "@hyperframes/parsers/composition";
8
+ export type { CompositionVariable, CompositionVariableType, CompositionVariableBase, StringVariable, NumberVariable, ColorVariable, BooleanVariable, EnumVariable, FontVariable, FontValue, ImageVariable, ImageValue, } from "@hyperframes/parsers/composition";
9
9
  export { COMPOSITION_VARIABLE_TYPES, parseCompositionVariables, isCompositionVariable, isScalarVariableValue, scanVariableUsage, } from "@hyperframes/parsers/composition";
10
10
  export type { VariableUsageScan } from "@hyperframes/parsers/composition";
11
11
  export { getVariables, readDeclaredDefaults } from "./runtime/getVariables.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/core",
3
- "version": "0.8.200",
3
+ "version": "0.8.201",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -397,9 +397,9 @@
397
397
  "linkedom": "^0.18.12",
398
398
  "postcss": "^8.5.8",
399
399
  "wawoff2": "^2.0.1",
400
- "@hyperframes/studio-server": "0.8.200",
401
- "@hyperframes/parsers": "0.8.200",
402
- "@hyperframes/lint": "0.8.200"
400
+ "@hyperframes/lint": "0.8.201",
401
+ "@hyperframes/studio-server": "0.8.201",
402
+ "@hyperframes/parsers": "0.8.201"
403
403
  },
404
404
  "devDependencies": {
405
405
  "@fontsource/archivo-black": "^5.2.8",