@arraypress/waveform-bar 1.2.0 → 1.3.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/README.md +173 -334
- package/dist/waveform-bar-icons.css +14 -5
- package/dist/waveform-bar.esm.js +101 -14
- package/dist/waveform-bar.js +101 -14
- package/dist/waveform-bar.min.js +8 -8
- package/package.json +1 -1
- package/src/css/waveform-bar-icons.css +14 -5
- package/src/js/core.js +132 -17
package/src/js/core.js
CHANGED
|
@@ -43,7 +43,6 @@ const DEFAULTS = {
|
|
|
43
43
|
waveformColor: null,
|
|
44
44
|
progressColor: null,
|
|
45
45
|
markerColor: 'rgba(255, 255, 255, 0.25)',
|
|
46
|
-
configPath: null, // Directory for auto-resolved config JSON files (e.g. 'waveforms/')
|
|
47
46
|
volume: 1,
|
|
48
47
|
storageKey: 'waveform-bar',
|
|
49
48
|
actions: null,
|
|
@@ -312,6 +311,7 @@ export class WaveformBar {
|
|
|
312
311
|
this.isPlaying = true;
|
|
313
312
|
this._updatePlayButton();
|
|
314
313
|
this._syncPageState();
|
|
314
|
+
this._pumpExternalPlayState(true);
|
|
315
315
|
const track = this.getCurrentTrack();
|
|
316
316
|
this._emit('play', {track});
|
|
317
317
|
if (this.config.onPlay) this.config.onPlay(track);
|
|
@@ -320,6 +320,7 @@ export class WaveformBar {
|
|
|
320
320
|
this.isPlaying = false;
|
|
321
321
|
this._updatePlayButton();
|
|
322
322
|
this._syncPageState();
|
|
323
|
+
this._pumpExternalPlayState(false);
|
|
323
324
|
this._saveState();
|
|
324
325
|
const track = this.getCurrentTrack();
|
|
325
326
|
this._emit('pause', {track});
|
|
@@ -329,6 +330,7 @@ export class WaveformBar {
|
|
|
329
330
|
this.isPlaying = false;
|
|
330
331
|
this._updatePlayButton();
|
|
331
332
|
this._syncPageState();
|
|
333
|
+
this._pumpExternalPlayState(false);
|
|
332
334
|
|
|
333
335
|
// Reset time display
|
|
334
336
|
if (this.timeCurrentEl) this.timeCurrentEl.textContent = '0:00';
|
|
@@ -359,6 +361,11 @@ export class WaveformBar {
|
|
|
359
361
|
if (this.timeCurrentEl) this.timeCurrentEl.textContent = formatTime(currentTime);
|
|
360
362
|
if (this.timeTotalEl) this.timeTotalEl.textContent = formatTime(duration);
|
|
361
363
|
|
|
364
|
+
// Mirror progress into any external-mode WaveformPlayer
|
|
365
|
+
// instances tracking this URL — their canvases scrub in
|
|
366
|
+
// sync with the bar's audio.
|
|
367
|
+
this._pumpExternalProgress(currentTime, duration);
|
|
368
|
+
|
|
362
369
|
// Save state periodically during playback
|
|
363
370
|
if (!this._lastSaveTime || currentTime - this._lastSaveTime > 2) {
|
|
364
371
|
this._lastSaveTime = currentTime;
|
|
@@ -405,6 +412,123 @@ export class WaveformBar {
|
|
|
405
412
|
if (track) this.addToQueue(track);
|
|
406
413
|
});
|
|
407
414
|
});
|
|
415
|
+
|
|
416
|
+
// External-mode WaveformPlayer instances on the page act as
|
|
417
|
+
// visualization surfaces controlled by this bar — see the
|
|
418
|
+
// `audioMode: 'external'` option in @arraypress/waveform-player.
|
|
419
|
+
// Each instance dispatches `waveformplayer:request-play`
|
|
420
|
+
// (cancelable) when its play button is clicked; we route that
|
|
421
|
+
// into this bar so the audio always lives in one place.
|
|
422
|
+
this._attachExternalPlayers();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Discover external-mode WaveformPlayer instances and listen for
|
|
427
|
+
* their request-play / request-pause / request-seek events. Also
|
|
428
|
+
* builds a url → Set<WaveformPlayer> map used by _syncPageState()
|
|
429
|
+
* and the onTimeUpdate callback to push state into the matching
|
|
430
|
+
* inline visualizations.
|
|
431
|
+
*
|
|
432
|
+
* Idempotent — safe to call repeatedly. Late-mounted players are
|
|
433
|
+
* picked up by the MutationObserver in _observeDOM().
|
|
434
|
+
*
|
|
435
|
+
* @private
|
|
436
|
+
*/
|
|
437
|
+
_attachExternalPlayers() {
|
|
438
|
+
// Document-level listeners only bind once.
|
|
439
|
+
if (!this._externalListenersBound) {
|
|
440
|
+
this._externalListenersBound = true;
|
|
441
|
+
|
|
442
|
+
document.addEventListener('waveformplayer:request-play', (e) => {
|
|
443
|
+
const t = e.detail;
|
|
444
|
+
if (!t || !t.url) return;
|
|
445
|
+
e.preventDefault();
|
|
446
|
+
this.play(t);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
document.addEventListener('waveformplayer:request-pause', (e) => {
|
|
450
|
+
const t = e.detail;
|
|
451
|
+
if (!t || !t.url) return;
|
|
452
|
+
// Only honour pause when this is the currently-playing
|
|
453
|
+
// track — pause requests from other players are noise.
|
|
454
|
+
const current = this.getCurrentTrack();
|
|
455
|
+
if (current && current.url === t.url) {
|
|
456
|
+
e.preventDefault();
|
|
457
|
+
if (this.isPlaying) this.togglePlay();
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
document.addEventListener('waveformplayer:request-seek', (e) => {
|
|
462
|
+
const t = e.detail;
|
|
463
|
+
if (!t || !t.url || typeof t.percent !== 'number') return;
|
|
464
|
+
const current = this.getCurrentTrack();
|
|
465
|
+
if (current && current.url === t.url && this.player && this.player.audio) {
|
|
466
|
+
e.preventDefault();
|
|
467
|
+
this.player.seekToPercent(t.percent);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Rebuild the URL → players map from scratch each pass — cheap
|
|
473
|
+
// (single querySelectorAll + Map insert) and avoids stale entries
|
|
474
|
+
// for players that have been torn down. Late-mounted players
|
|
475
|
+
// come in via the MutationObserver tick.
|
|
476
|
+
this._externalPlayers = new Map();
|
|
477
|
+
const WP = window.WaveformPlayer;
|
|
478
|
+
if (!WP || !WP.instances) return;
|
|
479
|
+
document.querySelectorAll('[data-waveform-player][data-audio-mode="external"]').forEach((el) => {
|
|
480
|
+
const inst = WP.instances.get(el.id);
|
|
481
|
+
if (!inst || !inst.options || !inst.options.url) return;
|
|
482
|
+
const url = inst.options.url;
|
|
483
|
+
if (!this._externalPlayers.has(url)) this._externalPlayers.set(url, new Set());
|
|
484
|
+
this._externalPlayers.get(url).add(inst);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Push playing-state into every external-mode player whose URL
|
|
490
|
+
* matches the currently playing track. Other URLs get set to
|
|
491
|
+
* false (paused) — covers the case where the bar switched tracks
|
|
492
|
+
* and the previously-current external player should stop showing
|
|
493
|
+
* its play indicator.
|
|
494
|
+
*
|
|
495
|
+
* @private
|
|
496
|
+
* @param {boolean} playing
|
|
497
|
+
*/
|
|
498
|
+
_pumpExternalPlayState(playing) {
|
|
499
|
+
if (!this._externalPlayers || this._externalPlayers.size === 0) return;
|
|
500
|
+
const current = this.getCurrentTrack();
|
|
501
|
+
const currentUrl = current ? current.url : null;
|
|
502
|
+
this._externalPlayers.forEach((set, url) => {
|
|
503
|
+
const isCurrent = url === currentUrl;
|
|
504
|
+
set.forEach((player) => {
|
|
505
|
+
if (typeof player.setPlayingState === 'function') {
|
|
506
|
+
player.setPlayingState(isCurrent && playing);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Push progress (currentTime + duration) into the external-mode
|
|
514
|
+
* player(s) tracking the current URL. Called on every timeupdate
|
|
515
|
+
* tick of the internal player.
|
|
516
|
+
*
|
|
517
|
+
* @private
|
|
518
|
+
* @param {number} currentTime
|
|
519
|
+
* @param {number} duration
|
|
520
|
+
*/
|
|
521
|
+
_pumpExternalProgress(currentTime, duration) {
|
|
522
|
+
if (!this._externalPlayers || this._externalPlayers.size === 0) return;
|
|
523
|
+
const current = this.getCurrentTrack();
|
|
524
|
+
if (!current) return;
|
|
525
|
+
const set = this._externalPlayers.get(current.url);
|
|
526
|
+
if (!set) return;
|
|
527
|
+
set.forEach((player) => {
|
|
528
|
+
if (typeof player.setProgress === 'function') {
|
|
529
|
+
player.setProgress(currentTime, duration);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
408
532
|
}
|
|
409
533
|
|
|
410
534
|
_observeDOM() {
|
|
@@ -827,6 +951,12 @@ export class WaveformBar {
|
|
|
827
951
|
const track = this.getCurrentTrack();
|
|
828
952
|
if (!track || !this.player) return;
|
|
829
953
|
|
|
954
|
+
// Reset any previously-current external player so its UI flips
|
|
955
|
+
// back to "paused" while the new track loads. The new track's
|
|
956
|
+
// onPlay callback will set the matching external to playing
|
|
957
|
+
// once playback actually starts.
|
|
958
|
+
this._pumpExternalPlayState(false);
|
|
959
|
+
|
|
830
960
|
this.show();
|
|
831
961
|
this._updateTrackDisplay(track);
|
|
832
962
|
this._updateFavoriteUI();
|
|
@@ -838,14 +968,6 @@ export class WaveformBar {
|
|
|
838
968
|
loadOpts.waveform = track.waveform;
|
|
839
969
|
}
|
|
840
970
|
|
|
841
|
-
// Auto-resolve config JSON from configPath
|
|
842
|
-
if (this.config.configPath && track.url) {
|
|
843
|
-
const audioFile = track.url.split('/').pop().split('?')[0];
|
|
844
|
-
const jsonFile = audioFile.replace(/\.[^.]+$/, '.json');
|
|
845
|
-
const path = this.config.configPath.replace(/\/?$/, '/');
|
|
846
|
-
loadOpts.config = path + jsonFile;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
971
|
// Always pass markers — empty array clears previous track's markers
|
|
850
972
|
if (track.markers && track.markers.length) {
|
|
851
973
|
const defaultColor = this.config.markerColor;
|
|
@@ -1269,14 +1391,6 @@ export class WaveformBar {
|
|
|
1269
1391
|
this.player.options.waveform = track.waveform;
|
|
1270
1392
|
}
|
|
1271
1393
|
|
|
1272
|
-
// Auto-resolve config JSON from configPath
|
|
1273
|
-
if (this.config.configPath && track.url) {
|
|
1274
|
-
const audioFile = track.url.split('/').pop().split('?')[0];
|
|
1275
|
-
const jsonFile = audioFile.replace(/\.[^.]+$/, '.json');
|
|
1276
|
-
const path = this.config.configPath.replace(/\/?$/, '/');
|
|
1277
|
-
this.player.options.config = path + jsonFile;
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
1394
|
this.player.options.title = track.title || '';
|
|
1281
1395
|
this.player.options.subtitle = track.artist || '';
|
|
1282
1396
|
|
|
@@ -1344,4 +1458,5 @@ export class WaveformBar {
|
|
|
1344
1458
|
_restoreFavorites() {
|
|
1345
1459
|
this._favorites = restoreFavorites(this.config.storageKey);
|
|
1346
1460
|
}
|
|
1461
|
+
|
|
1347
1462
|
}
|