visualfries 0.1.10115 → 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 (139) hide show
  1. package/README.md +122 -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.d.ts +3 -0
  83. package/dist/components/hooks/PixiSplitScreenDisplayObjectHook.js +85 -8
  84. package/dist/components/hooks/PixiVideoTextureHook.js +10 -4
  85. package/dist/components/hooks/PixiVisualTransformHook.d.ts +16 -0
  86. package/dist/components/hooks/PixiVisualTransformHook.js +102 -0
  87. package/dist/components/hooks/SubtitlesHook.js +48 -24
  88. package/dist/components/hooks/VerifyMediaHook.js +2 -2
  89. package/dist/directors/ComponentDirector.d.ts +1 -0
  90. package/dist/directors/ComponentDirector.js +41 -8
  91. package/dist/factories/SceneBuilderFactory.d.ts +3 -0
  92. package/dist/factories/SceneBuilderFactory.js +6 -0
  93. package/dist/fonts/fontDiscovery.js +12 -1
  94. package/dist/layers/Layer.svelte.d.ts +54 -0
  95. package/dist/layers/Layer.svelte.js +2 -25
  96. package/dist/managers/AppManager.svelte.d.ts +12 -0
  97. package/dist/managers/AppManager.svelte.js +180 -8
  98. package/dist/managers/ComponentsManager.svelte.d.ts +7 -7
  99. package/dist/managers/ComponentsManager.svelte.js +29 -15
  100. package/dist/managers/DeterministicMediaManager.d.ts +6 -0
  101. package/dist/managers/DeterministicMediaManager.js +57 -3
  102. package/dist/managers/EventManager.js +3 -1
  103. package/dist/managers/LayersManager.svelte.d.ts +54 -0
  104. package/dist/managers/RenderManager.d.ts +5 -0
  105. package/dist/managers/RenderManager.js +67 -21
  106. package/dist/managers/StateManager.svelte.d.ts +56 -0
  107. package/dist/managers/StateManager.svelte.js +60 -58
  108. package/dist/managers/SubtitlesManager.svelte.js +1 -3
  109. package/dist/managers/TimeManager.svelte.d.ts +4 -0
  110. package/dist/managers/TimeManager.svelte.js +23 -7
  111. package/dist/schemas/runtime/deterministic.d.ts +3 -0
  112. package/dist/schemas/runtime/deterministic.js +5 -1
  113. package/dist/schemas/runtime/types.d.ts +36 -15
  114. package/dist/schemas/scene/components.d.ts +126 -0
  115. package/dist/schemas/scene/components.js +11 -0
  116. package/dist/schemas/scene/core.d.ts +109 -10
  117. package/dist/schemas/scene/core.js +1 -1
  118. package/dist/utils/mediaWindow.d.ts +13 -0
  119. package/dist/utils/mediaWindow.js +25 -0
  120. package/dist/utils/svgGenerator.js +1 -1
  121. package/dist/utils/timelineWindow.d.ts +2 -0
  122. package/dist/utils/timelineWindow.js +7 -0
  123. package/dist/utils/utils.js +2 -4
  124. package/docs/AGENT_PATTERNS.md +243 -0
  125. package/docs/AGENT_WORKFLOW.md +364 -0
  126. package/docs/AUTHORING_BEST_PRACTICES.md +188 -0
  127. package/docs/AUTHORING_SYSTEM_AUDIT.md +98 -0
  128. package/docs/BROWSER_EXPORT_SPIKE.md +55 -0
  129. package/docs/DETERMINISTIC_RENDERER.md +83 -0
  130. package/docs/NEXT_RELEASE.md +28 -0
  131. package/docs/PROPOSED_VISUALFRIES_SKILL.md +68 -0
  132. package/docs/examples/agent-cues.json +49 -0
  133. package/docs/examples/agent-transcript.json +23 -0
  134. package/docs/examples/agent-transcript.srt +7 -0
  135. package/docs/examples/agent-transcript.vtt +7 -0
  136. package/docs/examples/image-animation.json +87 -0
  137. package/docs/examples/native-text-metric-badge.json +109 -0
  138. package/package.json +133 -100
  139. package/skills/visualfries/SKILL.md +328 -0
@@ -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
  }
@@ -1,15 +1,31 @@
1
1
  export class TimeManager {
2
2
  fps = $state(30); // Default
3
3
  duration = $state(0);
4
- transformTime(time, skipDurationCheck = false) {
5
- // Round to nearest frame boundary (1/fps seconds)
6
- let frameAdjustedTime = Math.round(time * this.fps) / this.fps;
7
- frameAdjustedTime = Math.max(frameAdjustedTime, 0);
4
+ static FRAME_EPSILON = 1e-4;
5
+ getFrameIndex(time, mode = 'nearest', skipDurationCheck = false) {
6
+ let adjustedTime = Math.max(time, 0);
7
+ if (!skipDurationCheck) {
8
+ adjustedTime = Math.min(adjustedTime, this.duration);
9
+ }
10
+ const scaledTime = adjustedTime * this.fps;
11
+ if (mode === 'current') {
12
+ return Math.max(0, Math.floor(scaledTime + TimeManager.FRAME_EPSILON));
13
+ }
14
+ return Math.max(0, Math.round(scaledTime));
15
+ }
16
+ getTimeForFrame(frame, skipDurationCheck = false) {
17
+ const frameTime = Math.max(0, frame) / this.fps;
8
18
  if (!skipDurationCheck) {
9
- // Keep duration check since endTime is always <= duration
10
- return frameAdjustedTime > this.duration ? this.duration : frameAdjustedTime;
19
+ return frameTime > this.duration ? this.duration : frameTime;
11
20
  }
12
- return frameAdjustedTime;
21
+ return frameTime;
22
+ }
23
+ transformTime(time, skipDurationCheck = false) {
24
+ const frame = this.getFrameIndex(time, 'current', skipDurationCheck);
25
+ return this.getTimeForFrame(frame, skipDurationCheck);
26
+ }
27
+ getCurrentFrameTime(time, skipDurationCheck = false) {
28
+ return this.transformTime(time, skipDurationCheck);
13
29
  }
14
30
  updateTimeConfig(newFps, newDuration) {
15
31
  this.fps = newFps;
@@ -63,6 +63,9 @@ export type DeterministicDiagnosticsReport = {
63
63
  providerMisses: number;
64
64
  cacheHits: number;
65
65
  cacheHitRatio: number;
66
+ selectedRendererType: 'canvas' | 'webgl';
67
+ rendererFallbackOccurred: boolean;
68
+ rendererFallbackReason?: string;
66
69
  readyAttempts: number;
67
70
  extraRenderPasses: number;
68
71
  blurRedraws: number;
@@ -8,7 +8,11 @@ export const DeterministicMediaConfigShape = z.object({
8
8
  loadingMaxAttempts: z.number().int().positive().prefault(2),
9
9
  readyYieldMs: z.number().int().nonnegative().prefault(0),
10
10
  blurDownscale: z.number().positive().max(1).prefault(0.33),
11
- provider: z.custom().optional()
11
+ provider: z
12
+ .custom((value) => typeof value === 'object' &&
13
+ value !== null &&
14
+ typeof value.getFrame === 'function', 'provider must implement getFrame(request)')
15
+ .optional()
12
16
  }).prefault({});
13
17
  export const defaultDeterministicMediaConfig = DeterministicMediaConfigShape.parse(undefined);
14
18
  export class DeterministicRenderError extends Error {
@@ -4,6 +4,7 @@ import type { ComponentsManager } from '../../managers/ComponentsManager.svelte.
4
4
  import type { LayersManager } from '../../managers/LayersManager.svelte.js';
5
5
  import type { SubtitlesManager } from '../../managers/SubtitlesManager.svelte.js';
6
6
  import type { EventManager } from '../../managers/EventManager.js';
7
+ import type { PixiAnimationTarget } from '../../animations/PixiAnimationTarget.js';
7
8
  import type { Component as SceneLayerComponent, ComponentBase, AppearanceInput, Scene, RenderEnvironment, ComponentInput, SceneLayerInput, SceneLayer, VideoComponentShape, ImageComponentShape, GifComponentShape, Subtitle } from '../..';
8
9
  import type { DeterministicMediaConfig, DeterministicFrameProvider, DeterministicDiagnosticsReport, FrameImageEncodingOptions, RenderFrameRangeOptions, RenderFrameRangeSummary } from './deterministic.js';
9
10
  declare const SCENE_LAYER_COMPONENT_TYPE: readonly ["IMAGE", "GIF", "VIDEO", "TEXT", "SHAPE", "AUDIO", "COLOR", "GRADIENT", "SUBTITLES"];
@@ -88,7 +89,7 @@ export interface ResourceManager<T, D, F> {
88
89
  get(id: string): T | undefined;
89
90
  getAll(): T[];
90
91
  update(id: string, data: Partial<F>, refreshType?: ComponentRefreshType): void;
91
- delete(id: string): void;
92
+ delete(id: string): void | Promise<void>;
92
93
  create(data: D): Promise<T | null>;
93
94
  setOrder(id: string, newOrder: number): void;
94
95
  moveUp(id: string): void;
@@ -99,7 +100,7 @@ export interface ResourceManager<T, D, F> {
99
100
  id: string;
100
101
  data: Partial<F>;
101
102
  }>): void;
102
- bulkDelete(ids: string[]): void;
103
+ bulkDelete(ids: string[]): void | Promise<void>;
103
104
  hide(id: string): void;
104
105
  show(id: string): void;
105
106
  toggle(id: string): void;
@@ -109,14 +110,16 @@ export type ComponentData = SceneLayerComponent;
109
110
  export type ComponentProps = ComponentBase & {
110
111
  checksum: string;
111
112
  duration: number;
113
+ sourceUrl?: string | undefined;
114
+ sourceStartAt?: number | undefined;
112
115
  setStart(start: number): void;
113
116
  setEnd(end: number): void;
114
117
  getData(): ComponentData;
115
118
  update(data: Partial<AppearanceInput>): void;
116
- updateText(text: string): Promise<void>;
117
- updateAppearance(appearance: Partial<AppearanceInput>): Promise<void>;
118
- setVisible(visible: boolean): Promise<void>;
119
- setOrder(order: number): Promise<void>;
119
+ updateText(text: string): void;
120
+ updateAppearance(appearance: Partial<AppearanceInput>): void;
121
+ setVisible(visible: boolean): void;
122
+ setOrder(order: number): void;
120
123
  };
121
124
  export interface PixiComponent {
122
125
  displayObject: Container;
@@ -131,6 +134,7 @@ export interface ComponentBuilder {
131
134
  withHtmlText(): ComponentBuilder;
132
135
  withHtmlAnimation(): ComponentBuilder;
133
136
  withAnimation(): ComponentBuilder;
137
+ withPixiAnimationTarget(): ComponentBuilder;
134
138
  withSubtitles(): ComponentBuilder;
135
139
  withDisplayObject(): ComponentBuilder;
136
140
  withTexture(): ComponentBuilder;
@@ -139,6 +143,7 @@ export interface ComponentBuilder {
139
143
  withGif(): ComponentBuilder;
140
144
  withShape(): ComponentBuilder;
141
145
  withCanvasShape(): ComponentBuilder;
146
+ withCanvasFill(): ComponentBuilder;
142
147
  withProgressShape(): ComponentBuilder;
143
148
  withHtmlToCanvasHook(): ComponentBuilder;
144
149
  }
@@ -155,14 +160,14 @@ export interface Component {
155
160
  setup(): void;
156
161
  update(): void;
157
162
  refresh(type?: ComponentRefreshType): void;
158
- destroy(): void;
163
+ destroy(): Promise<void>;
159
164
  setStart(start: number): Component;
160
165
  setEnd(end: number): Component;
161
- updateAppearance(appearance: Partial<AppearanceInput>): Promise<Component>;
162
- updateText(text: string): Promise<Component>;
163
- setText(text: string): Promise<Component>;
164
- setVisible(visible: boolean): Promise<Component>;
165
- setOrder(order: number): Promise<Component>;
166
+ updateAppearance(appearance: Partial<AppearanceInput>): Component;
167
+ updateText(text: string): Component;
168
+ setText(text: string): Component;
169
+ setVisible(visible: boolean): Component;
170
+ setOrder(order: number): Component;
166
171
  onChange(callback: (changes: ComponentData) => void): () => void;
167
172
  onTimelineChange(callback: (time: number) => void): () => void;
168
173
  }
@@ -193,7 +198,7 @@ export interface ResourceTypes {
193
198
  pixiContainer: Container | undefined;
194
199
  wrapperHtmlEl: HTMLElement | undefined;
195
200
  htmlEl: HTMLElement | undefined;
196
- animationTarget: HTMLElement | Container | undefined;
201
+ animationTarget: HTMLElement | Container | PixiAnimationTarget | undefined;
197
202
  animationData: Record<string, any> | undefined;
198
203
  pixiRenderObject: Container | undefined;
199
204
  htmlRenderObject: HTMLDivElement | undefined;
@@ -228,6 +233,21 @@ type Zone = {
228
233
  start: number;
229
234
  end: number;
230
235
  };
236
+ export type ComponentAnimationExplanation = {
237
+ componentId: string;
238
+ type: ComponentData['type'];
239
+ time: number;
240
+ active: boolean;
241
+ relativeTime: number;
242
+ targetKind: 'html' | 'pixi' | 'none';
243
+ targetOwner: 'wrapper' | 'element' | 'pixi' | 'none';
244
+ computed: Record<string, number | string>;
245
+ animations: Array<{
246
+ id: string;
247
+ enabled: boolean;
248
+ startAt: number;
249
+ }>;
250
+ };
231
251
  export interface SceneBuilder {
232
252
  readonly sceneData: Scene;
233
253
  readonly environment: RenderEnvironment;
@@ -245,7 +265,7 @@ export interface SceneBuilder {
245
265
  readonly components: ComponentsManager;
246
266
  readonly subtitles: SubtitlesManager;
247
267
  readonly htmlContainer: HTMLElement;
248
- readonly canvasContainer: HTMLElement;
268
+ readonly canvasContainer: HTMLCanvasElement;
249
269
  readonly disabledTimeZones: Zone[];
250
270
  syncChanges: () => void;
251
271
  addExcludedTimestamp(start: number, end: number): void;
@@ -259,6 +279,7 @@ export interface SceneBuilder {
259
279
  markDirty(): void;
260
280
  initialize(): Promise<void>;
261
281
  seek(time: number): Promise<void>;
282
+ explainComponentState(componentId: string): ComponentAnimationExplanation | null;
262
283
  replaceSourceOnTime(time: number, componentId: string, base64data: string): Promise<void>;
263
284
  setDeterministicFrameProvider(provider: DeterministicFrameProvider | null): void;
264
285
  getDeterministicFrameProvider(): DeterministicFrameProvider | null;
@@ -276,7 +297,7 @@ export interface SceneBuilder {
276
297
  removeLoadingComponent(componentId: string): void;
277
298
  buildCharactersList(): void;
278
299
  render(): void;
279
- destroy(): void;
300
+ destroy(): Promise<void>;
280
301
  addComponent(componentData: ComponentInput): Promise<ComponentData | undefined>;
281
302
  addLayer(layerInput: SceneLayerInput): Promise<Layer | undefined>;
282
303
  addNewLayerWithComponents(components: ComponentInput[]): Promise<Layer | undefined>;