@mingxy/ocosay 1.0.20 → 1.0.22

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,128 +1,3111 @@
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
 
4
2248
  // src/core/speaker.ts
5
2249
  import { EventEmitter as EventEmitter2 } from "events";
6
2250
 
7
- // src/core/types.ts
8
- var TTSError = class extends Error {
9
- constructor(message, code, provider, details) {
10
- super(message);
11
- this.name = "TTSError";
12
- this.code = code;
13
- this.provider = provider;
14
- this.details = details;
2251
+ // src/core/types.ts
2252
+ var TTSError = class extends Error {
2253
+ constructor(message, code, provider, details) {
2254
+ super(message);
2255
+ this.name = "TTSError";
2256
+ this.code = code;
2257
+ this.provider = provider;
2258
+ this.details = details;
2259
+ }
2260
+ code;
2261
+ provider;
2262
+ details;
2263
+ };
2264
+
2265
+ // src/providers/base.ts
2266
+ var providers = /* @__PURE__ */ new Map();
2267
+ function registerProvider(name, provider) {
2268
+ if (providers.has(name)) {
2269
+ throw new TTSError(
2270
+ `Provider "${name}" is already registered`,
2271
+ "UNKNOWN" /* UNKNOWN */,
2272
+ "system"
2273
+ );
2274
+ }
2275
+ providers.set(name, provider);
2276
+ }
2277
+ function getProvider(name) {
2278
+ const provider = providers.get(name);
2279
+ if (!provider) {
2280
+ throw new TTSError(
2281
+ `TTS Provider "${name}" not found`,
2282
+ "UNKNOWN" /* UNKNOWN */,
2283
+ "system"
2284
+ );
2285
+ }
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);
15
2585
  }
16
- code;
17
- provider;
18
- details;
19
2586
  };
20
2587
 
21
- // src/providers/base.ts
22
- var providers = /* @__PURE__ */ new Map();
23
- function registerProvider(name, provider) {
24
- if (providers.has(name)) {
25
- throw new TTSError(
26
- `Provider "${name}" is already registered`,
27
- "UNKNOWN" /* UNKNOWN */,
28
- "system"
29
- );
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;
30
2608
  }
31
- providers.set(name, provider);
32
- }
33
- function getProvider(name) {
34
- const provider = providers.get(name);
35
- if (!provider) {
36
- throw new TTSError(
37
- `TTS Provider "${name}" not found`,
38
- "UNKNOWN" /* UNKNOWN */,
39
- "system"
40
- );
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
+ });
41
2630
  }
42
- return provider;
43
- }
44
- function listProviders() {
45
- return Array.from(providers.keys());
46
- }
47
- function hasProvider(name) {
48
- return providers.has(name);
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";
2716
+ }
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;
2732
+ }
2733
+ start(filePath) {
2734
+ if (this._started) return;
2735
+ if (isWsl()) {
2736
+ filePath = wslPathToWindows(filePath);
2737
+ }
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);
2786
+ }
2787
+ pause() {
2788
+ if (!this._started || this._paused || this._stopped) return;
2789
+ throw new UnsupportedError2("pause is not supported by PowerShell SoundPlayer");
2790
+ }
2791
+ resume() {
2792
+ if (!this._paused || this._stopped) return;
2793
+ throw new UnsupportedError2("resume is not supported by PowerShell SoundPlayer");
2794
+ }
2795
+ stop() {
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?.();
2810
+ }
2811
+ setVolume(_volume) {
2812
+ }
2813
+ destroy() {
2814
+ this.stop();
2815
+ }
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;
2825
+ }
55
2826
  }
56
- async destroy() {
2827
+ handleError(error) {
2828
+ this.events?.onError?.(error);
57
2829
  }
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
- );
2830
+ };
2831
+
2832
+ // src/core/backends/howler-backend.ts
2833
+ var import_howler = __toESM(require_howler(), 1);
2834
+ var HowlerBackend = class {
2835
+ name = "howler";
2836
+ supportsStreaming = false;
2837
+ // Howler.js 不支持真正的流式播放
2838
+ howl = null;
2839
+ events;
2840
+ _started = false;
2841
+ _paused = false;
2842
+ _stopped = false;
2843
+ volume = 1;
2844
+ bytesWritten = 0;
2845
+ blobUrl = null;
2846
+ format;
2847
+ constructor(options = {}) {
2848
+ this.events = options.events;
2849
+ this.volume = options.volume ?? 1;
2850
+ this.format = options.format || "mp3";
2851
+ }
2852
+ start(_filePath) {
2853
+ if (this._started) {
2854
+ this.stop();
2855
+ }
2856
+ this._started = true;
2857
+ this._stopped = false;
2858
+ this._paused = false;
2859
+ this.bytesWritten = 0;
2860
+ this.howl = new import_howler.Howl({
2861
+ src: [""],
2862
+ // 初始为空
2863
+ html5: true,
2864
+ // 启用 HTML5 Audio 模式支持流式
2865
+ volume: this.volume,
2866
+ format: [this.format],
2867
+ onplay: () => {
2868
+ this.events?.onStart?.();
2869
+ },
2870
+ onpause: () => {
2871
+ if (!this._stopped) {
2872
+ this.events?.onPause?.();
2873
+ }
2874
+ },
2875
+ onstop: () => {
2876
+ if (!this._stopped) {
2877
+ this.events?.onStop?.();
2878
+ }
2879
+ },
2880
+ onend: () => {
2881
+ this.events?.onEnd?.();
2882
+ },
2883
+ onloaderror: (_id, error) => {
2884
+ this.handleError(new Error(`Howler load error: ${error}`));
2885
+ },
2886
+ onplayerror: (_id, error) => {
2887
+ this.handleError(new Error(`Howler play error: ${error}`));
2888
+ }
2889
+ });
2890
+ }
2891
+ write(chunk) {
2892
+ if (!this._started || this._stopped) return;
2893
+ if (!this.howl) return;
2894
+ this.bytesWritten += chunk.length;
2895
+ this.events?.onProgress?.(this.bytesWritten);
2896
+ const blob = new Blob([chunk], { type: `audio/${this.format}` });
2897
+ if (this.blobUrl) {
2898
+ URL.revokeObjectURL(this.blobUrl);
2899
+ }
2900
+ this.blobUrl = URL.createObjectURL(blob);
2901
+ const wasPlaying = this.howl.playing();
2902
+ this.howl.stop();
2903
+ this.howl.unload();
2904
+ this.howl = new import_howler.Howl({
2905
+ src: [this.blobUrl],
2906
+ html5: true,
2907
+ volume: this.volume,
2908
+ format: [this.format],
2909
+ onplay: () => {
2910
+ if (!wasPlaying) {
2911
+ this.events?.onStart?.();
2912
+ }
2913
+ },
2914
+ onpause: () => {
2915
+ if (!this._stopped) {
2916
+ this.events?.onPause?.();
2917
+ }
2918
+ },
2919
+ onstop: () => {
2920
+ if (!this._stopped) {
2921
+ this.events?.onStop?.();
2922
+ }
2923
+ },
2924
+ onend: () => {
2925
+ this.events?.onEnd?.();
2926
+ },
2927
+ onloaderror: (_id, error) => {
2928
+ this.handleError(new Error(`Howler load error: ${error}`));
2929
+ },
2930
+ onplayerror: (_id, error) => {
2931
+ this.handleError(new Error(`Howler play error: ${error}`));
2932
+ }
2933
+ });
2934
+ if (wasPlaying || this._started) {
2935
+ this.howl.play();
68
2936
  }
69
- const voice = options?.voice || this.defaultVoice;
70
- const model = options?.model || this.defaultModel;
71
- return this.doSpeak(text, voice, model, options);
2937
+ }
2938
+ end() {
72
2939
  }
73
2940
  pause() {
74
- throw new TTSError(
75
- "Pause is not supported by this provider",
76
- "UNKNOWN" /* UNKNOWN */,
77
- this.name
78
- );
2941
+ if (!this._started || this._paused || this._stopped) return;
2942
+ if (this.howl && this.howl.playing()) {
2943
+ this.howl.pause();
2944
+ this._paused = true;
2945
+ this.events?.onPause?.();
2946
+ }
79
2947
  }
80
2948
  resume() {
81
- throw new TTSError(
82
- "Resume is not supported by this provider",
83
- "UNKNOWN" /* UNKNOWN */,
84
- this.name
85
- );
2949
+ if (!this._paused || this._stopped) return;
2950
+ if (this.howl) {
2951
+ this.howl.play();
2952
+ this._paused = false;
2953
+ this.events?.onResume?.();
2954
+ }
86
2955
  }
87
2956
  stop() {
88
- return Promise.resolve();
2957
+ this._stopped = true;
2958
+ this._started = false;
2959
+ this._paused = false;
2960
+ if (this.howl) {
2961
+ try {
2962
+ this.howl.stop();
2963
+ this.howl.unload();
2964
+ } catch (e) {
2965
+ }
2966
+ this.howl = null;
2967
+ }
2968
+ if (this.blobUrl) {
2969
+ URL.revokeObjectURL(this.blobUrl);
2970
+ this.blobUrl = null;
2971
+ }
2972
+ this.bytesWritten = 0;
2973
+ this.events?.onStop?.();
89
2974
  }
90
- async listVoices() {
91
- return [];
2975
+ getCurrentTime() {
2976
+ if (this.howl) {
2977
+ return this.howl.seek();
2978
+ }
2979
+ return void 0;
92
2980
  }
93
- getCapabilities() {
94
- return this.capabilities;
2981
+ getDuration() {
2982
+ if (this.howl) {
2983
+ return this.howl.duration();
2984
+ }
2985
+ return void 0;
95
2986
  }
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
- );
2987
+ setVolume(volume) {
2988
+ this.volume = Math.max(0, Math.min(1, volume));
2989
+ if (this.howl) {
2990
+ this.howl.volume(this.volume);
106
2991
  }
107
2992
  }
2993
+ destroy() {
2994
+ this.stop();
2995
+ }
2996
+ handleError(error) {
2997
+ this.events?.onError?.(error);
2998
+ }
108
2999
  };
109
3000
 
3001
+ // src/core/backends/index.ts
3002
+ function isNaudiodonAvailable() {
3003
+ try {
3004
+ __require.resolve("naudiodon");
3005
+ return true;
3006
+ } catch (e) {
3007
+ return false;
3008
+ }
3009
+ }
3010
+ function isWsl() {
3011
+ if (process.platform !== "linux") return false;
3012
+ try {
3013
+ return __require("fs").readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft");
3014
+ } catch {
3015
+ return false;
3016
+ }
3017
+ }
3018
+ function createBackend(type = "auto" /* AUTO */, options = {}) {
3019
+ const platform = process.platform;
3020
+ if (type !== "auto" /* AUTO */) {
3021
+ return createBackendByType(type, options);
3022
+ }
3023
+ if (isNaudiodonAvailable()) {
3024
+ try {
3025
+ const naudiodon = __require("naudiodon");
3026
+ if (naudiodon) {
3027
+ return new NaudiodonBackend(options);
3028
+ }
3029
+ } catch (e) {
3030
+ }
3031
+ }
3032
+ switch (platform) {
3033
+ case "darwin":
3034
+ return new AfplayBackend(options);
3035
+ case "linux":
3036
+ if (isWsl()) {
3037
+ return new PowerShellBackend(options);
3038
+ }
3039
+ return new AplayBackend(options);
3040
+ case "win32":
3041
+ return new PowerShellBackend(options);
3042
+ default:
3043
+ throw new Error(`Unsupported platform: ${platform}`);
3044
+ }
3045
+ }
3046
+ function createBackendByType(type, options) {
3047
+ switch (type) {
3048
+ case "naudiodon" /* NAUDIODON */:
3049
+ return new NaudiodonBackend(options);
3050
+ case "afplay" /* AFPLAY */:
3051
+ return new AfplayBackend(options);
3052
+ case "aplay" /* APLAY */:
3053
+ return new AplayBackend(options);
3054
+ case "powershell" /* POWERSHELL */:
3055
+ return new PowerShellBackend(options);
3056
+ case "howler" /* HOWLER */:
3057
+ return new HowlerBackend(options);
3058
+ default:
3059
+ throw new Error(`Unknown backend type: ${type}`);
3060
+ }
3061
+ }
3062
+
110
3063
  // 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
3064
  var AudioPlayer = class extends EventEmitter {
118
3065
  constructor(events) {
119
3066
  super();
120
3067
  this.events = events;
3068
+ this.backend = createBackend("auto" /* AUTO */, {
3069
+ events: {
3070
+ onStart: () => {
3071
+ this.events?.onStart?.();
3072
+ this.emit("start");
3073
+ },
3074
+ onEnd: () => {
3075
+ this._playing = false;
3076
+ this.events?.onEnd?.();
3077
+ this.emit("end");
3078
+ this.cleanup();
3079
+ },
3080
+ onError: (error) => {
3081
+ this._playing = false;
3082
+ const ttsError = new TTSError(
3083
+ error.message || "Playback failed",
3084
+ "PLAYER_ERROR" /* PLAYER_ERROR */,
3085
+ "player"
3086
+ );
3087
+ this.events?.onError?.(ttsError);
3088
+ this.emit("error", ttsError);
3089
+ },
3090
+ onPause: () => {
3091
+ this.events?.onPause?.();
3092
+ this.emit("pause");
3093
+ },
3094
+ onResume: () => {
3095
+ this.events?.onResume?.();
3096
+ this.emit("resume");
3097
+ },
3098
+ onStop: () => {
3099
+ this.events?.onStop?.();
3100
+ this.emit("stop");
3101
+ }
3102
+ }
3103
+ });
121
3104
  }
122
3105
  events;
123
3106
  _playing = false;
124
3107
  _paused = false;
125
- currentProcess;
3108
+ backend;
126
3109
  currentFile;
127
3110
  /**
128
3111
  * 播放音频
@@ -136,7 +3119,7 @@ var AudioPlayer = class extends EventEmitter {
136
3119
  this._playing = true;
137
3120
  this._paused = false;
138
3121
  try {
139
- const tempFile = join(tmpdir(), `ocosay-${Date.now()}.${format}`);
3122
+ const tempFile = join4(tmpdir4(), `ocosay-${Date.now()}.${format}`);
140
3123
  this.currentFile = tempFile;
141
3124
  if (Buffer.isBuffer(audioData)) {
142
3125
  fs.writeFileSync(tempFile, audioData);
@@ -181,88 +3164,29 @@ var AudioPlayer = class extends EventEmitter {
181
3164
  }
182
3165
  /**
183
3166
  * 播放音频文件
184
- * 优先使用 afplay (macOS), aplay (Linux), 否则用 PowerShell (Windows)
3167
+ * 使用 AudioBackend 统一后端播放
185
3168
  */
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
- });
3169
+ playFile(filePath, _format) {
3170
+ return new Promise((resolve) => {
3171
+ this.backend.start(filePath);
3172
+ resolve();
217
3173
  });
218
3174
  }
219
- /**
220
- * 暂停播放
221
- * 注意: 目前通过 SIGSTOP 实现,真正的 pause 需要支持暂停的音频库
222
- */
223
3175
  pause() {
224
3176
  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
- }
3177
+ this.backend.pause();
3178
+ this._paused = true;
234
3179
  }
235
- /**
236
- * 恢复播放
237
- */
238
3180
  resume() {
239
3181
  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
- }
3182
+ this.backend.resume();
3183
+ this._paused = false;
249
3184
  }
250
- /**
251
- * 停止播放
252
- */
253
3185
  async stop() {
254
3186
  this._playing = false;
255
3187
  this._paused = false;
256
- if (this.currentProcess) {
257
- try {
258
- this.currentProcess.kill("SIGTERM");
259
- } catch (e) {
260
- }
261
- this.currentProcess = void 0;
262
- }
3188
+ this.backend.stop();
263
3189
  this.cleanup();
264
- this.events?.onStop?.();
265
- this.emit("stop");
266
3190
  }
267
3191
  /**
268
3192
  * 是否正在播放
@@ -523,6 +3447,20 @@ async function listVoices(providerName) {
523
3447
  }
524
3448
 
525
3449
  // src/tools/tts.ts
3450
+ function extractTextArg(args) {
3451
+ if (!args || typeof args !== "object") {
3452
+ return void 0;
3453
+ }
3454
+ const argObj = args;
3455
+ const text = argObj.text;
3456
+ if (typeof text === "string" && text.trim().length > 0) {
3457
+ return text;
3458
+ }
3459
+ if (text !== void 0) {
3460
+ console.warn("[tts] text arg is not a valid string, type:", typeof text, "value:", String(text).substring(0, 100));
3461
+ }
3462
+ return void 0;
3463
+ }
526
3464
  async function handleToolCall(toolName, args) {
527
3465
  try {
528
3466
  switch (toolName) {
@@ -545,21 +3483,24 @@ async function handleToolCall(toolName, args) {
545
3483
  case "tts_resume":
546
3484
  resume();
547
3485
  return { success: true, message: "Resumed" };
548
- case "tts_list_voices":
3486
+ case "tts_list_voices": {
549
3487
  const voices = await listVoices(args?.provider);
550
3488
  return { success: true, voices };
551
- case "tts_list_providers":
3489
+ }
3490
+ case "tts_list_providers": {
552
3491
  const speaker2 = getDefaultSpeaker();
553
3492
  const providers2 = speaker2.getProviders();
554
3493
  return { success: true, providers: providers2 };
555
- case "tts_status":
3494
+ }
3495
+ case "tts_status": {
556
3496
  const s = getDefaultSpeaker();
557
3497
  return {
558
3498
  success: true,
559
3499
  isPlaying: s.isPlaying(),
560
3500
  isPaused: s.isPausedState()
561
3501
  };
562
- case "tts_stream_speak":
3502
+ }
3503
+ case "tts_stream_speak": {
563
3504
  if (!isAutoReadEnabled()) {
564
3505
  throw new TTSError(
565
3506
  "Stream mode is not enabled. autoRead must be enabled in configuration to use tts_stream_speak.",
@@ -578,11 +3519,10 @@ async function handleToolCall(toolName, args) {
578
3519
  const synthesizer = getStreamingSynthesizer();
579
3520
  if (streamReader2 && synthesizer) {
580
3521
  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);
3522
+ const textArg = extractTextArg(args);
3523
+ if (textArg && typeof textArg === "string" && textArg.trim().length > 0) {
3524
+ console.log("[tts_stream_speak] synthesizing text:", textArg.substring(0, 50) + "...");
3525
+ synthesizer.synthesize(textArg);
586
3526
  }
587
3527
  return { success: true, message: "Stream speak started" };
588
3528
  }
@@ -591,7 +3531,8 @@ async function handleToolCall(toolName, args) {
591
3531
  "UNKNOWN" /* UNKNOWN */,
592
3532
  "tts_stream"
593
3533
  );
594
- case "tts_stream_stop":
3534
+ }
3535
+ case "tts_stream_stop": {
595
3536
  if (!isStreamEnabled()) {
596
3537
  throw new TTSError(
597
3538
  "Stream mode is not enabled. Please enable autoRead in configuration.",
@@ -609,6 +3550,7 @@ async function handleToolCall(toolName, args) {
609
3550
  "UNKNOWN" /* UNKNOWN */,
610
3551
  "tts_stream"
611
3552
  );
3553
+ }
612
3554
  case "tts_stream_status":
613
3555
  if (!isStreamEnabled()) {
614
3556
  return {
@@ -1310,15 +4252,8 @@ var StreamingSynthesizer = class extends EventEmitter4 {
1310
4252
 
1311
4253
  // src/core/stream-player.ts
1312
4254
  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
4255
  var StreamPlayer = class extends EventEmitter5 {
1319
- tempFile = "";
1320
- writeStream;
1321
- playerProcess;
4256
+ backend = null;
1322
4257
  _bytesWritten = 0;
1323
4258
  _started = false;
1324
4259
  _paused = false;
@@ -1329,69 +4264,62 @@ var StreamPlayer = class extends EventEmitter5 {
1329
4264
  super();
1330
4265
  this.format = options.format || "mp3";
1331
4266
  this.events = options.events;
4267
+ const backendType = options.backendType || "naudiodon" /* NAUDIODON */;
4268
+ this.backend = createBackend(backendType, {
4269
+ format: this.format,
4270
+ events: {
4271
+ onStart: () => {
4272
+ this.events?.onStart?.();
4273
+ this.emit("start");
4274
+ },
4275
+ onEnd: () => {
4276
+ this.events?.onEnd?.();
4277
+ this.emit("end");
4278
+ },
4279
+ onError: (error) => {
4280
+ this.handleError(error);
4281
+ },
4282
+ onPause: () => {
4283
+ this._paused = true;
4284
+ this.events?.onPause?.();
4285
+ this.emit("pause");
4286
+ },
4287
+ onResume: () => {
4288
+ this._paused = false;
4289
+ this.events?.onResume?.();
4290
+ this.emit("resume");
4291
+ },
4292
+ onStop: () => {
4293
+ this.events?.onStop?.();
4294
+ this.emit("stop");
4295
+ },
4296
+ onProgress: (bytes) => {
4297
+ this.events?.onProgress?.(bytes);
4298
+ this.emit("progress", bytes);
4299
+ }
4300
+ }
4301
+ });
1332
4302
  }
1333
4303
  /**
1334
4304
  * 开始播放
1335
- * 创建临时文件,创建写入流,启动播放器进程
4305
+ * 初始化后端,准备接收音频数据
1336
4306
  */
1337
4307
  start() {
1338
4308
  if (this._started) {
1339
4309
  return;
1340
4310
  }
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."));
4311
+ if (!this.backend) {
4312
+ this.handleError(new Error("Audio backend not initialized"));
1369
4313
  return;
1370
4314
  }
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
- });
4315
+ this.backend.start("");
4316
+ this._started = true;
4317
+ this._stopped = false;
4318
+ this._paused = false;
4319
+ this._bytesWritten = 0;
1392
4320
  }
1393
4321
  /**
1394
- * 写入音频数据块(边收边写)
4322
+ * 写入音频数据块(边收边播)
1395
4323
  * 如果尚未 start(),会自动调用
1396
4324
  */
1397
4325
  write(chunk) {
@@ -1401,12 +4329,8 @@ var StreamPlayer = class extends EventEmitter5 {
1401
4329
  if (!this._started) {
1402
4330
  this.start();
1403
4331
  }
1404
- if (this.writeStream) {
1405
- const canContinue = this.writeStream.write(chunk);
1406
- if (!canContinue) {
1407
- this.writeStream.once("drain", () => {
1408
- });
1409
- }
4332
+ if (this.backend) {
4333
+ this.backend.write(chunk);
1410
4334
  this._bytesWritten += chunk.length;
1411
4335
  this.events?.onProgress?.(this._bytesWritten);
1412
4336
  this.emit("progress", this._bytesWritten);
@@ -1414,56 +4338,37 @@ var StreamPlayer = class extends EventEmitter5 {
1414
4338
  }
1415
4339
  /**
1416
4340
  * 结束写入
1417
- * 关闭写入流,但不杀播放器进程,让它播完
4341
+ * 通知后端写入完成,但保持播放直到结束
1418
4342
  */
1419
4343
  end() {
1420
- if (this.writeStream) {
1421
- this.writeStream.end();
1422
- this.writeStream = void 0;
4344
+ if (this.backend) {
4345
+ this.backend.end();
1423
4346
  }
1424
4347
  }
1425
4348
  /**
1426
4349
  * 停止播放
1427
- * 杀死播放器进程,删除临时文件
4350
+ * 立即停止播放并释放资源
1428
4351
  */
1429
4352
  stop() {
1430
4353
  this._stopped = true;
1431
4354
  this._started = false;
1432
4355
  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;
4356
+ if (this.backend) {
4357
+ this.backend.stop();
1446
4358
  }
1447
- this.deleteTempFile();
4359
+ this._bytesWritten = 0;
1448
4360
  this.events?.onStop?.();
1449
4361
  this.emit("stop");
1450
4362
  }
1451
4363
  /**
1452
4364
  * 暂停播放
1453
- * 使用 SIGSTOP 暂停播放器进程
1454
4365
  */
1455
4366
  pause() {
1456
4367
  if (!this._started || this._paused || this._stopped) {
1457
4368
  return;
1458
4369
  }
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
- }
4370
+ if (this.backend) {
4371
+ this.backend.pause();
1467
4372
  }
1468
4373
  }
1469
4374
  /**
@@ -1473,14 +4378,8 @@ var StreamPlayer = class extends EventEmitter5 {
1473
4378
  if (!this._paused || this._stopped) {
1474
4379
  return;
1475
4380
  }
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
- }
4381
+ if (this.backend) {
4382
+ this.backend.resume();
1484
4383
  }
1485
4384
  }
1486
4385
  /**
@@ -1507,12 +4406,6 @@ var StreamPlayer = class extends EventEmitter5 {
1507
4406
  getBytesWritten() {
1508
4407
  return this._bytesWritten;
1509
4408
  }
1510
- /**
1511
- * 获取临时文件路径
1512
- */
1513
- getTempFile() {
1514
- return this.tempFile;
1515
- }
1516
4409
  /**
1517
4410
  * 处理错误
1518
4411
  */
@@ -1520,20 +4413,6 @@ var StreamPlayer = class extends EventEmitter5 {
1520
4413
  this.events?.onError?.(error);
1521
4414
  this.emit("error", error);
1522
4415
  }
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
4416
  };
1538
4417
 
1539
4418
  // src/index.ts
@@ -1671,7 +4550,7 @@ function getStreamPlayer() {
1671
4550
  }
1672
4551
 
1673
4552
  // src/config.ts
1674
- import * as fs3 from "fs";
4553
+ import * as fs2 from "fs";
1675
4554
  import * as path from "path";
1676
4555
  import * as os from "os";
1677
4556
  var DEFAULT_CONFIG = {
@@ -1768,21 +4647,21 @@ function resolveEnvValue(value) {
1768
4647
  }
1769
4648
  function loadOrCreateConfig() {
1770
4649
  const configDir = path.dirname(CONFIG_PATH);
1771
- if (!fs3.existsSync(configDir)) {
4650
+ if (!fs2.existsSync(configDir)) {
1772
4651
  try {
1773
- fs3.mkdirSync(configDir, { recursive: true });
4652
+ fs2.mkdirSync(configDir, { recursive: true });
1774
4653
  } catch (err) {
1775
4654
  throw new Error(`[ocosay] \u65E0\u6CD5\u521B\u5EFA\u914D\u7F6E\u76EE\u5F55 ${configDir}: ${err}`);
1776
4655
  }
1777
4656
  }
1778
- if (!fs3.existsSync(CONFIG_PATH)) {
4657
+ if (!fs2.existsSync(CONFIG_PATH)) {
1779
4658
  console.info("[ocosay] \u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u6B63\u5728\u521B\u5EFA\u9ED8\u8BA4\u914D\u7F6E...");
1780
4659
  const defaultConfig = generateDefaultConfig();
1781
4660
  const configContent = JSON.stringify(defaultConfig, null, 2);
1782
4661
  try {
1783
- fs3.writeFileSync(CONFIG_PATH, configContent, "utf-8");
4662
+ fs2.writeFileSync(CONFIG_PATH, configContent, "utf-8");
1784
4663
  try {
1785
- fs3.chmodSync(CONFIG_PATH, 384);
4664
+ fs2.chmodSync(CONFIG_PATH, 384);
1786
4665
  } catch (err) {
1787
4666
  console.warn(`[ocosay] \u65E0\u6CD5\u8BBE\u7F6E\u914D\u7F6E\u6587\u4EF6\u6743\u9650: ${err}`);
1788
4667
  }
@@ -1794,7 +4673,7 @@ function loadOrCreateConfig() {
1794
4673
  return defaultConfig;
1795
4674
  }
1796
4675
  try {
1797
- const content = fs3.readFileSync(CONFIG_PATH, "utf-8");
4676
+ const content = fs2.readFileSync(CONFIG_PATH, "utf-8");
1798
4677
  const stripped = stripComments(content);
1799
4678
  const loaded = JSON.parse(stripped);
1800
4679
  const merged = mergeWithDefaults(loaded, DEFAULT_CONFIG);
@@ -1819,11 +4698,14 @@ function loadOrCreateConfig() {
1819
4698
 
1820
4699
  // src/plugin.ts
1821
4700
  import { readFileSync as readFileSync2 } from "fs";
1822
- import { join as join4 } from "path";
4701
+ import { fileURLToPath } from "url";
4702
+ import { dirname as dirname2, join as join6 } from "path";
4703
+ var __filename = fileURLToPath(import.meta.url);
4704
+ var __dirname = dirname2(__filename);
1823
4705
  var pluginName = "ocosay";
1824
4706
  var pluginVersion = "0.0.0";
1825
4707
  try {
1826
- const pkg = JSON.parse(readFileSync2(join4(process.cwd(), "package.json"), "utf-8"));
4708
+ const pkg = JSON.parse(readFileSync2(join6(__dirname, "package.json"), "utf-8"));
1827
4709
  pluginVersion = pkg.version || "0.0.0";
1828
4710
  } catch {
1829
4711
  }
@@ -1974,11 +4856,11 @@ var OcosayPlugin = async (input, _options) => {
1974
4856
  if (opencodeShowToast) {
1975
4857
  global.__opencode_tui_showToast__ = opencodeShowToast;
1976
4858
  }
1977
- setTimeout(() => {
4859
+ setTimeout(async () => {
1978
4860
  if (!opencodeShowToast) return;
1979
4861
  if (initError) {
1980
4862
  console.info(`[Ocosay] Ocosay v${pluginVersion} Initialization Failed: Initialization failed, please check config`);
1981
- opencodeShowToast({
4863
+ await opencodeShowToast({
1982
4864
  body: {
1983
4865
  title: `Ocosay v${pluginVersion} Initialization Failed`,
1984
4866
  message: "Initialization failed, please check config",
@@ -1987,7 +4869,7 @@ var OcosayPlugin = async (input, _options) => {
1987
4869
  });
1988
4870
  } else {
1989
4871
  console.info(`[Ocosay] Ocosay v${pluginVersion} Plugin Loaded: Auto-read: ${config.autoRead ? "ON" : "OFF"}`);
1990
- opencodeShowToast({
4872
+ await opencodeShowToast({
1991
4873
  body: {
1992
4874
  title: `Ocosay v${pluginVersion} Plugin Loaded`,
1993
4875
  message: `Auto-read: ${config.autoRead ? "ON" : "OFF"}`,
@@ -2045,7 +4927,7 @@ var OcosayPlugin = async (input, _options) => {
2045
4927
  if (input.client?.tui?.showToast) {
2046
4928
  global.__opencode_tui_showToast__ = input.client.tui.showToast;
2047
4929
  }
2048
- setTimeout(() => {
4930
+ setTimeout(async () => {
2049
4931
  const showToastFn = input.client?.tui?.showToast;
2050
4932
  if (!showToastFn) {
2051
4933
  console.warn("[Ocosay] showToast not available");
@@ -2053,7 +4935,7 @@ var OcosayPlugin = async (input, _options) => {
2053
4935
  }
2054
4936
  if (initError) {
2055
4937
  console.info(`[Ocosay] Ocosay v${pluginVersion} Initialization Failed: Initialization failed, please check config`);
2056
- showToastFn({
4938
+ await showToastFn({
2057
4939
  body: {
2058
4940
  title: `Ocosay v${pluginVersion} Initialization Failed`,
2059
4941
  message: "Initialization failed, please check config",
@@ -2062,7 +4944,7 @@ var OcosayPlugin = async (input, _options) => {
2062
4944
  });
2063
4945
  } else {
2064
4946
  console.info(`[Ocosay] Ocosay v${pluginVersion} Plugin Loaded: Auto-read: ${config.autoRead ? "ON" : "OFF"}`);
2065
- showToastFn({
4947
+ await showToastFn({
2066
4948
  body: {
2067
4949
  title: `Ocosay v${pluginVersion} Plugin Loaded`,
2068
4950
  message: `Auto-read: ${config.autoRead ? "ON" : "OFF"}`,
@@ -2083,4 +4965,28 @@ export {
2083
4965
  plugin_default as default,
2084
4966
  server
2085
4967
  };
4968
+ /*! Bundled license information:
4969
+
4970
+ howler/dist/howler.js:
4971
+ (*!
4972
+ * howler.js v2.2.4
4973
+ * howlerjs.com
4974
+ *
4975
+ * (c) 2013-2020, James Simpson of GoldFire Studios
4976
+ * goldfirestudios.com
4977
+ *
4978
+ * MIT License
4979
+ *)
4980
+ (*!
4981
+ * Spatial Plugin - Adds support for stereo and 3D audio where Web Audio is supported.
4982
+ *
4983
+ * howler.js v2.2.4
4984
+ * howlerjs.com
4985
+ *
4986
+ * (c) 2013-2020, James Simpson of GoldFire Studios
4987
+ * goldfirestudios.com
4988
+ *
4989
+ * MIT License
4990
+ *)
4991
+ */
2086
4992
  //# sourceMappingURL=plugin.js.map