@viji-dev/core 0.5.5 → 0.5.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/artist-dts-p5.js +1 -1
- package/dist/artist-dts.js +1 -1
- package/dist/artist-global-p5.d.ts +1 -1
- package/dist/artist-global.d.ts +1 -1
- package/dist/docs-api.js +2 -2
- package/dist/{essentia-wasm.web-C58CPq4U.js → essentia-wasm.web-htE1Skqw.js} +2 -2
- package/dist/{essentia-wasm.web-C58CPq4U.js.map → essentia-wasm.web-htE1Skqw.js.map} +1 -1
- package/dist/{index-DsJxKERc.js → index-CwwVLcjs.js} +122 -21
- package/dist/index-CwwVLcjs.js.map +1 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/index-DsJxKERc.js.map +0 -1
|
@@ -100,6 +100,13 @@ class IFrameManager {
|
|
|
100
100
|
iframeReadyPromise = null;
|
|
101
101
|
iframeReadyResolver = null;
|
|
102
102
|
iframeReadyRejecter = null;
|
|
103
|
+
/**
|
|
104
|
+
* 5s safety timer that rejects `iframeReadyPromise` if the inline-script
|
|
105
|
+
* never emits `iframe-ready` (e.g. blob script error). Promoted from
|
|
106
|
+
* closure-captured local to instance state so `destroy()` can clear it
|
|
107
|
+
* on cancellation (docs/14 rule 5).
|
|
108
|
+
*/
|
|
109
|
+
iframeReadyTimeoutId = null;
|
|
103
110
|
lockedOrigin = null;
|
|
104
111
|
// Window message dispatch
|
|
105
112
|
hostMessageListener = null;
|
|
@@ -159,6 +166,8 @@ class IFrameManager {
|
|
|
159
166
|
this.iframeReadyResolver = resolve;
|
|
160
167
|
this.iframeReadyRejecter = reject;
|
|
161
168
|
});
|
|
169
|
+
this.iframeReadyPromise.catch(() => {
|
|
170
|
+
});
|
|
162
171
|
this.installHostMessageListener(iframe);
|
|
163
172
|
const iframeContent = this.generateIFrameHTML();
|
|
164
173
|
const blob = new Blob([iframeContent], { type: "text/html" });
|
|
@@ -184,7 +193,8 @@ class IFrameManager {
|
|
|
184
193
|
iframe.style.cssText = `position:absolute;top:0;left:0;width:100%;height:100%;border:none;${visibility}`;
|
|
185
194
|
this.hostContainer.appendChild(iframe);
|
|
186
195
|
}
|
|
187
|
-
|
|
196
|
+
this.iframeReadyTimeoutId = setTimeout(() => {
|
|
197
|
+
this.iframeReadyTimeoutId = null;
|
|
188
198
|
if (this.iframeReadyRejecter) {
|
|
189
199
|
const r = this.iframeReadyRejecter;
|
|
190
200
|
this.iframeReadyResolver = null;
|
|
@@ -192,11 +202,6 @@ class IFrameManager {
|
|
|
192
202
|
r(new VijiCoreError("IFrame load timeout", "IFRAME_TIMEOUT"));
|
|
193
203
|
}
|
|
194
204
|
}, 5e3);
|
|
195
|
-
const originalResolver = this.iframeReadyResolver;
|
|
196
|
-
this.iframeReadyResolver = () => {
|
|
197
|
-
clearTimeout(timeoutId);
|
|
198
|
-
originalResolver();
|
|
199
|
-
};
|
|
200
205
|
iframe.onerror = () => {
|
|
201
206
|
if (this.iframeReadyRejecter) {
|
|
202
207
|
this.iframeReadyRejecter(
|
|
@@ -323,6 +328,10 @@ class IFrameManager {
|
|
|
323
328
|
switch (msg.type) {
|
|
324
329
|
case "iframe-ready": {
|
|
325
330
|
this.lockedOrigin = OPAQUE_ORIGIN;
|
|
331
|
+
if (this.iframeReadyTimeoutId !== null) {
|
|
332
|
+
clearTimeout(this.iframeReadyTimeoutId);
|
|
333
|
+
this.iframeReadyTimeoutId = null;
|
|
334
|
+
}
|
|
326
335
|
if (this.iframeReadyResolver) {
|
|
327
336
|
const r = this.iframeReadyResolver;
|
|
328
337
|
this.iframeReadyResolver = null;
|
|
@@ -463,6 +472,16 @@ class IFrameManager {
|
|
|
463
472
|
*/
|
|
464
473
|
destroy() {
|
|
465
474
|
try {
|
|
475
|
+
if (this.iframeReadyTimeoutId !== null) {
|
|
476
|
+
clearTimeout(this.iframeReadyTimeoutId);
|
|
477
|
+
this.iframeReadyTimeoutId = null;
|
|
478
|
+
}
|
|
479
|
+
if (this.iframeReadyRejecter) {
|
|
480
|
+
const r = this.iframeReadyRejecter;
|
|
481
|
+
this.iframeReadyResolver = null;
|
|
482
|
+
this.iframeReadyRejecter = null;
|
|
483
|
+
r(new VijiCoreError("Initialization cancelled", "INITIALIZATION_CANCELLED"));
|
|
484
|
+
}
|
|
466
485
|
if (this.iframe?.contentWindow && this.lockedOrigin) {
|
|
467
486
|
try {
|
|
468
487
|
this.postEnvelope({ type: "viji-terminate" });
|
|
@@ -670,6 +689,20 @@ class WorkerManager {
|
|
|
670
689
|
*/
|
|
671
690
|
controlPort = null;
|
|
672
691
|
onControlPortMessageBound = null;
|
|
692
|
+
/**
|
|
693
|
+
* Bootstrap-window cancellation state. The 10s `viji-ready` timer and
|
|
694
|
+
* the in-flight `vijiReady` rejecter are tracked on the instance so
|
|
695
|
+
* `destroy()` can cancel an in-progress bootstrap synchronously
|
|
696
|
+
* (clearing the timer + rejecting the awaiter with
|
|
697
|
+
* `INITIALIZATION_CANCELLED`). `isDestroyed` is the synchronous flag
|
|
698
|
+
* the outer catch in `createWorker` consults to distinguish a
|
|
699
|
+
* destroy-mid-init from a genuine bootstrap failure (e.g. the
|
|
700
|
+
* `postEnvelope` synchronous throw path, where no cancellation error
|
|
701
|
+
* was rejected through the awaiter).
|
|
702
|
+
*/
|
|
703
|
+
bootstrapTimeoutId = null;
|
|
704
|
+
bootstrapReject = null;
|
|
705
|
+
isDestroyed = false;
|
|
673
706
|
/**
|
|
674
707
|
* Bootstraps the worker inside the iframe.
|
|
675
708
|
*
|
|
@@ -723,7 +756,9 @@ class WorkerManager {
|
|
|
723
756
|
}
|
|
724
757
|
};
|
|
725
758
|
const vijiReady = new Promise((resolve, reject) => {
|
|
726
|
-
|
|
759
|
+
this.bootstrapReject = reject;
|
|
760
|
+
this.bootstrapTimeoutId = setTimeout(() => {
|
|
761
|
+
this.bootstrapTimeoutId = null;
|
|
727
762
|
reject(
|
|
728
763
|
new VijiCoreError(
|
|
729
764
|
"viji-ready timeout",
|
|
@@ -732,15 +767,29 @@ class WorkerManager {
|
|
|
732
767
|
);
|
|
733
768
|
}, 1e4);
|
|
734
769
|
this.iframeManager.onVijiReady(() => {
|
|
735
|
-
|
|
770
|
+
if (this.bootstrapTimeoutId !== null) {
|
|
771
|
+
clearTimeout(this.bootstrapTimeoutId);
|
|
772
|
+
this.bootstrapTimeoutId = null;
|
|
773
|
+
}
|
|
774
|
+
this.bootstrapReject = null;
|
|
736
775
|
resolve();
|
|
737
776
|
});
|
|
738
777
|
});
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
778
|
+
vijiReady.catch(() => {
|
|
779
|
+
});
|
|
780
|
+
try {
|
|
781
|
+
this.iframeManager.postEnvelope(
|
|
782
|
+
bootstrap,
|
|
783
|
+
collectBootstrapTransferables(bootstrap)
|
|
784
|
+
);
|
|
785
|
+
await vijiReady;
|
|
786
|
+
} finally {
|
|
787
|
+
if (this.bootstrapTimeoutId !== null) {
|
|
788
|
+
clearTimeout(this.bootstrapTimeoutId);
|
|
789
|
+
this.bootstrapTimeoutId = null;
|
|
790
|
+
}
|
|
791
|
+
this.bootstrapReject = null;
|
|
792
|
+
}
|
|
744
793
|
this.controlPort = this.iframeManager.getControlPort1();
|
|
745
794
|
if (this.controlPort && this.onWorkerMessageBound) {
|
|
746
795
|
const dispatcher = this.onWorkerMessageBound;
|
|
@@ -752,6 +801,15 @@ class WorkerManager {
|
|
|
752
801
|
this.postMessage("set-scene-code", { sceneCode: this.sceneCode });
|
|
753
802
|
this.isInitialized = true;
|
|
754
803
|
} catch (error) {
|
|
804
|
+
if (error instanceof VijiCoreError && error.code === "INITIALIZATION_CANCELLED") {
|
|
805
|
+
throw error;
|
|
806
|
+
}
|
|
807
|
+
if (this.isDestroyed) {
|
|
808
|
+
throw new VijiCoreError(
|
|
809
|
+
"Initialization cancelled",
|
|
810
|
+
"INITIALIZATION_CANCELLED"
|
|
811
|
+
);
|
|
812
|
+
}
|
|
755
813
|
throw new VijiCoreError(
|
|
756
814
|
`Failed to create worker: ${error}`,
|
|
757
815
|
"WORKER_CREATION_ERROR",
|
|
@@ -856,6 +914,16 @@ class WorkerManager {
|
|
|
856
914
|
*/
|
|
857
915
|
destroy() {
|
|
858
916
|
try {
|
|
917
|
+
this.isDestroyed = true;
|
|
918
|
+
if (this.bootstrapTimeoutId !== null) {
|
|
919
|
+
clearTimeout(this.bootstrapTimeoutId);
|
|
920
|
+
this.bootstrapTimeoutId = null;
|
|
921
|
+
}
|
|
922
|
+
if (this.bootstrapReject) {
|
|
923
|
+
const r = this.bootstrapReject;
|
|
924
|
+
this.bootstrapReject = null;
|
|
925
|
+
r(new VijiCoreError("Initialization cancelled", "INITIALIZATION_CANCELLED"));
|
|
926
|
+
}
|
|
859
927
|
this.pendingMessages.forEach(({ timeout, reject }) => {
|
|
860
928
|
clearTimeout(timeout);
|
|
861
929
|
reject(new VijiCoreError("Worker destroyed", "WORKER_DESTROYED"));
|
|
@@ -1897,7 +1965,7 @@ class EssentiaOnsetDetection {
|
|
|
1897
1965
|
this.initPromise = (async () => {
|
|
1898
1966
|
try {
|
|
1899
1967
|
const essentiaModule = await import("./essentia.js-core.es-DnrJE0uR.js");
|
|
1900
|
-
const wasmModule = await import("./essentia-wasm.web-
|
|
1968
|
+
const wasmModule = await import("./essentia-wasm.web-htE1Skqw.js").then((n) => n.e);
|
|
1901
1969
|
const EssentiaClass = essentiaModule.Essentia || essentiaModule.default?.Essentia || essentiaModule.default;
|
|
1902
1970
|
let WASMModule = wasmModule.default || wasmModule.EssentiaWASM || wasmModule.default?.EssentiaWASM;
|
|
1903
1971
|
if (!WASMModule) {
|
|
@@ -7422,6 +7490,7 @@ class AudioChannel {
|
|
|
7422
7490
|
if (this.workletNode) {
|
|
7423
7491
|
try {
|
|
7424
7492
|
this.workletNode.port.onmessage = null;
|
|
7493
|
+
this.workletNode.port.close();
|
|
7425
7494
|
this.workletNode.disconnect();
|
|
7426
7495
|
} catch (_) {
|
|
7427
7496
|
}
|
|
@@ -7471,6 +7540,14 @@ class AudioSystem {
|
|
|
7471
7540
|
onsetLogBuffer = [];
|
|
7472
7541
|
// Debug logging control
|
|
7473
7542
|
debugMode = false;
|
|
7543
|
+
/**
|
|
7544
|
+
* One-way lifecycle flag flipped at the top of `resetAudioState()`.
|
|
7545
|
+
* Gates re-entry into `ensureAudioContext()` and the public stream-mutation
|
|
7546
|
+
* entry points so an in-flight async caller (e.g. a worker RPC firing
|
|
7547
|
+
* mid-teardown) cannot resurrect a fresh `AudioContext` after destroy
|
|
7548
|
+
* has already nulled the field. AudioSystem is single-use after reset.
|
|
7549
|
+
*/
|
|
7550
|
+
destroyed = false;
|
|
7474
7551
|
// Diagnostic logger
|
|
7475
7552
|
diagnosticLogger = new DiagnosticLogger();
|
|
7476
7553
|
// Industry-grade audio analysis modules (4-layer architecture)
|
|
@@ -8056,6 +8133,7 @@ class AudioSystem {
|
|
|
8056
8133
|
* Handle audio stream update for main stream (called from VijiCore)
|
|
8057
8134
|
*/
|
|
8058
8135
|
handleAudioStreamUpdate(data) {
|
|
8136
|
+
if (this.destroyed) return;
|
|
8059
8137
|
try {
|
|
8060
8138
|
if (data.audioStream) {
|
|
8061
8139
|
this.setAudioStream(data.audioStream);
|
|
@@ -8072,6 +8150,9 @@ class AudioSystem {
|
|
|
8072
8150
|
* Ensure the shared AudioContext is created and resumed.
|
|
8073
8151
|
*/
|
|
8074
8152
|
async ensureAudioContext() {
|
|
8153
|
+
if (this.destroyed) {
|
|
8154
|
+
throw new Error("AudioSystem is destroyed");
|
|
8155
|
+
}
|
|
8075
8156
|
if (!this.audioContext) {
|
|
8076
8157
|
this.audioContext = new AudioContext();
|
|
8077
8158
|
}
|
|
@@ -8123,6 +8204,7 @@ class AudioSystem {
|
|
|
8123
8204
|
if (ch.workletNode) {
|
|
8124
8205
|
try {
|
|
8125
8206
|
ch.workletNode.port.onmessage = null;
|
|
8207
|
+
ch.workletNode.port.close();
|
|
8126
8208
|
ch.workletNode.disconnect();
|
|
8127
8209
|
} catch {
|
|
8128
8210
|
}
|
|
@@ -8173,6 +8255,7 @@ class AudioSystem {
|
|
|
8173
8255
|
* Set the main audio stream for analysis (preserves original public API surface).
|
|
8174
8256
|
*/
|
|
8175
8257
|
async setAudioStream(audioStream) {
|
|
8258
|
+
if (this.destroyed) return;
|
|
8176
8259
|
this.disconnectMainStream();
|
|
8177
8260
|
this.resetEssentiaBandHistories();
|
|
8178
8261
|
await this.connectChannel(this.mainChannel, audioStream, true);
|
|
@@ -8444,8 +8527,14 @@ class AudioSystem {
|
|
|
8444
8527
|
/**
|
|
8445
8528
|
* Reset all audio state (called when destroying).
|
|
8446
8529
|
* Disconnects all channels, closes AudioContext, resets all modules.
|
|
8530
|
+
*
|
|
8531
|
+
* Returns a Promise that resolves once `AudioContext.close()` has fully
|
|
8532
|
+
* transitioned the context to `closed`. Callers tearing the system down
|
|
8533
|
+
* (e.g. `VijiCore.destroy()`) MUST await this; otherwise Blink keeps the
|
|
8534
|
+
* context alive in `Pending activities` and leaks ~4 MB per cycle.
|
|
8447
8535
|
*/
|
|
8448
|
-
resetAudioState() {
|
|
8536
|
+
async resetAudioState() {
|
|
8537
|
+
this.destroyed = true;
|
|
8449
8538
|
this.stopAnalysisLoop();
|
|
8450
8539
|
this.stopStalenessTimer();
|
|
8451
8540
|
this.stopIdleTicker();
|
|
@@ -8458,9 +8547,14 @@ class AudioSystem {
|
|
|
8458
8547
|
this.mainChannel.audioState.isConnected = false;
|
|
8459
8548
|
this.mainChannel.isAnalysisRunning = false;
|
|
8460
8549
|
this.mainChannel.currentStream = null;
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8550
|
+
const ctxToClose = this.audioContext;
|
|
8551
|
+
this.audioContext = null;
|
|
8552
|
+
if (ctxToClose && ctxToClose.state !== "closed") {
|
|
8553
|
+
try {
|
|
8554
|
+
await ctxToClose.close();
|
|
8555
|
+
} catch (e) {
|
|
8556
|
+
this.debugLog(`AudioContext.close() rejected: ${e?.message ?? e}`);
|
|
8557
|
+
}
|
|
8464
8558
|
}
|
|
8465
8559
|
this.workletRegistered = false;
|
|
8466
8560
|
this.onsetDetection.reset();
|
|
@@ -8482,6 +8576,7 @@ class AudioSystem {
|
|
|
8482
8576
|
* @param stream The MediaStream to analyze
|
|
8483
8577
|
*/
|
|
8484
8578
|
async addStream(streamIndex, stream) {
|
|
8579
|
+
if (this.destroyed) return;
|
|
8485
8580
|
if (this.additionalChannels.has(streamIndex)) {
|
|
8486
8581
|
this.removeStream(streamIndex);
|
|
8487
8582
|
}
|
|
@@ -8511,6 +8606,7 @@ class AudioSystem {
|
|
|
8511
8606
|
* Mirrors video's reinitializeAdditionalCoordinators pattern.
|
|
8512
8607
|
*/
|
|
8513
8608
|
async reinitializeAdditionalChannels(streams, baseIndex) {
|
|
8609
|
+
if (this.destroyed) return;
|
|
8514
8610
|
const toRemove = [];
|
|
8515
8611
|
for (const [idx, ch] of this.additionalChannels) {
|
|
8516
8612
|
if (idx >= baseIndex && idx < baseIndex + 100) {
|
|
@@ -8628,6 +8724,7 @@ class AudioSystem {
|
|
|
8628
8724
|
// do when audio is active.
|
|
8629
8725
|
// ─────────────────────────────────────────────────────────────────────────
|
|
8630
8726
|
startIdleTicker() {
|
|
8727
|
+
if (this.destroyed) return;
|
|
8631
8728
|
if (this.idleTickerHandle !== null) return;
|
|
8632
8729
|
if (typeof requestAnimationFrame === "undefined") return;
|
|
8633
8730
|
this.idleTickerLastTime = performance.now();
|
|
@@ -8642,6 +8739,7 @@ class AudioSystem {
|
|
|
8642
8739
|
}
|
|
8643
8740
|
tickIdle(now) {
|
|
8644
8741
|
this.idleTickerHandle = null;
|
|
8742
|
+
if (this.destroyed) return;
|
|
8645
8743
|
if (this.mainChannel.audioState.isConnected) return;
|
|
8646
8744
|
const dtMs = Math.min(100, now - this.idleTickerLastTime);
|
|
8647
8745
|
this.idleTickerLastTime = now;
|
|
@@ -10018,6 +10116,9 @@ class VijiCore {
|
|
|
10018
10116
|
} catch (error) {
|
|
10019
10117
|
this.isInitializing = false;
|
|
10020
10118
|
await this.cleanup();
|
|
10119
|
+
if (error instanceof VijiCoreError && (error.code === "INITIALIZATION_CANCELLED" || error.code === "INSTANCE_DESTROYED")) {
|
|
10120
|
+
throw error;
|
|
10121
|
+
}
|
|
10021
10122
|
throw new VijiCoreError(
|
|
10022
10123
|
`Failed to initialize VijiCore: ${error}`,
|
|
10023
10124
|
"INITIALIZATION_ERROR",
|
|
@@ -11630,7 +11731,7 @@ class VijiCore {
|
|
|
11630
11731
|
}
|
|
11631
11732
|
this.deviceAudioStreamIndices.clear();
|
|
11632
11733
|
if (this.audioSystem) {
|
|
11633
|
-
this.audioSystem.resetAudioState();
|
|
11734
|
+
await this.audioSystem.resetAudioState();
|
|
11634
11735
|
this.audioSystem = null;
|
|
11635
11736
|
}
|
|
11636
11737
|
this.currentAudioStream = null;
|
|
@@ -11723,7 +11824,7 @@ function validateCoreStatePayload(state) {
|
|
|
11723
11824
|
}
|
|
11724
11825
|
return null;
|
|
11725
11826
|
}
|
|
11726
|
-
const VERSION = "0.5.
|
|
11827
|
+
const VERSION = "0.5.7";
|
|
11727
11828
|
export {
|
|
11728
11829
|
AudioSystem as A,
|
|
11729
11830
|
VERSION as V,
|
|
@@ -11731,4 +11832,4 @@ export {
|
|
|
11731
11832
|
VijiCoreError as b,
|
|
11732
11833
|
getDefaultExportFromCjs as g
|
|
11733
11834
|
};
|
|
11734
|
-
//# sourceMappingURL=index-
|
|
11835
|
+
//# sourceMappingURL=index-CwwVLcjs.js.map
|