@hyperframes/core 0.8.206 → 0.8.207

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.
@@ -0,0 +1,24 @@
1
+ declare const RETRY: {
2
+ readonly recursive: true;
3
+ readonly force: true;
4
+ readonly maxRetries: 10;
5
+ readonly retryDelay: 100;
6
+ };
7
+ type RemoveSync = (path: string, options: typeof RETRY) => void;
8
+ type RemoveAsync = (path: string, options: typeof RETRY) => Promise<void>;
9
+ type Warn = (message: string) => void;
10
+ /** Removes a tree the next step needs gone, riding out transient locks; throws if it still fails. */
11
+ export declare function removeTreeWithRetry(path: string, remove?: RemoveSync): void;
12
+ /** Async twin of {@link removeTreeWithRetry}. */
13
+ export declare function removeTreeWithRetryAsync(path: string, remove?: RemoveAsync): Promise<void>;
14
+ /** Cleans up after finished work: a refusal that outlasts the retries is warned about, never thrown. */
15
+ export declare function removeTreeBestEffort(path: string, options?: {
16
+ remove?: RemoveSync;
17
+ warn?: Warn;
18
+ }): void;
19
+ /** Async twin of {@link removeTreeBestEffort}. */
20
+ export declare function removeTreeBestEffortAsync(path: string, options?: {
21
+ remove?: RemoveAsync;
22
+ warn?: Warn;
23
+ }): Promise<void>;
24
+ export {};
@@ -0,0 +1,35 @@
1
+ import { rmSync } from "node:fs";
2
+ import { rm } from "node:fs/promises";
3
+ // Windows holds files for a moment after a process exits or while antivirus scans them (EBUSY,
4
+ // EPERM, ENOTEMPTY). Node retries those itself when asked: about 5.5 s in all.
5
+ const RETRY = { recursive: true, force: true, maxRetries: 10, retryDelay: 100 };
6
+ function refusal(path, error) {
7
+ const reason = error instanceof Error ? error.message : String(error);
8
+ return `[cleanup] could not remove ${path}: ${reason}`;
9
+ }
10
+ /** Removes a tree the next step needs gone, riding out transient locks; throws if it still fails. */
11
+ export function removeTreeWithRetry(path, remove = rmSync) {
12
+ remove(path, RETRY);
13
+ }
14
+ /** Async twin of {@link removeTreeWithRetry}. */
15
+ export async function removeTreeWithRetryAsync(path, remove = rm) {
16
+ await remove(path, RETRY);
17
+ }
18
+ /** Cleans up after finished work: a refusal that outlasts the retries is warned about, never thrown. */
19
+ export function removeTreeBestEffort(path, options = {}) {
20
+ try {
21
+ removeTreeWithRetry(path, options.remove);
22
+ }
23
+ catch (error) {
24
+ (options.warn ?? console.warn)(refusal(path, error));
25
+ }
26
+ }
27
+ /** Async twin of {@link removeTreeBestEffort}. */
28
+ export async function removeTreeBestEffortAsync(path, options = {}) {
29
+ try {
30
+ await removeTreeWithRetryAsync(path, options.remove);
31
+ }
32
+ catch (error) {
33
+ (options.warn ?? console.warn)(refusal(path, error));
34
+ }
35
+ }
@@ -2,6 +2,7 @@ import { type VolumeKeyframe } from "./mediaVolumeEnvelope.js";
2
2
  import { type AudioFades } from "../audioFade.js";
3
3
  import { type RateSpec } from "../speedRamp.js";
4
4
  import { type HostSlot } from "../mediaTiming.js";
5
+ export { MEDIA_SYNC_TOLERANCE_SECONDS } from "./mediaSteer.js";
5
6
  export { readElementPlaybackRate, readElementRateSpec, resolveNaturalMediaTimelineDuration, } from "./playbackRate.js";
6
7
  export declare function readElementPlaybackStart(el: Element): number;
7
8
  export declare const HOLD_CAP_MS = 5000;
@@ -80,10 +81,8 @@ export declare function evictMediaSyncState(el: HTMLMediaElement): void;
80
81
  /** Test-only seam: whether any per-source sync state is still tracked for `el`. */
81
82
  export declare function hasMediaSyncStateForTest(el: HTMLMediaElement): boolean;
82
83
  export declare const MEDIA_HARD_SYNC_SECONDS = 0.5;
83
- export declare const VIDEO_PRESEEK_SECONDS = 1.5;
84
+ export declare const MEDIA_PRESEEK_SECONDS = 1.5;
84
85
  export declare function mediaSourceTimeAt(clip: RuntimeMediaClip, timeSeconds: number): number;
85
- /** Drift a playing audio element may carry before sync pulls it back onto the playhead. */
86
- export declare const MEDIA_SYNC_TOLERANCE_SECONDS = 0.04;
87
86
  export declare function syncRuntimeMedia(params: {
88
87
  clips: RuntimeMediaClip[];
89
88
  timeSeconds: number;
@@ -120,8 +119,12 @@ export declare function syncRuntimeMedia(params: {
120
119
  * unity; do not mistake that transport write for an authored volume edit. */
121
120
  isWebAudioRouted?: (el: HTMLMediaElement) => boolean;
122
121
  forceSync?: boolean;
122
+ /** False while the playhead is not moving (audio output has not started): a paused video waits for it. */
123
+ clockAdvancing?: boolean;
123
124
  /** How far the next tick will move the playhead: an audio clip due within it starts now. */
124
125
  cueAheadSeconds?: number;
126
+ /** Seeks a sounding element under a gain dip; false when it has no dip stage and the sync seeks it plainly. */
127
+ seekUnderDip?: (el: HTMLMediaElement, target: number) => boolean;
125
128
  /** Lets a video clip that runs to the composition end hold its last frame at the terminal time.
126
129
  * A thunk, because deriving the duration is only worth it for a clip past its own end. */
127
130
  getCompositionDuration: () => number;
@@ -13,6 +13,8 @@ import { findInjectedRenderFrame } from "./renderFrameSibling.js";
13
13
  import { isRenderFrameVideo } from "./preloadMedia.js";
14
14
  import { registerSeekCompletion } from "./adapters/seek-dispatch.js";
15
15
  import { cutToHostSlots } from "../mediaTiming.js";
16
+ import { forgetSteering, isSteering, MEDIA_SYNC_TOLERANCE_SECONDS, steerMedia, } from "./mediaSteer.js";
17
+ export { MEDIA_SYNC_TOLERANCE_SECONDS } from "./mediaSteer.js";
16
18
  export { readElementPlaybackRate, readElementRateSpec, resolveNaturalMediaTimelineDuration, } from "./playbackRate.js";
17
19
  export function readElementPlaybackStart(el) {
18
20
  return readMediaStart(el);
@@ -181,7 +183,8 @@ export function evictMediaSyncState(el) {
181
183
  strictDriftSamples.delete(el);
182
184
  seekLoadRetried.delete(el);
183
185
  lastRuntimeAppliedVolume.delete(el);
184
- videoSteering.delete(el);
186
+ forgetSteering(el);
187
+ heldAhead.delete(el);
185
188
  }
186
189
  /** Test-only seam: whether any per-source sync state is still tracked for `el`. */
187
190
  export function hasMediaSyncStateForTest(el) {
@@ -190,10 +193,10 @@ export function hasMediaSyncStateForTest(el) {
190
193
  strictDriftSamples.has(el) ||
191
194
  seekLoadRetried.has(el) ||
192
195
  lastRuntimeAppliedVolume.has(el) ||
193
- videoSteering.has(el));
196
+ isSteering(el));
194
197
  }
195
198
  export const MEDIA_HARD_SYNC_SECONDS = 0.5;
196
- export const VIDEO_PRESEEK_SECONDS = 1.5;
199
+ export const MEDIA_PRESEEK_SECONDS = 1.5;
197
200
  export function mediaSourceTimeAt(clip, timeSeconds) {
198
201
  const elapsed = Math.max(0, Math.min(timeSeconds, clip.end) - (clip.origin ?? clip.start));
199
202
  const target = clip.mediaStart + sourceTimeAt(clip.rate ?? clip.playbackRate, elapsed);
@@ -207,13 +210,12 @@ export function mediaSourceTimeAt(clip, timeSeconds) {
207
210
  return Math.min(target, clip.sourceDuration);
208
211
  return target;
209
212
  }
210
- function prepareUpcomingVideo(clip, time, playing, rate) {
213
+ function prepareUpcomingMedia(clip, time, playing, rate) {
211
214
  const { el } = clip;
212
215
  const dueIn = clip.start - time;
213
216
  if (!playing ||
214
- el.tagName !== "VIDEO" ||
215
217
  dueIn <= 0 ||
216
- dueIn > VIDEO_PRESEEK_SECONDS * rate ||
218
+ dueIn > MEDIA_PRESEEK_SECONDS * rate ||
217
219
  !el.paused ||
218
220
  el.seeking ||
219
221
  el.readyState < el.HAVE_METADATA ||
@@ -224,7 +226,7 @@ function prepareUpcomingVideo(clip, time, playing, rate) {
224
226
  if (!Number.isFinite(target) || target < 0 || Math.abs(el.currentTime - target) <= 0.001)
225
227
  return;
226
228
  try {
227
- // A trimmed video exposed during its in-point seek has no current frame at the cut.
229
+ // A trimmed clip seeked as it opens shows no frame and plays late, then is seeked again while it sounds.
228
230
  // Hidden preparation must not hold the visible-frame seek barrier.
229
231
  el.currentTime = mediaElementTime(target);
230
232
  }
@@ -232,35 +234,12 @@ function prepareUpcomingVideo(clip, time, playing, rate) {
232
234
  swallow("runtime.media.preseek", err);
233
235
  }
234
236
  }
235
- /** Drift a playing audio element may carry before sync pulls it back onto the playhead. */
236
- export const MEDIA_SYNC_TOLERANCE_SECONDS = 0.04;
237
237
  // Chromium refuses a slower playbackRate (NotSupportedError).
238
238
  const MIN_NATIVE_PLAYBACK_RATE = 1 / 16;
239
- // A playing video is steered back by rate, not seeked (a seek resets its decoder).
240
- // Its rate is written only when steering starts or stops: every write costs a frame.
241
- const VIDEO_STEER = 0.03;
242
- // A video that starts a cut late closes its lag over one clip at 3%; a bigger lag steers harder, in steps so the rate is written rarely.
243
- const VIDEO_STEER_STEPS = [
244
- [0.3, 0.25],
245
- [0.1, 0.1],
246
- ];
247
- const VIDEO_STEER_RELEASE_SECONDS = 0.01;
248
- /** Direction (+1 fast, -1 slow) a video is being steered in; absent when it plays at its authored rate. */
249
- const videoSteering = new WeakMap();
250
- /** Rate for a playing video `offset` seconds behind (+) or ahead (-) of the playhead. */
251
- function steeredVideoRate(el, offset, baseRate) {
252
- const direction = Math.sign(offset);
253
- const steering = videoSteering.get(el);
254
- if (Math.abs(offset) > MEDIA_SYNC_TOLERANCE_SECONDS)
255
- videoSteering.set(el, direction);
256
- else if (steering !== direction || Math.abs(offset) <= VIDEO_STEER_RELEASE_SECONDS) {
257
- videoSteering.delete(el);
258
- return baseRate;
259
- }
260
- const lag = Math.abs(offset);
261
- const steer = VIDEO_STEER_STEPS.find(([from]) => lag > from)?.[1] ?? VIDEO_STEER;
262
- return baseRate * (1 + direction * steer);
263
- }
239
+ // A video this far ahead of the playhead, or at its out-point, is held until the playhead reaches it:
240
+ // steering a lead of a few hundred ms takes seconds and shows frames past the cut.
241
+ const VIDEO_LEAD_HOLD_SECONDS = 0.1;
242
+ const heldAhead = new WeakSet();
264
243
  // fallow-ignore-next-line complexity
265
244
  export function syncRuntimeMedia(params) {
266
245
  const forceMuteAll = !!(params.outputMuted || params.userMuted);
@@ -451,12 +430,24 @@ export function syncRuntimeMedia(params) {
451
430
  (el.ended && canSeekEndedMediaBackward && drift > 0.001) ||
452
431
  staleAudioOnFirstTick ||
453
432
  (drift > MEDIA_HARD_SYNC_SECONDS && (firstTickOfClip || offsetJumped || catastrophicDrift));
454
- // Playing videos use the browser's decoder for timing. Seeking one resets the decoder: a
455
- // ~150ms freeze while it re-buffers, as the monotonic clock advances, which loops into a
456
- // seek→freeze→drift→seek stutter. So a playing video skips strict and force sync; only hard
457
- // sync (>0.5s) warrants the decoder-reset cost. A paused transport pauses this video below,
458
- // so a seek that pauses mid-playback still lands it.
459
- const isPlayingVideo = el.tagName === "VIDEO" && !el.paused && playing;
433
+ // Seeking playing media resets a video's decoder (a ~150ms freeze that loops into a
434
+ // seek→freeze→drift→seek stutter) and jumps sounding audio (a click). So playing media skips
435
+ // strict and force sync and is steered by rate; hard sync (>0.5s) and audio past its steer
436
+ // limit seek. A paused transport pauses it below, so a seek that pauses mid-playback lands it.
437
+ if (el.tagName === "VIDEO" && !isHeldVideoTail) {
438
+ // A loop's playhead wraps before its element, so its offset jumps by a lap: steering owns loops.
439
+ const ahead = !clip.loop &&
440
+ (offset < -VIDEO_LEAD_HOLD_SECONDS ||
441
+ currentElTime >= mediaSourceTimeAt(clip, clip.end) - MEDIA_SYNC_TOLERANCE_SECONDS);
442
+ if (!playing || (!ahead && offset >= -MEDIA_SYNC_TOLERANCE_SECONDS))
443
+ heldAhead.delete(el);
444
+ else if (ahead && !el.paused) {
445
+ heldAhead.add(el);
446
+ el.pause();
447
+ }
448
+ }
449
+ const holdingAhead = heldAhead.has(el);
450
+ const isPlayingMedia = !el.paused && playing;
460
451
  // Only apply strict sync when offset has stabilized (not growing).
461
452
  // During initial buffering, offset grows ~16ms/tick as the timeline
462
453
  // advances while media stays at 0. Accumulated drift from pause/play
@@ -464,7 +455,8 @@ export function syncRuntimeMedia(params) {
464
455
  const heldBelowFloor = params.playing && !playing;
465
456
  const offsetStabilized = heldBelowFloor || (prevOffset !== undefined && Math.abs(offset - prevOffset) < 0.004);
466
457
  let strictSync = false;
467
- if (!isPlayingVideo &&
458
+ if (!isPlayingMedia &&
459
+ !holdingAhead &&
468
460
  !hardSync &&
469
461
  !firstTickOfClip &&
470
462
  offsetStabilized &&
@@ -479,12 +471,15 @@ export function syncRuntimeMedia(params) {
479
471
  else if (drift <= MEDIA_SYNC_TOLERANCE_SECONDS) {
480
472
  strictDriftSamples.set(el, 0);
481
473
  }
482
- const forceSync = !isPlayingVideo && params.forceSync && drift > 0.02;
474
+ const forceSync = !isPlayingMedia && !holdingAhead && params.forceSync && drift > 0.02;
475
+ const steered = isPlayingMedia && !hardSync ? steerMedia(el, offset, baseRate, offsetStabilized) : null;
476
+ const steerSeeks = steered?.seek === true;
477
+ const dipped = steerSeeks && params.seekUnderDip?.(el, relTime) === true;
483
478
  try {
484
- // A hard sync lands the video on the playhead, so its pre-seek offset says nothing.
485
- if (!isPlayingVideo || hardSync)
486
- videoSteering.delete(el);
487
- const rate = Math.max(MIN_NATIVE_PLAYBACK_RATE, isPlayingVideo && !hardSync ? steeredVideoRate(el, offset, baseRate) : baseRate);
479
+ // A hard sync lands the element on the playhead, so its pre-seek offset says nothing.
480
+ if (!steered)
481
+ forgetSteering(el);
482
+ const rate = Math.max(MIN_NATIVE_PLAYBACK_RATE, steered?.rate ?? baseRate);
488
483
  // Some engines read a rate back at lower precision; an equal-enough rate is not rewritten.
489
484
  if (Math.abs(el.playbackRate - rate) > 1e-6)
490
485
  el.playbackRate = rate;
@@ -493,7 +488,7 @@ export function syncRuntimeMedia(params) {
493
488
  // ignore unsupported playbackRate
494
489
  swallow("runtime.media.site1", err);
495
490
  }
496
- if (hardSync || strictSync || forceSync) {
491
+ if (hardSync || strictSync || forceSync || (steerSeeks && !dipped)) {
497
492
  // Skip the per-tick seek (and the `el.load()` drift-recovery retry
498
493
  // below) for `<video>` elements that have a sibling
499
494
  // `<img id="__render_frame_<id>__">`. The sibling is created only
@@ -526,6 +521,7 @@ export function syncRuntimeMedia(params) {
526
521
  holdSeekBarrierUntilVideoLands(el);
527
522
  }
528
523
  playRequested.delete(el);
524
+ heldAhead.delete(el);
529
525
  }
530
526
  else if (!playing) {
531
527
  holdSeekBarrierUntilVideoLands(el);
@@ -534,7 +530,12 @@ export function syncRuntimeMedia(params) {
534
530
  if (!el.paused)
535
531
  el.pause();
536
532
  }
537
- else if (playing && el.paused && !playRequested.has(el) && !isUnplayable(el)) {
533
+ else if (playing &&
534
+ !heldAhead.has(el) &&
535
+ params.clockAdvancing !== false &&
536
+ el.paused &&
537
+ !playRequested.has(el) &&
538
+ !isUnplayable(el)) {
538
539
  // `HTMLMediaElement.play()` is spec'd to queue playback and resolve
539
540
  // once enough data is buffered, so we can unconditionally call it —
540
541
  // no need to gate on `readyState` or defer to a `canplay` listener.
@@ -580,6 +581,6 @@ export function syncRuntimeMedia(params) {
580
581
  lastRelativeTime.set(el, relTime);
581
582
  if (!el.paused)
582
583
  el.pause();
583
- prepareUpcomingVideo(clip, params.timeSeconds, params.playing, params.playbackRate);
584
+ prepareUpcomingMedia(clip, params.timeSeconds, params.playing, params.playbackRate);
584
585
  }
585
586
  }
@@ -0,0 +1,10 @@
1
+ /** Drift a playing element may carry before sync pulls it back onto the playhead. */
2
+ export declare const MEDIA_SYNC_TOLERANCE_SECONDS = 0.04;
3
+ /** Rate or seek for media `offset` s behind (+) or ahead (-); a lag still growing (`settled` false) never seeks. */
4
+ export declare function steerMedia(el: HTMLMediaElement, offset: number, baseRate: number, settled: boolean, audioOnly?: boolean): {
5
+ rate: number;
6
+ seek: boolean;
7
+ };
8
+ /** Drop `el`'s steering: it was seeked, paused or replaced, so its old offset says nothing. */
9
+ export declare function forgetSteering(el: HTMLMediaElement): void;
10
+ export declare function isSteering(el: HTMLMediaElement): boolean;
@@ -0,0 +1,40 @@
1
+ /** Drift a playing element may carry before sync pulls it back onto the playhead. */
2
+ export const MEDIA_SYNC_TOLERANCE_SECONDS = 0.04;
3
+ // Steered by rate, not seeked (a seek resets a video's decoder and clicks in audio); steps keep rate writes rare.
4
+ const MEDIA_STEER = 0.03;
5
+ const MEDIA_STEER_STEPS = [
6
+ [0.3, 0.25],
7
+ [0.1, 0.1],
8
+ ];
9
+ const MEDIA_STEER_RELEASE_SECONDS = 0.01;
10
+ // Audio never steers past 10%, where speech changes tempo; a lag held past 100 ms is seeked instead.
11
+ const AUDIO_MAX_STEER = 0.1;
12
+ const AUDIO_STEER_LIMIT_SECONDS = 0.1;
13
+ /** Direction (+1 fast, -1 slow) an element is being steered in; absent when it plays at its authored rate. */
14
+ const steering = new WeakMap();
15
+ /** Rate or seek for media `offset` s behind (+) or ahead (-); a lag still growing (`settled` false) never seeks. */
16
+ export function steerMedia(el, offset, baseRate, settled, audioOnly = el.tagName === "AUDIO") {
17
+ const lag = Math.abs(offset);
18
+ if (audioOnly && settled && lag > AUDIO_STEER_LIMIT_SECONDS) {
19
+ steering.delete(el);
20
+ return { rate: baseRate, seek: true };
21
+ }
22
+ const direction = Math.sign(offset);
23
+ const current = steering.get(el);
24
+ if (lag > MEDIA_SYNC_TOLERANCE_SECONDS)
25
+ steering.set(el, direction);
26
+ else if (current !== direction || lag <= MEDIA_STEER_RELEASE_SECONDS) {
27
+ steering.delete(el);
28
+ return { rate: baseRate, seek: false };
29
+ }
30
+ const step = MEDIA_STEER_STEPS.find(([from]) => lag > from)?.[1] ?? MEDIA_STEER;
31
+ const steer = audioOnly ? Math.min(step, AUDIO_MAX_STEER) : step;
32
+ return { rate: baseRate * (1 + direction * steer), seek: false };
33
+ }
34
+ /** Drop `el`'s steering: it was seeked, paused or replaced, so its old offset says nothing. */
35
+ export function forgetSteering(el) {
36
+ steering.delete(el);
37
+ }
38
+ export function isSteering(el) {
39
+ return steering.has(el);
40
+ }
@@ -20,7 +20,8 @@ function greatestCommonDivisor(a, b) {
20
20
  }
21
21
  export function runtimeProtocolFpsFromNumber(value) {
22
22
  const safe = Number.isFinite(value) && value > 0 ? value : 30;
23
- const denominator = Number.isInteger(safe) ? 1 : 1_000_000;
23
+ const isNtscRate = Math.abs(safe * 1001 - Math.round(safe * 1001)) < 1e-6;
24
+ const denominator = Number.isInteger(safe) ? 1 : isNtscRate ? 1001 : 1_000_000;
24
25
  const numerator = Math.round(safe * denominator);
25
26
  const divisor = greatestCommonDivisor(numerator, denominator);
26
27
  return { numerator: numerator / divisor, denominator: denominator / divisor };
@@ -3,6 +3,8 @@ export declare const STUDIO_PREVIEW_CAPTURE_MODE = "capture";
3
3
  export declare const STUDIO_PREVIEW_UPCOMING_ATTR = "data-hf-upcoming";
4
4
  export declare const STUDIO_PREVIEW_LAZY_ATTR = "data-hf-preview-lazy";
5
5
  export declare const STUDIO_PREVIEW_ERRORS = "__hfPreviewErrors";
6
+ export declare const refusedPreviewMessage: (status: number) => string;
7
+ export declare function refusedPreviewStatus(message: string): number | null;
6
8
  export declare const PREVIEW_RASTER_ATTR = "data-hf-preview-raster";
7
9
  export declare const RUNTIME_STAMPED_ATTR = "data-hf-autostamped";
8
10
  export declare const CLIP_HIDDEN_ATTR = "data-hf-clip-hidden";
@@ -3,6 +3,11 @@ export const STUDIO_PREVIEW_CAPTURE_MODE = "capture";
3
3
  export const STUDIO_PREVIEW_UPCOMING_ATTR = "data-hf-upcoming";
4
4
  export const STUDIO_PREVIEW_LAZY_ATTR = "data-hf-preview-lazy";
5
5
  export const STUDIO_PREVIEW_ERRORS = "__hfPreviewErrors";
6
+ export const refusedPreviewMessage = (status) => `The preview page could not be loaded (HTTP ${status}).`;
7
+ export function refusedPreviewStatus(message) {
8
+ const status = /^The preview page could not be loaded \(HTTP (\d{3})\)\.$/.exec(message)?.[1];
9
+ return status ? Number(status) : null;
10
+ }
6
11
  export const PREVIEW_RASTER_ATTR = "data-hf-preview-raster";
7
12
  export const RUNTIME_STAMPED_ATTR = "data-hf-autostamped";
8
13
  export const CLIP_HIDDEN_ATTR = "data-hf-clip-hidden";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/core",
3
- "version": "0.8.206",
3
+ "version": "0.8.207",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,6 +60,10 @@
60
60
  "import": "./dist/atomicFile.js",
61
61
  "types": "./dist/atomicFile.d.ts"
62
62
  },
63
+ "./remove-tree": {
64
+ "import": "./dist/removeTree.js",
65
+ "types": "./dist/removeTree.d.ts"
66
+ },
63
67
  "./project-folders": {
64
68
  "import": "./dist/projectFolders.js",
65
69
  "types": "./dist/projectFolders.d.ts"
@@ -273,6 +277,10 @@
273
277
  "import": "./dist/runtime/mediaClock.js",
274
278
  "types": "./dist/runtime/mediaClock.d.ts"
275
279
  },
280
+ "./runtime/media-steer": {
281
+ "import": "./dist/runtime/mediaSteer.js",
282
+ "types": "./dist/runtime/mediaSteer.d.ts"
283
+ },
276
284
  "./runtime/clip-reveal": {
277
285
  "import": "./dist/runtime/clipReveal.js",
278
286
  "types": "./dist/runtime/clipReveal.d.ts"
@@ -409,9 +417,9 @@
409
417
  "linkedom": "^0.18.12",
410
418
  "postcss": "^8.5.8",
411
419
  "wawoff2": "^2.0.1",
412
- "@hyperframes/lint": "0.8.206",
413
- "@hyperframes/studio-server": "0.8.206",
414
- "@hyperframes/parsers": "0.8.206"
420
+ "@hyperframes/lint": "0.8.207",
421
+ "@hyperframes/studio-server": "0.8.207",
422
+ "@hyperframes/parsers": "0.8.207"
415
423
  },
416
424
  "devDependencies": {
417
425
  "@fontsource/archivo-black": "^5.2.8",