@viji-dev/core 0.11.5 → 0.11.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.
package/dist/docs-api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export const docsApi = {
2
2
  "version": "1.1.0",
3
- "coreVersion": "0.11.5",
4
- "generatedAt": "2026-07-19T11:29:49.493Z",
3
+ "coreVersion": "0.11.7",
4
+ "generatedAt": "2026-07-24T15:25:39.379Z",
5
5
  "navigation": [
6
6
  {
7
7
  "id": "getting-started",
@@ -1,4 +1,4 @@
1
- import { g as kn } from "./index-CFKPh6Ia.js";
1
+ import { g as kn } from "./index-zKfpXJhf.js";
2
2
  function jn(Oe, ot) {
3
3
  for (var ve = 0; ve < ot.length; ve++) {
4
4
  const Z = ot[ve];
@@ -3374,4 +3374,4 @@ const xn = /* @__PURE__ */ kn(yi), Ln = /* @__PURE__ */ jn({
3374
3374
  export {
3375
3375
  Ln as e
3376
3376
  };
3377
- //# sourceMappingURL=essentia-wasm.web-BjQ6Tlu7.js.map
3377
+ //# sourceMappingURL=essentia-wasm.web-QxZd-8n4.js.map
@@ -547,7 +547,7 @@ class lB {
547
547
  }
548
548
  }
549
549
  }
550
- const nB = "" + new URL("assets/viji.worker-Dz7THSog.js", import.meta.url).href;
550
+ const nB = "" + new URL("assets/viji.worker-BKg5vsqf.js", import.meta.url).href;
551
551
  class dB {
552
552
  constructor(A, B, I) {
553
553
  this.iframeManager = A, this.sceneCode = B, this.init = I;
@@ -1518,7 +1518,7 @@ class HB {
1518
1518
  if (!this.isInitialized)
1519
1519
  return this.initPromise ? this.initPromise : (this.initPromise = (async () => {
1520
1520
  try {
1521
- const A = await import("./essentia.js-core.es-CC_3Ap1i.js"), B = await import("./essentia-wasm.web-BjQ6Tlu7.js").then((g) => g.e), I = A.Essentia || A.default?.Essentia || A.default;
1521
+ const A = await import("./essentia.js-core.es-CC_3Ap1i.js"), B = await import("./essentia-wasm.web-QxZd-8n4.js").then((g) => g.e), I = A.Essentia || A.default?.Essentia || A.default;
1522
1522
  let Q = B.default || B.EssentiaWASM || B.default?.EssentiaWASM;
1523
1523
  if (!Q)
1524
1524
  throw new Error("WASM module not found - check essentia-wasm.web.js export");
@@ -5179,6 +5179,16 @@ class eA {
5179
5179
  // Shared AudioContext (one for all channels)
5180
5180
  audioContext = null;
5181
5181
  workletRegistered = !1;
5182
+ /**
5183
+ * Persistent export capture tap (main channel only). Created lazily by
5184
+ * `getAudioCaptureStream()`, lives for the AudioSystem lifetime, and is
5185
+ * torn down only in `resetAudioState()`. Deliberately NOT severed by
5186
+ * `disconnectMainStream()`: on mid-recording disconnect the tap stream
5187
+ * goes silent while its tracks stay live, and `connectChannel()` re-wires
5188
+ * each new main source node into it, so the exposed MediaStream stays
5189
+ * stable across `setAudioStream()` swaps (a recorder never renegotiates).
5190
+ */
5191
+ captureDestination = null;
5182
5192
  // Main audio channel (index 0, full analysis with beat detection)
5183
5193
  mainChannel;
5184
5194
  // Additional audio channels (lightweight analysis, no beat detection)
@@ -5598,6 +5608,20 @@ class eA {
5598
5608
  getAudioState() {
5599
5609
  return { ...this.mainChannel.audioState };
5600
5610
  }
5611
+ /**
5612
+ * Get a recordable MediaStream carrying the main channel's audio, for
5613
+ * host-side export recording. Main channel only — additional channels and
5614
+ * source-fed device channels (analysis values, no PCM) are not included.
5615
+ *
5616
+ * The underlying destination node is created lazily on first call and then
5617
+ * persists for the AudioSystem lifetime: on a source swap the stream keeps
5618
+ * flowing with the new audio, and on disconnect it goes silent with tracks
5619
+ * still live. Returns `null` when the main channel is not connected at
5620
+ * call time (or after teardown).
5621
+ */
5622
+ getAudioCaptureStream() {
5623
+ return this.destroyed || !this.mainChannel.audioState.isConnected || !this.audioContext ? null : (this.captureDestination || (this.captureDestination = this.audioContext.createMediaStreamDestination()), this.mainChannel.mediaStreamSource && this.mainChannel.mediaStreamSource.connect(this.captureDestination), this.captureDestination.stream);
5624
+ }
5601
5625
  /**
5602
5626
  * Initialize Essentia.js (async WASM loading)
5603
5627
  */
@@ -5659,7 +5683,7 @@ class eA {
5659
5683
  }
5660
5684
  try {
5661
5685
  const g = await this.ensureAudioContext();
5662
- A.mediaStreamSource = g.createMediaStreamSource(B), await this.setupChannelWorklet(A, I) && A.workletNode ? (A.analysisMode = "worklet", A.currentSampleRate = g.sampleRate, A.mediaStreamSource.connect(A.workletNode), A.workletNode.connect(g.destination), this.debugLog(`Audio worklet enabled for channel ${A.streamIndex}`), A.workletWatchdog = setTimeout(() => {
5686
+ A.mediaStreamSource = g.createMediaStreamSource(B), I && this.captureDestination && A.mediaStreamSource.connect(this.captureDestination), await this.setupChannelWorklet(A, I) && A.workletNode ? (A.analysisMode = "worklet", A.currentSampleRate = g.sampleRate, A.mediaStreamSource.connect(A.workletNode), A.workletNode.connect(g.destination), this.debugLog(`Audio worklet enabled for channel ${A.streamIndex}`), A.workletWatchdog = setTimeout(() => {
5663
5687
  if (A.workletWatchdog = null, A.analysisMode === "worklet" && A.workletFrameCount === 0) {
5664
5688
  if (this.debugLog(`[AudioSystem] Worklet silent for channel ${A.streamIndex}, falling back to analyser.`), A.analysisMode = "analyser", A.workletNode) {
5665
5689
  try {
@@ -5943,7 +5967,13 @@ class eA {
5943
5967
  this.destroyed = !0, this.stopAnalysisLoop(), this.stopStalenessTimer(), this.stopIdleTicker(), this.resetEssentiaBandHistories();
5944
5968
  for (const B of this.additionalChannels.values())
5945
5969
  B.destroy();
5946
- this.additionalChannels.clear(), this.mainChannel.disconnectNodes(), this.mainChannel.audioState.isConnected = !1, this.mainChannel.isAnalysisRunning = !1, this.mainChannel.currentStream = null;
5970
+ if (this.additionalChannels.clear(), this.mainChannel.disconnectNodes(), this.mainChannel.audioState.isConnected = !1, this.mainChannel.isAnalysisRunning = !1, this.mainChannel.currentStream = null, this.captureDestination) {
5971
+ try {
5972
+ this.captureDestination.disconnect();
5973
+ } catch {
5974
+ }
5975
+ this.captureDestination = null;
5976
+ }
5947
5977
  const A = this.audioContext;
5948
5978
  if (this.audioContext = null, A && A.state !== "closed")
5949
5979
  try {
@@ -9124,6 +9154,16 @@ class sA {
9124
9154
  getMainAudioStream() {
9125
9155
  return this.mainAudioStream;
9126
9156
  }
9157
+ /**
9158
+ * Recordable tap on the main channel's audio, for export recording.
9159
+ * Pure read: uses the non-materializing `audio` getter — a capture stream
9160
+ * can only exist once the audio engine is already live, so a headless
9161
+ * preview constructs nothing here. No connectivity edge: capability state
9162
+ * is unchanged.
9163
+ */
9164
+ getAudioCaptureStream() {
9165
+ return this.audio?.getAudioCaptureStream() ?? null;
9166
+ }
9127
9167
  get hasMainAudio() {
9128
9168
  return this.mainAudioStream !== null;
9129
9169
  }
@@ -9793,7 +9833,9 @@ class rA {
9793
9833
  const B = A.format || "blob", I = await this.workerManager.sendMessage("capture-frame", {
9794
9834
  format: B,
9795
9835
  type: B === "blob" ? A.type : void 0,
9796
- resolution: A.resolution
9836
+ quality: B === "blob" ? A.quality : void 0,
9837
+ resolution: A.resolution,
9838
+ frame: A.frame
9797
9839
  }, 1e4);
9798
9840
  return B === "bitmap" ? I.bitmap : I.blob;
9799
9841
  }
@@ -9821,11 +9863,17 @@ class rA {
9821
9863
  /**
9822
9864
  * Enable auto-capture. Captured frames are retrieved via getLatestFrame().
9823
9865
  * @param format.flipY — true for WebGL texture consumers, false for 2D canvas drawImage.
9866
+ * @param format.frame — frame role: `'plate'` (default) captures the clean
9867
+ * pre-overlay frame (thumbnails, VJ sources); `'display'` captures the
9868
+ * composited frame the viewer sees, including the branding overlay when the
9869
+ * session applies it. Headless cores degrade `'display'` to `'plate'`.
9870
+ * Omitting the field on a re-enable keeps the previously set role.
9824
9871
  */
9825
9872
  enableAutoCapture(A) {
9826
9873
  this.autoCaptureEnabled = !0, this.workerManager && this.workerManager.postMessage("enable-auto-capture", {
9827
9874
  enabled: !0,
9828
- flipY: A?.flipY
9875
+ flipY: A?.flipY,
9876
+ frame: A?.frame
9829
9877
  });
9830
9878
  const B = A ? ` with flipY=${A.flipY}` : "";
9831
9879
  this.debugLog(`[AutoCapture] ENABLED${B}`);
@@ -10878,6 +10926,38 @@ class rA {
10878
10926
  getAudioStream() {
10879
10927
  return this.engineGroup.getMainAudioStream();
10880
10928
  }
10929
+ /**
10930
+ * Gets a recordable MediaStream carrying the main audio channel's signal,
10931
+ * for client-side export recording (e.g. feeding a WebCodecs
10932
+ * `AudioEncoder` or `MediaRecorder` while capturing scene video).
10933
+ *
10934
+ * The returned stream is stable across `setAudioStream()` swaps: obtain it
10935
+ * once at record start and keep recording — a source change flows into the
10936
+ * same stream with no renegotiation. If audio is disconnected
10937
+ * mid-recording the stream goes **silent** (tracks stay live, the A/V
10938
+ * timeline stays continuous); its tracks end only when the media engines
10939
+ * are disposed (last core sharing them destroyed).
10940
+ *
10941
+ * Scope: main channel only. Additional audio channels and remote-device
10942
+ * audio (analysis values only, no PCM host-side) are not included.
10943
+ * Engine-scoped — identical for all cores sharing this core's media
10944
+ * engines, like {@link getAudioStream}.
10945
+ *
10946
+ * @returns The capture stream, or `null` when no main audio is connected
10947
+ * at call time.
10948
+ *
10949
+ * @example
10950
+ * ```ts
10951
+ * const audioTap = core.getAudioCaptureStream();
10952
+ * if (audioTap) {
10953
+ * const [track] = audioTap.getAudioTracks();
10954
+ * // feed track into MediaStreamTrackProcessor → AudioEncoder
10955
+ * }
10956
+ * ```
10957
+ */
10958
+ getAudioCaptureStream() {
10959
+ return this.engineGroup.getAudioCaptureStream();
10960
+ }
10881
10961
  /**
10882
10962
  * Enables or disables user interactions (mouse, keyboard, touch) at runtime
10883
10963
  */
@@ -11551,7 +11631,7 @@ function HI(D) {
11551
11631
  expectedVersion: 1
11552
11632
  } : typeof A.senderTime != "number" ? { code: "invalid-field", details: "senderTime must be a number", field: "senderTime" } : null;
11553
11633
  }
11554
- const mI = "0.11.5";
11634
+ const mI = "0.11.7";
11555
11635
  export {
11556
11636
  eA as A,
11557
11637
  LI as C,
@@ -11562,4 +11642,4 @@ export {
11562
11642
  J as c,
11563
11643
  gI as g
11564
11644
  };
11565
- //# sourceMappingURL=index-CFKPh6Ia.js.map
11645
+ //# sourceMappingURL=index-zKfpXJhf.js.map
package/dist/index.d.ts CHANGED
@@ -239,6 +239,16 @@ declare interface AudioStreamAPI {
239
239
  export declare class AudioSystem {
240
240
  private audioContext;
241
241
  private workletRegistered;
242
+ /**
243
+ * Persistent export capture tap (main channel only). Created lazily by
244
+ * `getAudioCaptureStream()`, lives for the AudioSystem lifetime, and is
245
+ * torn down only in `resetAudioState()`. Deliberately NOT severed by
246
+ * `disconnectMainStream()`: on mid-recording disconnect the tap stream
247
+ * goes silent while its tracks stay live, and `connectChannel()` re-wires
248
+ * each new main source node into it, so the exposed MediaStream stays
249
+ * stable across `setAudioStream()` swaps (a recorder never renegotiates).
250
+ */
251
+ private captureDestination;
242
252
  private mainChannel;
243
253
  private additionalChannels;
244
254
  private analysisInterval;
@@ -372,6 +382,18 @@ export declare class AudioSystem {
372
382
  flatness: number;
373
383
  };
374
384
  };
385
+ /**
386
+ * Get a recordable MediaStream carrying the main channel's audio, for
387
+ * host-side export recording. Main channel only — additional channels and
388
+ * source-fed device channels (analysis values, no PCM) are not included.
389
+ *
390
+ * The underlying destination node is created lazily on first call and then
391
+ * persists for the AudioSystem lifetime: on a source swap the stream keeps
392
+ * flowing with the new audio, and on disconnect it goes silent with tracks
393
+ * still live. Returns `null` when the main channel is not connected at
394
+ * call time (or after teardown).
395
+ */
396
+ getAudioCaptureStream(): MediaStream | null;
375
397
  /**
376
398
  * Initialize Essentia.js (async WASM loading)
377
399
  */
@@ -894,6 +916,13 @@ export declare interface CaptureFrameOptions {
894
916
  format?: 'blob' | 'bitmap';
895
917
  /** MIME type for `'blob'` output (ignored for `'bitmap'`). Examples: `'image/png'`, `'image/jpeg'`, `'image/webp'`. */
896
918
  type?: string;
919
+ /**
920
+ * Encoding quality for lossy `'blob'` formats (`'image/jpeg'`, `'image/webp'`),
921
+ * from `0` (smallest) to `1` (best). Values outside the range are clamped.
922
+ * Ignored for `'image/png'` and for `format: 'bitmap'`. When omitted, the
923
+ * browser's default quality is used.
924
+ */
925
+ quality?: number;
897
926
  /**
898
927
  * Target resolution.
899
928
  * - `number`: scale factor relative to the current canvas size (e.g., `0.5` = 50%).
@@ -901,6 +930,16 @@ export declare interface CaptureFrameOptions {
901
930
  * canvas, the source is center-cropped before scaling.
902
931
  */
903
932
  resolution?: number | Resolution;
933
+ /**
934
+ * Frame role to capture. `'plate'` (default) captures the clean pre-overlay
935
+ * frame — thumbnails and VJ source frames, identical to previous behavior.
936
+ * `'display'` captures the composited frame the viewer sees, including the
937
+ * branding overlay when the session applies it. On headless cores no
938
+ * overlay is ever drawn, so `'display'` degrades to `'plate'`. Note:
939
+ * center-cropping via `resolution` can crop the edge-anchored overlay out
940
+ * of a `'display'` capture — display captures are intended at canvas aspect.
941
+ */
942
+ frame?: 'plate' | 'display';
904
943
  }
905
944
 
906
945
  /**
@@ -2832,7 +2871,7 @@ declare type TrackingState = 'TRACKING' | 'LOCKED' | 'BREAKDOWN' | 'LOST';
2832
2871
  /** Returned by `on*` listener registration calls; invoke to unsubscribe. */
2833
2872
  export declare type Unsubscribe = () => void;
2834
2873
 
2835
- export declare const VERSION = "0.11.5";
2874
+ export declare const VERSION = "0.11.7";
2836
2875
 
2837
2876
  /**
2838
2877
  * Real-time video API: drawable frame, dimensions, and the source-side
@@ -3262,9 +3301,15 @@ export declare class VijiCore {
3262
3301
  /**
3263
3302
  * Enable auto-capture. Captured frames are retrieved via getLatestFrame().
3264
3303
  * @param format.flipY — true for WebGL texture consumers, false for 2D canvas drawImage.
3304
+ * @param format.frame — frame role: `'plate'` (default) captures the clean
3305
+ * pre-overlay frame (thumbnails, VJ sources); `'display'` captures the
3306
+ * composited frame the viewer sees, including the branding overlay when the
3307
+ * session applies it. Headless cores degrade `'display'` to `'plate'`.
3308
+ * Omitting the field on a re-enable keeps the previously set role.
3265
3309
  */
3266
3310
  enableAutoCapture(format?: {
3267
3311
  flipY: boolean;
3312
+ frame?: 'plate' | 'display';
3268
3313
  }): void;
3269
3314
  /**
3270
3315
  * Disable auto-capture and release any pending frame buffer.
@@ -3735,6 +3780,36 @@ export declare class VijiCore {
3735
3780
  * sharing this core's media engines)
3736
3781
  */
3737
3782
  getAudioStream(): MediaStream | null;
3783
+ /**
3784
+ * Gets a recordable MediaStream carrying the main audio channel's signal,
3785
+ * for client-side export recording (e.g. feeding a WebCodecs
3786
+ * `AudioEncoder` or `MediaRecorder` while capturing scene video).
3787
+ *
3788
+ * The returned stream is stable across `setAudioStream()` swaps: obtain it
3789
+ * once at record start and keep recording — a source change flows into the
3790
+ * same stream with no renegotiation. If audio is disconnected
3791
+ * mid-recording the stream goes **silent** (tracks stay live, the A/V
3792
+ * timeline stays continuous); its tracks end only when the media engines
3793
+ * are disposed (last core sharing them destroyed).
3794
+ *
3795
+ * Scope: main channel only. Additional audio channels and remote-device
3796
+ * audio (analysis values only, no PCM host-side) are not included.
3797
+ * Engine-scoped — identical for all cores sharing this core's media
3798
+ * engines, like {@link getAudioStream}.
3799
+ *
3800
+ * @returns The capture stream, or `null` when no main audio is connected
3801
+ * at call time.
3802
+ *
3803
+ * @example
3804
+ * ```ts
3805
+ * const audioTap = core.getAudioCaptureStream();
3806
+ * if (audioTap) {
3807
+ * const [track] = audioTap.getAudioTracks();
3808
+ * // feed track into MediaStreamTrackProcessor → AudioEncoder
3809
+ * }
3810
+ * ```
3811
+ */
3812
+ getAudioCaptureStream(): MediaStream | null;
3738
3813
  /**
3739
3814
  * Enables or disables user interactions (mouse, keyboard, touch) at runtime
3740
3815
  */
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as e, C as i, I as r, V as o, a as C, c as E, b as A } from "./index-CFKPh6Ia.js";
1
+ import { A as e, C as i, I as r, V as o, a as C, c as E, b as A } from "./index-zKfpXJhf.js";
2
2
  export {
3
3
  e as AudioSystem,
4
4
  i as CV_FEATURE_KEYS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viji-dev/core",
3
- "version": "0.11.5",
3
+ "version": "0.11.7",
4
4
  "description": "Universal execution engine for Viji Creative scenes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",