@viji-dev/core 0.8.5 → 0.8.7

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.
@@ -1520,7 +1520,7 @@ class lB {
1520
1520
  if (!this.isInitialized)
1521
1521
  return this.initPromise ? this.initPromise : (this.initPromise = (async () => {
1522
1522
  try {
1523
- const A = await import("./essentia.js-core.es-CC_3Ap1i.js"), B = await import("./essentia-wasm.web-DwSQMYtc.js").then((g) => g.e), I = A.Essentia || A.default?.Essentia || A.default;
1523
+ const A = await import("./essentia.js-core.es-CC_3Ap1i.js"), B = await import("./essentia-wasm.web-CB3NZc0t.js").then((g) => g.e), I = A.Essentia || A.default?.Essentia || A.default;
1524
1524
  let Q = B.default || B.EssentiaWASM || B.default?.EssentiaWASM;
1525
1525
  if (!Q)
1526
1526
  throw new Error("WASM module not found - check essentia-wasm.web.js export");
@@ -6941,7 +6941,9 @@ class K {
6941
6941
  isInitializing = !1;
6942
6942
  instanceId;
6943
6943
  screenRefreshRate = 60;
6944
- // Will be detected
6944
+ // Sensible default (most common rate); refined asynchronously after init
6945
+ refreshRateRafId = null;
6946
+ // Handle for the non-blocking refresh-rate measurement loop
6945
6947
  debugMode = !1;
6946
6948
  // Debug logging control
6947
6949
  /**
@@ -7001,7 +7003,6 @@ class K {
7001
7003
  eventSourceCore = null;
7002
7004
  frameSourceCores = [];
7003
7005
  eventSourceCleanup = null;
7004
- resizeSyncEnabled = !1;
7005
7006
  // Interaction state management
7006
7007
  currentInteractionEnabled;
7007
7008
  // Parameter system for Phase 2
@@ -7220,11 +7221,7 @@ class K {
7220
7221
  effectiveWidth: A.width,
7221
7222
  effectiveHeight: A.height,
7222
7223
  displayScale: this.iframeManager.getDisplayScale()
7223
- }), await this.detectScreenRefreshRate(), this.isDestroyed)
7224
- throw new S("Initialization cancelled", "INITIALIZATION_CANCELLED");
7225
- if (this.workerManager.postMessage("refresh-rate-update", {
7226
- screenRefreshRate: this.screenRefreshRate
7227
- }), this.config.audioStream && await this.setAudioStream(this.config.audioStream), this.isDestroyed)
7224
+ }), this.detectScreenRefreshRate(), this.config.audioStream && await this.setAudioStream(this.config.audioStream), this.isDestroyed)
7228
7225
  throw new S("Initialization cancelled", "INITIALIZATION_CANCELLED");
7229
7226
  for (let B = 0; B < this.audioStreams.length; B++) {
7230
7227
  const I = K.AUDIO_ADDITIONAL_BASE + B;
@@ -7605,49 +7602,32 @@ class K {
7605
7602
  }, [A]);
7606
7603
  }
7607
7604
  /**
7608
- * Link this core to receive events from a source core
7609
- * @param syncResolution Smart default: true for headless, false for visible
7605
+ * Link this core to receive input events (mouse/keyboard/touch) from a
7606
+ * source core. Event forwarding only — resolution and frame-rate are pushed
7607
+ * mixer→sources through the frame-source relationship (see
7608
+ * {@link linkFrameSources}), not here.
7610
7609
  */
7611
- linkEventSource(A, B) {
7610
+ linkEventSource(A) {
7612
7611
  if (this.eventSourceCore && this.unlinkEventSource(), this.eventSourceCore = A, A.iframeManager) {
7613
- const Q = A.iframeManager, g = (i) => {
7614
- this.injectMouseEvent(i);
7615
- }, E = (i) => {
7616
- this.injectKeyboardEvent(i);
7617
- }, C = (i) => {
7618
- this.injectTouchEvent(i);
7612
+ const B = A.iframeManager, I = (E) => {
7613
+ this.injectMouseEvent(E);
7614
+ }, Q = (E) => {
7615
+ this.injectKeyboardEvent(E);
7616
+ }, g = (E) => {
7617
+ this.injectTouchEvent(E);
7619
7618
  };
7620
- Q.onInteractionEvent("mouse-update", g), Q.onInteractionEvent("keyboard-update", E), Q.onInteractionEvent("touch-update", C), this.eventSourceCleanup = () => {
7621
- Q.offInteractionEvent("mouse-update", g), Q.offInteractionEvent("keyboard-update", E), Q.offInteractionEvent("touch-update", C);
7619
+ B.onInteractionEvent("mouse-update", I), B.onInteractionEvent("keyboard-update", Q), B.onInteractionEvent("touch-update", g), this.eventSourceCleanup = () => {
7620
+ B.offInteractionEvent("mouse-update", I), B.offInteractionEvent("keyboard-update", Q), B.offInteractionEvent("touch-update", g);
7622
7621
  };
7623
7622
  } else
7624
7623
  console.warn(`[${this.instanceId}] Cannot link event source - ${A.instanceId} has no iframeManager`);
7625
- const I = B ?? this.isHeadless;
7626
- if (this.resizeSyncEnabled = I, I && !A.isHeadless && A.iframeManager) {
7627
- const Q = A.iframeManager.getEffectiveResolution();
7628
- Q && (this.setResolution(Q), this.debugLog(`Resolution synced: ${Q.width}x${Q.height}`));
7629
- const g = () => {
7630
- if (this.resizeSyncEnabled && A.iframeManager) {
7631
- const E = A.iframeManager.getEffectiveResolution();
7632
- E && (this.debugLog(`[ResizeSync] ${A.instanceId} → ${this.instanceId}: ${E.width}x${E.height}`), this.setResolution(E));
7633
- }
7634
- };
7635
- if (A.config.hostContainer) {
7636
- const E = new ResizeObserver(g);
7637
- E.observe(A.config.hostContainer);
7638
- const C = this.eventSourceCleanup;
7639
- this.eventSourceCleanup = () => {
7640
- E.disconnect(), C && C();
7641
- };
7642
- }
7643
- }
7644
7624
  this.debugLog(`Linked event source: ${A.instanceId}`);
7645
7625
  }
7646
7626
  /**
7647
7627
  * Unlink from event source
7648
7628
  */
7649
7629
  unlinkEventSource() {
7650
- this.eventSourceCore && (this.eventSourceCleanup && (this.eventSourceCleanup(), this.eventSourceCleanup = null), this.eventSourceCore = null, this.resizeSyncEnabled = !1);
7630
+ this.eventSourceCore && (this.eventSourceCleanup && (this.eventSourceCleanup(), this.eventSourceCleanup = null), this.eventSourceCore = null);
7651
7631
  }
7652
7632
  /**
7653
7633
  * Link source cores that this core will pull frames from
@@ -7658,7 +7638,7 @@ class K {
7658
7638
  this.debugLog(`[Compositor] Mixer renderer type: ${B}`), A.forEach((I, Q) => {
7659
7639
  const g = I.sceneRenderer, E = B === "shader";
7660
7640
  this.debugLog(`[Compositor] Source ${Q}: ${g} → ${B}, flipY=${E}`), I.enableAutoCapture({ flipY: E });
7661
- }), this.debugLog(`[Compositor] Linked ${A.length} frame source(s)`);
7641
+ }), this.syncFrameSourceQuality(), this.debugLog(`[Compositor] Linked ${A.length} frame source(s)`);
7662
7642
  }
7663
7643
  /**
7664
7644
  * Unlink all frame sources
@@ -7666,6 +7646,26 @@ class K {
7666
7646
  unlinkFrameSources() {
7667
7647
  this.frameSourceCores.forEach((A) => A.disableAutoCapture()), this.frameSourceCores = [];
7668
7648
  }
7649
+ /**
7650
+ * Push this (mixer) core's effective resolution and frame-rate mode to every
7651
+ * linked frame source, so sources render at exactly the compositor's quality
7652
+ * (no wasted pixels/frames the compositor would discard). No-op unless frame
7653
+ * sources are linked, so normal single-core use is unaffected. Re-invoked on
7654
+ * link and on every `setResolution`/`setFrameRate`/`updateResolution`.
7655
+ *
7656
+ * Each source is guarded on `ready` (a not-yet-initialized or destroyed
7657
+ * source would throw from `setResolution`/`setFrameRate`). `setFrameRate` is
7658
+ * async; its promise is intentionally not awaited (fire-and-forget) with a
7659
+ * catch so a source torn down mid-call cannot raise an unhandled rejection.
7660
+ */
7661
+ syncFrameSourceQuality() {
7662
+ if (this.frameSourceCores.length === 0 || !this.iframeManager) return;
7663
+ const A = this.iframeManager.getEffectiveResolution(), B = this.config.frameRateMode;
7664
+ for (const I of this.frameSourceCores)
7665
+ I.ready && (I.setResolution({ width: A.width, height: A.height }), I.setFrameRate(B).catch(
7666
+ (Q) => this.debugLog(`[Compositor] frame-source setFrameRate failed: ${Q}`)
7667
+ ));
7668
+ }
7669
7669
  /**
7670
7670
  * Get parameter groups filtered by active capabilities
7671
7671
  */
@@ -8022,7 +8022,7 @@ class K {
8022
8022
  * @param mode - render every animation frame (`'full'`), every 2nd (`'half'`), every 4th (`'quarter'`), or every 8th (`'eighth'`)
8023
8023
  */
8024
8024
  async setFrameRate(A) {
8025
- this.validateReady(), this.config.frameRateMode = A, this.workerManager && this.workerManager.postMessage("frame-rate-update", { mode: A }), this.updateFrameRateStats(), this.debugLog(`Scene frame rate set to ${A} (${this.instanceId})`);
8025
+ this.validateReady(), this.config.frameRateMode = A, this.workerManager && this.workerManager.postMessage("frame-rate-update", { mode: A }), this.updateFrameRateStats(), this.syncFrameSourceQuality(), this.debugLog(`Scene frame rate set to ${A} (${this.instanceId})`);
8026
8026
  }
8027
8027
  /**
8028
8028
  * Sets the CV processing frame rate mode (relative to scene frame rate)
@@ -8042,7 +8042,7 @@ class K {
8042
8042
  effectiveWidth: A.width,
8043
8043
  effectiveHeight: A.height,
8044
8044
  displayScale: this.iframeManager.getDisplayScale()
8045
- }), this.stats.resolution = A, this.stats.scale = this.iframeManager.getScale(), this.debugLog(`Resolution updated successfully (${this.instanceId})`, A);
8045
+ }), this.stats.resolution = A, this.stats.scale = this.iframeManager.getScale(), this.syncFrameSourceQuality(), this.debugLog(`Resolution updated successfully (${this.instanceId})`, A);
8046
8046
  }
8047
8047
  /**
8048
8048
  * Sets the audio stream for analysis
@@ -8498,22 +8498,33 @@ class K {
8498
8498
  effectiveWidth: B.width,
8499
8499
  effectiveHeight: B.height,
8500
8500
  displayScale: this.iframeManager.getDisplayScale()
8501
- }), this.stats.resolution = B, this.debugLog(`Resolution: ${B.width}x${B.height}`);
8501
+ }), this.stats.resolution = B, this.syncFrameSourceQuality(), this.debugLog(`Resolution: ${B.width}x${B.height}`);
8502
8502
  }
8503
8503
  /**
8504
- * Detects the screen refresh rate
8504
+ * Measures the screen refresh rate over 60 animation frames, off the init
8505
+ * critical path. Runs fire-and-forget: it does NOT gate `ready`, so the host
8506
+ * can control a visibly-rendering core immediately. On completion it pushes
8507
+ * the refined value to the worker (refresh-rate-update) and updates host
8508
+ * stats. Until then both sides use the 60 default. Measured once, never
8509
+ * re-run. Cancelled on `destroy()` via `refreshRateRafId`.
8505
8510
  */
8506
- async detectScreenRefreshRate() {
8507
- return new Promise((A) => {
8508
- let B = 0;
8509
- const I = performance.now(), Q = () => {
8510
- if (B++, B === 60) {
8511
- const g = performance.now() - I;
8512
- this.screenRefreshRate = Math.round(6e4 / g), this.debugLog("Detected screen refresh rate:", this.screenRefreshRate + "Hz"), A();
8513
- } else B < 60 && requestAnimationFrame(Q);
8514
- };
8515
- requestAnimationFrame(Q);
8516
- });
8511
+ detectScreenRefreshRate() {
8512
+ let A = 0;
8513
+ const B = performance.now(), I = () => {
8514
+ if (this.isDestroyed) {
8515
+ this.refreshRateRafId = null;
8516
+ return;
8517
+ }
8518
+ if (A++, A === 60) {
8519
+ this.refreshRateRafId = null;
8520
+ const Q = performance.now() - B;
8521
+ if (this.screenRefreshRate = Math.round(6e4 / Q), this.debugLog("Detected screen refresh rate:", this.screenRefreshRate + "Hz"), this.isDestroyed || !this.workerManager) return;
8522
+ this.workerManager.postMessage("refresh-rate-update", {
8523
+ screenRefreshRate: this.screenRefreshRate
8524
+ }), this.stats.frameRate.screenRefreshRate = this.screenRefreshRate;
8525
+ } else A < 60 && (this.refreshRateRafId = requestAnimationFrame(I));
8526
+ };
8527
+ this.refreshRateRafId = requestAnimationFrame(I);
8517
8528
  }
8518
8529
  /**
8519
8530
  * Updates frame rate statistics
@@ -8693,7 +8704,7 @@ class K {
8693
8704
  */
8694
8705
  async destroy() {
8695
8706
  if (!this.isDestroyed) {
8696
- this.isDestroyed = !0, this.parameterGroups.clear(), this.parameterValues.clear(), this.parameterListeners.clear(), this.parameterDefinedListeners.clear(), this.parameterErrorListeners.clear(), this.capabilitiesChangeListeners.clear(), this.stateImportErrorListeners.clear(), this.sceneRuntimeErrorListeners.clear(), this.sceneConsoleListeners.clear(), this.performanceSnapshotListeners.clear(), this.capabilityTransitionListeners.clear(), this.unlinkEventSource(), this.unlinkFrameSources();
8707
+ this.isDestroyed = !0, this.refreshRateRafId !== null && (cancelAnimationFrame(this.refreshRateRafId), this.refreshRateRafId = null), this.parameterGroups.clear(), this.parameterValues.clear(), this.parameterListeners.clear(), this.parameterDefinedListeners.clear(), this.parameterErrorListeners.clear(), this.capabilitiesChangeListeners.clear(), this.stateImportErrorListeners.clear(), this.sceneRuntimeErrorListeners.clear(), this.sceneConsoleListeners.clear(), this.performanceSnapshotListeners.clear(), this.capabilityTransitionListeners.clear(), this.unlinkEventSource(), this.unlinkFrameSources();
8697
8708
  for (const [A] of this.deviceVideoCoordinators)
8698
8709
  await this.clearDeviceVideo(A);
8699
8710
  this.deviceVideoCoordinators.clear(), this.deviceVideoStreamIndices.clear(), this.deviceSensorManager && (this.deviceSensorManager.destroy(), this.deviceSensorManager = null);
@@ -8752,7 +8763,7 @@ function DI(h) {
8752
8763
  expectedVersion: 1
8753
8764
  } : typeof A.senderTime != "number" ? { code: "invalid-field", details: "senderTime must be a number", field: "senderTime" } : null;
8754
8765
  }
8755
- const hI = "0.8.4";
8766
+ const hI = "0.8.6";
8756
8767
  export {
8757
8768
  yA as A,
8758
8769
  hI as V,
@@ -8760,4 +8771,4 @@ export {
8760
8771
  S as b,
8761
8772
  PB as g
8762
8773
  };
8763
- //# sourceMappingURL=index-DbnjzNSY.js.map
8774
+ //# sourceMappingURL=index-I8omPYZ-.js.map
package/dist/index.d.ts CHANGED
@@ -1273,14 +1273,16 @@ export declare interface FramePerfSample {
1273
1273
  declare interface FrameRateInfo {
1274
1274
  mode: FrameRateMode;
1275
1275
  /**
1276
- * Best-effort display refresh rate in Hz, detected once at init by averaging
1277
- * 60 `requestAnimationFrame` intervals.
1276
+ * Best-effort display refresh rate in Hz, measured once by averaging 60
1277
+ * `requestAnimationFrame` intervals. The measurement runs asynchronously and
1278
+ * does not block `initialize()`, so for the first few seconds after init this
1279
+ * reads the `60` default until the measured value lands.
1278
1280
  *
1279
1281
  * **Not reliable under load** — if the page is already GPU-stressed during
1280
1282
  * initialization, throttled rAFs inflate the interval and deflate the value
1281
1283
  * (e.g. 10 Hz on a 60 Hz monitor), and it is never re-measured. rAF cadence
1282
1284
  * cannot recover the true refresh post-load. **Do not derive performance
1283
- * thresholds from this** — prefer an absolute FPS target.
1285
+ * thresholds from this** — prefer an absolute FPS target or `effectiveRefreshRate`.
1284
1286
  */
1285
1287
  screenRefreshRate: number;
1286
1288
  effectiveRefreshRate: number;
@@ -2657,7 +2659,7 @@ declare type TrackingState = 'TRACKING' | 'LOCKED' | 'BREAKDOWN' | 'LOST';
2657
2659
  /** Returned by `on*` listener registration calls; invoke to unsubscribe. */
2658
2660
  export declare type Unsubscribe = () => void;
2659
2661
 
2660
- export declare const VERSION = "0.8.4";
2662
+ export declare const VERSION = "0.8.6";
2661
2663
 
2662
2664
  /**
2663
2665
  * Real-time video API: drawable frame, dimensions, and the source-side
@@ -3022,6 +3024,7 @@ export declare class VijiCore {
3022
3024
  private isInitializing;
3023
3025
  private instanceId;
3024
3026
  private screenRefreshRate;
3027
+ private refreshRateRafId;
3025
3028
  private debugMode;
3026
3029
  /**
3027
3030
  * Debug logging helper
@@ -3051,7 +3054,6 @@ export declare class VijiCore {
3051
3054
  private eventSourceCore;
3052
3055
  private frameSourceCores;
3053
3056
  private eventSourceCleanup;
3054
- private resizeSyncEnabled;
3055
3057
  private currentInteractionEnabled;
3056
3058
  private parameterGroups;
3057
3059
  private parameterValues;
@@ -3259,10 +3261,12 @@ export declare class VijiCore {
3259
3261
  */
3260
3262
  injectFrame(bitmap: ImageBitmap, streamIndex: number): void;
3261
3263
  /**
3262
- * Link this core to receive events from a source core
3263
- * @param syncResolution Smart default: true for headless, false for visible
3264
+ * Link this core to receive input events (mouse/keyboard/touch) from a
3265
+ * source core. Event forwarding only — resolution and frame-rate are pushed
3266
+ * mixer→sources through the frame-source relationship (see
3267
+ * {@link linkFrameSources}), not here.
3264
3268
  */
3265
- linkEventSource(sourceCore: VijiCore, syncResolution?: boolean): void;
3269
+ linkEventSource(sourceCore: VijiCore): void;
3266
3270
  /**
3267
3271
  * Unlink from event source
3268
3272
  */
@@ -3275,6 +3279,19 @@ export declare class VijiCore {
3275
3279
  * Unlink all frame sources
3276
3280
  */
3277
3281
  unlinkFrameSources(): void;
3282
+ /**
3283
+ * Push this (mixer) core's effective resolution and frame-rate mode to every
3284
+ * linked frame source, so sources render at exactly the compositor's quality
3285
+ * (no wasted pixels/frames the compositor would discard). No-op unless frame
3286
+ * sources are linked, so normal single-core use is unaffected. Re-invoked on
3287
+ * link and on every `setResolution`/`setFrameRate`/`updateResolution`.
3288
+ *
3289
+ * Each source is guarded on `ready` (a not-yet-initialized or destroyed
3290
+ * source would throw from `setResolution`/`setFrameRate`). `setFrameRate` is
3291
+ * async; its promise is intentionally not awaited (fire-and-forget) with a
3292
+ * catch so a source torn down mid-call cannot raise an unhandled rejection.
3293
+ */
3294
+ private syncFrameSourceQuality;
3278
3295
  /**
3279
3296
  * Get parameter groups filtered by active capabilities
3280
3297
  */
@@ -3804,7 +3821,12 @@ export declare class VijiCore {
3804
3821
  height: number;
3805
3822
  }): void;
3806
3823
  /**
3807
- * Detects the screen refresh rate
3824
+ * Measures the screen refresh rate over 60 animation frames, off the init
3825
+ * critical path. Runs fire-and-forget: it does NOT gate `ready`, so the host
3826
+ * can control a visibly-rendering core immediately. On completion it pushes
3827
+ * the refined value to the worker (refresh-rate-update) and updates host
3828
+ * stats. Until then both sides use the 60 default. Measured once, never
3829
+ * re-run. Cancelled on `destroy()` via `refreshRateRafId`.
3808
3830
  */
3809
3831
  private detectScreenRefreshRate;
3810
3832
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as a, V as i, a as s, b as e } from "./index-DbnjzNSY.js";
1
+ import { A as a, V as i, a as s, b as e } from "./index-I8omPYZ-.js";
2
2
  export {
3
3
  a as AudioSystem,
4
4
  i as VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viji-dev/core",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Universal execution engine for Viji Creative scenes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",