star-audio 0.1.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/LICENSE-3RD-PARTY.md +31 -0
- package/README.md +187 -0
- package/dist/index.d.cts +144 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +3149 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3134 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +74 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// ../../node_modules/howler/dist/howler.js
|
|
34
|
+
var require_howler = __commonJS({
|
|
35
|
+
"../../node_modules/howler/dist/howler.js"(exports2) {
|
|
36
|
+
"use strict";
|
|
37
|
+
(function() {
|
|
38
|
+
"use strict";
|
|
39
|
+
var HowlerGlobal2 = function() {
|
|
40
|
+
this.init();
|
|
41
|
+
};
|
|
42
|
+
HowlerGlobal2.prototype = {
|
|
43
|
+
/**
|
|
44
|
+
* Initialize the global Howler object.
|
|
45
|
+
* @return {Howler}
|
|
46
|
+
*/
|
|
47
|
+
init: function() {
|
|
48
|
+
var self = this || Howler3;
|
|
49
|
+
self._counter = 1e3;
|
|
50
|
+
self._html5AudioPool = [];
|
|
51
|
+
self.html5PoolSize = 10;
|
|
52
|
+
self._codecs = {};
|
|
53
|
+
self._howls = [];
|
|
54
|
+
self._muted = false;
|
|
55
|
+
self._volume = 1;
|
|
56
|
+
self._canPlayEvent = "canplaythrough";
|
|
57
|
+
self._navigator = typeof window !== "undefined" && window.navigator ? window.navigator : null;
|
|
58
|
+
self.masterGain = null;
|
|
59
|
+
self.noAudio = false;
|
|
60
|
+
self.usingWebAudio = true;
|
|
61
|
+
self.autoSuspend = true;
|
|
62
|
+
self.ctx = null;
|
|
63
|
+
self.autoUnlock = true;
|
|
64
|
+
self._setup();
|
|
65
|
+
return self;
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* Get/set the global volume for all sounds.
|
|
69
|
+
* @param {Float} vol Volume from 0.0 to 1.0.
|
|
70
|
+
* @return {Howler/Float} Returns self or current volume.
|
|
71
|
+
*/
|
|
72
|
+
volume: function(vol) {
|
|
73
|
+
var self = this || Howler3;
|
|
74
|
+
vol = parseFloat(vol);
|
|
75
|
+
if (!self.ctx) {
|
|
76
|
+
setupAudioContext();
|
|
77
|
+
}
|
|
78
|
+
if (typeof vol !== "undefined" && vol >= 0 && vol <= 1) {
|
|
79
|
+
self._volume = vol;
|
|
80
|
+
if (self._muted) {
|
|
81
|
+
return self;
|
|
82
|
+
}
|
|
83
|
+
if (self.usingWebAudio) {
|
|
84
|
+
self.masterGain.gain.setValueAtTime(vol, Howler3.ctx.currentTime);
|
|
85
|
+
}
|
|
86
|
+
for (var i = 0; i < self._howls.length; i++) {
|
|
87
|
+
if (!self._howls[i]._webAudio) {
|
|
88
|
+
var ids = self._howls[i]._getSoundIds();
|
|
89
|
+
for (var j = 0; j < ids.length; j++) {
|
|
90
|
+
var sound = self._howls[i]._soundById(ids[j]);
|
|
91
|
+
if (sound && sound._node) {
|
|
92
|
+
sound._node.volume = sound._volume * vol;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return self;
|
|
98
|
+
}
|
|
99
|
+
return self._volume;
|
|
100
|
+
},
|
|
101
|
+
/**
|
|
102
|
+
* Handle muting and unmuting globally.
|
|
103
|
+
* @param {Boolean} muted Is muted or not.
|
|
104
|
+
*/
|
|
105
|
+
mute: function(muted) {
|
|
106
|
+
var self = this || Howler3;
|
|
107
|
+
if (!self.ctx) {
|
|
108
|
+
setupAudioContext();
|
|
109
|
+
}
|
|
110
|
+
self._muted = muted;
|
|
111
|
+
if (self.usingWebAudio) {
|
|
112
|
+
self.masterGain.gain.setValueAtTime(muted ? 0 : self._volume, Howler3.ctx.currentTime);
|
|
113
|
+
}
|
|
114
|
+
for (var i = 0; i < self._howls.length; i++) {
|
|
115
|
+
if (!self._howls[i]._webAudio) {
|
|
116
|
+
var ids = self._howls[i]._getSoundIds();
|
|
117
|
+
for (var j = 0; j < ids.length; j++) {
|
|
118
|
+
var sound = self._howls[i]._soundById(ids[j]);
|
|
119
|
+
if (sound && sound._node) {
|
|
120
|
+
sound._node.muted = muted ? true : sound._muted;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return self;
|
|
126
|
+
},
|
|
127
|
+
/**
|
|
128
|
+
* Handle stopping all sounds globally.
|
|
129
|
+
*/
|
|
130
|
+
stop: function() {
|
|
131
|
+
var self = this || Howler3;
|
|
132
|
+
for (var i = 0; i < self._howls.length; i++) {
|
|
133
|
+
self._howls[i].stop();
|
|
134
|
+
}
|
|
135
|
+
return self;
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* Unload and destroy all currently loaded Howl objects.
|
|
139
|
+
* @return {Howler}
|
|
140
|
+
*/
|
|
141
|
+
unload: function() {
|
|
142
|
+
var self = this || Howler3;
|
|
143
|
+
for (var i = self._howls.length - 1; i >= 0; i--) {
|
|
144
|
+
self._howls[i].unload();
|
|
145
|
+
}
|
|
146
|
+
if (self.usingWebAudio && self.ctx && typeof self.ctx.close !== "undefined") {
|
|
147
|
+
self.ctx.close();
|
|
148
|
+
self.ctx = null;
|
|
149
|
+
setupAudioContext();
|
|
150
|
+
}
|
|
151
|
+
return self;
|
|
152
|
+
},
|
|
153
|
+
/**
|
|
154
|
+
* Check for codec support of specific extension.
|
|
155
|
+
* @param {String} ext Audio file extention.
|
|
156
|
+
* @return {Boolean}
|
|
157
|
+
*/
|
|
158
|
+
codecs: function(ext) {
|
|
159
|
+
return (this || Howler3)._codecs[ext.replace(/^x-/, "")];
|
|
160
|
+
},
|
|
161
|
+
/**
|
|
162
|
+
* Setup various state values for global tracking.
|
|
163
|
+
* @return {Howler}
|
|
164
|
+
*/
|
|
165
|
+
_setup: function() {
|
|
166
|
+
var self = this || Howler3;
|
|
167
|
+
self.state = self.ctx ? self.ctx.state || "suspended" : "suspended";
|
|
168
|
+
self._autoSuspend();
|
|
169
|
+
if (!self.usingWebAudio) {
|
|
170
|
+
if (typeof Audio !== "undefined") {
|
|
171
|
+
try {
|
|
172
|
+
var test = new Audio();
|
|
173
|
+
if (typeof test.oncanplaythrough === "undefined") {
|
|
174
|
+
self._canPlayEvent = "canplay";
|
|
175
|
+
}
|
|
176
|
+
} catch (e) {
|
|
177
|
+
self.noAudio = true;
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
self.noAudio = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
var test = new Audio();
|
|
185
|
+
if (test.muted) {
|
|
186
|
+
self.noAudio = true;
|
|
187
|
+
}
|
|
188
|
+
} catch (e) {
|
|
189
|
+
}
|
|
190
|
+
if (!self.noAudio) {
|
|
191
|
+
self._setupCodecs();
|
|
192
|
+
}
|
|
193
|
+
return self;
|
|
194
|
+
},
|
|
195
|
+
/**
|
|
196
|
+
* Check for browser support for various codecs and cache the results.
|
|
197
|
+
* @return {Howler}
|
|
198
|
+
*/
|
|
199
|
+
_setupCodecs: function() {
|
|
200
|
+
var self = this || Howler3;
|
|
201
|
+
var audioTest = null;
|
|
202
|
+
try {
|
|
203
|
+
audioTest = typeof Audio !== "undefined" ? new Audio() : null;
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return self;
|
|
206
|
+
}
|
|
207
|
+
if (!audioTest || typeof audioTest.canPlayType !== "function") {
|
|
208
|
+
return self;
|
|
209
|
+
}
|
|
210
|
+
var mpegTest = audioTest.canPlayType("audio/mpeg;").replace(/^no$/, "");
|
|
211
|
+
var ua = self._navigator ? self._navigator.userAgent : "";
|
|
212
|
+
var checkOpera = ua.match(/OPR\/(\d+)/g);
|
|
213
|
+
var isOldOpera = checkOpera && parseInt(checkOpera[0].split("/")[1], 10) < 33;
|
|
214
|
+
var checkSafari = ua.indexOf("Safari") !== -1 && ua.indexOf("Chrome") === -1;
|
|
215
|
+
var safariVersion = ua.match(/Version\/(.*?) /);
|
|
216
|
+
var isOldSafari = checkSafari && safariVersion && parseInt(safariVersion[1], 10) < 15;
|
|
217
|
+
self._codecs = {
|
|
218
|
+
mp3: !!(!isOldOpera && (mpegTest || audioTest.canPlayType("audio/mp3;").replace(/^no$/, ""))),
|
|
219
|
+
mpeg: !!mpegTest,
|
|
220
|
+
opus: !!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ""),
|
|
221
|
+
ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ""),
|
|
222
|
+
oga: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ""),
|
|
223
|
+
wav: !!(audioTest.canPlayType('audio/wav; codecs="1"') || audioTest.canPlayType("audio/wav")).replace(/^no$/, ""),
|
|
224
|
+
aac: !!audioTest.canPlayType("audio/aac;").replace(/^no$/, ""),
|
|
225
|
+
caf: !!audioTest.canPlayType("audio/x-caf;").replace(/^no$/, ""),
|
|
226
|
+
m4a: !!(audioTest.canPlayType("audio/x-m4a;") || audioTest.canPlayType("audio/m4a;") || audioTest.canPlayType("audio/aac;")).replace(/^no$/, ""),
|
|
227
|
+
m4b: !!(audioTest.canPlayType("audio/x-m4b;") || audioTest.canPlayType("audio/m4b;") || audioTest.canPlayType("audio/aac;")).replace(/^no$/, ""),
|
|
228
|
+
mp4: !!(audioTest.canPlayType("audio/x-mp4;") || audioTest.canPlayType("audio/mp4;") || audioTest.canPlayType("audio/aac;")).replace(/^no$/, ""),
|
|
229
|
+
weba: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, "")),
|
|
230
|
+
webm: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, "")),
|
|
231
|
+
dolby: !!audioTest.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/, ""),
|
|
232
|
+
flac: !!(audioTest.canPlayType("audio/x-flac;") || audioTest.canPlayType("audio/flac;")).replace(/^no$/, "")
|
|
233
|
+
};
|
|
234
|
+
return self;
|
|
235
|
+
},
|
|
236
|
+
/**
|
|
237
|
+
* Some browsers/devices will only allow audio to be played after a user interaction.
|
|
238
|
+
* Attempt to automatically unlock audio on the first user interaction.
|
|
239
|
+
* Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/
|
|
240
|
+
* @return {Howler}
|
|
241
|
+
*/
|
|
242
|
+
_unlockAudio: function() {
|
|
243
|
+
var self = this || Howler3;
|
|
244
|
+
if (self._audioUnlocked || !self.ctx) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
self._audioUnlocked = false;
|
|
248
|
+
self.autoUnlock = false;
|
|
249
|
+
if (!self._mobileUnloaded && self.ctx.sampleRate !== 44100) {
|
|
250
|
+
self._mobileUnloaded = true;
|
|
251
|
+
self.unload();
|
|
252
|
+
}
|
|
253
|
+
self._scratchBuffer = self.ctx.createBuffer(1, 1, 22050);
|
|
254
|
+
var unlock = function(e) {
|
|
255
|
+
while (self._html5AudioPool.length < self.html5PoolSize) {
|
|
256
|
+
try {
|
|
257
|
+
var audioNode = new Audio();
|
|
258
|
+
audioNode._unlocked = true;
|
|
259
|
+
self._releaseHtml5Audio(audioNode);
|
|
260
|
+
} catch (e2) {
|
|
261
|
+
self.noAudio = true;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
for (var i = 0; i < self._howls.length; i++) {
|
|
266
|
+
if (!self._howls[i]._webAudio) {
|
|
267
|
+
var ids = self._howls[i]._getSoundIds();
|
|
268
|
+
for (var j = 0; j < ids.length; j++) {
|
|
269
|
+
var sound = self._howls[i]._soundById(ids[j]);
|
|
270
|
+
if (sound && sound._node && !sound._node._unlocked) {
|
|
271
|
+
sound._node._unlocked = true;
|
|
272
|
+
sound._node.load();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
self._autoResume();
|
|
278
|
+
var source = self.ctx.createBufferSource();
|
|
279
|
+
source.buffer = self._scratchBuffer;
|
|
280
|
+
source.connect(self.ctx.destination);
|
|
281
|
+
if (typeof source.start === "undefined") {
|
|
282
|
+
source.noteOn(0);
|
|
283
|
+
} else {
|
|
284
|
+
source.start(0);
|
|
285
|
+
}
|
|
286
|
+
if (typeof self.ctx.resume === "function") {
|
|
287
|
+
self.ctx.resume();
|
|
288
|
+
}
|
|
289
|
+
source.onended = function() {
|
|
290
|
+
source.disconnect(0);
|
|
291
|
+
self._audioUnlocked = true;
|
|
292
|
+
document.removeEventListener("touchstart", unlock, true);
|
|
293
|
+
document.removeEventListener("touchend", unlock, true);
|
|
294
|
+
document.removeEventListener("click", unlock, true);
|
|
295
|
+
document.removeEventListener("keydown", unlock, true);
|
|
296
|
+
for (var i2 = 0; i2 < self._howls.length; i2++) {
|
|
297
|
+
self._howls[i2]._emit("unlock");
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
};
|
|
301
|
+
document.addEventListener("touchstart", unlock, true);
|
|
302
|
+
document.addEventListener("touchend", unlock, true);
|
|
303
|
+
document.addEventListener("click", unlock, true);
|
|
304
|
+
document.addEventListener("keydown", unlock, true);
|
|
305
|
+
return self;
|
|
306
|
+
},
|
|
307
|
+
/**
|
|
308
|
+
* Get an unlocked HTML5 Audio object from the pool. If none are left,
|
|
309
|
+
* return a new Audio object and throw a warning.
|
|
310
|
+
* @return {Audio} HTML5 Audio object.
|
|
311
|
+
*/
|
|
312
|
+
_obtainHtml5Audio: function() {
|
|
313
|
+
var self = this || Howler3;
|
|
314
|
+
if (self._html5AudioPool.length) {
|
|
315
|
+
return self._html5AudioPool.pop();
|
|
316
|
+
}
|
|
317
|
+
var testPlay = new Audio().play();
|
|
318
|
+
if (testPlay && typeof Promise !== "undefined" && (testPlay instanceof Promise || typeof testPlay.then === "function")) {
|
|
319
|
+
testPlay.catch(function() {
|
|
320
|
+
console.warn("HTML5 Audio pool exhausted, returning potentially locked audio object.");
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return new Audio();
|
|
324
|
+
},
|
|
325
|
+
/**
|
|
326
|
+
* Return an activated HTML5 Audio object to the pool.
|
|
327
|
+
* @return {Howler}
|
|
328
|
+
*/
|
|
329
|
+
_releaseHtml5Audio: function(audio) {
|
|
330
|
+
var self = this || Howler3;
|
|
331
|
+
if (audio._unlocked) {
|
|
332
|
+
self._html5AudioPool.push(audio);
|
|
333
|
+
}
|
|
334
|
+
return self;
|
|
335
|
+
},
|
|
336
|
+
/**
|
|
337
|
+
* Automatically suspend the Web Audio AudioContext after no sound has played for 30 seconds.
|
|
338
|
+
* This saves processing/energy and fixes various browser-specific bugs with audio getting stuck.
|
|
339
|
+
* @return {Howler}
|
|
340
|
+
*/
|
|
341
|
+
_autoSuspend: function() {
|
|
342
|
+
var self = this;
|
|
343
|
+
if (!self.autoSuspend || !self.ctx || typeof self.ctx.suspend === "undefined" || !Howler3.usingWebAudio) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
for (var i = 0; i < self._howls.length; i++) {
|
|
347
|
+
if (self._howls[i]._webAudio) {
|
|
348
|
+
for (var j = 0; j < self._howls[i]._sounds.length; j++) {
|
|
349
|
+
if (!self._howls[i]._sounds[j]._paused) {
|
|
350
|
+
return self;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (self._suspendTimer) {
|
|
356
|
+
clearTimeout(self._suspendTimer);
|
|
357
|
+
}
|
|
358
|
+
self._suspendTimer = setTimeout(function() {
|
|
359
|
+
if (!self.autoSuspend) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
self._suspendTimer = null;
|
|
363
|
+
self.state = "suspending";
|
|
364
|
+
var handleSuspension = function() {
|
|
365
|
+
self.state = "suspended";
|
|
366
|
+
if (self._resumeAfterSuspend) {
|
|
367
|
+
delete self._resumeAfterSuspend;
|
|
368
|
+
self._autoResume();
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
self.ctx.suspend().then(handleSuspension, handleSuspension);
|
|
372
|
+
}, 3e4);
|
|
373
|
+
return self;
|
|
374
|
+
},
|
|
375
|
+
/**
|
|
376
|
+
* Automatically resume the Web Audio AudioContext when a new sound is played.
|
|
377
|
+
* @return {Howler}
|
|
378
|
+
*/
|
|
379
|
+
_autoResume: function() {
|
|
380
|
+
var self = this;
|
|
381
|
+
if (!self.ctx || typeof self.ctx.resume === "undefined" || !Howler3.usingWebAudio) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (self.state === "running" && self.ctx.state !== "interrupted" && self._suspendTimer) {
|
|
385
|
+
clearTimeout(self._suspendTimer);
|
|
386
|
+
self._suspendTimer = null;
|
|
387
|
+
} else if (self.state === "suspended" || self.state === "running" && self.ctx.state === "interrupted") {
|
|
388
|
+
self.ctx.resume().then(function() {
|
|
389
|
+
self.state = "running";
|
|
390
|
+
for (var i = 0; i < self._howls.length; i++) {
|
|
391
|
+
self._howls[i]._emit("resume");
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
if (self._suspendTimer) {
|
|
395
|
+
clearTimeout(self._suspendTimer);
|
|
396
|
+
self._suspendTimer = null;
|
|
397
|
+
}
|
|
398
|
+
} else if (self.state === "suspending") {
|
|
399
|
+
self._resumeAfterSuspend = true;
|
|
400
|
+
}
|
|
401
|
+
return self;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
var Howler3 = new HowlerGlobal2();
|
|
405
|
+
var Howl3 = function(o) {
|
|
406
|
+
var self = this;
|
|
407
|
+
if (!o.src || o.src.length === 0) {
|
|
408
|
+
console.error("An array of source files must be passed with any new Howl.");
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
self.init(o);
|
|
412
|
+
};
|
|
413
|
+
Howl3.prototype = {
|
|
414
|
+
/**
|
|
415
|
+
* Initialize a new Howl group object.
|
|
416
|
+
* @param {Object} o Passed in properties for this group.
|
|
417
|
+
* @return {Howl}
|
|
418
|
+
*/
|
|
419
|
+
init: function(o) {
|
|
420
|
+
var self = this;
|
|
421
|
+
if (!Howler3.ctx) {
|
|
422
|
+
setupAudioContext();
|
|
423
|
+
}
|
|
424
|
+
self._autoplay = o.autoplay || false;
|
|
425
|
+
self._format = typeof o.format !== "string" ? o.format : [o.format];
|
|
426
|
+
self._html5 = o.html5 || false;
|
|
427
|
+
self._muted = o.mute || false;
|
|
428
|
+
self._loop = o.loop || false;
|
|
429
|
+
self._pool = o.pool || 5;
|
|
430
|
+
self._preload = typeof o.preload === "boolean" || o.preload === "metadata" ? o.preload : true;
|
|
431
|
+
self._rate = o.rate || 1;
|
|
432
|
+
self._sprite = o.sprite || {};
|
|
433
|
+
self._src = typeof o.src !== "string" ? o.src : [o.src];
|
|
434
|
+
self._volume = o.volume !== void 0 ? o.volume : 1;
|
|
435
|
+
self._xhr = {
|
|
436
|
+
method: o.xhr && o.xhr.method ? o.xhr.method : "GET",
|
|
437
|
+
headers: o.xhr && o.xhr.headers ? o.xhr.headers : null,
|
|
438
|
+
withCredentials: o.xhr && o.xhr.withCredentials ? o.xhr.withCredentials : false
|
|
439
|
+
};
|
|
440
|
+
self._duration = 0;
|
|
441
|
+
self._state = "unloaded";
|
|
442
|
+
self._sounds = [];
|
|
443
|
+
self._endTimers = {};
|
|
444
|
+
self._queue = [];
|
|
445
|
+
self._playLock = false;
|
|
446
|
+
self._onend = o.onend ? [{ fn: o.onend }] : [];
|
|
447
|
+
self._onfade = o.onfade ? [{ fn: o.onfade }] : [];
|
|
448
|
+
self._onload = o.onload ? [{ fn: o.onload }] : [];
|
|
449
|
+
self._onloaderror = o.onloaderror ? [{ fn: o.onloaderror }] : [];
|
|
450
|
+
self._onplayerror = o.onplayerror ? [{ fn: o.onplayerror }] : [];
|
|
451
|
+
self._onpause = o.onpause ? [{ fn: o.onpause }] : [];
|
|
452
|
+
self._onplay = o.onplay ? [{ fn: o.onplay }] : [];
|
|
453
|
+
self._onstop = o.onstop ? [{ fn: o.onstop }] : [];
|
|
454
|
+
self._onmute = o.onmute ? [{ fn: o.onmute }] : [];
|
|
455
|
+
self._onvolume = o.onvolume ? [{ fn: o.onvolume }] : [];
|
|
456
|
+
self._onrate = o.onrate ? [{ fn: o.onrate }] : [];
|
|
457
|
+
self._onseek = o.onseek ? [{ fn: o.onseek }] : [];
|
|
458
|
+
self._onunlock = o.onunlock ? [{ fn: o.onunlock }] : [];
|
|
459
|
+
self._onresume = [];
|
|
460
|
+
self._webAudio = Howler3.usingWebAudio && !self._html5;
|
|
461
|
+
if (typeof Howler3.ctx !== "undefined" && Howler3.ctx && Howler3.autoUnlock) {
|
|
462
|
+
Howler3._unlockAudio();
|
|
463
|
+
}
|
|
464
|
+
Howler3._howls.push(self);
|
|
465
|
+
if (self._autoplay) {
|
|
466
|
+
self._queue.push({
|
|
467
|
+
event: "play",
|
|
468
|
+
action: function() {
|
|
469
|
+
self.play();
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
if (self._preload && self._preload !== "none") {
|
|
474
|
+
self.load();
|
|
475
|
+
}
|
|
476
|
+
return self;
|
|
477
|
+
},
|
|
478
|
+
/**
|
|
479
|
+
* Load the audio file.
|
|
480
|
+
* @return {Howler}
|
|
481
|
+
*/
|
|
482
|
+
load: function() {
|
|
483
|
+
var self = this;
|
|
484
|
+
var url = null;
|
|
485
|
+
if (Howler3.noAudio) {
|
|
486
|
+
self._emit("loaderror", null, "No audio support.");
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (typeof self._src === "string") {
|
|
490
|
+
self._src = [self._src];
|
|
491
|
+
}
|
|
492
|
+
for (var i = 0; i < self._src.length; i++) {
|
|
493
|
+
var ext, str;
|
|
494
|
+
if (self._format && self._format[i]) {
|
|
495
|
+
ext = self._format[i];
|
|
496
|
+
} else {
|
|
497
|
+
str = self._src[i];
|
|
498
|
+
if (typeof str !== "string") {
|
|
499
|
+
self._emit("loaderror", null, "Non-string found in selected audio sources - ignoring.");
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
ext = /^data:audio\/([^;,]+);/i.exec(str);
|
|
503
|
+
if (!ext) {
|
|
504
|
+
ext = /\.([^.]+)$/.exec(str.split("?", 1)[0]);
|
|
505
|
+
}
|
|
506
|
+
if (ext) {
|
|
507
|
+
ext = ext[1].toLowerCase();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (!ext) {
|
|
511
|
+
console.warn('No file extension was found. Consider using the "format" property or specify an extension.');
|
|
512
|
+
}
|
|
513
|
+
if (ext && Howler3.codecs(ext)) {
|
|
514
|
+
url = self._src[i];
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (!url) {
|
|
519
|
+
self._emit("loaderror", null, "No codec support for selected audio sources.");
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
self._src = url;
|
|
523
|
+
self._state = "loading";
|
|
524
|
+
if (window.location.protocol === "https:" && url.slice(0, 5) === "http:") {
|
|
525
|
+
self._html5 = true;
|
|
526
|
+
self._webAudio = false;
|
|
527
|
+
}
|
|
528
|
+
new Sound2(self);
|
|
529
|
+
if (self._webAudio) {
|
|
530
|
+
loadBuffer(self);
|
|
531
|
+
}
|
|
532
|
+
return self;
|
|
533
|
+
},
|
|
534
|
+
/**
|
|
535
|
+
* Play a sound or resume previous playback.
|
|
536
|
+
* @param {String/Number} sprite Sprite name for sprite playback or sound id to continue previous.
|
|
537
|
+
* @param {Boolean} internal Internal Use: true prevents event firing.
|
|
538
|
+
* @return {Number} Sound ID.
|
|
539
|
+
*/
|
|
540
|
+
play: function(sprite, internal) {
|
|
541
|
+
var self = this;
|
|
542
|
+
var id = null;
|
|
543
|
+
if (typeof sprite === "number") {
|
|
544
|
+
id = sprite;
|
|
545
|
+
sprite = null;
|
|
546
|
+
} else if (typeof sprite === "string" && self._state === "loaded" && !self._sprite[sprite]) {
|
|
547
|
+
return null;
|
|
548
|
+
} else if (typeof sprite === "undefined") {
|
|
549
|
+
sprite = "__default";
|
|
550
|
+
if (!self._playLock) {
|
|
551
|
+
var num = 0;
|
|
552
|
+
for (var i = 0; i < self._sounds.length; i++) {
|
|
553
|
+
if (self._sounds[i]._paused && !self._sounds[i]._ended) {
|
|
554
|
+
num++;
|
|
555
|
+
id = self._sounds[i]._id;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (num === 1) {
|
|
559
|
+
sprite = null;
|
|
560
|
+
} else {
|
|
561
|
+
id = null;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
var sound = id ? self._soundById(id) : self._inactiveSound();
|
|
566
|
+
if (!sound) {
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
if (id && !sprite) {
|
|
570
|
+
sprite = sound._sprite || "__default";
|
|
571
|
+
}
|
|
572
|
+
if (self._state !== "loaded") {
|
|
573
|
+
sound._sprite = sprite;
|
|
574
|
+
sound._ended = false;
|
|
575
|
+
var soundId = sound._id;
|
|
576
|
+
self._queue.push({
|
|
577
|
+
event: "play",
|
|
578
|
+
action: function() {
|
|
579
|
+
self.play(soundId);
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
return soundId;
|
|
583
|
+
}
|
|
584
|
+
if (id && !sound._paused) {
|
|
585
|
+
if (!internal) {
|
|
586
|
+
self._loadQueue("play");
|
|
587
|
+
}
|
|
588
|
+
return sound._id;
|
|
589
|
+
}
|
|
590
|
+
if (self._webAudio) {
|
|
591
|
+
Howler3._autoResume();
|
|
592
|
+
}
|
|
593
|
+
var seek = Math.max(0, sound._seek > 0 ? sound._seek : self._sprite[sprite][0] / 1e3);
|
|
594
|
+
var duration = Math.max(0, (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1e3 - seek);
|
|
595
|
+
var timeout = duration * 1e3 / Math.abs(sound._rate);
|
|
596
|
+
var start = self._sprite[sprite][0] / 1e3;
|
|
597
|
+
var stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1e3;
|
|
598
|
+
sound._sprite = sprite;
|
|
599
|
+
sound._ended = false;
|
|
600
|
+
var setParams = function() {
|
|
601
|
+
sound._paused = false;
|
|
602
|
+
sound._seek = seek;
|
|
603
|
+
sound._start = start;
|
|
604
|
+
sound._stop = stop;
|
|
605
|
+
sound._loop = !!(sound._loop || self._sprite[sprite][2]);
|
|
606
|
+
};
|
|
607
|
+
if (seek >= stop) {
|
|
608
|
+
self._ended(sound);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
var node = sound._node;
|
|
612
|
+
if (self._webAudio) {
|
|
613
|
+
var playWebAudio = function() {
|
|
614
|
+
self._playLock = false;
|
|
615
|
+
setParams();
|
|
616
|
+
self._refreshBuffer(sound);
|
|
617
|
+
var vol = sound._muted || self._muted ? 0 : sound._volume;
|
|
618
|
+
node.gain.setValueAtTime(vol, Howler3.ctx.currentTime);
|
|
619
|
+
sound._playStart = Howler3.ctx.currentTime;
|
|
620
|
+
if (typeof node.bufferSource.start === "undefined") {
|
|
621
|
+
sound._loop ? node.bufferSource.noteGrainOn(0, seek, 86400) : node.bufferSource.noteGrainOn(0, seek, duration);
|
|
622
|
+
} else {
|
|
623
|
+
sound._loop ? node.bufferSource.start(0, seek, 86400) : node.bufferSource.start(0, seek, duration);
|
|
624
|
+
}
|
|
625
|
+
if (timeout !== Infinity) {
|
|
626
|
+
self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);
|
|
627
|
+
}
|
|
628
|
+
if (!internal) {
|
|
629
|
+
setTimeout(function() {
|
|
630
|
+
self._emit("play", sound._id);
|
|
631
|
+
self._loadQueue();
|
|
632
|
+
}, 0);
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
if (Howler3.state === "running" && Howler3.ctx.state !== "interrupted") {
|
|
636
|
+
playWebAudio();
|
|
637
|
+
} else {
|
|
638
|
+
self._playLock = true;
|
|
639
|
+
self.once("resume", playWebAudio);
|
|
640
|
+
self._clearTimer(sound._id);
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
var playHtml5 = function() {
|
|
644
|
+
node.currentTime = seek;
|
|
645
|
+
node.muted = sound._muted || self._muted || Howler3._muted || node.muted;
|
|
646
|
+
node.volume = sound._volume * Howler3.volume();
|
|
647
|
+
node.playbackRate = sound._rate;
|
|
648
|
+
try {
|
|
649
|
+
var play = node.play();
|
|
650
|
+
if (play && typeof Promise !== "undefined" && (play instanceof Promise || typeof play.then === "function")) {
|
|
651
|
+
self._playLock = true;
|
|
652
|
+
setParams();
|
|
653
|
+
play.then(function() {
|
|
654
|
+
self._playLock = false;
|
|
655
|
+
node._unlocked = true;
|
|
656
|
+
if (!internal) {
|
|
657
|
+
self._emit("play", sound._id);
|
|
658
|
+
} else {
|
|
659
|
+
self._loadQueue();
|
|
660
|
+
}
|
|
661
|
+
}).catch(function() {
|
|
662
|
+
self._playLock = false;
|
|
663
|
+
self._emit("playerror", sound._id, "Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");
|
|
664
|
+
sound._ended = true;
|
|
665
|
+
sound._paused = true;
|
|
666
|
+
});
|
|
667
|
+
} else if (!internal) {
|
|
668
|
+
self._playLock = false;
|
|
669
|
+
setParams();
|
|
670
|
+
self._emit("play", sound._id);
|
|
671
|
+
}
|
|
672
|
+
node.playbackRate = sound._rate;
|
|
673
|
+
if (node.paused) {
|
|
674
|
+
self._emit("playerror", sound._id, "Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
if (sprite !== "__default" || sound._loop) {
|
|
678
|
+
self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);
|
|
679
|
+
} else {
|
|
680
|
+
self._endTimers[sound._id] = function() {
|
|
681
|
+
self._ended(sound);
|
|
682
|
+
node.removeEventListener("ended", self._endTimers[sound._id], false);
|
|
683
|
+
};
|
|
684
|
+
node.addEventListener("ended", self._endTimers[sound._id], false);
|
|
685
|
+
}
|
|
686
|
+
} catch (err) {
|
|
687
|
+
self._emit("playerror", sound._id, err);
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
if (node.src === "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA") {
|
|
691
|
+
node.src = self._src;
|
|
692
|
+
node.load();
|
|
693
|
+
}
|
|
694
|
+
var loadedNoReadyState = window && window.ejecta || !node.readyState && Howler3._navigator.isCocoonJS;
|
|
695
|
+
if (node.readyState >= 3 || loadedNoReadyState) {
|
|
696
|
+
playHtml5();
|
|
697
|
+
} else {
|
|
698
|
+
self._playLock = true;
|
|
699
|
+
self._state = "loading";
|
|
700
|
+
var listener = function() {
|
|
701
|
+
self._state = "loaded";
|
|
702
|
+
playHtml5();
|
|
703
|
+
node.removeEventListener(Howler3._canPlayEvent, listener, false);
|
|
704
|
+
};
|
|
705
|
+
node.addEventListener(Howler3._canPlayEvent, listener, false);
|
|
706
|
+
self._clearTimer(sound._id);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return sound._id;
|
|
710
|
+
},
|
|
711
|
+
/**
|
|
712
|
+
* Pause playback and save current position.
|
|
713
|
+
* @param {Number} id The sound ID (empty to pause all in group).
|
|
714
|
+
* @return {Howl}
|
|
715
|
+
*/
|
|
716
|
+
pause: function(id) {
|
|
717
|
+
var self = this;
|
|
718
|
+
if (self._state !== "loaded" || self._playLock) {
|
|
719
|
+
self._queue.push({
|
|
720
|
+
event: "pause",
|
|
721
|
+
action: function() {
|
|
722
|
+
self.pause(id);
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
return self;
|
|
726
|
+
}
|
|
727
|
+
var ids = self._getSoundIds(id);
|
|
728
|
+
for (var i = 0; i < ids.length; i++) {
|
|
729
|
+
self._clearTimer(ids[i]);
|
|
730
|
+
var sound = self._soundById(ids[i]);
|
|
731
|
+
if (sound && !sound._paused) {
|
|
732
|
+
sound._seek = self.seek(ids[i]);
|
|
733
|
+
sound._rateSeek = 0;
|
|
734
|
+
sound._paused = true;
|
|
735
|
+
self._stopFade(ids[i]);
|
|
736
|
+
if (sound._node) {
|
|
737
|
+
if (self._webAudio) {
|
|
738
|
+
if (!sound._node.bufferSource) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
if (typeof sound._node.bufferSource.stop === "undefined") {
|
|
742
|
+
sound._node.bufferSource.noteOff(0);
|
|
743
|
+
} else {
|
|
744
|
+
sound._node.bufferSource.stop(0);
|
|
745
|
+
}
|
|
746
|
+
self._cleanBuffer(sound._node);
|
|
747
|
+
} else if (!isNaN(sound._node.duration) || sound._node.duration === Infinity) {
|
|
748
|
+
sound._node.pause();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (!arguments[1]) {
|
|
753
|
+
self._emit("pause", sound ? sound._id : null);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return self;
|
|
757
|
+
},
|
|
758
|
+
/**
|
|
759
|
+
* Stop playback and reset to start.
|
|
760
|
+
* @param {Number} id The sound ID (empty to stop all in group).
|
|
761
|
+
* @param {Boolean} internal Internal Use: true prevents event firing.
|
|
762
|
+
* @return {Howl}
|
|
763
|
+
*/
|
|
764
|
+
stop: function(id, internal) {
|
|
765
|
+
var self = this;
|
|
766
|
+
if (self._state !== "loaded" || self._playLock) {
|
|
767
|
+
self._queue.push({
|
|
768
|
+
event: "stop",
|
|
769
|
+
action: function() {
|
|
770
|
+
self.stop(id);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
return self;
|
|
774
|
+
}
|
|
775
|
+
var ids = self._getSoundIds(id);
|
|
776
|
+
for (var i = 0; i < ids.length; i++) {
|
|
777
|
+
self._clearTimer(ids[i]);
|
|
778
|
+
var sound = self._soundById(ids[i]);
|
|
779
|
+
if (sound) {
|
|
780
|
+
sound._seek = sound._start || 0;
|
|
781
|
+
sound._rateSeek = 0;
|
|
782
|
+
sound._paused = true;
|
|
783
|
+
sound._ended = true;
|
|
784
|
+
self._stopFade(ids[i]);
|
|
785
|
+
if (sound._node) {
|
|
786
|
+
if (self._webAudio) {
|
|
787
|
+
if (sound._node.bufferSource) {
|
|
788
|
+
if (typeof sound._node.bufferSource.stop === "undefined") {
|
|
789
|
+
sound._node.bufferSource.noteOff(0);
|
|
790
|
+
} else {
|
|
791
|
+
sound._node.bufferSource.stop(0);
|
|
792
|
+
}
|
|
793
|
+
self._cleanBuffer(sound._node);
|
|
794
|
+
}
|
|
795
|
+
} else if (!isNaN(sound._node.duration) || sound._node.duration === Infinity) {
|
|
796
|
+
sound._node.currentTime = sound._start || 0;
|
|
797
|
+
sound._node.pause();
|
|
798
|
+
if (sound._node.duration === Infinity) {
|
|
799
|
+
self._clearSound(sound._node);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (!internal) {
|
|
804
|
+
self._emit("stop", sound._id);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return self;
|
|
809
|
+
},
|
|
810
|
+
/**
|
|
811
|
+
* Mute/unmute a single sound or all sounds in this Howl group.
|
|
812
|
+
* @param {Boolean} muted Set to true to mute and false to unmute.
|
|
813
|
+
* @param {Number} id The sound ID to update (omit to mute/unmute all).
|
|
814
|
+
* @return {Howl}
|
|
815
|
+
*/
|
|
816
|
+
mute: function(muted, id) {
|
|
817
|
+
var self = this;
|
|
818
|
+
if (self._state !== "loaded" || self._playLock) {
|
|
819
|
+
self._queue.push({
|
|
820
|
+
event: "mute",
|
|
821
|
+
action: function() {
|
|
822
|
+
self.mute(muted, id);
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
return self;
|
|
826
|
+
}
|
|
827
|
+
if (typeof id === "undefined") {
|
|
828
|
+
if (typeof muted === "boolean") {
|
|
829
|
+
self._muted = muted;
|
|
830
|
+
} else {
|
|
831
|
+
return self._muted;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
var ids = self._getSoundIds(id);
|
|
835
|
+
for (var i = 0; i < ids.length; i++) {
|
|
836
|
+
var sound = self._soundById(ids[i]);
|
|
837
|
+
if (sound) {
|
|
838
|
+
sound._muted = muted;
|
|
839
|
+
if (sound._interval) {
|
|
840
|
+
self._stopFade(sound._id);
|
|
841
|
+
}
|
|
842
|
+
if (self._webAudio && sound._node) {
|
|
843
|
+
sound._node.gain.setValueAtTime(muted ? 0 : sound._volume, Howler3.ctx.currentTime);
|
|
844
|
+
} else if (sound._node) {
|
|
845
|
+
sound._node.muted = Howler3._muted ? true : muted;
|
|
846
|
+
}
|
|
847
|
+
self._emit("mute", sound._id);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return self;
|
|
851
|
+
},
|
|
852
|
+
/**
|
|
853
|
+
* Get/set the volume of this sound or of the Howl group. This method can optionally take 0, 1 or 2 arguments.
|
|
854
|
+
* volume() -> Returns the group's volume value.
|
|
855
|
+
* volume(id) -> Returns the sound id's current volume.
|
|
856
|
+
* volume(vol) -> Sets the volume of all sounds in this Howl group.
|
|
857
|
+
* volume(vol, id) -> Sets the volume of passed sound id.
|
|
858
|
+
* @return {Howl/Number} Returns self or current volume.
|
|
859
|
+
*/
|
|
860
|
+
volume: function() {
|
|
861
|
+
var self = this;
|
|
862
|
+
var args = arguments;
|
|
863
|
+
var vol, id;
|
|
864
|
+
if (args.length === 0) {
|
|
865
|
+
return self._volume;
|
|
866
|
+
} else if (args.length === 1 || args.length === 2 && typeof args[1] === "undefined") {
|
|
867
|
+
var ids = self._getSoundIds();
|
|
868
|
+
var index = ids.indexOf(args[0]);
|
|
869
|
+
if (index >= 0) {
|
|
870
|
+
id = parseInt(args[0], 10);
|
|
871
|
+
} else {
|
|
872
|
+
vol = parseFloat(args[0]);
|
|
873
|
+
}
|
|
874
|
+
} else if (args.length >= 2) {
|
|
875
|
+
vol = parseFloat(args[0]);
|
|
876
|
+
id = parseInt(args[1], 10);
|
|
877
|
+
}
|
|
878
|
+
var sound;
|
|
879
|
+
if (typeof vol !== "undefined" && vol >= 0 && vol <= 1) {
|
|
880
|
+
if (self._state !== "loaded" || self._playLock) {
|
|
881
|
+
self._queue.push({
|
|
882
|
+
event: "volume",
|
|
883
|
+
action: function() {
|
|
884
|
+
self.volume.apply(self, args);
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
return self;
|
|
888
|
+
}
|
|
889
|
+
if (typeof id === "undefined") {
|
|
890
|
+
self._volume = vol;
|
|
891
|
+
}
|
|
892
|
+
id = self._getSoundIds(id);
|
|
893
|
+
for (var i = 0; i < id.length; i++) {
|
|
894
|
+
sound = self._soundById(id[i]);
|
|
895
|
+
if (sound) {
|
|
896
|
+
sound._volume = vol;
|
|
897
|
+
if (!args[2]) {
|
|
898
|
+
self._stopFade(id[i]);
|
|
899
|
+
}
|
|
900
|
+
if (self._webAudio && sound._node && !sound._muted) {
|
|
901
|
+
sound._node.gain.setValueAtTime(vol, Howler3.ctx.currentTime);
|
|
902
|
+
} else if (sound._node && !sound._muted) {
|
|
903
|
+
sound._node.volume = vol * Howler3.volume();
|
|
904
|
+
}
|
|
905
|
+
self._emit("volume", sound._id);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
sound = id ? self._soundById(id) : self._sounds[0];
|
|
910
|
+
return sound ? sound._volume : 0;
|
|
911
|
+
}
|
|
912
|
+
return self;
|
|
913
|
+
},
|
|
914
|
+
/**
|
|
915
|
+
* Fade a currently playing sound between two volumes (if no id is passed, all sounds will fade).
|
|
916
|
+
* @param {Number} from The value to fade from (0.0 to 1.0).
|
|
917
|
+
* @param {Number} to The volume to fade to (0.0 to 1.0).
|
|
918
|
+
* @param {Number} len Time in milliseconds to fade.
|
|
919
|
+
* @param {Number} id The sound id (omit to fade all sounds).
|
|
920
|
+
* @return {Howl}
|
|
921
|
+
*/
|
|
922
|
+
fade: function(from, to, len, id) {
|
|
923
|
+
var self = this;
|
|
924
|
+
if (self._state !== "loaded" || self._playLock) {
|
|
925
|
+
self._queue.push({
|
|
926
|
+
event: "fade",
|
|
927
|
+
action: function() {
|
|
928
|
+
self.fade(from, to, len, id);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
return self;
|
|
932
|
+
}
|
|
933
|
+
from = Math.min(Math.max(0, parseFloat(from)), 1);
|
|
934
|
+
to = Math.min(Math.max(0, parseFloat(to)), 1);
|
|
935
|
+
len = parseFloat(len);
|
|
936
|
+
self.volume(from, id);
|
|
937
|
+
var ids = self._getSoundIds(id);
|
|
938
|
+
for (var i = 0; i < ids.length; i++) {
|
|
939
|
+
var sound = self._soundById(ids[i]);
|
|
940
|
+
if (sound) {
|
|
941
|
+
if (!id) {
|
|
942
|
+
self._stopFade(ids[i]);
|
|
943
|
+
}
|
|
944
|
+
if (self._webAudio && !sound._muted) {
|
|
945
|
+
var currentTime = Howler3.ctx.currentTime;
|
|
946
|
+
var end = currentTime + len / 1e3;
|
|
947
|
+
sound._volume = from;
|
|
948
|
+
sound._node.gain.setValueAtTime(from, currentTime);
|
|
949
|
+
sound._node.gain.linearRampToValueAtTime(to, end);
|
|
950
|
+
}
|
|
951
|
+
self._startFadeInterval(sound, from, to, len, ids[i], typeof id === "undefined");
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return self;
|
|
955
|
+
},
|
|
956
|
+
/**
|
|
957
|
+
* Starts the internal interval to fade a sound.
|
|
958
|
+
* @param {Object} sound Reference to sound to fade.
|
|
959
|
+
* @param {Number} from The value to fade from (0.0 to 1.0).
|
|
960
|
+
* @param {Number} to The volume to fade to (0.0 to 1.0).
|
|
961
|
+
* @param {Number} len Time in milliseconds to fade.
|
|
962
|
+
* @param {Number} id The sound id to fade.
|
|
963
|
+
* @param {Boolean} isGroup If true, set the volume on the group.
|
|
964
|
+
*/
|
|
965
|
+
_startFadeInterval: function(sound, from, to, len, id, isGroup) {
|
|
966
|
+
var self = this;
|
|
967
|
+
var vol = from;
|
|
968
|
+
var diff = to - from;
|
|
969
|
+
var steps = Math.abs(diff / 0.01);
|
|
970
|
+
var stepLen = Math.max(4, steps > 0 ? len / steps : len);
|
|
971
|
+
var lastTick = Date.now();
|
|
972
|
+
sound._fadeTo = to;
|
|
973
|
+
sound._interval = setInterval(function() {
|
|
974
|
+
var tick = (Date.now() - lastTick) / len;
|
|
975
|
+
lastTick = Date.now();
|
|
976
|
+
vol += diff * tick;
|
|
977
|
+
vol = Math.round(vol * 100) / 100;
|
|
978
|
+
if (diff < 0) {
|
|
979
|
+
vol = Math.max(to, vol);
|
|
980
|
+
} else {
|
|
981
|
+
vol = Math.min(to, vol);
|
|
982
|
+
}
|
|
983
|
+
if (self._webAudio) {
|
|
984
|
+
sound._volume = vol;
|
|
985
|
+
} else {
|
|
986
|
+
self.volume(vol, sound._id, true);
|
|
987
|
+
}
|
|
988
|
+
if (isGroup) {
|
|
989
|
+
self._volume = vol;
|
|
990
|
+
}
|
|
991
|
+
if (to < from && vol <= to || to > from && vol >= to) {
|
|
992
|
+
clearInterval(sound._interval);
|
|
993
|
+
sound._interval = null;
|
|
994
|
+
sound._fadeTo = null;
|
|
995
|
+
self.volume(to, sound._id);
|
|
996
|
+
self._emit("fade", sound._id);
|
|
997
|
+
}
|
|
998
|
+
}, stepLen);
|
|
999
|
+
},
|
|
1000
|
+
/**
|
|
1001
|
+
* Internal method that stops the currently playing fade when
|
|
1002
|
+
* a new fade starts, volume is changed or the sound is stopped.
|
|
1003
|
+
* @param {Number} id The sound id.
|
|
1004
|
+
* @return {Howl}
|
|
1005
|
+
*/
|
|
1006
|
+
_stopFade: function(id) {
|
|
1007
|
+
var self = this;
|
|
1008
|
+
var sound = self._soundById(id);
|
|
1009
|
+
if (sound && sound._interval) {
|
|
1010
|
+
if (self._webAudio) {
|
|
1011
|
+
sound._node.gain.cancelScheduledValues(Howler3.ctx.currentTime);
|
|
1012
|
+
}
|
|
1013
|
+
clearInterval(sound._interval);
|
|
1014
|
+
sound._interval = null;
|
|
1015
|
+
self.volume(sound._fadeTo, id);
|
|
1016
|
+
sound._fadeTo = null;
|
|
1017
|
+
self._emit("fade", id);
|
|
1018
|
+
}
|
|
1019
|
+
return self;
|
|
1020
|
+
},
|
|
1021
|
+
/**
|
|
1022
|
+
* Get/set the loop parameter on a sound. This method can optionally take 0, 1 or 2 arguments.
|
|
1023
|
+
* loop() -> Returns the group's loop value.
|
|
1024
|
+
* loop(id) -> Returns the sound id's loop value.
|
|
1025
|
+
* loop(loop) -> Sets the loop value for all sounds in this Howl group.
|
|
1026
|
+
* loop(loop, id) -> Sets the loop value of passed sound id.
|
|
1027
|
+
* @return {Howl/Boolean} Returns self or current loop value.
|
|
1028
|
+
*/
|
|
1029
|
+
loop: function() {
|
|
1030
|
+
var self = this;
|
|
1031
|
+
var args = arguments;
|
|
1032
|
+
var loop, id, sound;
|
|
1033
|
+
if (args.length === 0) {
|
|
1034
|
+
return self._loop;
|
|
1035
|
+
} else if (args.length === 1) {
|
|
1036
|
+
if (typeof args[0] === "boolean") {
|
|
1037
|
+
loop = args[0];
|
|
1038
|
+
self._loop = loop;
|
|
1039
|
+
} else {
|
|
1040
|
+
sound = self._soundById(parseInt(args[0], 10));
|
|
1041
|
+
return sound ? sound._loop : false;
|
|
1042
|
+
}
|
|
1043
|
+
} else if (args.length === 2) {
|
|
1044
|
+
loop = args[0];
|
|
1045
|
+
id = parseInt(args[1], 10);
|
|
1046
|
+
}
|
|
1047
|
+
var ids = self._getSoundIds(id);
|
|
1048
|
+
for (var i = 0; i < ids.length; i++) {
|
|
1049
|
+
sound = self._soundById(ids[i]);
|
|
1050
|
+
if (sound) {
|
|
1051
|
+
sound._loop = loop;
|
|
1052
|
+
if (self._webAudio && sound._node && sound._node.bufferSource) {
|
|
1053
|
+
sound._node.bufferSource.loop = loop;
|
|
1054
|
+
if (loop) {
|
|
1055
|
+
sound._node.bufferSource.loopStart = sound._start || 0;
|
|
1056
|
+
sound._node.bufferSource.loopEnd = sound._stop;
|
|
1057
|
+
if (self.playing(ids[i])) {
|
|
1058
|
+
self.pause(ids[i], true);
|
|
1059
|
+
self.play(ids[i], true);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return self;
|
|
1066
|
+
},
|
|
1067
|
+
/**
|
|
1068
|
+
* Get/set the playback rate of a sound. This method can optionally take 0, 1 or 2 arguments.
|
|
1069
|
+
* rate() -> Returns the first sound node's current playback rate.
|
|
1070
|
+
* rate(id) -> Returns the sound id's current playback rate.
|
|
1071
|
+
* rate(rate) -> Sets the playback rate of all sounds in this Howl group.
|
|
1072
|
+
* rate(rate, id) -> Sets the playback rate of passed sound id.
|
|
1073
|
+
* @return {Howl/Number} Returns self or the current playback rate.
|
|
1074
|
+
*/
|
|
1075
|
+
rate: function() {
|
|
1076
|
+
var self = this;
|
|
1077
|
+
var args = arguments;
|
|
1078
|
+
var rate, id;
|
|
1079
|
+
if (args.length === 0) {
|
|
1080
|
+
id = self._sounds[0]._id;
|
|
1081
|
+
} else if (args.length === 1) {
|
|
1082
|
+
var ids = self._getSoundIds();
|
|
1083
|
+
var index = ids.indexOf(args[0]);
|
|
1084
|
+
if (index >= 0) {
|
|
1085
|
+
id = parseInt(args[0], 10);
|
|
1086
|
+
} else {
|
|
1087
|
+
rate = parseFloat(args[0]);
|
|
1088
|
+
}
|
|
1089
|
+
} else if (args.length === 2) {
|
|
1090
|
+
rate = parseFloat(args[0]);
|
|
1091
|
+
id = parseInt(args[1], 10);
|
|
1092
|
+
}
|
|
1093
|
+
var sound;
|
|
1094
|
+
if (typeof rate === "number") {
|
|
1095
|
+
if (self._state !== "loaded" || self._playLock) {
|
|
1096
|
+
self._queue.push({
|
|
1097
|
+
event: "rate",
|
|
1098
|
+
action: function() {
|
|
1099
|
+
self.rate.apply(self, args);
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
return self;
|
|
1103
|
+
}
|
|
1104
|
+
if (typeof id === "undefined") {
|
|
1105
|
+
self._rate = rate;
|
|
1106
|
+
}
|
|
1107
|
+
id = self._getSoundIds(id);
|
|
1108
|
+
for (var i = 0; i < id.length; i++) {
|
|
1109
|
+
sound = self._soundById(id[i]);
|
|
1110
|
+
if (sound) {
|
|
1111
|
+
if (self.playing(id[i])) {
|
|
1112
|
+
sound._rateSeek = self.seek(id[i]);
|
|
1113
|
+
sound._playStart = self._webAudio ? Howler3.ctx.currentTime : sound._playStart;
|
|
1114
|
+
}
|
|
1115
|
+
sound._rate = rate;
|
|
1116
|
+
if (self._webAudio && sound._node && sound._node.bufferSource) {
|
|
1117
|
+
sound._node.bufferSource.playbackRate.setValueAtTime(rate, Howler3.ctx.currentTime);
|
|
1118
|
+
} else if (sound._node) {
|
|
1119
|
+
sound._node.playbackRate = rate;
|
|
1120
|
+
}
|
|
1121
|
+
var seek = self.seek(id[i]);
|
|
1122
|
+
var duration = (self._sprite[sound._sprite][0] + self._sprite[sound._sprite][1]) / 1e3 - seek;
|
|
1123
|
+
var timeout = duration * 1e3 / Math.abs(sound._rate);
|
|
1124
|
+
if (self._endTimers[id[i]] || !sound._paused) {
|
|
1125
|
+
self._clearTimer(id[i]);
|
|
1126
|
+
self._endTimers[id[i]] = setTimeout(self._ended.bind(self, sound), timeout);
|
|
1127
|
+
}
|
|
1128
|
+
self._emit("rate", sound._id);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
} else {
|
|
1132
|
+
sound = self._soundById(id);
|
|
1133
|
+
return sound ? sound._rate : self._rate;
|
|
1134
|
+
}
|
|
1135
|
+
return self;
|
|
1136
|
+
},
|
|
1137
|
+
/**
|
|
1138
|
+
* Get/set the seek position of a sound. This method can optionally take 0, 1 or 2 arguments.
|
|
1139
|
+
* seek() -> Returns the first sound node's current seek position.
|
|
1140
|
+
* seek(id) -> Returns the sound id's current seek position.
|
|
1141
|
+
* seek(seek) -> Sets the seek position of the first sound node.
|
|
1142
|
+
* seek(seek, id) -> Sets the seek position of passed sound id.
|
|
1143
|
+
* @return {Howl/Number} Returns self or the current seek position.
|
|
1144
|
+
*/
|
|
1145
|
+
seek: function() {
|
|
1146
|
+
var self = this;
|
|
1147
|
+
var args = arguments;
|
|
1148
|
+
var seek, id;
|
|
1149
|
+
if (args.length === 0) {
|
|
1150
|
+
if (self._sounds.length) {
|
|
1151
|
+
id = self._sounds[0]._id;
|
|
1152
|
+
}
|
|
1153
|
+
} else if (args.length === 1) {
|
|
1154
|
+
var ids = self._getSoundIds();
|
|
1155
|
+
var index = ids.indexOf(args[0]);
|
|
1156
|
+
if (index >= 0) {
|
|
1157
|
+
id = parseInt(args[0], 10);
|
|
1158
|
+
} else if (self._sounds.length) {
|
|
1159
|
+
id = self._sounds[0]._id;
|
|
1160
|
+
seek = parseFloat(args[0]);
|
|
1161
|
+
}
|
|
1162
|
+
} else if (args.length === 2) {
|
|
1163
|
+
seek = parseFloat(args[0]);
|
|
1164
|
+
id = parseInt(args[1], 10);
|
|
1165
|
+
}
|
|
1166
|
+
if (typeof id === "undefined") {
|
|
1167
|
+
return 0;
|
|
1168
|
+
}
|
|
1169
|
+
if (typeof seek === "number" && (self._state !== "loaded" || self._playLock)) {
|
|
1170
|
+
self._queue.push({
|
|
1171
|
+
event: "seek",
|
|
1172
|
+
action: function() {
|
|
1173
|
+
self.seek.apply(self, args);
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
return self;
|
|
1177
|
+
}
|
|
1178
|
+
var sound = self._soundById(id);
|
|
1179
|
+
if (sound) {
|
|
1180
|
+
if (typeof seek === "number" && seek >= 0) {
|
|
1181
|
+
var playing = self.playing(id);
|
|
1182
|
+
if (playing) {
|
|
1183
|
+
self.pause(id, true);
|
|
1184
|
+
}
|
|
1185
|
+
sound._seek = seek;
|
|
1186
|
+
sound._ended = false;
|
|
1187
|
+
self._clearTimer(id);
|
|
1188
|
+
if (!self._webAudio && sound._node && !isNaN(sound._node.duration)) {
|
|
1189
|
+
sound._node.currentTime = seek;
|
|
1190
|
+
}
|
|
1191
|
+
var seekAndEmit = function() {
|
|
1192
|
+
if (playing) {
|
|
1193
|
+
self.play(id, true);
|
|
1194
|
+
}
|
|
1195
|
+
self._emit("seek", id);
|
|
1196
|
+
};
|
|
1197
|
+
if (playing && !self._webAudio) {
|
|
1198
|
+
var emitSeek = function() {
|
|
1199
|
+
if (!self._playLock) {
|
|
1200
|
+
seekAndEmit();
|
|
1201
|
+
} else {
|
|
1202
|
+
setTimeout(emitSeek, 0);
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
setTimeout(emitSeek, 0);
|
|
1206
|
+
} else {
|
|
1207
|
+
seekAndEmit();
|
|
1208
|
+
}
|
|
1209
|
+
} else {
|
|
1210
|
+
if (self._webAudio) {
|
|
1211
|
+
var realTime = self.playing(id) ? Howler3.ctx.currentTime - sound._playStart : 0;
|
|
1212
|
+
var rateSeek = sound._rateSeek ? sound._rateSeek - sound._seek : 0;
|
|
1213
|
+
return sound._seek + (rateSeek + realTime * Math.abs(sound._rate));
|
|
1214
|
+
} else {
|
|
1215
|
+
return sound._node.currentTime;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return self;
|
|
1220
|
+
},
|
|
1221
|
+
/**
|
|
1222
|
+
* Check if a specific sound is currently playing or not (if id is provided), or check if at least one of the sounds in the group is playing or not.
|
|
1223
|
+
* @param {Number} id The sound id to check. If none is passed, the whole sound group is checked.
|
|
1224
|
+
* @return {Boolean} True if playing and false if not.
|
|
1225
|
+
*/
|
|
1226
|
+
playing: function(id) {
|
|
1227
|
+
var self = this;
|
|
1228
|
+
if (typeof id === "number") {
|
|
1229
|
+
var sound = self._soundById(id);
|
|
1230
|
+
return sound ? !sound._paused : false;
|
|
1231
|
+
}
|
|
1232
|
+
for (var i = 0; i < self._sounds.length; i++) {
|
|
1233
|
+
if (!self._sounds[i]._paused) {
|
|
1234
|
+
return true;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return false;
|
|
1238
|
+
},
|
|
1239
|
+
/**
|
|
1240
|
+
* Get the duration of this sound. Passing a sound id will return the sprite duration.
|
|
1241
|
+
* @param {Number} id The sound id to check. If none is passed, return full source duration.
|
|
1242
|
+
* @return {Number} Audio duration in seconds.
|
|
1243
|
+
*/
|
|
1244
|
+
duration: function(id) {
|
|
1245
|
+
var self = this;
|
|
1246
|
+
var duration = self._duration;
|
|
1247
|
+
var sound = self._soundById(id);
|
|
1248
|
+
if (sound) {
|
|
1249
|
+
duration = self._sprite[sound._sprite][1] / 1e3;
|
|
1250
|
+
}
|
|
1251
|
+
return duration;
|
|
1252
|
+
},
|
|
1253
|
+
/**
|
|
1254
|
+
* Returns the current loaded state of this Howl.
|
|
1255
|
+
* @return {String} 'unloaded', 'loading', 'loaded'
|
|
1256
|
+
*/
|
|
1257
|
+
state: function() {
|
|
1258
|
+
return this._state;
|
|
1259
|
+
},
|
|
1260
|
+
/**
|
|
1261
|
+
* Unload and destroy the current Howl object.
|
|
1262
|
+
* This will immediately stop all sound instances attached to this group.
|
|
1263
|
+
*/
|
|
1264
|
+
unload: function() {
|
|
1265
|
+
var self = this;
|
|
1266
|
+
var sounds = self._sounds;
|
|
1267
|
+
for (var i = 0; i < sounds.length; i++) {
|
|
1268
|
+
if (!sounds[i]._paused) {
|
|
1269
|
+
self.stop(sounds[i]._id);
|
|
1270
|
+
}
|
|
1271
|
+
if (!self._webAudio) {
|
|
1272
|
+
self._clearSound(sounds[i]._node);
|
|
1273
|
+
sounds[i]._node.removeEventListener("error", sounds[i]._errorFn, false);
|
|
1274
|
+
sounds[i]._node.removeEventListener(Howler3._canPlayEvent, sounds[i]._loadFn, false);
|
|
1275
|
+
sounds[i]._node.removeEventListener("ended", sounds[i]._endFn, false);
|
|
1276
|
+
Howler3._releaseHtml5Audio(sounds[i]._node);
|
|
1277
|
+
}
|
|
1278
|
+
delete sounds[i]._node;
|
|
1279
|
+
self._clearTimer(sounds[i]._id);
|
|
1280
|
+
}
|
|
1281
|
+
var index = Howler3._howls.indexOf(self);
|
|
1282
|
+
if (index >= 0) {
|
|
1283
|
+
Howler3._howls.splice(index, 1);
|
|
1284
|
+
}
|
|
1285
|
+
var remCache = true;
|
|
1286
|
+
for (i = 0; i < Howler3._howls.length; i++) {
|
|
1287
|
+
if (Howler3._howls[i]._src === self._src || self._src.indexOf(Howler3._howls[i]._src) >= 0) {
|
|
1288
|
+
remCache = false;
|
|
1289
|
+
break;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (cache && remCache) {
|
|
1293
|
+
delete cache[self._src];
|
|
1294
|
+
}
|
|
1295
|
+
Howler3.noAudio = false;
|
|
1296
|
+
self._state = "unloaded";
|
|
1297
|
+
self._sounds = [];
|
|
1298
|
+
self = null;
|
|
1299
|
+
return null;
|
|
1300
|
+
},
|
|
1301
|
+
/**
|
|
1302
|
+
* Listen to a custom event.
|
|
1303
|
+
* @param {String} event Event name.
|
|
1304
|
+
* @param {Function} fn Listener to call.
|
|
1305
|
+
* @param {Number} id (optional) Only listen to events for this sound.
|
|
1306
|
+
* @param {Number} once (INTERNAL) Marks event to fire only once.
|
|
1307
|
+
* @return {Howl}
|
|
1308
|
+
*/
|
|
1309
|
+
on: function(event, fn, id, once) {
|
|
1310
|
+
var self = this;
|
|
1311
|
+
var events = self["_on" + event];
|
|
1312
|
+
if (typeof fn === "function") {
|
|
1313
|
+
events.push(once ? { id, fn, once } : { id, fn });
|
|
1314
|
+
}
|
|
1315
|
+
return self;
|
|
1316
|
+
},
|
|
1317
|
+
/**
|
|
1318
|
+
* Remove a custom event. Call without parameters to remove all events.
|
|
1319
|
+
* @param {String} event Event name.
|
|
1320
|
+
* @param {Function} fn Listener to remove. Leave empty to remove all.
|
|
1321
|
+
* @param {Number} id (optional) Only remove events for this sound.
|
|
1322
|
+
* @return {Howl}
|
|
1323
|
+
*/
|
|
1324
|
+
off: function(event, fn, id) {
|
|
1325
|
+
var self = this;
|
|
1326
|
+
var events = self["_on" + event];
|
|
1327
|
+
var i = 0;
|
|
1328
|
+
if (typeof fn === "number") {
|
|
1329
|
+
id = fn;
|
|
1330
|
+
fn = null;
|
|
1331
|
+
}
|
|
1332
|
+
if (fn || id) {
|
|
1333
|
+
for (i = 0; i < events.length; i++) {
|
|
1334
|
+
var isId = id === events[i].id;
|
|
1335
|
+
if (fn === events[i].fn && isId || !fn && isId) {
|
|
1336
|
+
events.splice(i, 1);
|
|
1337
|
+
break;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
} else if (event) {
|
|
1341
|
+
self["_on" + event] = [];
|
|
1342
|
+
} else {
|
|
1343
|
+
var keys = Object.keys(self);
|
|
1344
|
+
for (i = 0; i < keys.length; i++) {
|
|
1345
|
+
if (keys[i].indexOf("_on") === 0 && Array.isArray(self[keys[i]])) {
|
|
1346
|
+
self[keys[i]] = [];
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return self;
|
|
1351
|
+
},
|
|
1352
|
+
/**
|
|
1353
|
+
* Listen to a custom event and remove it once fired.
|
|
1354
|
+
* @param {String} event Event name.
|
|
1355
|
+
* @param {Function} fn Listener to call.
|
|
1356
|
+
* @param {Number} id (optional) Only listen to events for this sound.
|
|
1357
|
+
* @return {Howl}
|
|
1358
|
+
*/
|
|
1359
|
+
once: function(event, fn, id) {
|
|
1360
|
+
var self = this;
|
|
1361
|
+
self.on(event, fn, id, 1);
|
|
1362
|
+
return self;
|
|
1363
|
+
},
|
|
1364
|
+
/**
|
|
1365
|
+
* Emit all events of a specific type and pass the sound id.
|
|
1366
|
+
* @param {String} event Event name.
|
|
1367
|
+
* @param {Number} id Sound ID.
|
|
1368
|
+
* @param {Number} msg Message to go with event.
|
|
1369
|
+
* @return {Howl}
|
|
1370
|
+
*/
|
|
1371
|
+
_emit: function(event, id, msg) {
|
|
1372
|
+
var self = this;
|
|
1373
|
+
var events = self["_on" + event];
|
|
1374
|
+
for (var i = events.length - 1; i >= 0; i--) {
|
|
1375
|
+
if (!events[i].id || events[i].id === id || event === "load") {
|
|
1376
|
+
setTimeout(function(fn) {
|
|
1377
|
+
fn.call(this, id, msg);
|
|
1378
|
+
}.bind(self, events[i].fn), 0);
|
|
1379
|
+
if (events[i].once) {
|
|
1380
|
+
self.off(event, events[i].fn, events[i].id);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
self._loadQueue(event);
|
|
1385
|
+
return self;
|
|
1386
|
+
},
|
|
1387
|
+
/**
|
|
1388
|
+
* Queue of actions initiated before the sound has loaded.
|
|
1389
|
+
* These will be called in sequence, with the next only firing
|
|
1390
|
+
* after the previous has finished executing (even if async like play).
|
|
1391
|
+
* @return {Howl}
|
|
1392
|
+
*/
|
|
1393
|
+
_loadQueue: function(event) {
|
|
1394
|
+
var self = this;
|
|
1395
|
+
if (self._queue.length > 0) {
|
|
1396
|
+
var task = self._queue[0];
|
|
1397
|
+
if (task.event === event) {
|
|
1398
|
+
self._queue.shift();
|
|
1399
|
+
self._loadQueue();
|
|
1400
|
+
}
|
|
1401
|
+
if (!event) {
|
|
1402
|
+
task.action();
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return self;
|
|
1406
|
+
},
|
|
1407
|
+
/**
|
|
1408
|
+
* Fired when playback ends at the end of the duration.
|
|
1409
|
+
* @param {Sound} sound The sound object to work with.
|
|
1410
|
+
* @return {Howl}
|
|
1411
|
+
*/
|
|
1412
|
+
_ended: function(sound) {
|
|
1413
|
+
var self = this;
|
|
1414
|
+
var sprite = sound._sprite;
|
|
1415
|
+
if (!self._webAudio && sound._node && !sound._node.paused && !sound._node.ended && sound._node.currentTime < sound._stop) {
|
|
1416
|
+
setTimeout(self._ended.bind(self, sound), 100);
|
|
1417
|
+
return self;
|
|
1418
|
+
}
|
|
1419
|
+
var loop = !!(sound._loop || self._sprite[sprite][2]);
|
|
1420
|
+
self._emit("end", sound._id);
|
|
1421
|
+
if (!self._webAudio && loop) {
|
|
1422
|
+
self.stop(sound._id, true).play(sound._id);
|
|
1423
|
+
}
|
|
1424
|
+
if (self._webAudio && loop) {
|
|
1425
|
+
self._emit("play", sound._id);
|
|
1426
|
+
sound._seek = sound._start || 0;
|
|
1427
|
+
sound._rateSeek = 0;
|
|
1428
|
+
sound._playStart = Howler3.ctx.currentTime;
|
|
1429
|
+
var timeout = (sound._stop - sound._start) * 1e3 / Math.abs(sound._rate);
|
|
1430
|
+
self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);
|
|
1431
|
+
}
|
|
1432
|
+
if (self._webAudio && !loop) {
|
|
1433
|
+
sound._paused = true;
|
|
1434
|
+
sound._ended = true;
|
|
1435
|
+
sound._seek = sound._start || 0;
|
|
1436
|
+
sound._rateSeek = 0;
|
|
1437
|
+
self._clearTimer(sound._id);
|
|
1438
|
+
self._cleanBuffer(sound._node);
|
|
1439
|
+
Howler3._autoSuspend();
|
|
1440
|
+
}
|
|
1441
|
+
if (!self._webAudio && !loop) {
|
|
1442
|
+
self.stop(sound._id, true);
|
|
1443
|
+
}
|
|
1444
|
+
return self;
|
|
1445
|
+
},
|
|
1446
|
+
/**
|
|
1447
|
+
* Clear the end timer for a sound playback.
|
|
1448
|
+
* @param {Number} id The sound ID.
|
|
1449
|
+
* @return {Howl}
|
|
1450
|
+
*/
|
|
1451
|
+
_clearTimer: function(id) {
|
|
1452
|
+
var self = this;
|
|
1453
|
+
if (self._endTimers[id]) {
|
|
1454
|
+
if (typeof self._endTimers[id] !== "function") {
|
|
1455
|
+
clearTimeout(self._endTimers[id]);
|
|
1456
|
+
} else {
|
|
1457
|
+
var sound = self._soundById(id);
|
|
1458
|
+
if (sound && sound._node) {
|
|
1459
|
+
sound._node.removeEventListener("ended", self._endTimers[id], false);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
delete self._endTimers[id];
|
|
1463
|
+
}
|
|
1464
|
+
return self;
|
|
1465
|
+
},
|
|
1466
|
+
/**
|
|
1467
|
+
* Return the sound identified by this ID, or return null.
|
|
1468
|
+
* @param {Number} id Sound ID
|
|
1469
|
+
* @return {Object} Sound object or null.
|
|
1470
|
+
*/
|
|
1471
|
+
_soundById: function(id) {
|
|
1472
|
+
var self = this;
|
|
1473
|
+
for (var i = 0; i < self._sounds.length; i++) {
|
|
1474
|
+
if (id === self._sounds[i]._id) {
|
|
1475
|
+
return self._sounds[i];
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return null;
|
|
1479
|
+
},
|
|
1480
|
+
/**
|
|
1481
|
+
* Return an inactive sound from the pool or create a new one.
|
|
1482
|
+
* @return {Sound} Sound playback object.
|
|
1483
|
+
*/
|
|
1484
|
+
_inactiveSound: function() {
|
|
1485
|
+
var self = this;
|
|
1486
|
+
self._drain();
|
|
1487
|
+
for (var i = 0; i < self._sounds.length; i++) {
|
|
1488
|
+
if (self._sounds[i]._ended) {
|
|
1489
|
+
return self._sounds[i].reset();
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
return new Sound2(self);
|
|
1493
|
+
},
|
|
1494
|
+
/**
|
|
1495
|
+
* Drain excess inactive sounds from the pool.
|
|
1496
|
+
*/
|
|
1497
|
+
_drain: function() {
|
|
1498
|
+
var self = this;
|
|
1499
|
+
var limit = self._pool;
|
|
1500
|
+
var cnt = 0;
|
|
1501
|
+
var i = 0;
|
|
1502
|
+
if (self._sounds.length < limit) {
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
for (i = 0; i < self._sounds.length; i++) {
|
|
1506
|
+
if (self._sounds[i]._ended) {
|
|
1507
|
+
cnt++;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
for (i = self._sounds.length - 1; i >= 0; i--) {
|
|
1511
|
+
if (cnt <= limit) {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
if (self._sounds[i]._ended) {
|
|
1515
|
+
if (self._webAudio && self._sounds[i]._node) {
|
|
1516
|
+
self._sounds[i]._node.disconnect(0);
|
|
1517
|
+
}
|
|
1518
|
+
self._sounds.splice(i, 1);
|
|
1519
|
+
cnt--;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
},
|
|
1523
|
+
/**
|
|
1524
|
+
* Get all ID's from the sounds pool.
|
|
1525
|
+
* @param {Number} id Only return one ID if one is passed.
|
|
1526
|
+
* @return {Array} Array of IDs.
|
|
1527
|
+
*/
|
|
1528
|
+
_getSoundIds: function(id) {
|
|
1529
|
+
var self = this;
|
|
1530
|
+
if (typeof id === "undefined") {
|
|
1531
|
+
var ids = [];
|
|
1532
|
+
for (var i = 0; i < self._sounds.length; i++) {
|
|
1533
|
+
ids.push(self._sounds[i]._id);
|
|
1534
|
+
}
|
|
1535
|
+
return ids;
|
|
1536
|
+
} else {
|
|
1537
|
+
return [id];
|
|
1538
|
+
}
|
|
1539
|
+
},
|
|
1540
|
+
/**
|
|
1541
|
+
* Load the sound back into the buffer source.
|
|
1542
|
+
* @param {Sound} sound The sound object to work with.
|
|
1543
|
+
* @return {Howl}
|
|
1544
|
+
*/
|
|
1545
|
+
_refreshBuffer: function(sound) {
|
|
1546
|
+
var self = this;
|
|
1547
|
+
sound._node.bufferSource = Howler3.ctx.createBufferSource();
|
|
1548
|
+
sound._node.bufferSource.buffer = cache[self._src];
|
|
1549
|
+
if (sound._panner) {
|
|
1550
|
+
sound._node.bufferSource.connect(sound._panner);
|
|
1551
|
+
} else {
|
|
1552
|
+
sound._node.bufferSource.connect(sound._node);
|
|
1553
|
+
}
|
|
1554
|
+
sound._node.bufferSource.loop = sound._loop;
|
|
1555
|
+
if (sound._loop) {
|
|
1556
|
+
sound._node.bufferSource.loopStart = sound._start || 0;
|
|
1557
|
+
sound._node.bufferSource.loopEnd = sound._stop || 0;
|
|
1558
|
+
}
|
|
1559
|
+
sound._node.bufferSource.playbackRate.setValueAtTime(sound._rate, Howler3.ctx.currentTime);
|
|
1560
|
+
return self;
|
|
1561
|
+
},
|
|
1562
|
+
/**
|
|
1563
|
+
* Prevent memory leaks by cleaning up the buffer source after playback.
|
|
1564
|
+
* @param {Object} node Sound's audio node containing the buffer source.
|
|
1565
|
+
* @return {Howl}
|
|
1566
|
+
*/
|
|
1567
|
+
_cleanBuffer: function(node) {
|
|
1568
|
+
var self = this;
|
|
1569
|
+
var isIOS = Howler3._navigator && Howler3._navigator.vendor.indexOf("Apple") >= 0;
|
|
1570
|
+
if (!node.bufferSource) {
|
|
1571
|
+
return self;
|
|
1572
|
+
}
|
|
1573
|
+
if (Howler3._scratchBuffer && node.bufferSource) {
|
|
1574
|
+
node.bufferSource.onended = null;
|
|
1575
|
+
node.bufferSource.disconnect(0);
|
|
1576
|
+
if (isIOS) {
|
|
1577
|
+
try {
|
|
1578
|
+
node.bufferSource.buffer = Howler3._scratchBuffer;
|
|
1579
|
+
} catch (e) {
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
node.bufferSource = null;
|
|
1584
|
+
return self;
|
|
1585
|
+
},
|
|
1586
|
+
/**
|
|
1587
|
+
* Set the source to a 0-second silence to stop any downloading (except in IE).
|
|
1588
|
+
* @param {Object} node Audio node to clear.
|
|
1589
|
+
*/
|
|
1590
|
+
_clearSound: function(node) {
|
|
1591
|
+
var checkIE = /MSIE |Trident\//.test(Howler3._navigator && Howler3._navigator.userAgent);
|
|
1592
|
+
if (!checkIE) {
|
|
1593
|
+
node.src = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA";
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
var Sound2 = function(howl) {
|
|
1598
|
+
this._parent = howl;
|
|
1599
|
+
this.init();
|
|
1600
|
+
};
|
|
1601
|
+
Sound2.prototype = {
|
|
1602
|
+
/**
|
|
1603
|
+
* Initialize a new Sound object.
|
|
1604
|
+
* @return {Sound}
|
|
1605
|
+
*/
|
|
1606
|
+
init: function() {
|
|
1607
|
+
var self = this;
|
|
1608
|
+
var parent = self._parent;
|
|
1609
|
+
self._muted = parent._muted;
|
|
1610
|
+
self._loop = parent._loop;
|
|
1611
|
+
self._volume = parent._volume;
|
|
1612
|
+
self._rate = parent._rate;
|
|
1613
|
+
self._seek = 0;
|
|
1614
|
+
self._paused = true;
|
|
1615
|
+
self._ended = true;
|
|
1616
|
+
self._sprite = "__default";
|
|
1617
|
+
self._id = ++Howler3._counter;
|
|
1618
|
+
parent._sounds.push(self);
|
|
1619
|
+
self.create();
|
|
1620
|
+
return self;
|
|
1621
|
+
},
|
|
1622
|
+
/**
|
|
1623
|
+
* Create and setup a new sound object, whether HTML5 Audio or Web Audio.
|
|
1624
|
+
* @return {Sound}
|
|
1625
|
+
*/
|
|
1626
|
+
create: function() {
|
|
1627
|
+
var self = this;
|
|
1628
|
+
var parent = self._parent;
|
|
1629
|
+
var volume = Howler3._muted || self._muted || self._parent._muted ? 0 : self._volume;
|
|
1630
|
+
if (parent._webAudio) {
|
|
1631
|
+
self._node = typeof Howler3.ctx.createGain === "undefined" ? Howler3.ctx.createGainNode() : Howler3.ctx.createGain();
|
|
1632
|
+
self._node.gain.setValueAtTime(volume, Howler3.ctx.currentTime);
|
|
1633
|
+
self._node.paused = true;
|
|
1634
|
+
self._node.connect(Howler3.masterGain);
|
|
1635
|
+
} else if (!Howler3.noAudio) {
|
|
1636
|
+
self._node = Howler3._obtainHtml5Audio();
|
|
1637
|
+
self._errorFn = self._errorListener.bind(self);
|
|
1638
|
+
self._node.addEventListener("error", self._errorFn, false);
|
|
1639
|
+
self._loadFn = self._loadListener.bind(self);
|
|
1640
|
+
self._node.addEventListener(Howler3._canPlayEvent, self._loadFn, false);
|
|
1641
|
+
self._endFn = self._endListener.bind(self);
|
|
1642
|
+
self._node.addEventListener("ended", self._endFn, false);
|
|
1643
|
+
self._node.src = parent._src;
|
|
1644
|
+
self._node.preload = parent._preload === true ? "auto" : parent._preload;
|
|
1645
|
+
self._node.volume = volume * Howler3.volume();
|
|
1646
|
+
self._node.load();
|
|
1647
|
+
}
|
|
1648
|
+
return self;
|
|
1649
|
+
},
|
|
1650
|
+
/**
|
|
1651
|
+
* Reset the parameters of this sound to the original state (for recycle).
|
|
1652
|
+
* @return {Sound}
|
|
1653
|
+
*/
|
|
1654
|
+
reset: function() {
|
|
1655
|
+
var self = this;
|
|
1656
|
+
var parent = self._parent;
|
|
1657
|
+
self._muted = parent._muted;
|
|
1658
|
+
self._loop = parent._loop;
|
|
1659
|
+
self._volume = parent._volume;
|
|
1660
|
+
self._rate = parent._rate;
|
|
1661
|
+
self._seek = 0;
|
|
1662
|
+
self._rateSeek = 0;
|
|
1663
|
+
self._paused = true;
|
|
1664
|
+
self._ended = true;
|
|
1665
|
+
self._sprite = "__default";
|
|
1666
|
+
self._id = ++Howler3._counter;
|
|
1667
|
+
return self;
|
|
1668
|
+
},
|
|
1669
|
+
/**
|
|
1670
|
+
* HTML5 Audio error listener callback.
|
|
1671
|
+
*/
|
|
1672
|
+
_errorListener: function() {
|
|
1673
|
+
var self = this;
|
|
1674
|
+
self._parent._emit("loaderror", self._id, self._node.error ? self._node.error.code : 0);
|
|
1675
|
+
self._node.removeEventListener("error", self._errorFn, false);
|
|
1676
|
+
},
|
|
1677
|
+
/**
|
|
1678
|
+
* HTML5 Audio canplaythrough listener callback.
|
|
1679
|
+
*/
|
|
1680
|
+
_loadListener: function() {
|
|
1681
|
+
var self = this;
|
|
1682
|
+
var parent = self._parent;
|
|
1683
|
+
parent._duration = Math.ceil(self._node.duration * 10) / 10;
|
|
1684
|
+
if (Object.keys(parent._sprite).length === 0) {
|
|
1685
|
+
parent._sprite = { __default: [0, parent._duration * 1e3] };
|
|
1686
|
+
}
|
|
1687
|
+
if (parent._state !== "loaded") {
|
|
1688
|
+
parent._state = "loaded";
|
|
1689
|
+
parent._emit("load");
|
|
1690
|
+
parent._loadQueue();
|
|
1691
|
+
}
|
|
1692
|
+
self._node.removeEventListener(Howler3._canPlayEvent, self._loadFn, false);
|
|
1693
|
+
},
|
|
1694
|
+
/**
|
|
1695
|
+
* HTML5 Audio ended listener callback.
|
|
1696
|
+
*/
|
|
1697
|
+
_endListener: function() {
|
|
1698
|
+
var self = this;
|
|
1699
|
+
var parent = self._parent;
|
|
1700
|
+
if (parent._duration === Infinity) {
|
|
1701
|
+
parent._duration = Math.ceil(self._node.duration * 10) / 10;
|
|
1702
|
+
if (parent._sprite.__default[1] === Infinity) {
|
|
1703
|
+
parent._sprite.__default[1] = parent._duration * 1e3;
|
|
1704
|
+
}
|
|
1705
|
+
parent._ended(self);
|
|
1706
|
+
}
|
|
1707
|
+
self._node.removeEventListener("ended", self._endFn, false);
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
var cache = {};
|
|
1711
|
+
var loadBuffer = function(self) {
|
|
1712
|
+
var url = self._src;
|
|
1713
|
+
if (cache[url]) {
|
|
1714
|
+
self._duration = cache[url].duration;
|
|
1715
|
+
loadSound(self);
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
if (/^data:[^;]+;base64,/.test(url)) {
|
|
1719
|
+
var data = atob(url.split(",")[1]);
|
|
1720
|
+
var dataView = new Uint8Array(data.length);
|
|
1721
|
+
for (var i = 0; i < data.length; ++i) {
|
|
1722
|
+
dataView[i] = data.charCodeAt(i);
|
|
1723
|
+
}
|
|
1724
|
+
decodeAudioData(dataView.buffer, self);
|
|
1725
|
+
} else {
|
|
1726
|
+
var xhr = new XMLHttpRequest();
|
|
1727
|
+
xhr.open(self._xhr.method, url, true);
|
|
1728
|
+
xhr.withCredentials = self._xhr.withCredentials;
|
|
1729
|
+
xhr.responseType = "arraybuffer";
|
|
1730
|
+
if (self._xhr.headers) {
|
|
1731
|
+
Object.keys(self._xhr.headers).forEach(function(key) {
|
|
1732
|
+
xhr.setRequestHeader(key, self._xhr.headers[key]);
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
xhr.onload = function() {
|
|
1736
|
+
var code = (xhr.status + "")[0];
|
|
1737
|
+
if (code !== "0" && code !== "2" && code !== "3") {
|
|
1738
|
+
self._emit("loaderror", null, "Failed loading audio file with status: " + xhr.status + ".");
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
decodeAudioData(xhr.response, self);
|
|
1742
|
+
};
|
|
1743
|
+
xhr.onerror = function() {
|
|
1744
|
+
if (self._webAudio) {
|
|
1745
|
+
self._html5 = true;
|
|
1746
|
+
self._webAudio = false;
|
|
1747
|
+
self._sounds = [];
|
|
1748
|
+
delete cache[url];
|
|
1749
|
+
self.load();
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
safeXhrSend(xhr);
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
var safeXhrSend = function(xhr) {
|
|
1756
|
+
try {
|
|
1757
|
+
xhr.send();
|
|
1758
|
+
} catch (e) {
|
|
1759
|
+
xhr.onerror();
|
|
1760
|
+
}
|
|
1761
|
+
};
|
|
1762
|
+
var decodeAudioData = function(arraybuffer, self) {
|
|
1763
|
+
var error = function() {
|
|
1764
|
+
self._emit("loaderror", null, "Decoding audio data failed.");
|
|
1765
|
+
};
|
|
1766
|
+
var success = function(buffer) {
|
|
1767
|
+
if (buffer && self._sounds.length > 0) {
|
|
1768
|
+
cache[self._src] = buffer;
|
|
1769
|
+
loadSound(self, buffer);
|
|
1770
|
+
} else {
|
|
1771
|
+
error();
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
if (typeof Promise !== "undefined" && Howler3.ctx.decodeAudioData.length === 1) {
|
|
1775
|
+
Howler3.ctx.decodeAudioData(arraybuffer).then(success).catch(error);
|
|
1776
|
+
} else {
|
|
1777
|
+
Howler3.ctx.decodeAudioData(arraybuffer, success, error);
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
var loadSound = function(self, buffer) {
|
|
1781
|
+
if (buffer && !self._duration) {
|
|
1782
|
+
self._duration = buffer.duration;
|
|
1783
|
+
}
|
|
1784
|
+
if (Object.keys(self._sprite).length === 0) {
|
|
1785
|
+
self._sprite = { __default: [0, self._duration * 1e3] };
|
|
1786
|
+
}
|
|
1787
|
+
if (self._state !== "loaded") {
|
|
1788
|
+
self._state = "loaded";
|
|
1789
|
+
self._emit("load");
|
|
1790
|
+
self._loadQueue();
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
var setupAudioContext = function() {
|
|
1794
|
+
if (!Howler3.usingWebAudio) {
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
try {
|
|
1798
|
+
if (typeof AudioContext !== "undefined") {
|
|
1799
|
+
Howler3.ctx = new AudioContext();
|
|
1800
|
+
} else if (typeof webkitAudioContext !== "undefined") {
|
|
1801
|
+
Howler3.ctx = new webkitAudioContext();
|
|
1802
|
+
} else {
|
|
1803
|
+
Howler3.usingWebAudio = false;
|
|
1804
|
+
}
|
|
1805
|
+
} catch (e) {
|
|
1806
|
+
Howler3.usingWebAudio = false;
|
|
1807
|
+
}
|
|
1808
|
+
if (!Howler3.ctx) {
|
|
1809
|
+
Howler3.usingWebAudio = false;
|
|
1810
|
+
}
|
|
1811
|
+
var iOS = /iP(hone|od|ad)/.test(Howler3._navigator && Howler3._navigator.platform);
|
|
1812
|
+
var appVersion = Howler3._navigator && Howler3._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
|
|
1813
|
+
var version = appVersion ? parseInt(appVersion[1], 10) : null;
|
|
1814
|
+
if (iOS && version && version < 9) {
|
|
1815
|
+
var safari = /safari/.test(Howler3._navigator && Howler3._navigator.userAgent.toLowerCase());
|
|
1816
|
+
if (Howler3._navigator && !safari) {
|
|
1817
|
+
Howler3.usingWebAudio = false;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
if (Howler3.usingWebAudio) {
|
|
1821
|
+
Howler3.masterGain = typeof Howler3.ctx.createGain === "undefined" ? Howler3.ctx.createGainNode() : Howler3.ctx.createGain();
|
|
1822
|
+
Howler3.masterGain.gain.setValueAtTime(Howler3._muted ? 0 : Howler3._volume, Howler3.ctx.currentTime);
|
|
1823
|
+
Howler3.masterGain.connect(Howler3.ctx.destination);
|
|
1824
|
+
}
|
|
1825
|
+
Howler3._setup();
|
|
1826
|
+
};
|
|
1827
|
+
if (typeof define === "function" && define.amd) {
|
|
1828
|
+
define([], function() {
|
|
1829
|
+
return {
|
|
1830
|
+
Howler: Howler3,
|
|
1831
|
+
Howl: Howl3
|
|
1832
|
+
};
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
if (typeof exports2 !== "undefined") {
|
|
1836
|
+
exports2.Howler = Howler3;
|
|
1837
|
+
exports2.Howl = Howl3;
|
|
1838
|
+
}
|
|
1839
|
+
if (typeof global !== "undefined") {
|
|
1840
|
+
global.HowlerGlobal = HowlerGlobal2;
|
|
1841
|
+
global.Howler = Howler3;
|
|
1842
|
+
global.Howl = Howl3;
|
|
1843
|
+
global.Sound = Sound2;
|
|
1844
|
+
} else if (typeof window !== "undefined") {
|
|
1845
|
+
window.HowlerGlobal = HowlerGlobal2;
|
|
1846
|
+
window.Howler = Howler3;
|
|
1847
|
+
window.Howl = Howl3;
|
|
1848
|
+
window.Sound = Sound2;
|
|
1849
|
+
}
|
|
1850
|
+
})();
|
|
1851
|
+
(function() {
|
|
1852
|
+
"use strict";
|
|
1853
|
+
HowlerGlobal.prototype._pos = [0, 0, 0];
|
|
1854
|
+
HowlerGlobal.prototype._orientation = [0, 0, -1, 0, 1, 0];
|
|
1855
|
+
HowlerGlobal.prototype.stereo = function(pan) {
|
|
1856
|
+
var self = this;
|
|
1857
|
+
if (!self.ctx || !self.ctx.listener) {
|
|
1858
|
+
return self;
|
|
1859
|
+
}
|
|
1860
|
+
for (var i = self._howls.length - 1; i >= 0; i--) {
|
|
1861
|
+
self._howls[i].stereo(pan);
|
|
1862
|
+
}
|
|
1863
|
+
return self;
|
|
1864
|
+
};
|
|
1865
|
+
HowlerGlobal.prototype.pos = function(x, y, z) {
|
|
1866
|
+
var self = this;
|
|
1867
|
+
if (!self.ctx || !self.ctx.listener) {
|
|
1868
|
+
return self;
|
|
1869
|
+
}
|
|
1870
|
+
y = typeof y !== "number" ? self._pos[1] : y;
|
|
1871
|
+
z = typeof z !== "number" ? self._pos[2] : z;
|
|
1872
|
+
if (typeof x === "number") {
|
|
1873
|
+
self._pos = [x, y, z];
|
|
1874
|
+
if (typeof self.ctx.listener.positionX !== "undefined") {
|
|
1875
|
+
self.ctx.listener.positionX.setTargetAtTime(self._pos[0], Howler.ctx.currentTime, 0.1);
|
|
1876
|
+
self.ctx.listener.positionY.setTargetAtTime(self._pos[1], Howler.ctx.currentTime, 0.1);
|
|
1877
|
+
self.ctx.listener.positionZ.setTargetAtTime(self._pos[2], Howler.ctx.currentTime, 0.1);
|
|
1878
|
+
} else {
|
|
1879
|
+
self.ctx.listener.setPosition(self._pos[0], self._pos[1], self._pos[2]);
|
|
1880
|
+
}
|
|
1881
|
+
} else {
|
|
1882
|
+
return self._pos;
|
|
1883
|
+
}
|
|
1884
|
+
return self;
|
|
1885
|
+
};
|
|
1886
|
+
HowlerGlobal.prototype.orientation = function(x, y, z, xUp, yUp, zUp) {
|
|
1887
|
+
var self = this;
|
|
1888
|
+
if (!self.ctx || !self.ctx.listener) {
|
|
1889
|
+
return self;
|
|
1890
|
+
}
|
|
1891
|
+
var or = self._orientation;
|
|
1892
|
+
y = typeof y !== "number" ? or[1] : y;
|
|
1893
|
+
z = typeof z !== "number" ? or[2] : z;
|
|
1894
|
+
xUp = typeof xUp !== "number" ? or[3] : xUp;
|
|
1895
|
+
yUp = typeof yUp !== "number" ? or[4] : yUp;
|
|
1896
|
+
zUp = typeof zUp !== "number" ? or[5] : zUp;
|
|
1897
|
+
if (typeof x === "number") {
|
|
1898
|
+
self._orientation = [x, y, z, xUp, yUp, zUp];
|
|
1899
|
+
if (typeof self.ctx.listener.forwardX !== "undefined") {
|
|
1900
|
+
self.ctx.listener.forwardX.setTargetAtTime(x, Howler.ctx.currentTime, 0.1);
|
|
1901
|
+
self.ctx.listener.forwardY.setTargetAtTime(y, Howler.ctx.currentTime, 0.1);
|
|
1902
|
+
self.ctx.listener.forwardZ.setTargetAtTime(z, Howler.ctx.currentTime, 0.1);
|
|
1903
|
+
self.ctx.listener.upX.setTargetAtTime(xUp, Howler.ctx.currentTime, 0.1);
|
|
1904
|
+
self.ctx.listener.upY.setTargetAtTime(yUp, Howler.ctx.currentTime, 0.1);
|
|
1905
|
+
self.ctx.listener.upZ.setTargetAtTime(zUp, Howler.ctx.currentTime, 0.1);
|
|
1906
|
+
} else {
|
|
1907
|
+
self.ctx.listener.setOrientation(x, y, z, xUp, yUp, zUp);
|
|
1908
|
+
}
|
|
1909
|
+
} else {
|
|
1910
|
+
return or;
|
|
1911
|
+
}
|
|
1912
|
+
return self;
|
|
1913
|
+
};
|
|
1914
|
+
Howl.prototype.init = /* @__PURE__ */ (function(_super) {
|
|
1915
|
+
return function(o) {
|
|
1916
|
+
var self = this;
|
|
1917
|
+
self._orientation = o.orientation || [1, 0, 0];
|
|
1918
|
+
self._stereo = o.stereo || null;
|
|
1919
|
+
self._pos = o.pos || null;
|
|
1920
|
+
self._pannerAttr = {
|
|
1921
|
+
coneInnerAngle: typeof o.coneInnerAngle !== "undefined" ? o.coneInnerAngle : 360,
|
|
1922
|
+
coneOuterAngle: typeof o.coneOuterAngle !== "undefined" ? o.coneOuterAngle : 360,
|
|
1923
|
+
coneOuterGain: typeof o.coneOuterGain !== "undefined" ? o.coneOuterGain : 0,
|
|
1924
|
+
distanceModel: typeof o.distanceModel !== "undefined" ? o.distanceModel : "inverse",
|
|
1925
|
+
maxDistance: typeof o.maxDistance !== "undefined" ? o.maxDistance : 1e4,
|
|
1926
|
+
panningModel: typeof o.panningModel !== "undefined" ? o.panningModel : "HRTF",
|
|
1927
|
+
refDistance: typeof o.refDistance !== "undefined" ? o.refDistance : 1,
|
|
1928
|
+
rolloffFactor: typeof o.rolloffFactor !== "undefined" ? o.rolloffFactor : 1
|
|
1929
|
+
};
|
|
1930
|
+
self._onstereo = o.onstereo ? [{ fn: o.onstereo }] : [];
|
|
1931
|
+
self._onpos = o.onpos ? [{ fn: o.onpos }] : [];
|
|
1932
|
+
self._onorientation = o.onorientation ? [{ fn: o.onorientation }] : [];
|
|
1933
|
+
return _super.call(this, o);
|
|
1934
|
+
};
|
|
1935
|
+
})(Howl.prototype.init);
|
|
1936
|
+
Howl.prototype.stereo = function(pan, id) {
|
|
1937
|
+
var self = this;
|
|
1938
|
+
if (!self._webAudio) {
|
|
1939
|
+
return self;
|
|
1940
|
+
}
|
|
1941
|
+
if (self._state !== "loaded") {
|
|
1942
|
+
self._queue.push({
|
|
1943
|
+
event: "stereo",
|
|
1944
|
+
action: function() {
|
|
1945
|
+
self.stereo(pan, id);
|
|
1946
|
+
}
|
|
1947
|
+
});
|
|
1948
|
+
return self;
|
|
1949
|
+
}
|
|
1950
|
+
var pannerType = typeof Howler.ctx.createStereoPanner === "undefined" ? "spatial" : "stereo";
|
|
1951
|
+
if (typeof id === "undefined") {
|
|
1952
|
+
if (typeof pan === "number") {
|
|
1953
|
+
self._stereo = pan;
|
|
1954
|
+
self._pos = [pan, 0, 0];
|
|
1955
|
+
} else {
|
|
1956
|
+
return self._stereo;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
var ids = self._getSoundIds(id);
|
|
1960
|
+
for (var i = 0; i < ids.length; i++) {
|
|
1961
|
+
var sound = self._soundById(ids[i]);
|
|
1962
|
+
if (sound) {
|
|
1963
|
+
if (typeof pan === "number") {
|
|
1964
|
+
sound._stereo = pan;
|
|
1965
|
+
sound._pos = [pan, 0, 0];
|
|
1966
|
+
if (sound._node) {
|
|
1967
|
+
sound._pannerAttr.panningModel = "equalpower";
|
|
1968
|
+
if (!sound._panner || !sound._panner.pan) {
|
|
1969
|
+
setupPanner(sound, pannerType);
|
|
1970
|
+
}
|
|
1971
|
+
if (pannerType === "spatial") {
|
|
1972
|
+
if (typeof sound._panner.positionX !== "undefined") {
|
|
1973
|
+
sound._panner.positionX.setValueAtTime(pan, Howler.ctx.currentTime);
|
|
1974
|
+
sound._panner.positionY.setValueAtTime(0, Howler.ctx.currentTime);
|
|
1975
|
+
sound._panner.positionZ.setValueAtTime(0, Howler.ctx.currentTime);
|
|
1976
|
+
} else {
|
|
1977
|
+
sound._panner.setPosition(pan, 0, 0);
|
|
1978
|
+
}
|
|
1979
|
+
} else {
|
|
1980
|
+
sound._panner.pan.setValueAtTime(pan, Howler.ctx.currentTime);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
self._emit("stereo", sound._id);
|
|
1984
|
+
} else {
|
|
1985
|
+
return sound._stereo;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
return self;
|
|
1990
|
+
};
|
|
1991
|
+
Howl.prototype.pos = function(x, y, z, id) {
|
|
1992
|
+
var self = this;
|
|
1993
|
+
if (!self._webAudio) {
|
|
1994
|
+
return self;
|
|
1995
|
+
}
|
|
1996
|
+
if (self._state !== "loaded") {
|
|
1997
|
+
self._queue.push({
|
|
1998
|
+
event: "pos",
|
|
1999
|
+
action: function() {
|
|
2000
|
+
self.pos(x, y, z, id);
|
|
2001
|
+
}
|
|
2002
|
+
});
|
|
2003
|
+
return self;
|
|
2004
|
+
}
|
|
2005
|
+
y = typeof y !== "number" ? 0 : y;
|
|
2006
|
+
z = typeof z !== "number" ? -0.5 : z;
|
|
2007
|
+
if (typeof id === "undefined") {
|
|
2008
|
+
if (typeof x === "number") {
|
|
2009
|
+
self._pos = [x, y, z];
|
|
2010
|
+
} else {
|
|
2011
|
+
return self._pos;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
var ids = self._getSoundIds(id);
|
|
2015
|
+
for (var i = 0; i < ids.length; i++) {
|
|
2016
|
+
var sound = self._soundById(ids[i]);
|
|
2017
|
+
if (sound) {
|
|
2018
|
+
if (typeof x === "number") {
|
|
2019
|
+
sound._pos = [x, y, z];
|
|
2020
|
+
if (sound._node) {
|
|
2021
|
+
if (!sound._panner || sound._panner.pan) {
|
|
2022
|
+
setupPanner(sound, "spatial");
|
|
2023
|
+
}
|
|
2024
|
+
if (typeof sound._panner.positionX !== "undefined") {
|
|
2025
|
+
sound._panner.positionX.setValueAtTime(x, Howler.ctx.currentTime);
|
|
2026
|
+
sound._panner.positionY.setValueAtTime(y, Howler.ctx.currentTime);
|
|
2027
|
+
sound._panner.positionZ.setValueAtTime(z, Howler.ctx.currentTime);
|
|
2028
|
+
} else {
|
|
2029
|
+
sound._panner.setPosition(x, y, z);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
self._emit("pos", sound._id);
|
|
2033
|
+
} else {
|
|
2034
|
+
return sound._pos;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
return self;
|
|
2039
|
+
};
|
|
2040
|
+
Howl.prototype.orientation = function(x, y, z, id) {
|
|
2041
|
+
var self = this;
|
|
2042
|
+
if (!self._webAudio) {
|
|
2043
|
+
return self;
|
|
2044
|
+
}
|
|
2045
|
+
if (self._state !== "loaded") {
|
|
2046
|
+
self._queue.push({
|
|
2047
|
+
event: "orientation",
|
|
2048
|
+
action: function() {
|
|
2049
|
+
self.orientation(x, y, z, id);
|
|
2050
|
+
}
|
|
2051
|
+
});
|
|
2052
|
+
return self;
|
|
2053
|
+
}
|
|
2054
|
+
y = typeof y !== "number" ? self._orientation[1] : y;
|
|
2055
|
+
z = typeof z !== "number" ? self._orientation[2] : z;
|
|
2056
|
+
if (typeof id === "undefined") {
|
|
2057
|
+
if (typeof x === "number") {
|
|
2058
|
+
self._orientation = [x, y, z];
|
|
2059
|
+
} else {
|
|
2060
|
+
return self._orientation;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
var ids = self._getSoundIds(id);
|
|
2064
|
+
for (var i = 0; i < ids.length; i++) {
|
|
2065
|
+
var sound = self._soundById(ids[i]);
|
|
2066
|
+
if (sound) {
|
|
2067
|
+
if (typeof x === "number") {
|
|
2068
|
+
sound._orientation = [x, y, z];
|
|
2069
|
+
if (sound._node) {
|
|
2070
|
+
if (!sound._panner) {
|
|
2071
|
+
if (!sound._pos) {
|
|
2072
|
+
sound._pos = self._pos || [0, 0, -0.5];
|
|
2073
|
+
}
|
|
2074
|
+
setupPanner(sound, "spatial");
|
|
2075
|
+
}
|
|
2076
|
+
if (typeof sound._panner.orientationX !== "undefined") {
|
|
2077
|
+
sound._panner.orientationX.setValueAtTime(x, Howler.ctx.currentTime);
|
|
2078
|
+
sound._panner.orientationY.setValueAtTime(y, Howler.ctx.currentTime);
|
|
2079
|
+
sound._panner.orientationZ.setValueAtTime(z, Howler.ctx.currentTime);
|
|
2080
|
+
} else {
|
|
2081
|
+
sound._panner.setOrientation(x, y, z);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
self._emit("orientation", sound._id);
|
|
2085
|
+
} else {
|
|
2086
|
+
return sound._orientation;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
return self;
|
|
2091
|
+
};
|
|
2092
|
+
Howl.prototype.pannerAttr = function() {
|
|
2093
|
+
var self = this;
|
|
2094
|
+
var args = arguments;
|
|
2095
|
+
var o, id, sound;
|
|
2096
|
+
if (!self._webAudio) {
|
|
2097
|
+
return self;
|
|
2098
|
+
}
|
|
2099
|
+
if (args.length === 0) {
|
|
2100
|
+
return self._pannerAttr;
|
|
2101
|
+
} else if (args.length === 1) {
|
|
2102
|
+
if (typeof args[0] === "object") {
|
|
2103
|
+
o = args[0];
|
|
2104
|
+
if (typeof id === "undefined") {
|
|
2105
|
+
if (!o.pannerAttr) {
|
|
2106
|
+
o.pannerAttr = {
|
|
2107
|
+
coneInnerAngle: o.coneInnerAngle,
|
|
2108
|
+
coneOuterAngle: o.coneOuterAngle,
|
|
2109
|
+
coneOuterGain: o.coneOuterGain,
|
|
2110
|
+
distanceModel: o.distanceModel,
|
|
2111
|
+
maxDistance: o.maxDistance,
|
|
2112
|
+
refDistance: o.refDistance,
|
|
2113
|
+
rolloffFactor: o.rolloffFactor,
|
|
2114
|
+
panningModel: o.panningModel
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
self._pannerAttr = {
|
|
2118
|
+
coneInnerAngle: typeof o.pannerAttr.coneInnerAngle !== "undefined" ? o.pannerAttr.coneInnerAngle : self._coneInnerAngle,
|
|
2119
|
+
coneOuterAngle: typeof o.pannerAttr.coneOuterAngle !== "undefined" ? o.pannerAttr.coneOuterAngle : self._coneOuterAngle,
|
|
2120
|
+
coneOuterGain: typeof o.pannerAttr.coneOuterGain !== "undefined" ? o.pannerAttr.coneOuterGain : self._coneOuterGain,
|
|
2121
|
+
distanceModel: typeof o.pannerAttr.distanceModel !== "undefined" ? o.pannerAttr.distanceModel : self._distanceModel,
|
|
2122
|
+
maxDistance: typeof o.pannerAttr.maxDistance !== "undefined" ? o.pannerAttr.maxDistance : self._maxDistance,
|
|
2123
|
+
refDistance: typeof o.pannerAttr.refDistance !== "undefined" ? o.pannerAttr.refDistance : self._refDistance,
|
|
2124
|
+
rolloffFactor: typeof o.pannerAttr.rolloffFactor !== "undefined" ? o.pannerAttr.rolloffFactor : self._rolloffFactor,
|
|
2125
|
+
panningModel: typeof o.pannerAttr.panningModel !== "undefined" ? o.pannerAttr.panningModel : self._panningModel
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
} else {
|
|
2129
|
+
sound = self._soundById(parseInt(args[0], 10));
|
|
2130
|
+
return sound ? sound._pannerAttr : self._pannerAttr;
|
|
2131
|
+
}
|
|
2132
|
+
} else if (args.length === 2) {
|
|
2133
|
+
o = args[0];
|
|
2134
|
+
id = parseInt(args[1], 10);
|
|
2135
|
+
}
|
|
2136
|
+
var ids = self._getSoundIds(id);
|
|
2137
|
+
for (var i = 0; i < ids.length; i++) {
|
|
2138
|
+
sound = self._soundById(ids[i]);
|
|
2139
|
+
if (sound) {
|
|
2140
|
+
var pa = sound._pannerAttr;
|
|
2141
|
+
pa = {
|
|
2142
|
+
coneInnerAngle: typeof o.coneInnerAngle !== "undefined" ? o.coneInnerAngle : pa.coneInnerAngle,
|
|
2143
|
+
coneOuterAngle: typeof o.coneOuterAngle !== "undefined" ? o.coneOuterAngle : pa.coneOuterAngle,
|
|
2144
|
+
coneOuterGain: typeof o.coneOuterGain !== "undefined" ? o.coneOuterGain : pa.coneOuterGain,
|
|
2145
|
+
distanceModel: typeof o.distanceModel !== "undefined" ? o.distanceModel : pa.distanceModel,
|
|
2146
|
+
maxDistance: typeof o.maxDistance !== "undefined" ? o.maxDistance : pa.maxDistance,
|
|
2147
|
+
refDistance: typeof o.refDistance !== "undefined" ? o.refDistance : pa.refDistance,
|
|
2148
|
+
rolloffFactor: typeof o.rolloffFactor !== "undefined" ? o.rolloffFactor : pa.rolloffFactor,
|
|
2149
|
+
panningModel: typeof o.panningModel !== "undefined" ? o.panningModel : pa.panningModel
|
|
2150
|
+
};
|
|
2151
|
+
var panner = sound._panner;
|
|
2152
|
+
if (!panner) {
|
|
2153
|
+
if (!sound._pos) {
|
|
2154
|
+
sound._pos = self._pos || [0, 0, -0.5];
|
|
2155
|
+
}
|
|
2156
|
+
setupPanner(sound, "spatial");
|
|
2157
|
+
panner = sound._panner;
|
|
2158
|
+
}
|
|
2159
|
+
panner.coneInnerAngle = pa.coneInnerAngle;
|
|
2160
|
+
panner.coneOuterAngle = pa.coneOuterAngle;
|
|
2161
|
+
panner.coneOuterGain = pa.coneOuterGain;
|
|
2162
|
+
panner.distanceModel = pa.distanceModel;
|
|
2163
|
+
panner.maxDistance = pa.maxDistance;
|
|
2164
|
+
panner.refDistance = pa.refDistance;
|
|
2165
|
+
panner.rolloffFactor = pa.rolloffFactor;
|
|
2166
|
+
panner.panningModel = pa.panningModel;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return self;
|
|
2170
|
+
};
|
|
2171
|
+
Sound.prototype.init = /* @__PURE__ */ (function(_super) {
|
|
2172
|
+
return function() {
|
|
2173
|
+
var self = this;
|
|
2174
|
+
var parent = self._parent;
|
|
2175
|
+
self._orientation = parent._orientation;
|
|
2176
|
+
self._stereo = parent._stereo;
|
|
2177
|
+
self._pos = parent._pos;
|
|
2178
|
+
self._pannerAttr = parent._pannerAttr;
|
|
2179
|
+
_super.call(this);
|
|
2180
|
+
if (self._stereo) {
|
|
2181
|
+
parent.stereo(self._stereo);
|
|
2182
|
+
} else if (self._pos) {
|
|
2183
|
+
parent.pos(self._pos[0], self._pos[1], self._pos[2], self._id);
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
})(Sound.prototype.init);
|
|
2187
|
+
Sound.prototype.reset = /* @__PURE__ */ (function(_super) {
|
|
2188
|
+
return function() {
|
|
2189
|
+
var self = this;
|
|
2190
|
+
var parent = self._parent;
|
|
2191
|
+
self._orientation = parent._orientation;
|
|
2192
|
+
self._stereo = parent._stereo;
|
|
2193
|
+
self._pos = parent._pos;
|
|
2194
|
+
self._pannerAttr = parent._pannerAttr;
|
|
2195
|
+
if (self._stereo) {
|
|
2196
|
+
parent.stereo(self._stereo);
|
|
2197
|
+
} else if (self._pos) {
|
|
2198
|
+
parent.pos(self._pos[0], self._pos[1], self._pos[2], self._id);
|
|
2199
|
+
} else if (self._panner) {
|
|
2200
|
+
self._panner.disconnect(0);
|
|
2201
|
+
self._panner = void 0;
|
|
2202
|
+
parent._refreshBuffer(self);
|
|
2203
|
+
}
|
|
2204
|
+
return _super.call(this);
|
|
2205
|
+
};
|
|
2206
|
+
})(Sound.prototype.reset);
|
|
2207
|
+
var setupPanner = function(sound, type) {
|
|
2208
|
+
type = type || "spatial";
|
|
2209
|
+
if (type === "spatial") {
|
|
2210
|
+
sound._panner = Howler.ctx.createPanner();
|
|
2211
|
+
sound._panner.coneInnerAngle = sound._pannerAttr.coneInnerAngle;
|
|
2212
|
+
sound._panner.coneOuterAngle = sound._pannerAttr.coneOuterAngle;
|
|
2213
|
+
sound._panner.coneOuterGain = sound._pannerAttr.coneOuterGain;
|
|
2214
|
+
sound._panner.distanceModel = sound._pannerAttr.distanceModel;
|
|
2215
|
+
sound._panner.maxDistance = sound._pannerAttr.maxDistance;
|
|
2216
|
+
sound._panner.refDistance = sound._pannerAttr.refDistance;
|
|
2217
|
+
sound._panner.rolloffFactor = sound._pannerAttr.rolloffFactor;
|
|
2218
|
+
sound._panner.panningModel = sound._pannerAttr.panningModel;
|
|
2219
|
+
if (typeof sound._panner.positionX !== "undefined") {
|
|
2220
|
+
sound._panner.positionX.setValueAtTime(sound._pos[0], Howler.ctx.currentTime);
|
|
2221
|
+
sound._panner.positionY.setValueAtTime(sound._pos[1], Howler.ctx.currentTime);
|
|
2222
|
+
sound._panner.positionZ.setValueAtTime(sound._pos[2], Howler.ctx.currentTime);
|
|
2223
|
+
} else {
|
|
2224
|
+
sound._panner.setPosition(sound._pos[0], sound._pos[1], sound._pos[2]);
|
|
2225
|
+
}
|
|
2226
|
+
if (typeof sound._panner.orientationX !== "undefined") {
|
|
2227
|
+
sound._panner.orientationX.setValueAtTime(sound._orientation[0], Howler.ctx.currentTime);
|
|
2228
|
+
sound._panner.orientationY.setValueAtTime(sound._orientation[1], Howler.ctx.currentTime);
|
|
2229
|
+
sound._panner.orientationZ.setValueAtTime(sound._orientation[2], Howler.ctx.currentTime);
|
|
2230
|
+
} else {
|
|
2231
|
+
sound._panner.setOrientation(sound._orientation[0], sound._orientation[1], sound._orientation[2]);
|
|
2232
|
+
}
|
|
2233
|
+
} else {
|
|
2234
|
+
sound._panner = Howler.ctx.createStereoPanner();
|
|
2235
|
+
sound._panner.pan.setValueAtTime(sound._stereo, Howler.ctx.currentTime);
|
|
2236
|
+
}
|
|
2237
|
+
sound._panner.connect(sound._node);
|
|
2238
|
+
if (!sound._paused) {
|
|
2239
|
+
sound._parent.pause(sound._id, true).play(sound._id, true);
|
|
2240
|
+
}
|
|
2241
|
+
};
|
|
2242
|
+
})();
|
|
2243
|
+
}
|
|
2244
|
+
});
|
|
2245
|
+
|
|
2246
|
+
// src/index.ts
|
|
2247
|
+
var index_exports = {};
|
|
2248
|
+
__export(index_exports, {
|
|
2249
|
+
VERSION: () => VERSION,
|
|
2250
|
+
createStarAudio: () => createStarAudio,
|
|
2251
|
+
default: () => index_default
|
|
2252
|
+
});
|
|
2253
|
+
module.exports = __toCommonJS(index_exports);
|
|
2254
|
+
|
|
2255
|
+
// src/internals/StarAudioImpl.ts
|
|
2256
|
+
var import_howler = __toESM(require_howler(), 1);
|
|
2257
|
+
|
|
2258
|
+
// src/internals/ProceduralSynth.ts
|
|
2259
|
+
var PROCEDURAL_VOLUME_SCALE = 0.03;
|
|
2260
|
+
var PRESET_VERSION = "v34";
|
|
2261
|
+
var PRESET_DEFINITIONS = {
|
|
2262
|
+
beep: {
|
|
2263
|
+
waveform: "square",
|
|
2264
|
+
// More presence than sine
|
|
2265
|
+
frequency: [660, 880],
|
|
2266
|
+
// E5-A5 - friendly "boop boop"
|
|
2267
|
+
duration: 0.1,
|
|
2268
|
+
// Two-tone button press
|
|
2269
|
+
volume: 0.38,
|
|
2270
|
+
// Clear confirmation
|
|
2271
|
+
envelope: "percussive"
|
|
2272
|
+
},
|
|
2273
|
+
coin: {
|
|
2274
|
+
waveform: "sine",
|
|
2275
|
+
// Pure, bell-like "ting"
|
|
2276
|
+
frequency: [988, 1318, 1568, 1976],
|
|
2277
|
+
// B5-E6-G6-B6 - shimmery ascending
|
|
2278
|
+
duration: 0.16,
|
|
2279
|
+
// Quick but lets the shimmer breathe
|
|
2280
|
+
volume: 0.42,
|
|
2281
|
+
// Rewarding presence
|
|
2282
|
+
envelope: "percussive"
|
|
2283
|
+
},
|
|
2284
|
+
pickup: {
|
|
2285
|
+
waveform: "sine",
|
|
2286
|
+
// Pure, satisfying
|
|
2287
|
+
frequency: [784, 988, 1175, 1397],
|
|
2288
|
+
// G-B-D-F# - bright rising
|
|
2289
|
+
duration: 0.13,
|
|
2290
|
+
// Quick but complete
|
|
2291
|
+
volume: 0.4,
|
|
2292
|
+
// Satisfying positive feedback
|
|
2293
|
+
envelope: "percussive"
|
|
2294
|
+
},
|
|
2295
|
+
jump: {
|
|
2296
|
+
waveform: "triangle",
|
|
2297
|
+
// Warmer, less harsh than square
|
|
2298
|
+
frequency: [180, 240, 320, 450],
|
|
2299
|
+
// Quick rising arpeggio simulates "boing"
|
|
2300
|
+
duration: 0.15,
|
|
2301
|
+
// Tighter for snappier feel
|
|
2302
|
+
volume: 0.7,
|
|
2303
|
+
// 2x for satisfying bounce feedback
|
|
2304
|
+
envelope: "percussive"
|
|
2305
|
+
},
|
|
2306
|
+
hurt: {
|
|
2307
|
+
waveform: "sawtooth",
|
|
2308
|
+
// Harsh, painful quality
|
|
2309
|
+
frequency: [600, 400, 250, 150],
|
|
2310
|
+
// Sharp descending sweep - "oof!"
|
|
2311
|
+
duration: 0.25,
|
|
2312
|
+
// Quick but noticeable
|
|
2313
|
+
volume: 0.28,
|
|
2314
|
+
// Slightly more presence for feedback
|
|
2315
|
+
envelope: "percussive"
|
|
2316
|
+
},
|
|
2317
|
+
explosion: {
|
|
2318
|
+
waveform: "sawtooth",
|
|
2319
|
+
// Harsh, noise-like
|
|
2320
|
+
frequency: [
|
|
2321
|
+
800,
|
|
2322
|
+
600,
|
|
2323
|
+
450,
|
|
2324
|
+
320,
|
|
2325
|
+
220,
|
|
2326
|
+
// Initial crack/burst (fast descent)
|
|
2327
|
+
160,
|
|
2328
|
+
120,
|
|
2329
|
+
90,
|
|
2330
|
+
70,
|
|
2331
|
+
55,
|
|
2332
|
+
// Transition to rumble
|
|
2333
|
+
45,
|
|
2334
|
+
40,
|
|
2335
|
+
35,
|
|
2336
|
+
30
|
|
2337
|
+
// Deep rumble tail
|
|
2338
|
+
],
|
|
2339
|
+
// Dense frequency sweep simulates noise
|
|
2340
|
+
duration: 0.65,
|
|
2341
|
+
// Long tail for satisfying boom
|
|
2342
|
+
volume: 0.45,
|
|
2343
|
+
// Needs big presence
|
|
2344
|
+
envelope: "percussive"
|
|
2345
|
+
},
|
|
2346
|
+
powerup: {
|
|
2347
|
+
waveform: "triangle",
|
|
2348
|
+
// Warm, rich
|
|
2349
|
+
frequency: [262, 330, 392, 523, 659, 784, 1046],
|
|
2350
|
+
// C-E-G-C-E-G-C - full ascending cascade
|
|
2351
|
+
duration: 0.48,
|
|
2352
|
+
// Long celebration
|
|
2353
|
+
volume: 0.44,
|
|
2354
|
+
// Epic presence
|
|
2355
|
+
envelope: "sustained"
|
|
2356
|
+
},
|
|
2357
|
+
shoot: {
|
|
2358
|
+
waveform: "triangle",
|
|
2359
|
+
// Versatile - works for guns, bows, throws, spells
|
|
2360
|
+
frequency: [
|
|
2361
|
+
1800,
|
|
2362
|
+
1400,
|
|
2363
|
+
// Quick "release" attack (projectile launch feel)
|
|
2364
|
+
900,
|
|
2365
|
+
600,
|
|
2366
|
+
// Mid-range arc (satisfying descent)
|
|
2367
|
+
380,
|
|
2368
|
+
220
|
|
2369
|
+
// Subtle bass anchor (adds weight without being heavy)
|
|
2370
|
+
],
|
|
2371
|
+
// Universal projectile: works for bullets, arrows, fireballs, thrown items
|
|
2372
|
+
duration: 0.1,
|
|
2373
|
+
// Fast - perfect for rapid fire
|
|
2374
|
+
volume: 0.45,
|
|
2375
|
+
// Present but not overpowering
|
|
2376
|
+
envelope: "percussive"
|
|
2377
|
+
},
|
|
2378
|
+
laser: {
|
|
2379
|
+
waveform: "sawtooth",
|
|
2380
|
+
// Rich harmonics for that sci-fi "zing" bite
|
|
2381
|
+
frequency: [
|
|
2382
|
+
3200,
|
|
2383
|
+
2800,
|
|
2384
|
+
2400,
|
|
2385
|
+
// Powerful charge-up without the shrillness
|
|
2386
|
+
1800,
|
|
2387
|
+
1400,
|
|
2388
|
+
1e3,
|
|
2389
|
+
// Mid-range energy sweep (moderate)
|
|
2390
|
+
700,
|
|
2391
|
+
450,
|
|
2392
|
+
260,
|
|
2393
|
+
140
|
|
2394
|
+
// Bass impact tail (satisfying thump)
|
|
2395
|
+
],
|
|
2396
|
+
// Valve-quality energy weapon: smooth attack → powerful descent → bass punch
|
|
2397
|
+
duration: 0.18,
|
|
2398
|
+
// Slightly longer for the full sci-fi experience
|
|
2399
|
+
volume: 0.42,
|
|
2400
|
+
// Punchy presence - this is a WEAPON
|
|
2401
|
+
envelope: "percussive"
|
|
2402
|
+
},
|
|
2403
|
+
error: {
|
|
2404
|
+
waveform: "sawtooth",
|
|
2405
|
+
// Harsh, unpleasant (intentionally)
|
|
2406
|
+
frequency: [800, 500, 250, 150],
|
|
2407
|
+
// Sharp descending blast - "WRONG!"
|
|
2408
|
+
duration: 0.22,
|
|
2409
|
+
// Long enough to register as negative
|
|
2410
|
+
volume: 0.32,
|
|
2411
|
+
// Needs to be noticed
|
|
2412
|
+
envelope: "percussive"
|
|
2413
|
+
},
|
|
2414
|
+
click: {
|
|
2415
|
+
waveform: "sine",
|
|
2416
|
+
// Pure, pleasant
|
|
2417
|
+
frequency: 800,
|
|
2418
|
+
// Simple, clean tone
|
|
2419
|
+
duration: 0.05,
|
|
2420
|
+
// Quick
|
|
2421
|
+
volume: 0.7,
|
|
2422
|
+
// 2x previous (was 0.35)
|
|
2423
|
+
envelope: "percussive"
|
|
2424
|
+
},
|
|
2425
|
+
success: {
|
|
2426
|
+
waveform: "triangle",
|
|
2427
|
+
// Warm, full
|
|
2428
|
+
frequency: [
|
|
2429
|
+
784,
|
|
2430
|
+
988,
|
|
2431
|
+
1175,
|
|
2432
|
+
// G-B-D (ascending excitement - "dun dun dun")
|
|
2433
|
+
1046,
|
|
2434
|
+
1318,
|
|
2435
|
+
1568,
|
|
2436
|
+
1976,
|
|
2437
|
+
// C-E-G-B (soaring triumphant ascent)
|
|
2438
|
+
2349
|
|
2439
|
+
// D-D-D-D (sustained victorious peak)
|
|
2440
|
+
],
|
|
2441
|
+
// Victory phrase with sustained high note at end
|
|
2442
|
+
duration: 1.2,
|
|
2443
|
+
// Extended for longer sustained finale
|
|
2444
|
+
volume: 0.48,
|
|
2445
|
+
// Victorious presence
|
|
2446
|
+
envelope: "sustained"
|
|
2447
|
+
},
|
|
2448
|
+
bonus: {
|
|
2449
|
+
waveform: "sine",
|
|
2450
|
+
// Pure, magical sparkle
|
|
2451
|
+
frequency: [659, 880, 1046, 1318, 1568, 1976],
|
|
2452
|
+
// E-A-C-E-G-B - soaring ascent
|
|
2453
|
+
duration: 0.42,
|
|
2454
|
+
// Longer celebration
|
|
2455
|
+
volume: 0.5,
|
|
2456
|
+
// BIGGEST reward - most epic sound
|
|
2457
|
+
envelope: "sustained"
|
|
2458
|
+
},
|
|
2459
|
+
select: {
|
|
2460
|
+
waveform: "sine",
|
|
2461
|
+
// Pure, pleasant - not annoying when repeated
|
|
2462
|
+
frequency: [880, 1046],
|
|
2463
|
+
// A-C - gentle upward confirmation
|
|
2464
|
+
duration: 0.06,
|
|
2465
|
+
// Very quick - instant responsive feedback
|
|
2466
|
+
volume: 0.35,
|
|
2467
|
+
// Subtle - plays constantly in menus
|
|
2468
|
+
envelope: "percussive"
|
|
2469
|
+
},
|
|
2470
|
+
unlock: {
|
|
2471
|
+
waveform: "triangle",
|
|
2472
|
+
// Warm, satisfying
|
|
2473
|
+
frequency: [
|
|
2474
|
+
220,
|
|
2475
|
+
196,
|
|
2476
|
+
220,
|
|
2477
|
+
// Low "ka-chunk" mechanism (back and forth)
|
|
2478
|
+
440,
|
|
2479
|
+
659,
|
|
2480
|
+
880,
|
|
2481
|
+
1175
|
|
2482
|
+
// Rising victorious chime (A-E-A-D)
|
|
2483
|
+
],
|
|
2484
|
+
// Two-phase: mechanical lock → triumphant unlock
|
|
2485
|
+
duration: 0.35,
|
|
2486
|
+
// Long enough to savor the satisfaction
|
|
2487
|
+
volume: 0.46,
|
|
2488
|
+
// Strong presence - this is a reward moment
|
|
2489
|
+
envelope: "sustained"
|
|
2490
|
+
},
|
|
2491
|
+
swoosh: {
|
|
2492
|
+
waveform: "sine",
|
|
2493
|
+
// Pure, soft whoosh
|
|
2494
|
+
frequency: [
|
|
2495
|
+
350,
|
|
2496
|
+
600,
|
|
2497
|
+
900,
|
|
2498
|
+
1100,
|
|
2499
|
+
// Gentle rise (approaching)
|
|
2500
|
+
950,
|
|
2501
|
+
700,
|
|
2502
|
+
450,
|
|
2503
|
+
250
|
|
2504
|
+
// Smooth fall (passing by)
|
|
2505
|
+
],
|
|
2506
|
+
// Soft doppler: gentle motion arc
|
|
2507
|
+
duration: 0.15,
|
|
2508
|
+
// Smooth, unhurried
|
|
2509
|
+
volume: 0.36,
|
|
2510
|
+
// Softer - background motion
|
|
2511
|
+
envelope: "percussive"
|
|
2512
|
+
},
|
|
2513
|
+
hit: {
|
|
2514
|
+
waveform: "sawtooth",
|
|
2515
|
+
// Dense harmonics for impact texture
|
|
2516
|
+
frequency: [
|
|
2517
|
+
450,
|
|
2518
|
+
400,
|
|
2519
|
+
350,
|
|
2520
|
+
// Tight mid-range cluster (main impact body)
|
|
2521
|
+
300,
|
|
2522
|
+
250,
|
|
2523
|
+
200,
|
|
2524
|
+
// Lower cluster (weight/thump)
|
|
2525
|
+
280,
|
|
2526
|
+
340,
|
|
2527
|
+
420
|
|
2528
|
+
// Quick rising "ting" (satisfaction/success)
|
|
2529
|
+
],
|
|
2530
|
+
// Impact thump + rising satisfaction (you landed it!)
|
|
2531
|
+
duration: 0.06,
|
|
2532
|
+
// Slightly longer for satisfaction tail
|
|
2533
|
+
volume: 0.56,
|
|
2534
|
+
// Strong and rewarding
|
|
2535
|
+
envelope: "percussive"
|
|
2536
|
+
}
|
|
2537
|
+
};
|
|
2538
|
+
function isPreset(value) {
|
|
2539
|
+
return typeof value === "string" && value in PRESET_DEFINITIONS;
|
|
2540
|
+
}
|
|
2541
|
+
function isSynthDefinition(value) {
|
|
2542
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && ("waveform" in value || "frequency" in value || "duration" in value);
|
|
2543
|
+
}
|
|
2544
|
+
async function generateWavUrl(definition) {
|
|
2545
|
+
const {
|
|
2546
|
+
waveform = "square",
|
|
2547
|
+
frequency = 440,
|
|
2548
|
+
duration = 0.1,
|
|
2549
|
+
volume: presetVolume = 1,
|
|
2550
|
+
envelope = "percussive"
|
|
2551
|
+
} = definition;
|
|
2552
|
+
const sampleRate = 48e3;
|
|
2553
|
+
const numSamples = Math.floor(sampleRate * duration);
|
|
2554
|
+
const offlineCtx = new OfflineAudioContext(1, numSamples, sampleRate);
|
|
2555
|
+
const frequencies = Array.isArray(frequency) ? frequency : [frequency];
|
|
2556
|
+
const gainNode = offlineCtx.createGain();
|
|
2557
|
+
gainNode.connect(offlineCtx.destination);
|
|
2558
|
+
const scaledVolume = presetVolume * PROCEDURAL_VOLUME_SCALE;
|
|
2559
|
+
const attackTime = envelope === "percussive" ? 0.01 : 0.05;
|
|
2560
|
+
const releaseTime = envelope === "percussive" ? 0.05 : 0.1;
|
|
2561
|
+
gainNode.gain.setValueAtTime(0, 0);
|
|
2562
|
+
gainNode.gain.linearRampToValueAtTime(scaledVolume, attackTime);
|
|
2563
|
+
gainNode.gain.linearRampToValueAtTime(scaledVolume * 0.7, duration - releaseTime);
|
|
2564
|
+
gainNode.gain.linearRampToValueAtTime(0, duration);
|
|
2565
|
+
if (frequencies.length === 1) {
|
|
2566
|
+
const osc = offlineCtx.createOscillator();
|
|
2567
|
+
osc.type = waveform;
|
|
2568
|
+
osc.frequency.setValueAtTime(frequencies[0], 0);
|
|
2569
|
+
osc.connect(gainNode);
|
|
2570
|
+
osc.start(0);
|
|
2571
|
+
osc.stop(duration);
|
|
2572
|
+
} else {
|
|
2573
|
+
const timePerNote = duration / frequencies.length;
|
|
2574
|
+
frequencies.forEach((freq, index) => {
|
|
2575
|
+
const osc = offlineCtx.createOscillator();
|
|
2576
|
+
osc.type = waveform;
|
|
2577
|
+
const startTime = index * timePerNote;
|
|
2578
|
+
const endTime = startTime + timePerNote;
|
|
2579
|
+
osc.frequency.setValueAtTime(freq, startTime);
|
|
2580
|
+
osc.connect(gainNode);
|
|
2581
|
+
osc.start(startTime);
|
|
2582
|
+
osc.stop(endTime);
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
const audioBuffer = await offlineCtx.startRendering();
|
|
2586
|
+
const wavBlob = audioBufferToWav(audioBuffer);
|
|
2587
|
+
return URL.createObjectURL(wavBlob);
|
|
2588
|
+
}
|
|
2589
|
+
function audioBufferToWav(buffer) {
|
|
2590
|
+
const numChannels = buffer.numberOfChannels;
|
|
2591
|
+
const sampleRate = buffer.sampleRate;
|
|
2592
|
+
const format = 1;
|
|
2593
|
+
const bitDepth = 16;
|
|
2594
|
+
const bytesPerSample = bitDepth / 8;
|
|
2595
|
+
const blockAlign = numChannels * bytesPerSample;
|
|
2596
|
+
const data = buffer.getChannelData(0);
|
|
2597
|
+
const dataLength = data.length * bytesPerSample;
|
|
2598
|
+
const bufferLength = 44 + dataLength;
|
|
2599
|
+
const arrayBuffer = new ArrayBuffer(bufferLength);
|
|
2600
|
+
const view = new DataView(arrayBuffer);
|
|
2601
|
+
writeString(view, 0, "RIFF");
|
|
2602
|
+
view.setUint32(4, 36 + dataLength, true);
|
|
2603
|
+
writeString(view, 8, "WAVE");
|
|
2604
|
+
writeString(view, 12, "fmt ");
|
|
2605
|
+
view.setUint32(16, 16, true);
|
|
2606
|
+
view.setUint16(20, format, true);
|
|
2607
|
+
view.setUint16(22, numChannels, true);
|
|
2608
|
+
view.setUint32(24, sampleRate, true);
|
|
2609
|
+
view.setUint32(28, sampleRate * blockAlign, true);
|
|
2610
|
+
view.setUint16(32, blockAlign, true);
|
|
2611
|
+
view.setUint16(34, bitDepth, true);
|
|
2612
|
+
writeString(view, 36, "data");
|
|
2613
|
+
view.setUint32(40, dataLength, true);
|
|
2614
|
+
let offset = 44;
|
|
2615
|
+
for (let i = 0; i < data.length; i++) {
|
|
2616
|
+
const sample = Math.max(-1, Math.min(1, data[i]));
|
|
2617
|
+
const int16 = sample < 0 ? sample * 32768 : sample * 32767;
|
|
2618
|
+
view.setInt16(offset, int16, true);
|
|
2619
|
+
offset += 2;
|
|
2620
|
+
}
|
|
2621
|
+
return new Blob([arrayBuffer], { type: "audio/wav" });
|
|
2622
|
+
}
|
|
2623
|
+
function writeString(view, offset, string) {
|
|
2624
|
+
for (let i = 0; i < string.length; i++) {
|
|
2625
|
+
view.setUint8(offset + i, string.charCodeAt(i));
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
function resolveDefinition(value) {
|
|
2629
|
+
if (isPreset(value)) {
|
|
2630
|
+
return { ...PRESET_DEFINITIONS[value], _version: PRESET_VERSION };
|
|
2631
|
+
}
|
|
2632
|
+
return value;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
// src/internals/StarAudioImpl.ts
|
|
2636
|
+
var SoundHandleImpl = class {
|
|
2637
|
+
constructor(id, _howl, _soundId) {
|
|
2638
|
+
this.id = id;
|
|
2639
|
+
this._howl = _howl;
|
|
2640
|
+
this._soundId = _soundId;
|
|
2641
|
+
}
|
|
2642
|
+
get playing() {
|
|
2643
|
+
return this._howl.playing(this._soundId);
|
|
2644
|
+
}
|
|
2645
|
+
stop(at = 0) {
|
|
2646
|
+
this._howl.stop(this._soundId);
|
|
2647
|
+
}
|
|
2648
|
+
setVolume(v, _nowPlusSec = 0) {
|
|
2649
|
+
const clampedVolume = Math.max(0, Math.min(1, v));
|
|
2650
|
+
this._howl.volume(clampedVolume, this._soundId);
|
|
2651
|
+
}
|
|
2652
|
+
};
|
|
2653
|
+
var StarAudioImpl = class {
|
|
2654
|
+
constructor(options = {}) {
|
|
2655
|
+
this._state = "locked";
|
|
2656
|
+
this._sounds = /* @__PURE__ */ new Map();
|
|
2657
|
+
this._soundGroups = /* @__PURE__ */ new Map();
|
|
2658
|
+
this._soundVolumes = /* @__PURE__ */ new Map();
|
|
2659
|
+
// Per-sound volume overrides
|
|
2660
|
+
this._currentMusicId = null;
|
|
2661
|
+
this._currentMusicSoundId = null;
|
|
2662
|
+
this._unlockHandlerRemover = null;
|
|
2663
|
+
this._eventTarget = new EventTarget();
|
|
2664
|
+
this._isMuted = false;
|
|
2665
|
+
this._volumes = { music: 0.8, sfx: 0.9 };
|
|
2666
|
+
this._visibilityHandler = null;
|
|
2667
|
+
this._readyResolver = null;
|
|
2668
|
+
this._pendingFadeTimeout = null;
|
|
2669
|
+
this._generatedWavUrls = /* @__PURE__ */ new Map();
|
|
2670
|
+
this.playSound = this.play;
|
|
2671
|
+
this.setMusic = this.setMusicVolume;
|
|
2672
|
+
this.setSfx = this.setSfxVolume;
|
|
2673
|
+
this.mute = this.setMute;
|
|
2674
|
+
// --- Lifecycle ---
|
|
2675
|
+
this.pause = async () => {
|
|
2676
|
+
for (const howl of this._sounds.values()) {
|
|
2677
|
+
howl.pause();
|
|
2678
|
+
}
|
|
2679
|
+
this._state = "suspended";
|
|
2680
|
+
this._eventTarget.dispatchEvent(new Event("suspended"));
|
|
2681
|
+
};
|
|
2682
|
+
this.resume = async () => {
|
|
2683
|
+
if (this._state === "locked") {
|
|
2684
|
+
console.log("[StarAudio] Context will unlock on first user interaction");
|
|
2685
|
+
}
|
|
2686
|
+
if (this._currentMusicId && this._currentMusicSoundId !== null) {
|
|
2687
|
+
const musicHowl = this._sounds.get(this._currentMusicId);
|
|
2688
|
+
if (musicHowl && !musicHowl.playing(this._currentMusicSoundId)) {
|
|
2689
|
+
musicHowl.play(this._currentMusicSoundId);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
this._state = "running";
|
|
2693
|
+
this._eventTarget.dispatchEvent(new Event("resumed"));
|
|
2694
|
+
if (this._readyResolver) {
|
|
2695
|
+
this._readyResolver();
|
|
2696
|
+
this._readyResolver = null;
|
|
2697
|
+
}
|
|
2698
|
+
};
|
|
2699
|
+
this.unlock = this.resume;
|
|
2700
|
+
console.log(`[Star Audio v${VERSION}] Initializing...`);
|
|
2701
|
+
this._persistKey = options.persistKey ?? "star.audio.v0";
|
|
2702
|
+
this._loadState();
|
|
2703
|
+
if (options.initialMute !== void 0) {
|
|
2704
|
+
this._isMuted = options.initialMute;
|
|
2705
|
+
}
|
|
2706
|
+
if (options.initialVolumes) {
|
|
2707
|
+
if (options.initialVolumes.music !== void 0) {
|
|
2708
|
+
this._volumes.music = Math.max(0, Math.min(1, options.initialVolumes.music));
|
|
2709
|
+
}
|
|
2710
|
+
if (options.initialVolumes.sfx !== void 0) {
|
|
2711
|
+
this._volumes.sfx = Math.max(0, Math.min(1, options.initialVolumes.sfx));
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
import_howler.Howler.volume(this._isMuted ? 0 : 1);
|
|
2715
|
+
this.ready = new Promise((resolve) => {
|
|
2716
|
+
this._readyResolver = resolve;
|
|
2717
|
+
if (import_howler.Howler.ctx && import_howler.Howler.ctx.state === "running") {
|
|
2718
|
+
this._state = "running";
|
|
2719
|
+
resolve();
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2722
|
+
console.log("[StarAudio] SDK Initialized (powered by Howler.js)");
|
|
2723
|
+
if (options.unlockWith !== false) {
|
|
2724
|
+
this.attachUnlock(options.unlockWith === "auto" ? window : options.unlockWith);
|
|
2725
|
+
}
|
|
2726
|
+
if (options.suspendOnHidden ?? true) {
|
|
2727
|
+
this._visibilityHandler = () => {
|
|
2728
|
+
if (document.visibilityState === "hidden") {
|
|
2729
|
+
this.pause();
|
|
2730
|
+
} else if (document.visibilityState === "visible") {
|
|
2731
|
+
this.resume();
|
|
2732
|
+
}
|
|
2733
|
+
};
|
|
2734
|
+
document.addEventListener("visibilitychange", this._visibilityHandler);
|
|
2735
|
+
}
|
|
2736
|
+
this.music = {
|
|
2737
|
+
crossfadeTo: async (id, o = {}) => {
|
|
2738
|
+
const duration = (o.duration ?? 1) * 1e3;
|
|
2739
|
+
const loop = o.loop ?? true;
|
|
2740
|
+
const howl = this._sounds.get(id);
|
|
2741
|
+
if (!howl) {
|
|
2742
|
+
console.warn(`[StarAudio] Sound not loaded: ${id}`);
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2745
|
+
if (this._currentMusicId === id && this._currentMusicSoundId !== null) {
|
|
2746
|
+
console.log(`[StarAudio] Already playing ${id}, updating loop setting`);
|
|
2747
|
+
howl.loop(loop);
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
if (this._pendingFadeTimeout !== null) {
|
|
2751
|
+
clearTimeout(this._pendingFadeTimeout);
|
|
2752
|
+
this._pendingFadeTimeout = null;
|
|
2753
|
+
}
|
|
2754
|
+
if (this._currentMusicId && this._currentMusicSoundId !== null) {
|
|
2755
|
+
const currentHowl = this._sounds.get(this._currentMusicId);
|
|
2756
|
+
if (currentHowl) {
|
|
2757
|
+
const oldSoundId = this._currentMusicSoundId;
|
|
2758
|
+
currentHowl.fade(currentHowl.volume(), 0, duration, oldSoundId);
|
|
2759
|
+
this._pendingFadeTimeout = setTimeout(() => {
|
|
2760
|
+
currentHowl.stop(oldSoundId);
|
|
2761
|
+
this._pendingFadeTimeout = null;
|
|
2762
|
+
}, duration);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
const audioDuration = howl.duration();
|
|
2766
|
+
const safeToLoop = loop && audioDuration > 0.1;
|
|
2767
|
+
if (loop && !safeToLoop) {
|
|
2768
|
+
console.warn(`[StarAudio] \u26A0\uFE0F Audio "${id}" is too short (${audioDuration.toFixed(3)}s) to loop safely - disabling loop to prevent crash`);
|
|
2769
|
+
}
|
|
2770
|
+
howl.loop(safeToLoop);
|
|
2771
|
+
howl.volume(0);
|
|
2772
|
+
const soundId = howl.play();
|
|
2773
|
+
howl.fade(0, this._volumes.music, duration, soundId);
|
|
2774
|
+
this._currentMusicId = id;
|
|
2775
|
+
this._currentMusicSoundId = soundId;
|
|
2776
|
+
},
|
|
2777
|
+
stop: (fadeSec = 0.2) => {
|
|
2778
|
+
if (this._pendingFadeTimeout !== null) {
|
|
2779
|
+
clearTimeout(this._pendingFadeTimeout);
|
|
2780
|
+
this._pendingFadeTimeout = null;
|
|
2781
|
+
}
|
|
2782
|
+
if (this._currentMusicId && this._currentMusicSoundId !== null) {
|
|
2783
|
+
const howl = this._sounds.get(this._currentMusicId);
|
|
2784
|
+
if (howl) {
|
|
2785
|
+
const soundId = this._currentMusicSoundId;
|
|
2786
|
+
howl.fade(howl.volume(), 0, fadeSec * 1e3, soundId);
|
|
2787
|
+
this._pendingFadeTimeout = setTimeout(() => {
|
|
2788
|
+
howl.stop(soundId);
|
|
2789
|
+
this._currentMusicId = null;
|
|
2790
|
+
this._currentMusicSoundId = null;
|
|
2791
|
+
this._pendingFadeTimeout = null;
|
|
2792
|
+
}, fadeSec * 1e3);
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
},
|
|
2796
|
+
fadeTo: void 0,
|
|
2797
|
+
switchTo: void 0
|
|
2798
|
+
};
|
|
2799
|
+
this.music.fadeTo = this.music.crossfadeTo;
|
|
2800
|
+
this.music.switchTo = this.music.crossfadeTo;
|
|
2801
|
+
this.musicFadeTo = this.music.crossfadeTo;
|
|
2802
|
+
}
|
|
2803
|
+
get state() {
|
|
2804
|
+
return this._state;
|
|
2805
|
+
}
|
|
2806
|
+
// --- Asset Loading ---
|
|
2807
|
+
async preload(manifest) {
|
|
2808
|
+
const promises = [];
|
|
2809
|
+
for (const [id, value] of Object.entries(manifest)) {
|
|
2810
|
+
if (isPreset(value) || isSynthDefinition(value)) {
|
|
2811
|
+
promises.push(this._loadProceduralSound(id, value));
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
if (typeof value === "string" || Array.isArray(value)) {
|
|
2815
|
+
const strValue = Array.isArray(value) ? value[0] : value;
|
|
2816
|
+
if (typeof strValue === "string" && !strValue.includes(".") && !strValue.startsWith("http")) {
|
|
2817
|
+
console.error(`[StarAudio] \u26A0\uFE0F "${id}: '${strValue}'" looks like an invalid preset name. Valid presets: beep, click, select, jump, swoosh, shoot, laser, explosion, hit, hurt, coin, pickup, bonus, unlock, powerup, error, success. Use { synth: 'presetName' } for presets or provide a valid audio file path.`);
|
|
2818
|
+
continue;
|
|
2819
|
+
}
|
|
2820
|
+
promises.push(this.load({ id, src: value }));
|
|
2821
|
+
} else {
|
|
2822
|
+
const volume = "volume" in value ? value.volume : void 0;
|
|
2823
|
+
if (volume !== void 0) {
|
|
2824
|
+
this._soundVolumes.set(id, volume);
|
|
2825
|
+
}
|
|
2826
|
+
if ("synth" in value && value.synth) {
|
|
2827
|
+
promises.push(this._loadProceduralSound(id, value.synth, value.group));
|
|
2828
|
+
} else {
|
|
2829
|
+
promises.push(this.load({ id, ...value }));
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
const results = await Promise.allSettled(promises);
|
|
2834
|
+
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
2835
|
+
const failed = results.filter((r) => r.status === "rejected").length;
|
|
2836
|
+
if (failed > 0) {
|
|
2837
|
+
console.warn(`[StarAudio] Preload completed: ${succeeded} succeeded, ${failed} failed`);
|
|
2838
|
+
} else {
|
|
2839
|
+
console.log(`[StarAudio] Preload completed: ${succeeded} sounds loaded`);
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
async _loadProceduralSound(id, value, group) {
|
|
2843
|
+
const definition = resolveDefinition(value);
|
|
2844
|
+
const defKey = typeof value === "string" ? `${value}:${JSON.stringify(definition)}` : JSON.stringify(definition);
|
|
2845
|
+
let wavUrl = this._generatedWavUrls.get(defKey);
|
|
2846
|
+
if (!wavUrl) {
|
|
2847
|
+
wavUrl = await generateWavUrl(definition);
|
|
2848
|
+
this._generatedWavUrls.set(defKey, wavUrl);
|
|
2849
|
+
}
|
|
2850
|
+
const inferredGroup = group || this._inferGroup(id);
|
|
2851
|
+
this._soundGroups.set(id, inferredGroup);
|
|
2852
|
+
return new Promise((resolve, reject) => {
|
|
2853
|
+
const howl = new import_howler.Howl({
|
|
2854
|
+
src: [wavUrl],
|
|
2855
|
+
format: ["wav"],
|
|
2856
|
+
// CRITICAL: Tell Howler.js this is a WAV file (blob URLs have no extension)
|
|
2857
|
+
volume: this._volumes[inferredGroup],
|
|
2858
|
+
html5: true,
|
|
2859
|
+
// Use HTML5 Audio for mobile reliability and silent mode bypass
|
|
2860
|
+
onload: () => {
|
|
2861
|
+
console.log(`[StarAudio] Generated procedural sound: ${id}`);
|
|
2862
|
+
this._sounds.set(id, howl);
|
|
2863
|
+
resolve();
|
|
2864
|
+
},
|
|
2865
|
+
onloaderror: (_id, error) => {
|
|
2866
|
+
console.warn(`[StarAudio] \u26A0\uFE0F Failed to generate procedural sound "${id}" - the game will continue without this sound.`, error);
|
|
2867
|
+
this._eventTarget.dispatchEvent(new Event("error"));
|
|
2868
|
+
this._soundGroups.delete(id);
|
|
2869
|
+
resolve();
|
|
2870
|
+
}
|
|
2871
|
+
});
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
async load(config) {
|
|
2875
|
+
const { id, src, url, group } = config;
|
|
2876
|
+
const sources = src || url;
|
|
2877
|
+
if (!sources) {
|
|
2878
|
+
console.warn(`[StarAudio] No src/url provided for ${id}`);
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
const inferredGroup = group || this._inferGroup(id);
|
|
2882
|
+
this._soundGroups.set(id, inferredGroup);
|
|
2883
|
+
return new Promise((resolve, reject) => {
|
|
2884
|
+
const howl = new import_howler.Howl({
|
|
2885
|
+
src: Array.isArray(sources) ? sources : [sources],
|
|
2886
|
+
volume: this._volumes[inferredGroup],
|
|
2887
|
+
html5: true,
|
|
2888
|
+
// FORCE HTML5 AUDIO FOR MOBILE RELIABILITY
|
|
2889
|
+
onload: () => {
|
|
2890
|
+
console.log(`[StarAudio] Loaded: ${id}`);
|
|
2891
|
+
this._sounds.set(id, howl);
|
|
2892
|
+
resolve();
|
|
2893
|
+
},
|
|
2894
|
+
onloaderror: (_id, error) => {
|
|
2895
|
+
console.warn(`[StarAudio] \u26A0\uFE0F Failed to load "${id}" - the game will continue without this sound.`, error);
|
|
2896
|
+
this._eventTarget.dispatchEvent(new Event("error"));
|
|
2897
|
+
this._soundGroups.delete(id);
|
|
2898
|
+
resolve();
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
// --- Playback ---
|
|
2904
|
+
play(id, opts = {}) {
|
|
2905
|
+
if (isPreset(id) && !this._sounds.has(id)) {
|
|
2906
|
+
console.info(`[StarAudio] Auto-generating preset: ${id}`);
|
|
2907
|
+
this._loadProceduralSound(id, id).catch((err) => {
|
|
2908
|
+
console.error(`[StarAudio] Failed to generate preset ${id}:`, err);
|
|
2909
|
+
});
|
|
2910
|
+
console.warn(`[StarAudio] Preset ${id} is generating, try again in a moment`);
|
|
2911
|
+
return null;
|
|
2912
|
+
}
|
|
2913
|
+
if ((opts.src || opts.url) && !this._sounds.has(id)) {
|
|
2914
|
+
console.log(`[StarAudio] Auto-loading: ${id}`);
|
|
2915
|
+
const sources = opts.src || opts.url;
|
|
2916
|
+
const inferredGroup = opts.group || this._inferGroup(id);
|
|
2917
|
+
this._soundGroups.set(id, inferredGroup);
|
|
2918
|
+
const requestedLoop = opts.loop ?? false;
|
|
2919
|
+
const howl2 = new import_howler.Howl({
|
|
2920
|
+
src: Array.isArray(sources) ? sources : [sources],
|
|
2921
|
+
volume: opts.volume ?? this._volumes[inferredGroup],
|
|
2922
|
+
loop: false,
|
|
2923
|
+
// Set to false initially, check duration in onload
|
|
2924
|
+
rate: opts.rate ?? 1,
|
|
2925
|
+
html5: true,
|
|
2926
|
+
autoplay: true,
|
|
2927
|
+
// Auto-play as soon as loaded
|
|
2928
|
+
onload: () => {
|
|
2929
|
+
console.log(`[StarAudio] Loaded and playing: ${id}`);
|
|
2930
|
+
if (requestedLoop) {
|
|
2931
|
+
const audioDuration = howl2.duration();
|
|
2932
|
+
const safeToLoop = audioDuration > 0.1;
|
|
2933
|
+
if (!safeToLoop) {
|
|
2934
|
+
console.warn(`[StarAudio] \u26A0\uFE0F Audio "${id}" is too short (${audioDuration.toFixed(3)}s) to loop safely - disabling loop to prevent crash`);
|
|
2935
|
+
} else {
|
|
2936
|
+
howl2.loop(true);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
this._sounds.set(id, howl2);
|
|
2940
|
+
},
|
|
2941
|
+
onloaderror: (_id, error) => {
|
|
2942
|
+
console.warn(`[StarAudio] \u26A0\uFE0F Failed to auto-load "${id}" - sound will not play.`, error);
|
|
2943
|
+
this._eventTarget.dispatchEvent(new Event("error"));
|
|
2944
|
+
this._soundGroups.delete(id);
|
|
2945
|
+
}
|
|
2946
|
+
});
|
|
2947
|
+
return null;
|
|
2948
|
+
}
|
|
2949
|
+
const howl = this._sounds.get(id);
|
|
2950
|
+
if (!howl) {
|
|
2951
|
+
console.warn(`[StarAudio] Sound not loaded: ${id}. Available presets: beep, coin, pickup, jump, hurt, shoot, laser, explosion, powerup, click, success, bonus, error`);
|
|
2952
|
+
return null;
|
|
2953
|
+
}
|
|
2954
|
+
const group = this._soundGroups.get(id) || "sfx";
|
|
2955
|
+
const perSoundVolume = this._soundVolumes.get(id);
|
|
2956
|
+
const effectiveVolume = opts.volume ?? perSoundVolume ?? this._volumes[group];
|
|
2957
|
+
if (effectiveVolume !== void 0) {
|
|
2958
|
+
howl.volume(effectiveVolume);
|
|
2959
|
+
}
|
|
2960
|
+
if (opts.loop !== void 0) {
|
|
2961
|
+
const audioDuration = howl.duration();
|
|
2962
|
+
const safeToLoop = opts.loop && audioDuration > 0.1;
|
|
2963
|
+
if (opts.loop && !safeToLoop) {
|
|
2964
|
+
console.warn(`[StarAudio] \u26A0\uFE0F Audio "${id}" is too short (${audioDuration.toFixed(3)}s) to loop safely - disabling loop to prevent crash`);
|
|
2965
|
+
}
|
|
2966
|
+
howl.loop(safeToLoop);
|
|
2967
|
+
}
|
|
2968
|
+
if (opts.rate !== void 0) {
|
|
2969
|
+
howl.rate(opts.rate);
|
|
2970
|
+
}
|
|
2971
|
+
try {
|
|
2972
|
+
const soundId = howl.play();
|
|
2973
|
+
const group2 = this._soundGroups.get(id) || "sfx";
|
|
2974
|
+
if (group2 === "sfx") {
|
|
2975
|
+
}
|
|
2976
|
+
return new SoundHandleImpl(id, howl, soundId);
|
|
2977
|
+
} catch (error) {
|
|
2978
|
+
console.warn(`[StarAudio] Failed to play ${id}:`, error);
|
|
2979
|
+
this._eventTarget.dispatchEvent(new Event("error"));
|
|
2980
|
+
return null;
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
// --- Volume & Mute ---
|
|
2984
|
+
setMusicVolume(v, _o = {}) {
|
|
2985
|
+
const clampedVolume = Math.max(0, Math.min(1, v));
|
|
2986
|
+
this._volumes.music = clampedVolume;
|
|
2987
|
+
for (const [id, howl] of this._sounds.entries()) {
|
|
2988
|
+
if (this._soundGroups.get(id) === "music") {
|
|
2989
|
+
howl.volume(clampedVolume);
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
this._saveState();
|
|
2993
|
+
}
|
|
2994
|
+
setSfxVolume(v, _o = {}) {
|
|
2995
|
+
const clampedVolume = Math.max(0, Math.min(1, v));
|
|
2996
|
+
this._volumes.sfx = clampedVolume;
|
|
2997
|
+
for (const [id, howl] of this._sounds.entries()) {
|
|
2998
|
+
if (this._soundGroups.get(id) === "sfx") {
|
|
2999
|
+
howl.volume(clampedVolume);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
this._saveState();
|
|
3003
|
+
}
|
|
3004
|
+
setMute(muted) {
|
|
3005
|
+
this._isMuted = muted;
|
|
3006
|
+
import_howler.Howler.volume(muted ? 0 : 1);
|
|
3007
|
+
this._saveState();
|
|
3008
|
+
}
|
|
3009
|
+
toggleMute() {
|
|
3010
|
+
this.setMute(!this._isMuted);
|
|
3011
|
+
}
|
|
3012
|
+
isMuted() {
|
|
3013
|
+
return this._isMuted;
|
|
3014
|
+
}
|
|
3015
|
+
attachUnlock(target) {
|
|
3016
|
+
if (this._unlockHandlerRemover) {
|
|
3017
|
+
this._unlockHandlerRemover();
|
|
3018
|
+
this._unlockHandlerRemover = null;
|
|
3019
|
+
}
|
|
3020
|
+
let effectiveTarget = target || window;
|
|
3021
|
+
if (effectiveTarget === window && window.self !== window.top) {
|
|
3022
|
+
console.log("[StarAudio] Detected iframe context, attaching unlock to document");
|
|
3023
|
+
effectiveTarget = document;
|
|
3024
|
+
}
|
|
3025
|
+
if (!effectiveTarget) return;
|
|
3026
|
+
const handler = () => {
|
|
3027
|
+
console.log("[StarAudio] User interaction detected");
|
|
3028
|
+
this._unlock();
|
|
3029
|
+
};
|
|
3030
|
+
const events = ["pointerdown", "touchstart", "keydown", "click"];
|
|
3031
|
+
events.forEach((eventType) => {
|
|
3032
|
+
effectiveTarget.addEventListener(eventType, handler, { once: true, capture: true });
|
|
3033
|
+
});
|
|
3034
|
+
this._unlockHandlerRemover = () => {
|
|
3035
|
+
events.forEach((eventType) => {
|
|
3036
|
+
effectiveTarget.removeEventListener(eventType, handler, { capture: true });
|
|
3037
|
+
});
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
// --- Events ---
|
|
3041
|
+
on(evt, cb) {
|
|
3042
|
+
this._eventTarget.addEventListener(evt, cb);
|
|
3043
|
+
}
|
|
3044
|
+
off(evt, cb) {
|
|
3045
|
+
this._eventTarget.removeEventListener(evt, cb);
|
|
3046
|
+
}
|
|
3047
|
+
// --- Cleanup ---
|
|
3048
|
+
destroy() {
|
|
3049
|
+
if (this._unlockHandlerRemover) {
|
|
3050
|
+
this._unlockHandlerRemover();
|
|
3051
|
+
}
|
|
3052
|
+
if (this._visibilityHandler) {
|
|
3053
|
+
document.removeEventListener("visibilitychange", this._visibilityHandler);
|
|
3054
|
+
}
|
|
3055
|
+
if (this._pendingFadeTimeout !== null) {
|
|
3056
|
+
clearTimeout(this._pendingFadeTimeout);
|
|
3057
|
+
this._pendingFadeTimeout = null;
|
|
3058
|
+
}
|
|
3059
|
+
for (const howl of this._sounds.values()) {
|
|
3060
|
+
howl.unload();
|
|
3061
|
+
}
|
|
3062
|
+
this._sounds.clear();
|
|
3063
|
+
for (const url of this._generatedWavUrls.values()) {
|
|
3064
|
+
URL.revokeObjectURL(url);
|
|
3065
|
+
}
|
|
3066
|
+
this._generatedWavUrls.clear();
|
|
3067
|
+
}
|
|
3068
|
+
// --- Private Methods ---
|
|
3069
|
+
_unlock() {
|
|
3070
|
+
if (this._state === "running") return;
|
|
3071
|
+
this._state = "running";
|
|
3072
|
+
this._eventTarget.dispatchEvent(new Event("unlocked"));
|
|
3073
|
+
console.log("[StarAudio] \u2705 Unlocked (Howler handling audio context)");
|
|
3074
|
+
if (this._readyResolver) {
|
|
3075
|
+
this._readyResolver();
|
|
3076
|
+
this._readyResolver = null;
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
_inferGroup(id) {
|
|
3080
|
+
const lower = id.toLowerCase();
|
|
3081
|
+
if (lower.startsWith("music.") || lower.startsWith("bgm.")) {
|
|
3082
|
+
return "music";
|
|
3083
|
+
}
|
|
3084
|
+
return "sfx";
|
|
3085
|
+
}
|
|
3086
|
+
_loadState() {
|
|
3087
|
+
try {
|
|
3088
|
+
const stored = localStorage.getItem(this._persistKey);
|
|
3089
|
+
if (stored) {
|
|
3090
|
+
const state = JSON.parse(stored);
|
|
3091
|
+
this._isMuted = state.mute;
|
|
3092
|
+
this._volumes = state.volumes;
|
|
3093
|
+
}
|
|
3094
|
+
} catch (e) {
|
|
3095
|
+
console.warn("[StarAudio] Could not load persisted state.");
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
_saveState() {
|
|
3099
|
+
try {
|
|
3100
|
+
const state = {
|
|
3101
|
+
mute: this._isMuted,
|
|
3102
|
+
volumes: this._volumes
|
|
3103
|
+
};
|
|
3104
|
+
localStorage.setItem(this._persistKey, JSON.stringify(state));
|
|
3105
|
+
} catch (e) {
|
|
3106
|
+
console.warn("[StarAudio] Could not save persisted state.");
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
};
|
|
3110
|
+
|
|
3111
|
+
// src/index.ts
|
|
3112
|
+
var VERSION = "0.1.0";
|
|
3113
|
+
function createStarAudio(opts) {
|
|
3114
|
+
if (typeof window === "undefined" || !(window.AudioContext || window.webkitAudioContext)) {
|
|
3115
|
+
throw new Error("[StarAudio] createStarAudio must be called in a browser (no AudioContext).");
|
|
3116
|
+
}
|
|
3117
|
+
return new StarAudioImpl(opts);
|
|
3118
|
+
}
|
|
3119
|
+
var index_default = createStarAudio;
|
|
3120
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3121
|
+
0 && (module.exports = {
|
|
3122
|
+
VERSION,
|
|
3123
|
+
createStarAudio
|
|
3124
|
+
});
|
|
3125
|
+
/*! Bundled license information:
|
|
3126
|
+
|
|
3127
|
+
howler/dist/howler.js:
|
|
3128
|
+
(*!
|
|
3129
|
+
* howler.js v2.2.4
|
|
3130
|
+
* howlerjs.com
|
|
3131
|
+
*
|
|
3132
|
+
* (c) 2013-2020, James Simpson of GoldFire Studios
|
|
3133
|
+
* goldfirestudios.com
|
|
3134
|
+
*
|
|
3135
|
+
* MIT License
|
|
3136
|
+
*)
|
|
3137
|
+
(*!
|
|
3138
|
+
* Spatial Plugin - Adds support for stereo and 3D audio where Web Audio is supported.
|
|
3139
|
+
*
|
|
3140
|
+
* howler.js v2.2.4
|
|
3141
|
+
* howlerjs.com
|
|
3142
|
+
*
|
|
3143
|
+
* (c) 2013-2020, James Simpson of GoldFire Studios
|
|
3144
|
+
* goldfirestudios.com
|
|
3145
|
+
*
|
|
3146
|
+
* MIT License
|
|
3147
|
+
*)
|
|
3148
|
+
*/
|
|
3149
|
+
//# sourceMappingURL=index.js.map
|