@viji-dev/core 0.11.5 → 0.11.6

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.6",
4
+ "generatedAt": "2026-07-24T10:51:17.968Z",
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-BKvsDqj0.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-B5WvSGTX.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-Bu5UOGpW.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-B5WvSGTX.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,6 +9833,7 @@ 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,
9836
+ quality: B === "blob" ? A.quality : void 0,
9796
9837
  resolution: A.resolution
9797
9838
  }, 1e4);
9798
9839
  return B === "bitmap" ? I.bitmap : I.blob;
@@ -10878,6 +10919,38 @@ class rA {
10878
10919
  getAudioStream() {
10879
10920
  return this.engineGroup.getMainAudioStream();
10880
10921
  }
10922
+ /**
10923
+ * Gets a recordable MediaStream carrying the main audio channel's signal,
10924
+ * for client-side export recording (e.g. feeding a WebCodecs
10925
+ * `AudioEncoder` or `MediaRecorder` while capturing scene video).
10926
+ *
10927
+ * The returned stream is stable across `setAudioStream()` swaps: obtain it
10928
+ * once at record start and keep recording — a source change flows into the
10929
+ * same stream with no renegotiation. If audio is disconnected
10930
+ * mid-recording the stream goes **silent** (tracks stay live, the A/V
10931
+ * timeline stays continuous); its tracks end only when the media engines
10932
+ * are disposed (last core sharing them destroyed).
10933
+ *
10934
+ * Scope: main channel only. Additional audio channels and remote-device
10935
+ * audio (analysis values only, no PCM host-side) are not included.
10936
+ * Engine-scoped — identical for all cores sharing this core's media
10937
+ * engines, like {@link getAudioStream}.
10938
+ *
10939
+ * @returns The capture stream, or `null` when no main audio is connected
10940
+ * at call time.
10941
+ *
10942
+ * @example
10943
+ * ```ts
10944
+ * const audioTap = core.getAudioCaptureStream();
10945
+ * if (audioTap) {
10946
+ * const [track] = audioTap.getAudioTracks();
10947
+ * // feed track into MediaStreamTrackProcessor → AudioEncoder
10948
+ * }
10949
+ * ```
10950
+ */
10951
+ getAudioCaptureStream() {
10952
+ return this.engineGroup.getAudioCaptureStream();
10953
+ }
10881
10954
  /**
10882
10955
  * Enables or disables user interactions (mouse, keyboard, touch) at runtime
10883
10956
  */
@@ -11551,7 +11624,7 @@ function HI(D) {
11551
11624
  expectedVersion: 1
11552
11625
  } : typeof A.senderTime != "number" ? { code: "invalid-field", details: "senderTime must be a number", field: "senderTime" } : null;
11553
11626
  }
11554
- const mI = "0.11.5";
11627
+ const mI = "0.11.6";
11555
11628
  export {
11556
11629
  eA as A,
11557
11630
  LI as C,
@@ -11562,4 +11635,4 @@ export {
11562
11635
  J as c,
11563
11636
  gI as g
11564
11637
  };
11565
- //# sourceMappingURL=index-CFKPh6Ia.js.map
11638
+ //# sourceMappingURL=index-BKvsDqj0.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%).
@@ -2832,7 +2861,7 @@ declare type TrackingState = 'TRACKING' | 'LOCKED' | 'BREAKDOWN' | 'LOST';
2832
2861
  /** Returned by `on*` listener registration calls; invoke to unsubscribe. */
2833
2862
  export declare type Unsubscribe = () => void;
2834
2863
 
2835
- export declare const VERSION = "0.11.5";
2864
+ export declare const VERSION = "0.11.6";
2836
2865
 
2837
2866
  /**
2838
2867
  * Real-time video API: drawable frame, dimensions, and the source-side
@@ -3735,6 +3764,36 @@ export declare class VijiCore {
3735
3764
  * sharing this core's media engines)
3736
3765
  */
3737
3766
  getAudioStream(): MediaStream | null;
3767
+ /**
3768
+ * Gets a recordable MediaStream carrying the main audio channel's signal,
3769
+ * for client-side export recording (e.g. feeding a WebCodecs
3770
+ * `AudioEncoder` or `MediaRecorder` while capturing scene video).
3771
+ *
3772
+ * The returned stream is stable across `setAudioStream()` swaps: obtain it
3773
+ * once at record start and keep recording — a source change flows into the
3774
+ * same stream with no renegotiation. If audio is disconnected
3775
+ * mid-recording the stream goes **silent** (tracks stay live, the A/V
3776
+ * timeline stays continuous); its tracks end only when the media engines
3777
+ * are disposed (last core sharing them destroyed).
3778
+ *
3779
+ * Scope: main channel only. Additional audio channels and remote-device
3780
+ * audio (analysis values only, no PCM host-side) are not included.
3781
+ * Engine-scoped — identical for all cores sharing this core's media
3782
+ * engines, like {@link getAudioStream}.
3783
+ *
3784
+ * @returns The capture stream, or `null` when no main audio is connected
3785
+ * at call time.
3786
+ *
3787
+ * @example
3788
+ * ```ts
3789
+ * const audioTap = core.getAudioCaptureStream();
3790
+ * if (audioTap) {
3791
+ * const [track] = audioTap.getAudioTracks();
3792
+ * // feed track into MediaStreamTrackProcessor → AudioEncoder
3793
+ * }
3794
+ * ```
3795
+ */
3796
+ getAudioCaptureStream(): MediaStream | null;
3738
3797
  /**
3739
3798
  * Enables or disables user interactions (mouse, keyboard, touch) at runtime
3740
3799
  */
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-BKvsDqj0.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.6",
4
4
  "description": "Universal execution engine for Viji Creative scenes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",