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
|
@@ -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
|
|
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.
|
|
24
|
-
this.eventManager.on('timeupdate', this.
|
|
25
|
-
this.eventManager.on('rerender', this.
|
|
26
|
-
this.eventManager.on('changestate',
|
|
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
|
|
79
|
+
const isVisibleByTime = isTimeWithinTimeline(currentTime, startAt, endAt);
|
|
52
80
|
const isExplicitlyVisible = component.props.visible !== false;
|
|
53
|
-
const shouldBeVisible =
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
//
|
|
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.
|
|
71
|
-
this.eventManager.removeEventListener('timeupdate', this.
|
|
72
|
-
this.eventManager.removeEventListener('rerender', this.
|
|
73
|
-
this.eventManager.removeEventListener('changestate', this.
|
|
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
|
-
|
|
39
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
108
|
-
const end =
|
|
109
|
-
const
|
|
110
|
-
const duration =
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
152
|
+
else {
|
|
156
153
|
mergedZones.push(currentZone);
|
|
154
|
+
currentZone = { ...zone };
|
|
157
155
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
let
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
return frameAdjustedTime > this.duration ? this.duration : frameAdjustedTime;
|
|
19
|
+
return frameTime > this.duration ? this.duration : frameTime;
|
|
11
20
|
}
|
|
12
|
-
return
|
|
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
|
|
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):
|
|
117
|
-
updateAppearance(appearance: Partial<AppearanceInput>):
|
|
118
|
-
setVisible(visible: boolean):
|
|
119
|
-
setOrder(order: number):
|
|
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>):
|
|
162
|
-
updateText(text: string):
|
|
163
|
-
setText(text: string):
|
|
164
|
-
setVisible(visible: boolean):
|
|
165
|
-
setOrder(order: number):
|
|
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:
|
|
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>;
|