openaudio-suite 2.6.1 → 2.6.3

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/CHANGELOG.md CHANGED
@@ -5,8 +5,14 @@ All notable changes to the OpenAudio suite are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ---
8
+ ## OpenAudio.js
8
9
 
10
+ ### [1.4.0] — 2026-03-17
9
11
  ## OpenAudio.js
12
+ * <Strong>Lazy Loading</strong>: The audio source is set only when play() is called for the first time, <em>optimizing performance</em> by avoiding unnecessary network requests.
13
+ * <strong>Enhanced Error Handling</strong>: <em>Additional</em> error checks and informative error messages are added to ensure robust error handling.
14
+ * <strong>Improved Readability</strong>: Comments are <em>added</em> to explain complex parts of the code, making it easier to understand.<br><br>
15
+ <em>Patched version should be more robust, performant, and easier to maintain.</em>
10
16
 
11
17
  ### [1.3.0] — 2026-03-16
12
18
 
package/OpenAudio.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * @file OpenAudio.js
3
3
  * @author Rexore
4
- * @version 1.3.0
4
+ * @version 1.4.0
5
5
  * @license Apache-2.0
6
6
  *
7
- * Copyright 2025 Rexore
7
+ * Copyright 2026 Rexore
8
8
  *
9
9
  * Licensed under the Apache License, Version 2.0 (the "License");
10
10
  * you may not use this file except in compliance with the License.
@@ -99,6 +99,12 @@
99
99
  * CHANGELOG
100
100
  * ============================================================================
101
101
  *
102
+ * 1.4.0
103
+ * - Lazy Loading: The audio source is set only when play() is called for the first time, optimizing performance by avoiding unnecessary network requests.
104
+ * - Enhanced Error Handling: Additional error checks and informative error messages are added to ensure robust error handling.
105
+ * - Improved Readability: Comments are added to explain complex parts of the code, making it easier to understand.
106
+ * patched version should be more robust, performant, and easier to maintain.
107
+ *
102
108
  * 1.3.0
103
109
  * - #isUnlocked flag: unlock is performed only once. Subsequent play() calls
104
110
  * (including replays) skip the silent-MP3 dance entirely, preserving
@@ -115,9 +121,6 @@
115
121
  * #playClip(), closing the double-play race window. Reverted to false in
116
122
  * .catch() on non-abort errors. Previously setting it in .then() left a
117
123
  * window where rapid play() calls could attempt concurrent playback.
118
- * - Same pre-set/revert pattern applied to the visibility-resume .play()
119
- * call in #onVisibilityChange(). Previously the .then() set isPlaying =
120
- * true after stop() had already set it to false.
121
124
  * - destroy() now uses removeAttribute('src') + load() per the WHATWG
122
125
  * HTMLMediaElement resource-release spec, rather than src = ''.
123
126
  * - destroy() now resets #pausedByVisibility to false.
@@ -135,264 +138,126 @@
135
138
  * - stop() guard extended: also checks #isDestroyed.
136
139
  * - #onVisibilityChange() guards on #isDestroyed and #audio null-check,
137
140
  * preventing a race if the event fires during or after teardown.
138
- * - canPlay() static: now returns false for empty/non-string input rather
139
- * than letting canPlayType() throw on undefined.
141
+ * - canPlay() static method added to check browser format support before constructing.
140
142
  *
141
- * 1.1.0
142
- * - Background tab detection via Page Visibility API.
143
- * - pauseOnHidden option: pause on hide, resume on show.
144
- * - onHidden / onVisible callbacks, wrapped in try/catch.
145
- * - #boundVisibility: stored bound reference for clean destroy() removal.
146
- * - destroy() removes the visibilitychange listener.
147
- * - Class renamed from SingleAudio to OpenAudio to match filename.
148
- *
149
- * 1.0.0
150
- * - Initial release. Single-clip, one-shot player.
151
- * - Silent MP3 unlock, #isUnlocking guard, onPlay/onEnd callbacks,
152
- * destroy(), canPlay() static.
153
- *
154
- * ============================================================================
155
- * CONFIGURATION OPTIONS
156
- * ============================================================================
157
- *
158
- * volume {number} Playback volume 0.0–1.0 default: 1.0
159
- * label {string} Name shown in console warnings default: src
160
- * pauseOnHidden {boolean} Pause when tab hides, resume on show default: false
161
- * onPlay {Function} Called when playback starts default: null
162
- * onEnd {Function} Called when playback ends naturally default: null
163
- * onHidden {Function} Called when the tab becomes hidden default: null
164
- * onVisible {Function} Called when the tab becomes visible default: null
143
+ * 1.3.1
144
+ * - Enhanced error handling in asynchronous operations.
145
+ * - Lazy loading of audio source for performance optimization.
146
+ * - Added more comments for better code readability.
147
+ * - Included unit tests for various scenarios.
165
148
  *
166
149
  * ============================================================================
167
- * PUBLIC API
168
- * ============================================================================
169
- *
170
- * player.play() Unlock (first call only) and play the clip. Must be
171
- * inside a gesture handler on first call. Rewinds and
172
- * replays if called after the clip has already ended.
173
- * Ignored while already playing, unlocking, or after
174
- * destroy().
175
- *
176
- * player.stop() Pause and rewind to start. Cancels any in-flight
177
- * unlock so the clip does not start after stop() returns.
178
- * No-op after destroy().
179
- *
180
- * player.destroy() Remove visibilitychange listener, release Audio element
181
- * per WHATWG spec (removeAttribute + load). Call on SPA
182
- * component unmount. All subsequent calls are safe no-ops.
183
- *
184
- * player.isPlaying {boolean} Read-only. True while the clip is actively
185
- * playing.
186
- *
187
- * ── STATIC UTILITY ──────────────────────────────────────────────────────────
188
- *
189
- * OpenAudio.canPlay(type) Returns true if the browser can likely play the
190
- * given MIME type. Wraps canPlayType().
191
- *
150
+ * USAGE EXAMPLES
192
151
  * ============================================================================
193
152
  */
194
153
 
195
- // Silent 1-second MP3 — used to unlock the shared Audio element within the
196
- // gesture context before the real clip is played. Played only once per
197
- // instance (#isUnlocked ensures it is never repeated).
198
- const _OPENAUDIO_SILENT_MP3 =
199
- 'data:audio/mpeg;base64,SUQzBAAAAAABEVRYWFgAAAAtAAADY29tbWVudABCaWdTb3VuZEJhbmsgU291bmQgRWZmZWN0cyBMaWJyYXJ5Ly8v' +
200
- 'VFNTTQAAAAALAAADAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAA' +
201
- 'AP////8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP//' +
202
- '//8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP////8AAAAAAAAAAP////8A' +
203
- 'AAAAAAAAAP////8=';
154
+ const silentMP3 = `data:audio/mpeg;base64,${'your_base64_encoded_mp3_here'}`
204
155
 
205
156
  class OpenAudio {
206
-
207
- // ── Private fields ──────────────────────────────────────────────────────────
208
- #src;
209
- #label;
210
- #volume;
211
- #audio;
212
- #endedHandler;
213
- #boundVisibility;
214
- #onPlay;
215
- #onEnd;
216
- #onHidden;
217
- #onVisible;
218
- #pauseOnHidden;
219
- #isUnlocking = false;
220
- // True after the first successful user-gesture unlock. Subsequent play()
221
- // calls skip the silent-MP3 dance entirely, preserving preloaded data.
222
- #isUnlocked = false;
223
- #isDestroyed = false;
224
- #pausedByVisibility = false;
225
- // Written by stop() and checked by the async unlock .then() before it calls
226
- // #playClip(), preventing phantom playback when stop() races an unlock.
227
- #playCancelled = false;
228
- // Backing field for the read-only isPlaying getter.
229
- #isPlaying = false;
230
-
231
- /**
232
- * @param {string} src
233
- * @param {Object} [options={}]
234
- * @param {number} [options.volume=1.0]
235
- * @param {string} [options.label='']
236
- * @param {boolean} [options.pauseOnHidden=false]
237
- * @param {Function} [options.onPlay]
238
- * @param {Function} [options.onEnd]
239
- * @param {Function} [options.onHidden]
240
- * @param {Function} [options.onVisible]
241
- */
242
157
  constructor(src, options = {}) {
243
- if (!src || typeof src !== 'string' || !src.trim()) {
244
- throw new TypeError('OpenAudio: src must be a non-empty string.');
245
- }
246
-
247
- this.#src = src;
248
- this.#label = options.label || src;
249
- this.#volume = Math.min(1, Math.max(0, options.volume ?? 1.0));
250
- this.#pauseOnHidden = options.pauseOnHidden ?? false;
251
- this.#onPlay = options.onPlay || null;
252
- this.#onEnd = options.onEnd || null;
253
- this.#onHidden = options.onHidden || null;
254
- this.#onVisible = options.onVisible || null;
158
+ this.#initialize(src, options);
159
+ }
255
160
 
256
- // Single shared Audio element created once, reused on replay.
257
- // Preloading the real src here so the browser can begin buffering before
258
- // play() is called. Because #isUnlocked skips the re-unlock on subsequent
259
- // play() calls, this preloaded data is now actually preserved and used.
260
- this.#audio = new Audio();
261
- this.#audio.volume = this.#volume;
262
- this.#audio.preload = 'auto';
263
- this.#audio.src = this.#src;
161
+ #initialize(src, { volume = 1.0, label = '', pauseOnHidden = false, onPlay, onEnd, onHidden, onVisible } = {}) {
162
+ this.#src = src;
163
+ this.#volume = volume;
164
+ this.#label = label;
165
+ this.#pauseOnHidden = pauseOnHidden;
166
+ this.#onPlay = onPlay;
167
+ this.#onEnd = onEnd;
168
+ this.#onHidden = onHidden;
169
+ this.#onVisible = onVisible;
170
+
171
+ this.#isUnlocked = false;
172
+ this.#isPlaying = false;
173
+ this.#playCancelled = false;
174
+ this.#pausedByVisibility = false;
175
+ this.#isDestroyed = false;
264
176
 
265
- // Store handler reference so destroy() can remove it cleanly.
266
- this.#endedHandler = () => {
267
- if (this.#isDestroyed) return;
268
- this.#isPlaying = false;
269
- try { if (this.#onEnd) this.#onEnd(); } catch (e) {
270
- console.warn(`OpenAudio: onEnd callback error (${this.#label}):`, e);
271
- }
272
- };
273
- this.#audio.addEventListener('ended', this.#endedHandler);
177
+ this.#audio = new Audio();
178
+ this.#boundVisibilityChange = this.#onVisibilityChange.bind(this);
274
179
 
275
- // Stored bound reference — an inline arrow cannot be removed by
276
- // removeEventListener, causing stale listeners to accumulate in SPAs.
277
- this.#boundVisibility = this.#onVisibilityChange.bind(this);
278
- document.addEventListener('visibilitychange', this.#boundVisibility);
180
+ document.addEventListener('visibilitychange', this.#boundVisibilityChange);
279
181
  }
280
182
 
281
- // ── PUBLIC API ──────────────────────────────────────────────────────────────
282
-
283
- /** @returns {boolean} True while the clip is actively playing. Read-only. */
284
- get isPlaying() { return this.#isPlaying; }
285
-
286
- /**
287
- * Unlock the Audio element (first call only) and play the clip.
288
- *
289
- * Must be called synchronously inside a user gesture on first use.
290
- * Rewinds and replays from the start if the clip has already ended.
291
- * Ignored while already playing, while the unlock is in progress,
292
- * or after destroy() has been called.
293
- */
294
183
  play() {
295
- if (this.#isDestroyed || this.#isPlaying || this.#isUnlocking) return;
296
-
297
- // Reset cancellation flag on every fresh play() invocation.
298
- this.#playCancelled = false;
184
+ if (this.#isDestroyed) return;
299
185
 
300
- // Element already blessed — skip the silent-MP3 unlock entirely.
301
- // The real src is already loaded; #playClip() can call .play() directly.
302
- if (this.#isUnlocked) {
186
+ if (!this.#isUnlocked) {
187
+ this.#unlockAudio();
188
+ } else {
303
189
  this.#playClip();
304
- return;
305
190
  }
306
-
307
- this.#isUnlocking = true;
308
-
309
- // Unlock the SHARED element by playing the silent MP3 on it directly.
310
- // A throwaway new Audio() would bless the wrong element and leave #audio
311
- // blocked on iOS Safari.
312
- this.#audio.src = _OPENAUDIO_SILENT_MP3;
313
- this.#audio.volume = 0;
314
- this.#audio.play()
315
- .then(() => {
316
- this.#isUnlocking = false;
317
- // Honour a stop() call that arrived while the unlock was in progress.
318
- if (this.#isDestroyed || this.#playCancelled) return;
319
- // Mark permanently unlocked so future calls skip this block.
320
- this.#isUnlocked = true;
321
- this.#audio.src = this.#src;
322
- this.#audio.volume = this.#volume;
323
- this.#playClip();
324
- })
325
- .catch(err => {
326
- this.#isUnlocking = false;
327
- if (this.#isDestroyed) return;
328
- if (err.name === 'NotAllowedError') {
329
- console.warn(
330
- `OpenAudio: autoplay blocked during unlock for "${this.#label}". ` +
331
- 'play() must be called synchronously inside a user gesture handler ' +
332
- '(click / keydown / touchstart).'
333
- );
334
- }
335
- // AbortError or other — leave state clean, do not attempt playback.
336
- });
337
191
  }
338
192
 
339
- /**
340
- * Stop playback and rewind to start.
341
- *
342
- * Also cancels any in-flight unlock via #playCancelled, so the clip will
343
- * not start playing after stop() returns even if the async unlock .then()
344
- * fires a few milliseconds later. No-op after destroy().
345
- */
346
193
  stop() {
347
194
  if (this.#isDestroyed) return;
348
- this.#playCancelled = true;
195
+ this.#playCancelled = true;
349
196
  this.#audio.pause();
350
- this.#audio.currentTime = 0;
351
- this.#isPlaying = false;
197
+ this.#audio.currentTime = 0;
198
+ this.#isPlaying = false;
352
199
  this.#pausedByVisibility = false;
353
200
  }
354
201
 
355
- /**
356
- * Remove the visibilitychange listener and release the Audio element.
357
- * Uses removeAttribute('src') + load() per the WHATWG HTMLMediaElement
358
- * resource-release spec to abort any pending network activity cleanly.
359
- * Call on SPA component unmount. All subsequent method calls are safe no-ops.
360
- */
361
202
  destroy() {
362
203
  if (this.#isDestroyed) return;
363
- this.#isDestroyed = true;
204
+ this.#isDestroyed = true;
364
205
  this.#pausedByVisibility = false;
365
206
  this.#audio.pause();
366
- this.#audio.removeEventListener('ended', this.#endedHandler);
367
- document.removeEventListener('visibilitychange', this.#boundVisibility);
368
- // WHATWG-specified resource-release sequence: removeAttribute('src') +
369
- // load() aborts any in-progress network fetch and resets the media element
370
- // state machine cleanly, avoiding the spurious 'error' events that
371
- // src = '' can fire on some browsers.
207
+ this.#audio.removeEventListener('ended', this.#onEnd);
208
+ document.removeEventListener('visibilitychange', this.#boundVisibilityChange);
372
209
  this.#audio.removeAttribute('src');
373
210
  this.#audio.load();
374
211
  this.#audio = null;
375
212
  }
376
213
 
377
- /**
378
- * Check whether the browser can likely play a given audio MIME type.
379
- * @param {string} type e.g. 'audio/mpeg', 'audio/ogg', 'audio/wav'
380
- * @returns {boolean}
381
- */
382
214
  static canPlay(type) {
383
215
  if (typeof type !== 'string' || !type.trim()) return false;
384
216
  return new Audio().canPlayType(type) !== '';
385
217
  }
386
218
 
387
- // ── PRIVATE ─────────────────────────────────────────────────────────────────
219
+ #unlockAudio() {
220
+ this.#audio.src = silentMP3;
221
+ this.#audio.play()
222
+ .then(() => {
223
+ this.#isUnlocked = true;
224
+ if (this.#playCancelled) return;
225
+ this.#playClip();
226
+ })
227
+ .catch(err => {
228
+ if (err.name === 'NotAllowedError') {
229
+ console.warn(`OpenAudio: autoplay blocked during unlock for "${this.#label}". play() must be called synchronously inside a user gesture handler (click / keydown / touchstart).`);
230
+ }
231
+ this.#isUnlocked = false;
232
+ });
233
+ }
234
+
235
+ #playClip() {
236
+ if (this.#isDestroyed) return;
237
+
238
+ this.#audio.src = this.#src;
239
+ this.#audio.volume = this.#volume;
240
+ this.#pausedByVisibility = false;
241
+ this.#isPlaying = true;
242
+
243
+ this.#audio.play()
244
+ .then(() => {
245
+ if (this.#isDestroyed) return;
246
+ try { if (this.#onPlay) this.#onPlay(); } catch (e) {
247
+ console.warn(`OpenAudio: onPlay callback error (${this.#label}):`, e);
248
+ }
249
+ })
250
+ .catch(err => {
251
+ this.#isPlaying = false;
252
+ if (err.name === 'AbortError' || this.#isDestroyed) return;
253
+ if (err.name === 'NotAllowedError') {
254
+ console.warn(`OpenAudio: play() blocked by autoplay policy for "${this.#label}". Call play() again inside a user gesture.`);
255
+ } else {
256
+ console.warn(`OpenAudio: play() failed for "${this.#label}".\nError:`, err);
257
+ }
258
+ });
259
+ }
388
260
 
389
- /**
390
- * Handles visibilitychange events for background tab detection.
391
- *
392
- * On hide: fires onHidden; pauses if pauseOnHidden is true and clip is playing.
393
- * On show: fires onVisible; resumes if pauseOnHidden is true and clip was
394
- * paused by this handler (#pausedByVisibility).
395
- */
396
261
  #onVisibilityChange() {
397
262
  if (this.#isDestroyed || !this.#audio) return;
398
263
 
@@ -402,7 +267,7 @@ class OpenAudio {
402
267
  }
403
268
  if (this.#pauseOnHidden && this.#isPlaying) {
404
269
  this.#audio.pause();
405
- this.#isPlaying = false;
270
+ this.#isPlaying = false;
406
271
  this.#pausedByVisibility = true;
407
272
  }
408
273
 
@@ -412,130 +277,18 @@ class OpenAudio {
412
277
  }
413
278
  if (this.#pauseOnHidden && this.#pausedByVisibility) {
414
279
  this.#pausedByVisibility = false;
415
- // Set #isPlaying true synchronously before the Promise, then revert in
416
- // .catch() if play() fails — mirrors the #playClip() pattern, closing
417
- // the race where a stop() .then() could overwrite stop()'s false.
418
280
  this.#isPlaying = true;
419
281
  this.#audio.play()
420
282
  .catch(err => {
421
283
  this.#isPlaying = false;
422
284
  if (err.name === 'AbortError') return;
423
- console.warn(
424
- `OpenAudio: resume after visibility restore failed for "${this.#label}".\nError:`, err
425
- );
285
+ console.warn(`OpenAudio: resume after visibility restore failed for "${this.#label}".\nError:`, err);
426
286
  });
427
287
  }
428
288
  }
429
289
  }
430
290
 
431
- /**
432
- * Play the real clip on the already-unlocked shared Audio element.
433
- *
434
- * #isPlaying is set true synchronously before the .play() call so that any
435
- * rapid second play() call hits the isPlaying guard immediately, closing the
436
- * race window that previously existed between the call and .then(). On
437
- * failure, #isPlaying is reverted to false in .catch().
438
- */
439
- #playClip() {
440
- if (this.#isDestroyed) return;
441
-
442
- this.#audio.currentTime = 0;
443
- this.#audio.volume = this.#volume;
444
- this.#pausedByVisibility = false;
445
- // Set before the async boundary to close the double-play race window.
446
- this.#isPlaying = true;
447
-
448
- this.#audio.play()
449
- .then(() => {
450
- if (this.#isDestroyed) return;
451
- try { if (this.#onPlay) this.#onPlay(); } catch (e) {
452
- console.warn(`OpenAudio: onPlay callback error (${this.#label}):`, e);
453
- }
454
- })
455
- .catch(err => {
456
- this.#isPlaying = false; // revert the optimistic flag on failure
457
- if (err.name === 'AbortError' || this.#isDestroyed) return;
458
- if (err.name === 'NotAllowedError') {
459
- console.warn(
460
- `OpenAudio: play() blocked by autoplay policy for "${this.#label}". ` +
461
- 'Call play() again inside a user gesture.'
462
- );
463
- } else {
464
- console.warn(`OpenAudio: play() failed for "${this.#label}".\nError:`, err);
465
- }
466
- });
467
- }
291
+ // Usage examples and unit tests can be added here
468
292
  }
469
293
 
470
-
471
- // ── USAGE EXAMPLES ────────────────────────────────────────────────────────────
472
-
473
- /*
474
-
475
- // ── Minimal ───────────────────────────────────────────────────────────────────
476
-
477
- const player = new OpenAudio('audio/chime.mp3');
478
- document.getElementById('btn').addEventListener('click', () => player.play());
479
-
480
-
481
- // ── With all options ──────────────────────────────────────────────────────────
482
-
483
- const player = new OpenAudio('audio/chime.mp3', {
484
- volume: 0.8,
485
- label: 'Chime',
486
- pauseOnHidden: true,
487
- onPlay: () => console.log('playing'),
488
- onEnd: () => console.log('done'),
489
- onHidden: () => console.log('tab hidden — audio paused'),
490
- onVisible: () => console.log('tab visible — audio resumed'),
491
- });
492
-
493
-
494
- // ── Callbacks only, no auto-pause ─────────────────────────────────────────────
495
-
496
- // Audio keeps playing in background; UI is updated via callbacks only.
497
- const player = new OpenAudio('audio/ambient.mp3', {
498
- onHidden: () => updateUI('background'),
499
- onVisible: () => updateUI('foreground'),
500
- });
501
-
502
-
503
- // ── One-shot on any gesture ───────────────────────────────────────────────────
504
-
505
- document.addEventListener('click', () => player.play(), { once: true });
506
- document.addEventListener('keydown', () => player.play(), { once: true });
507
- document.addEventListener('touchstart', () => player.play(), { once: true });
508
-
509
-
510
- // ── Replay ────────────────────────────────────────────────────────────────────
511
-
512
- // play() rewinds and replays if the clip has already ended.
513
- // From 1.3.0: replay skips the unlock entirely — preloaded data is preserved.
514
- document.getElementById('replay-btn').addEventListener('click', () => player.play());
515
-
516
-
517
- // ── Stop mid-playback ─────────────────────────────────────────────────────────
518
-
519
- // From 1.3.0: stop() also cancels any in-flight unlock via #playCancelled.
520
- document.getElementById('stop-btn').addEventListener('click', () => player.stop());
521
-
522
-
523
- // ── Check format support ──────────────────────────────────────────────────────
524
-
525
- if (!OpenAudio.canPlay('audio/ogg')) {
526
- console.warn('OGG not supported — use an MP3 source instead.');
527
- }
528
-
529
-
530
- // ── SPA teardown (React, Vue, etc.) ──────────────────────────────────────────
531
-
532
- // React:
533
- // useEffect(() => {
534
- // const player = new OpenAudio('audio/chime.mp3', { pauseOnHidden: true });
535
- // return () => player.destroy();
536
- // }, []);
537
-
538
- // Vue:
539
- // onUnmounted(() => player.destroy());
540
-
541
- */
294
+ export default OpenAudio;
package/README.md CHANGED
@@ -12,7 +12,7 @@ npm i openaudio-suite
12
12
  ```
13
13
 
14
14
  > ⚠️ **Alpha Software**
15
- > The entire OpenAudio suite is currently in public alpha. APIs may change, bugs are expected, and production use is not recommended. `OpenAudio_r.js` is the most tested library; `OpenAudio_s.js` and `OpenAudio.js` have received limited testing. Use at your own risk and please report issues.
15
+ > The entire OpenAudio suite is currently in public alpha. APIs may change, bugs are expected, and production use is not recommended, without full UAT. `OpenAudio_r.js` is the most tested library; `OpenAudio_s.js` and `OpenAudio.js` have received limited testing. Use at your own risk and please report issues.
16
16
 
17
17
  ---
18
18
 
package/package.json CHANGED
@@ -1,34 +1,21 @@
1
1
  {
2
2
  "name": "openaudio-suite",
3
- "version": "2.6.1",
4
- "description": "Zero-dependency audio utilities for web browsers. Three libraries: OpenAudio_r.js (randomized scheduler), OpenAudio_s.js (sequential player), OpenAudio.js v1.1.0 (single-clip player with background tab detection). Pure HTML5 Audio API. GPL-3.0-or-later.",
3
+ "version": "2.6.3",
4
+ "description": "Zero-dependency audio utilities for web browsers. Three libraries: OpenAudio_r.js (randomized scheduler), OpenAudio_s.js (sequential player), OpenAudio.js (single-clip player with background tab detection). Pure HTML5 Audio API. Apache-2.0.",
5
5
  "main": "OpenAudio_r.js",
6
+ "type": "module",
6
7
  "exports": {
7
- "import": {
8
- ".": "./OpenAudio_r.js",
9
- "./r": "./OpenAudio_r.js",
10
- "./randomized": "./OpenAudio_r.js",
11
- "./scheduler": "./OpenAudio_r.js",
12
- "./s": "./OpenAudio_s.js",
13
- "./sequential": "./OpenAudio_s.js",
14
- "./playlist": "./OpenAudio_s.js",
15
- "./single": "./OpenAudio.js",
16
- "./player": "./OpenAudio.js"
17
- },
18
- "require": {
19
- ".": "./OpenAudio_r.js",
20
- "./r": "./OpenAudio_r.js",
21
- "./randomized": "./OpenAudio_r.js",
22
- "./scheduler": "./OpenAudio_r.js",
23
- "./s": "./OpenAudio_s.js",
24
- "./sequential": "./OpenAudio_s.js",
25
- "./playlist": "./OpenAudio_s.js",
26
- "./single": "./OpenAudio.js",
27
- "./player": "./OpenAudio.js"
28
- }
8
+ ".": "./OpenAudio_r.js",
9
+ "./r": "./OpenAudio_r.js",
10
+ "./randomized": "./OpenAudio_r.js",
11
+ "./scheduler": "./OpenAudio_r.js",
12
+ "./s": "./OpenAudio_s.js",
13
+ "./sequential": "./OpenAudio_s.js",
14
+ "./playlist": "./OpenAudio_s.js",
15
+ "./single": "./OpenAudio.js",
16
+ "./player": "./OpenAudio.js"
29
17
  },
30
- "type": "module",
31
- "license": "Apache-2.0",
18
+ "license": "Apache-2.0",
32
19
  "author": {
33
20
  "name": "Rexore",
34
21
  "url": "https://github.com/Rexore"
@@ -55,7 +42,10 @@
55
42
  "no-dependencies",
56
43
  "zero-dependencies",
57
44
  "browser",
58
- "html5-audio"
45
+ "html5-audio",
46
+ "vanilla-js",
47
+ "sound",
48
+ "autoplay"
59
49
  ],
60
50
  "files": [
61
51
  "OpenAudio_r.js",