visualfries 0.1.0
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/LICENSE +21 -0
- package/README.md +213 -0
- package/dist/DIContainer.d.ts +4 -0
- package/dist/DIContainer.js +145 -0
- package/dist/SceneBuilder.svelte.d.ts +8574 -0
- package/dist/SceneBuilder.svelte.js +409 -0
- package/dist/adapters/subtitleHelpers.d.ts +2 -0
- package/dist/adapters/subtitleHelpers.js +187 -0
- package/dist/animations/AnimationContext.d.ts +17 -0
- package/dist/animations/AnimationContext.js +72 -0
- package/dist/animations/AnimationPresetsRegister.d.ts +362 -0
- package/dist/animations/AnimationPresetsRegister.js +20 -0
- package/dist/animations/AnimationSetup.d.ts +8 -0
- package/dist/animations/AnimationSetup.js +30 -0
- package/dist/animations/SplitTextCache.d.ts +28 -0
- package/dist/animations/SplitTextCache.js +68 -0
- package/dist/animations/animationBuilder.d.ts +31 -0
- package/dist/animations/animationBuilder.js +255 -0
- package/dist/animations/animationPreset.d.ts +7 -0
- package/dist/animations/animationPreset.js +31 -0
- package/dist/animations/builders/AnimationPresetFactory.d.ts +43 -0
- package/dist/animations/builders/AnimationPresetFactory.js +139 -0
- package/dist/animations/builders/LineHighlighterAnimationBuilder.d.ts +16 -0
- package/dist/animations/builders/LineHighlighterAnimationBuilder.js +183 -0
- package/dist/animations/builders/WordHighlighterAnimationBuilder.d.ts +15 -0
- package/dist/animations/builders/WordHighlighterAnimationBuilder.js +180 -0
- package/dist/animations/engines/AnimationEngineAdaptor.d.ts +107 -0
- package/dist/animations/engines/AnimationEngineAdaptor.js +1 -0
- package/dist/animations/engines/GSAPEngineAdaptor.d.ts +21 -0
- package/dist/animations/engines/GSAPEngineAdaptor.js +145 -0
- package/dist/animations/presets/index.d.ts +2 -0
- package/dist/animations/presets/index.js +3 -0
- package/dist/animations/presets/lines.d.ts +52 -0
- package/dist/animations/presets/lines.js +547 -0
- package/dist/animations/presets/words.d.ts +31 -0
- package/dist/animations/presets/words.js +268 -0
- package/dist/animations/transformers/AnimationReferenceTransformer.d.ts +9 -0
- package/dist/animations/transformers/AnimationReferenceTransformer.js +114 -0
- package/dist/builders/PixiComponentBuilder.d.ts +63 -0
- package/dist/builders/PixiComponentBuilder.js +112 -0
- package/dist/builders/_ComponentState.svelte.d.ts +795 -0
- package/dist/builders/_ComponentState.svelte.js +203 -0
- package/dist/builders/html/HtmlBuilder.d.ts +66 -0
- package/dist/builders/html/HtmlBuilder.js +171 -0
- package/dist/builders/html/HtmlBuilderFactory.d.ts +27 -0
- package/dist/builders/html/HtmlBuilderFactory.js +30 -0
- package/dist/builders/html/StyleBuilder.d.ts +13 -0
- package/dist/builders/html/StyleBuilder.js +133 -0
- package/dist/builders/html/StyleProcessor.d.ts +9 -0
- package/dist/builders/html/StyleProcessor.js +1 -0
- package/dist/builders/html/TextComponentHtmlBuilder.d.ts +16 -0
- package/dist/builders/html/TextComponentHtmlBuilder.js +93 -0
- package/dist/builders/html/TextShadowBuilder.d.ts +60 -0
- package/dist/builders/html/TextShadowBuilder.js +227 -0
- package/dist/builders/html/processors/AppearanceStyleProcessor.d.ts +5 -0
- package/dist/builders/html/processors/AppearanceStyleProcessor.js +57 -0
- package/dist/builders/html/processors/TextAppearanceStyleProcessor.d.ts +5 -0
- package/dist/builders/html/processors/TextAppearanceStyleProcessor.js +37 -0
- package/dist/builders/html/processors/TextEffectsStyleProcessor.d.ts +6 -0
- package/dist/builders/html/processors/TextEffectsStyleProcessor.js +68 -0
- package/dist/commands/Command.d.ts +6 -0
- package/dist/commands/Command.js +1 -0
- package/dist/commands/CommandRunner.d.ts +28 -0
- package/dist/commands/CommandRunner.js +81 -0
- package/dist/commands/CommandTypes.d.ts +11 -0
- package/dist/commands/CommandTypes.js +13 -0
- package/dist/commands/PauseCommand.d.ts +4 -0
- package/dist/commands/PauseCommand.js +5 -0
- package/dist/commands/PlayCommand.d.ts +4 -0
- package/dist/commands/PlayCommand.js +6 -0
- package/dist/commands/RenderCommand.d.ts +15 -0
- package/dist/commands/RenderCommand.js +18 -0
- package/dist/commands/RenderFrameCommand.d.ts +17 -0
- package/dist/commands/RenderFrameCommand.js +93 -0
- package/dist/commands/ReplaceSourceOnTimeCommand.d.ts +4 -0
- package/dist/commands/ReplaceSourceOnTimeCommand.js +22 -0
- package/dist/commands/SeekCommand.d.ts +15 -0
- package/dist/commands/SeekCommand.js +39 -0
- package/dist/commands/UpdateComponentCommand.d.ts +4 -0
- package/dist/commands/UpdateComponentCommand.js +17 -0
- package/dist/components/AnimatedGIF.d.ts +201 -0
- package/dist/components/AnimatedGIF.js +391 -0
- package/dist/components/Component.svelte.d.ts +33 -0
- package/dist/components/Component.svelte.js +152 -0
- package/dist/components/ComponentContext.svelte.d.ts +33 -0
- package/dist/components/ComponentContext.svelte.js +105 -0
- package/dist/components/hooks/AnimationHook.d.ts +25 -0
- package/dist/components/hooks/AnimationHook.js +180 -0
- package/dist/components/hooks/CanvasShapeHook.d.ts +12 -0
- package/dist/components/hooks/CanvasShapeHook.js +229 -0
- package/dist/components/hooks/HtmlAnimationHook.d.ts +8 -0
- package/dist/components/hooks/HtmlAnimationHook.js +70 -0
- package/dist/components/hooks/HtmlTextHook.d.ts +16 -0
- package/dist/components/hooks/HtmlTextHook.js +102 -0
- package/dist/components/hooks/HtmlToCanvasHook.d.ts +16 -0
- package/dist/components/hooks/HtmlToCanvasHook.js +148 -0
- package/dist/components/hooks/ImageHook.d.ts +10 -0
- package/dist/components/hooks/ImageHook.js +45 -0
- package/dist/components/hooks/MediaHook.d.ts +15 -0
- package/dist/components/hooks/MediaHook.js +252 -0
- package/dist/components/hooks/MediaSeekingHook.d.ts +12 -0
- package/dist/components/hooks/MediaSeekingHook.js +204 -0
- package/dist/components/hooks/PixiDisplayObjectHook.d.ts +15 -0
- package/dist/components/hooks/PixiDisplayObjectHook.js +77 -0
- package/dist/components/hooks/PixiGifHook.d.ts +15 -0
- package/dist/components/hooks/PixiGifHook.js +97 -0
- package/dist/components/hooks/PixiProgressShapeHook.d.ts +12 -0
- package/dist/components/hooks/PixiProgressShapeHook.js +128 -0
- package/dist/components/hooks/PixiSplitScreenDisplayObjectHook.d.ts +21 -0
- package/dist/components/hooks/PixiSplitScreenDisplayObjectHook.js +210 -0
- package/dist/components/hooks/PixiTextureHook.d.ts +7 -0
- package/dist/components/hooks/PixiTextureHook.js +29 -0
- package/dist/components/hooks/PixiVideoTextureHook.d.ts +10 -0
- package/dist/components/hooks/PixiVideoTextureHook.js +35 -0
- package/dist/components/hooks/SubtitlesHook.d.ts +88 -0
- package/dist/components/hooks/SubtitlesHook.js +199 -0
- package/dist/components/hooks/VerifyGifHook.d.ts +7 -0
- package/dist/components/hooks/VerifyGifHook.js +27 -0
- package/dist/components/hooks/VerifyImageHook.d.ts +7 -0
- package/dist/components/hooks/VerifyImageHook.js +27 -0
- package/dist/components/hooks/VerifyMediaHook.d.ts +7 -0
- package/dist/components/hooks/VerifyMediaHook.js +21 -0
- package/dist/components/hooks/shapes/progress/CustomProgressRenderer.d.ts +8 -0
- package/dist/components/hooks/shapes/progress/CustomProgressRenderer.js +53 -0
- package/dist/components/hooks/shapes/progress/DoubleProgressRenderer.d.ts +8 -0
- package/dist/components/hooks/shapes/progress/DoubleProgressRenderer.js +69 -0
- package/dist/components/hooks/shapes/progress/LinearProgressRenderer.d.ts +8 -0
- package/dist/components/hooks/shapes/progress/LinearProgressRenderer.js +60 -0
- package/dist/components/hooks/shapes/progress/PerimeterProgressRenderer.d.ts +9 -0
- package/dist/components/hooks/shapes/progress/PerimeterProgressRenderer.js +213 -0
- package/dist/components/hooks/shapes/progress/ProgressRenderer.d.ts +17 -0
- package/dist/components/hooks/shapes/progress/ProgressRenderer.js +75 -0
- package/dist/components/hooks/shapes/progress/RadialProgressRenderer.d.ts +8 -0
- package/dist/components/hooks/shapes/progress/RadialProgressRenderer.js +50 -0
- package/dist/components/hooks/shapes/progress/index.d.ts +6 -0
- package/dist/components/hooks/shapes/progress/index.js +6 -0
- package/dist/composers/componentComposer.d.ts +55 -0
- package/dist/composers/componentComposer.js +118 -0
- package/dist/composers/layerComposer.d.ts +46 -0
- package/dist/composers/layerComposer.js +79 -0
- package/dist/composers/sceneComposer.d.ts +48 -0
- package/dist/composers/sceneComposer.js +92 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +14 -0
- package/dist/directors/ComponentDirector.d.ts +20 -0
- package/dist/directors/ComponentDirector.js +86 -0
- package/dist/factories/SceneBuilderFactory.d.ts +15 -0
- package/dist/factories/SceneBuilderFactory.js +51 -0
- package/dist/fonts/GoogleFontsProvider.d.ts +12 -0
- package/dist/fonts/GoogleFontsProvider.js +125 -0
- package/dist/fonts/fontLoader.d.ts +15 -0
- package/dist/fonts/fontLoader.js +41 -0
- package/dist/fonts/types.d.ts +1 -0
- package/dist/fonts/types.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +14 -0
- package/dist/layers/Layer.svelte.d.ts +8492 -0
- package/dist/layers/Layer.svelte.js +125 -0
- package/dist/managers/AppManager.svelte.d.ts +23 -0
- package/dist/managers/AppManager.svelte.js +89 -0
- package/dist/managers/ComponentsManager.svelte.d.ts +49 -0
- package/dist/managers/ComponentsManager.svelte.js +247 -0
- package/dist/managers/DomManager.d.ts +18 -0
- package/dist/managers/DomManager.js +73 -0
- package/dist/managers/EventManager.d.ts +7 -0
- package/dist/managers/EventManager.js +22 -0
- package/dist/managers/LayersManager.svelte.d.ts +8499 -0
- package/dist/managers/LayersManager.svelte.js +176 -0
- package/dist/managers/MediaManager.d.ts +32 -0
- package/dist/managers/MediaManager.js +243 -0
- package/dist/managers/RenderManager.d.ts +23 -0
- package/dist/managers/RenderManager.js +59 -0
- package/dist/managers/StateManager.svelte.d.ts +8746 -0
- package/dist/managers/StateManager.svelte.js +272 -0
- package/dist/managers/SubtitlesManager.svelte.d.ts +261 -0
- package/dist/managers/SubtitlesManager.svelte.js +1385 -0
- package/dist/managers/TimeManager.svelte.d.ts +6 -0
- package/dist/managers/TimeManager.svelte.js +18 -0
- package/dist/managers/TimelineManager.svelte.d.ts +25 -0
- package/dist/managers/TimelineManager.svelte.js +152 -0
- package/dist/registers.d.ts +12 -0
- package/dist/registers.js +29 -0
- package/dist/schemas/runtime/index.d.ts +3 -0
- package/dist/schemas/runtime/index.js +4 -0
- package/dist/schemas/runtime/types.d.ts +323 -0
- package/dist/schemas/runtime/types.js +12 -0
- package/dist/schemas/scene/animations.d.ts +89738 -0
- package/dist/schemas/scene/animations.js +211 -0
- package/dist/schemas/scene/components.js +515 -0
- package/dist/schemas/scene/core.js +160 -0
- package/dist/schemas/scene/index.d.ts +22 -0
- package/dist/schemas/scene/index.js +10 -0
- package/dist/schemas/scene/properties.d.ts +914 -0
- package/dist/schemas/scene/properties.js +398 -0
- package/dist/schemas/scene/subtitles.d.ts +1141 -0
- package/dist/schemas/scene/subtitles.js +111 -0
- package/dist/schemas/scene/utils.d.ts +1 -0
- package/dist/schemas/scene/utils.js +5 -0
- package/dist/seeds/SeedFactory.d.ts +59 -0
- package/dist/seeds/SeedFactory.js +99 -0
- package/dist/seeds/index.d.ts +8 -0
- package/dist/seeds/index.js +8 -0
- package/dist/transformers/ColorTransformer.d.ts +5 -0
- package/dist/transformers/ColorTransformer.js +67 -0
- package/dist/transformers/PixiColorTransformer.d.ts +22 -0
- package/dist/transformers/PixiColorTransformer.js +104 -0
- package/dist/utils/canvas.d.ts +6 -0
- package/dist/utils/canvas.js +18 -0
- package/dist/utils/document.d.ts +2 -0
- package/dist/utils/document.js +36 -0
- package/dist/utils/emoji.d.ts +10 -0
- package/dist/utils/emoji.js +51 -0
- package/dist/utils/html.d.ts +4 -0
- package/dist/utils/html.js +45 -0
- package/dist/utils/svgGenerator.d.ts +20 -0
- package/dist/utils/svgGenerator.js +103 -0
- package/dist/utils/utils.d.ts +5 -0
- package/dist/utils/utils.js +125 -0
- package/package.json +96 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { HtmlBuilderFactory } from '../../builders/html/HtmlBuilderFactory.js';
|
|
2
|
+
export class HtmlTextHook {
|
|
3
|
+
#handlers = {
|
|
4
|
+
setup: this.#handleSetup.bind(this),
|
|
5
|
+
update: this.#handleUpdate.bind(this),
|
|
6
|
+
destroy: this.#handleDestroy.bind(this),
|
|
7
|
+
refresh: this.#handleRefresh.bind(this),
|
|
8
|
+
'refresh:content': this.#handleRefresh.bind(this),
|
|
9
|
+
'refresh:config': this.#handleRefresh.bind(this)
|
|
10
|
+
};
|
|
11
|
+
types = ['setup', 'update', 'destroy', 'refresh'];
|
|
12
|
+
priority = 1;
|
|
13
|
+
#currentId = undefined;
|
|
14
|
+
#context;
|
|
15
|
+
#htmlEl = undefined;
|
|
16
|
+
#wrapperEl = undefined;
|
|
17
|
+
domManager;
|
|
18
|
+
componentsManager;
|
|
19
|
+
constructor(cradle) {
|
|
20
|
+
this.domManager = cradle.domManager;
|
|
21
|
+
this.componentsManager = cradle.componentsManager;
|
|
22
|
+
}
|
|
23
|
+
buildHtmlElements() {
|
|
24
|
+
const componentData = this.#context.contextData;
|
|
25
|
+
if (componentData.type !== 'TEXT')
|
|
26
|
+
return { wrapper: undefined, element: undefined };
|
|
27
|
+
try {
|
|
28
|
+
// Use the factory to create an appropriate builder
|
|
29
|
+
const builder = HtmlBuilderFactory.createBuilder(componentData, document);
|
|
30
|
+
// Build the HTML elements
|
|
31
|
+
const { wrapper, element } = builder.build();
|
|
32
|
+
return { wrapper, element };
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
return { wrapper: undefined, element: undefined };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async #handleSetup() {
|
|
39
|
+
if (this.#htmlEl) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.#currentId = this.#context.contextData.id;
|
|
43
|
+
const comp = this.componentsManager.get(this.#context.contextData.id);
|
|
44
|
+
if (this.#context.type === 'TEXT' && comp) {
|
|
45
|
+
this.#context.updateContextData(comp.props.getData());
|
|
46
|
+
}
|
|
47
|
+
const { wrapper, element } = this.buildHtmlElements();
|
|
48
|
+
if (!element || !wrapper) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.#wrapperEl = wrapper;
|
|
52
|
+
this.#htmlEl = element;
|
|
53
|
+
// Store references in the context for other hooks
|
|
54
|
+
this.#context.setResource('wrapperHtmlEl', this.#wrapperEl);
|
|
55
|
+
this.#context.setResource('htmlEl', this.#htmlEl);
|
|
56
|
+
// for texts we need to target this, but if we animate scale, position etc. we should target parent
|
|
57
|
+
this.#context.setResource('animationTarget', this.#htmlEl);
|
|
58
|
+
// Add to DOM if not already present
|
|
59
|
+
const hasEl = this.domManager.htmlContainer.querySelector('#' + this.#wrapperEl.id);
|
|
60
|
+
if (!hasEl) {
|
|
61
|
+
this.domManager.htmlContainer.appendChild(this.#wrapperEl);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async #handleUpdate() {
|
|
65
|
+
if (this.#currentId !== this.#context.contextData.id) {
|
|
66
|
+
await this.#handleRefresh();
|
|
67
|
+
}
|
|
68
|
+
const isActive = this.#context.isActive;
|
|
69
|
+
if (this.#wrapperEl) {
|
|
70
|
+
if (isActive) {
|
|
71
|
+
this.#wrapperEl.style.display = 'flex';
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this.#wrapperEl.style.display = 'none';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (this.#context.disabled != isActive) {
|
|
78
|
+
this.#context.disabled = isActive !== true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async #handleRefresh() {
|
|
82
|
+
await this.#handleDestroy();
|
|
83
|
+
await this.#handleSetup();
|
|
84
|
+
}
|
|
85
|
+
async #handleDestroy() {
|
|
86
|
+
this.#wrapperEl?.remove();
|
|
87
|
+
this.#wrapperEl = undefined;
|
|
88
|
+
this.#htmlEl = undefined;
|
|
89
|
+
this.#context.removeResource('wrapperHtmlEl');
|
|
90
|
+
this.#context.removeResource('htmlEl');
|
|
91
|
+
}
|
|
92
|
+
async handle(type, context) {
|
|
93
|
+
this.#context = context;
|
|
94
|
+
// if (this.#context.disabled) {
|
|
95
|
+
// return;
|
|
96
|
+
// }
|
|
97
|
+
const handler = this.#handlers[type];
|
|
98
|
+
if (handler) {
|
|
99
|
+
await handler();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IComponentContext, IComponentHook, HookType } from '../..';
|
|
2
|
+
import { StateManager } from '../../managers/StateManager.svelte.js';
|
|
3
|
+
export declare class HtmlToCanvasHook implements IComponentHook {
|
|
4
|
+
#private;
|
|
5
|
+
shouldCreateObjectURL: boolean;
|
|
6
|
+
private svgBase;
|
|
7
|
+
private svgEnd;
|
|
8
|
+
private svg;
|
|
9
|
+
types: HookType[];
|
|
10
|
+
priority: number;
|
|
11
|
+
private state;
|
|
12
|
+
constructor(cradle: {
|
|
13
|
+
stateManager: StateManager;
|
|
14
|
+
});
|
|
15
|
+
handle(type: HookType, context: IComponentContext): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Texture, BaseTexture, Sprite, Container } from 'pixi.js-legacy';
|
|
2
|
+
import { StateManager } from '../../managers/StateManager.svelte.js';
|
|
3
|
+
import { svgGenerator } from '../../utils/svgGenerator.js';
|
|
4
|
+
export class HtmlToCanvasHook {
|
|
5
|
+
#handlers = {
|
|
6
|
+
setup: this.#handleSetup.bind(this),
|
|
7
|
+
destroy: this.#handleDestroy.bind(this),
|
|
8
|
+
update: this.#handleUpdate.bind(this),
|
|
9
|
+
'refresh:content': this.#handleRefresh.bind(this)
|
|
10
|
+
};
|
|
11
|
+
#context;
|
|
12
|
+
#htmlEl = undefined;
|
|
13
|
+
#currentId = undefined;
|
|
14
|
+
shouldCreateObjectURL;
|
|
15
|
+
#displayObject;
|
|
16
|
+
#sprite;
|
|
17
|
+
#lastHtml = undefined;
|
|
18
|
+
svgBase = null;
|
|
19
|
+
svgEnd = null;
|
|
20
|
+
svg = null;
|
|
21
|
+
types = Object.keys(this.#handlers);
|
|
22
|
+
priority = 1;
|
|
23
|
+
state;
|
|
24
|
+
constructor(cradle) {
|
|
25
|
+
this.state = cradle.stateManager;
|
|
26
|
+
this.shouldCreateObjectURL = navigator.userAgent.includes('Firefox');
|
|
27
|
+
}
|
|
28
|
+
async #svgToTexture(svgString) {
|
|
29
|
+
// seems fastest
|
|
30
|
+
const { width, height } = this.state;
|
|
31
|
+
let imgPath = '';
|
|
32
|
+
if (this.shouldCreateObjectURL) {
|
|
33
|
+
const svgBlob = new Blob([svgString], {
|
|
34
|
+
type: 'image/svg+xml'
|
|
35
|
+
});
|
|
36
|
+
imgPath = URL.createObjectURL(svgBlob);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
imgPath = `data:image/svg+xml;charset=utf8,${encodeURIComponent(svgString)}`;
|
|
40
|
+
}
|
|
41
|
+
// const svgBlobArrayBuffer = new Uint8Array(await svgBlob.arrayBuffer());
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const img = new Image(width, height);
|
|
44
|
+
img.crossOrigin = 'anonymous';
|
|
45
|
+
img.src = imgPath;
|
|
46
|
+
img.width = width;
|
|
47
|
+
img.height = height;
|
|
48
|
+
img.onload = () => {
|
|
49
|
+
const base = new BaseTexture(img);
|
|
50
|
+
const texture = new Texture(base);
|
|
51
|
+
// const texture = PIXI.Texture.from(canvas); // true
|
|
52
|
+
img.remove();
|
|
53
|
+
if (this.shouldCreateObjectURL) {
|
|
54
|
+
URL.revokeObjectURL(imgPath);
|
|
55
|
+
}
|
|
56
|
+
resolve(texture);
|
|
57
|
+
};
|
|
58
|
+
img.onerror = () => {
|
|
59
|
+
console.warn('img.onerror', svgString);
|
|
60
|
+
reject(new Error('Failed to load SVG'));
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async #renderSvg() {
|
|
65
|
+
if (!this.#htmlEl) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const { width, height } = this.state;
|
|
69
|
+
if (!this.svgBase) {
|
|
70
|
+
const { base, content, end } = await svgGenerator.generateSVG(this.#htmlEl, this.#context.data.appearance.text, width, height, 'svg-' + this.#context.contextData.id, encodeURIComponent(this.state.getCharactersList().join('')));
|
|
71
|
+
this.svgBase = base;
|
|
72
|
+
this.svgEnd = end;
|
|
73
|
+
this.svg = base + content + end;
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const html = this.#htmlEl.outerHTML.replace(/<br\s*>/gi, '<br />');
|
|
78
|
+
const svg = this.svgBase + html + this.svgEnd;
|
|
79
|
+
if (this.svg !== svg) {
|
|
80
|
+
this.svg = svg;
|
|
81
|
+
}
|
|
82
|
+
if (html !== this.#lastHtml) {
|
|
83
|
+
this.state.markDirty();
|
|
84
|
+
this.#lastHtml = html;
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
async #handleSetup() {
|
|
91
|
+
if (this.#htmlEl) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const wrapperEl = this.#context.getResource('wrapperHtmlEl');
|
|
95
|
+
const el = wrapperEl ? wrapperEl : this.#context.getResource('htmlEl');
|
|
96
|
+
if (!el) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.#htmlEl = el;
|
|
100
|
+
this.#currentId = this.#context.contextData.id;
|
|
101
|
+
if (!this.#displayObject) {
|
|
102
|
+
this.#displayObject = new Container();
|
|
103
|
+
this.#sprite = new Sprite(Texture.EMPTY);
|
|
104
|
+
this.#sprite.x = this.state.width / 2;
|
|
105
|
+
this.#sprite.y = this.state.height / 2;
|
|
106
|
+
this.#sprite.anchor.set(0.5);
|
|
107
|
+
this.#sprite.width = this.state.width;
|
|
108
|
+
this.#sprite.height = this.state.height;
|
|
109
|
+
this.#displayObject.addChild(this.#sprite);
|
|
110
|
+
this.#context.setResource('pixiRenderObject', this.#displayObject);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async #handleRefresh() {
|
|
114
|
+
await this.#handleDestroy();
|
|
115
|
+
await this.#handleSetup();
|
|
116
|
+
}
|
|
117
|
+
// update on context id change, for example, subtitle changed so id changed as well
|
|
118
|
+
async #handleUpdate() {
|
|
119
|
+
if (this.#currentId !== this.#context.contextData.id) {
|
|
120
|
+
await this.#handleRefresh();
|
|
121
|
+
}
|
|
122
|
+
if (this.#htmlEl && this.#lastHtml == this.#htmlEl.outerHTML) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const rerendered = await this.#renderSvg();
|
|
126
|
+
if (this.svg && rerendered) {
|
|
127
|
+
const texture = await this.#svgToTexture(this.svg);
|
|
128
|
+
if (texture) {
|
|
129
|
+
this.#sprite.texture.destroy();
|
|
130
|
+
this.#sprite.texture = texture;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async #handleDestroy() {
|
|
135
|
+
this.#htmlEl = undefined;
|
|
136
|
+
}
|
|
137
|
+
// we need to set context here and not inject it is bound to component and we can update any of the components at any time
|
|
138
|
+
async handle(type, context) {
|
|
139
|
+
this.#context = context;
|
|
140
|
+
if (this.#context.disabled) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const handler = this.#handlers[type];
|
|
144
|
+
if (handler) {
|
|
145
|
+
await handler();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IComponentContext, IComponentHook, HookType } from '../..';
|
|
2
|
+
import { ImageComponentShape } from '../..';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export declare class ImageHook implements IComponentHook {
|
|
5
|
+
#private;
|
|
6
|
+
types: HookType[];
|
|
7
|
+
priority: number;
|
|
8
|
+
componentElement: z.infer<typeof ImageComponentShape>;
|
|
9
|
+
handle(type: HookType, context: IComponentContext): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ImageComponentShape } from '../..';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export class ImageHook {
|
|
4
|
+
types = ['setup', 'destroy'];
|
|
5
|
+
#handlers = {
|
|
6
|
+
setup: this.#handleSetup.bind(this),
|
|
7
|
+
destroy: this.#handleDestroy.bind(this)
|
|
8
|
+
};
|
|
9
|
+
priority = 1;
|
|
10
|
+
#context;
|
|
11
|
+
#imageElement;
|
|
12
|
+
componentElement;
|
|
13
|
+
async #handleSetup() {
|
|
14
|
+
if (this.#imageElement) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const img = new Image();
|
|
18
|
+
img.src = this.componentElement.source.url;
|
|
19
|
+
img.crossOrigin = 'anonymous';
|
|
20
|
+
// Wait for the image to load
|
|
21
|
+
await new Promise((resolve, reject) => {
|
|
22
|
+
img.onload = resolve;
|
|
23
|
+
img.onerror = (error) => reject(new Error(`Failed to load image: ${this.componentElement.source.url}`));
|
|
24
|
+
});
|
|
25
|
+
this.#imageElement = img;
|
|
26
|
+
this.#context.setResource('imageElement', img);
|
|
27
|
+
this.#context.setResource('pixiResource', img);
|
|
28
|
+
}
|
|
29
|
+
async #handleDestroy() {
|
|
30
|
+
// remove event listeners from video
|
|
31
|
+
this.#imageElement.remove();
|
|
32
|
+
}
|
|
33
|
+
async handle(type, context) {
|
|
34
|
+
this.#context = context;
|
|
35
|
+
const data = this.#context.getResource('imageShape');
|
|
36
|
+
if (!data) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.componentElement = data;
|
|
40
|
+
const handler = this.#handlers[type];
|
|
41
|
+
if (handler) {
|
|
42
|
+
await handler();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MediaManager } from '../../managers/MediaManager.js';
|
|
2
|
+
import type { IComponentContext, IComponentHook, HookType } from '../..';
|
|
3
|
+
import { StateManager } from '../../managers/StateManager.svelte.js';
|
|
4
|
+
export declare class MediaHook implements IComponentHook {
|
|
5
|
+
#private;
|
|
6
|
+
types: HookType[];
|
|
7
|
+
priority: number;
|
|
8
|
+
private mediaManager;
|
|
9
|
+
private state;
|
|
10
|
+
constructor(cradle: {
|
|
11
|
+
mediaManager: MediaManager;
|
|
12
|
+
stateManager: StateManager;
|
|
13
|
+
});
|
|
14
|
+
handle(type: HookType, context: IComponentContext): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { MediaManager } from '../../managers/MediaManager.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { StateManager } from '../../managers/StateManager.svelte.js';
|
|
4
|
+
export class MediaHook {
|
|
5
|
+
types = ['setup', 'update', 'destroy', 'refresh'];
|
|
6
|
+
priority = 1;
|
|
7
|
+
#context;
|
|
8
|
+
#mediaElement;
|
|
9
|
+
#MAX_LAG_TIME = 1;
|
|
10
|
+
#lastSyncCheck = 0;
|
|
11
|
+
#lastTargetTime = null;
|
|
12
|
+
mediaManager;
|
|
13
|
+
state;
|
|
14
|
+
#destroyed = false;
|
|
15
|
+
#playRequested = false;
|
|
16
|
+
#lastControllerCheck = 0;
|
|
17
|
+
#CONTROLLER_CHECK_INTERVAL = 100; // ms
|
|
18
|
+
constructor(cradle) {
|
|
19
|
+
this.mediaManager = cradle.mediaManager;
|
|
20
|
+
this.state = cradle.stateManager;
|
|
21
|
+
}
|
|
22
|
+
async #handleSetup() {
|
|
23
|
+
if (this.#mediaElement && !this.#destroyed) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (this.#context.contextData.type !== 'VIDEO' && this.#context.contextData.type !== 'AUDIO')
|
|
27
|
+
return;
|
|
28
|
+
const mediaType = this.#context.type === 'VIDEO' ? 'video' : 'audio';
|
|
29
|
+
const source = this.#context.contextData.source; // TODO: remove as any
|
|
30
|
+
if (!source || !source.url) {
|
|
31
|
+
console.error('No media source found');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const startAt = source.startAt;
|
|
35
|
+
try {
|
|
36
|
+
const media = await this.mediaManager.getMediaElement(source.url, mediaType, {
|
|
37
|
+
muted: true,
|
|
38
|
+
startAt
|
|
39
|
+
});
|
|
40
|
+
if (!media) {
|
|
41
|
+
console.error('Failed to get media element');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.#mediaElement = media;
|
|
45
|
+
this.#context.setResource(mediaType === 'video' ? 'videoElement' : 'audioElement', media);
|
|
46
|
+
this.#destroyed = false;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('Error in media setup:', error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async #autoSeek() {
|
|
53
|
+
const target = this.#context.currentComponentTime;
|
|
54
|
+
const frameDuration = 1 / (this.state.data.settings.fps || 30);
|
|
55
|
+
if (this.#lastTargetTime !== null &&
|
|
56
|
+
Math.abs(this.#lastTargetTime - target) < frameDuration * 0.5) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.#lastTargetTime = target;
|
|
60
|
+
await this.#seek(target);
|
|
61
|
+
}
|
|
62
|
+
async #seek(time) {
|
|
63
|
+
const element = this.#mediaElement;
|
|
64
|
+
const frameDuration = 1 / (this.state.data.settings.fps || 30);
|
|
65
|
+
// Use fastSeek for larger jumps when supported
|
|
66
|
+
try {
|
|
67
|
+
if (typeof element.fastSeek === 'function' &&
|
|
68
|
+
Math.abs(element.currentTime - time) > frameDuration * 2) {
|
|
69
|
+
element.fastSeek(time);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
element.currentTime = time;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
element.currentTime = time;
|
|
77
|
+
}
|
|
78
|
+
// Prefer requestVideoFrameCallback when available (videos only)
|
|
79
|
+
const anyElement = element;
|
|
80
|
+
if (typeof anyElement.requestVideoFrameCallback === 'function') {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
const timeout = setTimeout(() => resolve(true), 120);
|
|
83
|
+
anyElement.requestVideoFrameCallback((_now, _metadata) => {
|
|
84
|
+
clearTimeout(timeout);
|
|
85
|
+
resolve(true);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Fallback to seeked event
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const onSeeked = () => {
|
|
92
|
+
element.removeEventListener('seeked', onSeeked);
|
|
93
|
+
resolve(true);
|
|
94
|
+
};
|
|
95
|
+
element.addEventListener('seeked', onSeeked);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
#isOutOfSync() {
|
|
99
|
+
// run only once per MAX_LAG_TIME
|
|
100
|
+
const now = performance.now() / 1000; // convert to seconds
|
|
101
|
+
if (now - this.#lastSyncCheck < this.#MAX_LAG_TIME) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
this.#lastSyncCheck = now;
|
|
105
|
+
const lagTime = Math.abs(this.#mediaElement.currentTime - this.#context.currentComponentTime);
|
|
106
|
+
return lagTime >= this.#MAX_LAG_TIME;
|
|
107
|
+
}
|
|
108
|
+
async #pause(reason) {
|
|
109
|
+
if (!this.#mediaElement.paused && !this.#playRequested) {
|
|
110
|
+
this.#mediaElement.pause();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async #play() {
|
|
114
|
+
if (this.#mediaElement.paused) {
|
|
115
|
+
this.#playRequested = true;
|
|
116
|
+
try {
|
|
117
|
+
// Safari-specific: Ensure audio state is correct before playing
|
|
118
|
+
const isMuted = this.#context.contextData.muted ?? false;
|
|
119
|
+
const componentVolume = this.#context.contextData.volume ?? 1;
|
|
120
|
+
if (!isMuted) {
|
|
121
|
+
this.#mediaElement.muted = false;
|
|
122
|
+
this.#mediaElement.volume = componentVolume;
|
|
123
|
+
}
|
|
124
|
+
const playPromise = this.#mediaElement.play();
|
|
125
|
+
if (playPromise !== undefined) {
|
|
126
|
+
await playPromise;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
// If autoplay was prevented, try again with muted
|
|
131
|
+
if (err instanceof Error && err.name === 'NotAllowedError') {
|
|
132
|
+
this.#mediaElement.muted = true;
|
|
133
|
+
try {
|
|
134
|
+
await this.#mediaElement.play();
|
|
135
|
+
}
|
|
136
|
+
catch (mutedErr) {
|
|
137
|
+
console.error('Failed to play even when muted:', mutedErr);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
this.#playRequested = false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async #handleUpdate() {
|
|
147
|
+
// On server, seeking is handled by MediaSeekingHook. Avoid duplicate logic/races.
|
|
148
|
+
if (this.state.environment === 'server')
|
|
149
|
+
return;
|
|
150
|
+
if (this.#context.contextData.type !== 'VIDEO' && this.#context.contextData.type !== 'AUDIO')
|
|
151
|
+
return;
|
|
152
|
+
const isActive = this.#context.isActive;
|
|
153
|
+
const mediaType = this.#context.type === 'VIDEO' ? 'video' : 'audio';
|
|
154
|
+
const isMuted = this.#context.contextData.muted ?? false;
|
|
155
|
+
const componentVolume = this.#context.contextData.volume ?? 1;
|
|
156
|
+
const source = this.#context.contextData.source;
|
|
157
|
+
if (!source || !source.url)
|
|
158
|
+
return;
|
|
159
|
+
// Debounce controller checks to prevent Safari issues with rapid controller changes
|
|
160
|
+
const now = performance.now();
|
|
161
|
+
const shouldCheckController = now - this.#lastControllerCheck > this.#CONTROLLER_CHECK_INTERVAL;
|
|
162
|
+
const isController = this.mediaManager.getMediaController(source.url, mediaType) === this.#context.contextData.id;
|
|
163
|
+
if (!isActive) {
|
|
164
|
+
if (isController) {
|
|
165
|
+
await this.#pause('!isActive isController');
|
|
166
|
+
this.mediaManager.setMediaController(source.url, undefined, mediaType);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Make sure we're still marked as the controller (debounced)
|
|
171
|
+
if (!isController && shouldCheckController) {
|
|
172
|
+
// Only set controller if no other component is currently controlling this media
|
|
173
|
+
const currentController = this.mediaManager.getMediaController(source.url, mediaType);
|
|
174
|
+
if (!currentController) {
|
|
175
|
+
this.mediaManager.setMediaController(source.url, this.#context.contextData.id, mediaType);
|
|
176
|
+
await this.#autoSeek();
|
|
177
|
+
this.#lastControllerCheck = now;
|
|
178
|
+
// Safari-specific: Ensure audio is properly restored when taking control
|
|
179
|
+
if (this.#mediaElement && !isMuted) {
|
|
180
|
+
this.#mediaElement.muted = false;
|
|
181
|
+
this.#mediaElement.volume = componentVolume;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Check if component is loading using the StateManager
|
|
186
|
+
if (this.state.isLoadingComponent(this.#context.contextData.id)) {
|
|
187
|
+
if (this.#mediaElement.readyState < 2) {
|
|
188
|
+
await this.#pause('readyState < 2');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.state.removeLoadingComponent(this.#context.contextData.id);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Ensure the media element matches the component's mute and volume state
|
|
196
|
+
if (this.#mediaElement.muted != isMuted) {
|
|
197
|
+
this.#mediaElement.muted = isMuted;
|
|
198
|
+
}
|
|
199
|
+
if (this.#mediaElement.volume != componentVolume) {
|
|
200
|
+
this.#mediaElement.volume = componentVolume;
|
|
201
|
+
}
|
|
202
|
+
const isScenePlaying = this.#context.sceneState.state === 'playing' || this.#context.sceneState.state === 'loading';
|
|
203
|
+
try {
|
|
204
|
+
if (!isScenePlaying) {
|
|
205
|
+
// When scene is not playing, pause media and seek to current position
|
|
206
|
+
await this.#pause('isScenePlaying is false, it is ' + this.#context.sceneState.state);
|
|
207
|
+
return await this.#autoSeek();
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
// When scene is playing and media is paused, play media
|
|
211
|
+
if (this.#mediaElement.paused) {
|
|
212
|
+
await this.#play();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
218
|
+
return this.#handleUpdate();
|
|
219
|
+
}
|
|
220
|
+
// sync with timeline
|
|
221
|
+
if (this.#isOutOfSync()) {
|
|
222
|
+
await this.#autoSeek();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async #handleRefresh() {
|
|
226
|
+
await this.#handleDestroy();
|
|
227
|
+
await this.#handleSetup();
|
|
228
|
+
}
|
|
229
|
+
async #handleDestroy() {
|
|
230
|
+
this.#destroyed = true;
|
|
231
|
+
this.#lastTargetTime = null;
|
|
232
|
+
}
|
|
233
|
+
async handle(type, context) {
|
|
234
|
+
this.#context = context;
|
|
235
|
+
if (this.#context.contextData.type !== 'VIDEO' && this.#context.contextData.type !== 'AUDIO')
|
|
236
|
+
return;
|
|
237
|
+
const source = this.#context.contextData.source;
|
|
238
|
+
if (!source || !source.url) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (type === 'setup') {
|
|
242
|
+
return await this.#handleSetup();
|
|
243
|
+
}
|
|
244
|
+
else if (type === 'destroy') {
|
|
245
|
+
return await this.#handleDestroy();
|
|
246
|
+
}
|
|
247
|
+
else if (type === 'refresh') {
|
|
248
|
+
return await this.#handleRefresh();
|
|
249
|
+
}
|
|
250
|
+
await this.#handleUpdate();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HookType, IComponentContext, IComponentHook } from '../..';
|
|
2
|
+
import { StateManager } from '../../managers/StateManager.svelte.js';
|
|
3
|
+
export declare class MediaSeekingHook implements IComponentHook {
|
|
4
|
+
#private;
|
|
5
|
+
types: HookType[];
|
|
6
|
+
priority: number;
|
|
7
|
+
state: StateManager;
|
|
8
|
+
constructor(cradle: {
|
|
9
|
+
stateManager: StateManager;
|
|
10
|
+
});
|
|
11
|
+
handle(type: HookType, context: IComponentContext): Promise<void>;
|
|
12
|
+
}
|