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.
- package/README.md +122 -0
- package/bin/visualfries.js +1570 -0
- package/dist/DIContainer.d.ts +2 -1
- package/dist/DIContainer.js +16 -5
- package/dist/SceneBuilder.svelte.d.ts +70 -1
- package/dist/SceneBuilder.svelte.js +96 -29
- package/dist/agent/audioMixer.d.ts +34 -0
- package/dist/agent/audioMixer.js +308 -0
- package/dist/agent/broll.d.ts +35 -0
- package/dist/agent/broll.js +226 -0
- package/dist/agent/capabilities.d.ts +34 -0
- package/dist/agent/capabilities.js +246 -0
- package/dist/agent/captionScene.d.ts +23 -0
- package/dist/agent/captionScene.js +204 -0
- package/dist/agent/catalog.d.ts +24 -0
- package/dist/agent/catalog.js +67 -0
- package/dist/agent/cueFiles.d.ts +54 -0
- package/dist/agent/cueFiles.js +219 -0
- package/dist/agent/cuePresets.d.ts +7 -0
- package/dist/agent/cuePresets.js +50 -0
- package/dist/agent/deterministicMedia.d.ts +61 -0
- package/dist/agent/deterministicMedia.js +303 -0
- package/dist/agent/index.d.ts +18 -0
- package/dist/agent/index.js +18 -0
- package/dist/agent/inspectScene.d.ts +36 -0
- package/dist/agent/inspectScene.js +139 -0
- package/dist/agent/localRender.d.ts +82 -0
- package/dist/agent/localRender.js +702 -0
- package/dist/agent/overlays.d.ts +40 -0
- package/dist/agent/overlays.js +325 -0
- package/dist/agent/productionPlan.d.ts +131 -0
- package/dist/agent/productionPlan.js +322 -0
- package/dist/agent/renderPlan.d.ts +27 -0
- package/dist/agent/renderPlan.js +68 -0
- package/dist/agent/renderRanges.d.ts +24 -0
- package/dist/agent/renderRanges.js +73 -0
- package/dist/agent/runtimeSupport.d.ts +19 -0
- package/dist/agent/runtimeSupport.js +192 -0
- package/dist/agent/streamingEncoder.d.ts +31 -0
- package/dist/agent/streamingEncoder.js +208 -0
- package/dist/agent/transcripts.d.ts +29 -0
- package/dist/agent/transcripts.js +146 -0
- package/dist/agent/transitions.d.ts +20 -0
- package/dist/agent/transitions.js +169 -0
- package/dist/animations/PixiAnimationTarget.d.ts +29 -0
- package/dist/animations/PixiAnimationTarget.js +63 -0
- package/dist/animations/builders/LineHighlighterAnimationBuilder.js +20 -3
- package/dist/animations/builders/WordHighlighterAnimationBuilder.js +15 -2
- package/dist/browser/browserExport.d.ts +73 -0
- package/dist/browser/browserExport.js +180 -0
- package/dist/browser/glTransitions.d.ts +37 -0
- package/dist/browser/glTransitions.js +261 -0
- package/dist/browser/index.d.ts +3 -0
- package/dist/browser/index.js +3 -0
- package/dist/browser/mediaDecode.d.ts +31 -0
- package/dist/browser/mediaDecode.js +152 -0
- package/dist/builders/PixiComponentBuilder.d.ts +6 -0
- package/dist/builders/PixiComponentBuilder.js +12 -0
- package/dist/builders/_ComponentState.svelte.d.ts +7 -5
- package/dist/builders/_ComponentState.svelte.js +29 -15
- package/dist/builders/html/StyleBuilder.js +9 -8
- package/dist/builders/html/TextComponentHtmlBuilder.js +4 -2
- package/dist/builders/html/processors/AppearanceStyleProcessor.js +1 -1
- package/dist/builders/html/processors/TextAppearanceStyleProcessor.js +7 -0
- package/dist/builders/html/processors/TextEffectsStyleProcessor.js +32 -13
- package/dist/commands/ReplaceSourceOnTimeCommand.js +9 -1
- package/dist/commands/SeekCommand.js +19 -6
- package/dist/components/AnimatedGIF.d.ts +3 -0
- package/dist/components/AnimatedGIF.js +28 -1
- package/dist/components/Component.svelte.d.ts +5 -5
- package/dist/components/Component.svelte.js +29 -13
- package/dist/components/ComponentContext.svelte.js +68 -23
- package/dist/components/hooks/AnimationHook.js +1 -1
- package/dist/components/hooks/CanvasFillHook.d.ts +12 -0
- package/dist/components/hooks/CanvasFillHook.js +75 -0
- package/dist/components/hooks/DeterministicMediaFrameHook.js +3 -0
- package/dist/components/hooks/HtmlTextHook.js +11 -3
- package/dist/components/hooks/HtmlToCanvasHook.js +8 -25
- package/dist/components/hooks/MediaHook.js +172 -47
- package/dist/components/hooks/MediaSeekingHook.js +138 -88
- package/dist/components/hooks/PixiGifHook.js +4 -4
- package/dist/components/hooks/PixiSplitScreenDisplayObjectHook.d.ts +3 -0
- package/dist/components/hooks/PixiSplitScreenDisplayObjectHook.js +85 -8
- package/dist/components/hooks/PixiVideoTextureHook.js +10 -4
- package/dist/components/hooks/PixiVisualTransformHook.d.ts +16 -0
- package/dist/components/hooks/PixiVisualTransformHook.js +102 -0
- package/dist/components/hooks/SubtitlesHook.js +48 -24
- package/dist/components/hooks/VerifyMediaHook.js +2 -2
- package/dist/directors/ComponentDirector.d.ts +1 -0
- package/dist/directors/ComponentDirector.js +41 -8
- package/dist/factories/SceneBuilderFactory.d.ts +3 -0
- package/dist/factories/SceneBuilderFactory.js +6 -0
- package/dist/fonts/fontDiscovery.js +12 -1
- package/dist/layers/Layer.svelte.d.ts +54 -0
- package/dist/layers/Layer.svelte.js +2 -25
- package/dist/managers/AppManager.svelte.d.ts +12 -0
- package/dist/managers/AppManager.svelte.js +180 -8
- package/dist/managers/ComponentsManager.svelte.d.ts +7 -7
- package/dist/managers/ComponentsManager.svelte.js +29 -15
- package/dist/managers/DeterministicMediaManager.d.ts +6 -0
- package/dist/managers/DeterministicMediaManager.js +57 -3
- package/dist/managers/EventManager.js +3 -1
- package/dist/managers/LayersManager.svelte.d.ts +54 -0
- package/dist/managers/RenderManager.d.ts +5 -0
- package/dist/managers/RenderManager.js +67 -21
- package/dist/managers/StateManager.svelte.d.ts +56 -0
- package/dist/managers/StateManager.svelte.js +60 -58
- package/dist/managers/SubtitlesManager.svelte.js +1 -3
- package/dist/managers/TimeManager.svelte.d.ts +4 -0
- package/dist/managers/TimeManager.svelte.js +23 -7
- package/dist/schemas/runtime/deterministic.d.ts +3 -0
- package/dist/schemas/runtime/deterministic.js +5 -1
- package/dist/schemas/runtime/types.d.ts +36 -15
- package/dist/schemas/scene/components.d.ts +126 -0
- package/dist/schemas/scene/components.js +11 -0
- package/dist/schemas/scene/core.d.ts +109 -10
- package/dist/schemas/scene/core.js +1 -1
- package/dist/utils/mediaWindow.d.ts +13 -0
- package/dist/utils/mediaWindow.js +25 -0
- package/dist/utils/svgGenerator.js +1 -1
- package/dist/utils/timelineWindow.d.ts +2 -0
- package/dist/utils/timelineWindow.js +7 -0
- package/dist/utils/utils.js +2 -4
- package/docs/AGENT_PATTERNS.md +243 -0
- package/docs/AGENT_WORKFLOW.md +364 -0
- package/docs/AUTHORING_BEST_PRACTICES.md +188 -0
- package/docs/AUTHORING_SYSTEM_AUDIT.md +98 -0
- package/docs/BROWSER_EXPORT_SPIKE.md +55 -0
- package/docs/DETERMINISTIC_RENDERER.md +83 -0
- package/docs/NEXT_RELEASE.md +28 -0
- package/docs/PROPOSED_VISUALFRIES_SKILL.md +68 -0
- package/docs/examples/agent-cues.json +49 -0
- package/docs/examples/agent-transcript.json +23 -0
- package/docs/examples/agent-transcript.srt +7 -0
- package/docs/examples/agent-transcript.vtt +7 -0
- package/docs/examples/image-animation.json +87 -0
- package/docs/examples/native-text-metric-badge.json +109 -0
- package/package.json +133 -100
- package/skills/visualfries/SKILL.md +328 -0
|
@@ -9,10 +9,18 @@ export class AppManager {
|
|
|
9
9
|
state;
|
|
10
10
|
dom;
|
|
11
11
|
forceCanvas; // ForceCanvas
|
|
12
|
+
serverRendererMode;
|
|
13
|
+
preferWebGL2;
|
|
14
|
+
powerPreference;
|
|
15
|
+
deterministicMediaManager;
|
|
12
16
|
constructor(cradle) {
|
|
13
17
|
this.state = cradle.stateManager;
|
|
14
18
|
this.dom = cradle.domManager;
|
|
15
19
|
this.forceCanvas = cradle.forceCanvas;
|
|
20
|
+
this.serverRendererMode = cradle.serverRendererMode ?? 'canvas';
|
|
21
|
+
this.preferWebGL2 = cradle.preferWebGL2 ?? true;
|
|
22
|
+
this.powerPreference = cradle.powerPreference ?? 'high-performance';
|
|
23
|
+
this.deterministicMediaManager = cradle.deterministicMediaManager;
|
|
16
24
|
}
|
|
17
25
|
get app() {
|
|
18
26
|
return this.#app;
|
|
@@ -30,29 +38,193 @@ export class AppManager {
|
|
|
30
38
|
const { width, height, environment, scale } = this.state;
|
|
31
39
|
this.#destroyed = false;
|
|
32
40
|
const canvas = this.dom.canvas;
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
let rendererSelection = this.#resolveRendererSelection(environment);
|
|
42
|
+
if (environment === 'server' && rendererSelection.forceCanvas) {
|
|
43
|
+
canvas.getContext('2d', { willReadFrequently: true });
|
|
35
44
|
}
|
|
36
45
|
const { PixiPlugin } = await registerGsapPlugins();
|
|
37
46
|
// give the plugin a reference to the PIXI object
|
|
38
|
-
PixiPlugin.registerPIXI(
|
|
39
|
-
const
|
|
47
|
+
PixiPlugin.registerPIXI(PIXI);
|
|
48
|
+
const sceneBackground = this.state.data.settings.backgroundColor;
|
|
49
|
+
const solidBackground = typeof sceneBackground === 'string' ? sceneBackground : 'transparent';
|
|
50
|
+
const baseOptions = {
|
|
40
51
|
...PIXI_DEFAULTS,
|
|
41
52
|
preserveDrawingBuffer: environment === 'server',
|
|
42
53
|
// clearBeforeRender: environment === 'server' ? false : true,
|
|
43
54
|
width,
|
|
44
55
|
height,
|
|
45
56
|
view: canvas,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
backgroundAlpha: environment === 'server' ? 0 : 1
|
|
57
|
+
backgroundColor: solidBackground,
|
|
58
|
+
backgroundAlpha: solidBackground === 'transparent' ? 0 : 1
|
|
49
59
|
};
|
|
50
|
-
this.#
|
|
60
|
+
let options = this.#buildRendererOptions(baseOptions, environment, rendererSelection);
|
|
61
|
+
try {
|
|
62
|
+
this.#app = new PIXI.Application(options);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (!rendererSelection.forceCanvas) {
|
|
66
|
+
const fallbackReason = this.#buildInitFallbackReason(error);
|
|
67
|
+
this.#warnRendererFallback(fallbackReason);
|
|
68
|
+
rendererSelection = {
|
|
69
|
+
requestedMode: rendererSelection.requestedMode,
|
|
70
|
+
selectedMode: 'canvas',
|
|
71
|
+
forceCanvas: true,
|
|
72
|
+
fallbackOccurred: true,
|
|
73
|
+
fallbackReason
|
|
74
|
+
};
|
|
75
|
+
canvas.getContext('2d', { willReadFrequently: true });
|
|
76
|
+
options = this.#buildRendererOptions(baseOptions, environment, rendererSelection);
|
|
77
|
+
this.#app = new PIXI.Application(options);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!rendererSelection.forceCanvas) {
|
|
84
|
+
const actualRenderer = this.#detectRendererType();
|
|
85
|
+
if (actualRenderer !== 'webgl') {
|
|
86
|
+
const fallbackReason = 'Renderer initialized as canvas despite serverRendererMode="webgl"';
|
|
87
|
+
this.#warnRendererFallback(fallbackReason);
|
|
88
|
+
rendererSelection = {
|
|
89
|
+
requestedMode: rendererSelection.requestedMode,
|
|
90
|
+
selectedMode: 'canvas',
|
|
91
|
+
forceCanvas: true,
|
|
92
|
+
fallbackOccurred: true,
|
|
93
|
+
fallbackReason
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
rendererSelection = { ...rendererSelection, selectedMode: actualRenderer };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
51
100
|
if (scale !== 1) {
|
|
52
101
|
this.scale(scale);
|
|
53
102
|
}
|
|
54
103
|
// Stop the default ticker as we'll use GSAP's ticker
|
|
55
104
|
this.#app.ticker.stop();
|
|
105
|
+
this.deterministicMediaManager?.recordRendererSelection({
|
|
106
|
+
rendererType: rendererSelection.selectedMode,
|
|
107
|
+
fallbackOccurred: rendererSelection.fallbackOccurred,
|
|
108
|
+
fallbackReason: rendererSelection.fallbackReason
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
#buildRendererOptions(baseOptions, environment, selection) {
|
|
112
|
+
const options = {
|
|
113
|
+
...baseOptions,
|
|
114
|
+
forceCanvas: selection.forceCanvas
|
|
115
|
+
};
|
|
116
|
+
if (environment === 'server' && selection.selectedMode === 'webgl' && !selection.forceCanvas) {
|
|
117
|
+
this.#configureWebGLEnvironmentPreference();
|
|
118
|
+
options.preference = 'webgl';
|
|
119
|
+
options.powerPreference = this.powerPreference;
|
|
120
|
+
}
|
|
121
|
+
return options;
|
|
122
|
+
}
|
|
123
|
+
#resolveRendererSelection(environment) {
|
|
124
|
+
if (environment !== 'server') {
|
|
125
|
+
return {
|
|
126
|
+
requestedMode: 'canvas',
|
|
127
|
+
selectedMode: this.forceCanvas ? 'canvas' : 'webgl',
|
|
128
|
+
forceCanvas: this.forceCanvas,
|
|
129
|
+
fallbackOccurred: false
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const requestedMode = this.serverRendererMode;
|
|
133
|
+
if (this.forceCanvas) {
|
|
134
|
+
if (requestedMode === 'webgl') {
|
|
135
|
+
const fallbackReason = 'serverRendererMode="webgl" requested but forceCanvas=true override is enabled';
|
|
136
|
+
this.#warnRendererFallback(fallbackReason);
|
|
137
|
+
return {
|
|
138
|
+
requestedMode,
|
|
139
|
+
selectedMode: 'canvas',
|
|
140
|
+
forceCanvas: true,
|
|
141
|
+
fallbackOccurred: true,
|
|
142
|
+
fallbackReason
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
requestedMode,
|
|
147
|
+
selectedMode: 'canvas',
|
|
148
|
+
forceCanvas: true,
|
|
149
|
+
fallbackOccurred: false
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (requestedMode === 'canvas') {
|
|
153
|
+
return {
|
|
154
|
+
requestedMode,
|
|
155
|
+
selectedMode: 'canvas',
|
|
156
|
+
forceCanvas: true,
|
|
157
|
+
fallbackOccurred: false
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const support = this.#checkWebGLSupport();
|
|
161
|
+
if (!support.supported) {
|
|
162
|
+
this.#warnRendererFallback(support.reason);
|
|
163
|
+
return {
|
|
164
|
+
requestedMode,
|
|
165
|
+
selectedMode: 'canvas',
|
|
166
|
+
forceCanvas: true,
|
|
167
|
+
fallbackOccurred: true,
|
|
168
|
+
fallbackReason: support.reason
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
requestedMode,
|
|
173
|
+
selectedMode: 'webgl',
|
|
174
|
+
forceCanvas: false,
|
|
175
|
+
fallbackOccurred: false
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
#checkWebGLSupport() {
|
|
179
|
+
try {
|
|
180
|
+
const probe = this.dom?.canvas ??
|
|
181
|
+
(typeof document !== 'undefined' ? document.createElement('canvas') : null);
|
|
182
|
+
if (!probe || typeof probe.getContext !== 'function') {
|
|
183
|
+
return { supported: false, reason: 'WebGL unavailable: no canvas available for probing' };
|
|
184
|
+
}
|
|
185
|
+
if (this.preferWebGL2 && probe.getContext('webgl2')) {
|
|
186
|
+
return { supported: true, reason: '' };
|
|
187
|
+
}
|
|
188
|
+
if (probe.getContext('webgl') || probe.getContext('experimental-webgl')) {
|
|
189
|
+
return { supported: true, reason: '' };
|
|
190
|
+
}
|
|
191
|
+
return { supported: false, reason: 'WebGL unavailable: context creation failed' };
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return { supported: false, reason: 'WebGL unavailable: context probe failed' };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
#configureWebGLEnvironmentPreference() {
|
|
198
|
+
const pixiAny = PIXI;
|
|
199
|
+
if (!pixiAny.settings || !pixiAny.ENV) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (this.preferWebGL2 && pixiAny.ENV.WEBGL2 !== undefined) {
|
|
203
|
+
pixiAny.settings.PREFER_ENV = pixiAny.ENV.WEBGL2;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (pixiAny.ENV.WEBGL !== undefined) {
|
|
207
|
+
pixiAny.settings.PREFER_ENV = pixiAny.ENV.WEBGL;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
#detectRendererType() {
|
|
211
|
+
if (!this.#app) {
|
|
212
|
+
return 'canvas';
|
|
213
|
+
}
|
|
214
|
+
const renderer = this.#app.renderer;
|
|
215
|
+
if (renderer?.gl || renderer?.context?.gl || renderer?.context?.webGLVersion) {
|
|
216
|
+
return 'webgl';
|
|
217
|
+
}
|
|
218
|
+
return 'canvas';
|
|
219
|
+
}
|
|
220
|
+
#buildInitFallbackReason(error) {
|
|
221
|
+
if (error instanceof Error && error.message) {
|
|
222
|
+
return `WebGL initialization failed: ${error.message}`;
|
|
223
|
+
}
|
|
224
|
+
return 'WebGL initialization failed with unknown error';
|
|
225
|
+
}
|
|
226
|
+
#warnRendererFallback(reason) {
|
|
227
|
+
console.warn(`[AppManager] Falling back to canvas renderer. ${reason}`);
|
|
56
228
|
}
|
|
57
229
|
async extractBase64(target, format = 'png', quality = 1) {
|
|
58
230
|
if (!this.#app)
|
|
@@ -24,9 +24,9 @@ export declare class ComponentsManager implements ResourceManager<IComponent, Co
|
|
|
24
24
|
private isVisible;
|
|
25
25
|
getAll(): IComponent[];
|
|
26
26
|
getMediaComponents(): (Component & MediaComponent)[];
|
|
27
|
-
update(componentId: string, data: Partial<AppearanceInput>, refreshType?: ComponentRefreshType):
|
|
27
|
+
update(componentId: string, data: Partial<AppearanceInput>, refreshType?: ComponentRefreshType): void;
|
|
28
28
|
get(componentId: string): IComponent | undefined;
|
|
29
|
-
delete(componentId: string): void
|
|
29
|
+
delete(componentId: string): Promise<void>;
|
|
30
30
|
create(componentData: ComponentData): Promise<IComponent | null>;
|
|
31
31
|
setOrder(id: string, order: number): void;
|
|
32
32
|
moveUp(id: string): void;
|
|
@@ -39,11 +39,11 @@ export declare class ComponentsManager implements ResourceManager<IComponent, Co
|
|
|
39
39
|
id: string;
|
|
40
40
|
data: Partial<AppearanceInput>;
|
|
41
41
|
}[]): void;
|
|
42
|
-
bulkDelete(ids: string[]): void
|
|
43
|
-
hide(id: string):
|
|
44
|
-
show(id: string):
|
|
45
|
-
toggle(id: string):
|
|
42
|
+
bulkDelete(ids: string[]): Promise<void>;
|
|
43
|
+
hide(id: string): void;
|
|
44
|
+
show(id: string): void;
|
|
45
|
+
toggle(id: string): void;
|
|
46
46
|
filter(predicate: (component: IComponent) => boolean): IComponent[];
|
|
47
47
|
isComponentVisible(componentId: string): boolean;
|
|
48
|
-
destroy(): void
|
|
48
|
+
destroy(): Promise<void>;
|
|
49
49
|
}
|
|
@@ -6,6 +6,7 @@ import { PixiComponentBuilder } from '../builders/PixiComponentBuilder.js';
|
|
|
6
6
|
import { ComponentState } from '../builders/_ComponentState.svelte.js';
|
|
7
7
|
import { asClass, asValue } from 'awilix/browser';
|
|
8
8
|
import { debounce } from 'lodash-es';
|
|
9
|
+
import { isTimeWithinTimeline } from '../utils/timelineWindow.js';
|
|
9
10
|
// import { builtTextComponentForSubtitle } from '../utils/subtitles.js';
|
|
10
11
|
export class ComponentsManager {
|
|
11
12
|
components = $state(new Map());
|
|
@@ -28,7 +29,7 @@ export class ComponentsManager {
|
|
|
28
29
|
this.initializeEventListeners();
|
|
29
30
|
}
|
|
30
31
|
initializeEventListeners() {
|
|
31
|
-
this.eventManager.on('subtitleschange', this.debouncedRefreshSubtitles
|
|
32
|
+
this.eventManager.on('subtitleschange', this.debouncedRefreshSubtitles);
|
|
32
33
|
}
|
|
33
34
|
#scheduleSubtitleRefresh() {
|
|
34
35
|
// Schedule the refresh on the next animation frame to avoid blocking
|
|
@@ -43,7 +44,7 @@ export class ComponentsManager {
|
|
|
43
44
|
const { currentTime, duration } = this.state;
|
|
44
45
|
const startAt = component.props.timeline.startAt || 0;
|
|
45
46
|
const endAt = component.props.timeline.endAt || duration;
|
|
46
|
-
return currentTime
|
|
47
|
+
return isTimeWithinTimeline(currentTime, startAt, endAt);
|
|
47
48
|
}
|
|
48
49
|
getAll() {
|
|
49
50
|
return Array.from(this.components.values());
|
|
@@ -57,10 +58,10 @@ export class ComponentsManager {
|
|
|
57
58
|
component.refresh(refreshType);
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
+
update(componentId, data, refreshType = 'refresh') {
|
|
61
62
|
const component = this.get(componentId);
|
|
62
63
|
if (component) {
|
|
63
|
-
|
|
64
|
+
component.props.updateAppearance(data);
|
|
64
65
|
// Only manually refresh if auto-refresh is not enabled
|
|
65
66
|
if (!component.autoRefresh) {
|
|
66
67
|
this.#refreshComponent(component, refreshType);
|
|
@@ -70,7 +71,7 @@ export class ComponentsManager {
|
|
|
70
71
|
get(componentId) {
|
|
71
72
|
return this.components.get(componentId);
|
|
72
73
|
}
|
|
73
|
-
delete(componentId) {
|
|
74
|
+
async delete(componentId) {
|
|
74
75
|
const component = this.components.get(componentId);
|
|
75
76
|
if (component) {
|
|
76
77
|
const layers = this.layersManager.getAll();
|
|
@@ -82,8 +83,8 @@ export class ComponentsManager {
|
|
|
82
83
|
this.layersManager.delete(componentLayer.id);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
|
-
component.destroy();
|
|
86
86
|
this.components.delete(componentId);
|
|
87
|
+
await component.destroy();
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
async #buildComponent(componentData) {
|
|
@@ -236,25 +237,30 @@ export class ComponentsManager {
|
|
|
236
237
|
bulkUpdate(updates) {
|
|
237
238
|
updates.forEach(({ id, data }) => this.update(id, data));
|
|
238
239
|
}
|
|
239
|
-
bulkDelete(ids) {
|
|
240
|
-
ids.
|
|
240
|
+
async bulkDelete(ids) {
|
|
241
|
+
const results = await Promise.allSettled(ids.map((id) => this.delete(id)));
|
|
242
|
+
const errors = results
|
|
243
|
+
.filter((result) => result.status === 'rejected')
|
|
244
|
+
.map((result) => result.reason);
|
|
245
|
+
if (errors.length > 0)
|
|
246
|
+
throw new AggregateError(errors, 'Component deletion failed.');
|
|
241
247
|
}
|
|
242
|
-
|
|
248
|
+
hide(id) {
|
|
243
249
|
const component = this.get(id);
|
|
244
250
|
if (component) {
|
|
245
|
-
|
|
251
|
+
component.props.setVisible(false);
|
|
246
252
|
}
|
|
247
253
|
}
|
|
248
|
-
|
|
254
|
+
show(id) {
|
|
249
255
|
const component = this.get(id);
|
|
250
256
|
if (component) {
|
|
251
|
-
|
|
257
|
+
component.props.setVisible(true);
|
|
252
258
|
}
|
|
253
259
|
}
|
|
254
|
-
|
|
260
|
+
toggle(id) {
|
|
255
261
|
const component = this.get(id);
|
|
256
262
|
if (component) {
|
|
257
|
-
|
|
263
|
+
component.props.setVisible(!component.props.visible);
|
|
258
264
|
}
|
|
259
265
|
}
|
|
260
266
|
filter(predicate) {
|
|
@@ -267,8 +273,16 @@ export class ComponentsManager {
|
|
|
267
273
|
}
|
|
268
274
|
return this.isVisible(component);
|
|
269
275
|
}
|
|
270
|
-
destroy() {
|
|
276
|
+
async destroy() {
|
|
271
277
|
this.eventManager.removeEventListener('subtitleschange', this.debouncedRefreshSubtitles);
|
|
272
278
|
this.debouncedRefreshSubtitles.cancel(); // Cancel any pending debounced calls
|
|
279
|
+
const components = [...this.components.values()];
|
|
280
|
+
this.components.clear();
|
|
281
|
+
const results = await Promise.allSettled(components.map((component) => component.destroy()));
|
|
282
|
+
const errors = results
|
|
283
|
+
.filter((result) => result.status === 'rejected')
|
|
284
|
+
.map((result) => result.reason);
|
|
285
|
+
if (errors.length > 0)
|
|
286
|
+
throw new AggregateError(errors, 'Component teardown failed.');
|
|
273
287
|
}
|
|
274
288
|
}
|
|
@@ -16,6 +16,12 @@ export declare class DeterministicMediaManager {
|
|
|
16
16
|
getFingerprint(): string;
|
|
17
17
|
setOneTimeOverride(componentId: string, frameIndex: number, payload: DeterministicFramePayload): void;
|
|
18
18
|
getDiagnosticsReport(): DeterministicDiagnosticsReport | null;
|
|
19
|
+
getSelectedRendererType(): 'canvas' | 'webgl';
|
|
20
|
+
recordRendererSelection(args: {
|
|
21
|
+
rendererType: 'canvas' | 'webgl';
|
|
22
|
+
fallbackOccurred?: boolean;
|
|
23
|
+
fallbackReason?: string;
|
|
24
|
+
}): void;
|
|
19
25
|
recordReadyAttempt(sceneFrameIndex: number, count?: number): void;
|
|
20
26
|
recordExtraRenderPass(sceneFrameIndex: number, count?: number): void;
|
|
21
27
|
recordBlurRedraw(sceneFrameIndex: number, count?: number): void;
|
|
@@ -4,6 +4,9 @@ const createDefaultDiagnosticsState = () => ({
|
|
|
4
4
|
providerHits: 0,
|
|
5
5
|
providerMisses: 0,
|
|
6
6
|
cacheHits: 0,
|
|
7
|
+
selectedRendererType: 'canvas',
|
|
8
|
+
rendererFallbackOccurred: false,
|
|
9
|
+
rendererFallbackReason: undefined,
|
|
7
10
|
readyAttempts: 0,
|
|
8
11
|
extraRenderPasses: 0,
|
|
9
12
|
blurRedraws: 0,
|
|
@@ -13,6 +16,7 @@ const createDefaultDiagnosticsState = () => ({
|
|
|
13
16
|
latencyMinMs: Number.POSITIVE_INFINITY,
|
|
14
17
|
latencyMaxMs: 0
|
|
15
18
|
});
|
|
19
|
+
const IMAGE_LOAD_TIMEOUT_MS = 5000;
|
|
16
20
|
export class DeterministicMediaManager {
|
|
17
21
|
config;
|
|
18
22
|
#provider;
|
|
@@ -144,6 +148,9 @@ export class DeterministicMediaManager {
|
|
|
144
148
|
providerMisses: this.#diagnostics.providerMisses,
|
|
145
149
|
cacheHits: this.#diagnostics.cacheHits,
|
|
146
150
|
cacheHitRatio,
|
|
151
|
+
selectedRendererType: this.#diagnostics.selectedRendererType,
|
|
152
|
+
rendererFallbackOccurred: this.#diagnostics.rendererFallbackOccurred,
|
|
153
|
+
rendererFallbackReason: this.#diagnostics.rendererFallbackReason,
|
|
147
154
|
readyAttempts: this.#diagnostics.readyAttempts,
|
|
148
155
|
extraRenderPasses: this.#diagnostics.extraRenderPasses,
|
|
149
156
|
blurRedraws: this.#diagnostics.blurRedraws,
|
|
@@ -155,6 +162,19 @@ export class DeterministicMediaManager {
|
|
|
155
162
|
}
|
|
156
163
|
};
|
|
157
164
|
}
|
|
165
|
+
getSelectedRendererType() {
|
|
166
|
+
return this.#diagnostics.selectedRendererType;
|
|
167
|
+
}
|
|
168
|
+
recordRendererSelection(args) {
|
|
169
|
+
try {
|
|
170
|
+
this.#diagnostics.selectedRendererType = args.rendererType;
|
|
171
|
+
this.#diagnostics.rendererFallbackOccurred = Boolean(args.fallbackOccurred);
|
|
172
|
+
this.#diagnostics.rendererFallbackReason = args.fallbackReason;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// Diagnostics are best-effort and never allowed to fail the render path.
|
|
176
|
+
}
|
|
177
|
+
}
|
|
158
178
|
recordReadyAttempt(sceneFrameIndex, count = 1) {
|
|
159
179
|
this.#recordRuntimeCounter(sceneFrameIndex, 'readyAttempts', count);
|
|
160
180
|
}
|
|
@@ -300,10 +320,44 @@ export class DeterministicMediaManager {
|
|
|
300
320
|
async #loadImage(url) {
|
|
301
321
|
const img = new Image();
|
|
302
322
|
img.crossOrigin = 'anonymous';
|
|
303
|
-
img.src = url;
|
|
304
323
|
await new Promise((resolve, reject) => {
|
|
305
|
-
|
|
306
|
-
|
|
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;
|
|
307
361
|
});
|
|
308
362
|
return img;
|
|
309
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
|
-
|
|
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>;
|