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.
Files changed (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -0
  3. package/dist/DIContainer.d.ts +4 -0
  4. package/dist/DIContainer.js +145 -0
  5. package/dist/SceneBuilder.svelte.d.ts +8574 -0
  6. package/dist/SceneBuilder.svelte.js +409 -0
  7. package/dist/adapters/subtitleHelpers.d.ts +2 -0
  8. package/dist/adapters/subtitleHelpers.js +187 -0
  9. package/dist/animations/AnimationContext.d.ts +17 -0
  10. package/dist/animations/AnimationContext.js +72 -0
  11. package/dist/animations/AnimationPresetsRegister.d.ts +362 -0
  12. package/dist/animations/AnimationPresetsRegister.js +20 -0
  13. package/dist/animations/AnimationSetup.d.ts +8 -0
  14. package/dist/animations/AnimationSetup.js +30 -0
  15. package/dist/animations/SplitTextCache.d.ts +28 -0
  16. package/dist/animations/SplitTextCache.js +68 -0
  17. package/dist/animations/animationBuilder.d.ts +31 -0
  18. package/dist/animations/animationBuilder.js +255 -0
  19. package/dist/animations/animationPreset.d.ts +7 -0
  20. package/dist/animations/animationPreset.js +31 -0
  21. package/dist/animations/builders/AnimationPresetFactory.d.ts +43 -0
  22. package/dist/animations/builders/AnimationPresetFactory.js +139 -0
  23. package/dist/animations/builders/LineHighlighterAnimationBuilder.d.ts +16 -0
  24. package/dist/animations/builders/LineHighlighterAnimationBuilder.js +183 -0
  25. package/dist/animations/builders/WordHighlighterAnimationBuilder.d.ts +15 -0
  26. package/dist/animations/builders/WordHighlighterAnimationBuilder.js +180 -0
  27. package/dist/animations/engines/AnimationEngineAdaptor.d.ts +107 -0
  28. package/dist/animations/engines/AnimationEngineAdaptor.js +1 -0
  29. package/dist/animations/engines/GSAPEngineAdaptor.d.ts +21 -0
  30. package/dist/animations/engines/GSAPEngineAdaptor.js +145 -0
  31. package/dist/animations/presets/index.d.ts +2 -0
  32. package/dist/animations/presets/index.js +3 -0
  33. package/dist/animations/presets/lines.d.ts +52 -0
  34. package/dist/animations/presets/lines.js +547 -0
  35. package/dist/animations/presets/words.d.ts +31 -0
  36. package/dist/animations/presets/words.js +268 -0
  37. package/dist/animations/transformers/AnimationReferenceTransformer.d.ts +9 -0
  38. package/dist/animations/transformers/AnimationReferenceTransformer.js +114 -0
  39. package/dist/builders/PixiComponentBuilder.d.ts +63 -0
  40. package/dist/builders/PixiComponentBuilder.js +112 -0
  41. package/dist/builders/_ComponentState.svelte.d.ts +795 -0
  42. package/dist/builders/_ComponentState.svelte.js +203 -0
  43. package/dist/builders/html/HtmlBuilder.d.ts +66 -0
  44. package/dist/builders/html/HtmlBuilder.js +171 -0
  45. package/dist/builders/html/HtmlBuilderFactory.d.ts +27 -0
  46. package/dist/builders/html/HtmlBuilderFactory.js +30 -0
  47. package/dist/builders/html/StyleBuilder.d.ts +13 -0
  48. package/dist/builders/html/StyleBuilder.js +133 -0
  49. package/dist/builders/html/StyleProcessor.d.ts +9 -0
  50. package/dist/builders/html/StyleProcessor.js +1 -0
  51. package/dist/builders/html/TextComponentHtmlBuilder.d.ts +16 -0
  52. package/dist/builders/html/TextComponentHtmlBuilder.js +93 -0
  53. package/dist/builders/html/TextShadowBuilder.d.ts +60 -0
  54. package/dist/builders/html/TextShadowBuilder.js +227 -0
  55. package/dist/builders/html/processors/AppearanceStyleProcessor.d.ts +5 -0
  56. package/dist/builders/html/processors/AppearanceStyleProcessor.js +57 -0
  57. package/dist/builders/html/processors/TextAppearanceStyleProcessor.d.ts +5 -0
  58. package/dist/builders/html/processors/TextAppearanceStyleProcessor.js +37 -0
  59. package/dist/builders/html/processors/TextEffectsStyleProcessor.d.ts +6 -0
  60. package/dist/builders/html/processors/TextEffectsStyleProcessor.js +68 -0
  61. package/dist/commands/Command.d.ts +6 -0
  62. package/dist/commands/Command.js +1 -0
  63. package/dist/commands/CommandRunner.d.ts +28 -0
  64. package/dist/commands/CommandRunner.js +81 -0
  65. package/dist/commands/CommandTypes.d.ts +11 -0
  66. package/dist/commands/CommandTypes.js +13 -0
  67. package/dist/commands/PauseCommand.d.ts +4 -0
  68. package/dist/commands/PauseCommand.js +5 -0
  69. package/dist/commands/PlayCommand.d.ts +4 -0
  70. package/dist/commands/PlayCommand.js +6 -0
  71. package/dist/commands/RenderCommand.d.ts +15 -0
  72. package/dist/commands/RenderCommand.js +18 -0
  73. package/dist/commands/RenderFrameCommand.d.ts +17 -0
  74. package/dist/commands/RenderFrameCommand.js +93 -0
  75. package/dist/commands/ReplaceSourceOnTimeCommand.d.ts +4 -0
  76. package/dist/commands/ReplaceSourceOnTimeCommand.js +22 -0
  77. package/dist/commands/SeekCommand.d.ts +15 -0
  78. package/dist/commands/SeekCommand.js +39 -0
  79. package/dist/commands/UpdateComponentCommand.d.ts +4 -0
  80. package/dist/commands/UpdateComponentCommand.js +17 -0
  81. package/dist/components/AnimatedGIF.d.ts +201 -0
  82. package/dist/components/AnimatedGIF.js +391 -0
  83. package/dist/components/Component.svelte.d.ts +33 -0
  84. package/dist/components/Component.svelte.js +152 -0
  85. package/dist/components/ComponentContext.svelte.d.ts +33 -0
  86. package/dist/components/ComponentContext.svelte.js +105 -0
  87. package/dist/components/hooks/AnimationHook.d.ts +25 -0
  88. package/dist/components/hooks/AnimationHook.js +180 -0
  89. package/dist/components/hooks/CanvasShapeHook.d.ts +12 -0
  90. package/dist/components/hooks/CanvasShapeHook.js +229 -0
  91. package/dist/components/hooks/HtmlAnimationHook.d.ts +8 -0
  92. package/dist/components/hooks/HtmlAnimationHook.js +70 -0
  93. package/dist/components/hooks/HtmlTextHook.d.ts +16 -0
  94. package/dist/components/hooks/HtmlTextHook.js +102 -0
  95. package/dist/components/hooks/HtmlToCanvasHook.d.ts +16 -0
  96. package/dist/components/hooks/HtmlToCanvasHook.js +148 -0
  97. package/dist/components/hooks/ImageHook.d.ts +10 -0
  98. package/dist/components/hooks/ImageHook.js +45 -0
  99. package/dist/components/hooks/MediaHook.d.ts +15 -0
  100. package/dist/components/hooks/MediaHook.js +252 -0
  101. package/dist/components/hooks/MediaSeekingHook.d.ts +12 -0
  102. package/dist/components/hooks/MediaSeekingHook.js +204 -0
  103. package/dist/components/hooks/PixiDisplayObjectHook.d.ts +15 -0
  104. package/dist/components/hooks/PixiDisplayObjectHook.js +77 -0
  105. package/dist/components/hooks/PixiGifHook.d.ts +15 -0
  106. package/dist/components/hooks/PixiGifHook.js +97 -0
  107. package/dist/components/hooks/PixiProgressShapeHook.d.ts +12 -0
  108. package/dist/components/hooks/PixiProgressShapeHook.js +128 -0
  109. package/dist/components/hooks/PixiSplitScreenDisplayObjectHook.d.ts +21 -0
  110. package/dist/components/hooks/PixiSplitScreenDisplayObjectHook.js +210 -0
  111. package/dist/components/hooks/PixiTextureHook.d.ts +7 -0
  112. package/dist/components/hooks/PixiTextureHook.js +29 -0
  113. package/dist/components/hooks/PixiVideoTextureHook.d.ts +10 -0
  114. package/dist/components/hooks/PixiVideoTextureHook.js +35 -0
  115. package/dist/components/hooks/SubtitlesHook.d.ts +88 -0
  116. package/dist/components/hooks/SubtitlesHook.js +199 -0
  117. package/dist/components/hooks/VerifyGifHook.d.ts +7 -0
  118. package/dist/components/hooks/VerifyGifHook.js +27 -0
  119. package/dist/components/hooks/VerifyImageHook.d.ts +7 -0
  120. package/dist/components/hooks/VerifyImageHook.js +27 -0
  121. package/dist/components/hooks/VerifyMediaHook.d.ts +7 -0
  122. package/dist/components/hooks/VerifyMediaHook.js +21 -0
  123. package/dist/components/hooks/shapes/progress/CustomProgressRenderer.d.ts +8 -0
  124. package/dist/components/hooks/shapes/progress/CustomProgressRenderer.js +53 -0
  125. package/dist/components/hooks/shapes/progress/DoubleProgressRenderer.d.ts +8 -0
  126. package/dist/components/hooks/shapes/progress/DoubleProgressRenderer.js +69 -0
  127. package/dist/components/hooks/shapes/progress/LinearProgressRenderer.d.ts +8 -0
  128. package/dist/components/hooks/shapes/progress/LinearProgressRenderer.js +60 -0
  129. package/dist/components/hooks/shapes/progress/PerimeterProgressRenderer.d.ts +9 -0
  130. package/dist/components/hooks/shapes/progress/PerimeterProgressRenderer.js +213 -0
  131. package/dist/components/hooks/shapes/progress/ProgressRenderer.d.ts +17 -0
  132. package/dist/components/hooks/shapes/progress/ProgressRenderer.js +75 -0
  133. package/dist/components/hooks/shapes/progress/RadialProgressRenderer.d.ts +8 -0
  134. package/dist/components/hooks/shapes/progress/RadialProgressRenderer.js +50 -0
  135. package/dist/components/hooks/shapes/progress/index.d.ts +6 -0
  136. package/dist/components/hooks/shapes/progress/index.js +6 -0
  137. package/dist/composers/componentComposer.d.ts +55 -0
  138. package/dist/composers/componentComposer.js +118 -0
  139. package/dist/composers/layerComposer.d.ts +46 -0
  140. package/dist/composers/layerComposer.js +79 -0
  141. package/dist/composers/sceneComposer.d.ts +48 -0
  142. package/dist/composers/sceneComposer.js +92 -0
  143. package/dist/constants.d.ts +12 -0
  144. package/dist/constants.js +14 -0
  145. package/dist/directors/ComponentDirector.d.ts +20 -0
  146. package/dist/directors/ComponentDirector.js +86 -0
  147. package/dist/factories/SceneBuilderFactory.d.ts +15 -0
  148. package/dist/factories/SceneBuilderFactory.js +51 -0
  149. package/dist/fonts/GoogleFontsProvider.d.ts +12 -0
  150. package/dist/fonts/GoogleFontsProvider.js +125 -0
  151. package/dist/fonts/fontLoader.d.ts +15 -0
  152. package/dist/fonts/fontLoader.js +41 -0
  153. package/dist/fonts/types.d.ts +1 -0
  154. package/dist/fonts/types.js +1 -0
  155. package/dist/index.d.ts +11 -0
  156. package/dist/index.js +14 -0
  157. package/dist/layers/Layer.svelte.d.ts +8492 -0
  158. package/dist/layers/Layer.svelte.js +125 -0
  159. package/dist/managers/AppManager.svelte.d.ts +23 -0
  160. package/dist/managers/AppManager.svelte.js +89 -0
  161. package/dist/managers/ComponentsManager.svelte.d.ts +49 -0
  162. package/dist/managers/ComponentsManager.svelte.js +247 -0
  163. package/dist/managers/DomManager.d.ts +18 -0
  164. package/dist/managers/DomManager.js +73 -0
  165. package/dist/managers/EventManager.d.ts +7 -0
  166. package/dist/managers/EventManager.js +22 -0
  167. package/dist/managers/LayersManager.svelte.d.ts +8499 -0
  168. package/dist/managers/LayersManager.svelte.js +176 -0
  169. package/dist/managers/MediaManager.d.ts +32 -0
  170. package/dist/managers/MediaManager.js +243 -0
  171. package/dist/managers/RenderManager.d.ts +23 -0
  172. package/dist/managers/RenderManager.js +59 -0
  173. package/dist/managers/StateManager.svelte.d.ts +8746 -0
  174. package/dist/managers/StateManager.svelte.js +272 -0
  175. package/dist/managers/SubtitlesManager.svelte.d.ts +261 -0
  176. package/dist/managers/SubtitlesManager.svelte.js +1385 -0
  177. package/dist/managers/TimeManager.svelte.d.ts +6 -0
  178. package/dist/managers/TimeManager.svelte.js +18 -0
  179. package/dist/managers/TimelineManager.svelte.d.ts +25 -0
  180. package/dist/managers/TimelineManager.svelte.js +152 -0
  181. package/dist/registers.d.ts +12 -0
  182. package/dist/registers.js +29 -0
  183. package/dist/schemas/runtime/index.d.ts +3 -0
  184. package/dist/schemas/runtime/index.js +4 -0
  185. package/dist/schemas/runtime/types.d.ts +323 -0
  186. package/dist/schemas/runtime/types.js +12 -0
  187. package/dist/schemas/scene/animations.d.ts +89738 -0
  188. package/dist/schemas/scene/animations.js +211 -0
  189. package/dist/schemas/scene/components.js +515 -0
  190. package/dist/schemas/scene/core.js +160 -0
  191. package/dist/schemas/scene/index.d.ts +22 -0
  192. package/dist/schemas/scene/index.js +10 -0
  193. package/dist/schemas/scene/properties.d.ts +914 -0
  194. package/dist/schemas/scene/properties.js +398 -0
  195. package/dist/schemas/scene/subtitles.d.ts +1141 -0
  196. package/dist/schemas/scene/subtitles.js +111 -0
  197. package/dist/schemas/scene/utils.d.ts +1 -0
  198. package/dist/schemas/scene/utils.js +5 -0
  199. package/dist/seeds/SeedFactory.d.ts +59 -0
  200. package/dist/seeds/SeedFactory.js +99 -0
  201. package/dist/seeds/index.d.ts +8 -0
  202. package/dist/seeds/index.js +8 -0
  203. package/dist/transformers/ColorTransformer.d.ts +5 -0
  204. package/dist/transformers/ColorTransformer.js +67 -0
  205. package/dist/transformers/PixiColorTransformer.d.ts +22 -0
  206. package/dist/transformers/PixiColorTransformer.js +104 -0
  207. package/dist/utils/canvas.d.ts +6 -0
  208. package/dist/utils/canvas.js +18 -0
  209. package/dist/utils/document.d.ts +2 -0
  210. package/dist/utils/document.js +36 -0
  211. package/dist/utils/emoji.d.ts +10 -0
  212. package/dist/utils/emoji.js +51 -0
  213. package/dist/utils/html.d.ts +4 -0
  214. package/dist/utils/html.js +45 -0
  215. package/dist/utils/svgGenerator.d.ts +20 -0
  216. package/dist/utils/svgGenerator.js +103 -0
  217. package/dist/utils/utils.d.ts +5 -0
  218. package/dist/utils/utils.js +125 -0
  219. 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
+ }