visualfries 0.1.1098 → 0.1.1099

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 (35) hide show
  1. package/dist/DIContainer.js +8 -0
  2. package/dist/SceneBuilder.svelte.d.ts +9 -0
  3. package/dist/SceneBuilder.svelte.js +76 -0
  4. package/dist/builders/PixiComponentBuilder.d.ts +3 -0
  5. package/dist/builders/PixiComponentBuilder.js +6 -0
  6. package/dist/builders/_ComponentState.svelte.d.ts +1 -1
  7. package/dist/commands/RenderFrameCommand.d.ts +4 -0
  8. package/dist/commands/RenderFrameCommand.js +7 -1
  9. package/dist/commands/ReplaceSourceOnTimeCommand.d.ts +9 -0
  10. package/dist/commands/ReplaceSourceOnTimeCommand.js +40 -7
  11. package/dist/components/Component.svelte.d.ts +1 -1
  12. package/dist/components/ComponentContext.svelte.d.ts +1 -1
  13. package/dist/components/hooks/DeterministicMediaFrameHook.d.ts +13 -0
  14. package/dist/components/hooks/DeterministicMediaFrameHook.js +89 -0
  15. package/dist/components/hooks/PixiDisplayObjectHook.js +4 -0
  16. package/dist/components/hooks/PixiSplitScreenDisplayObjectHook.js +28 -2
  17. package/dist/components/hooks/PixiTextureHook.js +35 -9
  18. package/dist/components/hooks/PixiVideoTextureHook.js +16 -0
  19. package/dist/directors/ComponentDirector.d.ts +4 -0
  20. package/dist/directors/ComponentDirector.js +14 -3
  21. package/dist/factories/SceneBuilderFactory.d.ts +2 -0
  22. package/dist/factories/SceneBuilderFactory.js +6 -2
  23. package/dist/layers/Layer.svelte.d.ts +1 -1
  24. package/dist/managers/DeterministicMediaManager.d.ts +21 -0
  25. package/dist/managers/DeterministicMediaManager.js +341 -0
  26. package/dist/managers/StateManager.svelte.d.ts +1 -1
  27. package/dist/schemas/runtime/deterministic.d.ts +94 -0
  28. package/dist/schemas/runtime/deterministic.js +21 -0
  29. package/dist/schemas/runtime/index.d.ts +2 -0
  30. package/dist/schemas/runtime/index.js +1 -1
  31. package/dist/schemas/runtime/types.d.ts +8 -1
  32. package/dist/schemas/scene/components.d.ts +2 -2
  33. package/dist/schemas/scene/core.d.ts +4 -4
  34. package/dist/schemas/scene/properties.d.ts +16 -16
  35. package/package.json +1 -1
@@ -41,10 +41,12 @@ import { Layer } from './layers/Layer.svelte.js';
41
41
  import { LayersManager } from './managers/LayersManager.svelte.js';
42
42
  import { MediaSeekingHook } from './components/hooks/MediaSeekingHook.js';
43
43
  import { VerifyGifHook } from './components/hooks/VerifyGifHook.js';
44
+ import { DeterministicMediaFrameHook } from './components/hooks/DeterministicMediaFrameHook.js';
44
45
  import { ComponentAnimationTransformer } from './animations/transformers/AnimationReferenceTransformer.js';
45
46
  import { AnimationPresetsRegister } from './animations/AnimationPresetsRegister.js';
46
47
  import { SplitTextCache } from './animations/SplitTextCache.js';
47
48
  import { TimeManager } from './managers/TimeManager.svelte.js';
49
+ import { DeterministicMediaManager } from './managers/DeterministicMediaManager.js';
48
50
  const containers = new Map();
49
51
  export const registerNewContainer = function (data, instances) {
50
52
  if (containers.has(data.id)) {
@@ -75,6 +77,9 @@ export const registerNewContainer = function (data, instances) {
75
77
  renderManager: asClass(RenderManager, { lifetime: Lifetime.SINGLETON }),
76
78
  domManager: asClass(DomManager, { lifetime: Lifetime.SINGLETON }),
77
79
  mediaManager: asClass(MediaManager, { lifetime: Lifetime.SINGLETON }),
80
+ deterministicMediaManager: asClass(DeterministicMediaManager, {
81
+ lifetime: Lifetime.SINGLETON
82
+ }),
78
83
  sceneBuilder: asClass(SceneBuilder, { lifetime: Lifetime.SINGLETON }),
79
84
  layersManager: asClass(LayersManager, { lifetime: Lifetime.SINGLETON }),
80
85
  // transients
@@ -87,6 +92,9 @@ export const registerNewContainer = function (data, instances) {
87
92
  imageHook: asClass(ImageHook, { lifetime: Lifetime.TRANSIENT }),
88
93
  verifyImageHook: asClass(VerifyImageHook, { lifetime: Lifetime.TRANSIENT }),
89
94
  verifyGifHook: asClass(VerifyGifHook, { lifetime: Lifetime.TRANSIENT }),
95
+ deterministicMediaFrameHook: asClass(DeterministicMediaFrameHook, {
96
+ lifetime: Lifetime.TRANSIENT
97
+ }),
90
98
  videoTextureHook: asClass(PixiVideoTextureHook, { lifetime: Lifetime.TRANSIENT }),
91
99
  splitScreenHook: asClass(PixiSplitScreenDisplayObjectHook, { lifetime: Lifetime.TRANSIENT }),
92
100
  htmlTextHook: asClass(HtmlTextHook, { lifetime: Lifetime.TRANSIENT }),
@@ -8,7 +8,9 @@ import { DomManager } from './managers/DomManager.js';
8
8
  import { AppManager } from './managers/AppManager.svelte.js';
9
9
  import { ComponentsManager } from './managers/ComponentsManager.svelte.js';
10
10
  import type { EventMap, EventType, EventPayload, BuilderState, ISceneBuilder } from './';
11
+ import type { DeterministicFrameProvider, DeterministicMediaConfig, DeterministicDiagnosticsReport, RenderFrameRangeOptions, RenderFrameRangeSummary } from './';
11
12
  import { MediaManager } from './managers/MediaManager.js';
13
+ import { DeterministicMediaManager } from './managers/DeterministicMediaManager.js';
12
14
  import { LayersManager } from './managers/LayersManager.svelte.js';
13
15
  import { SubtitlesManager } from './managers/SubtitlesManager.svelte.js';
14
16
  import type { Component } from './components/Component.svelte.js';
@@ -24,6 +26,7 @@ export declare class SceneBuilder implements ISceneBuilder {
24
26
  private stateManager;
25
27
  private commandRunner;
26
28
  private mediaManager;
29
+ private deterministicMediaManager;
27
30
  private subtitlesManager;
28
31
  private fonts;
29
32
  constructor(cradle: {
@@ -36,6 +39,7 @@ export declare class SceneBuilder implements ISceneBuilder {
36
39
  stateManager: StateManager;
37
40
  commandRunner: CommandRunner;
38
41
  mediaManager: MediaManager;
42
+ deterministicMediaManager: DeterministicMediaManager;
39
43
  subtitlesManager: SubtitlesManager;
40
44
  fonts: FontType[];
41
45
  });
@@ -5369,6 +5373,10 @@ export declare class SceneBuilder implements ISceneBuilder {
5369
5373
  splitComponent(component: Component): Promise<boolean>;
5370
5374
  seek(time: number): Promise<void>;
5371
5375
  replaceSourceOnTime(time: number, componentId: string, base64data: string): Promise<void>;
5376
+ setDeterministicFrameProvider(provider: DeterministicFrameProvider | null): void;
5377
+ getDeterministicFrameProvider(): DeterministicFrameProvider | null;
5378
+ getDeterministicMediaConfig(): DeterministicMediaConfig;
5379
+ getDiagnosticsReport(): DeterministicDiagnosticsReport | null;
5372
5380
  seekAndRenderFrame(time: number, target?: PIXI.DisplayObject | PIXI.RenderTexture, format?: string, quality?: number): Promise<string | ArrayBuffer | Blob>;
5373
5381
  /**
5374
5382
  * Check if seeking to a specific time would result in visual changes
@@ -5395,6 +5403,7 @@ export declare class SceneBuilder implements ISceneBuilder {
5395
5403
  */
5396
5404
  isSceneDirty(time: number): Promise<boolean>;
5397
5405
  renderFrame(target?: PIXI.DisplayObject | PIXI.RenderTexture, format?: string, quality?: number): Promise<string | ArrayBuffer | Blob>;
5406
+ renderFrameRange(options: RenderFrameRangeOptions): Promise<RenderFrameRangeSummary>;
5398
5407
  log(message: string): void;
5399
5408
  play(changeState?: boolean): void;
5400
5409
  pause(changeState?: boolean): void;
@@ -13,6 +13,7 @@ import { AppManager } from './managers/AppManager.svelte.js';
13
13
  import { ComponentsManager } from './managers/ComponentsManager.svelte.js';
14
14
  import { v4 as uuidv4 } from 'uuid';
15
15
  import { MediaManager } from './managers/MediaManager.js';
16
+ import { DeterministicMediaManager } from './managers/DeterministicMediaManager.js';
16
17
  import { LayersManager } from './managers/LayersManager.svelte.js';
17
18
  import { SubtitlesManager } from './managers/SubtitlesManager.svelte.js';
18
19
  import { removeContainer } from './DIContainer.js';
@@ -28,6 +29,7 @@ export class SceneBuilder {
28
29
  stateManager;
29
30
  commandRunner;
30
31
  mediaManager;
32
+ deterministicMediaManager;
31
33
  subtitlesManager;
32
34
  fonts;
33
35
  // Replace constructor with cradle pattern
@@ -41,6 +43,7 @@ export class SceneBuilder {
41
43
  this.stateManager = cradle.stateManager;
42
44
  this.commandRunner = cradle.commandRunner;
43
45
  this.mediaManager = cradle.mediaManager;
46
+ this.deterministicMediaManager = cradle.deterministicMediaManager;
44
47
  this.subtitlesManager = cradle.subtitlesManager;
45
48
  this.fonts = cradle.fonts;
46
49
  // TODO - check scene is v2
@@ -295,6 +298,18 @@ export class SceneBuilder {
295
298
  base64data
296
299
  });
297
300
  }
301
+ setDeterministicFrameProvider(provider) {
302
+ this.deterministicMediaManager.setProvider(provider);
303
+ }
304
+ getDeterministicFrameProvider() {
305
+ return this.deterministicMediaManager.getProvider();
306
+ }
307
+ getDeterministicMediaConfig() {
308
+ return this.deterministicMediaManager.config;
309
+ }
310
+ getDiagnosticsReport() {
311
+ return this.deterministicMediaManager.getDiagnosticsReport();
312
+ }
298
313
  async seekAndRenderFrame(time, target, format = 'png', quality = 1) {
299
314
  await this.seek(time);
300
315
  // Ensure scene is rendered after seek so media hooks apply their updates
@@ -345,6 +360,66 @@ export class SceneBuilder {
345
360
  }
346
361
  return frame;
347
362
  }
363
+ async renderFrameRange(options) {
364
+ if (this.environment !== 'server') {
365
+ throw new Error('renderFrameRange is only available in server environment');
366
+ }
367
+ const format = options.format ?? 'blob';
368
+ const quality = options.quality ?? 1;
369
+ const skipDuplicates = options.skipDuplicates ?? false;
370
+ const fromFrame = Math.max(0, Math.floor(options.fromFrame));
371
+ const toFrame = Math.max(fromFrame, Math.floor(options.toFrame));
372
+ let framesRendered = 0;
373
+ let framesSkipped = 0;
374
+ let aborted = false;
375
+ let previousFrame = null;
376
+ for (let frameIndex = fromFrame; frameIndex < toFrame; frameIndex += 1) {
377
+ if (options.signal?.aborted) {
378
+ aborted = true;
379
+ break;
380
+ }
381
+ let frame;
382
+ let isDuplicate = false;
383
+ const frameTime = frameIndex / this.fps;
384
+ if (skipDuplicates) {
385
+ const isDirty = await this.isSceneDirty(frameTime);
386
+ if (!isDirty && previousFrame) {
387
+ frame = previousFrame;
388
+ isDuplicate = true;
389
+ framesSkipped += 1;
390
+ }
391
+ else {
392
+ frame = await this.seekAndRenderFrame(frameTime, undefined, format, quality);
393
+ previousFrame = frame;
394
+ }
395
+ }
396
+ else {
397
+ frame = await this.seekAndRenderFrame(frameTime, undefined, format, quality);
398
+ previousFrame = frame;
399
+ }
400
+ let released = false;
401
+ const release = () => {
402
+ if (released) {
403
+ return;
404
+ }
405
+ released = true;
406
+ };
407
+ await options.onFrame({
408
+ frameIndex,
409
+ frame,
410
+ isDuplicate,
411
+ release
412
+ });
413
+ release();
414
+ framesRendered += 1;
415
+ }
416
+ return {
417
+ framesRendered,
418
+ framesSkipped,
419
+ aborted,
420
+ diagnostics: this.getDiagnosticsReport()
421
+ };
422
+ }
348
423
  log(message) {
349
424
  $effect.root(function () {
350
425
  $inspect(message);
@@ -401,6 +476,7 @@ export class SceneBuilder {
401
476
  this.componentsManager.destroy();
402
477
  // media manages should be destroyed last
403
478
  this.mediaManager.destroy();
479
+ void this.deterministicMediaManager.destroy();
404
480
  // Remove the container from the DI container cache
405
481
  removeContainer(this.sceneData.id);
406
482
  }
@@ -20,6 +20,7 @@ type PixiComponentCradle = {
20
20
  pixiProgressShapeHook: ComponentHook;
21
21
  htmlToCanvasHook: ComponentHook;
22
22
  mediaSeekingHook: ComponentHook;
23
+ deterministicMediaFrameHook: ComponentHook;
23
24
  };
24
25
  export declare class PixiComponentBuilder implements IComponentBuilder {
25
26
  private component;
@@ -41,11 +42,13 @@ export declare class PixiComponentBuilder implements IComponentBuilder {
41
42
  private htmlToCanvasHook;
42
43
  private animationHook;
43
44
  private mediaSeekingHook;
45
+ private deterministicMediaFrameHook;
44
46
  constructor(cradle: PixiComponentCradle);
45
47
  withCanvasShape(): this;
46
48
  withProgressShape(): this;
47
49
  withMedia(): this;
48
50
  withMediaSeeking(): this;
51
+ withDeterministicMedia(): this;
49
52
  withImage(): this;
50
53
  withTexture(): this;
51
54
  withDisplayObject(): this;
@@ -19,6 +19,7 @@ export class PixiComponentBuilder {
19
19
  htmlToCanvasHook;
20
20
  animationHook;
21
21
  mediaSeekingHook;
22
+ deterministicMediaFrameHook;
22
23
  constructor(cradle) {
23
24
  this.component = cradle.component;
24
25
  this.mediaHook = cradle.mediaHook;
@@ -39,6 +40,7 @@ export class PixiComponentBuilder {
39
40
  this.pixiProgressShapeHook = cradle.pixiProgressShapeHook;
40
41
  this.htmlToCanvasHook = cradle.htmlToCanvasHook;
41
42
  this.mediaSeekingHook = cradle.mediaSeekingHook;
43
+ this.deterministicMediaFrameHook = cradle.deterministicMediaFrameHook;
42
44
  }
43
45
  withCanvasShape() {
44
46
  this.component.addHook(this.canvasShapeHook);
@@ -57,6 +59,10 @@ export class PixiComponentBuilder {
57
59
  this.component.addHook(this.mediaSeekingHook);
58
60
  return this;
59
61
  }
62
+ withDeterministicMedia() {
63
+ this.component.addHook(this.deterministicMediaFrameHook, 4);
64
+ return this;
65
+ }
60
66
  withImage() {
61
67
  this.component.addHook(this.verifyImageHook);
62
68
  this.component.addHook(this.imageHook);
@@ -15,7 +15,7 @@ export declare class ComponentState implements ComponentProps {
15
15
  setRefreshCallback(callback: () => Promise<void>): void;
16
16
  private maybeAutoRefresh;
17
17
  get id(): string;
18
- get type(): "IMAGE" | "GIF" | "VIDEO" | "TEXT" | "SHAPE" | "AUDIO" | "COLOR" | "GRADIENT" | "SUBTITLES";
18
+ get type(): "VIDEO" | "GIF" | "IMAGE" | "TEXT" | "SHAPE" | "AUDIO" | "COLOR" | "GRADIENT" | "SUBTITLES";
19
19
  get name(): string;
20
20
  get start_at(): number;
21
21
  get end_at(): number;
@@ -2,16 +2,20 @@ import type { Command } from './Command.js';
2
2
  import { StateManager } from '../managers/StateManager.svelte.js';
3
3
  import { DomManager } from '../managers/DomManager.js';
4
4
  import { AppManager } from '../managers/AppManager.svelte.js';
5
+ import { DeterministicMediaManager } from '../managers/DeterministicMediaManager.js';
5
6
  export declare class RenderFrameCommand implements Command<string | ArrayBuffer | Blob | null> {
6
7
  private sceneState;
7
8
  private domManager;
8
9
  private appManager;
9
10
  private lastRenderedFrame;
10
11
  private lastRenderArgs;
12
+ private deterministicMediaManager?;
13
+ private lastDeterministicFingerprint;
11
14
  constructor(cradle: {
12
15
  stateManager: StateManager;
13
16
  domManager: DomManager;
14
17
  appManager: AppManager;
18
+ deterministicMediaManager?: DeterministicMediaManager;
15
19
  });
16
20
  execute(args: unknown): Promise<string | ArrayBuffer | Blob | null>;
17
21
  }
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { StateManager } from '../managers/StateManager.svelte.js';
3
3
  import { DomManager } from '../managers/DomManager.js';
4
4
  import { AppManager } from '../managers/AppManager.svelte.js';
5
+ import { DeterministicMediaManager } from '../managers/DeterministicMediaManager.js';
5
6
  const replaceSourceOnTimeSchema = z.object({
6
7
  format: z.enum(['arraybuffer', 'blob', 'png', 'jpg', 'jpeg']),
7
8
  quality: z.number().min(0).max(1),
@@ -13,10 +14,13 @@ export class RenderFrameCommand {
13
14
  appManager;
14
15
  lastRenderedFrame = null;
15
16
  lastRenderArgs = null;
17
+ deterministicMediaManager;
18
+ lastDeterministicFingerprint = '';
16
19
  constructor(cradle) {
17
20
  this.sceneState = cradle.stateManager;
18
21
  this.domManager = cradle.domManager;
19
22
  this.appManager = cradle.appManager;
23
+ this.deterministicMediaManager = cradle.deterministicMediaManager;
20
24
  }
21
25
  async execute(args) {
22
26
  const check = replaceSourceOnTimeSchema.safeParse(args);
@@ -24,6 +28,7 @@ export class RenderFrameCommand {
24
28
  return null;
25
29
  }
26
30
  const { format, quality, target } = check.data;
31
+ const currentDeterministicFingerprint = this.deterministicMediaManager?.isEnabled() ? this.deterministicMediaManager.getFingerprint() : '';
27
32
  // Server optimization: Return cached frame if nothing changed visually and render args match
28
33
  if (this.sceneState.environment === 'server' && !this.sceneState.isDirty) {
29
34
  if (this.lastRenderedFrame && this.lastRenderArgs) {
@@ -31,7 +36,7 @@ export class RenderFrameCommand {
31
36
  const argsMatch = this.lastRenderArgs.format === format &&
32
37
  this.lastRenderArgs.quality === quality &&
33
38
  this.lastRenderArgs.target === target;
34
- if (argsMatch) {
39
+ if (argsMatch && this.lastDeterministicFingerprint === currentDeterministicFingerprint) {
35
40
  return this.lastRenderedFrame;
36
41
  }
37
42
  }
@@ -86,6 +91,7 @@ export class RenderFrameCommand {
86
91
  if (this.sceneState.environment === 'server') {
87
92
  this.lastRenderedFrame = frame;
88
93
  this.lastRenderArgs = { format, quality, target };
94
+ this.lastDeterministicFingerprint = currentDeterministicFingerprint;
89
95
  this.sceneState.clearDirty();
90
96
  }
91
97
  return frame;
@@ -1,4 +1,13 @@
1
1
  import type { Command } from './Command.js';
2
+ import { StateManager } from '../managers/StateManager.svelte.js';
3
+ import { DeterministicMediaManager } from '../managers/DeterministicMediaManager.js';
2
4
  export declare class ReplaceSourceOnTimeCommand implements Command<void> {
5
+ #private;
6
+ private stateManager;
7
+ private deterministicMediaManager;
8
+ constructor(cradle: {
9
+ stateManager: StateManager;
10
+ deterministicMediaManager: DeterministicMediaManager;
11
+ });
3
12
  execute(args: unknown): Promise<void>;
4
13
  }
@@ -1,22 +1,55 @@
1
1
  import { z } from 'zod';
2
+ import { StateManager } from '../managers/StateManager.svelte.js';
3
+ import { DeterministicMediaManager } from '../managers/DeterministicMediaManager.js';
2
4
  const replaceSourceOnTimeSchema = z.object({
3
5
  componentId: z.string(),
4
6
  base64data: z.string(),
5
7
  time: z.number()
6
8
  });
7
9
  export class ReplaceSourceOnTimeCommand {
10
+ stateManager;
11
+ deterministicMediaManager;
12
+ constructor(cradle) {
13
+ this.stateManager = cradle.stateManager;
14
+ this.deterministicMediaManager = cradle.deterministicMediaManager;
15
+ }
8
16
  async execute(args) {
9
- // TODO: Complete implementation - this is work in progress
10
- // if (this.sceneBuilder.environment != 'server') {
11
- // this.sceneBuilder.log('replaceSource is only available in server environment');
12
- // return;
13
- // }
14
17
  const check = replaceSourceOnTimeSchema.safeParse(args);
15
18
  if (!check.success) {
16
- // this.sceneBuilder.log('ReplaceSourceOnTimeCommand failed with error: ' + check.error);
17
19
  return;
18
20
  }
19
- // WIP: Need to implement the actual source replacement logic
21
+ const { componentId, base64data, time } = check.data;
22
+ if (!this.deterministicMediaManager.isEnabled()) {
23
+ return;
24
+ }
25
+ const frameIndex = Math.max(0, Math.round(time * (this.stateManager.data.settings.fps || 30)));
26
+ const blob = this.#base64ToBlob(base64data);
27
+ if (!blob) {
28
+ return;
29
+ }
30
+ const cacheKey = `replace:${componentId}:${frameIndex}:${base64data.length}`;
31
+ this.deterministicMediaManager.setOneTimeOverride(componentId, frameIndex, {
32
+ kind: 'blob',
33
+ cacheKey,
34
+ blob
35
+ });
36
+ this.stateManager.markDirty();
20
37
  return;
21
38
  }
39
+ #base64ToBlob(base64Data) {
40
+ try {
41
+ const [header, encoded] = base64Data.split(',', 2);
42
+ const mimeMatch = /data:(.*?);base64/.exec(header ?? '');
43
+ const mimeType = mimeMatch?.[1] || 'image/png';
44
+ const binary = atob(encoded ?? base64Data);
45
+ const bytes = new Uint8Array(binary.length);
46
+ for (let index = 0; index < binary.length; index += 1) {
47
+ bytes[index] = binary.charCodeAt(index);
48
+ }
49
+ return new Blob([bytes], { type: mimeType });
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
22
55
  }
@@ -8,7 +8,7 @@ export declare class Component implements IComponent {
8
8
  componentContext: ComponentContext;
9
9
  });
10
10
  get id(): string;
11
- get type(): "IMAGE" | "GIF" | "VIDEO" | "TEXT" | "SHAPE" | "AUDIO" | "COLOR" | "GRADIENT" | "SUBTITLES";
11
+ get type(): "VIDEO" | "GIF" | "IMAGE" | "TEXT" | "SHAPE" | "AUDIO" | "COLOR" | "GRADIENT" | "SUBTITLES";
12
12
  get props(): ComponentProps;
13
13
  get displayObject(): import("pixi.js-legacy").Container<import("pixi.js-legacy").DisplayObject> | undefined;
14
14
  get context(): IComponentContext;
@@ -16,7 +16,7 @@ export declare class ComponentContext implements IComponentContext {
16
16
  get contextData(): ComponentData;
17
17
  get data(): ComponentData;
18
18
  get id(): string;
19
- get type(): "IMAGE" | "GIF" | "VIDEO" | "TEXT" | "SHAPE" | "AUDIO" | "COLOR" | "GRADIENT" | "SUBTITLES";
19
+ get type(): "VIDEO" | "GIF" | "IMAGE" | "TEXT" | "SHAPE" | "AUDIO" | "COLOR" | "GRADIENT" | "SUBTITLES";
20
20
  get isActive(): boolean;
21
21
  get progress(): number;
22
22
  get currentComponentTime(): number;
@@ -0,0 +1,13 @@
1
+ import type { HookType, IComponentContext, IComponentHook } from '../..';
2
+ import { DeterministicMediaManager } from '../../managers/DeterministicMediaManager.js';
3
+ import { StateManager } from '../../managers/StateManager.svelte.js';
4
+ export declare class DeterministicMediaFrameHook implements IComponentHook {
5
+ #private;
6
+ types: HookType[];
7
+ priority: number;
8
+ constructor(cradle: {
9
+ stateManager: StateManager;
10
+ deterministicMediaManager: DeterministicMediaManager;
11
+ });
12
+ handle(type: HookType, context: IComponentContext): Promise<void>;
13
+ }
@@ -0,0 +1,89 @@
1
+ import { DeterministicRenderError } from '../../schemas/runtime/deterministic.js';
2
+ import { DeterministicMediaManager } from '../../managers/DeterministicMediaManager.js';
3
+ import { StateManager } from '../../managers/StateManager.svelte.js';
4
+ export class DeterministicMediaFrameHook {
5
+ types = ['setup', 'update', 'destroy'];
6
+ priority = 4;
7
+ #context;
8
+ #state;
9
+ #manager;
10
+ constructor(cradle) {
11
+ this.#state = cradle.stateManager;
12
+ this.#manager = cradle.deterministicMediaManager;
13
+ }
14
+ #clearDeterministicResources(componentId) {
15
+ const cacheKeyChanged = this.#manager.clearCacheKey(componentId);
16
+ this.#context.removeResource('pixiResource');
17
+ this.#context.removeResource('imageElement');
18
+ return cacheKeyChanged;
19
+ }
20
+ async #handleUpdate() {
21
+ if (!this.#manager.isEnabled() || this.#state.environment !== 'server') {
22
+ return;
23
+ }
24
+ const fps = this.#state.data.settings.fps || 30;
25
+ const data = this.#context.contextData;
26
+ if (!this.#context.isActive) {
27
+ const cacheKeyChanged = this.#clearDeterministicResources(data.id);
28
+ if (cacheKeyChanged) {
29
+ this.#state.markDirty();
30
+ }
31
+ return;
32
+ }
33
+ const frameIndex = Math.max(0, Math.round(this.#context.currentComponentTime * fps));
34
+ const override = await this.#manager.resolveOverride({
35
+ componentId: data.id,
36
+ componentType: data.type === 'GIF' ? 'GIF' : 'VIDEO',
37
+ frameIndex,
38
+ fps,
39
+ width: this.#state.width,
40
+ height: this.#state.height
41
+ });
42
+ if (!override) {
43
+ const cacheKeyChanged = this.#clearDeterministicResources(data.id);
44
+ if (cacheKeyChanged) {
45
+ this.#state.markDirty();
46
+ }
47
+ if (this.#manager.config.strict && this.#manager.getProvider()) {
48
+ throw new DeterministicRenderError('Deterministic frame provider returned null', {
49
+ componentId: data.id,
50
+ frameIndex,
51
+ sceneTime: this.#state.currentTime
52
+ });
53
+ }
54
+ return;
55
+ }
56
+ this.#context.setResource('pixiResource', override.pixiResource);
57
+ if (override.imageElement) {
58
+ this.#context.setResource('imageElement', override.imageElement);
59
+ }
60
+ const cacheKeyChanged = this.#manager.commitCacheKey(data.id, override.cacheKey);
61
+ if (cacheKeyChanged) {
62
+ this.#state.markDirty();
63
+ }
64
+ }
65
+ async #handleDestroy() {
66
+ const data = this.#context.contextData;
67
+ this.#clearDeterministicResources(data.id);
68
+ try {
69
+ await this.#manager.releaseComponent(data.id);
70
+ }
71
+ catch {
72
+ // Best-effort cleanup only.
73
+ }
74
+ }
75
+ async handle(type, context) {
76
+ this.#context = context;
77
+ const data = this.#context.contextData;
78
+ if (data.type !== 'VIDEO' && data.type !== 'GIF') {
79
+ return;
80
+ }
81
+ if (type === 'update') {
82
+ await this.#handleUpdate();
83
+ return;
84
+ }
85
+ if (type === 'destroy') {
86
+ await this.#handleDestroy();
87
+ }
88
+ }
89
+ }
@@ -79,6 +79,10 @@ export class PixiDisplayObjectHook {
79
79
  // Regular shapes need texture to create sprite
80
80
  const texture = this.#context.getResource('pixiTexture');
81
81
  if (!texture) {
82
+ const type = this.#context.contextData.type;
83
+ if (type === 'VIDEO' || type === 'GIF') {
84
+ return;
85
+ }
82
86
  throw new Error('pixiTexture not found in resources.');
83
87
  }
84
88
  this.#pixiTexture = texture;
@@ -164,16 +164,38 @@ export class PixiSplitScreenDisplayObjectHook {
164
164
  }
165
165
  this.initMainSprite();
166
166
  }
167
+ #swapDisplayTexture(nextTexture) {
168
+ if (!this.#displayObject) {
169
+ return;
170
+ }
171
+ const previousTexture = this.#pixiTexture;
172
+ const walk = (node) => {
173
+ const children = node.children;
174
+ if (!Array.isArray(children)) {
175
+ return;
176
+ }
177
+ for (const child of children) {
178
+ const spriteLike = child;
179
+ if (spriteLike.texture && spriteLike.texture === previousTexture) {
180
+ spriteLike.texture = nextTexture;
181
+ }
182
+ walk(child);
183
+ }
184
+ };
185
+ walk(this.#displayObject);
186
+ this.#pixiTexture = nextTexture;
187
+ }
167
188
  async #handleUpdate() {
168
189
  const isActive = this.#context.isActive;
169
190
  if (isActive) {
170
191
  this.#drawBlurredBackground();
171
192
  }
172
193
  if (this.#displayObject) {
173
- // If the texture was swapped (e.g. by refresh:content), rebuild the display object
194
+ // Texture swaps are frequent in deterministic mode; update sprite textures
195
+ // in-place instead of rebuilding split/blur geometry each frame.
174
196
  const currentTexture = this.#context.getResource('pixiTexture');
175
197
  if (currentTexture && currentTexture !== this.#pixiTexture) {
176
- await this.#handleRefresh();
198
+ this.#swapDisplayTexture(currentTexture);
177
199
  }
178
200
  // Always re-assert the resource in case the context was cleared or updated
179
201
  this.#context.setResource('pixiRenderObject', this.#displayObject);
@@ -184,6 +206,10 @@ export class PixiSplitScreenDisplayObjectHook {
184
206
  }
185
207
  const texture = this.#context.getResource('pixiTexture');
186
208
  if (!texture) {
209
+ const type = this.#context.contextData.type;
210
+ if (type === 'VIDEO' || type === 'GIF') {
211
+ return;
212
+ }
187
213
  throw new Error('pixiTexture not found in resources.');
188
214
  }
189
215
  this.#pixiTexture = texture;