@mingxy/ocosay 1.0.20 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.js CHANGED
@@ -1,3 +1,2247 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
+ }) : x)(function(x) {
10
+ if (typeof require !== "undefined") return require.apply(this, arguments);
11
+ throw Error('Dynamic require of "' + x + '" is not supported');
12
+ });
13
+ var __commonJS = (cb, mod) => function __require2() {
14
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+
33
+ // node_modules/howler/dist/howler.js
34
+ var require_howler = __commonJS({
35
+ "node_modules/howler/dist/howler.js"(exports) {
36
+ (function() {
37
+ "use strict";
38
+ var HowlerGlobal2 = function() {
39
+ this.init();
40
+ };
41
+ HowlerGlobal2.prototype = {
42
+ /**
43
+ * Initialize the global Howler object.
44
+ * @return {Howler}
45
+ */
46
+ init: function() {
47
+ var self = this || Howler2;
48
+ self._counter = 1e3;
49
+ self._html5AudioPool = [];
50
+ self.html5PoolSize = 10;
51
+ self._codecs = {};
52
+ self._howls = [];
53
+ self._muted = false;
54
+ self._volume = 1;
55
+ self._canPlayEvent = "canplaythrough";
56
+ self._navigator = typeof window !== "undefined" && window.navigator ? window.navigator : null;
57
+ self.masterGain = null;
58
+ self.noAudio = false;
59
+ self.usingWebAudio = true;
60
+ self.autoSuspend = true;
61
+ self.ctx = null;
62
+ self.autoUnlock = true;
63
+ self._setup();
64
+ return self;
65
+ },
66
+ /**
67
+ * Get/set the global volume for all sounds.
68
+ * @param {Float} vol Volume from 0.0 to 1.0.
69
+ * @return {Howler/Float} Returns self or current volume.
70
+ */
71
+ volume: function(vol) {
72
+ var self = this || Howler2;
73
+ vol = parseFloat(vol);
74
+ if (!self.ctx) {
75
+ setupAudioContext();
76
+ }
77
+ if (typeof vol !== "undefined" && vol >= 0 && vol <= 1) {
78
+ self._volume = vol;
79
+ if (self._muted) {
80
+ return self;
81
+ }
82
+ if (self.usingWebAudio) {
83
+ self.masterGain.gain.setValueAtTime(vol, Howler2.ctx.currentTime);
84
+ }
85
+ for (var i = 0; i < self._howls.length; i++) {
86
+ if (!self._howls[i]._webAudio) {
87
+ var ids = self._howls[i]._getSoundIds();
88
+ for (var j = 0; j < ids.length; j++) {
89
+ var sound = self._howls[i]._soundById(ids[j]);
90
+ if (sound && sound._node) {
91
+ sound._node.volume = sound._volume * vol;
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return self;
97
+ }
98
+ return self._volume;
99
+ },
100
+ /**
101
+ * Handle muting and unmuting globally.
102
+ * @param {Boolean} muted Is muted or not.
103
+ */
104
+ mute: function(muted) {
105
+ var self = this || Howler2;
106
+ if (!self.ctx) {
107
+ setupAudioContext();
108
+ }
109
+ self._muted = muted;
110
+ if (self.usingWebAudio) {
111
+ self.masterGain.gain.setValueAtTime(muted ? 0 : self._volume, Howler2.ctx.currentTime);
112
+ }
113
+ for (var i = 0; i < self._howls.length; i++) {
114
+ if (!self._howls[i]._webAudio) {
115
+ var ids = self._howls[i]._getSoundIds();
116
+ for (var j = 0; j < ids.length; j++) {
117
+ var sound = self._howls[i]._soundById(ids[j]);
118
+ if (sound && sound._node) {
119
+ sound._node.muted = muted ? true : sound._muted;
120
+ }
121
+ }
122
+ }
123
+ }
124
+ return self;
125
+ },
126
+ /**
127
+ * Handle stopping all sounds globally.
128
+ */
129
+ stop: function() {
130
+ var self = this || Howler2;
131
+ for (var i = 0; i < self._howls.length; i++) {
132
+ self._howls[i].stop();
133
+ }
134
+ return self;
135
+ },
136
+ /**
137
+ * Unload and destroy all currently loaded Howl objects.
138
+ * @return {Howler}
139
+ */
140
+ unload: function() {
141
+ var self = this || Howler2;
142
+ for (var i = self._howls.length - 1; i >= 0; i--) {
143
+ self._howls[i].unload();
144
+ }
145
+ if (self.usingWebAudio && self.ctx && typeof self.ctx.close !== "undefined") {
146
+ self.ctx.close();
147
+ self.ctx = null;
148
+ setupAudioContext();
149
+ }
150
+ return self;
151
+ },
152
+ /**
153
+ * Check for codec support of specific extension.
154
+ * @param {String} ext Audio file extention.
155
+ * @return {Boolean}
156
+ */
157
+ codecs: function(ext) {
158
+ return (this || Howler2)._codecs[ext.replace(/^x-/, "")];
159
+ },
160
+ /**
161
+ * Setup various state values for global tracking.
162
+ * @return {Howler}
163
+ */
164
+ _setup: function() {
165
+ var self = this || Howler2;
166
+ self.state = self.ctx ? self.ctx.state || "suspended" : "suspended";
167
+ self._autoSuspend();
168
+ if (!self.usingWebAudio) {
169
+ if (typeof Audio !== "undefined") {
170
+ try {
171
+ var test = new Audio();
172
+ if (typeof test.oncanplaythrough === "undefined") {
173
+ self._canPlayEvent = "canplay";
174
+ }
175
+ } catch (e) {
176
+ self.noAudio = true;
177
+ }
178
+ } else {
179
+ self.noAudio = true;
180
+ }
181
+ }
182
+ try {
183
+ var test = new Audio();
184
+ if (test.muted) {
185
+ self.noAudio = true;
186
+ }
187
+ } catch (e) {
188
+ }
189
+ if (!self.noAudio) {
190
+ self._setupCodecs();
191
+ }
192
+ return self;
193
+ },
194
+ /**
195
+ * Check for browser support for various codecs and cache the results.
196
+ * @return {Howler}
197
+ */
198
+ _setupCodecs: function() {
199
+ var self = this || Howler2;
200
+ var audioTest = null;
201
+ try {
202
+ audioTest = typeof Audio !== "undefined" ? new Audio() : null;
203
+ } catch (err) {
204
+ return self;
205
+ }
206
+ if (!audioTest || typeof audioTest.canPlayType !== "function") {
207
+ return self;
208
+ }
209
+ var mpegTest = audioTest.canPlayType("audio/mpeg;").replace(/^no$/, "");
210
+ var ua = self._navigator ? self._navigator.userAgent : "";
211
+ var checkOpera = ua.match(/OPR\/(\d+)/g);
212
+ var isOldOpera = checkOpera && parseInt(checkOpera[0].split("/")[1], 10) < 33;
213
+ var checkSafari = ua.indexOf("Safari") !== -1 && ua.indexOf("Chrome") === -1;
214
+ var safariVersion = ua.match(/Version\/(.*?) /);
215
+ var isOldSafari = checkSafari && safariVersion && parseInt(safariVersion[1], 10) < 15;
216
+ self._codecs = {
217
+ mp3: !!(!isOldOpera && (mpegTest || audioTest.canPlayType("audio/mp3;").replace(/^no$/, ""))),
218
+ mpeg: !!mpegTest,
219
+ opus: !!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ""),
220
+ ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ""),
221
+ oga: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ""),
222
+ wav: !!(audioTest.canPlayType('audio/wav; codecs="1"') || audioTest.canPlayType("audio/wav")).replace(/^no$/, ""),
223
+ aac: !!audioTest.canPlayType("audio/aac;").replace(/^no$/, ""),
224
+ caf: !!audioTest.canPlayType("audio/x-caf;").replace(/^no$/, ""),
225
+ m4a: !!(audioTest.canPlayType("audio/x-m4a;") || audioTest.canPlayType("audio/m4a;") || audioTest.canPlayType("audio/aac;")).replace(/^no$/, ""),
226
+ m4b: !!(audioTest.canPlayType("audio/x-m4b;") || audioTest.canPlayType("audio/m4b;") || audioTest.canPlayType("audio/aac;")).replace(/^no$/, ""),
227
+ mp4: !!(audioTest.canPlayType("audio/x-mp4;") || audioTest.canPlayType("audio/mp4;") || audioTest.canPlayType("audio/aac;")).replace(/^no$/, ""),
228
+ weba: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, "")),
229
+ webm: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, "")),
230
+ dolby: !!audioTest.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/, ""),
231
+ flac: !!(audioTest.canPlayType("audio/x-flac;") || audioTest.canPlayType("audio/flac;")).replace(/^no$/, "")
232
+ };
233
+ return self;
234
+ },
235
+ /**
236
+ * Some browsers/devices will only allow audio to be played after a user interaction.
237
+ * Attempt to automatically unlock audio on the first user interaction.
238
+ * Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/
239
+ * @return {Howler}
240
+ */
241
+ _unlockAudio: function() {
242
+ var self = this || Howler2;
243
+ if (self._audioUnlocked || !self.ctx) {
244
+ return;
245
+ }
246
+ self._audioUnlocked = false;
247
+ self.autoUnlock = false;
248
+ if (!self._mobileUnloaded && self.ctx.sampleRate !== 44100) {
249
+ self._mobileUnloaded = true;
250
+ self.unload();
251
+ }
252
+ self._scratchBuffer = self.ctx.createBuffer(1, 1, 22050);
253
+ var unlock = function(e) {
254
+ while (self._html5AudioPool.length < self.html5PoolSize) {
255
+ try {
256
+ var audioNode = new Audio();
257
+ audioNode._unlocked = true;
258
+ self._releaseHtml5Audio(audioNode);
259
+ } catch (e2) {
260
+ self.noAudio = true;
261
+ break;
262
+ }
263
+ }
264
+ for (var i = 0; i < self._howls.length; i++) {
265
+ if (!self._howls[i]._webAudio) {
266
+ var ids = self._howls[i]._getSoundIds();
267
+ for (var j = 0; j < ids.length; j++) {
268
+ var sound = self._howls[i]._soundById(ids[j]);
269
+ if (sound && sound._node && !sound._node._unlocked) {
270
+ sound._node._unlocked = true;
271
+ sound._node.load();
272
+ }
273
+ }
274
+ }
275
+ }
276
+ self._autoResume();
277
+ var source = self.ctx.createBufferSource();
278
+ source.buffer = self._scratchBuffer;
279
+ source.connect(self.ctx.destination);
280
+ if (typeof source.start === "undefined") {
281
+ source.noteOn(0);
282
+ } else {
283
+ source.start(0);
284
+ }
285
+ if (typeof self.ctx.resume === "function") {
286
+ self.ctx.resume();
287
+ }
288
+ source.onended = function() {
289
+ source.disconnect(0);
290
+ self._audioUnlocked = true;
291
+ document.removeEventListener("touchstart", unlock, true);
292
+ document.removeEventListener("touchend", unlock, true);
293
+ document.removeEventListener("click", unlock, true);
294
+ document.removeEventListener("keydown", unlock, true);
295
+ for (var i2 = 0; i2 < self._howls.length; i2++) {
296
+ self._howls[i2]._emit("unlock");
297
+ }
298
+ };
299
+ };
300
+ document.addEventListener("touchstart", unlock, true);
301
+ document.addEventListener("touchend", unlock, true);
302
+ document.addEventListener("click", unlock, true);
303
+ document.addEventListener("keydown", unlock, true);
304
+ return self;
305
+ },
306
+ /**
307
+ * Get an unlocked HTML5 Audio object from the pool. If none are left,
308
+ * return a new Audio object and throw a warning.
309
+ * @return {Audio} HTML5 Audio object.
310
+ */
311
+ _obtainHtml5Audio: function() {
312
+ var self = this || Howler2;
313
+ if (self._html5AudioPool.length) {
314
+ return self._html5AudioPool.pop();
315
+ }
316
+ var testPlay = new Audio().play();
317
+ if (testPlay && typeof Promise !== "undefined" && (testPlay instanceof Promise || typeof testPlay.then === "function")) {
318
+ testPlay.catch(function() {
319
+ console.warn("HTML5 Audio pool exhausted, returning potentially locked audio object.");
320
+ });
321
+ }
322
+ return new Audio();
323
+ },
324
+ /**
325
+ * Return an activated HTML5 Audio object to the pool.
326
+ * @return {Howler}
327
+ */
328
+ _releaseHtml5Audio: function(audio) {
329
+ var self = this || Howler2;
330
+ if (audio._unlocked) {
331
+ self._html5AudioPool.push(audio);
332
+ }
333
+ return self;
334
+ },
335
+ /**
336
+ * Automatically suspend the Web Audio AudioContext after no sound has played for 30 seconds.
337
+ * This saves processing/energy and fixes various browser-specific bugs with audio getting stuck.
338
+ * @return {Howler}
339
+ */
340
+ _autoSuspend: function() {
341
+ var self = this;
342
+ if (!self.autoSuspend || !self.ctx || typeof self.ctx.suspend === "undefined" || !Howler2.usingWebAudio) {
343
+ return;
344
+ }
345
+ for (var i = 0; i < self._howls.length; i++) {
346
+ if (self._howls[i]._webAudio) {
347
+ for (var j = 0; j < self._howls[i]._sounds.length; j++) {
348
+ if (!self._howls[i]._sounds[j]._paused) {
349
+ return self;
350
+ }
351
+ }
352
+ }
353
+ }
354
+ if (self._suspendTimer) {
355
+ clearTimeout(self._suspendTimer);
356
+ }
357
+ self._suspendTimer = setTimeout(function() {
358
+ if (!self.autoSuspend) {
359
+ return;
360
+ }
361
+ self._suspendTimer = null;
362
+ self.state = "suspending";
363
+ var handleSuspension = function() {
364
+ self.state = "suspended";
365
+ if (self._resumeAfterSuspend) {
366
+ delete self._resumeAfterSuspend;
367
+ self._autoResume();
368
+ }
369
+ };
370
+ self.ctx.suspend().then(handleSuspension, handleSuspension);
371
+ }, 3e4);
372
+ return self;
373
+ },
374
+ /**
375
+ * Automatically resume the Web Audio AudioContext when a new sound is played.
376
+ * @return {Howler}
377
+ */
378
+ _autoResume: function() {
379
+ var self = this;
380
+ if (!self.ctx || typeof self.ctx.resume === "undefined" || !Howler2.usingWebAudio) {
381
+ return;
382
+ }
383
+ if (self.state === "running" && self.ctx.state !== "interrupted" && self._suspendTimer) {
384
+ clearTimeout(self._suspendTimer);
385
+ self._suspendTimer = null;
386
+ } else if (self.state === "suspended" || self.state === "running" && self.ctx.state === "interrupted") {
387
+ self.ctx.resume().then(function() {
388
+ self.state = "running";
389
+ for (var i = 0; i < self._howls.length; i++) {
390
+ self._howls[i]._emit("resume");
391
+ }
392
+ });
393
+ if (self._suspendTimer) {
394
+ clearTimeout(self._suspendTimer);
395
+ self._suspendTimer = null;
396
+ }
397
+ } else if (self.state === "suspending") {
398
+ self._resumeAfterSuspend = true;
399
+ }
400
+ return self;
401
+ }
402
+ };
403
+ var Howler2 = new HowlerGlobal2();
404
+ var Howl3 = function(o) {
405
+ var self = this;
406
+ if (!o.src || o.src.length === 0) {
407
+ console.error("An array of source files must be passed with any new Howl.");
408
+ return;
409
+ }
410
+ self.init(o);
411
+ };
412
+ Howl3.prototype = {
413
+ /**
414
+ * Initialize a new Howl group object.
415
+ * @param {Object} o Passed in properties for this group.
416
+ * @return {Howl}
417
+ */
418
+ init: function(o) {
419
+ var self = this;
420
+ if (!Howler2.ctx) {
421
+ setupAudioContext();
422
+ }
423
+ self._autoplay = o.autoplay || false;
424
+ self._format = typeof o.format !== "string" ? o.format : [o.format];
425
+ self._html5 = o.html5 || false;
426
+ self._muted = o.mute || false;
427
+ self._loop = o.loop || false;
428
+ self._pool = o.pool || 5;
429
+ self._preload = typeof o.preload === "boolean" || o.preload === "metadata" ? o.preload : true;
430
+ self._rate = o.rate || 1;
431
+ self._sprite = o.sprite || {};
432
+ self._src = typeof o.src !== "string" ? o.src : [o.src];
433
+ self._volume = o.volume !== void 0 ? o.volume : 1;
434
+ self._xhr = {
435
+ method: o.xhr && o.xhr.method ? o.xhr.method : "GET",
436
+ headers: o.xhr && o.xhr.headers ? o.xhr.headers : null,
437
+ withCredentials: o.xhr && o.xhr.withCredentials ? o.xhr.withCredentials : false
438
+ };
439
+ self._duration = 0;
440
+ self._state = "unloaded";
441
+ self._sounds = [];
442
+ self._endTimers = {};
443
+ self._queue = [];
444
+ self._playLock = false;
445
+ self._onend = o.onend ? [{ fn: o.onend }] : [];
446
+ self._onfade = o.onfade ? [{ fn: o.onfade }] : [];
447
+ self._onload = o.onload ? [{ fn: o.onload }] : [];
448
+ self._onloaderror = o.onloaderror ? [{ fn: o.onloaderror }] : [];
449
+ self._onplayerror = o.onplayerror ? [{ fn: o.onplayerror }] : [];
450
+ self._onpause = o.onpause ? [{ fn: o.onpause }] : [];
451
+ self._onplay = o.onplay ? [{ fn: o.onplay }] : [];
452
+ self._onstop = o.onstop ? [{ fn: o.onstop }] : [];
453
+ self._onmute = o.onmute ? [{ fn: o.onmute }] : [];
454
+ self._onvolume = o.onvolume ? [{ fn: o.onvolume }] : [];
455
+ self._onrate = o.onrate ? [{ fn: o.onrate }] : [];
456
+ self._onseek = o.onseek ? [{ fn: o.onseek }] : [];
457
+ self._onunlock = o.onunlock ? [{ fn: o.onunlock }] : [];
458
+ self._onresume = [];
459
+ self._webAudio = Howler2.usingWebAudio && !self._html5;
460
+ if (typeof Howler2.ctx !== "undefined" && Howler2.ctx && Howler2.autoUnlock) {
461
+ Howler2._unlockAudio();
462
+ }
463
+ Howler2._howls.push(self);
464
+ if (self._autoplay) {
465
+ self._queue.push({
466
+ event: "play",
467
+ action: function() {
468
+ self.play();
469
+ }
470
+ });
471
+ }
472
+ if (self._preload && self._preload !== "none") {
473
+ self.load();
474
+ }
475
+ return self;
476
+ },
477
+ /**
478
+ * Load the audio file.
479
+ * @return {Howler}
480
+ */
481
+ load: function() {
482
+ var self = this;
483
+ var url = null;
484
+ if (Howler2.noAudio) {
485
+ self._emit("loaderror", null, "No audio support.");
486
+ return;
487
+ }
488
+ if (typeof self._src === "string") {
489
+ self._src = [self._src];
490
+ }
491
+ for (var i = 0; i < self._src.length; i++) {
492
+ var ext, str;
493
+ if (self._format && self._format[i]) {
494
+ ext = self._format[i];
495
+ } else {
496
+ str = self._src[i];
497
+ if (typeof str !== "string") {
498
+ self._emit("loaderror", null, "Non-string found in selected audio sources - ignoring.");
499
+ continue;
500
+ }
501
+ ext = /^data:audio\/([^;,]+);/i.exec(str);
502
+ if (!ext) {
503
+ ext = /\.([^.]+)$/.exec(str.split("?", 1)[0]);
504
+ }
505
+ if (ext) {
506
+ ext = ext[1].toLowerCase();
507
+ }
508
+ }
509
+ if (!ext) {
510
+ console.warn('No file extension was found. Consider using the "format" property or specify an extension.');
511
+ }
512
+ if (ext && Howler2.codecs(ext)) {
513
+ url = self._src[i];
514
+ break;
515
+ }
516
+ }
517
+ if (!url) {
518
+ self._emit("loaderror", null, "No codec support for selected audio sources.");
519
+ return;
520
+ }
521
+ self._src = url;
522
+ self._state = "loading";
523
+ if (window.location.protocol === "https:" && url.slice(0, 5) === "http:") {
524
+ self._html5 = true;
525
+ self._webAudio = false;
526
+ }
527
+ new Sound2(self);
528
+ if (self._webAudio) {
529
+ loadBuffer(self);
530
+ }
531
+ return self;
532
+ },
533
+ /**
534
+ * Play a sound or resume previous playback.
535
+ * @param {String/Number} sprite Sprite name for sprite playback or sound id to continue previous.
536
+ * @param {Boolean} internal Internal Use: true prevents event firing.
537
+ * @return {Number} Sound ID.
538
+ */
539
+ play: function(sprite, internal) {
540
+ var self = this;
541
+ var id = null;
542
+ if (typeof sprite === "number") {
543
+ id = sprite;
544
+ sprite = null;
545
+ } else if (typeof sprite === "string" && self._state === "loaded" && !self._sprite[sprite]) {
546
+ return null;
547
+ } else if (typeof sprite === "undefined") {
548
+ sprite = "__default";
549
+ if (!self._playLock) {
550
+ var num = 0;
551
+ for (var i = 0; i < self._sounds.length; i++) {
552
+ if (self._sounds[i]._paused && !self._sounds[i]._ended) {
553
+ num++;
554
+ id = self._sounds[i]._id;
555
+ }
556
+ }
557
+ if (num === 1) {
558
+ sprite = null;
559
+ } else {
560
+ id = null;
561
+ }
562
+ }
563
+ }
564
+ var sound = id ? self._soundById(id) : self._inactiveSound();
565
+ if (!sound) {
566
+ return null;
567
+ }
568
+ if (id && !sprite) {
569
+ sprite = sound._sprite || "__default";
570
+ }
571
+ if (self._state !== "loaded") {
572
+ sound._sprite = sprite;
573
+ sound._ended = false;
574
+ var soundId = sound._id;
575
+ self._queue.push({
576
+ event: "play",
577
+ action: function() {
578
+ self.play(soundId);
579
+ }
580
+ });
581
+ return soundId;
582
+ }
583
+ if (id && !sound._paused) {
584
+ if (!internal) {
585
+ self._loadQueue("play");
586
+ }
587
+ return sound._id;
588
+ }
589
+ if (self._webAudio) {
590
+ Howler2._autoResume();
591
+ }
592
+ var seek = Math.max(0, sound._seek > 0 ? sound._seek : self._sprite[sprite][0] / 1e3);
593
+ var duration = Math.max(0, (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1e3 - seek);
594
+ var timeout = duration * 1e3 / Math.abs(sound._rate);
595
+ var start = self._sprite[sprite][0] / 1e3;
596
+ var stop2 = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1e3;
597
+ sound._sprite = sprite;
598
+ sound._ended = false;
599
+ var setParams = function() {
600
+ sound._paused = false;
601
+ sound._seek = seek;
602
+ sound._start = start;
603
+ sound._stop = stop2;
604
+ sound._loop = !!(sound._loop || self._sprite[sprite][2]);
605
+ };
606
+ if (seek >= stop2) {
607
+ self._ended(sound);
608
+ return;
609
+ }
610
+ var node = sound._node;
611
+ if (self._webAudio) {
612
+ var playWebAudio = function() {
613
+ self._playLock = false;
614
+ setParams();
615
+ self._refreshBuffer(sound);
616
+ var vol = sound._muted || self._muted ? 0 : sound._volume;
617
+ node.gain.setValueAtTime(vol, Howler2.ctx.currentTime);
618
+ sound._playStart = Howler2.ctx.currentTime;
619
+ if (typeof node.bufferSource.start === "undefined") {
620
+ sound._loop ? node.bufferSource.noteGrainOn(0, seek, 86400) : node.bufferSource.noteGrainOn(0, seek, duration);
621
+ } else {
622
+ sound._loop ? node.bufferSource.start(0, seek, 86400) : node.bufferSource.start(0, seek, duration);
623
+ }
624
+ if (timeout !== Infinity) {
625
+ self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);
626
+ }
627
+ if (!internal) {
628
+ setTimeout(function() {
629
+ self._emit("play", sound._id);
630
+ self._loadQueue();
631
+ }, 0);
632
+ }
633
+ };
634
+ if (Howler2.state === "running" && Howler2.ctx.state !== "interrupted") {
635
+ playWebAudio();
636
+ } else {
637
+ self._playLock = true;
638
+ self.once("resume", playWebAudio);
639
+ self._clearTimer(sound._id);
640
+ }
641
+ } else {
642
+ var playHtml5 = function() {
643
+ node.currentTime = seek;
644
+ node.muted = sound._muted || self._muted || Howler2._muted || node.muted;
645
+ node.volume = sound._volume * Howler2.volume();
646
+ node.playbackRate = sound._rate;
647
+ try {
648
+ var play = node.play();
649
+ if (play && typeof Promise !== "undefined" && (play instanceof Promise || typeof play.then === "function")) {
650
+ self._playLock = true;
651
+ setParams();
652
+ play.then(function() {
653
+ self._playLock = false;
654
+ node._unlocked = true;
655
+ if (!internal) {
656
+ self._emit("play", sound._id);
657
+ } else {
658
+ self._loadQueue();
659
+ }
660
+ }).catch(function() {
661
+ self._playLock = false;
662
+ 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.");
663
+ sound._ended = true;
664
+ sound._paused = true;
665
+ });
666
+ } else if (!internal) {
667
+ self._playLock = false;
668
+ setParams();
669
+ self._emit("play", sound._id);
670
+ }
671
+ node.playbackRate = sound._rate;
672
+ if (node.paused) {
673
+ 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.");
674
+ return;
675
+ }
676
+ if (sprite !== "__default" || sound._loop) {
677
+ self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);
678
+ } else {
679
+ self._endTimers[sound._id] = function() {
680
+ self._ended(sound);
681
+ node.removeEventListener("ended", self._endTimers[sound._id], false);
682
+ };
683
+ node.addEventListener("ended", self._endTimers[sound._id], false);
684
+ }
685
+ } catch (err) {
686
+ self._emit("playerror", sound._id, err);
687
+ }
688
+ };
689
+ if (node.src === "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA") {
690
+ node.src = self._src;
691
+ node.load();
692
+ }
693
+ var loadedNoReadyState = window && window.ejecta || !node.readyState && Howler2._navigator.isCocoonJS;
694
+ if (node.readyState >= 3 || loadedNoReadyState) {
695
+ playHtml5();
696
+ } else {
697
+ self._playLock = true;
698
+ self._state = "loading";
699
+ var listener = function() {
700
+ self._state = "loaded";
701
+ playHtml5();
702
+ node.removeEventListener(Howler2._canPlayEvent, listener, false);
703
+ };
704
+ node.addEventListener(Howler2._canPlayEvent, listener, false);
705
+ self._clearTimer(sound._id);
706
+ }
707
+ }
708
+ return sound._id;
709
+ },
710
+ /**
711
+ * Pause playback and save current position.
712
+ * @param {Number} id The sound ID (empty to pause all in group).
713
+ * @return {Howl}
714
+ */
715
+ pause: function(id) {
716
+ var self = this;
717
+ if (self._state !== "loaded" || self._playLock) {
718
+ self._queue.push({
719
+ event: "pause",
720
+ action: function() {
721
+ self.pause(id);
722
+ }
723
+ });
724
+ return self;
725
+ }
726
+ var ids = self._getSoundIds(id);
727
+ for (var i = 0; i < ids.length; i++) {
728
+ self._clearTimer(ids[i]);
729
+ var sound = self._soundById(ids[i]);
730
+ if (sound && !sound._paused) {
731
+ sound._seek = self.seek(ids[i]);
732
+ sound._rateSeek = 0;
733
+ sound._paused = true;
734
+ self._stopFade(ids[i]);
735
+ if (sound._node) {
736
+ if (self._webAudio) {
737
+ if (!sound._node.bufferSource) {
738
+ continue;
739
+ }
740
+ if (typeof sound._node.bufferSource.stop === "undefined") {
741
+ sound._node.bufferSource.noteOff(0);
742
+ } else {
743
+ sound._node.bufferSource.stop(0);
744
+ }
745
+ self._cleanBuffer(sound._node);
746
+ } else if (!isNaN(sound._node.duration) || sound._node.duration === Infinity) {
747
+ sound._node.pause();
748
+ }
749
+ }
750
+ }
751
+ if (!arguments[1]) {
752
+ self._emit("pause", sound ? sound._id : null);
753
+ }
754
+ }
755
+ return self;
756
+ },
757
+ /**
758
+ * Stop playback and reset to start.
759
+ * @param {Number} id The sound ID (empty to stop all in group).
760
+ * @param {Boolean} internal Internal Use: true prevents event firing.
761
+ * @return {Howl}
762
+ */
763
+ stop: function(id, internal) {
764
+ var self = this;
765
+ if (self._state !== "loaded" || self._playLock) {
766
+ self._queue.push({
767
+ event: "stop",
768
+ action: function() {
769
+ self.stop(id);
770
+ }
771
+ });
772
+ return self;
773
+ }
774
+ var ids = self._getSoundIds(id);
775
+ for (var i = 0; i < ids.length; i++) {
776
+ self._clearTimer(ids[i]);
777
+ var sound = self._soundById(ids[i]);
778
+ if (sound) {
779
+ sound._seek = sound._start || 0;
780
+ sound._rateSeek = 0;
781
+ sound._paused = true;
782
+ sound._ended = true;
783
+ self._stopFade(ids[i]);
784
+ if (sound._node) {
785
+ if (self._webAudio) {
786
+ if (sound._node.bufferSource) {
787
+ if (typeof sound._node.bufferSource.stop === "undefined") {
788
+ sound._node.bufferSource.noteOff(0);
789
+ } else {
790
+ sound._node.bufferSource.stop(0);
791
+ }
792
+ self._cleanBuffer(sound._node);
793
+ }
794
+ } else if (!isNaN(sound._node.duration) || sound._node.duration === Infinity) {
795
+ sound._node.currentTime = sound._start || 0;
796
+ sound._node.pause();
797
+ if (sound._node.duration === Infinity) {
798
+ self._clearSound(sound._node);
799
+ }
800
+ }
801
+ }
802
+ if (!internal) {
803
+ self._emit("stop", sound._id);
804
+ }
805
+ }
806
+ }
807
+ return self;
808
+ },
809
+ /**
810
+ * Mute/unmute a single sound or all sounds in this Howl group.
811
+ * @param {Boolean} muted Set to true to mute and false to unmute.
812
+ * @param {Number} id The sound ID to update (omit to mute/unmute all).
813
+ * @return {Howl}
814
+ */
815
+ mute: function(muted, id) {
816
+ var self = this;
817
+ if (self._state !== "loaded" || self._playLock) {
818
+ self._queue.push({
819
+ event: "mute",
820
+ action: function() {
821
+ self.mute(muted, id);
822
+ }
823
+ });
824
+ return self;
825
+ }
826
+ if (typeof id === "undefined") {
827
+ if (typeof muted === "boolean") {
828
+ self._muted = muted;
829
+ } else {
830
+ return self._muted;
831
+ }
832
+ }
833
+ var ids = self._getSoundIds(id);
834
+ for (var i = 0; i < ids.length; i++) {
835
+ var sound = self._soundById(ids[i]);
836
+ if (sound) {
837
+ sound._muted = muted;
838
+ if (sound._interval) {
839
+ self._stopFade(sound._id);
840
+ }
841
+ if (self._webAudio && sound._node) {
842
+ sound._node.gain.setValueAtTime(muted ? 0 : sound._volume, Howler2.ctx.currentTime);
843
+ } else if (sound._node) {
844
+ sound._node.muted = Howler2._muted ? true : muted;
845
+ }
846
+ self._emit("mute", sound._id);
847
+ }
848
+ }
849
+ return self;
850
+ },
851
+ /**
852
+ * Get/set the volume of this sound or of the Howl group. This method can optionally take 0, 1 or 2 arguments.
853
+ * volume() -> Returns the group's volume value.
854
+ * volume(id) -> Returns the sound id's current volume.
855
+ * volume(vol) -> Sets the volume of all sounds in this Howl group.
856
+ * volume(vol, id) -> Sets the volume of passed sound id.
857
+ * @return {Howl/Number} Returns self or current volume.
858
+ */
859
+ volume: function() {
860
+ var self = this;
861
+ var args = arguments;
862
+ var vol, id;
863
+ if (args.length === 0) {
864
+ return self._volume;
865
+ } else if (args.length === 1 || args.length === 2 && typeof args[1] === "undefined") {
866
+ var ids = self._getSoundIds();
867
+ var index = ids.indexOf(args[0]);
868
+ if (index >= 0) {
869
+ id = parseInt(args[0], 10);
870
+ } else {
871
+ vol = parseFloat(args[0]);
872
+ }
873
+ } else if (args.length >= 2) {
874
+ vol = parseFloat(args[0]);
875
+ id = parseInt(args[1], 10);
876
+ }
877
+ var sound;
878
+ if (typeof vol !== "undefined" && vol >= 0 && vol <= 1) {
879
+ if (self._state !== "loaded" || self._playLock) {
880
+ self._queue.push({
881
+ event: "volume",
882
+ action: function() {
883
+ self.volume.apply(self, args);
884
+ }
885
+ });
886
+ return self;
887
+ }
888
+ if (typeof id === "undefined") {
889
+ self._volume = vol;
890
+ }
891
+ id = self._getSoundIds(id);
892
+ for (var i = 0; i < id.length; i++) {
893
+ sound = self._soundById(id[i]);
894
+ if (sound) {
895
+ sound._volume = vol;
896
+ if (!args[2]) {
897
+ self._stopFade(id[i]);
898
+ }
899
+ if (self._webAudio && sound._node && !sound._muted) {
900
+ sound._node.gain.setValueAtTime(vol, Howler2.ctx.currentTime);
901
+ } else if (sound._node && !sound._muted) {
902
+ sound._node.volume = vol * Howler2.volume();
903
+ }
904
+ self._emit("volume", sound._id);
905
+ }
906
+ }
907
+ } else {
908
+ sound = id ? self._soundById(id) : self._sounds[0];
909
+ return sound ? sound._volume : 0;
910
+ }
911
+ return self;
912
+ },
913
+ /**
914
+ * Fade a currently playing sound between two volumes (if no id is passed, all sounds will fade).
915
+ * @param {Number} from The value to fade from (0.0 to 1.0).
916
+ * @param {Number} to The volume to fade to (0.0 to 1.0).
917
+ * @param {Number} len Time in milliseconds to fade.
918
+ * @param {Number} id The sound id (omit to fade all sounds).
919
+ * @return {Howl}
920
+ */
921
+ fade: function(from, to, len, id) {
922
+ var self = this;
923
+ if (self._state !== "loaded" || self._playLock) {
924
+ self._queue.push({
925
+ event: "fade",
926
+ action: function() {
927
+ self.fade(from, to, len, id);
928
+ }
929
+ });
930
+ return self;
931
+ }
932
+ from = Math.min(Math.max(0, parseFloat(from)), 1);
933
+ to = Math.min(Math.max(0, parseFloat(to)), 1);
934
+ len = parseFloat(len);
935
+ self.volume(from, id);
936
+ var ids = self._getSoundIds(id);
937
+ for (var i = 0; i < ids.length; i++) {
938
+ var sound = self._soundById(ids[i]);
939
+ if (sound) {
940
+ if (!id) {
941
+ self._stopFade(ids[i]);
942
+ }
943
+ if (self._webAudio && !sound._muted) {
944
+ var currentTime = Howler2.ctx.currentTime;
945
+ var end = currentTime + len / 1e3;
946
+ sound._volume = from;
947
+ sound._node.gain.setValueAtTime(from, currentTime);
948
+ sound._node.gain.linearRampToValueAtTime(to, end);
949
+ }
950
+ self._startFadeInterval(sound, from, to, len, ids[i], typeof id === "undefined");
951
+ }
952
+ }
953
+ return self;
954
+ },
955
+ /**
956
+ * Starts the internal interval to fade a sound.
957
+ * @param {Object} sound Reference to sound to fade.
958
+ * @param {Number} from The value to fade from (0.0 to 1.0).
959
+ * @param {Number} to The volume to fade to (0.0 to 1.0).
960
+ * @param {Number} len Time in milliseconds to fade.
961
+ * @param {Number} id The sound id to fade.
962
+ * @param {Boolean} isGroup If true, set the volume on the group.
963
+ */
964
+ _startFadeInterval: function(sound, from, to, len, id, isGroup) {
965
+ var self = this;
966
+ var vol = from;
967
+ var diff = to - from;
968
+ var steps = Math.abs(diff / 0.01);
969
+ var stepLen = Math.max(4, steps > 0 ? len / steps : len);
970
+ var lastTick = Date.now();
971
+ sound._fadeTo = to;
972
+ sound._interval = setInterval(function() {
973
+ var tick = (Date.now() - lastTick) / len;
974
+ lastTick = Date.now();
975
+ vol += diff * tick;
976
+ vol = Math.round(vol * 100) / 100;
977
+ if (diff < 0) {
978
+ vol = Math.max(to, vol);
979
+ } else {
980
+ vol = Math.min(to, vol);
981
+ }
982
+ if (self._webAudio) {
983
+ sound._volume = vol;
984
+ } else {
985
+ self.volume(vol, sound._id, true);
986
+ }
987
+ if (isGroup) {
988
+ self._volume = vol;
989
+ }
990
+ if (to < from && vol <= to || to > from && vol >= to) {
991
+ clearInterval(sound._interval);
992
+ sound._interval = null;
993
+ sound._fadeTo = null;
994
+ self.volume(to, sound._id);
995
+ self._emit("fade", sound._id);
996
+ }
997
+ }, stepLen);
998
+ },
999
+ /**
1000
+ * Internal method that stops the currently playing fade when
1001
+ * a new fade starts, volume is changed or the sound is stopped.
1002
+ * @param {Number} id The sound id.
1003
+ * @return {Howl}
1004
+ */
1005
+ _stopFade: function(id) {
1006
+ var self = this;
1007
+ var sound = self._soundById(id);
1008
+ if (sound && sound._interval) {
1009
+ if (self._webAudio) {
1010
+ sound._node.gain.cancelScheduledValues(Howler2.ctx.currentTime);
1011
+ }
1012
+ clearInterval(sound._interval);
1013
+ sound._interval = null;
1014
+ self.volume(sound._fadeTo, id);
1015
+ sound._fadeTo = null;
1016
+ self._emit("fade", id);
1017
+ }
1018
+ return self;
1019
+ },
1020
+ /**
1021
+ * Get/set the loop parameter on a sound. This method can optionally take 0, 1 or 2 arguments.
1022
+ * loop() -> Returns the group's loop value.
1023
+ * loop(id) -> Returns the sound id's loop value.
1024
+ * loop(loop) -> Sets the loop value for all sounds in this Howl group.
1025
+ * loop(loop, id) -> Sets the loop value of passed sound id.
1026
+ * @return {Howl/Boolean} Returns self or current loop value.
1027
+ */
1028
+ loop: function() {
1029
+ var self = this;
1030
+ var args = arguments;
1031
+ var loop, id, sound;
1032
+ if (args.length === 0) {
1033
+ return self._loop;
1034
+ } else if (args.length === 1) {
1035
+ if (typeof args[0] === "boolean") {
1036
+ loop = args[0];
1037
+ self._loop = loop;
1038
+ } else {
1039
+ sound = self._soundById(parseInt(args[0], 10));
1040
+ return sound ? sound._loop : false;
1041
+ }
1042
+ } else if (args.length === 2) {
1043
+ loop = args[0];
1044
+ id = parseInt(args[1], 10);
1045
+ }
1046
+ var ids = self._getSoundIds(id);
1047
+ for (var i = 0; i < ids.length; i++) {
1048
+ sound = self._soundById(ids[i]);
1049
+ if (sound) {
1050
+ sound._loop = loop;
1051
+ if (self._webAudio && sound._node && sound._node.bufferSource) {
1052
+ sound._node.bufferSource.loop = loop;
1053
+ if (loop) {
1054
+ sound._node.bufferSource.loopStart = sound._start || 0;
1055
+ sound._node.bufferSource.loopEnd = sound._stop;
1056
+ if (self.playing(ids[i])) {
1057
+ self.pause(ids[i], true);
1058
+ self.play(ids[i], true);
1059
+ }
1060
+ }
1061
+ }
1062
+ }
1063
+ }
1064
+ return self;
1065
+ },
1066
+ /**
1067
+ * Get/set the playback rate of a sound. This method can optionally take 0, 1 or 2 arguments.
1068
+ * rate() -> Returns the first sound node's current playback rate.
1069
+ * rate(id) -> Returns the sound id's current playback rate.
1070
+ * rate(rate) -> Sets the playback rate of all sounds in this Howl group.
1071
+ * rate(rate, id) -> Sets the playback rate of passed sound id.
1072
+ * @return {Howl/Number} Returns self or the current playback rate.
1073
+ */
1074
+ rate: function() {
1075
+ var self = this;
1076
+ var args = arguments;
1077
+ var rate, id;
1078
+ if (args.length === 0) {
1079
+ id = self._sounds[0]._id;
1080
+ } else if (args.length === 1) {
1081
+ var ids = self._getSoundIds();
1082
+ var index = ids.indexOf(args[0]);
1083
+ if (index >= 0) {
1084
+ id = parseInt(args[0], 10);
1085
+ } else {
1086
+ rate = parseFloat(args[0]);
1087
+ }
1088
+ } else if (args.length === 2) {
1089
+ rate = parseFloat(args[0]);
1090
+ id = parseInt(args[1], 10);
1091
+ }
1092
+ var sound;
1093
+ if (typeof rate === "number") {
1094
+ if (self._state !== "loaded" || self._playLock) {
1095
+ self._queue.push({
1096
+ event: "rate",
1097
+ action: function() {
1098
+ self.rate.apply(self, args);
1099
+ }
1100
+ });
1101
+ return self;
1102
+ }
1103
+ if (typeof id === "undefined") {
1104
+ self._rate = rate;
1105
+ }
1106
+ id = self._getSoundIds(id);
1107
+ for (var i = 0; i < id.length; i++) {
1108
+ sound = self._soundById(id[i]);
1109
+ if (sound) {
1110
+ if (self.playing(id[i])) {
1111
+ sound._rateSeek = self.seek(id[i]);
1112
+ sound._playStart = self._webAudio ? Howler2.ctx.currentTime : sound._playStart;
1113
+ }
1114
+ sound._rate = rate;
1115
+ if (self._webAudio && sound._node && sound._node.bufferSource) {
1116
+ sound._node.bufferSource.playbackRate.setValueAtTime(rate, Howler2.ctx.currentTime);
1117
+ } else if (sound._node) {
1118
+ sound._node.playbackRate = rate;
1119
+ }
1120
+ var seek = self.seek(id[i]);
1121
+ var duration = (self._sprite[sound._sprite][0] + self._sprite[sound._sprite][1]) / 1e3 - seek;
1122
+ var timeout = duration * 1e3 / Math.abs(sound._rate);
1123
+ if (self._endTimers[id[i]] || !sound._paused) {
1124
+ self._clearTimer(id[i]);
1125
+ self._endTimers[id[i]] = setTimeout(self._ended.bind(self, sound), timeout);
1126
+ }
1127
+ self._emit("rate", sound._id);
1128
+ }
1129
+ }
1130
+ } else {
1131
+ sound = self._soundById(id);
1132
+ return sound ? sound._rate : self._rate;
1133
+ }
1134
+ return self;
1135
+ },
1136
+ /**
1137
+ * Get/set the seek position of a sound. This method can optionally take 0, 1 or 2 arguments.
1138
+ * seek() -> Returns the first sound node's current seek position.
1139
+ * seek(id) -> Returns the sound id's current seek position.
1140
+ * seek(seek) -> Sets the seek position of the first sound node.
1141
+ * seek(seek, id) -> Sets the seek position of passed sound id.
1142
+ * @return {Howl/Number} Returns self or the current seek position.
1143
+ */
1144
+ seek: function() {
1145
+ var self = this;
1146
+ var args = arguments;
1147
+ var seek, id;
1148
+ if (args.length === 0) {
1149
+ if (self._sounds.length) {
1150
+ id = self._sounds[0]._id;
1151
+ }
1152
+ } else if (args.length === 1) {
1153
+ var ids = self._getSoundIds();
1154
+ var index = ids.indexOf(args[0]);
1155
+ if (index >= 0) {
1156
+ id = parseInt(args[0], 10);
1157
+ } else if (self._sounds.length) {
1158
+ id = self._sounds[0]._id;
1159
+ seek = parseFloat(args[0]);
1160
+ }
1161
+ } else if (args.length === 2) {
1162
+ seek = parseFloat(args[0]);
1163
+ id = parseInt(args[1], 10);
1164
+ }
1165
+ if (typeof id === "undefined") {
1166
+ return 0;
1167
+ }
1168
+ if (typeof seek === "number" && (self._state !== "loaded" || self._playLock)) {
1169
+ self._queue.push({
1170
+ event: "seek",
1171
+ action: function() {
1172
+ self.seek.apply(self, args);
1173
+ }
1174
+ });
1175
+ return self;
1176
+ }
1177
+ var sound = self._soundById(id);
1178
+ if (sound) {
1179
+ if (typeof seek === "number" && seek >= 0) {
1180
+ var playing = self.playing(id);
1181
+ if (playing) {
1182
+ self.pause(id, true);
1183
+ }
1184
+ sound._seek = seek;
1185
+ sound._ended = false;
1186
+ self._clearTimer(id);
1187
+ if (!self._webAudio && sound._node && !isNaN(sound._node.duration)) {
1188
+ sound._node.currentTime = seek;
1189
+ }
1190
+ var seekAndEmit = function() {
1191
+ if (playing) {
1192
+ self.play(id, true);
1193
+ }
1194
+ self._emit("seek", id);
1195
+ };
1196
+ if (playing && !self._webAudio) {
1197
+ var emitSeek = function() {
1198
+ if (!self._playLock) {
1199
+ seekAndEmit();
1200
+ } else {
1201
+ setTimeout(emitSeek, 0);
1202
+ }
1203
+ };
1204
+ setTimeout(emitSeek, 0);
1205
+ } else {
1206
+ seekAndEmit();
1207
+ }
1208
+ } else {
1209
+ if (self._webAudio) {
1210
+ var realTime = self.playing(id) ? Howler2.ctx.currentTime - sound._playStart : 0;
1211
+ var rateSeek = sound._rateSeek ? sound._rateSeek - sound._seek : 0;
1212
+ return sound._seek + (rateSeek + realTime * Math.abs(sound._rate));
1213
+ } else {
1214
+ return sound._node.currentTime;
1215
+ }
1216
+ }
1217
+ }
1218
+ return self;
1219
+ },
1220
+ /**
1221
+ * 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.
1222
+ * @param {Number} id The sound id to check. If none is passed, the whole sound group is checked.
1223
+ * @return {Boolean} True if playing and false if not.
1224
+ */
1225
+ playing: function(id) {
1226
+ var self = this;
1227
+ if (typeof id === "number") {
1228
+ var sound = self._soundById(id);
1229
+ return sound ? !sound._paused : false;
1230
+ }
1231
+ for (var i = 0; i < self._sounds.length; i++) {
1232
+ if (!self._sounds[i]._paused) {
1233
+ return true;
1234
+ }
1235
+ }
1236
+ return false;
1237
+ },
1238
+ /**
1239
+ * Get the duration of this sound. Passing a sound id will return the sprite duration.
1240
+ * @param {Number} id The sound id to check. If none is passed, return full source duration.
1241
+ * @return {Number} Audio duration in seconds.
1242
+ */
1243
+ duration: function(id) {
1244
+ var self = this;
1245
+ var duration = self._duration;
1246
+ var sound = self._soundById(id);
1247
+ if (sound) {
1248
+ duration = self._sprite[sound._sprite][1] / 1e3;
1249
+ }
1250
+ return duration;
1251
+ },
1252
+ /**
1253
+ * Returns the current loaded state of this Howl.
1254
+ * @return {String} 'unloaded', 'loading', 'loaded'
1255
+ */
1256
+ state: function() {
1257
+ return this._state;
1258
+ },
1259
+ /**
1260
+ * Unload and destroy the current Howl object.
1261
+ * This will immediately stop all sound instances attached to this group.
1262
+ */
1263
+ unload: function() {
1264
+ var self = this;
1265
+ var sounds = self._sounds;
1266
+ for (var i = 0; i < sounds.length; i++) {
1267
+ if (!sounds[i]._paused) {
1268
+ self.stop(sounds[i]._id);
1269
+ }
1270
+ if (!self._webAudio) {
1271
+ self._clearSound(sounds[i]._node);
1272
+ sounds[i]._node.removeEventListener("error", sounds[i]._errorFn, false);
1273
+ sounds[i]._node.removeEventListener(Howler2._canPlayEvent, sounds[i]._loadFn, false);
1274
+ sounds[i]._node.removeEventListener("ended", sounds[i]._endFn, false);
1275
+ Howler2._releaseHtml5Audio(sounds[i]._node);
1276
+ }
1277
+ delete sounds[i]._node;
1278
+ self._clearTimer(sounds[i]._id);
1279
+ }
1280
+ var index = Howler2._howls.indexOf(self);
1281
+ if (index >= 0) {
1282
+ Howler2._howls.splice(index, 1);
1283
+ }
1284
+ var remCache = true;
1285
+ for (i = 0; i < Howler2._howls.length; i++) {
1286
+ if (Howler2._howls[i]._src === self._src || self._src.indexOf(Howler2._howls[i]._src) >= 0) {
1287
+ remCache = false;
1288
+ break;
1289
+ }
1290
+ }
1291
+ if (cache && remCache) {
1292
+ delete cache[self._src];
1293
+ }
1294
+ Howler2.noAudio = false;
1295
+ self._state = "unloaded";
1296
+ self._sounds = [];
1297
+ self = null;
1298
+ return null;
1299
+ },
1300
+ /**
1301
+ * Listen to a custom event.
1302
+ * @param {String} event Event name.
1303
+ * @param {Function} fn Listener to call.
1304
+ * @param {Number} id (optional) Only listen to events for this sound.
1305
+ * @param {Number} once (INTERNAL) Marks event to fire only once.
1306
+ * @return {Howl}
1307
+ */
1308
+ on: function(event, fn, id, once) {
1309
+ var self = this;
1310
+ var events = self["_on" + event];
1311
+ if (typeof fn === "function") {
1312
+ events.push(once ? { id, fn, once } : { id, fn });
1313
+ }
1314
+ return self;
1315
+ },
1316
+ /**
1317
+ * Remove a custom event. Call without parameters to remove all events.
1318
+ * @param {String} event Event name.
1319
+ * @param {Function} fn Listener to remove. Leave empty to remove all.
1320
+ * @param {Number} id (optional) Only remove events for this sound.
1321
+ * @return {Howl}
1322
+ */
1323
+ off: function(event, fn, id) {
1324
+ var self = this;
1325
+ var events = self["_on" + event];
1326
+ var i = 0;
1327
+ if (typeof fn === "number") {
1328
+ id = fn;
1329
+ fn = null;
1330
+ }
1331
+ if (fn || id) {
1332
+ for (i = 0; i < events.length; i++) {
1333
+ var isId = id === events[i].id;
1334
+ if (fn === events[i].fn && isId || !fn && isId) {
1335
+ events.splice(i, 1);
1336
+ break;
1337
+ }
1338
+ }
1339
+ } else if (event) {
1340
+ self["_on" + event] = [];
1341
+ } else {
1342
+ var keys = Object.keys(self);
1343
+ for (i = 0; i < keys.length; i++) {
1344
+ if (keys[i].indexOf("_on") === 0 && Array.isArray(self[keys[i]])) {
1345
+ self[keys[i]] = [];
1346
+ }
1347
+ }
1348
+ }
1349
+ return self;
1350
+ },
1351
+ /**
1352
+ * Listen to a custom event and remove it once fired.
1353
+ * @param {String} event Event name.
1354
+ * @param {Function} fn Listener to call.
1355
+ * @param {Number} id (optional) Only listen to events for this sound.
1356
+ * @return {Howl}
1357
+ */
1358
+ once: function(event, fn, id) {
1359
+ var self = this;
1360
+ self.on(event, fn, id, 1);
1361
+ return self;
1362
+ },
1363
+ /**
1364
+ * Emit all events of a specific type and pass the sound id.
1365
+ * @param {String} event Event name.
1366
+ * @param {Number} id Sound ID.
1367
+ * @param {Number} msg Message to go with event.
1368
+ * @return {Howl}
1369
+ */
1370
+ _emit: function(event, id, msg) {
1371
+ var self = this;
1372
+ var events = self["_on" + event];
1373
+ for (var i = events.length - 1; i >= 0; i--) {
1374
+ if (!events[i].id || events[i].id === id || event === "load") {
1375
+ setTimeout(function(fn) {
1376
+ fn.call(this, id, msg);
1377
+ }.bind(self, events[i].fn), 0);
1378
+ if (events[i].once) {
1379
+ self.off(event, events[i].fn, events[i].id);
1380
+ }
1381
+ }
1382
+ }
1383
+ self._loadQueue(event);
1384
+ return self;
1385
+ },
1386
+ /**
1387
+ * Queue of actions initiated before the sound has loaded.
1388
+ * These will be called in sequence, with the next only firing
1389
+ * after the previous has finished executing (even if async like play).
1390
+ * @return {Howl}
1391
+ */
1392
+ _loadQueue: function(event) {
1393
+ var self = this;
1394
+ if (self._queue.length > 0) {
1395
+ var task = self._queue[0];
1396
+ if (task.event === event) {
1397
+ self._queue.shift();
1398
+ self._loadQueue();
1399
+ }
1400
+ if (!event) {
1401
+ task.action();
1402
+ }
1403
+ }
1404
+ return self;
1405
+ },
1406
+ /**
1407
+ * Fired when playback ends at the end of the duration.
1408
+ * @param {Sound} sound The sound object to work with.
1409
+ * @return {Howl}
1410
+ */
1411
+ _ended: function(sound) {
1412
+ var self = this;
1413
+ var sprite = sound._sprite;
1414
+ if (!self._webAudio && sound._node && !sound._node.paused && !sound._node.ended && sound._node.currentTime < sound._stop) {
1415
+ setTimeout(self._ended.bind(self, sound), 100);
1416
+ return self;
1417
+ }
1418
+ var loop = !!(sound._loop || self._sprite[sprite][2]);
1419
+ self._emit("end", sound._id);
1420
+ if (!self._webAudio && loop) {
1421
+ self.stop(sound._id, true).play(sound._id);
1422
+ }
1423
+ if (self._webAudio && loop) {
1424
+ self._emit("play", sound._id);
1425
+ sound._seek = sound._start || 0;
1426
+ sound._rateSeek = 0;
1427
+ sound._playStart = Howler2.ctx.currentTime;
1428
+ var timeout = (sound._stop - sound._start) * 1e3 / Math.abs(sound._rate);
1429
+ self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);
1430
+ }
1431
+ if (self._webAudio && !loop) {
1432
+ sound._paused = true;
1433
+ sound._ended = true;
1434
+ sound._seek = sound._start || 0;
1435
+ sound._rateSeek = 0;
1436
+ self._clearTimer(sound._id);
1437
+ self._cleanBuffer(sound._node);
1438
+ Howler2._autoSuspend();
1439
+ }
1440
+ if (!self._webAudio && !loop) {
1441
+ self.stop(sound._id, true);
1442
+ }
1443
+ return self;
1444
+ },
1445
+ /**
1446
+ * Clear the end timer for a sound playback.
1447
+ * @param {Number} id The sound ID.
1448
+ * @return {Howl}
1449
+ */
1450
+ _clearTimer: function(id) {
1451
+ var self = this;
1452
+ if (self._endTimers[id]) {
1453
+ if (typeof self._endTimers[id] !== "function") {
1454
+ clearTimeout(self._endTimers[id]);
1455
+ } else {
1456
+ var sound = self._soundById(id);
1457
+ if (sound && sound._node) {
1458
+ sound._node.removeEventListener("ended", self._endTimers[id], false);
1459
+ }
1460
+ }
1461
+ delete self._endTimers[id];
1462
+ }
1463
+ return self;
1464
+ },
1465
+ /**
1466
+ * Return the sound identified by this ID, or return null.
1467
+ * @param {Number} id Sound ID
1468
+ * @return {Object} Sound object or null.
1469
+ */
1470
+ _soundById: function(id) {
1471
+ var self = this;
1472
+ for (var i = 0; i < self._sounds.length; i++) {
1473
+ if (id === self._sounds[i]._id) {
1474
+ return self._sounds[i];
1475
+ }
1476
+ }
1477
+ return null;
1478
+ },
1479
+ /**
1480
+ * Return an inactive sound from the pool or create a new one.
1481
+ * @return {Sound} Sound playback object.
1482
+ */
1483
+ _inactiveSound: function() {
1484
+ var self = this;
1485
+ self._drain();
1486
+ for (var i = 0; i < self._sounds.length; i++) {
1487
+ if (self._sounds[i]._ended) {
1488
+ return self._sounds[i].reset();
1489
+ }
1490
+ }
1491
+ return new Sound2(self);
1492
+ },
1493
+ /**
1494
+ * Drain excess inactive sounds from the pool.
1495
+ */
1496
+ _drain: function() {
1497
+ var self = this;
1498
+ var limit = self._pool;
1499
+ var cnt = 0;
1500
+ var i = 0;
1501
+ if (self._sounds.length < limit) {
1502
+ return;
1503
+ }
1504
+ for (i = 0; i < self._sounds.length; i++) {
1505
+ if (self._sounds[i]._ended) {
1506
+ cnt++;
1507
+ }
1508
+ }
1509
+ for (i = self._sounds.length - 1; i >= 0; i--) {
1510
+ if (cnt <= limit) {
1511
+ return;
1512
+ }
1513
+ if (self._sounds[i]._ended) {
1514
+ if (self._webAudio && self._sounds[i]._node) {
1515
+ self._sounds[i]._node.disconnect(0);
1516
+ }
1517
+ self._sounds.splice(i, 1);
1518
+ cnt--;
1519
+ }
1520
+ }
1521
+ },
1522
+ /**
1523
+ * Get all ID's from the sounds pool.
1524
+ * @param {Number} id Only return one ID if one is passed.
1525
+ * @return {Array} Array of IDs.
1526
+ */
1527
+ _getSoundIds: function(id) {
1528
+ var self = this;
1529
+ if (typeof id === "undefined") {
1530
+ var ids = [];
1531
+ for (var i = 0; i < self._sounds.length; i++) {
1532
+ ids.push(self._sounds[i]._id);
1533
+ }
1534
+ return ids;
1535
+ } else {
1536
+ return [id];
1537
+ }
1538
+ },
1539
+ /**
1540
+ * Load the sound back into the buffer source.
1541
+ * @param {Sound} sound The sound object to work with.
1542
+ * @return {Howl}
1543
+ */
1544
+ _refreshBuffer: function(sound) {
1545
+ var self = this;
1546
+ sound._node.bufferSource = Howler2.ctx.createBufferSource();
1547
+ sound._node.bufferSource.buffer = cache[self._src];
1548
+ if (sound._panner) {
1549
+ sound._node.bufferSource.connect(sound._panner);
1550
+ } else {
1551
+ sound._node.bufferSource.connect(sound._node);
1552
+ }
1553
+ sound._node.bufferSource.loop = sound._loop;
1554
+ if (sound._loop) {
1555
+ sound._node.bufferSource.loopStart = sound._start || 0;
1556
+ sound._node.bufferSource.loopEnd = sound._stop || 0;
1557
+ }
1558
+ sound._node.bufferSource.playbackRate.setValueAtTime(sound._rate, Howler2.ctx.currentTime);
1559
+ return self;
1560
+ },
1561
+ /**
1562
+ * Prevent memory leaks by cleaning up the buffer source after playback.
1563
+ * @param {Object} node Sound's audio node containing the buffer source.
1564
+ * @return {Howl}
1565
+ */
1566
+ _cleanBuffer: function(node) {
1567
+ var self = this;
1568
+ var isIOS = Howler2._navigator && Howler2._navigator.vendor.indexOf("Apple") >= 0;
1569
+ if (!node.bufferSource) {
1570
+ return self;
1571
+ }
1572
+ if (Howler2._scratchBuffer && node.bufferSource) {
1573
+ node.bufferSource.onended = null;
1574
+ node.bufferSource.disconnect(0);
1575
+ if (isIOS) {
1576
+ try {
1577
+ node.bufferSource.buffer = Howler2._scratchBuffer;
1578
+ } catch (e) {
1579
+ }
1580
+ }
1581
+ }
1582
+ node.bufferSource = null;
1583
+ return self;
1584
+ },
1585
+ /**
1586
+ * Set the source to a 0-second silence to stop any downloading (except in IE).
1587
+ * @param {Object} node Audio node to clear.
1588
+ */
1589
+ _clearSound: function(node) {
1590
+ var checkIE = /MSIE |Trident\//.test(Howler2._navigator && Howler2._navigator.userAgent);
1591
+ if (!checkIE) {
1592
+ node.src = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA";
1593
+ }
1594
+ }
1595
+ };
1596
+ var Sound2 = function(howl) {
1597
+ this._parent = howl;
1598
+ this.init();
1599
+ };
1600
+ Sound2.prototype = {
1601
+ /**
1602
+ * Initialize a new Sound object.
1603
+ * @return {Sound}
1604
+ */
1605
+ init: function() {
1606
+ var self = this;
1607
+ var parent = self._parent;
1608
+ self._muted = parent._muted;
1609
+ self._loop = parent._loop;
1610
+ self._volume = parent._volume;
1611
+ self._rate = parent._rate;
1612
+ self._seek = 0;
1613
+ self._paused = true;
1614
+ self._ended = true;
1615
+ self._sprite = "__default";
1616
+ self._id = ++Howler2._counter;
1617
+ parent._sounds.push(self);
1618
+ self.create();
1619
+ return self;
1620
+ },
1621
+ /**
1622
+ * Create and setup a new sound object, whether HTML5 Audio or Web Audio.
1623
+ * @return {Sound}
1624
+ */
1625
+ create: function() {
1626
+ var self = this;
1627
+ var parent = self._parent;
1628
+ var volume = Howler2._muted || self._muted || self._parent._muted ? 0 : self._volume;
1629
+ if (parent._webAudio) {
1630
+ self._node = typeof Howler2.ctx.createGain === "undefined" ? Howler2.ctx.createGainNode() : Howler2.ctx.createGain();
1631
+ self._node.gain.setValueAtTime(volume, Howler2.ctx.currentTime);
1632
+ self._node.paused = true;
1633
+ self._node.connect(Howler2.masterGain);
1634
+ } else if (!Howler2.noAudio) {
1635
+ self._node = Howler2._obtainHtml5Audio();
1636
+ self._errorFn = self._errorListener.bind(self);
1637
+ self._node.addEventListener("error", self._errorFn, false);
1638
+ self._loadFn = self._loadListener.bind(self);
1639
+ self._node.addEventListener(Howler2._canPlayEvent, self._loadFn, false);
1640
+ self._endFn = self._endListener.bind(self);
1641
+ self._node.addEventListener("ended", self._endFn, false);
1642
+ self._node.src = parent._src;
1643
+ self._node.preload = parent._preload === true ? "auto" : parent._preload;
1644
+ self._node.volume = volume * Howler2.volume();
1645
+ self._node.load();
1646
+ }
1647
+ return self;
1648
+ },
1649
+ /**
1650
+ * Reset the parameters of this sound to the original state (for recycle).
1651
+ * @return {Sound}
1652
+ */
1653
+ reset: function() {
1654
+ var self = this;
1655
+ var parent = self._parent;
1656
+ self._muted = parent._muted;
1657
+ self._loop = parent._loop;
1658
+ self._volume = parent._volume;
1659
+ self._rate = parent._rate;
1660
+ self._seek = 0;
1661
+ self._rateSeek = 0;
1662
+ self._paused = true;
1663
+ self._ended = true;
1664
+ self._sprite = "__default";
1665
+ self._id = ++Howler2._counter;
1666
+ return self;
1667
+ },
1668
+ /**
1669
+ * HTML5 Audio error listener callback.
1670
+ */
1671
+ _errorListener: function() {
1672
+ var self = this;
1673
+ self._parent._emit("loaderror", self._id, self._node.error ? self._node.error.code : 0);
1674
+ self._node.removeEventListener("error", self._errorFn, false);
1675
+ },
1676
+ /**
1677
+ * HTML5 Audio canplaythrough listener callback.
1678
+ */
1679
+ _loadListener: function() {
1680
+ var self = this;
1681
+ var parent = self._parent;
1682
+ parent._duration = Math.ceil(self._node.duration * 10) / 10;
1683
+ if (Object.keys(parent._sprite).length === 0) {
1684
+ parent._sprite = { __default: [0, parent._duration * 1e3] };
1685
+ }
1686
+ if (parent._state !== "loaded") {
1687
+ parent._state = "loaded";
1688
+ parent._emit("load");
1689
+ parent._loadQueue();
1690
+ }
1691
+ self._node.removeEventListener(Howler2._canPlayEvent, self._loadFn, false);
1692
+ },
1693
+ /**
1694
+ * HTML5 Audio ended listener callback.
1695
+ */
1696
+ _endListener: function() {
1697
+ var self = this;
1698
+ var parent = self._parent;
1699
+ if (parent._duration === Infinity) {
1700
+ parent._duration = Math.ceil(self._node.duration * 10) / 10;
1701
+ if (parent._sprite.__default[1] === Infinity) {
1702
+ parent._sprite.__default[1] = parent._duration * 1e3;
1703
+ }
1704
+ parent._ended(self);
1705
+ }
1706
+ self._node.removeEventListener("ended", self._endFn, false);
1707
+ }
1708
+ };
1709
+ var cache = {};
1710
+ var loadBuffer = function(self) {
1711
+ var url = self._src;
1712
+ if (cache[url]) {
1713
+ self._duration = cache[url].duration;
1714
+ loadSound(self);
1715
+ return;
1716
+ }
1717
+ if (/^data:[^;]+;base64,/.test(url)) {
1718
+ var data = atob(url.split(",")[1]);
1719
+ var dataView = new Uint8Array(data.length);
1720
+ for (var i = 0; i < data.length; ++i) {
1721
+ dataView[i] = data.charCodeAt(i);
1722
+ }
1723
+ decodeAudioData(dataView.buffer, self);
1724
+ } else {
1725
+ var xhr = new XMLHttpRequest();
1726
+ xhr.open(self._xhr.method, url, true);
1727
+ xhr.withCredentials = self._xhr.withCredentials;
1728
+ xhr.responseType = "arraybuffer";
1729
+ if (self._xhr.headers) {
1730
+ Object.keys(self._xhr.headers).forEach(function(key) {
1731
+ xhr.setRequestHeader(key, self._xhr.headers[key]);
1732
+ });
1733
+ }
1734
+ xhr.onload = function() {
1735
+ var code = (xhr.status + "")[0];
1736
+ if (code !== "0" && code !== "2" && code !== "3") {
1737
+ self._emit("loaderror", null, "Failed loading audio file with status: " + xhr.status + ".");
1738
+ return;
1739
+ }
1740
+ decodeAudioData(xhr.response, self);
1741
+ };
1742
+ xhr.onerror = function() {
1743
+ if (self._webAudio) {
1744
+ self._html5 = true;
1745
+ self._webAudio = false;
1746
+ self._sounds = [];
1747
+ delete cache[url];
1748
+ self.load();
1749
+ }
1750
+ };
1751
+ safeXhrSend(xhr);
1752
+ }
1753
+ };
1754
+ var safeXhrSend = function(xhr) {
1755
+ try {
1756
+ xhr.send();
1757
+ } catch (e) {
1758
+ xhr.onerror();
1759
+ }
1760
+ };
1761
+ var decodeAudioData = function(arraybuffer, self) {
1762
+ var error = function() {
1763
+ self._emit("loaderror", null, "Decoding audio data failed.");
1764
+ };
1765
+ var success = function(buffer) {
1766
+ if (buffer && self._sounds.length > 0) {
1767
+ cache[self._src] = buffer;
1768
+ loadSound(self, buffer);
1769
+ } else {
1770
+ error();
1771
+ }
1772
+ };
1773
+ if (typeof Promise !== "undefined" && Howler2.ctx.decodeAudioData.length === 1) {
1774
+ Howler2.ctx.decodeAudioData(arraybuffer).then(success).catch(error);
1775
+ } else {
1776
+ Howler2.ctx.decodeAudioData(arraybuffer, success, error);
1777
+ }
1778
+ };
1779
+ var loadSound = function(self, buffer) {
1780
+ if (buffer && !self._duration) {
1781
+ self._duration = buffer.duration;
1782
+ }
1783
+ if (Object.keys(self._sprite).length === 0) {
1784
+ self._sprite = { __default: [0, self._duration * 1e3] };
1785
+ }
1786
+ if (self._state !== "loaded") {
1787
+ self._state = "loaded";
1788
+ self._emit("load");
1789
+ self._loadQueue();
1790
+ }
1791
+ };
1792
+ var setupAudioContext = function() {
1793
+ if (!Howler2.usingWebAudio) {
1794
+ return;
1795
+ }
1796
+ try {
1797
+ if (typeof AudioContext !== "undefined") {
1798
+ Howler2.ctx = new AudioContext();
1799
+ } else if (typeof webkitAudioContext !== "undefined") {
1800
+ Howler2.ctx = new webkitAudioContext();
1801
+ } else {
1802
+ Howler2.usingWebAudio = false;
1803
+ }
1804
+ } catch (e) {
1805
+ Howler2.usingWebAudio = false;
1806
+ }
1807
+ if (!Howler2.ctx) {
1808
+ Howler2.usingWebAudio = false;
1809
+ }
1810
+ var iOS = /iP(hone|od|ad)/.test(Howler2._navigator && Howler2._navigator.platform);
1811
+ var appVersion = Howler2._navigator && Howler2._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
1812
+ var version = appVersion ? parseInt(appVersion[1], 10) : null;
1813
+ if (iOS && version && version < 9) {
1814
+ var safari = /safari/.test(Howler2._navigator && Howler2._navigator.userAgent.toLowerCase());
1815
+ if (Howler2._navigator && !safari) {
1816
+ Howler2.usingWebAudio = false;
1817
+ }
1818
+ }
1819
+ if (Howler2.usingWebAudio) {
1820
+ Howler2.masterGain = typeof Howler2.ctx.createGain === "undefined" ? Howler2.ctx.createGainNode() : Howler2.ctx.createGain();
1821
+ Howler2.masterGain.gain.setValueAtTime(Howler2._muted ? 0 : Howler2._volume, Howler2.ctx.currentTime);
1822
+ Howler2.masterGain.connect(Howler2.ctx.destination);
1823
+ }
1824
+ Howler2._setup();
1825
+ };
1826
+ if (typeof define === "function" && define.amd) {
1827
+ define([], function() {
1828
+ return {
1829
+ Howler: Howler2,
1830
+ Howl: Howl3
1831
+ };
1832
+ });
1833
+ }
1834
+ if (typeof exports !== "undefined") {
1835
+ exports.Howler = Howler2;
1836
+ exports.Howl = Howl3;
1837
+ }
1838
+ if (typeof global !== "undefined") {
1839
+ global.HowlerGlobal = HowlerGlobal2;
1840
+ global.Howler = Howler2;
1841
+ global.Howl = Howl3;
1842
+ global.Sound = Sound2;
1843
+ } else if (typeof window !== "undefined") {
1844
+ window.HowlerGlobal = HowlerGlobal2;
1845
+ window.Howler = Howler2;
1846
+ window.Howl = Howl3;
1847
+ window.Sound = Sound2;
1848
+ }
1849
+ })();
1850
+ (function() {
1851
+ "use strict";
1852
+ HowlerGlobal.prototype._pos = [0, 0, 0];
1853
+ HowlerGlobal.prototype._orientation = [0, 0, -1, 0, 1, 0];
1854
+ HowlerGlobal.prototype.stereo = function(pan) {
1855
+ var self = this;
1856
+ if (!self.ctx || !self.ctx.listener) {
1857
+ return self;
1858
+ }
1859
+ for (var i = self._howls.length - 1; i >= 0; i--) {
1860
+ self._howls[i].stereo(pan);
1861
+ }
1862
+ return self;
1863
+ };
1864
+ HowlerGlobal.prototype.pos = function(x, y, z) {
1865
+ var self = this;
1866
+ if (!self.ctx || !self.ctx.listener) {
1867
+ return self;
1868
+ }
1869
+ y = typeof y !== "number" ? self._pos[1] : y;
1870
+ z = typeof z !== "number" ? self._pos[2] : z;
1871
+ if (typeof x === "number") {
1872
+ self._pos = [x, y, z];
1873
+ if (typeof self.ctx.listener.positionX !== "undefined") {
1874
+ self.ctx.listener.positionX.setTargetAtTime(self._pos[0], Howler.ctx.currentTime, 0.1);
1875
+ self.ctx.listener.positionY.setTargetAtTime(self._pos[1], Howler.ctx.currentTime, 0.1);
1876
+ self.ctx.listener.positionZ.setTargetAtTime(self._pos[2], Howler.ctx.currentTime, 0.1);
1877
+ } else {
1878
+ self.ctx.listener.setPosition(self._pos[0], self._pos[1], self._pos[2]);
1879
+ }
1880
+ } else {
1881
+ return self._pos;
1882
+ }
1883
+ return self;
1884
+ };
1885
+ HowlerGlobal.prototype.orientation = function(x, y, z, xUp, yUp, zUp) {
1886
+ var self = this;
1887
+ if (!self.ctx || !self.ctx.listener) {
1888
+ return self;
1889
+ }
1890
+ var or = self._orientation;
1891
+ y = typeof y !== "number" ? or[1] : y;
1892
+ z = typeof z !== "number" ? or[2] : z;
1893
+ xUp = typeof xUp !== "number" ? or[3] : xUp;
1894
+ yUp = typeof yUp !== "number" ? or[4] : yUp;
1895
+ zUp = typeof zUp !== "number" ? or[5] : zUp;
1896
+ if (typeof x === "number") {
1897
+ self._orientation = [x, y, z, xUp, yUp, zUp];
1898
+ if (typeof self.ctx.listener.forwardX !== "undefined") {
1899
+ self.ctx.listener.forwardX.setTargetAtTime(x, Howler.ctx.currentTime, 0.1);
1900
+ self.ctx.listener.forwardY.setTargetAtTime(y, Howler.ctx.currentTime, 0.1);
1901
+ self.ctx.listener.forwardZ.setTargetAtTime(z, Howler.ctx.currentTime, 0.1);
1902
+ self.ctx.listener.upX.setTargetAtTime(xUp, Howler.ctx.currentTime, 0.1);
1903
+ self.ctx.listener.upY.setTargetAtTime(yUp, Howler.ctx.currentTime, 0.1);
1904
+ self.ctx.listener.upZ.setTargetAtTime(zUp, Howler.ctx.currentTime, 0.1);
1905
+ } else {
1906
+ self.ctx.listener.setOrientation(x, y, z, xUp, yUp, zUp);
1907
+ }
1908
+ } else {
1909
+ return or;
1910
+ }
1911
+ return self;
1912
+ };
1913
+ Howl.prototype.init = /* @__PURE__ */ (function(_super) {
1914
+ return function(o) {
1915
+ var self = this;
1916
+ self._orientation = o.orientation || [1, 0, 0];
1917
+ self._stereo = o.stereo || null;
1918
+ self._pos = o.pos || null;
1919
+ self._pannerAttr = {
1920
+ coneInnerAngle: typeof o.coneInnerAngle !== "undefined" ? o.coneInnerAngle : 360,
1921
+ coneOuterAngle: typeof o.coneOuterAngle !== "undefined" ? o.coneOuterAngle : 360,
1922
+ coneOuterGain: typeof o.coneOuterGain !== "undefined" ? o.coneOuterGain : 0,
1923
+ distanceModel: typeof o.distanceModel !== "undefined" ? o.distanceModel : "inverse",
1924
+ maxDistance: typeof o.maxDistance !== "undefined" ? o.maxDistance : 1e4,
1925
+ panningModel: typeof o.panningModel !== "undefined" ? o.panningModel : "HRTF",
1926
+ refDistance: typeof o.refDistance !== "undefined" ? o.refDistance : 1,
1927
+ rolloffFactor: typeof o.rolloffFactor !== "undefined" ? o.rolloffFactor : 1
1928
+ };
1929
+ self._onstereo = o.onstereo ? [{ fn: o.onstereo }] : [];
1930
+ self._onpos = o.onpos ? [{ fn: o.onpos }] : [];
1931
+ self._onorientation = o.onorientation ? [{ fn: o.onorientation }] : [];
1932
+ return _super.call(this, o);
1933
+ };
1934
+ })(Howl.prototype.init);
1935
+ Howl.prototype.stereo = function(pan, id) {
1936
+ var self = this;
1937
+ if (!self._webAudio) {
1938
+ return self;
1939
+ }
1940
+ if (self._state !== "loaded") {
1941
+ self._queue.push({
1942
+ event: "stereo",
1943
+ action: function() {
1944
+ self.stereo(pan, id);
1945
+ }
1946
+ });
1947
+ return self;
1948
+ }
1949
+ var pannerType = typeof Howler.ctx.createStereoPanner === "undefined" ? "spatial" : "stereo";
1950
+ if (typeof id === "undefined") {
1951
+ if (typeof pan === "number") {
1952
+ self._stereo = pan;
1953
+ self._pos = [pan, 0, 0];
1954
+ } else {
1955
+ return self._stereo;
1956
+ }
1957
+ }
1958
+ var ids = self._getSoundIds(id);
1959
+ for (var i = 0; i < ids.length; i++) {
1960
+ var sound = self._soundById(ids[i]);
1961
+ if (sound) {
1962
+ if (typeof pan === "number") {
1963
+ sound._stereo = pan;
1964
+ sound._pos = [pan, 0, 0];
1965
+ if (sound._node) {
1966
+ sound._pannerAttr.panningModel = "equalpower";
1967
+ if (!sound._panner || !sound._panner.pan) {
1968
+ setupPanner(sound, pannerType);
1969
+ }
1970
+ if (pannerType === "spatial") {
1971
+ if (typeof sound._panner.positionX !== "undefined") {
1972
+ sound._panner.positionX.setValueAtTime(pan, Howler.ctx.currentTime);
1973
+ sound._panner.positionY.setValueAtTime(0, Howler.ctx.currentTime);
1974
+ sound._panner.positionZ.setValueAtTime(0, Howler.ctx.currentTime);
1975
+ } else {
1976
+ sound._panner.setPosition(pan, 0, 0);
1977
+ }
1978
+ } else {
1979
+ sound._panner.pan.setValueAtTime(pan, Howler.ctx.currentTime);
1980
+ }
1981
+ }
1982
+ self._emit("stereo", sound._id);
1983
+ } else {
1984
+ return sound._stereo;
1985
+ }
1986
+ }
1987
+ }
1988
+ return self;
1989
+ };
1990
+ Howl.prototype.pos = function(x, y, z, id) {
1991
+ var self = this;
1992
+ if (!self._webAudio) {
1993
+ return self;
1994
+ }
1995
+ if (self._state !== "loaded") {
1996
+ self._queue.push({
1997
+ event: "pos",
1998
+ action: function() {
1999
+ self.pos(x, y, z, id);
2000
+ }
2001
+ });
2002
+ return self;
2003
+ }
2004
+ y = typeof y !== "number" ? 0 : y;
2005
+ z = typeof z !== "number" ? -0.5 : z;
2006
+ if (typeof id === "undefined") {
2007
+ if (typeof x === "number") {
2008
+ self._pos = [x, y, z];
2009
+ } else {
2010
+ return self._pos;
2011
+ }
2012
+ }
2013
+ var ids = self._getSoundIds(id);
2014
+ for (var i = 0; i < ids.length; i++) {
2015
+ var sound = self._soundById(ids[i]);
2016
+ if (sound) {
2017
+ if (typeof x === "number") {
2018
+ sound._pos = [x, y, z];
2019
+ if (sound._node) {
2020
+ if (!sound._panner || sound._panner.pan) {
2021
+ setupPanner(sound, "spatial");
2022
+ }
2023
+ if (typeof sound._panner.positionX !== "undefined") {
2024
+ sound._panner.positionX.setValueAtTime(x, Howler.ctx.currentTime);
2025
+ sound._panner.positionY.setValueAtTime(y, Howler.ctx.currentTime);
2026
+ sound._panner.positionZ.setValueAtTime(z, Howler.ctx.currentTime);
2027
+ } else {
2028
+ sound._panner.setPosition(x, y, z);
2029
+ }
2030
+ }
2031
+ self._emit("pos", sound._id);
2032
+ } else {
2033
+ return sound._pos;
2034
+ }
2035
+ }
2036
+ }
2037
+ return self;
2038
+ };
2039
+ Howl.prototype.orientation = function(x, y, z, id) {
2040
+ var self = this;
2041
+ if (!self._webAudio) {
2042
+ return self;
2043
+ }
2044
+ if (self._state !== "loaded") {
2045
+ self._queue.push({
2046
+ event: "orientation",
2047
+ action: function() {
2048
+ self.orientation(x, y, z, id);
2049
+ }
2050
+ });
2051
+ return self;
2052
+ }
2053
+ y = typeof y !== "number" ? self._orientation[1] : y;
2054
+ z = typeof z !== "number" ? self._orientation[2] : z;
2055
+ if (typeof id === "undefined") {
2056
+ if (typeof x === "number") {
2057
+ self._orientation = [x, y, z];
2058
+ } else {
2059
+ return self._orientation;
2060
+ }
2061
+ }
2062
+ var ids = self._getSoundIds(id);
2063
+ for (var i = 0; i < ids.length; i++) {
2064
+ var sound = self._soundById(ids[i]);
2065
+ if (sound) {
2066
+ if (typeof x === "number") {
2067
+ sound._orientation = [x, y, z];
2068
+ if (sound._node) {
2069
+ if (!sound._panner) {
2070
+ if (!sound._pos) {
2071
+ sound._pos = self._pos || [0, 0, -0.5];
2072
+ }
2073
+ setupPanner(sound, "spatial");
2074
+ }
2075
+ if (typeof sound._panner.orientationX !== "undefined") {
2076
+ sound._panner.orientationX.setValueAtTime(x, Howler.ctx.currentTime);
2077
+ sound._panner.orientationY.setValueAtTime(y, Howler.ctx.currentTime);
2078
+ sound._panner.orientationZ.setValueAtTime(z, Howler.ctx.currentTime);
2079
+ } else {
2080
+ sound._panner.setOrientation(x, y, z);
2081
+ }
2082
+ }
2083
+ self._emit("orientation", sound._id);
2084
+ } else {
2085
+ return sound._orientation;
2086
+ }
2087
+ }
2088
+ }
2089
+ return self;
2090
+ };
2091
+ Howl.prototype.pannerAttr = function() {
2092
+ var self = this;
2093
+ var args = arguments;
2094
+ var o, id, sound;
2095
+ if (!self._webAudio) {
2096
+ return self;
2097
+ }
2098
+ if (args.length === 0) {
2099
+ return self._pannerAttr;
2100
+ } else if (args.length === 1) {
2101
+ if (typeof args[0] === "object") {
2102
+ o = args[0];
2103
+ if (typeof id === "undefined") {
2104
+ if (!o.pannerAttr) {
2105
+ o.pannerAttr = {
2106
+ coneInnerAngle: o.coneInnerAngle,
2107
+ coneOuterAngle: o.coneOuterAngle,
2108
+ coneOuterGain: o.coneOuterGain,
2109
+ distanceModel: o.distanceModel,
2110
+ maxDistance: o.maxDistance,
2111
+ refDistance: o.refDistance,
2112
+ rolloffFactor: o.rolloffFactor,
2113
+ panningModel: o.panningModel
2114
+ };
2115
+ }
2116
+ self._pannerAttr = {
2117
+ coneInnerAngle: typeof o.pannerAttr.coneInnerAngle !== "undefined" ? o.pannerAttr.coneInnerAngle : self._coneInnerAngle,
2118
+ coneOuterAngle: typeof o.pannerAttr.coneOuterAngle !== "undefined" ? o.pannerAttr.coneOuterAngle : self._coneOuterAngle,
2119
+ coneOuterGain: typeof o.pannerAttr.coneOuterGain !== "undefined" ? o.pannerAttr.coneOuterGain : self._coneOuterGain,
2120
+ distanceModel: typeof o.pannerAttr.distanceModel !== "undefined" ? o.pannerAttr.distanceModel : self._distanceModel,
2121
+ maxDistance: typeof o.pannerAttr.maxDistance !== "undefined" ? o.pannerAttr.maxDistance : self._maxDistance,
2122
+ refDistance: typeof o.pannerAttr.refDistance !== "undefined" ? o.pannerAttr.refDistance : self._refDistance,
2123
+ rolloffFactor: typeof o.pannerAttr.rolloffFactor !== "undefined" ? o.pannerAttr.rolloffFactor : self._rolloffFactor,
2124
+ panningModel: typeof o.pannerAttr.panningModel !== "undefined" ? o.pannerAttr.panningModel : self._panningModel
2125
+ };
2126
+ }
2127
+ } else {
2128
+ sound = self._soundById(parseInt(args[0], 10));
2129
+ return sound ? sound._pannerAttr : self._pannerAttr;
2130
+ }
2131
+ } else if (args.length === 2) {
2132
+ o = args[0];
2133
+ id = parseInt(args[1], 10);
2134
+ }
2135
+ var ids = self._getSoundIds(id);
2136
+ for (var i = 0; i < ids.length; i++) {
2137
+ sound = self._soundById(ids[i]);
2138
+ if (sound) {
2139
+ var pa = sound._pannerAttr;
2140
+ pa = {
2141
+ coneInnerAngle: typeof o.coneInnerAngle !== "undefined" ? o.coneInnerAngle : pa.coneInnerAngle,
2142
+ coneOuterAngle: typeof o.coneOuterAngle !== "undefined" ? o.coneOuterAngle : pa.coneOuterAngle,
2143
+ coneOuterGain: typeof o.coneOuterGain !== "undefined" ? o.coneOuterGain : pa.coneOuterGain,
2144
+ distanceModel: typeof o.distanceModel !== "undefined" ? o.distanceModel : pa.distanceModel,
2145
+ maxDistance: typeof o.maxDistance !== "undefined" ? o.maxDistance : pa.maxDistance,
2146
+ refDistance: typeof o.refDistance !== "undefined" ? o.refDistance : pa.refDistance,
2147
+ rolloffFactor: typeof o.rolloffFactor !== "undefined" ? o.rolloffFactor : pa.rolloffFactor,
2148
+ panningModel: typeof o.panningModel !== "undefined" ? o.panningModel : pa.panningModel
2149
+ };
2150
+ var panner = sound._panner;
2151
+ if (!panner) {
2152
+ if (!sound._pos) {
2153
+ sound._pos = self._pos || [0, 0, -0.5];
2154
+ }
2155
+ setupPanner(sound, "spatial");
2156
+ panner = sound._panner;
2157
+ }
2158
+ panner.coneInnerAngle = pa.coneInnerAngle;
2159
+ panner.coneOuterAngle = pa.coneOuterAngle;
2160
+ panner.coneOuterGain = pa.coneOuterGain;
2161
+ panner.distanceModel = pa.distanceModel;
2162
+ panner.maxDistance = pa.maxDistance;
2163
+ panner.refDistance = pa.refDistance;
2164
+ panner.rolloffFactor = pa.rolloffFactor;
2165
+ panner.panningModel = pa.panningModel;
2166
+ }
2167
+ }
2168
+ return self;
2169
+ };
2170
+ Sound.prototype.init = /* @__PURE__ */ (function(_super) {
2171
+ return function() {
2172
+ var self = this;
2173
+ var parent = self._parent;
2174
+ self._orientation = parent._orientation;
2175
+ self._stereo = parent._stereo;
2176
+ self._pos = parent._pos;
2177
+ self._pannerAttr = parent._pannerAttr;
2178
+ _super.call(this);
2179
+ if (self._stereo) {
2180
+ parent.stereo(self._stereo);
2181
+ } else if (self._pos) {
2182
+ parent.pos(self._pos[0], self._pos[1], self._pos[2], self._id);
2183
+ }
2184
+ };
2185
+ })(Sound.prototype.init);
2186
+ Sound.prototype.reset = /* @__PURE__ */ (function(_super) {
2187
+ return function() {
2188
+ var self = this;
2189
+ var parent = self._parent;
2190
+ self._orientation = parent._orientation;
2191
+ self._stereo = parent._stereo;
2192
+ self._pos = parent._pos;
2193
+ self._pannerAttr = parent._pannerAttr;
2194
+ if (self._stereo) {
2195
+ parent.stereo(self._stereo);
2196
+ } else if (self._pos) {
2197
+ parent.pos(self._pos[0], self._pos[1], self._pos[2], self._id);
2198
+ } else if (self._panner) {
2199
+ self._panner.disconnect(0);
2200
+ self._panner = void 0;
2201
+ parent._refreshBuffer(self);
2202
+ }
2203
+ return _super.call(this);
2204
+ };
2205
+ })(Sound.prototype.reset);
2206
+ var setupPanner = function(sound, type) {
2207
+ type = type || "spatial";
2208
+ if (type === "spatial") {
2209
+ sound._panner = Howler.ctx.createPanner();
2210
+ sound._panner.coneInnerAngle = sound._pannerAttr.coneInnerAngle;
2211
+ sound._panner.coneOuterAngle = sound._pannerAttr.coneOuterAngle;
2212
+ sound._panner.coneOuterGain = sound._pannerAttr.coneOuterGain;
2213
+ sound._panner.distanceModel = sound._pannerAttr.distanceModel;
2214
+ sound._panner.maxDistance = sound._pannerAttr.maxDistance;
2215
+ sound._panner.refDistance = sound._pannerAttr.refDistance;
2216
+ sound._panner.rolloffFactor = sound._pannerAttr.rolloffFactor;
2217
+ sound._panner.panningModel = sound._pannerAttr.panningModel;
2218
+ if (typeof sound._panner.positionX !== "undefined") {
2219
+ sound._panner.positionX.setValueAtTime(sound._pos[0], Howler.ctx.currentTime);
2220
+ sound._panner.positionY.setValueAtTime(sound._pos[1], Howler.ctx.currentTime);
2221
+ sound._panner.positionZ.setValueAtTime(sound._pos[2], Howler.ctx.currentTime);
2222
+ } else {
2223
+ sound._panner.setPosition(sound._pos[0], sound._pos[1], sound._pos[2]);
2224
+ }
2225
+ if (typeof sound._panner.orientationX !== "undefined") {
2226
+ sound._panner.orientationX.setValueAtTime(sound._orientation[0], Howler.ctx.currentTime);
2227
+ sound._panner.orientationY.setValueAtTime(sound._orientation[1], Howler.ctx.currentTime);
2228
+ sound._panner.orientationZ.setValueAtTime(sound._orientation[2], Howler.ctx.currentTime);
2229
+ } else {
2230
+ sound._panner.setOrientation(sound._orientation[0], sound._orientation[1], sound._orientation[2]);
2231
+ }
2232
+ } else {
2233
+ sound._panner = Howler.ctx.createStereoPanner();
2234
+ sound._panner.pan.setValueAtTime(sound._stereo, Howler.ctx.currentTime);
2235
+ }
2236
+ sound._panner.connect(sound._node);
2237
+ if (!sound._paused) {
2238
+ sound._parent.pause(sound._id, true).play(sound._id, true);
2239
+ }
2240
+ };
2241
+ })();
2242
+ }
2243
+ });
2244
+
1
2245
  // src/plugin.ts
2
2246
  import { tool } from "@opencode-ai/plugin";
3
2247
 
@@ -39,90 +2283,661 @@ function getProvider(name) {
39
2283
  "system"
40
2284
  );
41
2285
  }
42
- return provider;
43
- }
44
- function listProviders() {
45
- return Array.from(providers.keys());
46
- }
47
- function hasProvider(name) {
48
- return providers.has(name);
2286
+ return provider;
2287
+ }
2288
+ function listProviders() {
2289
+ return Array.from(providers.keys());
2290
+ }
2291
+ function hasProvider(name) {
2292
+ return providers.has(name);
2293
+ }
2294
+ var BaseTTSProvider = class {
2295
+ apiKey;
2296
+ defaultVoice;
2297
+ defaultModel = "stream";
2298
+ async initialize() {
2299
+ }
2300
+ async destroy() {
2301
+ }
2302
+ /**
2303
+ * 通用 speak 实现,处理通用逻辑
2304
+ */
2305
+ async speak(text, options) {
2306
+ if (!text || text.trim().length === 0) {
2307
+ throw new TTSError(
2308
+ "Text cannot be empty",
2309
+ "INVALID_PARAMS" /* INVALID_PARAMS */,
2310
+ this.name
2311
+ );
2312
+ }
2313
+ const voice = options?.voice || this.defaultVoice;
2314
+ const model = options?.model || this.defaultModel;
2315
+ return this.doSpeak(text, voice, model, options);
2316
+ }
2317
+ pause() {
2318
+ throw new TTSError(
2319
+ "Pause is not supported by this provider",
2320
+ "UNKNOWN" /* UNKNOWN */,
2321
+ this.name
2322
+ );
2323
+ }
2324
+ resume() {
2325
+ throw new TTSError(
2326
+ "Resume is not supported by this provider",
2327
+ "UNKNOWN" /* UNKNOWN */,
2328
+ this.name
2329
+ );
2330
+ }
2331
+ stop() {
2332
+ return Promise.resolve();
2333
+ }
2334
+ async listVoices() {
2335
+ return [];
2336
+ }
2337
+ getCapabilities() {
2338
+ return this.capabilities;
2339
+ }
2340
+ /**
2341
+ * 验证 API Key
2342
+ */
2343
+ validateApiKey() {
2344
+ if (!this.apiKey) {
2345
+ throw new TTSError(
2346
+ `API key is required for provider "${this.name}"`,
2347
+ "AUTH" /* AUTH */,
2348
+ this.name
2349
+ );
2350
+ }
2351
+ }
2352
+ };
2353
+
2354
+ // src/core/player.ts
2355
+ import { EventEmitter } from "events";
2356
+ import fs from "fs";
2357
+ import { createWriteStream } from "fs";
2358
+ import { tmpdir as tmpdir4 } from "os";
2359
+ import { join as join4 } from "path";
2360
+
2361
+ // src/core/backends/naudiodon-backend.ts
2362
+ var UnsupportedError = class extends Error {
2363
+ constructor(message) {
2364
+ super(message);
2365
+ this.name = "UnsupportedError";
2366
+ }
2367
+ };
2368
+ var NaudiodonBackend = class {
2369
+ name = "naudiodon";
2370
+ supportsStreaming = true;
2371
+ audioOutput;
2372
+ events;
2373
+ _started = false;
2374
+ _paused = false;
2375
+ _stopped = false;
2376
+ sampleRate;
2377
+ channels;
2378
+ volume = 1;
2379
+ bytesWritten = 0;
2380
+ constructor(options = {}) {
2381
+ this.sampleRate = options.sampleRate || 16e3;
2382
+ this.channels = options.channels || 1;
2383
+ this.events = options.events;
2384
+ this.volume = options.volume ?? 1;
2385
+ }
2386
+ start(_filePath) {
2387
+ if (this._started) return;
2388
+ try {
2389
+ const naudiodon = __require("naudiodon");
2390
+ const AudioOutput = naudiodon;
2391
+ this.audioOutput = new AudioOutput({
2392
+ sampleRate: this.sampleRate,
2393
+ channels: this.channels,
2394
+ bitDepth: 16
2395
+ });
2396
+ this.audioOutput.on("error", (error) => {
2397
+ this.handleError(error);
2398
+ });
2399
+ this.audioOutput.start();
2400
+ this._started = true;
2401
+ this._stopped = false;
2402
+ this.events?.onStart?.();
2403
+ } catch (error) {
2404
+ if (error.code === "MODULE_NOT_FOUND") {
2405
+ throw new Error("naudiodon is not installed. Run: npm install naudiodon");
2406
+ }
2407
+ throw error;
2408
+ }
2409
+ }
2410
+ write(chunk) {
2411
+ if (!this._started || this._stopped) return;
2412
+ if (this.audioOutput) {
2413
+ const adjustedChunk = this.adjustVolume(chunk);
2414
+ this.audioOutput.write(adjustedChunk);
2415
+ this.bytesWritten += chunk.length;
2416
+ this.events?.onProgress?.(this.bytesWritten);
2417
+ }
2418
+ }
2419
+ end() {
2420
+ if (this.audioOutput) {
2421
+ this.audioOutput.end();
2422
+ }
2423
+ }
2424
+ pause() {
2425
+ if (!this._started || this._paused || this._stopped) return;
2426
+ throw new UnsupportedError("naudiodon backend does not support pause");
2427
+ }
2428
+ resume() {
2429
+ if (!this._paused || this._stopped) return;
2430
+ this._paused = false;
2431
+ this.events?.onResume?.();
2432
+ }
2433
+ stop() {
2434
+ this._stopped = true;
2435
+ this._started = false;
2436
+ this._paused = false;
2437
+ if (this.audioOutput) {
2438
+ try {
2439
+ this.audioOutput.quit();
2440
+ } catch (e) {
2441
+ }
2442
+ this.audioOutput = void 0;
2443
+ }
2444
+ this.bytesWritten = 0;
2445
+ this.events?.onStop?.();
2446
+ }
2447
+ getCurrentTime() {
2448
+ return this.bytesWritten / (this.sampleRate * this.channels * 2);
2449
+ }
2450
+ getDuration() {
2451
+ return void 0;
2452
+ }
2453
+ setVolume(volume) {
2454
+ this.volume = Math.max(0, Math.min(1, volume));
2455
+ }
2456
+ destroy() {
2457
+ this.stop();
2458
+ }
2459
+ adjustVolume(chunk) {
2460
+ if (this.volume === 1) return chunk;
2461
+ const adjusted = Buffer.alloc(chunk.length);
2462
+ for (let i = 0; i < chunk.length; i += 2) {
2463
+ const sample = chunk.readInt16LE(i) * this.volume;
2464
+ adjusted.writeInt16LE(Math.round(sample), i);
2465
+ }
2466
+ return adjusted;
2467
+ }
2468
+ handleError(error) {
2469
+ this.events?.onError?.(error);
2470
+ }
2471
+ };
2472
+
2473
+ // src/core/backends/afplay-backend.ts
2474
+ import { execFile } from "child_process";
2475
+ import { tmpdir } from "os";
2476
+ import { join } from "path";
2477
+ import { writeFileSync, unlinkSync, existsSync } from "fs";
2478
+ var SAFE_PATH_REGEX = /^[\w\/\.]+$/;
2479
+ var AfplayBackend = class {
2480
+ name = "afplay";
2481
+ supportsStreaming = false;
2482
+ process;
2483
+ tempFile;
2484
+ events;
2485
+ _started = false;
2486
+ _paused = false;
2487
+ _stopped = false;
2488
+ // P0-4: 缓冲所有chunk,等end()时一次性写入文件
2489
+ chunks = [];
2490
+ hasEnded = false;
2491
+ constructor(options = {}) {
2492
+ this.events = options.events;
2493
+ }
2494
+ start(filePath) {
2495
+ if (this._started) return;
2496
+ if (!SAFE_PATH_REGEX.test(filePath)) {
2497
+ throw new Error(`Invalid file path: ${filePath}`);
2498
+ }
2499
+ this.tempFile = filePath;
2500
+ this._started = true;
2501
+ this._stopped = false;
2502
+ this.events?.onStart?.();
2503
+ this.process = execFile("afplay", [filePath], (error) => {
2504
+ if (this._stopped) return;
2505
+ if (error) {
2506
+ this.handleError(error);
2507
+ return;
2508
+ }
2509
+ this._started = false;
2510
+ this.events?.onEnd?.();
2511
+ });
2512
+ this.process.on("error", (error) => {
2513
+ this.handleError(error);
2514
+ });
2515
+ }
2516
+ write(chunk) {
2517
+ if (this._stopped) return;
2518
+ this.chunks.push(chunk);
2519
+ }
2520
+ end() {
2521
+ if (this._stopped || this.hasEnded) return;
2522
+ this.hasEnded = true;
2523
+ if (this.chunks.length === 0) return;
2524
+ this.tempFile = join(tmpdir(), `ocosay-${Date.now()}.wav`);
2525
+ writeFileSync(this.tempFile, Buffer.concat(this.chunks));
2526
+ this.chunks = [];
2527
+ this.start(this.tempFile);
2528
+ }
2529
+ pause() {
2530
+ if (!this._started || this._paused || this._stopped) return;
2531
+ if (this.process) {
2532
+ try {
2533
+ this.process.kill("SIGSTOP");
2534
+ this._paused = true;
2535
+ this.events?.onPause?.();
2536
+ } catch (e) {
2537
+ }
2538
+ }
2539
+ }
2540
+ resume() {
2541
+ if (!this._paused || this._stopped) return;
2542
+ if (this.process) {
2543
+ try {
2544
+ this.process.kill("SIGCONT");
2545
+ this._paused = false;
2546
+ this.events?.onResume?.();
2547
+ } catch (e) {
2548
+ }
2549
+ }
2550
+ }
2551
+ stop() {
2552
+ this._stopped = true;
2553
+ this._started = false;
2554
+ this._paused = false;
2555
+ if (this.process) {
2556
+ try {
2557
+ this.process.kill("SIGTERM");
2558
+ } catch (e) {
2559
+ }
2560
+ this.process = void 0;
2561
+ }
2562
+ this.cleanup();
2563
+ this.chunks = [];
2564
+ this.hasEnded = false;
2565
+ this.events?.onStop?.();
2566
+ }
2567
+ setVolume(_volume) {
2568
+ }
2569
+ destroy() {
2570
+ this.stop();
2571
+ }
2572
+ cleanup() {
2573
+ if (this.tempFile && this.tempFile.startsWith(tmpdir())) {
2574
+ try {
2575
+ if (existsSync(this.tempFile)) {
2576
+ unlinkSync(this.tempFile);
2577
+ }
2578
+ } catch (e) {
2579
+ }
2580
+ this.tempFile = void 0;
2581
+ }
2582
+ }
2583
+ handleError(error) {
2584
+ this.events?.onError?.(error);
2585
+ }
2586
+ };
2587
+
2588
+ // src/core/backends/aplay-backend.ts
2589
+ import { execFile as execFile2 } from "child_process";
2590
+ import { tmpdir as tmpdir2 } from "os";
2591
+ import { join as join2 } from "path";
2592
+ import { writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, existsSync as existsSync2 } from "fs";
2593
+ var SAFE_PATH_REGEX2 = /^[\w\/\.]+$/;
2594
+ var AplayBackend = class {
2595
+ name = "aplay";
2596
+ supportsStreaming = false;
2597
+ process;
2598
+ tempFile;
2599
+ events;
2600
+ _started = false;
2601
+ _paused = false;
2602
+ _stopped = false;
2603
+ // P0-4: 缓冲所有chunk,等end()时一次性写入文件
2604
+ chunks = [];
2605
+ hasEnded = false;
2606
+ constructor(options = {}) {
2607
+ this.events = options.events;
2608
+ }
2609
+ start(filePath) {
2610
+ if (this._started) return;
2611
+ if (!SAFE_PATH_REGEX2.test(filePath)) {
2612
+ throw new Error(`Invalid file path: ${filePath}`);
2613
+ }
2614
+ this.tempFile = filePath;
2615
+ this._started = true;
2616
+ this._stopped = false;
2617
+ this.events?.onStart?.();
2618
+ this.process = execFile2("aplay", [filePath], (error) => {
2619
+ if (this._stopped) return;
2620
+ if (error) {
2621
+ this.handleError(error);
2622
+ return;
2623
+ }
2624
+ this._started = false;
2625
+ this.events?.onEnd?.();
2626
+ });
2627
+ this.process.on("error", (error) => {
2628
+ this.handleError(error);
2629
+ });
2630
+ }
2631
+ write(chunk) {
2632
+ if (this._stopped) return;
2633
+ this.chunks.push(chunk);
2634
+ }
2635
+ end() {
2636
+ if (this._stopped || this.hasEnded) return;
2637
+ this.hasEnded = true;
2638
+ if (this.chunks.length === 0) return;
2639
+ this.tempFile = join2(tmpdir2(), `ocosay-${Date.now()}.wav`);
2640
+ writeFileSync2(this.tempFile, Buffer.concat(this.chunks));
2641
+ this.chunks = [];
2642
+ this.start(this.tempFile);
2643
+ }
2644
+ pause() {
2645
+ if (!this._started || this._paused || this._stopped) return;
2646
+ if (this.process) {
2647
+ try {
2648
+ this.process.kill("SIGSTOP");
2649
+ this._paused = true;
2650
+ this.events?.onPause?.();
2651
+ } catch (e) {
2652
+ }
2653
+ }
2654
+ }
2655
+ resume() {
2656
+ if (!this._paused || this._stopped) return;
2657
+ if (this.process) {
2658
+ try {
2659
+ this.process.kill("SIGCONT");
2660
+ this._paused = false;
2661
+ this.events?.onResume?.();
2662
+ } catch (e) {
2663
+ }
2664
+ }
2665
+ }
2666
+ stop() {
2667
+ this._stopped = true;
2668
+ this._started = false;
2669
+ this._paused = false;
2670
+ if (this.process) {
2671
+ try {
2672
+ this.process.kill("SIGTERM");
2673
+ } catch (e) {
2674
+ }
2675
+ this.process = void 0;
2676
+ }
2677
+ this.cleanup();
2678
+ this.chunks = [];
2679
+ this.hasEnded = false;
2680
+ this.events?.onStop?.();
2681
+ }
2682
+ setVolume(_volume) {
2683
+ }
2684
+ destroy() {
2685
+ this.stop();
2686
+ }
2687
+ cleanup() {
2688
+ if (this.tempFile && this.tempFile.startsWith(tmpdir2())) {
2689
+ try {
2690
+ if (existsSync2(this.tempFile)) {
2691
+ unlinkSync2(this.tempFile);
2692
+ }
2693
+ } catch (e) {
2694
+ }
2695
+ this.tempFile = void 0;
2696
+ }
2697
+ }
2698
+ handleError(error) {
2699
+ this.events?.onError?.(error);
2700
+ }
2701
+ };
2702
+
2703
+ // src/core/backends/powershell-backend.ts
2704
+ import { spawn } from "child_process";
2705
+ import { tmpdir as tmpdir3 } from "os";
2706
+ import { join as join3 } from "path";
2707
+ import { writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, existsSync as existsSync3 } from "fs";
2708
+ var SAFE_PATH_REGEX3 = /^[\w\:\\_.]+$/i;
2709
+ function wslPathToWindows(wslPath) {
2710
+ return wslPath.replace(/^\/mnt\/([a-z])\//, "$1:/").replace(/\//g, "\\");
49
2711
  }
50
- var BaseTTSProvider = class {
51
- apiKey;
52
- defaultVoice;
53
- defaultModel = "stream";
54
- async initialize() {
2712
+ var UnsupportedError2 = class extends Error {
2713
+ constructor(message) {
2714
+ super(message);
2715
+ this.name = "UnsupportedError";
55
2716
  }
56
- async destroy() {
2717
+ };
2718
+ var PowerShellBackend = class {
2719
+ name = "powershell";
2720
+ supportsStreaming = false;
2721
+ process;
2722
+ tempFile;
2723
+ events;
2724
+ _started = false;
2725
+ _paused = false;
2726
+ _stopped = false;
2727
+ // P0-4: 缓冲所有chunk,等end()时一次性写入文件
2728
+ chunks = [];
2729
+ hasEnded = false;
2730
+ constructor(options = {}) {
2731
+ this.events = options.events;
57
2732
  }
58
- /**
59
- * 通用 speak 实现,处理通用逻辑
60
- */
61
- async speak(text, options) {
62
- if (!text || text.trim().length === 0) {
63
- throw new TTSError(
64
- "Text cannot be empty",
65
- "INVALID_PARAMS" /* INVALID_PARAMS */,
66
- this.name
67
- );
2733
+ start(filePath) {
2734
+ if (this._started) return;
2735
+ if (isWsl()) {
2736
+ filePath = wslPathToWindows(filePath);
68
2737
  }
69
- const voice = options?.voice || this.defaultVoice;
70
- const model = options?.model || this.defaultModel;
71
- return this.doSpeak(text, voice, model, options);
2738
+ if (!SAFE_PATH_REGEX3.test(filePath)) {
2739
+ throw new Error(`Invalid file path: ${filePath}`);
2740
+ }
2741
+ this.tempFile = filePath;
2742
+ this._started = true;
2743
+ this._stopped = false;
2744
+ this.events?.onStart?.();
2745
+ const escapedPath = filePath.replace(/'/g, "''");
2746
+ const psScript = `$sound = New-Object System.Media.SoundPlayer('${escapedPath}'); $sound.PlayAsync()`;
2747
+ const scriptFile = join3(tmpdir3(), `ocosay-${Date.now()}.ps1`);
2748
+ writeFileSync3(scriptFile, psScript, { encoding: "utf8" });
2749
+ this.process = spawn("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptFile], {
2750
+ stdio: "ignore",
2751
+ detached: false
2752
+ });
2753
+ this.process.on("exit", () => {
2754
+ try {
2755
+ if (existsSync3(scriptFile)) {
2756
+ unlinkSync3(scriptFile);
2757
+ }
2758
+ } catch (e) {
2759
+ }
2760
+ });
2761
+ this.process.on("exit", (code) => {
2762
+ if (this._stopped) return;
2763
+ if (code === 0 || code === null) {
2764
+ this._started = false;
2765
+ this.events?.onEnd?.();
2766
+ } else {
2767
+ this.handleError(new Error(`PowerShell playback exited with code ${code}`));
2768
+ }
2769
+ });
2770
+ this.process.on("error", (error) => {
2771
+ this.handleError(error);
2772
+ });
2773
+ }
2774
+ write(chunk) {
2775
+ if (this._stopped) return;
2776
+ this.chunks.push(chunk);
2777
+ }
2778
+ end() {
2779
+ if (this._stopped || this.hasEnded) return;
2780
+ this.hasEnded = true;
2781
+ if (this.chunks.length === 0) return;
2782
+ this.tempFile = join3(tmpdir3(), `ocosay-${Date.now()}.wav`);
2783
+ writeFileSync3(this.tempFile, Buffer.concat(this.chunks));
2784
+ this.chunks = [];
2785
+ this.start(this.tempFile);
72
2786
  }
73
2787
  pause() {
74
- throw new TTSError(
75
- "Pause is not supported by this provider",
76
- "UNKNOWN" /* UNKNOWN */,
77
- this.name
78
- );
2788
+ if (!this._started || this._paused || this._stopped) return;
2789
+ throw new UnsupportedError2("pause is not supported by PowerShell SoundPlayer");
79
2790
  }
80
2791
  resume() {
81
- throw new TTSError(
82
- "Resume is not supported by this provider",
83
- "UNKNOWN" /* UNKNOWN */,
84
- this.name
85
- );
2792
+ if (!this._paused || this._stopped) return;
2793
+ throw new UnsupportedError2("resume is not supported by PowerShell SoundPlayer");
86
2794
  }
87
2795
  stop() {
88
- return Promise.resolve();
2796
+ this._stopped = true;
2797
+ this._started = false;
2798
+ this._paused = false;
2799
+ if (this.process) {
2800
+ try {
2801
+ this.process.kill("SIGTERM");
2802
+ } catch (e) {
2803
+ }
2804
+ this.process = void 0;
2805
+ }
2806
+ this.cleanup();
2807
+ this.chunks = [];
2808
+ this.hasEnded = false;
2809
+ this.events?.onStop?.();
89
2810
  }
90
- async listVoices() {
91
- return [];
2811
+ setVolume(_volume) {
92
2812
  }
93
- getCapabilities() {
94
- return this.capabilities;
2813
+ destroy() {
2814
+ this.stop();
95
2815
  }
96
- /**
97
- * 验证 API Key
98
- */
99
- validateApiKey() {
100
- if (!this.apiKey) {
101
- throw new TTSError(
102
- `API key is required for provider "${this.name}"`,
103
- "AUTH" /* AUTH */,
104
- this.name
105
- );
2816
+ cleanup() {
2817
+ if (this.tempFile && this.tempFile.startsWith(tmpdir3())) {
2818
+ try {
2819
+ if (existsSync3(this.tempFile)) {
2820
+ unlinkSync3(this.tempFile);
2821
+ }
2822
+ } catch (e) {
2823
+ }
2824
+ this.tempFile = void 0;
106
2825
  }
107
2826
  }
2827
+ handleError(error) {
2828
+ this.events?.onError?.(error);
2829
+ }
108
2830
  };
109
2831
 
2832
+ // src/core/backends/howler-backend.ts
2833
+ var import_howler = __toESM(require_howler(), 1);
2834
+
2835
+ // src/core/backends/index.ts
2836
+ function isNaudiodonAvailable() {
2837
+ try {
2838
+ __require.resolve("naudiodon");
2839
+ return true;
2840
+ } catch (e) {
2841
+ return false;
2842
+ }
2843
+ }
2844
+ function isWsl() {
2845
+ if (process.platform !== "linux") return false;
2846
+ try {
2847
+ return __require("fs").readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft");
2848
+ } catch {
2849
+ return false;
2850
+ }
2851
+ }
2852
+ function createBackend(type = "auto" /* AUTO */, options = {}) {
2853
+ const platform = process.platform;
2854
+ if (type !== "auto" /* AUTO */) {
2855
+ return createBackendByType(type, options);
2856
+ }
2857
+ if (isNaudiodonAvailable()) {
2858
+ try {
2859
+ const naudiodon = __require("naudiodon");
2860
+ if (naudiodon) {
2861
+ return new NaudiodonBackend(options);
2862
+ }
2863
+ } catch (e) {
2864
+ }
2865
+ }
2866
+ switch (platform) {
2867
+ case "darwin":
2868
+ return new AfplayBackend(options);
2869
+ case "linux":
2870
+ if (isWsl()) {
2871
+ return new PowerShellBackend(options);
2872
+ }
2873
+ return new AplayBackend(options);
2874
+ case "win32":
2875
+ return new PowerShellBackend(options);
2876
+ default:
2877
+ throw new Error(`Unsupported platform: ${platform}`);
2878
+ }
2879
+ }
2880
+ function createBackendByType(type, options) {
2881
+ switch (type) {
2882
+ case "naudiodon" /* NAUDIODON */:
2883
+ return new NaudiodonBackend(options);
2884
+ case "afplay" /* AFPLAY */:
2885
+ return new AfplayBackend(options);
2886
+ case "aplay" /* APLAY */:
2887
+ return new AplayBackend(options);
2888
+ case "powershell" /* POWERSHELL */:
2889
+ return new PowerShellBackend(options);
2890
+ default:
2891
+ throw new Error(`Unknown backend type: ${type}`);
2892
+ }
2893
+ }
2894
+
110
2895
  // src/core/player.ts
111
- import { EventEmitter } from "events";
112
- import { spawn } from "child_process";
113
- import fs from "fs";
114
- import { createWriteStream } from "fs";
115
- import { tmpdir } from "os";
116
- import { join } from "path";
117
2896
  var AudioPlayer = class extends EventEmitter {
118
2897
  constructor(events) {
119
2898
  super();
120
2899
  this.events = events;
2900
+ this.backend = createBackend("auto" /* AUTO */, {
2901
+ events: {
2902
+ onStart: () => {
2903
+ this.events?.onStart?.();
2904
+ this.emit("start");
2905
+ },
2906
+ onEnd: () => {
2907
+ this._playing = false;
2908
+ this.events?.onEnd?.();
2909
+ this.emit("end");
2910
+ this.cleanup();
2911
+ },
2912
+ onError: (error) => {
2913
+ this._playing = false;
2914
+ const ttsError = new TTSError(
2915
+ error.message || "Playback failed",
2916
+ "PLAYER_ERROR" /* PLAYER_ERROR */,
2917
+ "player"
2918
+ );
2919
+ this.events?.onError?.(ttsError);
2920
+ this.emit("error", ttsError);
2921
+ },
2922
+ onPause: () => {
2923
+ this.events?.onPause?.();
2924
+ this.emit("pause");
2925
+ },
2926
+ onResume: () => {
2927
+ this.events?.onResume?.();
2928
+ this.emit("resume");
2929
+ },
2930
+ onStop: () => {
2931
+ this.events?.onStop?.();
2932
+ this.emit("stop");
2933
+ }
2934
+ }
2935
+ });
121
2936
  }
122
2937
  events;
123
2938
  _playing = false;
124
2939
  _paused = false;
125
- currentProcess;
2940
+ backend;
126
2941
  currentFile;
127
2942
  /**
128
2943
  * 播放音频
@@ -136,7 +2951,7 @@ var AudioPlayer = class extends EventEmitter {
136
2951
  this._playing = true;
137
2952
  this._paused = false;
138
2953
  try {
139
- const tempFile = join(tmpdir(), `ocosay-${Date.now()}.${format}`);
2954
+ const tempFile = join4(tmpdir4(), `ocosay-${Date.now()}.${format}`);
140
2955
  this.currentFile = tempFile;
141
2956
  if (Buffer.isBuffer(audioData)) {
142
2957
  fs.writeFileSync(tempFile, audioData);
@@ -181,88 +2996,28 @@ var AudioPlayer = class extends EventEmitter {
181
2996
  }
182
2997
  /**
183
2998
  * 播放音频文件
184
- * 优先使用 afplay (macOS), aplay (Linux), 否则用 PowerShell (Windows)
2999
+ * 使用 AudioBackend 统一后端播放
185
3000
  */
186
- playFile(filePath, format) {
187
- const platform = process.platform;
188
- return new Promise((resolve, reject) => {
189
- let command;
190
- let args;
191
- if (platform === "darwin") {
192
- command = "afplay";
193
- args = [filePath];
194
- } else if (platform === "linux") {
195
- command = "aplay";
196
- args = [filePath];
197
- } else {
198
- command = "powershell";
199
- args = ["-c", `(New-Object System.Media.SoundPlayer('${filePath.replace(/\\/g, "\\\\")}')).PlaySync()`];
200
- }
201
- this.currentProcess = spawn(command, args, {
202
- stdio: "ignore",
203
- detached: false
204
- });
205
- this.currentProcess.on("exit", (code, signal) => {
206
- if (signal === "SIGTERM" || signal === "SIGINT") {
207
- resolve();
208
- } else if (code === 0) {
209
- resolve();
210
- } else {
211
- reject(new Error(`Player exited with code ${code}`));
212
- }
213
- });
214
- this.currentProcess.on("error", (error) => {
215
- reject(error);
216
- });
3001
+ playFile(filePath, _format) {
3002
+ return new Promise((_resolve, _reject) => {
3003
+ this.backend.start(filePath);
217
3004
  });
218
3005
  }
219
- /**
220
- * 暂停播放
221
- * 注意: 目前通过 SIGSTOP 实现,真正的 pause 需要支持暂停的音频库
222
- */
223
3006
  pause() {
224
3007
  if (!this._playing || this._paused) return;
225
- if (this.currentProcess) {
226
- try {
227
- this.currentProcess.kill("SIGSTOP");
228
- this._paused = true;
229
- this.events?.onPause?.();
230
- this.emit("pause");
231
- } catch (e) {
232
- }
233
- }
3008
+ this.backend.pause();
3009
+ this._paused = true;
234
3010
  }
235
- /**
236
- * 恢复播放
237
- */
238
3011
  resume() {
239
3012
  if (!this._playing || !this._paused) return;
240
- if (this.currentProcess) {
241
- try {
242
- this.currentProcess.kill("SIGCONT");
243
- this._paused = false;
244
- this.events?.onResume?.();
245
- this.emit("resume");
246
- } catch (e) {
247
- }
248
- }
3013
+ this.backend.resume();
3014
+ this._paused = false;
249
3015
  }
250
- /**
251
- * 停止播放
252
- */
253
3016
  async stop() {
254
3017
  this._playing = false;
255
3018
  this._paused = false;
256
- if (this.currentProcess) {
257
- try {
258
- this.currentProcess.kill("SIGTERM");
259
- } catch (e) {
260
- }
261
- this.currentProcess = void 0;
262
- }
3019
+ this.backend.stop();
263
3020
  this.cleanup();
264
- this.events?.onStop?.();
265
- this.emit("stop");
266
3021
  }
267
3022
  /**
268
3023
  * 是否正在播放
@@ -523,6 +3278,20 @@ async function listVoices(providerName) {
523
3278
  }
524
3279
 
525
3280
  // src/tools/tts.ts
3281
+ function extractTextArg(args) {
3282
+ if (!args || typeof args !== "object") {
3283
+ return void 0;
3284
+ }
3285
+ const argObj = args;
3286
+ const text = argObj.text;
3287
+ if (typeof text === "string" && text.trim().length > 0) {
3288
+ return text;
3289
+ }
3290
+ if (text !== void 0) {
3291
+ console.warn("[tts] text arg is not a valid string, type:", typeof text, "value:", String(text).substring(0, 100));
3292
+ }
3293
+ return void 0;
3294
+ }
526
3295
  async function handleToolCall(toolName, args) {
527
3296
  try {
528
3297
  switch (toolName) {
@@ -545,21 +3314,24 @@ async function handleToolCall(toolName, args) {
545
3314
  case "tts_resume":
546
3315
  resume();
547
3316
  return { success: true, message: "Resumed" };
548
- case "tts_list_voices":
3317
+ case "tts_list_voices": {
549
3318
  const voices = await listVoices(args?.provider);
550
3319
  return { success: true, voices };
551
- case "tts_list_providers":
3320
+ }
3321
+ case "tts_list_providers": {
552
3322
  const speaker2 = getDefaultSpeaker();
553
3323
  const providers2 = speaker2.getProviders();
554
3324
  return { success: true, providers: providers2 };
555
- case "tts_status":
3325
+ }
3326
+ case "tts_status": {
556
3327
  const s = getDefaultSpeaker();
557
3328
  return {
558
3329
  success: true,
559
3330
  isPlaying: s.isPlaying(),
560
3331
  isPaused: s.isPausedState()
561
3332
  };
562
- case "tts_stream_speak":
3333
+ }
3334
+ case "tts_stream_speak": {
563
3335
  if (!isAutoReadEnabled()) {
564
3336
  throw new TTSError(
565
3337
  "Stream mode is not enabled. autoRead must be enabled in configuration to use tts_stream_speak.",
@@ -578,11 +3350,10 @@ async function handleToolCall(toolName, args) {
578
3350
  const synthesizer = getStreamingSynthesizer();
579
3351
  if (streamReader2 && synthesizer) {
580
3352
  streamReader2.start();
581
- if (args?.text !== void 0 && typeof args.text === "string") {
582
- console.log("[tts_stream_speak] synthesizing text:", args.text.substring(0, 50) + "...");
583
- synthesizer.synthesize(args.text);
584
- } else if (args?.text !== void 0) {
585
- console.log("[tts_stream_speak] args.text is not a string, type:", typeof args.text, "value:", args.text);
3353
+ const textArg = extractTextArg(args);
3354
+ if (textArg && typeof textArg === "string" && textArg.trim().length > 0) {
3355
+ console.log("[tts_stream_speak] synthesizing text:", textArg.substring(0, 50) + "...");
3356
+ synthesizer.synthesize(textArg);
586
3357
  }
587
3358
  return { success: true, message: "Stream speak started" };
588
3359
  }
@@ -591,7 +3362,8 @@ async function handleToolCall(toolName, args) {
591
3362
  "UNKNOWN" /* UNKNOWN */,
592
3363
  "tts_stream"
593
3364
  );
594
- case "tts_stream_stop":
3365
+ }
3366
+ case "tts_stream_stop": {
595
3367
  if (!isStreamEnabled()) {
596
3368
  throw new TTSError(
597
3369
  "Stream mode is not enabled. Please enable autoRead in configuration.",
@@ -609,6 +3381,7 @@ async function handleToolCall(toolName, args) {
609
3381
  "UNKNOWN" /* UNKNOWN */,
610
3382
  "tts_stream"
611
3383
  );
3384
+ }
612
3385
  case "tts_stream_status":
613
3386
  if (!isStreamEnabled()) {
614
3387
  return {
@@ -1310,15 +4083,8 @@ var StreamingSynthesizer = class extends EventEmitter4 {
1310
4083
 
1311
4084
  // src/core/stream-player.ts
1312
4085
  import { EventEmitter as EventEmitter5 } from "events";
1313
- import { spawn as spawn2 } from "child_process";
1314
- import fs2 from "fs";
1315
- import { createWriteStream as createWriteStream2 } from "fs";
1316
- import { tmpdir as tmpdir2 } from "os";
1317
- import { join as join2 } from "path";
1318
4086
  var StreamPlayer = class extends EventEmitter5 {
1319
- tempFile = "";
1320
- writeStream;
1321
- playerProcess;
4087
+ backend = null;
1322
4088
  _bytesWritten = 0;
1323
4089
  _started = false;
1324
4090
  _paused = false;
@@ -1329,69 +4095,62 @@ var StreamPlayer = class extends EventEmitter5 {
1329
4095
  super();
1330
4096
  this.format = options.format || "mp3";
1331
4097
  this.events = options.events;
4098
+ const backendType = options.backendType || "naudiodon" /* NAUDIODON */;
4099
+ this.backend = createBackend(backendType, {
4100
+ format: this.format,
4101
+ events: {
4102
+ onStart: () => {
4103
+ this.events?.onStart?.();
4104
+ this.emit("start");
4105
+ },
4106
+ onEnd: () => {
4107
+ this.events?.onEnd?.();
4108
+ this.emit("end");
4109
+ },
4110
+ onError: (error) => {
4111
+ this.handleError(error);
4112
+ },
4113
+ onPause: () => {
4114
+ this._paused = true;
4115
+ this.events?.onPause?.();
4116
+ this.emit("pause");
4117
+ },
4118
+ onResume: () => {
4119
+ this._paused = false;
4120
+ this.events?.onResume?.();
4121
+ this.emit("resume");
4122
+ },
4123
+ onStop: () => {
4124
+ this.events?.onStop?.();
4125
+ this.emit("stop");
4126
+ },
4127
+ onProgress: (bytes) => {
4128
+ this.events?.onProgress?.(bytes);
4129
+ this.emit("progress", bytes);
4130
+ }
4131
+ }
4132
+ });
1332
4133
  }
1333
4134
  /**
1334
4135
  * 开始播放
1335
- * 创建临时文件,创建写入流,启动播放器进程
4136
+ * 初始化后端,准备接收音频数据
1336
4137
  */
1337
4138
  start() {
1338
4139
  if (this._started) {
1339
4140
  return;
1340
4141
  }
1341
- this.tempFile = join2(tmpdir2(), `ocosay-stream-${Date.now()}.${this.format}`);
1342
- this.writeStream = createWriteStream2(this.tempFile, { highWaterMark: 64 * 1024 });
1343
- this.writeStream.on("error", (error) => {
1344
- this.handleError(error);
1345
- });
1346
- this.writeStream.on("finish", () => {
1347
- });
1348
- this.startPlayer();
1349
- this._started = true;
1350
- this._stopped = false;
1351
- this.events?.onStart?.();
1352
- this.emit("start");
1353
- }
1354
- /**
1355
- * 启动播放器进程
1356
- */
1357
- startPlayer() {
1358
- const platform = process.platform;
1359
- let command;
1360
- let args;
1361
- if (platform === "darwin") {
1362
- command = "afplay";
1363
- args = [this.tempFile];
1364
- } else if (platform === "linux") {
1365
- command = "aplay";
1366
- args = [this.tempFile];
1367
- } else {
1368
- this.handleError(new Error("Windows platform is not supported for stream playback. PlaySync() blocks the Node.js event loop."));
4142
+ if (!this.backend) {
4143
+ this.handleError(new Error("Audio backend not initialized"));
1369
4144
  return;
1370
4145
  }
1371
- this.playerProcess = spawn2(command, args, {
1372
- stdio: "ignore",
1373
- detached: false
1374
- });
1375
- this.playerProcess.on("exit", (code, signal) => {
1376
- if (this._stopped) {
1377
- return;
1378
- }
1379
- if (signal === "SIGTERM" || signal === "SIGINT") {
1380
- return;
1381
- }
1382
- if (code === 0 || code === null) {
1383
- this.events?.onEnd?.();
1384
- this.emit("end");
1385
- } else {
1386
- this.handleError(new Error(`Player exited with code ${code}`));
1387
- }
1388
- });
1389
- this.playerProcess.on("error", (error) => {
1390
- this.handleError(error);
1391
- });
4146
+ this.backend.start("");
4147
+ this._started = true;
4148
+ this._stopped = false;
4149
+ this._paused = false;
4150
+ this._bytesWritten = 0;
1392
4151
  }
1393
4152
  /**
1394
- * 写入音频数据块(边收边写)
4153
+ * 写入音频数据块(边收边播)
1395
4154
  * 如果尚未 start(),会自动调用
1396
4155
  */
1397
4156
  write(chunk) {
@@ -1401,12 +4160,8 @@ var StreamPlayer = class extends EventEmitter5 {
1401
4160
  if (!this._started) {
1402
4161
  this.start();
1403
4162
  }
1404
- if (this.writeStream) {
1405
- const canContinue = this.writeStream.write(chunk);
1406
- if (!canContinue) {
1407
- this.writeStream.once("drain", () => {
1408
- });
1409
- }
4163
+ if (this.backend) {
4164
+ this.backend.write(chunk);
1410
4165
  this._bytesWritten += chunk.length;
1411
4166
  this.events?.onProgress?.(this._bytesWritten);
1412
4167
  this.emit("progress", this._bytesWritten);
@@ -1414,56 +4169,37 @@ var StreamPlayer = class extends EventEmitter5 {
1414
4169
  }
1415
4170
  /**
1416
4171
  * 结束写入
1417
- * 关闭写入流,但不杀播放器进程,让它播完
4172
+ * 通知后端写入完成,但保持播放直到结束
1418
4173
  */
1419
4174
  end() {
1420
- if (this.writeStream) {
1421
- this.writeStream.end();
1422
- this.writeStream = void 0;
4175
+ if (this.backend) {
4176
+ this.backend.end();
1423
4177
  }
1424
4178
  }
1425
4179
  /**
1426
4180
  * 停止播放
1427
- * 杀死播放器进程,删除临时文件
4181
+ * 立即停止播放并释放资源
1428
4182
  */
1429
4183
  stop() {
1430
4184
  this._stopped = true;
1431
4185
  this._started = false;
1432
4186
  this._paused = false;
1433
- if (this.playerProcess) {
1434
- try {
1435
- this.playerProcess.kill("SIGTERM");
1436
- } catch (e) {
1437
- }
1438
- this.playerProcess = void 0;
1439
- }
1440
- if (this.writeStream) {
1441
- try {
1442
- this.writeStream.destroy();
1443
- } catch (e) {
1444
- }
1445
- this.writeStream = void 0;
4187
+ if (this.backend) {
4188
+ this.backend.stop();
1446
4189
  }
1447
- this.deleteTempFile();
4190
+ this._bytesWritten = 0;
1448
4191
  this.events?.onStop?.();
1449
4192
  this.emit("stop");
1450
4193
  }
1451
4194
  /**
1452
4195
  * 暂停播放
1453
- * 使用 SIGSTOP 暂停播放器进程
1454
4196
  */
1455
4197
  pause() {
1456
4198
  if (!this._started || this._paused || this._stopped) {
1457
4199
  return;
1458
4200
  }
1459
- if (this.playerProcess) {
1460
- try {
1461
- this.playerProcess.kill("SIGSTOP");
1462
- this._paused = true;
1463
- this.events?.onPause?.();
1464
- this.emit("pause");
1465
- } catch (e) {
1466
- }
4201
+ if (this.backend) {
4202
+ this.backend.pause();
1467
4203
  }
1468
4204
  }
1469
4205
  /**
@@ -1473,14 +4209,8 @@ var StreamPlayer = class extends EventEmitter5 {
1473
4209
  if (!this._paused || this._stopped) {
1474
4210
  return;
1475
4211
  }
1476
- if (this.playerProcess) {
1477
- try {
1478
- this.playerProcess.kill("SIGCONT");
1479
- this._paused = false;
1480
- this.events?.onResume?.();
1481
- this.emit("resume");
1482
- } catch (e) {
1483
- }
4212
+ if (this.backend) {
4213
+ this.backend.resume();
1484
4214
  }
1485
4215
  }
1486
4216
  /**
@@ -1507,12 +4237,6 @@ var StreamPlayer = class extends EventEmitter5 {
1507
4237
  getBytesWritten() {
1508
4238
  return this._bytesWritten;
1509
4239
  }
1510
- /**
1511
- * 获取临时文件路径
1512
- */
1513
- getTempFile() {
1514
- return this.tempFile;
1515
- }
1516
4240
  /**
1517
4241
  * 处理错误
1518
4242
  */
@@ -1520,20 +4244,6 @@ var StreamPlayer = class extends EventEmitter5 {
1520
4244
  this.events?.onError?.(error);
1521
4245
  this.emit("error", error);
1522
4246
  }
1523
- /**
1524
- * 删除临时文件
1525
- */
1526
- deleteTempFile() {
1527
- if (this.tempFile) {
1528
- try {
1529
- if (fs2.existsSync(this.tempFile)) {
1530
- fs2.unlinkSync(this.tempFile);
1531
- }
1532
- } catch (e) {
1533
- }
1534
- this.tempFile = "";
1535
- }
1536
- }
1537
4247
  };
1538
4248
 
1539
4249
  // src/index.ts
@@ -1671,7 +4381,7 @@ function getStreamPlayer() {
1671
4381
  }
1672
4382
 
1673
4383
  // src/config.ts
1674
- import * as fs3 from "fs";
4384
+ import * as fs2 from "fs";
1675
4385
  import * as path from "path";
1676
4386
  import * as os from "os";
1677
4387
  var DEFAULT_CONFIG = {
@@ -1768,21 +4478,21 @@ function resolveEnvValue(value) {
1768
4478
  }
1769
4479
  function loadOrCreateConfig() {
1770
4480
  const configDir = path.dirname(CONFIG_PATH);
1771
- if (!fs3.existsSync(configDir)) {
4481
+ if (!fs2.existsSync(configDir)) {
1772
4482
  try {
1773
- fs3.mkdirSync(configDir, { recursive: true });
4483
+ fs2.mkdirSync(configDir, { recursive: true });
1774
4484
  } catch (err) {
1775
4485
  throw new Error(`[ocosay] \u65E0\u6CD5\u521B\u5EFA\u914D\u7F6E\u76EE\u5F55 ${configDir}: ${err}`);
1776
4486
  }
1777
4487
  }
1778
- if (!fs3.existsSync(CONFIG_PATH)) {
4488
+ if (!fs2.existsSync(CONFIG_PATH)) {
1779
4489
  console.info("[ocosay] \u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u6B63\u5728\u521B\u5EFA\u9ED8\u8BA4\u914D\u7F6E...");
1780
4490
  const defaultConfig = generateDefaultConfig();
1781
4491
  const configContent = JSON.stringify(defaultConfig, null, 2);
1782
4492
  try {
1783
- fs3.writeFileSync(CONFIG_PATH, configContent, "utf-8");
4493
+ fs2.writeFileSync(CONFIG_PATH, configContent, "utf-8");
1784
4494
  try {
1785
- fs3.chmodSync(CONFIG_PATH, 384);
4495
+ fs2.chmodSync(CONFIG_PATH, 384);
1786
4496
  } catch (err) {
1787
4497
  console.warn(`[ocosay] \u65E0\u6CD5\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u6743\u9650: ${err}`);
1788
4498
  }
@@ -1794,7 +4504,7 @@ function loadOrCreateConfig() {
1794
4504
  return defaultConfig;
1795
4505
  }
1796
4506
  try {
1797
- const content = fs3.readFileSync(CONFIG_PATH, "utf-8");
4507
+ const content = fs2.readFileSync(CONFIG_PATH, "utf-8");
1798
4508
  const stripped = stripComments(content);
1799
4509
  const loaded = JSON.parse(stripped);
1800
4510
  const merged = mergeWithDefaults(loaded, DEFAULT_CONFIG);
@@ -1819,11 +4529,14 @@ function loadOrCreateConfig() {
1819
4529
 
1820
4530
  // src/plugin.ts
1821
4531
  import { readFileSync as readFileSync2 } from "fs";
1822
- import { join as join4 } from "path";
4532
+ import { fileURLToPath } from "url";
4533
+ import { dirname as dirname2, join as join6 } from "path";
4534
+ var __filename = fileURLToPath(import.meta.url);
4535
+ var __dirname = dirname2(__filename);
1823
4536
  var pluginName = "ocosay";
1824
4537
  var pluginVersion = "0.0.0";
1825
4538
  try {
1826
- const pkg = JSON.parse(readFileSync2(join4(process.cwd(), "package.json"), "utf-8"));
4539
+ const pkg = JSON.parse(readFileSync2(join6(__dirname, "package.json"), "utf-8"));
1827
4540
  pluginVersion = pkg.version || "0.0.0";
1828
4541
  } catch {
1829
4542
  }
@@ -1974,11 +4687,11 @@ var OcosayPlugin = async (input, _options) => {
1974
4687
  if (opencodeShowToast) {
1975
4688
  global.__opencode_tui_showToast__ = opencodeShowToast;
1976
4689
  }
1977
- setTimeout(() => {
4690
+ setTimeout(async () => {
1978
4691
  if (!opencodeShowToast) return;
1979
4692
  if (initError) {
1980
4693
  console.info(`[Ocosay] Ocosay v${pluginVersion} Initialization Failed: Initialization failed, please check config`);
1981
- opencodeShowToast({
4694
+ await opencodeShowToast({
1982
4695
  body: {
1983
4696
  title: `Ocosay v${pluginVersion} Initialization Failed`,
1984
4697
  message: "Initialization failed, please check config",
@@ -1987,7 +4700,7 @@ var OcosayPlugin = async (input, _options) => {
1987
4700
  });
1988
4701
  } else {
1989
4702
  console.info(`[Ocosay] Ocosay v${pluginVersion} Plugin Loaded: Auto-read: ${config.autoRead ? "ON" : "OFF"}`);
1990
- opencodeShowToast({
4703
+ await opencodeShowToast({
1991
4704
  body: {
1992
4705
  title: `Ocosay v${pluginVersion} Plugin Loaded`,
1993
4706
  message: `Auto-read: ${config.autoRead ? "ON" : "OFF"}`,
@@ -2045,7 +4758,7 @@ var OcosayPlugin = async (input, _options) => {
2045
4758
  if (input.client?.tui?.showToast) {
2046
4759
  global.__opencode_tui_showToast__ = input.client.tui.showToast;
2047
4760
  }
2048
- setTimeout(() => {
4761
+ setTimeout(async () => {
2049
4762
  const showToastFn = input.client?.tui?.showToast;
2050
4763
  if (!showToastFn) {
2051
4764
  console.warn("[Ocosay] showToast not available");
@@ -2053,7 +4766,7 @@ var OcosayPlugin = async (input, _options) => {
2053
4766
  }
2054
4767
  if (initError) {
2055
4768
  console.info(`[Ocosay] Ocosay v${pluginVersion} Initialization Failed: Initialization failed, please check config`);
2056
- showToastFn({
4769
+ await showToastFn({
2057
4770
  body: {
2058
4771
  title: `Ocosay v${pluginVersion} Initialization Failed`,
2059
4772
  message: "Initialization failed, please check config",
@@ -2062,7 +4775,7 @@ var OcosayPlugin = async (input, _options) => {
2062
4775
  });
2063
4776
  } else {
2064
4777
  console.info(`[Ocosay] Ocosay v${pluginVersion} Plugin Loaded: Auto-read: ${config.autoRead ? "ON" : "OFF"}`);
2065
- showToastFn({
4778
+ await showToastFn({
2066
4779
  body: {
2067
4780
  title: `Ocosay v${pluginVersion} Plugin Loaded`,
2068
4781
  message: `Auto-read: ${config.autoRead ? "ON" : "OFF"}`,
@@ -2083,4 +4796,28 @@ export {
2083
4796
  plugin_default as default,
2084
4797
  server
2085
4798
  };
4799
+ /*! Bundled license information:
4800
+
4801
+ howler/dist/howler.js:
4802
+ (*!
4803
+ * howler.js v2.2.4
4804
+ * howlerjs.com
4805
+ *
4806
+ * (c) 2013-2020, James Simpson of GoldFire Studios
4807
+ * goldfirestudios.com
4808
+ *
4809
+ * MIT License
4810
+ *)
4811
+ (*!
4812
+ * Spatial Plugin - Adds support for stereo and 3D audio where Web Audio is supported.
4813
+ *
4814
+ * howler.js v2.2.4
4815
+ * howlerjs.com
4816
+ *
4817
+ * (c) 2013-2020, James Simpson of GoldFire Studios
4818
+ * goldfirestudios.com
4819
+ *
4820
+ * MIT License
4821
+ *)
4822
+ */
2086
4823
  //# sourceMappingURL=plugin.js.map