@babylonjs/lite 1.4.0 → 1.5.0
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/index.js +381 -375
- package/dist/index.js.map +1 -1
- package/index.d.ts +757 -0
- package/lib/audio/analyzer.js +65 -0
- package/lib/audio/analyzer.js.map +1 -0
- package/lib/audio/audio-bus.js +38 -0
- package/lib/audio/audio-bus.js.map +1 -0
- package/lib/audio/audio-engine.js +188 -0
- package/lib/audio/audio-engine.js.map +1 -0
- package/lib/audio/audio-fetch.js +18 -0
- package/lib/audio/audio-fetch.js.map +1 -0
- package/lib/audio/audio-param.js +96 -0
- package/lib/audio/audio-param.js.map +1 -0
- package/lib/audio/audio-signal.js +46 -0
- package/lib/audio/audio-signal.js.map +1 -0
- package/lib/audio/bus.js +33 -0
- package/lib/audio/bus.js.map +1 -0
- package/lib/audio/host-types.js +2 -0
- package/lib/audio/host-types.js.map +1 -0
- package/lib/audio/index.js +12 -0
- package/lib/audio/index.js.map +1 -0
- package/lib/audio/sound-buffer.js +59 -0
- package/lib/audio/sound-buffer.js.map +1 -0
- package/lib/audio/sound-source.js +57 -0
- package/lib/audio/sound-source.js.map +1 -0
- package/lib/audio/sound-sub-graph.js +72 -0
- package/lib/audio/sound-sub-graph.js.map +1 -0
- package/lib/audio/spatial.js +466 -0
- package/lib/audio/spatial.js.map +1 -0
- package/lib/audio/static-sound.js +313 -0
- package/lib/audio/static-sound.js.map +1 -0
- package/lib/audio/stereo.js +40 -0
- package/lib/audio/stereo.js.map +1 -0
- package/lib/audio/streaming-sound.js +377 -0
- package/lib/audio/streaming-sound.js.map +1 -0
- package/lib/audio/unmute-ui.js +72 -0
- package/lib/audio/unmute-ui.js.map +1 -0
- package/lib/audio/visualizer.js +101 -0
- package/lib/audio/visualizer.js.map +1 -0
- package/lib/engine/engine.js +1 -1
- package/lib/index.js +11 -0
- package/lib/index.js.map +1 -1
- package/lib/light/types.js.map +1 -1
- package/lib/loader-gltf/animation-pointer-basecolor.js +25 -0
- package/lib/loader-gltf/animation-pointer-basecolor.js.map +1 -0
- package/lib/loader-gltf/animation-pointer-ext.js +244 -0
- package/lib/loader-gltf/animation-pointer-ext.js.map +1 -0
- package/lib/loader-gltf/animation-pointer-lights.js +46 -0
- package/lib/loader-gltf/animation-pointer-lights.js.map +1 -0
- package/lib/loader-gltf/animation-pointer.js +4 -1
- package/lib/loader-gltf/animation-pointer.js.map +1 -1
- package/lib/loader-gltf/gltf-animation.js +5 -3
- package/lib/loader-gltf/gltf-animation.js.map +1 -1
- package/lib/loader-gltf/gltf-color-normalize.js +10 -1
- package/lib/loader-gltf/gltf-color-normalize.js.map +1 -1
- package/lib/loader-gltf/gltf-feature-animation-pointer.js +67 -47
- package/lib/loader-gltf/gltf-feature-animation-pointer.js.map +1 -1
- package/lib/loader-gltf/gltf-feature-lights-punctual.js +51 -9
- package/lib/loader-gltf/gltf-feature-lights-punctual.js.map +1 -1
- package/lib/loader-gltf/gltf-feature-primitive.js +20 -0
- package/lib/loader-gltf/gltf-feature-primitive.js.map +1 -0
- package/lib/loader-gltf/gltf-feature-registry.js +25 -0
- package/lib/loader-gltf/gltf-feature-registry.js.map +1 -1
- package/lib/loader-gltf/gltf-feature-skeleton.js +18 -3
- package/lib/loader-gltf/gltf-feature-skeleton.js.map +1 -1
- package/lib/loader-gltf/gltf-interleave.js +3 -2
- package/lib/loader-gltf/gltf-interleave.js.map +1 -1
- package/lib/loader-gltf/gltf-light-pointer-state.js +18 -0
- package/lib/loader-gltf/gltf-light-pointer-state.js.map +1 -0
- package/lib/loader-gltf/gltf-parser.js +7 -1
- package/lib/loader-gltf/gltf-parser.js.map +1 -1
- package/lib/loader-gltf/gltf-pbr-builder-ext.js +1 -1
- package/lib/loader-gltf/gltf-pbr-builder-ext.js.map +1 -1
- package/lib/loader-gltf/gltf-pbr-builder.js +1 -1
- package/lib/loader-gltf/gltf-pbr-builder.js.map +1 -1
- package/lib/loader-gltf/gltf-sampler-denorm.js +20 -0
- package/lib/loader-gltf/gltf-sampler-denorm.js.map +1 -0
- package/lib/loader-gltf/gltf-sampler-desc.js +11 -2
- package/lib/loader-gltf/gltf-sampler-desc.js.map +1 -1
- package/lib/loader-gltf/gltf-uv-denorm.js +28 -0
- package/lib/loader-gltf/gltf-uv-denorm.js.map +1 -0
- package/lib/loader-gltf/load-gltf.js +15 -6
- package/lib/loader-gltf/load-gltf.js.map +1 -1
- package/lib/material/material-rebuild.js +4 -0
- package/lib/material/material-rebuild.js.map +1 -1
- package/lib/material/mesh-features.js +8 -1
- package/lib/material/mesh-features.js.map +1 -1
- package/lib/material/pbr/fragments/reflectance-fragment.js +1 -1
- package/lib/material/pbr/fragments/reflectance-fragment.js.map +1 -1
- package/lib/material/pbr/fragments/refraction-rtt-fragment.js +1 -1
- package/lib/material/pbr/fragments/refraction-rtt-fragment.js.map +1 -1
- package/lib/material/pbr/pbr-pipeline.js +7 -3
- package/lib/material/pbr/pbr-pipeline.js.map +1 -1
- package/lib/material/pbr/pbr-primitive-resolver.js +34 -0
- package/lib/material/pbr/pbr-primitive-resolver.js.map +1 -0
- package/lib/material/pbr/pbr-renderable.js +1 -1
- package/lib/material/pbr/pbr-renderable.js.map +1 -1
- package/lib/material/shader/shader-material.js +9 -5
- package/lib/material/shader/shader-material.js.map +1 -1
- package/lib/material/shader/shader-thin-instance.js +1 -1
- package/lib/material/shader/shader-thin-instance.js.map +1 -1
- package/lib/material/standard/standard-renderable.js +1 -1
- package/lib/material/standard/standard-renderable.js.map +1 -1
- package/lib/mesh/mesh-dispose.js +1 -0
- package/lib/mesh/mesh-dispose.js.map +1 -1
- package/lib/mesh/thin-instance-cull-binding.js +15 -6
- package/lib/mesh/thin-instance-cull-binding.js.map +1 -1
- package/lib/scene/scene-core.js +1 -0
- package/lib/scene/scene-core.js.map +1 -1
- package/lib/scene/scene-material-swap.js +2 -0
- package/lib/scene/scene-material-swap.js.map +1 -1
- package/lib/shadow/csm-shadow-task-hooks.js +67 -9
- package/lib/shadow/csm-shadow-task-hooks.js.map +1 -1
- package/lib/sprite/sprite-2d.js +4 -0
- package/lib/sprite/sprite-2d.js.map +1 -1
- package/lib/sprite/sprite-pipeline.js +25 -22
- package/lib/sprite/sprite-pipeline.js.map +1 -1
- package/lib/text/_gpu/text-pipeline.js +1 -1
- package/lib/text/_gpu/text-pipeline.js.map +1 -1
- package/lib/text/text-renderer.js +3 -1
- package/lib/text/text-renderer.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { getBusInputNode } from './audio-bus.js';
|
|
2
|
+
import { cleanAudioUrl } from './audio-fetch.js';
|
|
3
|
+
import { createAudioSignal } from './audio-signal.js';
|
|
4
|
+
import { SoundState } from './static-sound.js';
|
|
5
|
+
import { createSoundSubGraph, connectSoundSubGraph, disposeSoundSubGraph, setSoundSubGraphVolume } from './sound-sub-graph.js';
|
|
6
|
+
|
|
7
|
+
async function createStreamingSoundAsync(engine, source, options = {}) {
|
|
8
|
+
if (engine._isOffline) {
|
|
9
|
+
throw new Error("Streaming sounds require a real-time AudioContext.");
|
|
10
|
+
}
|
|
11
|
+
await engine._isReady;
|
|
12
|
+
const graph = createSoundSubGraph(engine._ctx, engine, options.volume ?? 1);
|
|
13
|
+
const outBus = options.outBus ?? engine._mainBus;
|
|
14
|
+
connectSoundSubGraph(graph, getBusInputNode(outBus));
|
|
15
|
+
const onEnded = createAudioSignal();
|
|
16
|
+
const sound = {
|
|
17
|
+
get state() {
|
|
18
|
+
return sound._state;
|
|
19
|
+
},
|
|
20
|
+
get instanceCount() {
|
|
21
|
+
return sound._instances.size;
|
|
22
|
+
},
|
|
23
|
+
get preloadCompletedCount() {
|
|
24
|
+
return sound._preloaded.length;
|
|
25
|
+
},
|
|
26
|
+
get onEnded() {
|
|
27
|
+
return onEnded;
|
|
28
|
+
},
|
|
29
|
+
_engine: engine,
|
|
30
|
+
_source: source,
|
|
31
|
+
_graph: graph,
|
|
32
|
+
_outBus: outBus,
|
|
33
|
+
_options: {
|
|
34
|
+
autoplay: options.autoplay ?? false,
|
|
35
|
+
loop: options.loop ?? false,
|
|
36
|
+
maxInstances: options.maxInstances ?? Infinity,
|
|
37
|
+
preloadCount: options.preloadCount ?? 1,
|
|
38
|
+
startOffset: options.startOffset ?? 0
|
|
39
|
+
},
|
|
40
|
+
_instances: /* @__PURE__ */ new Set(),
|
|
41
|
+
_preloaded: [],
|
|
42
|
+
_newest: null,
|
|
43
|
+
_state: SoundState.Stopped,
|
|
44
|
+
_onEnded: onEnded,
|
|
45
|
+
_dispose: () => disposeStreamingSound(sound)
|
|
46
|
+
};
|
|
47
|
+
engine._sounds.add(sound);
|
|
48
|
+
if (sound._options.preloadCount > 0) {
|
|
49
|
+
await preloadStreamingInstancesAsync(sound, sound._options.preloadCount);
|
|
50
|
+
}
|
|
51
|
+
if (sound._options.autoplay) {
|
|
52
|
+
playStreamingSound(sound);
|
|
53
|
+
}
|
|
54
|
+
return sound;
|
|
55
|
+
}
|
|
56
|
+
function preloadStreamingInstanceAsync(sound) {
|
|
57
|
+
const instance = _createStreamingInstance(sound);
|
|
58
|
+
if (!sound._preloaded.includes(instance)) {
|
|
59
|
+
sound._preloaded.push(instance);
|
|
60
|
+
}
|
|
61
|
+
return instance._preloadedPromise;
|
|
62
|
+
}
|
|
63
|
+
async function preloadStreamingInstancesAsync(sound, count) {
|
|
64
|
+
const promises = [];
|
|
65
|
+
for (let i = 0; i < count; i++) {
|
|
66
|
+
promises.push(preloadStreamingInstanceAsync(sound));
|
|
67
|
+
}
|
|
68
|
+
await Promise.all(promises);
|
|
69
|
+
}
|
|
70
|
+
function playStreamingSound(sound, options = {}) {
|
|
71
|
+
if (sound._state === SoundState.Paused) {
|
|
72
|
+
resumeStreamingSound(sound, options);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let instance;
|
|
76
|
+
if (sound._preloaded.length > 0) {
|
|
77
|
+
instance = sound._preloaded[0];
|
|
78
|
+
instance._options.startOffset = sound._options.startOffset;
|
|
79
|
+
_removePreloaded(sound, instance);
|
|
80
|
+
} else {
|
|
81
|
+
instance = _createStreamingInstance(sound);
|
|
82
|
+
}
|
|
83
|
+
instance._onEnded.addOnce(instance._endedHandler);
|
|
84
|
+
sound._instances.add(instance);
|
|
85
|
+
sound._newest = instance;
|
|
86
|
+
const onStateChanged = () => {
|
|
87
|
+
if (instance._state === SoundState.Started) {
|
|
88
|
+
_stopExcessInstances(sound);
|
|
89
|
+
unsubStateChanged();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const unsubStateChanged = instance._onStateChanged.add(onStateChanged);
|
|
93
|
+
const opts = {
|
|
94
|
+
loop: options.loop ?? sound._options.loop,
|
|
95
|
+
startOffset: options.startOffset ?? sound._options.startOffset,
|
|
96
|
+
volume: options.volume ?? 1
|
|
97
|
+
};
|
|
98
|
+
_instancePlay(instance, opts);
|
|
99
|
+
sound._state = instance._state;
|
|
100
|
+
}
|
|
101
|
+
function pauseStreamingSound(sound) {
|
|
102
|
+
for (const instance of sound._instances) {
|
|
103
|
+
_instancePause(instance);
|
|
104
|
+
}
|
|
105
|
+
sound._state = SoundState.Paused;
|
|
106
|
+
}
|
|
107
|
+
function resumeStreamingSound(sound, options = {}) {
|
|
108
|
+
if (sound._state !== SoundState.Paused) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (const instance of sound._instances) {
|
|
112
|
+
_instanceResume(instance, options);
|
|
113
|
+
}
|
|
114
|
+
sound._state = SoundState.Started;
|
|
115
|
+
}
|
|
116
|
+
function stopStreamingSound(sound) {
|
|
117
|
+
sound._state = SoundState.Stopped;
|
|
118
|
+
for (const instance of Array.from(sound._instances)) {
|
|
119
|
+
_instanceStop(instance);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function setStreamingSoundVolume(sound, value, options) {
|
|
123
|
+
setSoundSubGraphVolume(sound._graph, value, options);
|
|
124
|
+
}
|
|
125
|
+
function disposeStreamingSound(sound) {
|
|
126
|
+
stopStreamingSound(sound);
|
|
127
|
+
for (const instance of Array.from(sound._instances)) {
|
|
128
|
+
_instanceDispose(instance);
|
|
129
|
+
}
|
|
130
|
+
sound._instances.clear();
|
|
131
|
+
for (const instance of sound._preloaded.slice()) {
|
|
132
|
+
_instanceDispose(instance);
|
|
133
|
+
}
|
|
134
|
+
sound._preloaded.length = 0;
|
|
135
|
+
disposeSoundSubGraph(sound._graph);
|
|
136
|
+
sound._onEnded._clear();
|
|
137
|
+
sound._engine._sounds.delete(sound);
|
|
138
|
+
}
|
|
139
|
+
function _stopExcessInstances(sound) {
|
|
140
|
+
if (sound._options.maxInstances < Infinity) {
|
|
141
|
+
const started = Array.from(sound._instances).filter((i) => i._state === SoundState.Started);
|
|
142
|
+
const toStop = started.length - sound._options.maxInstances;
|
|
143
|
+
for (let i = 0; i < toStop; i++) {
|
|
144
|
+
_instanceStop(started[i]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function _removePreloaded(sound, instance) {
|
|
149
|
+
const index = sound._preloaded.indexOf(instance);
|
|
150
|
+
if (index !== -1) {
|
|
151
|
+
sound._preloaded.splice(index, 1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function _onInstanceEnded(sound, instance) {
|
|
155
|
+
if (sound._newest === instance) {
|
|
156
|
+
sound._newest = null;
|
|
157
|
+
}
|
|
158
|
+
sound._instances.delete(instance);
|
|
159
|
+
if (sound._instances.size === 0) {
|
|
160
|
+
sound._state = SoundState.Stopped;
|
|
161
|
+
sound._onEnded._notify(sound);
|
|
162
|
+
}
|
|
163
|
+
_instanceDispose(instance);
|
|
164
|
+
}
|
|
165
|
+
function _createStreamingInstance(sound) {
|
|
166
|
+
const engine = sound._engine;
|
|
167
|
+
let resolveReady;
|
|
168
|
+
let rejectReady;
|
|
169
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
170
|
+
resolveReady = resolve;
|
|
171
|
+
rejectReady = reject;
|
|
172
|
+
});
|
|
173
|
+
const instance = {
|
|
174
|
+
_sound: sound,
|
|
175
|
+
_engine: engine,
|
|
176
|
+
_options: {
|
|
177
|
+
loop: sound._options.loop,
|
|
178
|
+
startOffset: sound._options.startOffset
|
|
179
|
+
},
|
|
180
|
+
_mediaElement: void 0,
|
|
181
|
+
_sourceNode: null,
|
|
182
|
+
_volumeNode: new GainNode(engine._ctx),
|
|
183
|
+
_state: SoundState.Stopped,
|
|
184
|
+
_enginePlayTime: Infinity,
|
|
185
|
+
_enginePauseTime: 0,
|
|
186
|
+
_isReady: false,
|
|
187
|
+
_readyPromise: readyPromise,
|
|
188
|
+
_resolveReady: resolveReady,
|
|
189
|
+
_rejectReady: rejectReady,
|
|
190
|
+
// The preloaded promise resolves on ready and rejects on error.
|
|
191
|
+
_preloadedPromise: readyPromise,
|
|
192
|
+
_engineStateUnsub: null,
|
|
193
|
+
_userGestureUnsub: null,
|
|
194
|
+
_onEnded: createAudioSignal(),
|
|
195
|
+
_onStateChanged: createAudioSignal(),
|
|
196
|
+
_endedHandler: () => _onInstanceEnded(sound, instance),
|
|
197
|
+
_canPlayThroughHandler: () => _onCanPlayThrough(instance),
|
|
198
|
+
_mediaEndedHandler: () => _onMediaEnded(instance),
|
|
199
|
+
_errorHandler: (reason) => _onMediaError(instance, reason),
|
|
200
|
+
_engineStateHandler: () => _onEngineStateChanged(instance),
|
|
201
|
+
_userGestureHandler: () => _instancePlay(instance, { loop: instance._options.loop, startOffset: instance._options.startOffset, volume: instance._volumeNode.gain.value })
|
|
202
|
+
};
|
|
203
|
+
_initMediaElement(instance);
|
|
204
|
+
return instance;
|
|
205
|
+
}
|
|
206
|
+
function _initMediaElement(instance) {
|
|
207
|
+
const source = instance._sound._source;
|
|
208
|
+
let mediaElement;
|
|
209
|
+
if (typeof source === "string") {
|
|
210
|
+
mediaElement = new Audio(cleanAudioUrl(source));
|
|
211
|
+
} else if (Array.isArray(source)) {
|
|
212
|
+
mediaElement = new Audio();
|
|
213
|
+
for (const url of source) {
|
|
214
|
+
const sourceEl = document.createElement("source");
|
|
215
|
+
sourceEl.src = cleanAudioUrl(url);
|
|
216
|
+
mediaElement.appendChild(sourceEl);
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
mediaElement = source;
|
|
220
|
+
}
|
|
221
|
+
mediaElement.crossOrigin = "anonymous";
|
|
222
|
+
mediaElement.controls = false;
|
|
223
|
+
mediaElement.loop = instance._options.loop;
|
|
224
|
+
mediaElement.preload = "auto";
|
|
225
|
+
mediaElement.addEventListener("canplaythrough", instance._canPlayThroughHandler, { once: true });
|
|
226
|
+
mediaElement.addEventListener("ended", instance._mediaEndedHandler, { once: true });
|
|
227
|
+
mediaElement.addEventListener("error", instance._errorHandler, { once: true });
|
|
228
|
+
mediaElement.load();
|
|
229
|
+
const sourceNode = new MediaElementAudioSourceNode(instance._engine._ctx, { mediaElement });
|
|
230
|
+
sourceNode.connect(instance._volumeNode);
|
|
231
|
+
instance._volumeNode.connect(instance._sound._graph._in);
|
|
232
|
+
instance._sourceNode = sourceNode;
|
|
233
|
+
instance._mediaElement = mediaElement;
|
|
234
|
+
}
|
|
235
|
+
function _setStreamingInstanceState(instance, state) {
|
|
236
|
+
instance._state = state;
|
|
237
|
+
instance._onStateChanged._notify(instance);
|
|
238
|
+
}
|
|
239
|
+
function _instanceCurrentTime(instance) {
|
|
240
|
+
if (instance._state === SoundState.Stopped) {
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
const timeSinceLastStart = instance._state === SoundState.Paused ? 0 : instance._engine.currentTime - instance._enginePlayTime;
|
|
244
|
+
return instance._enginePauseTime + timeSinceLastStart + instance._options.startOffset;
|
|
245
|
+
}
|
|
246
|
+
function _instancePlay(instance, options) {
|
|
247
|
+
if (instance._state === SoundState.Started) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
instance._options.loop = options.loop;
|
|
251
|
+
instance._mediaElement.loop = options.loop;
|
|
252
|
+
let startOffset = options.startOffset;
|
|
253
|
+
if (instance._state === SoundState.Paused) {
|
|
254
|
+
startOffset = _instanceCurrentTime(instance);
|
|
255
|
+
}
|
|
256
|
+
if (startOffset > 0) {
|
|
257
|
+
instance._mediaElement.currentTime = startOffset;
|
|
258
|
+
}
|
|
259
|
+
instance._volumeNode.gain.value = options.volume;
|
|
260
|
+
_play(instance);
|
|
261
|
+
}
|
|
262
|
+
function _instancePause(instance) {
|
|
263
|
+
if (instance._state !== SoundState.Starting && instance._state !== SoundState.Started) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
_setStreamingInstanceState(instance, SoundState.Paused);
|
|
267
|
+
instance._enginePauseTime += instance._engine.currentTime - instance._enginePlayTime;
|
|
268
|
+
instance._mediaElement.pause();
|
|
269
|
+
}
|
|
270
|
+
function _instanceResume(instance, options) {
|
|
271
|
+
if (instance._state !== SoundState.Paused) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
_instancePlay(instance, {
|
|
275
|
+
loop: options.loop ?? instance._options.loop,
|
|
276
|
+
startOffset: options.startOffset ?? instance._options.startOffset,
|
|
277
|
+
volume: options.volume ?? instance._volumeNode.gain.value
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function _instanceStop(instance) {
|
|
281
|
+
if (instance._state === SoundState.Stopped) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
instance._mediaElement.pause();
|
|
285
|
+
_onMediaEnded(instance);
|
|
286
|
+
instance._engineStateUnsub?.();
|
|
287
|
+
instance._engineStateUnsub = null;
|
|
288
|
+
}
|
|
289
|
+
function _instanceDispose(instance) {
|
|
290
|
+
_instanceStop(instance);
|
|
291
|
+
instance._sourceNode?.disconnect(instance._volumeNode);
|
|
292
|
+
instance._sourceNode = null;
|
|
293
|
+
instance._volumeNode.disconnect();
|
|
294
|
+
const mediaElement = instance._mediaElement;
|
|
295
|
+
if (mediaElement) {
|
|
296
|
+
mediaElement.removeEventListener("canplaythrough", instance._canPlayThroughHandler);
|
|
297
|
+
mediaElement.removeEventListener("ended", instance._mediaEndedHandler);
|
|
298
|
+
mediaElement.removeEventListener("error", instance._errorHandler);
|
|
299
|
+
}
|
|
300
|
+
instance._engineStateUnsub?.();
|
|
301
|
+
instance._engineStateUnsub = null;
|
|
302
|
+
instance._userGestureUnsub?.();
|
|
303
|
+
instance._userGestureUnsub = null;
|
|
304
|
+
instance._resolveReady();
|
|
305
|
+
instance._onEnded._clear();
|
|
306
|
+
instance._onStateChanged._clear();
|
|
307
|
+
}
|
|
308
|
+
function _onCanPlayThrough(instance) {
|
|
309
|
+
instance._isReady = true;
|
|
310
|
+
instance._resolveReady();
|
|
311
|
+
}
|
|
312
|
+
function _onMediaEnded(instance) {
|
|
313
|
+
const wasPaused = instance._state === SoundState.Paused;
|
|
314
|
+
_setStreamingInstanceState(instance, SoundState.Stopped);
|
|
315
|
+
if (!wasPaused) {
|
|
316
|
+
instance._onEnded._notify(instance);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function _onMediaError(instance, reason) {
|
|
320
|
+
_setStreamingInstanceState(instance, SoundState.FailedToStart);
|
|
321
|
+
instance._rejectReady(reason);
|
|
322
|
+
_instanceDispose(instance);
|
|
323
|
+
}
|
|
324
|
+
function _onEngineStateChanged(instance) {
|
|
325
|
+
if (instance._engine.state !== "running") {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (instance._options.loop && instance._state === SoundState.Starting) {
|
|
329
|
+
_instancePlay(instance, { loop: instance._options.loop, startOffset: instance._options.startOffset, volume: instance._volumeNode.gain.value });
|
|
330
|
+
}
|
|
331
|
+
instance._engineStateUnsub?.();
|
|
332
|
+
instance._engineStateUnsub = null;
|
|
333
|
+
}
|
|
334
|
+
function _play(instance) {
|
|
335
|
+
_setStreamingInstanceState(instance, SoundState.Starting);
|
|
336
|
+
if (!instance._isReady) {
|
|
337
|
+
void _playWhenReady(instance);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (instance._state !== SoundState.Starting) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (instance._engine.state === "running") {
|
|
344
|
+
const result = instance._mediaElement.play();
|
|
345
|
+
instance._enginePlayTime = instance._engine.currentTime;
|
|
346
|
+
_setStreamingInstanceState(instance, SoundState.Started);
|
|
347
|
+
if (result && typeof result.then === "function") {
|
|
348
|
+
void _awaitPlayResult(instance, result);
|
|
349
|
+
}
|
|
350
|
+
} else if (instance._options.loop) {
|
|
351
|
+
instance._engineStateUnsub = instance._engine.onStateChanged.add(instance._engineStateHandler);
|
|
352
|
+
} else {
|
|
353
|
+
_instanceStop(instance);
|
|
354
|
+
_setStreamingInstanceState(instance, SoundState.FailedToStart);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async function _playWhenReady(instance) {
|
|
358
|
+
try {
|
|
359
|
+
await instance._readyPromise;
|
|
360
|
+
_play(instance);
|
|
361
|
+
} catch {
|
|
362
|
+
_setStreamingInstanceState(instance, SoundState.FailedToStart);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async function _awaitPlayResult(instance, result) {
|
|
366
|
+
try {
|
|
367
|
+
await result;
|
|
368
|
+
} catch {
|
|
369
|
+
_setStreamingInstanceState(instance, SoundState.FailedToStart);
|
|
370
|
+
if (instance._options.loop) {
|
|
371
|
+
instance._userGestureUnsub = instance._engine.onUserGesture.addOnce(instance._userGestureHandler);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export { createStreamingSoundAsync, disposeStreamingSound, pauseStreamingSound, playStreamingSound, preloadStreamingInstanceAsync, preloadStreamingInstancesAsync, resumeStreamingSound, setStreamingSoundVolume, stopStreamingSound };
|
|
377
|
+
//# sourceMappingURL=streaming-sound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming-sound.js","sources":["../../../src/audio/streaming-sound.ts"],"sourcesContent":["/**\n * Streaming sounds (HTMLMediaElement-backed) and their play instances.\n *\n * Faithful port of AudioV2 `StreamingSound` / `_WebAudioStreamingSound`\n * (+ `_StreamingSoundInstance`), collapsed to pure state + standalone functions.\n *\n * A streaming sound plays an `<audio>` element through a\n * `MediaElementAudioSourceNode`, so the media is fetched/decoded in chunks by\n * the browser as it plays. This is cheaper to start for large/long assets than\n * a fully-decoded {@link StaticSound}, but it cannot have loop points, pitch, or\n * playback-rate changes.\n *\n * Graph per playing instance:\n *\n * `<audio>` becomes `MediaElementAudioSourceNode` then `instance._volumeNode`\n * then `sound._graph._in` then `sound._graph._out` then the output bus, the main\n * bus, the main out, and finally `ctx.destination`.\n *\n * Because the first play of a streaming sound can be delayed while the media\n * buffers, instances can be pre-loaded (`preloadCount`, or\n * {@link preloadStreamingInstanceAsync}) so playback starts immediately.\n */\n\nimport { type AudioEngine } from \"./audio-engine.js\";\nimport { type PrimaryAudioBus, getBusInputNode } from \"./audio-bus.js\";\nimport { cleanAudioUrl } from \"./audio-fetch.js\";\nimport { type RampOptions } from \"./audio-param.js\";\nimport { type AudioSignal, type AudioSignalImpl, createAudioSignal } from \"./audio-signal.js\";\nimport { SoundState } from \"./static-sound.js\";\nimport { type SoundSubGraph, connectSoundSubGraph, createSoundSubGraph, disposeSoundSubGraph, setSoundSubGraphVolume } from \"./sound-sub-graph.js\";\n\n/** A streaming sound source: a URL, a list of candidate URLs, or an existing media element. */\nexport type StreamingSoundSource = string | string[] | HTMLMediaElement;\n\n/** Options for {@link createStreamingSoundAsync}. */\nexport interface StreamingSoundOptions {\n /** Play immediately once ready. Defaults to `false`. */\n autoplay?: boolean;\n /** Loop playback. Defaults to `false`. */\n loop?: boolean;\n /** Maximum simultaneous instances. Defaults to `Infinity`. */\n maxInstances?: number;\n /** Output bus. Defaults to the engine's default main bus. */\n outBus?: PrimaryAudioBus;\n /** Number of instances to preload. Defaults to `1`. */\n preloadCount?: number;\n /** Start offset in seconds. Defaults to `0`. */\n startOffset?: number;\n /** Initial volume. Defaults to `1`. */\n volume?: number;\n}\n\n/** Per-play overrides for {@link playStreamingSound}. */\nexport interface StreamingSoundPlayOptions {\n /** Loop playback. */\n loop?: boolean;\n /** Start offset in seconds. */\n startOffset?: number;\n /** Per-instance volume. */\n volume?: number;\n}\n\ninterface StoredStreamingOptions {\n autoplay: boolean;\n loop: boolean;\n maxInstances: number;\n preloadCount: number;\n startOffset: number;\n}\n\n/** A streaming, media-element-backed sound. Pure state — drive it with the streaming-sound functions. */\nexport interface StreamingSound {\n /** Optional name. */\n readonly name?: string;\n /** Current playback state. */\n readonly state: SoundState;\n /** Number of live instances. */\n readonly instanceCount: number;\n /** Number of instances that have finished preloading. */\n readonly preloadCompletedCount: number;\n /** Fires when the sound finishes (all instances ended). */\n readonly onEnded: AudioSignal<StreamingSound>;\n\n /** @internal */ _engine: AudioEngine;\n /** @internal */ _source: StreamingSoundSource;\n /** @internal */ _graph: SoundSubGraph;\n /** @internal */ _outBus: PrimaryAudioBus;\n /** @internal */ _options: StoredStreamingOptions;\n /** @internal */ _instances: Set<StreamingInstance>;\n /** @internal */ _preloaded: StreamingInstance[];\n /** @internal */ _newest: StreamingInstance | null;\n /** @internal */ _state: SoundState;\n /** @internal */ _onEnded: AudioSignalImpl<StreamingSound>;\n /** @internal */ _dispose(): void;\n}\n\n/** A single in-flight playback of a {@link StreamingSound}. @internal */\nexport interface StreamingInstance {\n /** @internal */ _sound: StreamingSound;\n /** @internal */ _engine: AudioEngine;\n /** @internal */ _options: { loop: boolean; startOffset: number };\n /** @internal */ _mediaElement: HTMLMediaElement;\n /** @internal */ _sourceNode: MediaElementAudioSourceNode | null;\n /** @internal */ _volumeNode: GainNode;\n /** @internal */ _state: SoundState;\n /** @internal */ _enginePlayTime: number;\n /** @internal */ _enginePauseTime: number;\n /** @internal */ _isReady: boolean;\n /** @internal */ _readyPromise: Promise<void>;\n /** @internal */ _resolveReady: () => void;\n /** @internal */ _rejectReady: (reason?: unknown) => void;\n /** @internal */ _preloadedPromise: Promise<void>;\n /** @internal */ _engineStateUnsub: (() => void) | null;\n /** @internal */ _userGestureUnsub: (() => void) | null;\n /** @internal */ _onEnded: AudioSignalImpl<StreamingInstance>;\n /** @internal */ _onStateChanged: AudioSignalImpl<StreamingInstance>;\n /** @internal */ _endedHandler: () => void;\n /** @internal */ _canPlayThroughHandler: () => void;\n /** @internal */ _mediaEndedHandler: () => void;\n /** @internal */ _errorHandler: (reason?: unknown) => void;\n /** @internal */ _engineStateHandler: () => void;\n /** @internal */ _userGestureHandler: () => void;\n}\n\n/**\n * Creates a streaming, media-element-backed sound routed to the given output bus\n * (or the engine's default main bus). Requires a real-time `AudioContext`.\n * @param engine - The audio engine.\n * @param source - A URL, a list of candidate URLs, or an `HTMLMediaElement`.\n * @param options - Streaming sound options.\n * @returns A promise that resolves with the ready sound.\n */\nexport async function createStreamingSoundAsync(engine: AudioEngine, source: StreamingSoundSource, options: StreamingSoundOptions = {}): Promise<StreamingSound> {\n if (engine._isOffline) {\n throw new Error(\"Streaming sounds require a real-time AudioContext.\");\n }\n\n await engine._isReady;\n\n const graph = createSoundSubGraph(engine._ctx, engine, options.volume ?? 1);\n const outBus = options.outBus ?? engine._mainBus;\n connectSoundSubGraph(graph, getBusInputNode(outBus));\n\n const onEnded = createAudioSignal<StreamingSound>();\n\n const sound: StreamingSound = {\n get state() {\n return sound._state;\n },\n get instanceCount() {\n return sound._instances.size;\n },\n get preloadCompletedCount() {\n return sound._preloaded.length;\n },\n get onEnded(): AudioSignal<StreamingSound> {\n return onEnded;\n },\n _engine: engine,\n _source: source,\n _graph: graph,\n _outBus: outBus,\n _options: {\n autoplay: options.autoplay ?? false,\n loop: options.loop ?? false,\n maxInstances: options.maxInstances ?? Infinity,\n preloadCount: options.preloadCount ?? 1,\n startOffset: options.startOffset ?? 0,\n },\n _instances: new Set<StreamingInstance>(),\n _preloaded: [],\n _newest: null,\n _state: SoundState.Stopped,\n _onEnded: onEnded,\n _dispose: () => disposeStreamingSound(sound),\n };\n\n engine._sounds.add(sound);\n\n if (sound._options.preloadCount > 0) {\n await preloadStreamingInstancesAsync(sound, sound._options.preloadCount);\n }\n\n if (sound._options.autoplay) {\n playStreamingSound(sound);\n }\n\n return sound;\n}\n\n/** Preloads a single instance of the sound and resolves once it can play through. */\nexport function preloadStreamingInstanceAsync(sound: StreamingSound): Promise<void> {\n const instance = _createStreamingInstance(sound);\n if (!sound._preloaded.includes(instance)) {\n sound._preloaded.push(instance);\n }\n return instance._preloadedPromise;\n}\n\n/** Preloads the given number of instances and resolves once all can play through. */\nexport async function preloadStreamingInstancesAsync(sound: StreamingSound, count: number): Promise<void> {\n const promises: Array<Promise<void>> = [];\n for (let i = 0; i < count; i++) {\n promises.push(preloadStreamingInstanceAsync(sound));\n }\n await Promise.all(promises);\n}\n\n/**\n * Plays the streaming sound. Reuses a preloaded instance when available,\n * otherwise spawns a new one (subject to `maxInstances`). A paused sound is\n * resumed instead.\n * @param sound - The sound to play.\n * @param options - Per-play overrides.\n */\nexport function playStreamingSound(sound: StreamingSound, options: StreamingSoundPlayOptions = {}): void {\n if (sound._state === SoundState.Paused) {\n resumeStreamingSound(sound, options);\n return;\n }\n\n let instance: StreamingInstance;\n if (sound._preloaded.length > 0) {\n instance = sound._preloaded[0]!;\n instance._options.startOffset = sound._options.startOffset;\n _removePreloaded(sound, instance);\n } else {\n instance = _createStreamingInstance(sound);\n }\n\n instance._onEnded.addOnce(instance._endedHandler);\n sound._instances.add(instance);\n sound._newest = instance;\n\n const onStateChanged = (): void => {\n if (instance._state === SoundState.Started) {\n _stopExcessInstances(sound);\n unsubStateChanged();\n }\n };\n const unsubStateChanged = instance._onStateChanged.add(onStateChanged);\n\n const opts: Required<StreamingSoundPlayOptions> = {\n loop: options.loop ?? sound._options.loop,\n startOffset: options.startOffset ?? sound._options.startOffset,\n volume: options.volume ?? 1,\n };\n\n _instancePlay(instance, opts);\n sound._state = instance._state;\n}\n\n/** Pauses all of the sound's instances. */\nexport function pauseStreamingSound(sound: StreamingSound): void {\n for (const instance of sound._instances) {\n _instancePause(instance);\n }\n sound._state = SoundState.Paused;\n}\n\n/** Resumes a paused streaming sound. */\nexport function resumeStreamingSound(sound: StreamingSound, options: StreamingSoundPlayOptions = {}): void {\n if (sound._state !== SoundState.Paused) {\n return;\n }\n for (const instance of sound._instances) {\n _instanceResume(instance, options);\n }\n sound._state = SoundState.Started;\n}\n\n/** Stops the sound (and all its instances). */\nexport function stopStreamingSound(sound: StreamingSound): void {\n sound._state = SoundState.Stopped;\n for (const instance of Array.from(sound._instances)) {\n _instanceStop(instance);\n }\n}\n\n/** Sets the sound's output volume, optionally ramping. */\nexport function setStreamingSoundVolume(sound: StreamingSound, value: number, options?: RampOptions): void {\n setSoundSubGraphVolume(sound._graph, value, options);\n}\n\n/** Disposes the sound, stopping playback and releasing its graph and preloaded instances. */\nexport function disposeStreamingSound(sound: StreamingSound): void {\n stopStreamingSound(sound);\n for (const instance of Array.from(sound._instances)) {\n _instanceDispose(instance);\n }\n sound._instances.clear();\n for (const instance of sound._preloaded.slice()) {\n _instanceDispose(instance);\n }\n sound._preloaded.length = 0;\n disposeSoundSubGraph(sound._graph);\n sound._onEnded._clear();\n sound._engine._sounds.delete(sound);\n}\n\nfunction _stopExcessInstances(sound: StreamingSound): void {\n if (sound._options.maxInstances < Infinity) {\n const started = Array.from(sound._instances).filter((i) => i._state === SoundState.Started);\n const toStop = started.length - sound._options.maxInstances;\n for (let i = 0; i < toStop; i++) {\n _instanceStop(started[i]!);\n }\n }\n}\n\nfunction _removePreloaded(sound: StreamingSound, instance: StreamingInstance): void {\n const index = sound._preloaded.indexOf(instance);\n if (index !== -1) {\n sound._preloaded.splice(index, 1);\n }\n}\n\nfunction _onInstanceEnded(sound: StreamingSound, instance: StreamingInstance): void {\n if (sound._newest === instance) {\n sound._newest = null;\n }\n sound._instances.delete(instance);\n if (sound._instances.size === 0) {\n sound._state = SoundState.Stopped;\n sound._onEnded._notify(sound);\n }\n _instanceDispose(instance);\n}\n\n// --- Instance ---------------------------------------------------------------\n\nfunction _createStreamingInstance(sound: StreamingSound): StreamingInstance {\n const engine = sound._engine;\n\n let resolveReady!: () => void;\n let rejectReady!: (reason?: unknown) => void;\n const readyPromise = new Promise<void>((resolve, reject) => {\n resolveReady = resolve;\n rejectReady = reject;\n });\n\n const instance: StreamingInstance = {\n _sound: sound,\n _engine: engine,\n _options: {\n loop: sound._options.loop,\n startOffset: sound._options.startOffset,\n },\n _mediaElement: undefined as unknown as HTMLMediaElement,\n _sourceNode: null,\n _volumeNode: new GainNode(engine._ctx),\n _state: SoundState.Stopped,\n _enginePlayTime: Infinity,\n _enginePauseTime: 0,\n _isReady: false,\n _readyPromise: readyPromise,\n _resolveReady: resolveReady,\n _rejectReady: rejectReady,\n // The preloaded promise resolves on ready and rejects on error.\n _preloadedPromise: readyPromise,\n _engineStateUnsub: null,\n _userGestureUnsub: null,\n _onEnded: createAudioSignal<StreamingInstance>(),\n _onStateChanged: createAudioSignal<StreamingInstance>(),\n _endedHandler: () => _onInstanceEnded(sound, instance),\n _canPlayThroughHandler: () => _onCanPlayThrough(instance),\n _mediaEndedHandler: () => _onMediaEnded(instance),\n _errorHandler: (reason?: unknown) => _onMediaError(instance, reason),\n _engineStateHandler: () => _onEngineStateChanged(instance),\n _userGestureHandler: () => _instancePlay(instance, { loop: instance._options.loop, startOffset: instance._options.startOffset, volume: instance._volumeNode.gain.value }),\n };\n\n _initMediaElement(instance);\n return instance;\n}\n\nfunction _initMediaElement(instance: StreamingInstance): void {\n const source = instance._sound._source;\n let mediaElement: HTMLMediaElement;\n\n if (typeof source === \"string\") {\n mediaElement = new Audio(cleanAudioUrl(source));\n } else if (Array.isArray(source)) {\n mediaElement = new Audio();\n for (const url of source) {\n const sourceEl = document.createElement(\"source\");\n sourceEl.src = cleanAudioUrl(url);\n mediaElement.appendChild(sourceEl);\n }\n } else {\n mediaElement = source;\n }\n\n // Babylon Lite default: request CORS so cross-origin media can be routed\n // through Web Audio (adaptation of AudioV2's Tools.SetCorsBehavior).\n mediaElement.crossOrigin = \"anonymous\";\n mediaElement.controls = false;\n mediaElement.loop = instance._options.loop;\n mediaElement.preload = \"auto\";\n\n mediaElement.addEventListener(\"canplaythrough\", instance._canPlayThroughHandler, { once: true });\n mediaElement.addEventListener(\"ended\", instance._mediaEndedHandler, { once: true });\n mediaElement.addEventListener(\"error\", instance._errorHandler, { once: true });\n\n mediaElement.load();\n\n const sourceNode = new MediaElementAudioSourceNode(instance._engine._ctx as AudioContext, { mediaElement });\n sourceNode.connect(instance._volumeNode);\n instance._volumeNode.connect(instance._sound._graph._in);\n\n instance._sourceNode = sourceNode;\n instance._mediaElement = mediaElement;\n}\n\nfunction _setStreamingInstanceState(instance: StreamingInstance, state: SoundState): void {\n instance._state = state;\n instance._onStateChanged._notify(instance);\n}\n\nfunction _instanceCurrentTime(instance: StreamingInstance): number {\n if (instance._state === SoundState.Stopped) {\n return 0;\n }\n const timeSinceLastStart = instance._state === SoundState.Paused ? 0 : instance._engine.currentTime - instance._enginePlayTime;\n return instance._enginePauseTime + timeSinceLastStart + instance._options.startOffset;\n}\n\nfunction _instancePlay(instance: StreamingInstance, options: Required<StreamingSoundPlayOptions>): void {\n if (instance._state === SoundState.Started) {\n return;\n }\n\n instance._options.loop = options.loop;\n instance._mediaElement.loop = options.loop;\n\n let startOffset = options.startOffset;\n if (instance._state === SoundState.Paused) {\n startOffset = _instanceCurrentTime(instance);\n }\n if (startOffset > 0) {\n instance._mediaElement.currentTime = startOffset;\n }\n\n instance._volumeNode.gain.value = options.volume;\n\n _play(instance);\n}\n\nfunction _instancePause(instance: StreamingInstance): void {\n if (instance._state !== SoundState.Starting && instance._state !== SoundState.Started) {\n return;\n }\n _setStreamingInstanceState(instance, SoundState.Paused);\n instance._enginePauseTime += instance._engine.currentTime - instance._enginePlayTime;\n instance._mediaElement.pause();\n}\n\nfunction _instanceResume(instance: StreamingInstance, options: StreamingSoundPlayOptions): void {\n if (instance._state !== SoundState.Paused) {\n return;\n }\n _instancePlay(instance, {\n loop: options.loop ?? instance._options.loop,\n startOffset: options.startOffset ?? instance._options.startOffset,\n volume: options.volume ?? instance._volumeNode.gain.value,\n });\n}\n\nfunction _instanceStop(instance: StreamingInstance): void {\n if (instance._state === SoundState.Stopped) {\n return;\n }\n instance._mediaElement.pause();\n _onMediaEnded(instance);\n instance._engineStateUnsub?.();\n instance._engineStateUnsub = null;\n}\n\nfunction _instanceDispose(instance: StreamingInstance): void {\n _instanceStop(instance);\n\n instance._sourceNode?.disconnect(instance._volumeNode);\n instance._sourceNode = null;\n instance._volumeNode.disconnect();\n\n const mediaElement = instance._mediaElement;\n if (mediaElement) {\n mediaElement.removeEventListener(\"canplaythrough\", instance._canPlayThroughHandler);\n mediaElement.removeEventListener(\"ended\", instance._mediaEndedHandler);\n mediaElement.removeEventListener(\"error\", instance._errorHandler);\n }\n\n instance._engineStateUnsub?.();\n instance._engineStateUnsub = null;\n instance._userGestureUnsub?.();\n instance._userGestureUnsub = null;\n\n // Resolve any still-pending preload waiters so disposal never hangs.\n instance._resolveReady();\n instance._onEnded._clear();\n instance._onStateChanged._clear();\n}\n\nfunction _onCanPlayThrough(instance: StreamingInstance): void {\n instance._isReady = true;\n instance._resolveReady();\n}\n\nfunction _onMediaEnded(instance: StreamingInstance): void {\n // Capture the state before the transition: `_setStreamingInstanceState`\n // overwrites `_state` to `Stopped`, so the paused check must read it first.\n const wasPaused = instance._state === SoundState.Paused;\n _setStreamingInstanceState(instance, SoundState.Stopped);\n if (!wasPaused) {\n instance._onEnded._notify(instance);\n }\n}\n\nfunction _onMediaError(instance: StreamingInstance, reason?: unknown): void {\n _setStreamingInstanceState(instance, SoundState.FailedToStart);\n instance._rejectReady(reason);\n _instanceDispose(instance);\n}\n\nfunction _onEngineStateChanged(instance: StreamingInstance): void {\n if (instance._engine.state !== \"running\") {\n return;\n }\n if (instance._options.loop && instance._state === SoundState.Starting) {\n _instancePlay(instance, { loop: instance._options.loop, startOffset: instance._options.startOffset, volume: instance._volumeNode.gain.value });\n }\n instance._engineStateUnsub?.();\n instance._engineStateUnsub = null;\n}\n\nfunction _play(instance: StreamingInstance): void {\n _setStreamingInstanceState(instance, SoundState.Starting);\n\n if (!instance._isReady) {\n void _playWhenReady(instance);\n return;\n }\n if (instance._state !== SoundState.Starting) {\n return;\n }\n\n if (instance._engine.state === \"running\") {\n const result = instance._mediaElement.play() as Promise<void> | undefined;\n instance._enginePlayTime = instance._engine.currentTime;\n _setStreamingInstanceState(instance, SoundState.Started);\n if (result && typeof result.then === \"function\") {\n void _awaitPlayResult(instance, result);\n }\n } else if (instance._options.loop) {\n instance._engineStateUnsub = instance._engine.onStateChanged.add(instance._engineStateHandler);\n } else {\n _instanceStop(instance);\n _setStreamingInstanceState(instance, SoundState.FailedToStart);\n }\n}\n\nasync function _playWhenReady(instance: StreamingInstance): Promise<void> {\n try {\n await instance._readyPromise;\n _play(instance);\n } catch {\n _setStreamingInstanceState(instance, SoundState.FailedToStart);\n }\n}\n\nasync function _awaitPlayResult(instance: StreamingInstance, result: Promise<void>): Promise<void> {\n try {\n await result;\n } catch {\n // Playback can fail even when the engine reports \"running\" (e.g. an OS\n // auto-resume without a user gesture). Recover on the next gesture.\n _setStreamingInstanceState(instance, SoundState.FailedToStart);\n if (instance._options.loop) {\n instance._userGestureUnsub = instance._engine.onUserGesture.addOnce(instance._userGestureHandler);\n }\n }\n}\n"],"names":[],"mappings":";;;;;;AAoIA,eAAsB,yBAAA,CAA0B,MAAA,EAAqB,MAAA,EAA8B,OAAA,GAAiC,EAAC,EAA4B;AAC7J,EAAA,IAAI,OAAO,UAAA,EAAY;AACnB,IAAA,MAAM,IAAI,MAAM,oDAAoD,CAAA;AAAA,EACxE;AAEA,EAAA,MAAM,MAAA,CAAO,QAAA;AAEb,EAAA,MAAM,QAAQ,mBAAA,CAAoB,MAAA,CAAO,MAAM,MAAA,EAAQ,OAAA,CAAQ,UAAU,CAAC,CAAA;AAC1E,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,IAAU,MAAA,CAAO,QAAA;AACxC,EAAA,oBAAA,CAAqB,KAAA,EAAO,eAAA,CAAgB,MAAM,CAAC,CAAA;AAEnD,EAAA,MAAM,UAAU,iBAAA,EAAkC;AAElD,EAAA,MAAM,KAAA,GAAwB;AAAA,IAC1B,IAAI,KAAA,GAAQ;AACR,MAAA,OAAO,KAAA,CAAM,MAAA;AAAA,IACjB,CAAA;AAAA,IACA,IAAI,aAAA,GAAgB;AAChB,MAAA,OAAO,MAAM,UAAA,CAAW,IAAA;AAAA,IAC5B,CAAA;AAAA,IACA,IAAI,qBAAA,GAAwB;AACxB,MAAA,OAAO,MAAM,UAAA,CAAW,MAAA;AAAA,IAC5B,CAAA;AAAA,IACA,IAAI,OAAA,GAAuC;AACvC,MAAA,OAAO,OAAA;AAAA,IACX,CAAA;AAAA,IACA,OAAA,EAAS,MAAA;AAAA,IACT,OAAA,EAAS,MAAA;AAAA,IACT,MAAA,EAAQ,KAAA;AAAA,IACR,OAAA,EAAS,MAAA;AAAA,IACT,QAAA,EAAU;AAAA,MACN,QAAA,EAAU,QAAQ,QAAA,IAAY,KAAA;AAAA,MAC9B,IAAA,EAAM,QAAQ,IAAA,IAAQ,KAAA;AAAA,MACtB,YAAA,EAAc,QAAQ,YAAA,IAAgB,QAAA;AAAA,MACtC,YAAA,EAAc,QAAQ,YAAA,IAAgB,CAAA;AAAA,MACtC,WAAA,EAAa,QAAQ,WAAA,IAAe;AAAA,KACxC;AAAA,IACA,UAAA,sBAAgB,GAAA,EAAuB;AAAA,IACvC,YAAY,EAAC;AAAA,IACb,OAAA,EAAS,IAAA;AAAA,IACT,QAAQ,UAAA,CAAW,OAAA;AAAA,IACnB,QAAA,EAAU,OAAA;AAAA,IACV,QAAA,EAAU,MAAM,qBAAA,CAAsB,KAAK;AAAA,GAC/C;AAEA,EAAA,MAAA,CAAO,OAAA,CAAQ,IAAI,KAAK,CAAA;AAExB,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,YAAA,GAAe,CAAA,EAAG;AACjC,IAAA,MAAM,8BAAA,CAA+B,KAAA,EAAO,KAAA,CAAM,QAAA,CAAS,YAAY,CAAA;AAAA,EAC3E;AAEA,EAAA,IAAI,KAAA,CAAM,SAAS,QAAA,EAAU;AACzB,IAAA,kBAAA,CAAmB,KAAK,CAAA;AAAA,EAC5B;AAEA,EAAA,OAAO,KAAA;AACX;AAGO,SAAS,8BAA8B,KAAA,EAAsC;AAChF,EAAA,MAAM,QAAA,GAAW,yBAAyB,KAAK,CAAA;AAC/C,EAAA,IAAI,CAAC,KAAA,CAAM,UAAA,CAAW,QAAA,CAAS,QAAQ,CAAA,EAAG;AACtC,IAAA,KAAA,CAAM,UAAA,CAAW,KAAK,QAAQ,CAAA;AAAA,EAClC;AACA,EAAA,OAAO,QAAA,CAAS,iBAAA;AACpB;AAGA,eAAsB,8BAAA,CAA+B,OAAuB,KAAA,EAA8B;AACtG,EAAA,MAAM,WAAiC,EAAC;AACxC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC5B,IAAA,QAAA,CAAS,IAAA,CAAK,6BAAA,CAA8B,KAAK,CAAC,CAAA;AAAA,EACtD;AACA,EAAA,MAAM,OAAA,CAAQ,IAAI,QAAQ,CAAA;AAC9B;AASO,SAAS,kBAAA,CAAmB,KAAA,EAAuB,OAAA,GAAqC,EAAC,EAAS;AACrG,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,UAAA,CAAW,MAAA,EAAQ;AACpC,IAAA,oBAAA,CAAqB,OAAO,OAAO,CAAA;AACnC,IAAA;AAAA,EACJ;AAEA,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI,KAAA,CAAM,UAAA,CAAW,MAAA,GAAS,CAAA,EAAG;AAC7B,IAAA,QAAA,GAAW,KAAA,CAAM,WAAW,CAAC,CAAA;AAC7B,IAAA,QAAA,CAAS,QAAA,CAAS,WAAA,GAAc,KAAA,CAAM,QAAA,CAAS,WAAA;AAC/C,IAAA,gBAAA,CAAiB,OAAO,QAAQ,CAAA;AAAA,EACpC,CAAA,MAAO;AACH,IAAA,QAAA,GAAW,yBAAyB,KAAK,CAAA;AAAA,EAC7C;AAEA,EAAA,QAAA,CAAS,QAAA,CAAS,OAAA,CAAQ,QAAA,CAAS,aAAa,CAAA;AAChD,EAAA,KAAA,CAAM,UAAA,CAAW,IAAI,QAAQ,CAAA;AAC7B,EAAA,KAAA,CAAM,OAAA,GAAU,QAAA;AAEhB,EAAA,MAAM,iBAAiB,MAAY;AAC/B,IAAA,IAAI,QAAA,CAAS,MAAA,KAAW,UAAA,CAAW,OAAA,EAAS;AACxC,MAAA,oBAAA,CAAqB,KAAK,CAAA;AAC1B,MAAA,iBAAA,EAAkB;AAAA,IACtB;AAAA,EACJ,CAAA;AACA,EAAA,MAAM,iBAAA,GAAoB,QAAA,CAAS,eAAA,CAAgB,GAAA,CAAI,cAAc,CAAA;AAErE,EAAA,MAAM,IAAA,GAA4C;AAAA,IAC9C,IAAA,EAAM,OAAA,CAAQ,IAAA,IAAQ,KAAA,CAAM,QAAA,CAAS,IAAA;AAAA,IACrC,WAAA,EAAa,OAAA,CAAQ,WAAA,IAAe,KAAA,CAAM,QAAA,CAAS,WAAA;AAAA,IACnD,MAAA,EAAQ,QAAQ,MAAA,IAAU;AAAA,GAC9B;AAEA,EAAA,aAAA,CAAc,UAAU,IAAI,CAAA;AAC5B,EAAA,KAAA,CAAM,SAAS,QAAA,CAAS,MAAA;AAC5B;AAGO,SAAS,oBAAoB,KAAA,EAA6B;AAC7D,EAAA,KAAA,MAAW,QAAA,IAAY,MAAM,UAAA,EAAY;AACrC,IAAA,cAAA,CAAe,QAAQ,CAAA;AAAA,EAC3B;AACA,EAAA,KAAA,CAAM,SAAS,UAAA,CAAW,MAAA;AAC9B;AAGO,SAAS,oBAAA,CAAqB,KAAA,EAAuB,OAAA,GAAqC,EAAC,EAAS;AACvG,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,UAAA,CAAW,MAAA,EAAQ;AACpC,IAAA;AAAA,EACJ;AACA,EAAA,KAAA,MAAW,QAAA,IAAY,MAAM,UAAA,EAAY;AACrC,IAAA,eAAA,CAAgB,UAAU,OAAO,CAAA;AAAA,EACrC;AACA,EAAA,KAAA,CAAM,SAAS,UAAA,CAAW,OAAA;AAC9B;AAGO,SAAS,mBAAmB,KAAA,EAA6B;AAC5D,EAAA,KAAA,CAAM,SAAS,UAAA,CAAW,OAAA;AAC1B,EAAA,KAAA,MAAW,QAAA,IAAY,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,UAAU,CAAA,EAAG;AACjD,IAAA,aAAA,CAAc,QAAQ,CAAA;AAAA,EAC1B;AACJ;AAGO,SAAS,uBAAA,CAAwB,KAAA,EAAuB,KAAA,EAAe,OAAA,EAA6B;AACvG,EAAA,sBAAA,CAAuB,KAAA,CAAM,MAAA,EAAQ,KAAA,EAAO,OAAO,CAAA;AACvD;AAGO,SAAS,sBAAsB,KAAA,EAA6B;AAC/D,EAAA,kBAAA,CAAmB,KAAK,CAAA;AACxB,EAAA,KAAA,MAAW,QAAA,IAAY,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,UAAU,CAAA,EAAG;AACjD,IAAA,gBAAA,CAAiB,QAAQ,CAAA;AAAA,EAC7B;AACA,EAAA,KAAA,CAAM,WAAW,KAAA,EAAM;AACvB,EAAA,KAAA,MAAW,QAAA,IAAY,KAAA,CAAM,UAAA,CAAW,KAAA,EAAM,EAAG;AAC7C,IAAA,gBAAA,CAAiB,QAAQ,CAAA;AAAA,EAC7B;AACA,EAAA,KAAA,CAAM,WAAW,MAAA,GAAS,CAAA;AAC1B,EAAA,oBAAA,CAAqB,MAAM,MAAM,CAAA;AACjC,EAAA,KAAA,CAAM,SAAS,MAAA,EAAO;AACtB,EAAA,KAAA,CAAM,OAAA,CAAQ,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAA;AACtC;AAEA,SAAS,qBAAqB,KAAA,EAA6B;AACvD,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,YAAA,GAAe,QAAA,EAAU;AACxC,IAAA,MAAM,OAAA,GAAU,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,UAAU,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,UAAA,CAAW,OAAO,CAAA;AAC1F,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,GAAS,KAAA,CAAM,QAAA,CAAS,YAAA;AAC/C,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC7B,MAAA,aAAA,CAAc,OAAA,CAAQ,CAAC,CAAE,CAAA;AAAA,IAC7B;AAAA,EACJ;AACJ;AAEA,SAAS,gBAAA,CAAiB,OAAuB,QAAA,EAAmC;AAChF,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,UAAA,CAAW,OAAA,CAAQ,QAAQ,CAAA;AAC/C,EAAA,IAAI,UAAU,EAAA,EAAI;AACd,IAAA,KAAA,CAAM,UAAA,CAAW,MAAA,CAAO,KAAA,EAAO,CAAC,CAAA;AAAA,EACpC;AACJ;AAEA,SAAS,gBAAA,CAAiB,OAAuB,QAAA,EAAmC;AAChF,EAAA,IAAI,KAAA,CAAM,YAAY,QAAA,EAAU;AAC5B,IAAA,KAAA,CAAM,OAAA,GAAU,IAAA;AAAA,EACpB;AACA,EAAA,KAAA,CAAM,UAAA,CAAW,OAAO,QAAQ,CAAA;AAChC,EAAA,IAAI,KAAA,CAAM,UAAA,CAAW,IAAA,KAAS,CAAA,EAAG;AAC7B,IAAA,KAAA,CAAM,SAAS,UAAA,CAAW,OAAA;AAC1B,IAAA,KAAA,CAAM,QAAA,CAAS,QAAQ,KAAK,CAAA;AAAA,EAChC;AACA,EAAA,gBAAA,CAAiB,QAAQ,CAAA;AAC7B;AAIA,SAAS,yBAAyB,KAAA,EAA0C;AACxE,EAAA,MAAM,SAAS,KAAA,CAAM,OAAA;AAErB,EAAA,IAAI,YAAA;AACJ,EAAA,IAAI,WAAA;AACJ,EAAA,MAAM,YAAA,GAAe,IAAI,OAAA,CAAc,CAAC,SAAS,MAAA,KAAW;AACxD,IAAA,YAAA,GAAe,OAAA;AACf,IAAA,WAAA,GAAc,MAAA;AAAA,EAClB,CAAC,CAAA;AAED,EAAA,MAAM,QAAA,GAA8B;AAAA,IAChC,MAAA,EAAQ,KAAA;AAAA,IACR,OAAA,EAAS,MAAA;AAAA,IACT,QAAA,EAAU;AAAA,MACN,IAAA,EAAM,MAAM,QAAA,CAAS,IAAA;AAAA,MACrB,WAAA,EAAa,MAAM,QAAA,CAAS;AAAA,KAChC;AAAA,IACA,aAAA,EAAe,MAAA;AAAA,IACf,WAAA,EAAa,IAAA;AAAA,IACb,WAAA,EAAa,IAAI,QAAA,CAAS,MAAA,CAAO,IAAI,CAAA;AAAA,IACrC,QAAQ,UAAA,CAAW,OAAA;AAAA,IACnB,eAAA,EAAiB,QAAA;AAAA,IACjB,gBAAA,EAAkB,CAAA;AAAA,IAClB,QAAA,EAAU,KAAA;AAAA,IACV,aAAA,EAAe,YAAA;AAAA,IACf,aAAA,EAAe,YAAA;AAAA,IACf,YAAA,EAAc,WAAA;AAAA;AAAA,IAEd,iBAAA,EAAmB,YAAA;AAAA,IACnB,iBAAA,EAAmB,IAAA;AAAA,IACnB,iBAAA,EAAmB,IAAA;AAAA,IACnB,UAAU,iBAAA,EAAqC;AAAA,IAC/C,iBAAiB,iBAAA,EAAqC;AAAA,IACtD,aAAA,EAAe,MAAM,gBAAA,CAAiB,KAAA,EAAO,QAAQ,CAAA;AAAA,IACrD,sBAAA,EAAwB,MAAM,iBAAA,CAAkB,QAAQ,CAAA;AAAA,IACxD,kBAAA,EAAoB,MAAM,aAAA,CAAc,QAAQ,CAAA;AAAA,IAChD,aAAA,EAAe,CAAC,MAAA,KAAqB,aAAA,CAAc,UAAU,MAAM,CAAA;AAAA,IACnE,mBAAA,EAAqB,MAAM,qBAAA,CAAsB,QAAQ,CAAA;AAAA,IACzD,qBAAqB,MAAM,aAAA,CAAc,UAAU,EAAE,IAAA,EAAM,SAAS,QAAA,CAAS,IAAA,EAAM,WAAA,EAAa,QAAA,CAAS,SAAS,WAAA,EAAa,MAAA,EAAQ,SAAS,WAAA,CAAY,IAAA,CAAK,OAAO;AAAA,GAC5K;AAEA,EAAA,iBAAA,CAAkB,QAAQ,CAAA;AAC1B,EAAA,OAAO,QAAA;AACX;AAEA,SAAS,kBAAkB,QAAA,EAAmC;AAC1D,EAAA,MAAM,MAAA,GAAS,SAAS,MAAA,CAAO,OAAA;AAC/B,EAAA,IAAI,YAAA;AAEJ,EAAA,IAAI,OAAO,WAAW,QAAA,EAAU;AAC5B,IAAA,YAAA,GAAe,IAAI,KAAA,CAAM,aAAA,CAAc,MAAM,CAAC,CAAA;AAAA,EAClD,CAAA,MAAA,IAAW,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,EAAG;AAC9B,IAAA,YAAA,GAAe,IAAI,KAAA,EAAM;AACzB,IAAA,KAAA,MAAW,OAAO,MAAA,EAAQ;AACtB,MAAA,MAAM,QAAA,GAAW,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAChD,MAAA,QAAA,CAAS,GAAA,GAAM,cAAc,GAAG,CAAA;AAChC,MAAA,YAAA,CAAa,YAAY,QAAQ,CAAA;AAAA,IACrC;AAAA,EACJ,CAAA,MAAO;AACH,IAAA,YAAA,GAAe,MAAA;AAAA,EACnB;AAIA,EAAA,YAAA,CAAa,WAAA,GAAc,WAAA;AAC3B,EAAA,YAAA,CAAa,QAAA,GAAW,KAAA;AACxB,EAAA,YAAA,CAAa,IAAA,GAAO,SAAS,QAAA,CAAS,IAAA;AACtC,EAAA,YAAA,CAAa,OAAA,GAAU,MAAA;AAEvB,EAAA,YAAA,CAAa,iBAAiB,gBAAA,EAAkB,QAAA,CAAS,wBAAwB,EAAE,IAAA,EAAM,MAAM,CAAA;AAC/F,EAAA,YAAA,CAAa,iBAAiB,OAAA,EAAS,QAAA,CAAS,oBAAoB,EAAE,IAAA,EAAM,MAAM,CAAA;AAClF,EAAA,YAAA,CAAa,iBAAiB,OAAA,EAAS,QAAA,CAAS,eAAe,EAAE,IAAA,EAAM,MAAM,CAAA;AAE7E,EAAA,YAAA,CAAa,IAAA,EAAK;AAElB,EAAA,MAAM,UAAA,GAAa,IAAI,2BAAA,CAA4B,QAAA,CAAS,QAAQ,IAAA,EAAsB,EAAE,cAAc,CAAA;AAC1G,EAAA,UAAA,CAAW,OAAA,CAAQ,SAAS,WAAW,CAAA;AACvC,EAAA,QAAA,CAAS,WAAA,CAAY,OAAA,CAAQ,QAAA,CAAS,MAAA,CAAO,OAAO,GAAG,CAAA;AAEvD,EAAA,QAAA,CAAS,WAAA,GAAc,UAAA;AACvB,EAAA,QAAA,CAAS,aAAA,GAAgB,YAAA;AAC7B;AAEA,SAAS,0BAAA,CAA2B,UAA6B,KAAA,EAAyB;AACtF,EAAA,QAAA,CAAS,MAAA,GAAS,KAAA;AAClB,EAAA,QAAA,CAAS,eAAA,CAAgB,QAAQ,QAAQ,CAAA;AAC7C;AAEA,SAAS,qBAAqB,QAAA,EAAqC;AAC/D,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,UAAA,CAAW,OAAA,EAAS;AACxC,IAAA,OAAO,CAAA;AAAA,EACX;AACA,EAAA,MAAM,kBAAA,GAAqB,SAAS,MAAA,KAAW,UAAA,CAAW,SAAS,CAAA,GAAI,QAAA,CAAS,OAAA,CAAQ,WAAA,GAAc,QAAA,CAAS,eAAA;AAC/G,EAAA,OAAO,QAAA,CAAS,gBAAA,GAAmB,kBAAA,GAAqB,QAAA,CAAS,QAAA,CAAS,WAAA;AAC9E;AAEA,SAAS,aAAA,CAAc,UAA6B,OAAA,EAAoD;AACpG,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,UAAA,CAAW,OAAA,EAAS;AACxC,IAAA;AAAA,EACJ;AAEA,EAAA,QAAA,CAAS,QAAA,CAAS,OAAO,OAAA,CAAQ,IAAA;AACjC,EAAA,QAAA,CAAS,aAAA,CAAc,OAAO,OAAA,CAAQ,IAAA;AAEtC,EAAA,IAAI,cAAc,OAAA,CAAQ,WAAA;AAC1B,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,UAAA,CAAW,MAAA,EAAQ;AACvC,IAAA,WAAA,GAAc,qBAAqB,QAAQ,CAAA;AAAA,EAC/C;AACA,EAAA,IAAI,cAAc,CAAA,EAAG;AACjB,IAAA,QAAA,CAAS,cAAc,WAAA,GAAc,WAAA;AAAA,EACzC;AAEA,EAAA,QAAA,CAAS,WAAA,CAAY,IAAA,CAAK,KAAA,GAAQ,OAAA,CAAQ,MAAA;AAE1C,EAAA,KAAA,CAAM,QAAQ,CAAA;AAClB;AAEA,SAAS,eAAe,QAAA,EAAmC;AACvD,EAAA,IAAI,SAAS,MAAA,KAAW,UAAA,CAAW,YAAY,QAAA,CAAS,MAAA,KAAW,WAAW,OAAA,EAAS;AACnF,IAAA;AAAA,EACJ;AACA,EAAA,0BAAA,CAA2B,QAAA,EAAU,WAAW,MAAM,CAAA;AACtD,EAAA,QAAA,CAAS,gBAAA,IAAoB,QAAA,CAAS,OAAA,CAAQ,WAAA,GAAc,QAAA,CAAS,eAAA;AACrE,EAAA,QAAA,CAAS,cAAc,KAAA,EAAM;AACjC;AAEA,SAAS,eAAA,CAAgB,UAA6B,OAAA,EAA0C;AAC5F,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,UAAA,CAAW,MAAA,EAAQ;AACvC,IAAA;AAAA,EACJ;AACA,EAAA,aAAA,CAAc,QAAA,EAAU;AAAA,IACpB,IAAA,EAAM,OAAA,CAAQ,IAAA,IAAQ,QAAA,CAAS,QAAA,CAAS,IAAA;AAAA,IACxC,WAAA,EAAa,OAAA,CAAQ,WAAA,IAAe,QAAA,CAAS,QAAA,CAAS,WAAA;AAAA,IACtD,MAAA,EAAQ,OAAA,CAAQ,MAAA,IAAU,QAAA,CAAS,YAAY,IAAA,CAAK;AAAA,GACvD,CAAA;AACL;AAEA,SAAS,cAAc,QAAA,EAAmC;AACtD,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,UAAA,CAAW,OAAA,EAAS;AACxC,IAAA;AAAA,EACJ;AACA,EAAA,QAAA,CAAS,cAAc,KAAA,EAAM;AAC7B,EAAA,aAAA,CAAc,QAAQ,CAAA;AACtB,EAAA,QAAA,CAAS,iBAAA,IAAoB;AAC7B,EAAA,QAAA,CAAS,iBAAA,GAAoB,IAAA;AACjC;AAEA,SAAS,iBAAiB,QAAA,EAAmC;AACzD,EAAA,aAAA,CAAc,QAAQ,CAAA;AAEtB,EAAA,QAAA,CAAS,WAAA,EAAa,UAAA,CAAW,QAAA,CAAS,WAAW,CAAA;AACrD,EAAA,QAAA,CAAS,WAAA,GAAc,IAAA;AACvB,EAAA,QAAA,CAAS,YAAY,UAAA,EAAW;AAEhC,EAAA,MAAM,eAAe,QAAA,CAAS,aAAA;AAC9B,EAAA,IAAI,YAAA,EAAc;AACd,IAAA,YAAA,CAAa,mBAAA,CAAoB,gBAAA,EAAkB,QAAA,CAAS,sBAAsB,CAAA;AAClF,IAAA,YAAA,CAAa,mBAAA,CAAoB,OAAA,EAAS,QAAA,CAAS,kBAAkB,CAAA;AACrE,IAAA,YAAA,CAAa,mBAAA,CAAoB,OAAA,EAAS,QAAA,CAAS,aAAa,CAAA;AAAA,EACpE;AAEA,EAAA,QAAA,CAAS,iBAAA,IAAoB;AAC7B,EAAA,QAAA,CAAS,iBAAA,GAAoB,IAAA;AAC7B,EAAA,QAAA,CAAS,iBAAA,IAAoB;AAC7B,EAAA,QAAA,CAAS,iBAAA,GAAoB,IAAA;AAG7B,EAAA,QAAA,CAAS,aAAA,EAAc;AACvB,EAAA,QAAA,CAAS,SAAS,MAAA,EAAO;AACzB,EAAA,QAAA,CAAS,gBAAgB,MAAA,EAAO;AACpC;AAEA,SAAS,kBAAkB,QAAA,EAAmC;AAC1D,EAAA,QAAA,CAAS,QAAA,GAAW,IAAA;AACpB,EAAA,QAAA,CAAS,aAAA,EAAc;AAC3B;AAEA,SAAS,cAAc,QAAA,EAAmC;AAGtD,EAAA,MAAM,SAAA,GAAY,QAAA,CAAS,MAAA,KAAW,UAAA,CAAW,MAAA;AACjD,EAAA,0BAAA,CAA2B,QAAA,EAAU,WAAW,OAAO,CAAA;AACvD,EAAA,IAAI,CAAC,SAAA,EAAW;AACZ,IAAA,QAAA,CAAS,QAAA,CAAS,QAAQ,QAAQ,CAAA;AAAA,EACtC;AACJ;AAEA,SAAS,aAAA,CAAc,UAA6B,MAAA,EAAwB;AACxE,EAAA,0BAAA,CAA2B,QAAA,EAAU,WAAW,aAAa,CAAA;AAC7D,EAAA,QAAA,CAAS,aAAa,MAAM,CAAA;AAC5B,EAAA,gBAAA,CAAiB,QAAQ,CAAA;AAC7B;AAEA,SAAS,sBAAsB,QAAA,EAAmC;AAC9D,EAAA,IAAI,QAAA,CAAS,OAAA,CAAQ,KAAA,KAAU,SAAA,EAAW;AACtC,IAAA;AAAA,EACJ;AACA,EAAA,IAAI,SAAS,QAAA,CAAS,IAAA,IAAQ,QAAA,CAAS,MAAA,KAAW,WAAW,QAAA,EAAU;AACnE,IAAA,aAAA,CAAc,QAAA,EAAU,EAAE,IAAA,EAAM,QAAA,CAAS,SAAS,IAAA,EAAM,WAAA,EAAa,QAAA,CAAS,QAAA,CAAS,aAAa,MAAA,EAAQ,QAAA,CAAS,WAAA,CAAY,IAAA,CAAK,OAAO,CAAA;AAAA,EACjJ;AACA,EAAA,QAAA,CAAS,iBAAA,IAAoB;AAC7B,EAAA,QAAA,CAAS,iBAAA,GAAoB,IAAA;AACjC;AAEA,SAAS,MAAM,QAAA,EAAmC;AAC9C,EAAA,0BAAA,CAA2B,QAAA,EAAU,WAAW,QAAQ,CAAA;AAExD,EAAA,IAAI,CAAC,SAAS,QAAA,EAAU;AACpB,IAAA,KAAK,eAAe,QAAQ,CAAA;AAC5B,IAAA;AAAA,EACJ;AACA,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,UAAA,CAAW,QAAA,EAAU;AACzC,IAAA;AAAA,EACJ;AAEA,EAAA,IAAI,QAAA,CAAS,OAAA,CAAQ,KAAA,KAAU,SAAA,EAAW;AACtC,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,IAAA,EAAK;AAC3C,IAAA,QAAA,CAAS,eAAA,GAAkB,SAAS,OAAA,CAAQ,WAAA;AAC5C,IAAA,0BAAA,CAA2B,QAAA,EAAU,WAAW,OAAO,CAAA;AACvD,IAAA,IAAI,MAAA,IAAU,OAAO,MAAA,CAAO,IAAA,KAAS,UAAA,EAAY;AAC7C,MAAA,KAAK,gBAAA,CAAiB,UAAU,MAAM,CAAA;AAAA,IAC1C;AAAA,EACJ,CAAA,MAAA,IAAW,QAAA,CAAS,QAAA,CAAS,IAAA,EAAM;AAC/B,IAAA,QAAA,CAAS,oBAAoB,QAAA,CAAS,OAAA,CAAQ,cAAA,CAAe,GAAA,CAAI,SAAS,mBAAmB,CAAA;AAAA,EACjG,CAAA,MAAO;AACH,IAAA,aAAA,CAAc,QAAQ,CAAA;AACtB,IAAA,0BAAA,CAA2B,QAAA,EAAU,WAAW,aAAa,CAAA;AAAA,EACjE;AACJ;AAEA,eAAe,eAAe,QAAA,EAA4C;AACtE,EAAA,IAAI;AACA,IAAA,MAAM,QAAA,CAAS,aAAA;AACf,IAAA,KAAA,CAAM,QAAQ,CAAA;AAAA,EAClB,CAAA,CAAA,MAAQ;AACJ,IAAA,0BAAA,CAA2B,QAAA,EAAU,WAAW,aAAa,CAAA;AAAA,EACjE;AACJ;AAEA,eAAe,gBAAA,CAAiB,UAA6B,MAAA,EAAsC;AAC/F,EAAA,IAAI;AACA,IAAA,MAAM,MAAA;AAAA,EACV,CAAA,CAAA,MAAQ;AAGJ,IAAA,0BAAA,CAA2B,QAAA,EAAU,WAAW,aAAa,CAAA;AAC7D,IAAA,IAAI,QAAA,CAAS,SAAS,IAAA,EAAM;AACxB,MAAA,QAAA,CAAS,oBAAoB,QAAA,CAAS,OAAA,CAAQ,aAAA,CAAc,OAAA,CAAQ,SAAS,mBAAmB,CAAA;AAAA,IACpG;AAAA,EACJ;AACJ;;;;"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { unlockAudioEngineAsync } from './audio-engine.js';
|
|
2
|
+
|
|
3
|
+
function buildCss(top) {
|
|
4
|
+
return `.babylonUnmute{position:absolute;top:${top}px;margin-left:20px;height:40px;width:60px;background-color:rgba(51,51,51,0.7);background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2239%22%20height%3D%2232%22%20viewBox%3D%220%200%2039%2032%22%3E%3Cpath%20fill%3D%22white%22%20d%3D%22M9.625%2018.938l-0.031%200.016h-4.953q-0.016%200-0.031-0.016v-12.453q0-0.016%200.031-0.016h4.953q0.031%200%200.031%200.016v12.453zM12.125%207.688l8.719-8.703v27.453l-8.719-8.719-0.016-0.047v-9.938zM23.359%207.875l1.406-1.406%204.219%204.203%204.203-4.203%201.422%201.406-4.219%204.219%204.219%204.203-1.484%201.359-4.141-4.156-4.219%204.219-1.406-1.422%204.219-4.203z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E");background-size:80%;background-repeat:no-repeat;background-position:center;background-position-y:4px;border:none;outline:none;transition:transform 0.125s ease-out;cursor:pointer;z-index:9999;}.babylonUnmute:hover{transform:scale(1.05)}`;
|
|
5
|
+
}
|
|
6
|
+
function showButton(ui) {
|
|
7
|
+
if (!ui._button || !ui._enabled) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
ui._button.style.display = "block";
|
|
11
|
+
}
|
|
12
|
+
function hideButton(ui) {
|
|
13
|
+
if (ui._button) {
|
|
14
|
+
ui._button.style.display = "none";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function createUnmuteUI(engine, options = {}) {
|
|
18
|
+
const parent = options.parentElement ?? document.body;
|
|
19
|
+
const top = (parent.offsetTop || 0) + 20;
|
|
20
|
+
const style = document.createElement("style");
|
|
21
|
+
style.appendChild(document.createTextNode(buildCss(top)));
|
|
22
|
+
document.head.appendChild(style);
|
|
23
|
+
const button = document.createElement("button");
|
|
24
|
+
button.className = "babylonUnmute";
|
|
25
|
+
button.id = "babylonUnmuteButton";
|
|
26
|
+
button.addEventListener("click", () => {
|
|
27
|
+
void unlockAudioEngineAsync(engine);
|
|
28
|
+
});
|
|
29
|
+
parent.appendChild(button);
|
|
30
|
+
const ui = {
|
|
31
|
+
_engine: engine,
|
|
32
|
+
_button: button,
|
|
33
|
+
_style: style,
|
|
34
|
+
_enabled: true,
|
|
35
|
+
_unsub: null,
|
|
36
|
+
_dispose: () => disposeUnmuteUI(ui)
|
|
37
|
+
};
|
|
38
|
+
ui._unsub = engine.onStateChanged.add(() => {
|
|
39
|
+
if (engine.state === "running") {
|
|
40
|
+
hideButton(ui);
|
|
41
|
+
} else {
|
|
42
|
+
showButton(ui);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
if (engine.state === "running") {
|
|
46
|
+
hideButton(ui);
|
|
47
|
+
} else {
|
|
48
|
+
showButton(ui);
|
|
49
|
+
}
|
|
50
|
+
return ui;
|
|
51
|
+
}
|
|
52
|
+
function setUnmuteUIEnabled(ui, enabled) {
|
|
53
|
+
ui._enabled = enabled;
|
|
54
|
+
if (enabled) {
|
|
55
|
+
if (ui._engine.state !== "running") {
|
|
56
|
+
showButton(ui);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
hideButton(ui);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function disposeUnmuteUI(ui) {
|
|
63
|
+
ui._button?.remove();
|
|
64
|
+
ui._button = null;
|
|
65
|
+
ui._style?.remove();
|
|
66
|
+
ui._style = null;
|
|
67
|
+
ui._unsub?.();
|
|
68
|
+
ui._unsub = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { createUnmuteUI, disposeUnmuteUI, setUnmuteUIEnabled };
|
|
72
|
+
//# sourceMappingURL=unmute-ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unmute-ui.js","sources":["../../../src/audio/unmute-ui.ts"],"sourcesContent":["/**\n * Unmute UI — opt-in feature module.\n *\n * Faithful port of AudioV2 `_WebAudioUnmuteUI`, re-architected to Lite idioms\n * (pure state + standalone functions). Adds a DOM button that resumes the\n * audio context when pressed, shown only while the engine is not running.\n * Pulls the DOM/CSS payload only when {@link createUnmuteUI} is called.\n *\n * Adaptation: the default parent is `document.body` (Lite's audio is decoupled\n * from the renderer, so there is no `EngineStore.LastCreatedEngine` canvas to\n * anchor to). Pass {@link UnmuteUIOptions.parentElement} to override.\n */\n\nimport { type AudioEngine, unlockAudioEngineAsync } from \"./audio-engine.js\";\n\n/** Options for {@link createUnmuteUI}. */\nexport interface UnmuteUIOptions {\n /** Parent element for the button. Defaults to `document.body`. */\n parentElement?: HTMLElement;\n}\n\n/** Unmute UI handle. Pure state — driven by the unmute-UI functions. */\nexport interface UnmuteUI {\n /** @internal */ _engine: AudioEngine;\n /** @internal */ _button: HTMLButtonElement | null;\n /** @internal */ _style: HTMLStyleElement | null;\n /** @internal */ _enabled: boolean;\n /** @internal */ _unsub: (() => void) | null;\n /** @internal */ _dispose(): void;\n}\n\nfunction buildCss(top: number): string {\n return `.babylonUnmute{position:absolute;top:${top}px;margin-left:20px;height:40px;width:60px;background-color:rgba(51,51,51,0.7);background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2239%22%20height%3D%2232%22%20viewBox%3D%220%200%2039%2032%22%3E%3Cpath%20fill%3D%22white%22%20d%3D%22M9.625%2018.938l-0.031%200.016h-4.953q-0.016%200-0.031-0.016v-12.453q0-0.016%200.031-0.016h4.953q0.031%200%200.031%200.016v12.453zM12.125%207.688l8.719-8.703v27.453l-8.719-8.719-0.016-0.047v-9.938zM23.359%207.875l1.406-1.406%204.219%204.203%204.203-4.203%201.422%201.406-4.219%204.219%204.219%204.203-1.484%201.359-4.141-4.156-4.219%204.219-1.406-1.422%204.219-4.203z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\");background-size:80%;background-repeat:no-repeat;background-position:center;background-position-y:4px;border:none;outline:none;transition:transform 0.125s ease-out;cursor:pointer;z-index:9999;}.babylonUnmute:hover{transform:scale(1.05)}`;\n}\n\nfunction showButton(ui: UnmuteUI): void {\n if (!ui._button || !ui._enabled) {\n return;\n }\n ui._button.style.display = \"block\";\n}\n\nfunction hideButton(ui: UnmuteUI): void {\n if (ui._button) {\n ui._button.style.display = \"none\";\n }\n}\n\n/**\n * Creates and mounts the unmute button. It is shown while the engine's context\n * is not `\"running\"` and hidden once it resumes; clicking it unlocks the engine.\n * @param engine - The audio engine to unlock.\n * @param options - UI options (parent element).\n * @returns The UI handle; dispose it with {@link disposeUnmuteUI}.\n */\nexport function createUnmuteUI(engine: AudioEngine, options: UnmuteUIOptions = {}): UnmuteUI {\n const parent = options.parentElement ?? document.body;\n const top = (parent.offsetTop || 0) + 20;\n\n const style = document.createElement(\"style\");\n style.appendChild(document.createTextNode(buildCss(top)));\n document.head.appendChild(style);\n\n const button = document.createElement(\"button\");\n button.className = \"babylonUnmute\";\n button.id = \"babylonUnmuteButton\";\n button.addEventListener(\"click\", () => {\n void unlockAudioEngineAsync(engine);\n });\n parent.appendChild(button);\n\n const ui: UnmuteUI = {\n _engine: engine,\n _button: button,\n _style: style,\n _enabled: true,\n _unsub: null,\n _dispose: () => disposeUnmuteUI(ui),\n };\n\n ui._unsub = engine.onStateChanged.add(() => {\n if (engine.state === \"running\") {\n hideButton(ui);\n } else {\n showButton(ui);\n }\n });\n\n // Reflect the initial state.\n if (engine.state === \"running\") {\n hideButton(ui);\n } else {\n showButton(ui);\n }\n\n return ui;\n}\n\n/**\n * Enables or disables the unmute button. When disabled it is hidden; when\n * enabled it is shown if the engine is not running.\n * @param ui - The UI handle.\n * @param enabled - Whether the button may be shown.\n */\nexport function setUnmuteUIEnabled(ui: UnmuteUI, enabled: boolean): void {\n ui._enabled = enabled;\n if (enabled) {\n if (ui._engine.state !== \"running\") {\n showButton(ui);\n }\n } else {\n hideButton(ui);\n }\n}\n\n/**\n * Removes the unmute button and its styles and unsubscribes from engine state.\n * @param ui - The UI handle.\n */\nexport function disposeUnmuteUI(ui: UnmuteUI): void {\n ui._button?.remove();\n ui._button = null;\n ui._style?.remove();\n ui._style = null;\n ui._unsub?.();\n ui._unsub = null;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { enableAnalyzer, getByteFrequencyData, getByteTimeDomainData } from './analyzer.js';
|
|
2
|
+
|
|
3
|
+
const Defaults = {
|
|
4
|
+
fftSize: 2048,
|
|
5
|
+
smoothing: 0.8,
|
|
6
|
+
mode: "both",
|
|
7
|
+
backgroundColor: "#101014",
|
|
8
|
+
barColor: "#4fc3f7",
|
|
9
|
+
waveformColor: "#ffffff"
|
|
10
|
+
};
|
|
11
|
+
function createAudioVisualizer(host, canvas, options = {}) {
|
|
12
|
+
const fftSize = options.fftSize ?? Defaults.fftSize;
|
|
13
|
+
enableAnalyzer(host, { fftSize, smoothing: options.smoothing ?? Defaults.smoothing });
|
|
14
|
+
const ctx2d = canvas.getContext("2d");
|
|
15
|
+
if (!ctx2d) {
|
|
16
|
+
throw new Error("Unable to get a 2D context from the visualizer canvas.");
|
|
17
|
+
}
|
|
18
|
+
const viz = {
|
|
19
|
+
canvas,
|
|
20
|
+
_host: host,
|
|
21
|
+
_ctx2d: ctx2d,
|
|
22
|
+
_mode: options.mode ?? Defaults.mode,
|
|
23
|
+
_bgColor: options.backgroundColor ?? Defaults.backgroundColor,
|
|
24
|
+
_barColor: options.barColor ?? Defaults.barColor,
|
|
25
|
+
_waveColor: options.waveformColor ?? Defaults.waveformColor,
|
|
26
|
+
_freq: new Uint8Array(fftSize / 2),
|
|
27
|
+
_time: new Uint8Array(fftSize),
|
|
28
|
+
_raf: null,
|
|
29
|
+
_dispose: () => disposeAudioVisualizer(viz)
|
|
30
|
+
};
|
|
31
|
+
return viz;
|
|
32
|
+
}
|
|
33
|
+
function drawBars(viz, width, height) {
|
|
34
|
+
const ctx = viz._ctx2d;
|
|
35
|
+
const bins = viz._freq;
|
|
36
|
+
getByteFrequencyData(viz._host, bins);
|
|
37
|
+
const count = bins.length;
|
|
38
|
+
const barWidth = width / count;
|
|
39
|
+
ctx.fillStyle = viz._barColor;
|
|
40
|
+
for (let i = 0; i < count; i++) {
|
|
41
|
+
const magnitude = bins[i] / 255;
|
|
42
|
+
const barHeight = magnitude * height;
|
|
43
|
+
ctx.fillRect(i * barWidth, height - barHeight, Math.max(barWidth - 1, 1), barHeight);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function drawWaveform(viz, width, height) {
|
|
47
|
+
const ctx = viz._ctx2d;
|
|
48
|
+
const samples = viz._time;
|
|
49
|
+
getByteTimeDomainData(viz._host, samples);
|
|
50
|
+
const count = samples.length;
|
|
51
|
+
const step = width / count;
|
|
52
|
+
ctx.lineWidth = 2;
|
|
53
|
+
ctx.strokeStyle = viz._waveColor;
|
|
54
|
+
ctx.beginPath();
|
|
55
|
+
for (let i = 0; i < count; i++) {
|
|
56
|
+
const y = samples[i] / 255 * height;
|
|
57
|
+
const x = i * step;
|
|
58
|
+
if (i === 0) {
|
|
59
|
+
ctx.moveTo(x, y);
|
|
60
|
+
} else {
|
|
61
|
+
ctx.lineTo(x, y);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
ctx.stroke();
|
|
65
|
+
}
|
|
66
|
+
function renderAudioVisualizerFrame(viz) {
|
|
67
|
+
const { width, height } = viz.canvas;
|
|
68
|
+
const ctx = viz._ctx2d;
|
|
69
|
+
ctx.fillStyle = viz._bgColor;
|
|
70
|
+
ctx.fillRect(0, 0, width, height);
|
|
71
|
+
if (viz._mode === "bars" || viz._mode === "both") {
|
|
72
|
+
drawBars(viz, width, height);
|
|
73
|
+
}
|
|
74
|
+
if (viz._mode === "waveform" || viz._mode === "both") {
|
|
75
|
+
drawWaveform(viz, width, height);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function startAudioVisualizer(viz) {
|
|
79
|
+
if (viz._raf !== null || typeof requestAnimationFrame !== "function") {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const loop = () => {
|
|
83
|
+
renderAudioVisualizerFrame(viz);
|
|
84
|
+
viz._raf = requestAnimationFrame(loop);
|
|
85
|
+
};
|
|
86
|
+
viz._raf = requestAnimationFrame(loop);
|
|
87
|
+
}
|
|
88
|
+
function stopAudioVisualizer(viz) {
|
|
89
|
+
if (viz._raf !== null) {
|
|
90
|
+
if (typeof cancelAnimationFrame === "function") {
|
|
91
|
+
cancelAnimationFrame(viz._raf);
|
|
92
|
+
}
|
|
93
|
+
viz._raf = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function disposeAudioVisualizer(viz) {
|
|
97
|
+
stopAudioVisualizer(viz);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export { createAudioVisualizer, disposeAudioVisualizer, renderAudioVisualizerFrame, startAudioVisualizer, stopAudioVisualizer };
|
|
101
|
+
//# sourceMappingURL=visualizer.js.map
|