visualfries 0.1.10120 → 0.3.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.
Files changed (133) hide show
  1. package/README.md +97 -0
  2. package/bin/visualfries.js +1570 -0
  3. package/dist/DIContainer.d.ts +2 -1
  4. package/dist/DIContainer.js +16 -5
  5. package/dist/SceneBuilder.svelte.d.ts +70 -1
  6. package/dist/SceneBuilder.svelte.js +96 -29
  7. package/dist/agent/audioMixer.d.ts +34 -0
  8. package/dist/agent/audioMixer.js +308 -0
  9. package/dist/agent/broll.d.ts +35 -0
  10. package/dist/agent/broll.js +226 -0
  11. package/dist/agent/capabilities.d.ts +34 -0
  12. package/dist/agent/capabilities.js +246 -0
  13. package/dist/agent/captionScene.d.ts +23 -0
  14. package/dist/agent/captionScene.js +204 -0
  15. package/dist/agent/catalog.d.ts +24 -0
  16. package/dist/agent/catalog.js +67 -0
  17. package/dist/agent/cueFiles.d.ts +54 -0
  18. package/dist/agent/cueFiles.js +219 -0
  19. package/dist/agent/cuePresets.d.ts +7 -0
  20. package/dist/agent/cuePresets.js +50 -0
  21. package/dist/agent/deterministicMedia.d.ts +61 -0
  22. package/dist/agent/deterministicMedia.js +303 -0
  23. package/dist/agent/index.d.ts +18 -0
  24. package/dist/agent/index.js +18 -0
  25. package/dist/agent/inspectScene.d.ts +36 -0
  26. package/dist/agent/inspectScene.js +139 -0
  27. package/dist/agent/localRender.d.ts +82 -0
  28. package/dist/agent/localRender.js +702 -0
  29. package/dist/agent/overlays.d.ts +40 -0
  30. package/dist/agent/overlays.js +325 -0
  31. package/dist/agent/productionPlan.d.ts +131 -0
  32. package/dist/agent/productionPlan.js +322 -0
  33. package/dist/agent/renderPlan.d.ts +27 -0
  34. package/dist/agent/renderPlan.js +68 -0
  35. package/dist/agent/renderRanges.d.ts +24 -0
  36. package/dist/agent/renderRanges.js +73 -0
  37. package/dist/agent/runtimeSupport.d.ts +19 -0
  38. package/dist/agent/runtimeSupport.js +192 -0
  39. package/dist/agent/streamingEncoder.d.ts +31 -0
  40. package/dist/agent/streamingEncoder.js +208 -0
  41. package/dist/agent/transcripts.d.ts +29 -0
  42. package/dist/agent/transcripts.js +146 -0
  43. package/dist/agent/transitions.d.ts +20 -0
  44. package/dist/agent/transitions.js +169 -0
  45. package/dist/animations/PixiAnimationTarget.d.ts +29 -0
  46. package/dist/animations/PixiAnimationTarget.js +63 -0
  47. package/dist/animations/builders/LineHighlighterAnimationBuilder.js +20 -3
  48. package/dist/animations/builders/WordHighlighterAnimationBuilder.js +15 -2
  49. package/dist/browser/browserExport.d.ts +73 -0
  50. package/dist/browser/browserExport.js +180 -0
  51. package/dist/browser/glTransitions.d.ts +37 -0
  52. package/dist/browser/glTransitions.js +261 -0
  53. package/dist/browser/index.d.ts +3 -0
  54. package/dist/browser/index.js +3 -0
  55. package/dist/browser/mediaDecode.d.ts +31 -0
  56. package/dist/browser/mediaDecode.js +152 -0
  57. package/dist/builders/PixiComponentBuilder.d.ts +6 -0
  58. package/dist/builders/PixiComponentBuilder.js +12 -0
  59. package/dist/builders/_ComponentState.svelte.d.ts +7 -5
  60. package/dist/builders/_ComponentState.svelte.js +29 -15
  61. package/dist/builders/html/StyleBuilder.js +9 -8
  62. package/dist/builders/html/TextComponentHtmlBuilder.js +4 -2
  63. package/dist/builders/html/processors/AppearanceStyleProcessor.js +1 -1
  64. package/dist/builders/html/processors/TextAppearanceStyleProcessor.js +7 -0
  65. package/dist/builders/html/processors/TextEffectsStyleProcessor.js +32 -13
  66. package/dist/commands/ReplaceSourceOnTimeCommand.js +9 -1
  67. package/dist/commands/SeekCommand.js +19 -6
  68. package/dist/components/AnimatedGIF.d.ts +3 -0
  69. package/dist/components/AnimatedGIF.js +28 -1
  70. package/dist/components/Component.svelte.d.ts +5 -5
  71. package/dist/components/Component.svelte.js +29 -13
  72. package/dist/components/ComponentContext.svelte.js +68 -23
  73. package/dist/components/hooks/AnimationHook.js +1 -1
  74. package/dist/components/hooks/CanvasFillHook.d.ts +12 -0
  75. package/dist/components/hooks/CanvasFillHook.js +75 -0
  76. package/dist/components/hooks/DeterministicMediaFrameHook.js +3 -0
  77. package/dist/components/hooks/HtmlTextHook.js +11 -3
  78. package/dist/components/hooks/HtmlToCanvasHook.js +8 -25
  79. package/dist/components/hooks/MediaHook.js +172 -47
  80. package/dist/components/hooks/MediaSeekingHook.js +138 -88
  81. package/dist/components/hooks/PixiGifHook.js +4 -4
  82. package/dist/components/hooks/PixiSplitScreenDisplayObjectHook.js +73 -6
  83. package/dist/components/hooks/PixiVideoTextureHook.js +10 -4
  84. package/dist/components/hooks/PixiVisualTransformHook.d.ts +16 -0
  85. package/dist/components/hooks/PixiVisualTransformHook.js +102 -0
  86. package/dist/components/hooks/SubtitlesHook.js +48 -24
  87. package/dist/components/hooks/VerifyMediaHook.js +2 -2
  88. package/dist/directors/ComponentDirector.d.ts +1 -0
  89. package/dist/directors/ComponentDirector.js +41 -8
  90. package/dist/fonts/fontDiscovery.js +12 -1
  91. package/dist/layers/Layer.svelte.d.ts +54 -0
  92. package/dist/layers/Layer.svelte.js +2 -25
  93. package/dist/managers/AppManager.svelte.js +8 -5
  94. package/dist/managers/ComponentsManager.svelte.d.ts +7 -7
  95. package/dist/managers/ComponentsManager.svelte.js +29 -15
  96. package/dist/managers/DeterministicMediaManager.js +38 -3
  97. package/dist/managers/EventManager.js +3 -1
  98. package/dist/managers/LayersManager.svelte.d.ts +54 -0
  99. package/dist/managers/RenderManager.d.ts +5 -0
  100. package/dist/managers/RenderManager.js +67 -21
  101. package/dist/managers/StateManager.svelte.d.ts +56 -0
  102. package/dist/managers/StateManager.svelte.js +60 -58
  103. package/dist/managers/SubtitlesManager.svelte.js +1 -3
  104. package/dist/managers/TimeManager.svelte.d.ts +4 -0
  105. package/dist/managers/TimeManager.svelte.js +23 -7
  106. package/dist/schemas/runtime/deterministic.js +5 -1
  107. package/dist/schemas/runtime/types.d.ts +36 -15
  108. package/dist/schemas/scene/components.d.ts +126 -0
  109. package/dist/schemas/scene/components.js +11 -0
  110. package/dist/schemas/scene/core.d.ts +109 -10
  111. package/dist/schemas/scene/core.js +1 -1
  112. package/dist/utils/mediaWindow.d.ts +13 -0
  113. package/dist/utils/mediaWindow.js +25 -0
  114. package/dist/utils/svgGenerator.js +1 -1
  115. package/dist/utils/timelineWindow.d.ts +2 -0
  116. package/dist/utils/timelineWindow.js +7 -0
  117. package/dist/utils/utils.js +2 -4
  118. package/docs/AGENT_PATTERNS.md +243 -0
  119. package/docs/AGENT_WORKFLOW.md +364 -0
  120. package/docs/AUTHORING_BEST_PRACTICES.md +188 -0
  121. package/docs/AUTHORING_SYSTEM_AUDIT.md +98 -0
  122. package/docs/BROWSER_EXPORT_SPIKE.md +55 -0
  123. package/docs/DETERMINISTIC_RENDERER.md +83 -0
  124. package/docs/NEXT_RELEASE.md +28 -0
  125. package/docs/PROPOSED_VISUALFRIES_SKILL.md +68 -0
  126. package/docs/examples/agent-cues.json +49 -0
  127. package/docs/examples/agent-transcript.json +23 -0
  128. package/docs/examples/agent-transcript.srt +7 -0
  129. package/docs/examples/agent-transcript.vtt +7 -0
  130. package/docs/examples/image-animation.json +87 -0
  131. package/docs/examples/native-text-metric-badge.json +109 -0
  132. package/package.json +133 -100
  133. package/skills/visualfries/SKILL.md +328 -0
@@ -16,6 +16,7 @@ const createDefaultDiagnosticsState = () => ({
16
16
  latencyMinMs: Number.POSITIVE_INFINITY,
17
17
  latencyMaxMs: 0
18
18
  });
19
+ const IMAGE_LOAD_TIMEOUT_MS = 5000;
19
20
  export class DeterministicMediaManager {
20
21
  config;
21
22
  #provider;
@@ -319,10 +320,44 @@ export class DeterministicMediaManager {
319
320
  async #loadImage(url) {
320
321
  const img = new Image();
321
322
  img.crossOrigin = 'anonymous';
322
- img.src = url;
323
323
  await new Promise((resolve, reject) => {
324
- img.onload = () => resolve();
325
- img.onerror = () => reject(new Error(`Failed to load deterministic frame: ${url}`));
324
+ let settled = false;
325
+ const timeoutId = setTimeout(() => {
326
+ if (settled) {
327
+ return;
328
+ }
329
+ settled = true;
330
+ cleanup();
331
+ try {
332
+ img.src = '';
333
+ }
334
+ catch {
335
+ // Ignore cancellation failures.
336
+ }
337
+ reject(new Error(`Timed out loading deterministic frame after ${IMAGE_LOAD_TIMEOUT_MS}ms: ${url}`));
338
+ }, IMAGE_LOAD_TIMEOUT_MS);
339
+ const cleanup = () => {
340
+ clearTimeout(timeoutId);
341
+ img.onload = null;
342
+ img.onerror = null;
343
+ };
344
+ img.onload = () => {
345
+ if (settled) {
346
+ return;
347
+ }
348
+ settled = true;
349
+ cleanup();
350
+ resolve();
351
+ };
352
+ img.onerror = () => {
353
+ if (settled) {
354
+ return;
355
+ }
356
+ settled = true;
357
+ cleanup();
358
+ reject(new Error(`Failed to load deterministic frame: ${url}`));
359
+ };
360
+ img.src = url;
326
361
  });
327
362
  return img;
328
363
  }
@@ -17,6 +17,8 @@ export class EventManager extends EventTarget {
17
17
  }
18
18
  // "on" automatically returns the right type under event.details object
19
19
  on(event, callback, options) {
20
- this.addEventListener(event, ((e) => callback(e)), options);
20
+ // Register the original function identity so removeEventListener can reliably
21
+ // detach manager callbacks during scene teardown.
22
+ this.addEventListener(event, callback, options);
21
23
  }
22
24
  }
@@ -476,6 +476,12 @@ export declare class LayersManager implements ResourceManager<ILayer, SceneLayer
476
476
  unit: "px" | "em" | "rem" | "%";
477
477
  } | null | undefined;
478
478
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
479
+ padding?: number | {
480
+ top: number;
481
+ right: number;
482
+ bottom: number;
483
+ left: number;
484
+ } | undefined;
479
485
  shadow?: {
480
486
  enabled?: boolean | undefined;
481
487
  preset?: string | undefined;
@@ -1095,6 +1101,12 @@ export declare class LayersManager implements ResourceManager<ILayer, SceneLayer
1095
1101
  unit: "px" | "em" | "rem" | "%";
1096
1102
  } | null | undefined;
1097
1103
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
1104
+ padding?: number | {
1105
+ top: number;
1106
+ right: number;
1107
+ bottom: number;
1108
+ left: number;
1109
+ } | undefined;
1098
1110
  shadow?: {
1099
1111
  enabled?: boolean | undefined;
1100
1112
  preset?: string | undefined;
@@ -1688,6 +1700,12 @@ export declare class LayersManager implements ResourceManager<ILayer, SceneLayer
1688
1700
  unit: "px" | "em" | "rem" | "%";
1689
1701
  } | null | undefined;
1690
1702
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
1703
+ padding?: number | {
1704
+ top: number;
1705
+ right: number;
1706
+ bottom: number;
1707
+ left: number;
1708
+ } | undefined;
1691
1709
  shadow?: {
1692
1710
  enabled?: boolean | undefined;
1693
1711
  preset?: string | undefined;
@@ -2279,6 +2297,12 @@ export declare class LayersManager implements ResourceManager<ILayer, SceneLayer
2279
2297
  unit: "px" | "em" | "rem" | "%";
2280
2298
  } | null | undefined;
2281
2299
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
2300
+ padding?: number | {
2301
+ top: number;
2302
+ right: number;
2303
+ bottom: number;
2304
+ left: number;
2305
+ } | undefined;
2282
2306
  shadow?: {
2283
2307
  enabled?: boolean | undefined;
2284
2308
  preset?: string | undefined;
@@ -2904,6 +2928,12 @@ export declare class LayersManager implements ResourceManager<ILayer, SceneLayer
2904
2928
  unit: "px" | "em" | "rem" | "%";
2905
2929
  } | null | undefined;
2906
2930
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
2931
+ padding?: number | {
2932
+ top: number;
2933
+ right: number;
2934
+ bottom: number;
2935
+ left: number;
2936
+ } | undefined;
2907
2937
  shadow?: {
2908
2938
  enabled?: boolean | undefined;
2909
2939
  preset?: string | undefined;
@@ -3499,6 +3529,12 @@ export declare class LayersManager implements ResourceManager<ILayer, SceneLayer
3499
3529
  unit: "px" | "em" | "rem" | "%";
3500
3530
  } | null | undefined;
3501
3531
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
3532
+ padding?: number | {
3533
+ top: number;
3534
+ right: number;
3535
+ bottom: number;
3536
+ left: number;
3537
+ } | undefined;
3502
3538
  shadow?: {
3503
3539
  enabled?: boolean | undefined;
3504
3540
  preset?: string | undefined;
@@ -4045,6 +4081,12 @@ export declare class LayersManager implements ResourceManager<ILayer, SceneLayer
4045
4081
  unit: "px" | "em" | "rem" | "%";
4046
4082
  } | null | undefined;
4047
4083
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
4084
+ padding?: number | {
4085
+ top: number;
4086
+ right: number;
4087
+ bottom: number;
4088
+ left: number;
4089
+ } | undefined;
4048
4090
  shadow?: {
4049
4091
  enabled?: boolean | undefined;
4050
4092
  preset?: string | undefined;
@@ -4596,6 +4638,12 @@ export declare class LayersManager implements ResourceManager<ILayer, SceneLayer
4596
4638
  unit: "px" | "em" | "rem" | "%";
4597
4639
  } | null | undefined;
4598
4640
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
4641
+ padding?: number | {
4642
+ top: number;
4643
+ right: number;
4644
+ bottom: number;
4645
+ left: number;
4646
+ } | undefined;
4599
4647
  shadow?: {
4600
4648
  enabled?: boolean | undefined;
4601
4649
  preset?: string | undefined;
@@ -5140,6 +5188,12 @@ export declare class LayersManager implements ResourceManager<ILayer, SceneLayer
5140
5188
  unit: "px" | "em" | "rem" | "%";
5141
5189
  } | null | undefined;
5142
5190
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
5191
+ padding?: number | {
5192
+ top: number;
5193
+ right: number;
5194
+ bottom: number;
5195
+ left: number;
5196
+ } | undefined;
5143
5197
  shadow?: {
5144
5198
  enabled?: boolean | undefined;
5145
5199
  preset?: string | undefined;
@@ -13,6 +13,11 @@ export declare class RenderManager {
13
13
  private layersManager;
14
14
  private lastActiveById;
15
15
  private lastRenderTime;
16
+ private renderInFlight;
17
+ private rerenderRequested;
18
+ private readonly boundHandleBeforeRender;
19
+ private readonly boundRender;
20
+ private readonly boundHandleChangeState;
16
21
  constructor(cradle: {
17
22
  stateManager: StateManager;
18
23
  componentsManager: ResourceManager<IComponent, ComponentData, AppearanceInput>;
@@ -2,32 +2,41 @@ import { StateManager } from './StateManager.svelte.js';
2
2
  import { EventManager } from './EventManager.js';
3
3
  import { AppManager } from './AppManager.svelte.js';
4
4
  import { LayersManager } from './LayersManager.svelte.js';
5
+ import { shouldPrepareMediaAtTime } from '../utils/mediaWindow.js';
6
+ import { isTimeWithinTimeline } from '../utils/timelineWindow.js';
5
7
  export class RenderManager {
6
8
  state;
7
9
  componentsManager;
8
10
  eventManager;
9
11
  appManager;
10
12
  layersManager;
11
- // Track last visibility to ensure we run one more update to hide components that just became invisible
13
+ // Track last prepared/visible state so media hooks get one final update to release resources.
12
14
  lastActiveById = new Map();
13
15
  lastRenderTime = -1;
16
+ renderInFlight = null;
17
+ rerenderRequested = false;
18
+ boundHandleBeforeRender;
19
+ boundRender;
20
+ boundHandleChangeState;
14
21
  constructor(cradle) {
15
22
  this.state = cradle.stateManager;
16
23
  this.componentsManager = cradle.componentsManager;
17
24
  this.eventManager = cradle.eventManager;
18
25
  this.appManager = cradle.appManager;
19
26
  this.layersManager = cradle.layersManager;
27
+ this.boundHandleBeforeRender = this.handleBeforeRender.bind(this);
28
+ this.boundRender = this.render.bind(this);
29
+ this.boundHandleChangeState = (event) => {
30
+ if (event.detail.state !== 'playing')
31
+ void this.render();
32
+ };
20
33
  this.initializeEventListeners();
21
34
  }
22
35
  initializeEventListeners() {
23
- this.eventManager.on('beforerender', this.handleBeforeRender.bind(this));
24
- this.eventManager.on('timeupdate', this.render.bind(this));
25
- this.eventManager.on('rerender', this.render.bind(this));
26
- this.eventManager.on('changestate', (event) => {
27
- if (event.detail.state !== 'playing') {
28
- this.render();
29
- }
30
- });
36
+ this.eventManager.on('beforerender', this.boundHandleBeforeRender);
37
+ this.eventManager.on('timeupdate', this.boundRender);
38
+ this.eventManager.on('rerender', this.boundRender);
39
+ this.eventManager.on('changestate', this.boundHandleChangeState);
31
40
  }
32
41
  async handleBeforeRender() { }
33
42
  #syncLayerDisplayObjects() {
@@ -43,33 +52,70 @@ export class RenderManager {
43
52
  }
44
53
  }
45
54
  async render() {
55
+ if (this.renderInFlight) {
56
+ this.rerenderRequested = true;
57
+ return this.renderInFlight;
58
+ }
59
+ this.renderInFlight = this.#drainRenderQueue();
60
+ return this.renderInFlight;
61
+ }
62
+ async #drainRenderQueue() {
63
+ try {
64
+ do {
65
+ this.rerenderRequested = false;
66
+ await this.#renderOnce();
67
+ } while (this.rerenderRequested);
68
+ }
69
+ finally {
70
+ this.renderInFlight = null;
71
+ }
72
+ }
73
+ async #renderOnce() {
46
74
  const components = this.componentsManager.getAll();
47
75
  const currentTime = this.state.currentTime;
48
76
  const entries = components.map((component) => {
49
77
  const startAt = component.props.timeline.startAt ?? 0;
50
78
  const endAt = component.props.timeline.endAt ?? this.state.duration;
51
- const isVisibleByTime = currentTime >= startAt && currentTime <= endAt;
79
+ const isVisibleByTime = isTimeWithinTimeline(currentTime, startAt, endAt);
52
80
  const isExplicitlyVisible = component.props.visible !== false;
53
- const shouldBeVisible = (isVisibleByTime && isExplicitlyVisible) || component.type === 'VIDEO';
81
+ const shouldBeVisible = isVisibleByTime && isExplicitlyVisible;
82
+ const shouldPrepareMedia = component.type === 'VIDEO' || component.type === 'AUDIO'
83
+ ? shouldPrepareMediaAtTime({
84
+ type: component.type,
85
+ visible: component.props.visible,
86
+ timeline: component.props.timeline,
87
+ source: { url: component.props.sourceUrl }
88
+ }, currentTime)
89
+ : false;
54
90
  const wasVisible = this.lastActiveById.get(component.id) === true;
55
- return { component, shouldBeVisible, wasVisible };
91
+ return { component, shouldBeVisible, shouldPrepareMedia, wasVisible };
56
92
  });
57
- const toUpdate = entries
58
- .filter((e) => e.shouldBeVisible || e.wasVisible)
59
- .map((e) => e.component);
93
+ // Server output is correctness-first: every display hook reasserts timeline
94
+ // visibility on every frame. The optimized preview path can otherwise leave
95
+ // a stale outgoing image visible across a cut in a long frame-range render.
96
+ const toUpdate = this.state.environment === 'server'
97
+ ? components
98
+ : entries
99
+ // Every component needs one initial update so its display hook can hide it
100
+ // when the first rendered frame is outside its timeline.
101
+ .filter((e) => !this.lastActiveById.has(e.component.id) ||
102
+ e.shouldBeVisible ||
103
+ e.wasVisible ||
104
+ e.shouldPrepareMedia)
105
+ .map((e) => e.component);
60
106
  await Promise.all(toUpdate.map((component) => component.update()));
61
- // Update visibility cache after updates
107
+ // Keep one extra tick after the warm window ends so media hooks can release pooled resources.
62
108
  for (const e of entries) {
63
- this.lastActiveById.set(e.component.id, e.shouldBeVisible);
109
+ this.lastActiveById.set(e.component.id, e.shouldBeVisible || e.shouldPrepareMedia);
64
110
  }
65
111
  this.#syncLayerDisplayObjects();
66
112
  this.appManager.render();
67
113
  this.lastRenderTime = currentTime;
68
114
  }
69
115
  destroy() {
70
- this.eventManager.removeEventListener('beforerender', this.handleBeforeRender);
71
- this.eventManager.removeEventListener('timeupdate', this.render);
72
- this.eventManager.removeEventListener('rerender', this.render);
73
- this.eventManager.removeEventListener('changestate', this.render);
116
+ this.eventManager.removeEventListener('beforerender', this.boundHandleBeforeRender);
117
+ this.eventManager.removeEventListener('timeupdate', this.boundRender);
118
+ this.eventManager.removeEventListener('rerender', this.boundRender);
119
+ this.eventManager.removeEventListener('changestate', this.boundHandleChangeState);
74
120
  }
75
121
  }
@@ -22,6 +22,8 @@ export declare class StateManager implements IStateManager {
22
22
  private loop;
23
23
  private timeManager;
24
24
  private layersManager;
25
+ private disabledTimeZonesCache;
26
+ private disabledTimeZonesCacheKey;
25
27
  constructor(cradle: {
26
28
  sceneData: SceneData;
27
29
  environment: RenderEnvironment;
@@ -647,6 +649,12 @@ export declare class StateManager implements IStateManager {
647
649
  unit: "px" | "em" | "rem" | "%";
648
650
  } | null | undefined;
649
651
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
652
+ padding?: number | {
653
+ top: number;
654
+ right: number;
655
+ bottom: number;
656
+ left: number;
657
+ } | undefined;
650
658
  shadow?: {
651
659
  enabled?: boolean | undefined;
652
660
  preset?: string | undefined;
@@ -1266,6 +1274,12 @@ export declare class StateManager implements IStateManager {
1266
1274
  unit: "px" | "em" | "rem" | "%";
1267
1275
  } | null | undefined;
1268
1276
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
1277
+ padding?: number | {
1278
+ top: number;
1279
+ right: number;
1280
+ bottom: number;
1281
+ left: number;
1282
+ } | undefined;
1269
1283
  shadow?: {
1270
1284
  enabled?: boolean | undefined;
1271
1285
  preset?: string | undefined;
@@ -1859,6 +1873,12 @@ export declare class StateManager implements IStateManager {
1859
1873
  unit: "px" | "em" | "rem" | "%";
1860
1874
  } | null | undefined;
1861
1875
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
1876
+ padding?: number | {
1877
+ top: number;
1878
+ right: number;
1879
+ bottom: number;
1880
+ left: number;
1881
+ } | undefined;
1862
1882
  shadow?: {
1863
1883
  enabled?: boolean | undefined;
1864
1884
  preset?: string | undefined;
@@ -2450,6 +2470,12 @@ export declare class StateManager implements IStateManager {
2450
2470
  unit: "px" | "em" | "rem" | "%";
2451
2471
  } | null | undefined;
2452
2472
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
2473
+ padding?: number | {
2474
+ top: number;
2475
+ right: number;
2476
+ bottom: number;
2477
+ left: number;
2478
+ } | undefined;
2453
2479
  shadow?: {
2454
2480
  enabled?: boolean | undefined;
2455
2481
  preset?: string | undefined;
@@ -3075,6 +3101,12 @@ export declare class StateManager implements IStateManager {
3075
3101
  unit: "px" | "em" | "rem" | "%";
3076
3102
  } | null | undefined;
3077
3103
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
3104
+ padding?: number | {
3105
+ top: number;
3106
+ right: number;
3107
+ bottom: number;
3108
+ left: number;
3109
+ } | undefined;
3078
3110
  shadow?: {
3079
3111
  enabled?: boolean | undefined;
3080
3112
  preset?: string | undefined;
@@ -3670,6 +3702,12 @@ export declare class StateManager implements IStateManager {
3670
3702
  unit: "px" | "em" | "rem" | "%";
3671
3703
  } | null | undefined;
3672
3704
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
3705
+ padding?: number | {
3706
+ top: number;
3707
+ right: number;
3708
+ bottom: number;
3709
+ left: number;
3710
+ } | undefined;
3673
3711
  shadow?: {
3674
3712
  enabled?: boolean | undefined;
3675
3713
  preset?: string | undefined;
@@ -4216,6 +4254,12 @@ export declare class StateManager implements IStateManager {
4216
4254
  unit: "px" | "em" | "rem" | "%";
4217
4255
  } | null | undefined;
4218
4256
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
4257
+ padding?: number | {
4258
+ top: number;
4259
+ right: number;
4260
+ bottom: number;
4261
+ left: number;
4262
+ } | undefined;
4219
4263
  shadow?: {
4220
4264
  enabled?: boolean | undefined;
4221
4265
  preset?: string | undefined;
@@ -4767,6 +4811,12 @@ export declare class StateManager implements IStateManager {
4767
4811
  unit: "px" | "em" | "rem" | "%";
4768
4812
  } | null | undefined;
4769
4813
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
4814
+ padding?: number | {
4815
+ top: number;
4816
+ right: number;
4817
+ bottom: number;
4818
+ left: number;
4819
+ } | undefined;
4770
4820
  shadow?: {
4771
4821
  enabled?: boolean | undefined;
4772
4822
  preset?: string | undefined;
@@ -5311,6 +5361,12 @@ export declare class StateManager implements IStateManager {
5311
5361
  unit: "px" | "em" | "rem" | "%";
5312
5362
  } | null | undefined;
5313
5363
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
5364
+ padding?: number | {
5365
+ top: number;
5366
+ right: number;
5367
+ bottom: number;
5368
+ left: number;
5369
+ } | undefined;
5314
5370
  shadow?: {
5315
5371
  enabled?: boolean | undefined;
5316
5372
  preset?: string | undefined;
@@ -15,6 +15,8 @@ export class StateManager {
15
15
  loop = false;
16
16
  timeManager;
17
17
  layersManager;
18
+ disabledTimeZonesCache = [];
19
+ disabledTimeZonesCacheKey = '';
18
20
  constructor(cradle) {
19
21
  const sceneDataRune = $state(cradle.sceneData);
20
22
  this.currentSceneData = sceneDataRune;
@@ -35,14 +37,9 @@ export class StateManager {
35
37
  this.currentSceneData.settings.subtitles = subtitlesSettings;
36
38
  }
37
39
  get currentTime() {
38
- if (this.currentTimeRune >= this.endTime) {
39
- return this.endTime;
40
- }
41
- else if (this.currentTimeRune <= this.startTime) {
42
- return this.startTime;
43
- }
44
- // Could cache fps if it doesn't change frequently
45
- return this.timeManager.transformTime(this.currentTimeRune);
40
+ const clampedTime = Math.max(this.startTime, Math.min(this.currentTimeRune, this.endTime));
41
+ const frameTime = this.timeManager.getCurrentFrameTime(clampedTime, true);
42
+ return Math.min(frameTime, this.endTime);
46
43
  }
47
44
  get data() {
48
45
  return this.currentSceneData;
@@ -71,7 +68,7 @@ export class StateManager {
71
68
  return Math.min(this.data.settings.endAt || this.duration, this.duration);
72
69
  }
73
70
  setCurrentTime(time) {
74
- const currentTime = this.timeManager.transformTime(Math.max(this.startTime, Math.min(time, this.endTime)));
71
+ const currentTime = Math.max(this.startTime, Math.min(time, this.endTime));
75
72
  this.currentTimeRune = currentTime;
76
73
  if (time >= this.endTime && this.isPlaying) {
77
74
  if (this.loop) {
@@ -101,63 +98,68 @@ export class StateManager {
101
98
  }
102
99
  }
103
100
  get currentFrame() {
104
- return Math.round(this.currentTime * this.data.settings.fps);
101
+ const clampedTime = Math.max(this.startTime, Math.min(this.currentTimeRune, this.endTime));
102
+ return this.timeManager.getFrameIndex(clampedTime, 'current', true);
105
103
  }
106
104
  get disabledTimeZones() {
107
- const start = $derived(this.data.settings.startAt);
108
- const end = $derived(this.data.settings.endAt);
109
- const excluded_timestamps = $derived(this.data.settings.trimZones);
110
- const duration = $derived(this.data.settings.duration);
111
- const zones = $derived.by(() => {
112
- const output = [];
113
- if (start && start > 0) {
114
- output.push({
115
- start: 0,
116
- end: start
117
- });
118
- }
119
- if (excluded_timestamps && excluded_timestamps.length) {
120
- for (const disabledZone of excluded_timestamps) {
121
- const { start, end } = disabledZone;
122
- if (end < duration) {
123
- output.push({
124
- start,
125
- end
126
- });
127
- }
105
+ const start = this.data.settings.startAt;
106
+ const end = this.data.settings.endAt;
107
+ const excludedTimestamps = this.data.settings.trimZones ?? [];
108
+ const duration = this.data.settings.duration;
109
+ const cacheKey = [
110
+ start ?? '',
111
+ end ?? '',
112
+ duration,
113
+ excludedTimestamps.map((zone) => `${zone.start}:${zone.end}`).join('|')
114
+ ].join('::');
115
+ if (cacheKey === this.disabledTimeZonesCacheKey) {
116
+ return this.disabledTimeZonesCache;
117
+ }
118
+ const output = [];
119
+ if (start && start > 0) {
120
+ output.push({
121
+ start: 0,
122
+ end: start
123
+ });
124
+ }
125
+ if (excludedTimestamps.length) {
126
+ for (const disabledZone of excludedTimestamps) {
127
+ const { start, end } = disabledZone;
128
+ if (end < duration) {
129
+ output.push({
130
+ start,
131
+ end
132
+ });
128
133
  }
129
134
  }
130
- if (end && end < duration) {
131
- output.push({
132
- start: end,
133
- end: duration
134
- });
135
+ }
136
+ if (end && end < duration) {
137
+ output.push({
138
+ start: end,
139
+ end: duration
140
+ });
141
+ }
142
+ output.sort((a, b) => a.start - b.start);
143
+ const mergedZones = [];
144
+ let currentZone = null;
145
+ for (const zone of output) {
146
+ if (!currentZone) {
147
+ currentZone = { ...zone };
135
148
  }
136
- // Sort zones by start time
137
- output.sort((a, b) => a.start - b.start);
138
- // Merge overlapping zones
139
- const mergedZones = [];
140
- let currentZone = null;
141
- for (const zone of output) {
142
- if (!currentZone) {
143
- currentZone = { ...zone };
144
- }
145
- else if (zone.start <= currentZone.end) {
146
- // Zones overlap, extend the current zone
147
- currentZone.end = Math.max(currentZone.end, zone.end);
148
- }
149
- else {
150
- // No overlap, push current zone and start a new one
151
- mergedZones.push(currentZone);
152
- currentZone = { ...zone };
153
- }
149
+ else if (zone.start <= currentZone.end) {
150
+ currentZone.end = Math.max(currentZone.end, zone.end);
154
151
  }
155
- if (currentZone) {
152
+ else {
156
153
  mergedZones.push(currentZone);
154
+ currentZone = { ...zone };
157
155
  }
158
- return mergedZones;
159
- });
160
- return zones;
156
+ }
157
+ if (currentZone) {
158
+ mergedZones.push(currentZone);
159
+ }
160
+ this.disabledTimeZonesCacheKey = cacheKey;
161
+ this.disabledTimeZonesCache = mergedZones;
162
+ return this.disabledTimeZonesCache;
161
163
  }
162
164
  changeState(updateState) {
163
165
  let newState = this.determineNewState(updateState);
@@ -208,9 +208,7 @@ function buildSubtitlesManager(timeManager, eventManager, sceneData, subtitles)
208
208
  for (const [lang, langIndex] of Object.entries(assetIndex)) {
209
209
  for (const [subtitleId, subtitle] of Object.entries(langIndex)) {
210
210
  const text = subtitle.text;
211
- // Use Array.from to properly handle multi-codepoint characters (emojis, etc.)
212
- // This prevents splitting emojis into unpaired surrogates that cause encodeURIComponent to fail
213
- const characters = Array.from(text);
211
+ const characters = text.split('');
214
212
  if (characters && characters.length > 0) {
215
213
  for (const char of characters) {
216
214
  if (!charactersList.includes(char)) {
@@ -1,6 +1,10 @@
1
1
  export declare class TimeManager {
2
2
  fps: number;
3
3
  duration: number;
4
+ private static readonly FRAME_EPSILON;
5
+ getFrameIndex(time: number, mode?: 'nearest' | 'current', skipDurationCheck?: boolean): number;
6
+ getTimeForFrame(frame: number, skipDurationCheck?: boolean): number;
4
7
  transformTime(time: number, skipDurationCheck?: boolean): number;
8
+ getCurrentFrameTime(time: number, skipDurationCheck?: boolean): number;
5
9
  updateTimeConfig(newFps: number, newDuration: number): void;
6
10
  }