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.
@@ -0,0 +1,760 @@
1
+ # OpenAudio_s.js API Reference
2
+
3
+ **Version:** 1.0.0
4
+ **Class:** `SequentialAudio`
5
+ **Use Case:** Sequential/playlist playback with manual or auto-advance
6
+
7
+ ---
8
+
9
+ ## Quick Start
10
+
11
+ ```javascript
12
+ // Create a player
13
+ const player = new SequentialAudio([
14
+ { src: 'intro.mp3', label: 'Introduction' },
15
+ { src: 'chapter1.mp3', label: 'Chapter 1' },
16
+ { src: 'chapter2.mp3', label: 'Chapter 2' }
17
+ ], {
18
+ autoAdvance: false, // Require manual click to advance
19
+ onPlay: (clip) => console.log(`Playing: ${clip.label}`),
20
+ onComplete: () => console.log('All clips finished!')
21
+ });
22
+
23
+ // Start the sequence
24
+ document.getElementById('play-btn').addEventListener('click', () => {
25
+ player.play();
26
+ });
27
+
28
+ // Advance to next clip
29
+ document.getElementById('next-btn').addEventListener('click', () => {
30
+ player.next();
31
+ });
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Constructor
37
+
38
+ ```javascript
39
+ new SequentialAudio(clips, options)
40
+ ```
41
+
42
+ ### Parameters
43
+
44
+ #### `clips` (array, required)
45
+ Array of clip objects in playback order.
46
+
47
+ ```javascript
48
+ [
49
+ { src: 'clip1.mp3', label: 'First Clip' },
50
+ { src: 'clip2.mp3', label: 'Second Clip' },
51
+ { src: 'clip3.mp3', label: 'Third Clip' }
52
+ ]
53
+ ```
54
+
55
+ Each clip object:
56
+ - **`src`** (string, required) — Path or data URI to audio file
57
+ - **`label`** (string) — Display name for console/callbacks
58
+
59
+ #### `options` (object, optional)
60
+
61
+ ```javascript
62
+ {
63
+ autoAdvance: false, // Boolean: auto-play next clip after current ends
64
+ loop: false, // Boolean: loop sequence when complete
65
+ onPlay: (clip) => {}, // Function: called when clip starts
66
+ onEnd: (clip) => {}, // Function: called when clip ends
67
+ onComplete: () => {} // Function: called when sequence finishes
68
+ }
69
+ ```
70
+
71
+ **All options are optional.**
72
+
73
+ ### Constructor Examples
74
+
75
+ ```javascript
76
+ // Minimal
77
+ const player = new SequentialAudio([
78
+ { src: 'clip1.mp3' },
79
+ { src: 'clip2.mp3' }
80
+ ]);
81
+
82
+ // With manual advance (require click between clips)
83
+ const player = new SequentialAudio(clips, {
84
+ autoAdvance: false,
85
+ onPlay: (clip) => updateUI(clip.label)
86
+ });
87
+
88
+ // With auto-advance (play next automatically)
89
+ const player = new SequentialAudio(clips, {
90
+ autoAdvance: true,
91
+ onComplete: () => showFinishScreen()
92
+ });
93
+
94
+ // With looping
95
+ const player = new SequentialAudio(clips, {
96
+ loop: true, // Restart sequence when complete
97
+ onComplete: () => console.log('Looping...')
98
+ });
99
+ ```
100
+
101
+ ### Constructor Errors
102
+
103
+ ```javascript
104
+ // Throws TypeError if clips is missing, empty, or invalid
105
+ new SequentialAudio(); // ❌ TypeError
106
+ new SequentialAudio([]); // ❌ TypeError
107
+ new SequentialAudio([{ src: '' }]); // ❌ Empty src
108
+ new SequentialAudio([{ label: 'No src' }]); // ❌ Missing src
109
+
110
+ // Valid
111
+ new SequentialAudio([
112
+ { src: 'clip.mp3', label: 'Clip' }
113
+ ]); // ✅
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Public Methods
119
+
120
+ ### `play()`
121
+
122
+ Start playback from the first clip (or current position if paused).
123
+
124
+ **Must be called inside a user gesture on first use.**
125
+
126
+ ```javascript
127
+ // First call (inside gesture)
128
+ document.addEventListener('click', () => {
129
+ player.play(); // ✅ Starts from clip 0
130
+ });
131
+
132
+ // Subsequent calls
133
+ player.play(); // Safe to call anytime
134
+ ```
135
+
136
+ **Behavior:**
137
+ - If already playing: ignored (safe)
138
+ - If paused: resumes from current position
139
+ - If finished: starts from beginning
140
+
141
+ ---
142
+
143
+ ### `next()`
144
+
145
+ Advance to the next clip and play it.
146
+
147
+ ```javascript
148
+ player.next(); // Play next clip
149
+ ```
150
+
151
+ **Behavior:**
152
+ - Increments clip index by 1
153
+ - Plays the new clip
154
+ - At end of sequence:
155
+ - If `loop: true` → Wraps to beginning and plays clip 0
156
+ - If `loop: false` → Stops and fires `onComplete`
157
+
158
+ ```javascript
159
+ const player = new SequentialAudio([clip1, clip2, clip3], {
160
+ loop: false
161
+ });
162
+
163
+ player.goto(0); // Clip 0
164
+ player.next(); // Clip 1
165
+ player.next(); // Clip 2
166
+ player.next(); // Stops, fires onComplete
167
+ ```
168
+
169
+ ---
170
+
171
+ ### `goto(index)`
172
+
173
+ Jump to a specific clip by index and play it immediately.
174
+
175
+ ```javascript
176
+ player.goto(0); // Jump to first clip
177
+ player.goto(2); // Jump to third clip
178
+ ```
179
+
180
+ **Parameters:**
181
+ - **`index`** (number) — 0-based clip index
182
+
183
+ **Behavior:**
184
+ - Jumps to the specified clip and plays it
185
+ - Validates index (warns if out of range, no-op)
186
+
187
+ ```javascript
188
+ const player = new SequentialAudio([c1, c2, c3, c4]);
189
+
190
+ player.goto(1); // ✅ Play clip 2
191
+ player.goto(10); // ❌ Warning: out of range
192
+ ```
193
+
194
+ ---
195
+
196
+ ### `gotoLabel(label)`
197
+
198
+ Jump to a clip by its label and play it.
199
+
200
+ ```javascript
201
+ player.gotoLabel('Chapter 5');
202
+ ```
203
+
204
+ **Parameters:**
205
+ - **`label`** (string) — Exact label match
206
+
207
+ **Behavior:**
208
+ - Searches clips for matching label
209
+ - Jumps to first match
210
+ - Warns if label not found
211
+
212
+ ```javascript
213
+ const player = new SequentialAudio([
214
+ { src: 'intro.mp3', label: 'Introduction' },
215
+ { src: 'ch1.mp3', label: 'Chapter 1' },
216
+ { src: 'ch2.mp3', label: 'Chapter 2' }
217
+ ]);
218
+
219
+ player.gotoLabel('Chapter 1'); // ✅ Jumps to index 1
220
+ player.gotoLabel('Chapter 99'); // ❌ Warning: not found
221
+ ```
222
+
223
+ ---
224
+
225
+ ### `pause()`
226
+
227
+ Pause playback without changing the clip or position.
228
+
229
+ ```javascript
230
+ player.pause();
231
+ ```
232
+
233
+ **Behavior:**
234
+ - Pauses audio at current position
235
+ - Sets `isPlaying = false`
236
+ - Call `resume()` to continue from same position
237
+
238
+ ```javascript
239
+ player.play(); // Playing clip 1, 2:30 elapsed
240
+ player.pause(); // Paused at 2:30
241
+ player.resume(); // Resume from 2:30
242
+ ```
243
+
244
+ ---
245
+
246
+ ### `resume()`
247
+
248
+ Resume playback from where it was paused.
249
+
250
+ ```javascript
251
+ player.resume();
252
+ ```
253
+
254
+ **Behavior:**
255
+ - If paused: resumes audio
256
+ - If already playing: no-op (safe)
257
+
258
+ ```javascript
259
+ player.play(); // Playing
260
+ player.pause(); // Paused
261
+ player.resume(); // Resume from pause point
262
+ player.resume(); // No-op (already playing)
263
+ ```
264
+
265
+ ---
266
+
267
+ ### `stop()`
268
+
269
+ Stop playback and rewind to the beginning of the current clip.
270
+
271
+ ```javascript
272
+ player.stop();
273
+ ```
274
+
275
+ **Behavior:**
276
+ - Pauses audio
277
+ - Rewinds to 0:00 of current clip
278
+ - Sets `isPlaying = false`
279
+ - Calling `play()` after will restart from beginning
280
+
281
+ ```javascript
282
+ player.play(); // Playing clip 1, 2:30 elapsed
283
+ player.stop(); // Stopped, rewound to 0:00
284
+ player.play(); // Restart from 0:00
285
+ ```
286
+
287
+ ---
288
+
289
+ ### `reset()`
290
+
291
+ Reset the sequence to the first clip without playing.
292
+
293
+ ```javascript
294
+ player.reset();
295
+ ```
296
+
297
+ **Behavior:**
298
+ - Stops playback
299
+ - Resets to clip index 0
300
+ - Resets `isStarted` flag
301
+ - Calling `play()` will start from beginning
302
+
303
+ ```javascript
304
+ player.goto(3); // At clip 4
305
+ player.reset(); // Back to clip 0, stopped
306
+ player.play(); // Play clip 0
307
+ ```
308
+
309
+ ---
310
+
311
+ ### `getCurrentClip()`
312
+
313
+ Get the current clip object.
314
+
315
+ ```javascript
316
+ const clip = player.getCurrentClip();
317
+ console.log(clip.label, clip.src);
318
+ ```
319
+
320
+ **Returns:** Current clip object `{ src, label, ... }`
321
+
322
+ ---
323
+
324
+ ### `getCurrentIndex()`
325
+
326
+ Get the current clip index (0-based).
327
+
328
+ ```javascript
329
+ const index = player.getCurrentIndex();
330
+ console.log(`Playing clip ${index + 1}`);
331
+ ```
332
+
333
+ **Returns:** Number (0 to clipCount - 1)
334
+
335
+ ---
336
+
337
+ ### `getClipCount()`
338
+
339
+ Get total number of clips in the sequence.
340
+
341
+ ```javascript
342
+ const total = player.getClipCount();
343
+ console.log(`${total} clips in sequence`);
344
+ ```
345
+
346
+ **Returns:** Number
347
+
348
+ ---
349
+
350
+ ### `getClips()`
351
+
352
+ Get a copy of all clips.
353
+
354
+ ```javascript
355
+ const allClips = player.getClips();
356
+ console.log(`Sequence has ${allClips.length} clips`);
357
+ ```
358
+
359
+ **Returns:** Array of clip objects (copy, not reference)
360
+
361
+ ---
362
+
363
+ ### `destroy()`
364
+
365
+ Clean up and remove all references. Call on SPA unmount.
366
+
367
+ ```javascript
368
+ player.destroy();
369
+ ```
370
+
371
+ **Behavior:**
372
+ - Stops playback
373
+ - Clears audio element
374
+ - Nullifies internal references
375
+ - After this, don't call any other methods
376
+
377
+ ```javascript
378
+ // React example
379
+ useEffect(() => {
380
+ const player = new SequentialAudio(clips);
381
+ return () => player.destroy();
382
+ }, []);
383
+ ```
384
+
385
+ ---
386
+
387
+ ### `canPlay(type)` — Static Method
388
+
389
+ Check if browser supports an audio format.
390
+
391
+ ```javascript
392
+ SequentialAudio.canPlay('audio/mpeg') → boolean
393
+ ```
394
+
395
+ **Parameters:**
396
+ - **`type`** (string) — MIME type to check
397
+ - `'audio/mpeg'` — MP3
398
+ - `'audio/ogg'` — OGG Vorbis
399
+ - `'audio/wav'` — WAV
400
+ - `'audio/webm'` — WebM
401
+
402
+ **Returns:** `true` if supported, `false` otherwise
403
+
404
+ ```javascript
405
+ if (SequentialAudio.canPlay('audio/ogg')) {
406
+ // Use OGG files
407
+ } else {
408
+ // Fall back to MP3
409
+ }
410
+ ```
411
+
412
+ ---
413
+
414
+ ## Public Properties
415
+
416
+ ### `isPlaying`
417
+
418
+ **Type:** `boolean` (read-only)
419
+
420
+ `true` if a clip is actively playing, `false` if paused or stopped.
421
+
422
+ ```javascript
423
+ player.play();
424
+
425
+ if (player.isPlaying) {
426
+ console.log('Clip is playing');
427
+ } else {
428
+ console.log('Clip is paused or stopped');
429
+ }
430
+ ```
431
+
432
+ ---
433
+
434
+ ### `isStarted`
435
+
436
+ **Type:** `boolean` (read-only)
437
+
438
+ `true` after first `play()` call (sequence has been unlocked and started).
439
+
440
+ ```javascript
441
+ const player = new SequentialAudio(clips);
442
+
443
+ console.log(player.isStarted); // false
444
+
445
+ player.play();
446
+
447
+ console.log(player.isStarted); // true
448
+ ```
449
+
450
+ ---
451
+
452
+ ## Callbacks
453
+
454
+ ### `onPlay(clip)`
455
+
456
+ Called when a clip **starts playing**.
457
+
458
+ ```javascript
459
+ const player = new SequentialAudio(clips, {
460
+ onPlay: (clip) => {
461
+ console.log(`Now playing: ${clip.label}`);
462
+ updateProgressBar(clip.label);
463
+ }
464
+ });
465
+ ```
466
+
467
+ **Parameter:**
468
+ - **`clip`** — The clip object being played `{ src, label }`
469
+
470
+ **Thrown errors are caught:** If your callback throws, the error is logged but playback continues.
471
+
472
+ ```javascript
473
+ const player = new SequentialAudio(clips, {
474
+ onPlay: (clip) => {
475
+ throw new Error('Oops!');
476
+ // Error logged, playback unaffected
477
+ }
478
+ });
479
+ ```
480
+
481
+ ---
482
+
483
+ ### `onEnd(clip)`
484
+
485
+ Called when a clip **finishes playing** (reaches natural end).
486
+
487
+ Does NOT fire if you call `stop()` or `pause()` before the clip ends.
488
+
489
+ ```javascript
490
+ const player = new SequentialAudio(clips, {
491
+ onEnd: (clip) => {
492
+ console.log(`Finished: ${clip.label}`);
493
+ // Auto-advance is handled separately
494
+ }
495
+ });
496
+ ```
497
+
498
+ **Parameter:**
499
+ - **`clip`** — The clip object that ended
500
+
501
+ **After `onEnd`:**
502
+ - If `autoAdvance: true` → Next clip starts automatically (after 500ms delay)
503
+ - If `autoAdvance: false` → Sequence pauses, waiting for `next()` call
504
+
505
+ ---
506
+
507
+ ### `onComplete()`
508
+
509
+ Called when the entire sequence finishes (no more clips).
510
+
511
+ Only fires if `autoAdvance: true` OR user explicitly calls `next()` on the last clip.
512
+
513
+ ```javascript
514
+ const player = new SequentialAudio(clips, {
515
+ onComplete: () => {
516
+ console.log('Sequence complete!');
517
+ showCongratulationsScreen();
518
+ }
519
+ });
520
+ ```
521
+
522
+ **After `onComplete`:**
523
+ - If `loop: true` → Sequence wraps to beginning, ready for replay
524
+ - If `loop: false` → Sequence stops
525
+
526
+ ---
527
+
528
+ ## Usage Patterns
529
+
530
+ ### Pattern 1: Narrated Story (Manual Advance)
531
+
532
+ ```javascript
533
+ const story = new SequentialAudio([
534
+ { src: 'chapter1.mp3', label: 'Chapter 1' },
535
+ { src: 'chapter2.mp3', label: 'Chapter 2' },
536
+ { src: 'chapter3.mp3', label: 'Chapter 3' }
537
+ ], {
538
+ autoAdvance: false, // User controls pacing
539
+ onPlay: (clip) => updateUI(`Reading: ${clip.label}`),
540
+ onComplete: () => showTheEnd()
541
+ });
542
+
543
+ document.addEventListener('click', () => story.play(), { once: true });
544
+ document.getElementById('next-btn').addEventListener('click', () => story.next());
545
+ document.getElementById('prev-btn').addEventListener('click', () => {
546
+ story.goto(Math.max(0, story.getCurrentIndex() - 1));
547
+ });
548
+ ```
549
+
550
+ ---
551
+
552
+ ### Pattern 2: Tutorial with Auto-Advance
553
+
554
+ ```javascript
555
+ const tutorial = new SequentialAudio([
556
+ { src: 'intro.mp3', label: 'Introduction' },
557
+ { src: 'step1.mp3', label: 'Step 1' },
558
+ { src: 'step2.mp3', label: 'Step 2' },
559
+ { src: 'conclusion.mp3', label: 'Conclusion' }
560
+ ], {
561
+ autoAdvance: true, // Auto-play next step
562
+ onPlay: (clip) => highlightStep(clip.label),
563
+ onComplete: () => showCompletionCertificate()
564
+ });
565
+
566
+ document.getElementById('start-tutorial').addEventListener('click', () => {
567
+ tutorial.play();
568
+ });
569
+ ```
570
+
571
+ ---
572
+
573
+ ### Pattern 3: Interactive Quiz with Narration
574
+
575
+ ```javascript
576
+ const quiz = new SequentialAudio([
577
+ { src: 'question1.mp3', label: 'Question 1' },
578
+ { src: 'question2.mp3', label: 'Question 2' },
579
+ { src: 'question3.mp3', label: 'Question 3' },
580
+ { src: 'results.mp3', label: 'Results' }
581
+ ], {
582
+ autoAdvance: false,
583
+ onPlay: (clip) => {
584
+ // Show question UI
585
+ showQuestion(clip.label);
586
+ },
587
+ onComplete: () => {
588
+ // Show final score
589
+ showResults();
590
+ }
591
+ });
592
+
593
+ // User answers question, then advances
594
+ function submitAnswer(answer) {
595
+ recordAnswer(answer);
596
+ quiz.next(); // Move to next question
597
+ }
598
+ ```
599
+
600
+ ---
601
+
602
+ ### Pattern 4: Looping Background Narration
603
+
604
+ ```javascript
605
+ const loopingNarration = new SequentialAudio([
606
+ { src: 'intro.mp3', label: 'Intro' },
607
+ { src: 'main.mp3', label: 'Main Message' },
608
+ { src: 'outro.mp3', label: 'Outro' }
609
+ ], {
610
+ autoAdvance: true,
611
+ loop: true, // Restart after outro
612
+ onPlay: (clip) => console.log(`[${clip.label}]`)
613
+ });
614
+
615
+ document.addEventListener('click', () => loopingNarration.play(), { once: true });
616
+ ```
617
+
618
+ ---
619
+
620
+ ### Pattern 5: Guided Tour with Skip/Rewind
621
+
622
+ ```javascript
623
+ const tour = new SequentialAudio([
624
+ { src: 'welcome.mp3', label: 'Welcome' },
625
+ { src: 'feature1.mp3', label: 'Feature 1: Dashboard' },
626
+ { src: 'feature2.mp3', label: 'Feature 2: Settings' },
627
+ { src: 'feature3.mp3', label: 'Feature 3: Profile' },
628
+ { src: 'thanks.mp3', label: 'Thanks' }
629
+ ], {
630
+ onPlay: (clip) => highlightFeature(clip.label),
631
+ onComplete: () => completeTour()
632
+ });
633
+
634
+ // Play from start
635
+ document.getElementById('start-tour').addEventListener('click', () => tour.play());
636
+
637
+ // Jump to specific section
638
+ document.getElementById('skip-to-settings').addEventListener('click', () => {
639
+ tour.gotoLabel('Feature 2: Settings');
640
+ });
641
+
642
+ // Rewind one step
643
+ document.getElementById('previous').addEventListener('click', () => {
644
+ const idx = Math.max(0, tour.getCurrentIndex() - 1);
645
+ tour.goto(idx);
646
+ });
647
+
648
+ // Forward one step
649
+ document.getElementById('next').addEventListener('click', () => {
650
+ tour.next();
651
+ });
652
+ ```
653
+
654
+ ---
655
+
656
+ ## Troubleshooting
657
+
658
+ ### Audio Won't Play on First Click
659
+
660
+ **Problem:** Calling `play()` outside a user gesture.
661
+
662
+ **Solution:**
663
+ ```javascript
664
+ // ✅ Correct
665
+ document.addEventListener('click', () => player.play());
666
+
667
+ // ❌ Wrong
668
+ setTimeout(() => player.play(), 1000);
669
+ ```
670
+
671
+ ---
672
+
673
+ ### Can't Advance to Next Clip
674
+
675
+ **Problem:** Calling `next()` but nothing happens.
676
+
677
+ **Cause:** Already at last clip and `loop: false`.
678
+
679
+ **Solution:**
680
+ ```javascript
681
+ if (player.getCurrentIndex() < player.getClipCount() - 1) {
682
+ player.next();
683
+ } else if (loop) {
684
+ // Already handled by library
685
+ player.next();
686
+ }
687
+ ```
688
+
689
+ ---
690
+
691
+ ### Progress Bar Shows Wrong Index
692
+
693
+ **Problem:** UI shows wrong clip number.
694
+
695
+ **Solution:** `getCurrentIndex()` is 0-based, so display `index + 1`:
696
+
697
+ ```javascript
698
+ const index = player.getCurrentIndex();
699
+ console.log(`Clip ${index + 1} of ${player.getClipCount()}`);
700
+ ```
701
+
702
+ ---
703
+
704
+ ### onComplete Fires Too Early
705
+
706
+ **Problem:** Callback fires before last clip actually ends.
707
+
708
+ **Cause:** `autoAdvance: true` fires `next()` → triggers `onComplete` before last clip fully plays.
709
+
710
+ **Solution:** Use manual advance or check if you really want `onComplete` at this time:
711
+
712
+ ```javascript
713
+ const player = new SequentialAudio(clips, {
714
+ autoAdvance: false, // User controls advancement
715
+ onComplete: () => {
716
+ // Now fires only when user explicitly ends sequence
717
+ }
718
+ });
719
+ ```
720
+
721
+ ---
722
+
723
+ ## Browser Compatibility
724
+
725
+ | Browser | Support | Notes |
726
+ |---------|---------|-------|
727
+ | Chrome 70+ | ✅ Full | Autoplay policy: gesture required first time |
728
+ | Firefox 65+ | ✅ Full | Same as Chrome |
729
+ | Safari 12+ | ✅ Full | iOS: gesture required; Desktop: autoplay OK |
730
+ | Edge 79+ | ✅ Full | Same as Chrome |
731
+ | iOS Safari 12+ | ✅ Full | Gesture required; extensively tested |
732
+ | Chrome Android | ✅ Full | Gesture required |
733
+
734
+ ---
735
+
736
+ ## Performance
737
+
738
+ - **File Size:** ~5 KB (minified)
739
+ - **Gzipped:** ~2 KB
740
+ - **Runtime Memory:** < 150 KB per player
741
+ - **CPU:** Negligible — HTML5 Audio element management only
742
+
743
+ ---
744
+
745
+ ## License
746
+
747
+ GNU General Public License v3.0 or later. See [LICENSE](../LICENSE).
748
+
749
+ ---
750
+
751
+ ## See Also
752
+
753
+ - [OpenAudio_r.js API](./OPENAUDIO_R.md) — Randomized scheduler
754
+ - [OpenAudio.js API](./OPENAUDIO.md) — Single-clip player
755
+ - [Feature Comparison](./COMPARISON.md) — Decide which library fits
756
+ - [Main README](../README.md) — OpenAudio suite overview
757
+
758
+ ---
759
+
760
+ *Last updated: March 2025*