openaudio-suite 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +196 -0
- package/CONTRIBUTING.md +349 -0
- package/LICENSE +674 -0
- package/OpenAudio.js +386 -0
- package/OpenAudio_r.js +863 -0
- package/OpenAudio_s.js +445 -0
- package/README.md +433 -0
- package/docs/COMPARISON.md +449 -0
- package/docs/OPENAUDIO-v1.1.0.md +641 -0
- package/docs/OPENAUDIO_R.md +571 -0
- package/docs/OPENAUDIO_S.md +760 -0
- package/package.json +79 -0
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
# OpenAudio.js API Reference
|
|
2
|
+
|
|
3
|
+
**Version:** 1.1.0
|
|
4
|
+
**Class:** `OpenAudio`
|
|
5
|
+
**Use Case:** Single-clip, one-shot audio playback with background tab awareness
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
// Create a player
|
|
13
|
+
const player = new OpenAudio('audio/chime.mp3', {
|
|
14
|
+
volume: 0.8,
|
|
15
|
+
label: 'Chime',
|
|
16
|
+
pauseOnHidden: true, // Pause when tab loses focus
|
|
17
|
+
onPlay: () => console.log('Playing'),
|
|
18
|
+
onEnd: () => console.log('Done'),
|
|
19
|
+
onHidden: () => console.log('Tab hidden'),
|
|
20
|
+
onVisible: () => console.log('Tab visible')
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Play on user interaction
|
|
24
|
+
document.getElementById('btn').addEventListener('click', () => player.play());
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Constructor
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
new OpenAudio(src, options)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Parameters
|
|
36
|
+
|
|
37
|
+
#### `src` (string, required)
|
|
38
|
+
Path or data URI to your audio file.
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
// File path
|
|
42
|
+
new OpenAudio('audio/sound.mp3');
|
|
43
|
+
|
|
44
|
+
// Absolute URL
|
|
45
|
+
new OpenAudio('https://cdn.example.com/audio/sound.mp3');
|
|
46
|
+
|
|
47
|
+
// Data URI (embedded, no external file)
|
|
48
|
+
new OpenAudio('data:audio/mp3;base64,SUQzBA...');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### `options` (object, optional)
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
{
|
|
55
|
+
volume: 0.8, // Number: 0.0–1.0 (default: 1.0)
|
|
56
|
+
label: 'My Sound', // String: display name for console messages
|
|
57
|
+
pauseOnHidden: false, // Boolean: pause when tab hides (default: false)
|
|
58
|
+
onPlay: () => {}, // Function: called when playback starts
|
|
59
|
+
onEnd: () => {}, // Function: called when playback ends
|
|
60
|
+
onHidden: () => {}, // Function: called when tab becomes hidden
|
|
61
|
+
onVisible: () => {} // Function: called when tab becomes visible
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**All options are optional.** You can pass an empty object `{}` or omit it entirely.
|
|
66
|
+
|
|
67
|
+
### Constructor Examples
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// Minimal
|
|
71
|
+
const player = new OpenAudio('audio/click.mp3');
|
|
72
|
+
|
|
73
|
+
// With volume
|
|
74
|
+
const player = new OpenAudio('audio/click.mp3', { volume: 0.5 });
|
|
75
|
+
|
|
76
|
+
// With callbacks
|
|
77
|
+
const player = new OpenAudio('audio/victory.mp3', {
|
|
78
|
+
volume: 0.9,
|
|
79
|
+
label: 'Victory Fanfare',
|
|
80
|
+
onPlay: () => console.log('Victory sound playing'),
|
|
81
|
+
onEnd: () => console.log('Victory sound finished')
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// With background tab control
|
|
85
|
+
const player = new OpenAudio('audio/ambient.mp3', {
|
|
86
|
+
volume: 0.75,
|
|
87
|
+
label: 'Ambient Sound',
|
|
88
|
+
pauseOnHidden: true, // Pause when tab loses focus
|
|
89
|
+
onPlay: () => updateUI('sound_playing'),
|
|
90
|
+
onHidden: () => updateUI('background'),
|
|
91
|
+
onVisible: () => updateUI('foreground')
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Callbacks without pause-on-hide (just notifications)
|
|
95
|
+
const player = new OpenAudio('audio/notification.mp3', {
|
|
96
|
+
onHidden: () => console.log('Tab hidden, audio continues'),
|
|
97
|
+
onVisible: () => console.log('Tab visible again')
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Constructor Errors
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
// Throws TypeError if src is missing or not a string
|
|
105
|
+
new OpenAudio(); // ❌ TypeError
|
|
106
|
+
new OpenAudio(123); // ❌ TypeError
|
|
107
|
+
new OpenAudio(''); // ❌ TypeError
|
|
108
|
+
new OpenAudio(null); // ❌ TypeError
|
|
109
|
+
|
|
110
|
+
// Valid
|
|
111
|
+
new OpenAudio('audio.mp3'); // ✅
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Public Methods
|
|
117
|
+
|
|
118
|
+
### `play()`
|
|
119
|
+
|
|
120
|
+
Unlock the audio element (if needed) and play the clip.
|
|
121
|
+
|
|
122
|
+
**Must be called inside a user gesture on first use.**
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
// First call (inside gesture)
|
|
126
|
+
document.addEventListener('click', () => {
|
|
127
|
+
player.play(); // ✅ Starts playback
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Subsequent calls
|
|
131
|
+
player.play(); // Safe to call anytime
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Behavior:**
|
|
135
|
+
- If already playing: ignored (safe)
|
|
136
|
+
- If paused: resumes from current position
|
|
137
|
+
- If finished: restarts from beginning
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### `stop()`
|
|
142
|
+
|
|
143
|
+
Stop playback and rewind to the start.
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
player.stop();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Behavior:**
|
|
150
|
+
- Pauses the audio
|
|
151
|
+
- Rewinds to 0:00
|
|
152
|
+
- Sets `isPlaying = false`
|
|
153
|
+
- Clears any pause-on-hidden state
|
|
154
|
+
- Calling `play()` after restarts from beginning
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
player.play(); // Playing at 2:30
|
|
158
|
+
player.stop(); // Paused at 0:00
|
|
159
|
+
player.play(); // Restart from 0:00
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### `destroy()`
|
|
165
|
+
|
|
166
|
+
Remove all listeners and clean up the audio element.
|
|
167
|
+
|
|
168
|
+
**Call this on SPA component unmount.**
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
player.destroy();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Behavior:**
|
|
175
|
+
- Removes the `visibilitychange` listener (prevents stale listeners in SPAs)
|
|
176
|
+
- Stops playback
|
|
177
|
+
- Releases the audio element
|
|
178
|
+
- After this, do not call any other methods
|
|
179
|
+
|
|
180
|
+
**Why this matters:** Without proper cleanup, listeners accumulate in SPAs:
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
// React example
|
|
184
|
+
import { useEffect } from 'react';
|
|
185
|
+
import OpenAudio from './OpenAudio.js';
|
|
186
|
+
|
|
187
|
+
function NotificationSound() {
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
const player = new OpenAudio('notification.mp3');
|
|
190
|
+
return () => player.destroy(); // ✅ Clean up on unmount
|
|
191
|
+
}, []);
|
|
192
|
+
|
|
193
|
+
return <button onClick={() => player.play()}>Notify</button>;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### `canPlay(type)` — Static Method
|
|
200
|
+
|
|
201
|
+
Check if the browser supports a specific audio format.
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
OpenAudio.canPlay(type) → boolean
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Parameters:**
|
|
208
|
+
- **`type`** (string) — MIME type
|
|
209
|
+
- `'audio/mpeg'` or `'audio/mp3'` — MP3
|
|
210
|
+
- `'audio/ogg'` — OGG Vorbis
|
|
211
|
+
- `'audio/wav'` — WAV
|
|
212
|
+
- `'audio/webm'` — WebM
|
|
213
|
+
- `'audio/flac'` — FLAC
|
|
214
|
+
|
|
215
|
+
**Returns:** `true` if supported, `false` otherwise
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
// Check support before constructing
|
|
219
|
+
if (OpenAudio.canPlay('audio/ogg')) {
|
|
220
|
+
const player = new OpenAudio('audio/sound.ogg');
|
|
221
|
+
} else {
|
|
222
|
+
const player = new OpenAudio('audio/sound.mp3'); // Fallback
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Public Properties
|
|
229
|
+
|
|
230
|
+
### `isPlaying`
|
|
231
|
+
|
|
232
|
+
**Type:** `boolean` (read-only)
|
|
233
|
+
|
|
234
|
+
`true` if a clip is actively playing, `false` if paused or stopped.
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
const player = new OpenAudio('audio/sound.mp3');
|
|
238
|
+
|
|
239
|
+
document.addEventListener('click', () => player.play());
|
|
240
|
+
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
if (player.isPlaying) {
|
|
243
|
+
console.log('Still playing');
|
|
244
|
+
} else {
|
|
245
|
+
console.log('Finished or paused');
|
|
246
|
+
}
|
|
247
|
+
}, 500);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Callbacks
|
|
253
|
+
|
|
254
|
+
### `onPlay()`
|
|
255
|
+
|
|
256
|
+
Called when playback **starts** (after the unlock MP3 finishes).
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
const player = new OpenAudio('audio/effect.mp3', {
|
|
260
|
+
onPlay: () => {
|
|
261
|
+
console.log('Playback started');
|
|
262
|
+
updateUI('sound_active');
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Thrown errors are caught:** Errors in callbacks are logged but don't affect playback.
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
const player = new OpenAudio('audio/effect.mp3', {
|
|
271
|
+
onPlay: () => {
|
|
272
|
+
throw new Error('Oops!');
|
|
273
|
+
// Error logged to console, playback NOT affected
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
### `onEnd()`
|
|
281
|
+
|
|
282
|
+
Called when playback **finishes naturally** (clip reaches end).
|
|
283
|
+
|
|
284
|
+
Does **not** fire if you call `stop()` before the clip ends.
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
const player = new OpenAudio('audio/narration.mp3', {
|
|
288
|
+
onEnd: () => {
|
|
289
|
+
console.log('Narration finished');
|
|
290
|
+
playNextScene();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Thrown errors are caught:** Same as `onPlay()`.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
### `onHidden()` ← **NEW in v1.1.0**
|
|
300
|
+
|
|
301
|
+
Called when the tab/window **becomes hidden** (user switches tabs, minimizes window, etc.).
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
const player = new OpenAudio('audio/background-music.mp3', {
|
|
305
|
+
onHidden: () => {
|
|
306
|
+
console.log('Tab hidden');
|
|
307
|
+
updateUI('background_mode');
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Behavior:**
|
|
313
|
+
- Fires whenever `document.visibilityState` changes to `'hidden'`
|
|
314
|
+
- Fires regardless of `pauseOnHidden` setting
|
|
315
|
+
- Useful for updating UI to reflect background state
|
|
316
|
+
|
|
317
|
+
**Thrown errors are caught:** Same error handling as other callbacks.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
### `onVisible()` ← **NEW in v1.1.0**
|
|
322
|
+
|
|
323
|
+
Called when the tab/window **becomes visible** (user returns to the tab).
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
const player = new OpenAudio('audio/background-music.mp3', {
|
|
327
|
+
onVisible: () => {
|
|
328
|
+
console.log('Tab visible');
|
|
329
|
+
updateUI('foreground_mode');
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Behavior:**
|
|
335
|
+
- Fires whenever `document.visibilityState` changes to `'visible'`
|
|
336
|
+
- Fires regardless of `pauseOnHidden` setting
|
|
337
|
+
- If `pauseOnHidden: true` and the clip was paused, it automatically resumes
|
|
338
|
+
|
|
339
|
+
**Interaction with `pauseOnHidden`:**
|
|
340
|
+
- If `pauseOnHidden: false` → `onVisible` fires but audio keeps playing
|
|
341
|
+
- If `pauseOnHidden: true` → `onVisible` fires AND audio automatically resumes
|
|
342
|
+
|
|
343
|
+
**Thrown errors are caught:** Same as other callbacks.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Background Tab Detection ← **NEW in v1.1.0**
|
|
348
|
+
|
|
349
|
+
OpenAudio now uses the **Page Visibility API** to detect when the tab loses focus.
|
|
350
|
+
|
|
351
|
+
### `pauseOnHidden: false` (Default)
|
|
352
|
+
|
|
353
|
+
Audio keeps playing in the background. Only callbacks fire:
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
const player = new OpenAudio('ambient.mp3', {
|
|
357
|
+
pauseOnHidden: false, // Default
|
|
358
|
+
onHidden: () => console.log('Tab hidden, audio continues'),
|
|
359
|
+
onVisible: () => console.log('Tab visible')
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// User plays audio → switches tab → audio keeps playing
|
|
363
|
+
// User returns to tab → onVisible fires
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Use case:** Background music, ambient sounds where uninterrupted playback is desired.
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
### `pauseOnHidden: true`
|
|
371
|
+
|
|
372
|
+
Audio automatically pauses when tab hides, resumes when tab returns.
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
const player = new OpenAudio('narration.mp3', {
|
|
376
|
+
pauseOnHidden: true, // Pause on hide, resume on show
|
|
377
|
+
onHidden: () => console.log('Paused'),
|
|
378
|
+
onVisible: () => console.log('Resumed')
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// User plays → switches tab → audio pauses at 2:30
|
|
382
|
+
// User returns to tab → audio resumes at 2:30
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Use case:** Game audio, UI sounds, narration where playback should be tied to app focus.
|
|
386
|
+
|
|
387
|
+
**Note:** After returning to the tab, the `resume()` call may be silently blocked if the user hasn't interacted with the page since the tab was hidden. This is due to stricter autoplay policies on some browsers.
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Usage Patterns
|
|
392
|
+
|
|
393
|
+
### UI Sounds (Buttons)
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
const clickSound = new OpenAudio('ui/click.mp3', { volume: 0.7 });
|
|
397
|
+
|
|
398
|
+
document.querySelectorAll('button').forEach(btn => {
|
|
399
|
+
btn.addEventListener('click', (e) => {
|
|
400
|
+
clickSound.play();
|
|
401
|
+
// ... handle button press
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
### Notifications / Alerts
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
const notificationSound = new OpenAudio('notifications/alert.mp3', {
|
|
412
|
+
volume: 0.9,
|
|
413
|
+
label: 'Alert',
|
|
414
|
+
onEnd: () => console.log('Alert sound finished')
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
function showNotification(message) {
|
|
418
|
+
notificationSound.play();
|
|
419
|
+
// ... show notification UI
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
### Game Audio with Background Tab Control
|
|
426
|
+
|
|
427
|
+
```javascript
|
|
428
|
+
const gameSound = new OpenAudio('game/explosion.mp3', {
|
|
429
|
+
volume: 0.95,
|
|
430
|
+
pauseOnHidden: true, // Pause when player switches tabs
|
|
431
|
+
onPlay: () => playExplosionAnimation(),
|
|
432
|
+
onHidden: () => updateGameUI('paused'),
|
|
433
|
+
onVisible: () => updateGameUI('resumed')
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
document.getElementById('fire-btn').addEventListener('click', () => {
|
|
437
|
+
gameSound.play();
|
|
438
|
+
});
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
### Ambient Background Music
|
|
444
|
+
|
|
445
|
+
```javascript
|
|
446
|
+
const bgMusic = new OpenAudio('music/ambient.mp3', {
|
|
447
|
+
volume: 0.5,
|
|
448
|
+
pauseOnHidden: false, // Keep playing in background
|
|
449
|
+
onHidden: () => hidePlaybackUI(),
|
|
450
|
+
onVisible: () => showPlaybackUI()
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
window.addEventListener('load', () => {
|
|
454
|
+
bgMusic.play(); // Won't work — must be in gesture
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Must be in gesture:
|
|
458
|
+
document.addEventListener('click', () => {
|
|
459
|
+
bgMusic.play();
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
### Hover Feedback
|
|
466
|
+
|
|
467
|
+
```javascript
|
|
468
|
+
const hoverSound = new OpenAudio('ui/hover.mp3', { volume: 0.4 });
|
|
469
|
+
|
|
470
|
+
document.querySelectorAll('a, button').forEach(el => {
|
|
471
|
+
el.addEventListener('mouseenter', () => {
|
|
472
|
+
if (!hoverSound.isPlaying) {
|
|
473
|
+
hoverSound.play();
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Troubleshooting
|
|
482
|
+
|
|
483
|
+
### Audio Won't Play (Silent)
|
|
484
|
+
|
|
485
|
+
**Problem:** `play()` is called but nothing happens.
|
|
486
|
+
|
|
487
|
+
**Causes:**
|
|
488
|
+
1. Called outside a user gesture on first use
|
|
489
|
+
2. CORS or mixed-content issue (HTTP audio on HTTPS page)
|
|
490
|
+
3. Browser doesn't support the audio format
|
|
491
|
+
4. Audio file doesn't exist (404 error)
|
|
492
|
+
|
|
493
|
+
**Solutions:**
|
|
494
|
+
|
|
495
|
+
```javascript
|
|
496
|
+
// ✅ Correct: gesture context
|
|
497
|
+
document.addEventListener('click', () => player.play());
|
|
498
|
+
|
|
499
|
+
// ❌ Wrong: no gesture
|
|
500
|
+
setTimeout(() => player.play(), 1000);
|
|
501
|
+
|
|
502
|
+
// ❌ Wrong: HTTPS page, HTTP audio
|
|
503
|
+
new OpenAudio('http://example.com/audio.mp3'); // Mixed content warning
|
|
504
|
+
|
|
505
|
+
// ✅ Check format support
|
|
506
|
+
if (!OpenAudio.canPlay('audio/ogg')) {
|
|
507
|
+
// Fall back to another format
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
### "NotAllowedError" in Console
|
|
514
|
+
|
|
515
|
+
**Problem:** Console shows: `DOMException: play() failed because NotAllowedError`
|
|
516
|
+
|
|
517
|
+
**Cause:** Browser autoplay policy blocked the attempt (not in a user gesture).
|
|
518
|
+
|
|
519
|
+
**Solution:** Call `play()` only inside a user event handler.
|
|
520
|
+
|
|
521
|
+
```javascript
|
|
522
|
+
// ✅ Correct
|
|
523
|
+
document.addEventListener('click', () => player.play());
|
|
524
|
+
|
|
525
|
+
// ❌ Wrong
|
|
526
|
+
window.addEventListener('load', () => player.play());
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
### Audio Resumes Fail After Tab Return
|
|
532
|
+
|
|
533
|
+
**Problem:** Set `pauseOnHidden: true`, but audio doesn't resume when tab returns.
|
|
534
|
+
|
|
535
|
+
**Cause:** Browser autoplay policy requires a gesture after the tab was inactive.
|
|
536
|
+
|
|
537
|
+
**Solution:** This is browser behavior. The API will try to resume automatically, but stricter policies may block it silently. As a workaround, prompt the user to click a play button:
|
|
538
|
+
|
|
539
|
+
```javascript
|
|
540
|
+
const player = new OpenAudio('audio.mp3', {
|
|
541
|
+
pauseOnHidden: true,
|
|
542
|
+
onVisible: () => {
|
|
543
|
+
// Try to resume (may be blocked by policy)
|
|
544
|
+
// Provide UI button as fallback
|
|
545
|
+
showPlayButton();
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
### Can't Replay After Clip Ends
|
|
553
|
+
|
|
554
|
+
**Problem:** You want to play the same clip twice in a row.
|
|
555
|
+
|
|
556
|
+
**Solution:** Calling `play()` after `onEnd()` fires will automatically rewind and replay.
|
|
557
|
+
|
|
558
|
+
```javascript
|
|
559
|
+
const player = new OpenAudio('audio/chime.mp3', {
|
|
560
|
+
onEnd: () => {
|
|
561
|
+
console.log('Clip finished');
|
|
562
|
+
// Can call play() immediately to loop
|
|
563
|
+
player.play();
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Browser Compatibility
|
|
571
|
+
|
|
572
|
+
| Browser | Support | Notes |
|
|
573
|
+
|---------|---------|-------|
|
|
574
|
+
| Chrome 70+ | ✅ Full | Autoplay policy: gesture required first time; background tab throttling applies to timers, not audio |
|
|
575
|
+
| Firefox 65+ | ✅ Full | Same autoplay policy as Chrome; Page Visibility API fully supported |
|
|
576
|
+
| Safari 12+ | ✅ Full | iOS Safari: gesture required; Desktop: autoplay allowed |
|
|
577
|
+
| Edge 79+ | ✅ Full | Same as Chrome |
|
|
578
|
+
| iOS Safari 12+ | ✅ Full | Gesture required; Page Visibility API supported |
|
|
579
|
+
| Chrome Android | ✅ Full | Gesture required; touchstart counts as gesture |
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## Performance
|
|
584
|
+
|
|
585
|
+
- **File Size:** ~5 KB (minified)
|
|
586
|
+
- **Gzipped:** ~2 KB
|
|
587
|
+
- **Runtime Memory:** < 150 KB per instance
|
|
588
|
+
- **CPU:** Negligible — just HTML5 Audio element + visibility listening
|
|
589
|
+
|
|
590
|
+
Creating multiple instances is fine:
|
|
591
|
+
|
|
592
|
+
```javascript
|
|
593
|
+
const sounds = {
|
|
594
|
+
click: new OpenAudio('click.mp3'),
|
|
595
|
+
hover: new OpenAudio('hover.mp3'),
|
|
596
|
+
error: new OpenAudio('error.mp3'),
|
|
597
|
+
success: new OpenAudio('success.mp3')
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// All four instances < 1 MB total memory
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## Changelog
|
|
606
|
+
|
|
607
|
+
### v1.1.0 (March 2025)
|
|
608
|
+
- **NEW:** Background tab detection via Page Visibility API
|
|
609
|
+
- **NEW:** `pauseOnHidden` option — pause on tab hide, resume on show
|
|
610
|
+
- **NEW:** `onHidden` callback — fire when tab becomes hidden
|
|
611
|
+
- **NEW:** `onVisible` callback — fire when tab becomes visible
|
|
612
|
+
- **NEW:** `#boundVisibility` — stored bound reference for clean listener removal
|
|
613
|
+
- **IMPROVED:** `destroy()` now removes the visibilitychange listener
|
|
614
|
+
- **RENAMED:** Class `SingleAudio` → `OpenAudio` (matches filename)
|
|
615
|
+
- **IMPROVED:** Better documentation on background tab behavior
|
|
616
|
+
|
|
617
|
+
### v1.0.0 (January 2025)
|
|
618
|
+
- Initial release
|
|
619
|
+
- Silent MP3 unlock for autoplay policy
|
|
620
|
+
- `onPlay`, `onEnd` callbacks
|
|
621
|
+
- `destroy()` for SPA cleanup
|
|
622
|
+
- `canPlay()` format checking
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## License
|
|
627
|
+
|
|
628
|
+
GNU General Public License v3.0 or later. See [LICENSE](../LICENSE).
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
## See Also
|
|
633
|
+
|
|
634
|
+
- [OpenAudio_r.js API](./OPENAUDIO_R.md) — Randomized scheduler
|
|
635
|
+
- [OpenAudio_s.js API](./OPENAUDIO_S.md) — Sequential playlist
|
|
636
|
+
- [Feature Comparison](./COMPARISON.md) — Decide which library fits
|
|
637
|
+
- [Main README](../README.md) — OpenAudio suite overview
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
*Last updated: March 2025*
|