@arraypress/waveform-bar 1.2.1 → 1.3.1
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 +27 -0
- package/dist/waveform-bar.esm.js +118 -0
- package/dist/waveform-bar.js +118 -0
- package/dist/waveform-bar.min.js +11 -11
- package/package.json +1 -1
- package/src/js/core.js +157 -0
package/README.md
CHANGED
|
@@ -78,6 +78,33 @@ npm install @arraypress/waveform-player @arraypress/waveform-bar
|
|
|
78
78
|
Click the button → the bar slides up from the bottom → music plays. Add more buttons anywhere on the page and they all
|
|
79
79
|
share the same player bar.
|
|
80
80
|
|
|
81
|
+
## Inline WaveformPlayer integration (v1.3+)
|
|
82
|
+
|
|
83
|
+
When `@arraypress/waveform-player` 1.6+ instances are mounted on the same page with `audioMode: 'external'`, WaveformBar discovers them automatically and treats them as **synchronized visual surfaces**:
|
|
84
|
+
|
|
85
|
+
- Click the inline player's play button → playback happens in the bar
|
|
86
|
+
- The bar's audio progress mirrors into the inline canvas in real time (scrubber moves, play/pause icon flips)
|
|
87
|
+
- When the bar switches tracks, the previously-active inline player resets to paused
|
|
88
|
+
|
|
89
|
+
No configuration on the bar side — the discovery scan runs on init + MutationObserver tick, listening for `waveformplayer:request-play|pause|seek` events at the document level. Any inline player whose URL matches the bar's current track gets `setPlayingState(...)` and `setProgress(...)` calls pumped to it.
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
<!-- The same element doubles as a bar trigger (data-wb-play) AND an
|
|
93
|
+
inline visual surface (data-waveform-player + audio-mode). Single
|
|
94
|
+
audio source (the bar), multiple visual surfaces (bar + every
|
|
95
|
+
matching inline player). -->
|
|
96
|
+
<div data-waveform-player
|
|
97
|
+
data-audio-mode="external"
|
|
98
|
+
data-url="song.mp3"
|
|
99
|
+
data-waveform-style="bars"
|
|
100
|
+
|
|
101
|
+
data-wb-play
|
|
102
|
+
data-wb-url="song.mp3"
|
|
103
|
+
data-wb-title="..."></div>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This is the SoundCloud / Bandcamp pattern — the persistent bar owns playback, but every place a track surfaces on the page gets its own playable, progress-aware visualization.
|
|
107
|
+
|
|
81
108
|
## Data Attributes
|
|
82
109
|
|
|
83
110
|
### Play Trigger (`data-wb-play`)
|
package/dist/waveform-bar.esm.js
CHANGED
|
@@ -536,6 +536,7 @@ var WaveformBar = class {
|
|
|
536
536
|
this.isPlaying = true;
|
|
537
537
|
this._updatePlayButton();
|
|
538
538
|
this._syncPageState();
|
|
539
|
+
this._pumpExternalPlayState(true);
|
|
539
540
|
const track = this.getCurrentTrack();
|
|
540
541
|
this._emit("play", { track });
|
|
541
542
|
if (this.config.onPlay) this.config.onPlay(track);
|
|
@@ -544,6 +545,7 @@ var WaveformBar = class {
|
|
|
544
545
|
this.isPlaying = false;
|
|
545
546
|
this._updatePlayButton();
|
|
546
547
|
this._syncPageState();
|
|
548
|
+
this._pumpExternalPlayState(false);
|
|
547
549
|
this._saveState();
|
|
548
550
|
const track = this.getCurrentTrack();
|
|
549
551
|
this._emit("pause", { track });
|
|
@@ -553,6 +555,7 @@ var WaveformBar = class {
|
|
|
553
555
|
this.isPlaying = false;
|
|
554
556
|
this._updatePlayButton();
|
|
555
557
|
this._syncPageState();
|
|
558
|
+
this._pumpExternalPlayState(false);
|
|
556
559
|
if (this.timeCurrentEl) this.timeCurrentEl.textContent = "0:00";
|
|
557
560
|
if (this.repeat === "one") {
|
|
558
561
|
if (this.player) {
|
|
@@ -574,6 +577,7 @@ var WaveformBar = class {
|
|
|
574
577
|
this._lastPosition = currentTime;
|
|
575
578
|
if (this.timeCurrentEl) this.timeCurrentEl.textContent = formatTime(currentTime);
|
|
576
579
|
if (this.timeTotalEl) this.timeTotalEl.textContent = formatTime(duration);
|
|
580
|
+
this._pumpExternalProgress(currentTime, duration);
|
|
577
581
|
if (!this._lastSaveTime || currentTime - this._lastSaveTime > 2) {
|
|
578
582
|
this._lastSaveTime = currentTime;
|
|
579
583
|
this._saveState();
|
|
@@ -612,6 +616,119 @@ var WaveformBar = class {
|
|
|
612
616
|
if (track) this.addToQueue(track);
|
|
613
617
|
});
|
|
614
618
|
});
|
|
619
|
+
this._attachExternalPlayers();
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Discover external-mode WaveformPlayer instances and listen for
|
|
623
|
+
* their request-play / request-pause / request-seek events. Also
|
|
624
|
+
* builds a url → Set<WaveformPlayer> map used by _syncPageState()
|
|
625
|
+
* and the onTimeUpdate callback to push state into the matching
|
|
626
|
+
* inline visualizations.
|
|
627
|
+
*
|
|
628
|
+
* Idempotent — safe to call repeatedly. Late-mounted players are
|
|
629
|
+
* picked up by the MutationObserver in _observeDOM().
|
|
630
|
+
*
|
|
631
|
+
* @private
|
|
632
|
+
*/
|
|
633
|
+
_attachExternalPlayers() {
|
|
634
|
+
if (!this._externalListenersBound) {
|
|
635
|
+
this._externalListenersBound = true;
|
|
636
|
+
document.addEventListener("waveformplayer:request-play", (e) => {
|
|
637
|
+
const t = e.detail;
|
|
638
|
+
if (!t || !t.url) return;
|
|
639
|
+
e.preventDefault();
|
|
640
|
+
this.play(t);
|
|
641
|
+
});
|
|
642
|
+
document.addEventListener("waveformplayer:request-pause", (e) => {
|
|
643
|
+
const t = e.detail;
|
|
644
|
+
if (!t || !t.url) return;
|
|
645
|
+
const current = this.getCurrentTrack();
|
|
646
|
+
if (current && current.url === t.url) {
|
|
647
|
+
e.preventDefault();
|
|
648
|
+
if (this.isPlaying) this.togglePlay();
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
document.addEventListener("waveformplayer:request-seek", (e) => {
|
|
652
|
+
const t = e.detail;
|
|
653
|
+
if (!t || !t.url || typeof t.percent !== "number") return;
|
|
654
|
+
const current = this.getCurrentTrack();
|
|
655
|
+
if (current && current.url === t.url && this.player && this.player.audio) {
|
|
656
|
+
e.preventDefault();
|
|
657
|
+
this.player.seekToPercent(t.percent);
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
const previous = this._externalPlayers || /* @__PURE__ */ new Map();
|
|
662
|
+
this._externalPlayers = /* @__PURE__ */ new Map();
|
|
663
|
+
const WP = window.WaveformPlayer;
|
|
664
|
+
if (!WP || !WP.instances) return;
|
|
665
|
+
const newlyDiscovered = [];
|
|
666
|
+
document.querySelectorAll('[data-waveform-player][data-audio-mode="external"]').forEach((el) => {
|
|
667
|
+
const inst = WP.instances.get(el.id);
|
|
668
|
+
if (!inst || !inst.options || !inst.options.url) return;
|
|
669
|
+
const url = inst.options.url;
|
|
670
|
+
if (!this._externalPlayers.has(url)) this._externalPlayers.set(url, /* @__PURE__ */ new Set());
|
|
671
|
+
this._externalPlayers.get(url).add(inst);
|
|
672
|
+
const wasKnown = previous.get(url) && previous.get(url).has(inst);
|
|
673
|
+
if (!wasKnown) newlyDiscovered.push({ inst, url });
|
|
674
|
+
});
|
|
675
|
+
if (newlyDiscovered.length) {
|
|
676
|
+
const current = this.getCurrentTrack();
|
|
677
|
+
const currentUrl = current ? current.url : null;
|
|
678
|
+
newlyDiscovered.forEach(({ inst, url }) => {
|
|
679
|
+
const isCurrent = url === currentUrl;
|
|
680
|
+
if (typeof inst.setPlayingState === "function") {
|
|
681
|
+
inst.setPlayingState(isCurrent && this.isPlaying);
|
|
682
|
+
}
|
|
683
|
+
if (isCurrent && typeof inst.setProgress === "function" && this.player && this.player.audio) {
|
|
684
|
+
inst.setProgress(this.player.audio.currentTime || 0, this.player.audio.duration || 0);
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Push playing-state into every external-mode player whose URL
|
|
691
|
+
* matches the currently playing track. Other URLs get set to
|
|
692
|
+
* false (paused) — covers the case where the bar switched tracks
|
|
693
|
+
* and the previously-current external player should stop showing
|
|
694
|
+
* its play indicator.
|
|
695
|
+
*
|
|
696
|
+
* @private
|
|
697
|
+
* @param {boolean} playing
|
|
698
|
+
*/
|
|
699
|
+
_pumpExternalPlayState(playing) {
|
|
700
|
+
if (!this._externalPlayers || this._externalPlayers.size === 0) return;
|
|
701
|
+
const current = this.getCurrentTrack();
|
|
702
|
+
const currentUrl = current ? current.url : null;
|
|
703
|
+
this._externalPlayers.forEach((set, url) => {
|
|
704
|
+
const isCurrent = url === currentUrl;
|
|
705
|
+
set.forEach((player) => {
|
|
706
|
+
if (typeof player.setPlayingState === "function") {
|
|
707
|
+
player.setPlayingState(isCurrent && playing);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Push progress (currentTime + duration) into the external-mode
|
|
714
|
+
* player(s) tracking the current URL. Called on every timeupdate
|
|
715
|
+
* tick of the internal player.
|
|
716
|
+
*
|
|
717
|
+
* @private
|
|
718
|
+
* @param {number} currentTime
|
|
719
|
+
* @param {number} duration
|
|
720
|
+
*/
|
|
721
|
+
_pumpExternalProgress(currentTime, duration) {
|
|
722
|
+
if (!this._externalPlayers || this._externalPlayers.size === 0) return;
|
|
723
|
+
const current = this.getCurrentTrack();
|
|
724
|
+
if (!current) return;
|
|
725
|
+
const set = this._externalPlayers.get(current.url);
|
|
726
|
+
if (!set) return;
|
|
727
|
+
set.forEach((player) => {
|
|
728
|
+
if (typeof player.setProgress === "function") {
|
|
729
|
+
player.setProgress(currentTime, duration);
|
|
730
|
+
}
|
|
731
|
+
});
|
|
615
732
|
}
|
|
616
733
|
_observeDOM() {
|
|
617
734
|
if (typeof MutationObserver === "undefined") return;
|
|
@@ -967,6 +1084,7 @@ var WaveformBar = class {
|
|
|
967
1084
|
_loadCurrentTrack() {
|
|
968
1085
|
const track = this.getCurrentTrack();
|
|
969
1086
|
if (!track || !this.player) return;
|
|
1087
|
+
this._pumpExternalPlayState(false);
|
|
970
1088
|
this.show();
|
|
971
1089
|
this._updateTrackDisplay(track);
|
|
972
1090
|
this._updateFavoriteUI();
|
package/dist/waveform-bar.js
CHANGED
|
@@ -537,6 +537,7 @@
|
|
|
537
537
|
this.isPlaying = true;
|
|
538
538
|
this._updatePlayButton();
|
|
539
539
|
this._syncPageState();
|
|
540
|
+
this._pumpExternalPlayState(true);
|
|
540
541
|
const track = this.getCurrentTrack();
|
|
541
542
|
this._emit("play", { track });
|
|
542
543
|
if (this.config.onPlay) this.config.onPlay(track);
|
|
@@ -545,6 +546,7 @@
|
|
|
545
546
|
this.isPlaying = false;
|
|
546
547
|
this._updatePlayButton();
|
|
547
548
|
this._syncPageState();
|
|
549
|
+
this._pumpExternalPlayState(false);
|
|
548
550
|
this._saveState();
|
|
549
551
|
const track = this.getCurrentTrack();
|
|
550
552
|
this._emit("pause", { track });
|
|
@@ -554,6 +556,7 @@
|
|
|
554
556
|
this.isPlaying = false;
|
|
555
557
|
this._updatePlayButton();
|
|
556
558
|
this._syncPageState();
|
|
559
|
+
this._pumpExternalPlayState(false);
|
|
557
560
|
if (this.timeCurrentEl) this.timeCurrentEl.textContent = "0:00";
|
|
558
561
|
if (this.repeat === "one") {
|
|
559
562
|
if (this.player) {
|
|
@@ -575,6 +578,7 @@
|
|
|
575
578
|
this._lastPosition = currentTime;
|
|
576
579
|
if (this.timeCurrentEl) this.timeCurrentEl.textContent = formatTime(currentTime);
|
|
577
580
|
if (this.timeTotalEl) this.timeTotalEl.textContent = formatTime(duration);
|
|
581
|
+
this._pumpExternalProgress(currentTime, duration);
|
|
578
582
|
if (!this._lastSaveTime || currentTime - this._lastSaveTime > 2) {
|
|
579
583
|
this._lastSaveTime = currentTime;
|
|
580
584
|
this._saveState();
|
|
@@ -613,6 +617,119 @@
|
|
|
613
617
|
if (track) this.addToQueue(track);
|
|
614
618
|
});
|
|
615
619
|
});
|
|
620
|
+
this._attachExternalPlayers();
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Discover external-mode WaveformPlayer instances and listen for
|
|
624
|
+
* their request-play / request-pause / request-seek events. Also
|
|
625
|
+
* builds a url → Set<WaveformPlayer> map used by _syncPageState()
|
|
626
|
+
* and the onTimeUpdate callback to push state into the matching
|
|
627
|
+
* inline visualizations.
|
|
628
|
+
*
|
|
629
|
+
* Idempotent — safe to call repeatedly. Late-mounted players are
|
|
630
|
+
* picked up by the MutationObserver in _observeDOM().
|
|
631
|
+
*
|
|
632
|
+
* @private
|
|
633
|
+
*/
|
|
634
|
+
_attachExternalPlayers() {
|
|
635
|
+
if (!this._externalListenersBound) {
|
|
636
|
+
this._externalListenersBound = true;
|
|
637
|
+
document.addEventListener("waveformplayer:request-play", (e) => {
|
|
638
|
+
const t = e.detail;
|
|
639
|
+
if (!t || !t.url) return;
|
|
640
|
+
e.preventDefault();
|
|
641
|
+
this.play(t);
|
|
642
|
+
});
|
|
643
|
+
document.addEventListener("waveformplayer:request-pause", (e) => {
|
|
644
|
+
const t = e.detail;
|
|
645
|
+
if (!t || !t.url) return;
|
|
646
|
+
const current = this.getCurrentTrack();
|
|
647
|
+
if (current && current.url === t.url) {
|
|
648
|
+
e.preventDefault();
|
|
649
|
+
if (this.isPlaying) this.togglePlay();
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
document.addEventListener("waveformplayer:request-seek", (e) => {
|
|
653
|
+
const t = e.detail;
|
|
654
|
+
if (!t || !t.url || typeof t.percent !== "number") return;
|
|
655
|
+
const current = this.getCurrentTrack();
|
|
656
|
+
if (current && current.url === t.url && this.player && this.player.audio) {
|
|
657
|
+
e.preventDefault();
|
|
658
|
+
this.player.seekToPercent(t.percent);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
const previous = this._externalPlayers || /* @__PURE__ */ new Map();
|
|
663
|
+
this._externalPlayers = /* @__PURE__ */ new Map();
|
|
664
|
+
const WP = window.WaveformPlayer;
|
|
665
|
+
if (!WP || !WP.instances) return;
|
|
666
|
+
const newlyDiscovered = [];
|
|
667
|
+
document.querySelectorAll('[data-waveform-player][data-audio-mode="external"]').forEach((el) => {
|
|
668
|
+
const inst = WP.instances.get(el.id);
|
|
669
|
+
if (!inst || !inst.options || !inst.options.url) return;
|
|
670
|
+
const url = inst.options.url;
|
|
671
|
+
if (!this._externalPlayers.has(url)) this._externalPlayers.set(url, /* @__PURE__ */ new Set());
|
|
672
|
+
this._externalPlayers.get(url).add(inst);
|
|
673
|
+
const wasKnown = previous.get(url) && previous.get(url).has(inst);
|
|
674
|
+
if (!wasKnown) newlyDiscovered.push({ inst, url });
|
|
675
|
+
});
|
|
676
|
+
if (newlyDiscovered.length) {
|
|
677
|
+
const current = this.getCurrentTrack();
|
|
678
|
+
const currentUrl = current ? current.url : null;
|
|
679
|
+
newlyDiscovered.forEach(({ inst, url }) => {
|
|
680
|
+
const isCurrent = url === currentUrl;
|
|
681
|
+
if (typeof inst.setPlayingState === "function") {
|
|
682
|
+
inst.setPlayingState(isCurrent && this.isPlaying);
|
|
683
|
+
}
|
|
684
|
+
if (isCurrent && typeof inst.setProgress === "function" && this.player && this.player.audio) {
|
|
685
|
+
inst.setProgress(this.player.audio.currentTime || 0, this.player.audio.duration || 0);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Push playing-state into every external-mode player whose URL
|
|
692
|
+
* matches the currently playing track. Other URLs get set to
|
|
693
|
+
* false (paused) — covers the case where the bar switched tracks
|
|
694
|
+
* and the previously-current external player should stop showing
|
|
695
|
+
* its play indicator.
|
|
696
|
+
*
|
|
697
|
+
* @private
|
|
698
|
+
* @param {boolean} playing
|
|
699
|
+
*/
|
|
700
|
+
_pumpExternalPlayState(playing) {
|
|
701
|
+
if (!this._externalPlayers || this._externalPlayers.size === 0) return;
|
|
702
|
+
const current = this.getCurrentTrack();
|
|
703
|
+
const currentUrl = current ? current.url : null;
|
|
704
|
+
this._externalPlayers.forEach((set, url) => {
|
|
705
|
+
const isCurrent = url === currentUrl;
|
|
706
|
+
set.forEach((player) => {
|
|
707
|
+
if (typeof player.setPlayingState === "function") {
|
|
708
|
+
player.setPlayingState(isCurrent && playing);
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Push progress (currentTime + duration) into the external-mode
|
|
715
|
+
* player(s) tracking the current URL. Called on every timeupdate
|
|
716
|
+
* tick of the internal player.
|
|
717
|
+
*
|
|
718
|
+
* @private
|
|
719
|
+
* @param {number} currentTime
|
|
720
|
+
* @param {number} duration
|
|
721
|
+
*/
|
|
722
|
+
_pumpExternalProgress(currentTime, duration) {
|
|
723
|
+
if (!this._externalPlayers || this._externalPlayers.size === 0) return;
|
|
724
|
+
const current = this.getCurrentTrack();
|
|
725
|
+
if (!current) return;
|
|
726
|
+
const set = this._externalPlayers.get(current.url);
|
|
727
|
+
if (!set) return;
|
|
728
|
+
set.forEach((player) => {
|
|
729
|
+
if (typeof player.setProgress === "function") {
|
|
730
|
+
player.setProgress(currentTime, duration);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
616
733
|
}
|
|
617
734
|
_observeDOM() {
|
|
618
735
|
if (typeof MutationObserver === "undefined") return;
|
|
@@ -968,6 +1085,7 @@
|
|
|
968
1085
|
_loadCurrentTrack() {
|
|
969
1086
|
const track = this.getCurrentTrack();
|
|
970
1087
|
if (!track || !this.player) return;
|
|
1088
|
+
this._pumpExternalPlayState(false);
|
|
971
1089
|
this.show();
|
|
972
1090
|
this._updateTrackDisplay(track);
|
|
973
1091
|
this._updateFavoriteUI();
|
package/dist/waveform-bar.min.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(()=>{var n={play:'<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>',pause:'<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg>',prev:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/></svg>',next:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg>',queue:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg>',music:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" opacity="0.5"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55C7.79 13 6 14.79 6 17s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>',volHigh:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg>',volLow:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/></svg>',volMute:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>',heart:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',heartFilled:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',cart:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>',close:'<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>',speaker:'<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/></svg>',repeatOff:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg>',repeatAll:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg>',repeatOne:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/><text x="12" y="15" text-anchor="middle" font-size="7" font-weight="bold" fill="currentColor">1</text></svg>'};function d(s){return s?s.split("/").pop().split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,t=>t.toUpperCase()):"Untitled"}function
|
|
1
|
+
(()=>{var n={play:'<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>',pause:'<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg>',prev:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/></svg>',next:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg>',queue:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg>',music:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" opacity="0.5"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55C7.79 13 6 14.79 6 17s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>',volHigh:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg>',volLow:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/></svg>',volMute:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>',heart:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',heartFilled:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',cart:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>',close:'<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>',speaker:'<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/></svg>',repeatOff:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg>',repeatAll:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg>',repeatOne:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/><text x="12" y="15" text-anchor="middle" font-size="7" font-weight="bold" fill="currentColor">1</text></svg>'};function d(s){return s?s.split("/").pop().split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,t=>t.toUpperCase()):"Untitled"}function h(s){if(!s)return"";let t=document.createElement("div");return t.textContent=s,t.innerHTML}function v(s){if(!s||isNaN(s))return"0:00";let t=Math.floor(s/60),e=Math.floor(s%60);return`${t}:${e.toString().padStart(2,"0")}`}function p(s){let t=s.dataset.wbUrl||s.dataset.url;if(!t)return null;let e={};try{e=JSON.parse(s.dataset.wbMeta||s.dataset.meta||"{}")}catch{}let i=null;try{i=JSON.parse(s.dataset.wbMarkers||s.dataset.markers||"null")}catch{}return{url:t,id:s.dataset.wbId||s.dataset.id||t,title:s.dataset.wbTitle||s.dataset.title||d(t),artist:s.dataset.wbArtist||s.dataset.artist||"",artwork:s.dataset.wbArtwork||s.dataset.artwork||"",album:s.dataset.wbAlbum||s.dataset.album||"",link:s.dataset.wbLink||s.dataset.link||"",duration:s.dataset.wbDuration||s.dataset.duration||"",bpm:s.dataset.wbBpm||s.dataset.bpm||"",key:s.dataset.wbKey||s.dataset.key||"",waveform:s.dataset.wbWaveform||s.dataset.waveform||"",markers:i,favorited:s.dataset.wbFavorited==="true",inCart:s.dataset.wbInCart==="true",meta:e}}function g(s,t){try{sessionStorage.setItem(s,JSON.stringify(t))}catch{}}function w(s){try{let t=sessionStorage.getItem(s);if(!t)return null;let e=JSON.parse(t);return!e||!e.queue||!e.queue.length?null:e}catch{return sessionStorage.removeItem(s),null}}function y(s,t,e,i){try{localStorage.setItem(s+"-vol",JSON.stringify({v:t,m:e,b:i}))}catch{}}function E(s){try{let t=JSON.parse(localStorage.getItem(s+"-vol"));return t?{volume:t.v!=null?t.v:1,muted:t.m||!1,volumeBeforeMute:t.b||1}:null}catch{return null}}function m(s,t){try{localStorage.setItem(s+"-favs",JSON.stringify([...t]))}catch{}}function _(s){try{let t=JSON.parse(localStorage.getItem(s+"-favs"));return Array.isArray(t)?new Set(t):new Set}catch{return new Set}}function b(s,t){if(!(!s||!s.endpoint)){if(typeof s.endpoint=="function"){try{s.endpoint(t)}catch(e){console.warn("WaveformBar action callback error:",e)}return}typeof s.endpoint=="string"&&fetch(s.endpoint,{method:s.method||"POST",headers:{"Content-Type":"application/json",...s.headers||{}},body:JSON.stringify(t)}).catch(e=>console.warn("WaveformBar action request failed:",e))}}function k(s){let t='<div class="wb-left">';t+='<div class="wb-controls">',s.showPrevNext&&(t+=`<button class="wb-btn wb-prev" aria-label="Previous" title="Previous">${n.prev}</button>`),t+=`<button class="wb-btn wb-play" aria-label="Play/Pause" title="Play">
|
|
2
2
|
<span class="wb-icon-play">${n.play}</span>
|
|
3
3
|
<span class="wb-icon-pause" style="display:none">${n.pause}</span>
|
|
4
4
|
</button>`,s.showPrevNext&&(t+=`<button class="wb-btn wb-next" aria-label="Next" title="Next">${n.next}</button>`),s.showRepeat&&(t+=`<button class="wb-btn wb-btn-sm wb-repeat" aria-label="Repeat" title="Repeat: Off">${n.repeatOff}</button>`),t+="</div>",t+=`<div class="wb-track">
|
|
@@ -22,26 +22,26 @@
|
|
|
22
22
|
<button class="wb-btn wb-btn-sm wb-queue-clear" aria-label="Clear queue">Clear</button>
|
|
23
23
|
</div>
|
|
24
24
|
<div class="wb-queue-body"></div>
|
|
25
|
-
`,s}function
|
|
25
|
+
`,s}function x(s,t,e,i,r){if(!s)return;let a=Math.max(0,e.length-1-i);if(t&&(t.textContent=a),e.length===0){s.innerHTML=`<div class="wb-queue-empty">${n.queue}<p>Queue is empty</p></div>`;return}let l="";if(i>=0&&i<e.length){let o=e[i];l+='<div class="wb-queue-label">Now Playing</div>',l+=`<div class="wb-queue-item wb-queue-current" data-qi="${i}">
|
|
26
26
|
<span class="wb-queue-num">${n.speaker}</span>
|
|
27
27
|
<div class="wb-queue-info">
|
|
28
|
-
<div class="wb-queue-item-title">${
|
|
29
|
-
<div class="wb-queue-item-artist">${
|
|
28
|
+
<div class="wb-queue-item-title">${h(o.title)}</div>
|
|
29
|
+
<div class="wb-queue-item-artist">${h(o.artist)}</div>
|
|
30
30
|
</div>
|
|
31
|
-
</div>`}let
|
|
31
|
+
</div>`}let u=!1;for(let o=i+1;o<e.length;o++){u||(l+='<div class="wb-queue-label">Next Up</div>',u=!0);let c=e[o];l+=`<div class="wb-queue-item" data-qi="${o}">
|
|
32
32
|
<span class="wb-queue-num">${o-i}</span>
|
|
33
33
|
<div class="wb-queue-info">
|
|
34
|
-
<div class="wb-queue-item-title">${
|
|
35
|
-
<div class="wb-queue-item-artist">${
|
|
34
|
+
<div class="wb-queue-item-title">${h(c.title)}</div>
|
|
35
|
+
<div class="wb-queue-item-artist">${h(c.artist)}</div>
|
|
36
36
|
</div>
|
|
37
37
|
<button class="wb-queue-remove" data-qi="${o}" aria-label="Remove">${n.close}</button>
|
|
38
|
-
</div>`}if(i>0){l+='<div class="wb-queue-label">Previously Played</div>';for(let o=i-1;o>=0;o--){let
|
|
38
|
+
</div>`}if(i>0){l+='<div class="wb-queue-label">Previously Played</div>';for(let o=i-1;o>=0;o--){let c=e[o];l+=`<div class="wb-queue-item wb-queue-played" data-qi="${o}">
|
|
39
39
|
<span class="wb-queue-num">${o+1}</span>
|
|
40
40
|
<div class="wb-queue-info">
|
|
41
|
-
<div class="wb-queue-item-title">${
|
|
42
|
-
<div class="wb-queue-item-artist">${
|
|
41
|
+
<div class="wb-queue-item-title">${h(c.title)}</div>
|
|
42
|
+
<div class="wb-queue-item-artist">${h(c.artist)}</div>
|
|
43
43
|
</div>
|
|
44
|
-
</div>`}}s.innerHTML=l,s.querySelectorAll(".wb-queue-item[data-qi]").forEach(o=>{o.addEventListener("click",h=>{h.target.closest(".wb-queue-remove")||r.onSkipTo&&r.onSkipTo(parseInt(o.dataset.qi))})}),s.querySelectorAll(".wb-queue-remove").forEach(o=>{o.addEventListener("click",h=>{h.stopPropagation(),r.onRemove&&r.onRemove(parseInt(o.dataset.qi))})})}var x={persist:!0,autoResume:!0,continuous:!0,repeat:"off",showRepeat:!0,showQueue:!0,showPrevNext:!0,showVolume:!0,showMute:!0,showMeta:!0,showTime:!0,showTrackLink:!0,maxMeta:3,defaultArtwork:null,theme:null,waveformStyle:"mirror",waveformHeight:32,barWidth:2,barSpacing:0,waveformColor:null,progressColor:null,markerColor:"rgba(255, 255, 255, 0.25)",volume:1,storageKey:"waveform-bar",actions:null,onPlay:null,onPause:null,onTrackChange:null,onQueueChange:null,onVolumeChange:null,onFavorite:null,onCart:null},f=class{constructor(){this.config=null,this.player=null,this.queue=[],this.currentIndex=-1,this.isPlaying=!1,this.isInitialized=!1,this.queueOpen=!1,this.volume=1,this.isMuted=!1,this._volumeBeforeMute=1,this._lastPosition=0,this._favorites=new Set,this._cartItems=new Set,this._observer=null,this._activeMarkers=null,this._currentMarkerIndex=-1,this.repeat="off",this.barEl=null,this.queueEl=null,this.waveformContainer=null,this.volumePopupEl=null,this.titleEl=null,this.artistEl=null,this.metaEl=null,this.playBtnEl=null,this.repeatBtnEl=null,this.queueBtnEl=null,this.queueBodyEl=null,this.queueCountEl=null,this.volumeSliderEl=null,this.muteBtnEl=null,this.favBtnEl=null,this.cartBtnEl=null,this.timeCurrentEl=null,this.timeTotalEl=null}init(t={}){return this.isInitialized&&this.destroy(),this.config={...x,...t},this.volume=this.config.volume,typeof window.WaveformPlayer>"u"?(console.error("WaveformBar: WaveformPlayer is required."),this):(this._createBar(),this._createQueue(),this._initPlayer(),this._bindTriggers(),this._observeDOM(),this.config.persist&&(this._restoreVolume(),this._restoreFavorites()),this._seedFromAttributes(),this.config.persist&&this._restoreState(),this.isInitialized=!0,this._beforeUnloadHandler=()=>this._saveState(),window.addEventListener("beforeunload",this._beforeUnloadHandler),this)}destroy(){return this.player&&(this.player.destroy(),this.player=null),this.barEl&&(this.barEl.remove(),this.barEl=null),this.queueEl&&(this.queueEl.remove(),this.queueEl=null),this._observer&&(this._observer.disconnect(),this._observer=null),this._beforeUnloadHandler&&(window.removeEventListener("beforeunload",this._beforeUnloadHandler),this._beforeUnloadHandler=null),document.querySelectorAll("[data-wb-play],[data-wb-queue]").forEach(t=>delete t._wbBound),document.querySelectorAll(".wb-current,.wb-playing").forEach(t=>t.classList.remove("wb-current","wb-playing")),this.queue=[],this.currentIndex=-1,this.isPlaying=!1,this.queueOpen=!1,this.isInitialized=!1,this.config=null,this}_createBar(){this.barEl=document.createElement("div"),this.barEl.className="waveform-bar";let t=this.config.theme||this._detectTheme();t==="light"&&this.barEl.classList.add("wb-light"),this._resolvedTheme=t,this.barEl.id="waveform-bar",this.barEl.innerHTML=k(this.config),document.body.appendChild(this.barEl),this.titleEl=this.barEl.querySelector(".wb-title"),this.artistEl=this.barEl.querySelector(".wb-artist"),this.metaEl=this.barEl.querySelector(".wb-meta"),this.playBtnEl=this.barEl.querySelector(".wb-play"),this.waveformContainer=this.barEl.querySelector(".wb-waveform-container"),this.queueBtnEl=this.barEl.querySelector(".wb-queue-btn"),this.muteBtnEl=this.barEl.querySelector(".wb-mute"),this.volumeSliderEl=this.barEl.querySelector(".wb-volume-slider"),this.favBtnEl=this.barEl.querySelector(".wb-fav"),this.cartBtnEl=this.barEl.querySelector(".wb-cart"),this.timeCurrentEl=this.barEl.querySelector(".wb-time-current"),this.timeTotalEl=this.barEl.querySelector(".wb-time-total"),this.playBtnEl.addEventListener("click",()=>this.togglePlay());let e=this.barEl.querySelector(".wb-prev"),i=this.barEl.querySelector(".wb-next");e&&e.addEventListener("click",()=>this.previous()),i&&i.addEventListener("click",()=>this.next()),this.repeatBtnEl=this.barEl.querySelector(".wb-repeat"),this.repeatBtnEl&&(this.repeat=this.config.repeat||"off",this._updateRepeatButton(),this.repeatBtnEl.addEventListener("click",()=>this.cycleRepeat())),this.queueBtnEl&&this.queueBtnEl.addEventListener("click",()=>this.toggleQueuePanel()),this.volumePopupEl=this.barEl.querySelector(".wb-volume-popup");let r=this.barEl.querySelector(".wb-volume");if(this.muteBtnEl&&this.muteBtnEl.addEventListener("click",a=>{a.stopPropagation(),this.toggleMute()}),r&&this.volumePopupEl){let a;r.addEventListener("mouseenter",()=>{clearTimeout(a),this.openVolumePopup()}),r.addEventListener("mouseleave",()=>{a=setTimeout(()=>this.closeVolumePopup(),300)})}this.volumeSliderEl&&this.volumeSliderEl.addEventListener("input",a=>{a.stopPropagation(),this.setVolume(parseInt(a.target.value)/100)}),document.addEventListener("click",a=>{this.volumePopupEl?.classList.contains("wb-volume-open")&&!this.barEl.querySelector(".wb-volume")?.contains(a.target)&&this.closeVolumePopup()}),this.favBtnEl&&this.favBtnEl.addEventListener("click",()=>this.toggleFavorite()),this.cartBtnEl&&this.cartBtnEl.addEventListener("click",()=>this.addToCart()),this.config.showTrackLink&&this.barEl.querySelector(".wb-track").addEventListener("click",()=>{let a=this.getCurrentTrack();a&&a.link&&(window.location.href=a.link)})}_createQueue(){this.config.showQueue&&(this.queueEl=q(),this._resolvedTheme==="light"&&this.queueEl.classList.add("wb-light"),document.body.appendChild(this.queueEl),this.queueBodyEl=this.queueEl.querySelector(".wb-queue-body"),this.queueCountEl=this.queueEl.querySelector(".wb-queue-count"),this.queueEl.querySelector(".wb-queue-clear").addEventListener("click",()=>this.clearQueue()),document.addEventListener("click",t=>{this.queueOpen&&!this.queueEl.contains(t.target)&&!this.queueBtnEl.contains(t.target)&&this.closeQueuePanel()}))}_initPlayer(){let t={showControls:!1,showInfo:!1,waveformStyle:this.config.waveformStyle,height:this.config.waveformHeight,barWidth:this.config.barWidth,barSpacing:this.config.barSpacing,singlePlay:!1,onPlay:()=>{this.isPlaying=!0,this._updatePlayButton(),this._syncPageState();let e=this.getCurrentTrack();this._emit("play",{track:e}),this.config.onPlay&&this.config.onPlay(e)},onPause:()=>{this.isPlaying=!1,this._updatePlayButton(),this._syncPageState(),this._saveState();let e=this.getCurrentTrack();this._emit("pause",{track:e}),this.config.onPause&&this.config.onPause(e)},onEnd:()=>{if(this.isPlaying=!1,this._updatePlayButton(),this._syncPageState(),this.timeCurrentEl&&(this.timeCurrentEl.textContent="0:00"),this.repeat==="one"){this.player&&(this.player.seekTo(0),this.player.play().catch(()=>{}));return}this.config.continuous&&this.currentIndex<this.queue.length-1?(this.currentIndex++,this._loadCurrentTrack()):this.repeat==="all"&&this.queue.length>0&&(this.currentIndex=0,this._loadCurrentTrack())},onTimeUpdate:(e,i)=>{this._lastPosition=e,this.timeCurrentEl&&(this.timeCurrentEl.textContent=v(e)),this.timeTotalEl&&(this.timeTotalEl.textContent=v(i)),(!this._lastSaveTime||e-this._lastSaveTime>2)&&(this._lastSaveTime=e,this._saveState()),this._activeMarkers&&this._checkMarkerBoundary(e)},onLoad:null};this.config.waveformColor&&(t.waveformColor=this.config.waveformColor),this.config.progressColor&&(t.progressColor=this.config.progressColor),this.player=new window.WaveformPlayer(this.waveformContainer,t),this.player.setVolume(this.volume)}_bindTriggers(){document.querySelectorAll("[data-wb-play]").forEach(t=>{t._wbBound||(t._wbBound=!0,t.addEventListener("click",e=>{e.preventDefault();let i=m(t);i&&this.play(i)}))}),document.querySelectorAll("[data-wb-queue]").forEach(t=>{t._wbBound||(t._wbBound=!0,t.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation();let i=m(t);i&&this.addToQueue(i)}))})}_observeDOM(){typeof MutationObserver>"u"||(this._observer=new MutationObserver(()=>{this._bindTriggers(),this._syncPageState()}),this._observer.observe(document.body,{childList:!0,subtree:!0}))}play(t){let e=typeof t=="string"?{url:t,id:t,title:d(t)}:t;if(!e||!e.url)return this;let i=this.getCurrentTrack();if(i&&i.url===e.url)return this.togglePlay(),this;let r=this.queue.findIndex(a=>a.url===e.url);if(r>=0)this.queue[r]={...this.queue[r],...e},this.currentIndex=r;else{let a=this.currentIndex+1;this.queue.splice(a,0,e),this.currentIndex=a}return this._loadCurrentTrack(),this}addToQueue(t){let e=typeof t=="string"?{url:t,id:t,title:d(t)}:t;return!e||!e.url?this:this.queue.find(i=>i.url===e.url)?this:(this.queue.push(e),this._renderQueue(),this._saveState(),this._updateNavButtons(),this.currentIndex===-1&&(this.currentIndex=0,this._loadCurrentTrack()),this.config.onQueueChange&&this.config.onQueueChange(this.queue,this.currentIndex),this)}togglePlay(){return this.player?(this.isPlaying?this.player.pause():this.player.play(),this):this}pause(){return this.player&&this.isPlaying&&this.player.pause(),this}next(){return this.currentIndex<this.queue.length-1?(this.currentIndex++,this._loadCurrentTrack()):this.repeat==="all"&&this.queue.length>0&&(this.currentIndex=0,this._loadCurrentTrack()),this}previous(){return this.player&&this.player.audio&&this.player.audio.currentTime>3?(this.player.seekTo(0),this):(this.currentIndex>0?(this.currentIndex--,this._loadCurrentTrack()):this.repeat==="all"&&this.queue.length>0&&(this.currentIndex=this.queue.length-1,this._loadCurrentTrack()),this)}skipTo(t){return t<0||t>=this.queue.length?this:t===this.currentIndex?(this.togglePlay(),this):(this.currentIndex=t,this._loadCurrentTrack(),this)}seekToMarker(t){if(!this._activeMarkers||t<0||t>=this._activeMarkers.length)return this;let e=this._activeMarkers[t];return this.player&&(this.player.seekTo(e.time),this.isPlaying||this.togglePlay()),this}seekToMarkerByLabel(t){if(!this._activeMarkers)return this;let e=this._activeMarkers.findIndex(i=>(i.label||i.title||"").toLowerCase()===t.toLowerCase());return e>=0&&this.seekToMarker(e),this}setVolume(t){return this.volume=Math.max(0,Math.min(1,t)),this.isMuted=this.volume===0,this.player&&this.player.setVolume(this.volume),this._updateVolumeUI(),y(this.config.storageKey,this.volume,this.isMuted,this._volumeBeforeMute),this._emit("volumechange",{volume:this.volume}),this.config.onVolumeChange&&this.config.onVolumeChange(this.volume),this}getVolume(){return this.volume}toggleMute(){return this.isMuted?this.setVolume(this._volumeBeforeMute||1):(this._volumeBeforeMute=this.volume,this.isMuted=!0,this.player&&this.player.setVolume(0),this._updateVolumeUI()),this}toggleFavorite(){let t=this.getCurrentTrack();if(!t)return this;let e=t.id||t.url,i=this._favorites.has(e);return i?this._favorites.delete(e):this._favorites.add(e),this._updateFavoriteUI(),this._syncFavoriteAttributes(t.url,!i),p(this.config.storageKey,this._favorites),this._emit("favorite",{track:t,favorited:!i}),this.config.onFavorite&&this.config.onFavorite(t,!i),this.config.actions?.favorite&&b(this.config.actions.favorite,{action:"favorite",id:e,url:t.url,title:t.title,favorited:!i}),this}addToCart(){let t=this.getCurrentTrack();if(!t)return this;let e=t.id||t.url;return this._cartItems.add(e),this.cartBtnEl&&(this.cartBtnEl.classList.add("wb-action-done"),setTimeout(()=>this.cartBtnEl.classList.remove("wb-action-done"),1500)),this._syncCartAttributes(t.url,!0),this._emit("cart",{track:t}),this.config.onCart&&this.config.onCart(t),this.config.actions?.cart&&b(this.config.actions.cart,{action:"cart",id:e,url:t.url,title:t.title}),this}isFavorited(t){if(!t){let e=this.getCurrentTrack();t=e?e.id||e.url:null}return t?this._favorites.has(t):!1}isInCart(t){if(!t){let e=this.getCurrentTrack();t=e?e.id||e.url:null}return t?this._cartItems.has(t):!1}removeFromQueue(t){return t<0||t>=this.queue.length||t===this.currentIndex?this:(this.queue.splice(t,1),t<this.currentIndex&&this.currentIndex--,this._renderQueue(),this._saveState(),this._updateNavButtons(),this._emit("queuechange",{queue:this.queue,currentIndex:this.currentIndex}),this.config.onQueueChange&&this.config.onQueueChange(this.queue,this.currentIndex),this)}clearQueue(){let t=this.getCurrentTrack();return this.queue=t?[t]:[],this.currentIndex=t?0:-1,this._renderQueue(),this._saveState(),this._updateNavButtons(),this._emit("queuechange",{queue:this.queue,currentIndex:this.currentIndex}),this.config.onQueueChange&&this.config.onQueueChange(this.queue,this.currentIndex),this}getCurrentTrack(){return this.currentIndex>=0&&this.currentIndex<this.queue.length?this.queue[this.currentIndex]:null}getQueue(){return[...this.queue]}getCurrentIndex(){return this.currentIndex}isCurrentlyPlaying(t){let e=this.getCurrentTrack();return this.isPlaying&&e&&e.url===t}isCurrentTrack(t){let e=this.getCurrentTrack();return e&&e.url===t}getPlayer(){return this.player}_emit(t,e={}){this.barEl&&this.barEl.dispatchEvent(new CustomEvent("waveformbar:"+t,{bubbles:!0,detail:e}))}show(){return this.barEl&&this.barEl.classList.add("wb-active"),this}hide(){return this.barEl&&this.barEl.classList.remove("wb-active"),this.closeQueuePanel(),this.closeVolumePopup(),this}toggleQueuePanel(){return this.queueOpen?this.closeQueuePanel():this.openQueuePanel()}openQueuePanel(){if(!this.queueEl)return this;if(this.queueOpen=!0,this.closeVolumePopup(),this.queueBtnEl){let t=this.queueBtnEl.getBoundingClientRect();this.queueEl.style.right=window.innerWidth-t.right+"px"}return this.queueEl.classList.add("wb-queue-open"),this.queueBtnEl&&this.queueBtnEl.classList.add("wb-active"),this._renderQueue(),this}closeQueuePanel(){return this.queueEl?(this.queueOpen=!1,this.queueEl.classList.remove("wb-queue-open"),this.queueBtnEl&&this.queueBtnEl.classList.remove("wb-active"),this):this}toggleVolumePopup(){return this.volumePopupEl?.classList.contains("wb-volume-open")?this.closeVolumePopup():this.openVolumePopup(),this}openVolumePopup(){return this.volumePopupEl?(this.closeQueuePanel(),this.volumePopupEl.classList.add("wb-volume-open"),this.muteBtnEl&&this.muteBtnEl.classList.add("wb-active"),this):this}closeVolumePopup(){return this.volumePopupEl?(this.volumePopupEl.classList.remove("wb-volume-open"),this.muteBtnEl&&this.muteBtnEl.classList.remove("wb-active"),this):this}_loadCurrentTrack(){let t=this.getCurrentTrack();if(!t||!this.player)return;this.show(),this._updateTrackDisplay(t),this._updateFavoriteUI();let e={artwork:t.artwork,album:t.album};if(t.waveform&&(e.waveform=t.waveform),t.markers&&t.markers.length){let i=this.config.markerColor;e.markers=t.markers.map(r=>({...r,color:r.color||i}))}else e.markers=[];this.player.loadTrack(t.url,t.title,t.artist,e),this._activeMarkers=t.markers&&t.markers.length?t.markers:null,this._currentMarkerIndex=-1,this.player&&this.player.setVolume(this.isMuted?0:this.volume),this._renderQueue(),this._syncPageState(),this._saveState(),this._updateNavButtons(),this._emit("trackchange",{track:t,index:this.currentIndex}),this.config.onTrackChange&&this.config.onTrackChange(t,this.currentIndex)}_updateTrackDisplay(t){this.titleEl&&this._setScrollText(this.titleEl,t.title||"Untitled"),this.artistEl&&this._setScrollText(this.artistEl,t.artist||"");let e=this.barEl.querySelector(".wb-artwork");if(e){let r=t.artwork||this.config.defaultArtwork;e.innerHTML=r?`<img src="${u(r)}" alt="${u(t.title)}" />`:n.music}this.metaEl&&this.config.showMeta&&this._renderMeta(t);let i=this.barEl.querySelector(".wb-track");i&&(i.style.cursor=t.link?"pointer":"default"),this.timeCurrentEl&&(this.timeCurrentEl.textContent="0:00"),this.timeTotalEl&&(this.timeTotalEl.textContent="0:00")}_setScrollText(t,e){t.classList.remove("wb-scrolling"),t.textContent=e,requestAnimationFrame(()=>{if(t.scrollWidth>t.clientWidth){let i=t.scrollWidth-t.clientWidth,r=Math.max(4,i/20);t.innerHTML=`<span class="wb-scroll-inner">${u(e)}</span>`,t.style.setProperty("--wb-scroll-distance",`-${i+48}px`),t.style.setProperty("--wb-scroll-duration",`${r}s`),t.classList.add("wb-scrolling")}})}_renderMeta(t){if(!this.metaEl)return;let e=[];if(t.bpm&&e.push({label:t.bpm+" BPM",type:"bpm"}),t.key&&e.push({label:t.key,type:"key"}),t.duration&&e.push({label:t.duration,type:"duration"}),t.meta)for(let[r,a]of Object.entries(t.meta))a&&e.length<this.config.maxMeta&&e.push({label:String(a),type:r});let i=e.slice(0,this.config.maxMeta);this.metaEl.style.display=i.length?"flex":"none",this.metaEl.innerHTML=i.map(r=>`<span class="wb-tag wb-tag-${u(r.type)}">${u(r.label)}</span>`).join("")}_updatePlayButton(){if(!this.playBtnEl)return;let t=this.playBtnEl.querySelector(".wb-icon-play"),e=this.playBtnEl.querySelector(".wb-icon-pause");t&&(t.style.display=this.isPlaying?"none":"block"),e&&(e.style.display=this.isPlaying?"block":"none"),this.playBtnEl.title=this.isPlaying?"Pause":"Play"}_updateNavButtons(){let t=this.barEl?.querySelector(".wb-prev"),e=this.barEl?.querySelector(".wb-next");this.repeat==="all"?(t&&t.classList.remove("wb-disabled"),e&&e.classList.remove("wb-disabled")):(t&&t.classList.toggle("wb-disabled",this.currentIndex<=0),e&&e.classList.toggle("wb-disabled",this.currentIndex>=this.queue.length-1))}cycleRepeat(){let t=["off","all","one"],e=t.indexOf(this.repeat);return this.repeat=t[(e+1)%t.length],this._updateRepeatButton(),this._updateNavButtons(),this._emit("repeatchange",{mode:this.repeat}),this}setRepeat(t){return["off","all","one"].includes(t)&&(this.repeat=t,this._updateRepeatButton(),this._updateNavButtons(),this._emit("repeatchange",{mode:this.repeat})),this}_updateRepeatButton(){if(!this.repeatBtnEl)return;let t={off:n.repeatOff,all:n.repeatAll,one:n.repeatOne},e={off:"Repeat: Off",all:"Repeat: All",one:"Repeat: One"};this.repeatBtnEl.innerHTML=t[this.repeat],this.repeatBtnEl.title=e[this.repeat],this.repeatBtnEl.classList.toggle("wb-repeat-active",this.repeat!=="off")}_checkMarkerBoundary(t){if(!this._activeMarkers)return;let e=-1;for(let l=this._activeMarkers.length-1;l>=0;l--)if(t>=this._activeMarkers[l].time){e=l;break}if(e===this._currentMarkerIndex||(this._currentMarkerIndex=e,e<0))return;let i=this._activeMarkers[e],r=this.getCurrentTrack();i.title&&this.titleEl&&this._setScrollText(this.titleEl,i.title),i.artist&&this.artistEl&&this._setScrollText(this.artistEl,i.artist);let a=this.waveformContainer?.querySelectorAll(".waveform-marker");if(a&&a.forEach((l,c)=>l.classList.toggle("wb-marker-active",c===e)),i.artwork){let l=this.barEl.querySelector(".wb-artwork");l&&(l.innerHTML=`<img src="${i.artwork}" alt="${i.title||""}" />`)}if(this.metaEl&&(i.bpm||i.key)){let l={...r||{},bpm:i.bpm||"",key:i.key||""};this._renderMeta(l)}this._emit("markerchange",{marker:i,index:e,track:r})}_updateVolumeUI(){this.volumeSliderEl&&(this.volumeSliderEl.value=this.isMuted?0:Math.round(this.volume*100)),this.muteBtnEl&&(this.isMuted||this.volume===0?(this.muteBtnEl.innerHTML=n.volMute,this.muteBtnEl.classList.add("wb-muted"),this.muteBtnEl.title="Unmute"):this.volume<.5?(this.muteBtnEl.innerHTML=n.volLow,this.muteBtnEl.classList.remove("wb-muted"),this.muteBtnEl.title="Mute"):(this.muteBtnEl.innerHTML=n.volHigh,this.muteBtnEl.classList.remove("wb-muted"),this.muteBtnEl.title="Mute"))}_detectTheme(){let t=document.documentElement,e=document.body,i=["dark","dark-mode","theme-dark"],r=["light","light-mode","theme-light"];for(let a of i)if(t.classList.contains(a)||e.classList.contains(a))return"dark";if(t.getAttribute("data-theme")==="dark"||e.getAttribute("data-theme")==="dark")return"dark";for(let a of r)if(t.classList.contains(a)||e.classList.contains(a))return"light";if(t.getAttribute("data-theme")==="light"||e.getAttribute("data-theme")==="light")return"light";try{let l=getComputedStyle(e).backgroundColor.match(/\d+/g);if(l&&l.length>=3){let c=(l[0]*299+l[1]*587+l[2]*114)/1e3;if(c>128)return"light";if(c<128)return"dark"}}catch{}return window.matchMedia?.("(prefers-color-scheme: light)").matches?"light":"dark"}_updateFavoriteUI(){if(!this.favBtnEl)return;let t=this.isFavorited();this.favBtnEl.innerHTML=t?n.heartFilled:n.heart,this.favBtnEl.classList.toggle("wb-fav-active",t)}_renderQueue(){B(this.queueBodyEl,this.queueCountEl,this.queue,this.currentIndex,{onSkipTo:t=>this.skipTo(t),onRemove:t=>this.removeFromQueue(t)})}_syncPageState(){let t=this.getCurrentTrack(),e=t?t.url:null;document.querySelectorAll("[data-wb-play]").forEach(i=>{let r=i.dataset.wbUrl||i.dataset.url,a=i.dataset.wbId||i.dataset.id||r,l=r&&r===e;i.classList.toggle("wb-current",l),i.classList.toggle("wb-playing",l&&this.isPlaying),i.classList.toggle("wb-favorited",this._favorites.has(a)),i.classList.toggle("wb-in-cart",this._cartItems.has(a))})}_seedFromAttributes(){let t=!1,e=!1;document.querySelectorAll("[data-wb-play]").forEach(i=>{let r=i.dataset.wbId||i.dataset.id||i.dataset.wbUrl||i.dataset.url;r&&(i.dataset.wbFavorited==="true"&&(this._favorites.add(r),t=!0),i.dataset.wbInCart==="true"&&(this._cartItems.add(r),e=!0))}),t&&p(this.config.storageKey,this._favorites)}_syncFavoriteAttributes(t,e){document.querySelectorAll("[data-wb-play]").forEach(i=>{(i.dataset.wbUrl||i.dataset.url)===t&&(i.dataset.wbFavorited=e?"true":"false",i.classList.toggle("wb-favorited",e))})}_syncCartAttributes(t,e){document.querySelectorAll("[data-wb-play]").forEach(i=>{(i.dataset.wbUrl||i.dataset.url)===t&&(i.dataset.wbInCart=e?"true":"false",i.classList.toggle("wb-in-cart",e))})}_saveState(){this.config.persist&&g(this.config.storageKey,{queue:this.queue,currentIndex:this.currentIndex,position:this._lastPosition||0,isPlaying:this.isPlaying})}_restoreState(){if(!this.config.persist)return;let t=w(this.config.storageKey);if(!t)return;this.queue=t.queue,this.currentIndex=t.currentIndex;let e=this.getCurrentTrack();if(e){if(this.show(),this._updateTrackDisplay(e),this._updateFavoriteUI(),this._updateNavButtons(),e.waveform&&(this.player.options.waveform=e.waveform),this.player.options.title=e.title||"",this.player.options.subtitle=e.artist||"",e.markers&&e.markers.length){let i=this.config.markerColor;this.player.options.markers=e.markers.map(r=>({...r,color:r.color||i})),this._activeMarkers=e.markers}else this.player.options.markers=[],this._activeMarkers=null;this._currentMarkerIndex=-1,this.player.load(e.url).then(()=>{if(this.player&&this.player.setVolume(this.isMuted?0:this.volume),t.isPlaying&&this.config.autoResume)try{let i=this.player.play();i&&typeof i.catch=="function"&&i.catch(()=>{this.isPlaying=!1,this._updatePlayButton(),this._syncPageState()})}catch{this.isPlaying=!1,this._updatePlayButton(),this._syncPageState()}t.position>0&&setTimeout(()=>{this.player&&(this.player.seekTo(t.position),this._lastPosition=t.position)},100)}).catch(()=>{}),this._renderQueue(),this._syncPageState()}}_restoreVolume(){let t=E(this.config.storageKey);t&&(this.volume=t.volume,this.isMuted=t.muted,this._volumeBeforeMute=t.volumeBeforeMute,this.player&&this.player.setVolume(this.isMuted?0:this.volume),this._updateVolumeUI())}_restoreFavorites(){this._favorites=_(this.config.storageKey)}};var C=new f;typeof window<"u"&&(window.WaveformBar=C);var W=C;})();
|
|
44
|
+
</div>`}}s.innerHTML=l,s.querySelectorAll(".wb-queue-item[data-qi]").forEach(o=>{o.addEventListener("click",c=>{c.target.closest(".wb-queue-remove")||r.onSkipTo&&r.onSkipTo(parseInt(o.dataset.qi))})}),s.querySelectorAll(".wb-queue-remove").forEach(o=>{o.addEventListener("click",c=>{c.stopPropagation(),r.onRemove&&r.onRemove(parseInt(o.dataset.qi))})})}var C={persist:!0,autoResume:!0,continuous:!0,repeat:"off",showRepeat:!0,showQueue:!0,showPrevNext:!0,showVolume:!0,showMute:!0,showMeta:!0,showTime:!0,showTrackLink:!0,maxMeta:3,defaultArtwork:null,theme:null,waveformStyle:"mirror",waveformHeight:32,barWidth:2,barSpacing:0,waveformColor:null,progressColor:null,markerColor:"rgba(255, 255, 255, 0.25)",volume:1,storageKey:"waveform-bar",actions:null,onPlay:null,onPause:null,onTrackChange:null,onQueueChange:null,onVolumeChange:null,onFavorite:null,onCart:null},f=class{constructor(){this.config=null,this.player=null,this.queue=[],this.currentIndex=-1,this.isPlaying=!1,this.isInitialized=!1,this.queueOpen=!1,this.volume=1,this.isMuted=!1,this._volumeBeforeMute=1,this._lastPosition=0,this._favorites=new Set,this._cartItems=new Set,this._observer=null,this._activeMarkers=null,this._currentMarkerIndex=-1,this.repeat="off",this.barEl=null,this.queueEl=null,this.waveformContainer=null,this.volumePopupEl=null,this.titleEl=null,this.artistEl=null,this.metaEl=null,this.playBtnEl=null,this.repeatBtnEl=null,this.queueBtnEl=null,this.queueBodyEl=null,this.queueCountEl=null,this.volumeSliderEl=null,this.muteBtnEl=null,this.favBtnEl=null,this.cartBtnEl=null,this.timeCurrentEl=null,this.timeTotalEl=null}init(t={}){return this.isInitialized&&this.destroy(),this.config={...C,...t},this.volume=this.config.volume,typeof window.WaveformPlayer>"u"?(console.error("WaveformBar: WaveformPlayer is required."),this):(this._createBar(),this._createQueue(),this._initPlayer(),this._bindTriggers(),this._observeDOM(),this.config.persist&&(this._restoreVolume(),this._restoreFavorites()),this._seedFromAttributes(),this.config.persist&&this._restoreState(),this.isInitialized=!0,this._beforeUnloadHandler=()=>this._saveState(),window.addEventListener("beforeunload",this._beforeUnloadHandler),this)}destroy(){return this.player&&(this.player.destroy(),this.player=null),this.barEl&&(this.barEl.remove(),this.barEl=null),this.queueEl&&(this.queueEl.remove(),this.queueEl=null),this._observer&&(this._observer.disconnect(),this._observer=null),this._beforeUnloadHandler&&(window.removeEventListener("beforeunload",this._beforeUnloadHandler),this._beforeUnloadHandler=null),document.querySelectorAll("[data-wb-play],[data-wb-queue]").forEach(t=>delete t._wbBound),document.querySelectorAll(".wb-current,.wb-playing").forEach(t=>t.classList.remove("wb-current","wb-playing")),this.queue=[],this.currentIndex=-1,this.isPlaying=!1,this.queueOpen=!1,this.isInitialized=!1,this.config=null,this}_createBar(){this.barEl=document.createElement("div"),this.barEl.className="waveform-bar";let t=this.config.theme||this._detectTheme();t==="light"&&this.barEl.classList.add("wb-light"),this._resolvedTheme=t,this.barEl.id="waveform-bar",this.barEl.innerHTML=k(this.config),document.body.appendChild(this.barEl),this.titleEl=this.barEl.querySelector(".wb-title"),this.artistEl=this.barEl.querySelector(".wb-artist"),this.metaEl=this.barEl.querySelector(".wb-meta"),this.playBtnEl=this.barEl.querySelector(".wb-play"),this.waveformContainer=this.barEl.querySelector(".wb-waveform-container"),this.queueBtnEl=this.barEl.querySelector(".wb-queue-btn"),this.muteBtnEl=this.barEl.querySelector(".wb-mute"),this.volumeSliderEl=this.barEl.querySelector(".wb-volume-slider"),this.favBtnEl=this.barEl.querySelector(".wb-fav"),this.cartBtnEl=this.barEl.querySelector(".wb-cart"),this.timeCurrentEl=this.barEl.querySelector(".wb-time-current"),this.timeTotalEl=this.barEl.querySelector(".wb-time-total"),this.playBtnEl.addEventListener("click",()=>this.togglePlay());let e=this.barEl.querySelector(".wb-prev"),i=this.barEl.querySelector(".wb-next");e&&e.addEventListener("click",()=>this.previous()),i&&i.addEventListener("click",()=>this.next()),this.repeatBtnEl=this.barEl.querySelector(".wb-repeat"),this.repeatBtnEl&&(this.repeat=this.config.repeat||"off",this._updateRepeatButton(),this.repeatBtnEl.addEventListener("click",()=>this.cycleRepeat())),this.queueBtnEl&&this.queueBtnEl.addEventListener("click",()=>this.toggleQueuePanel()),this.volumePopupEl=this.barEl.querySelector(".wb-volume-popup");let r=this.barEl.querySelector(".wb-volume");if(this.muteBtnEl&&this.muteBtnEl.addEventListener("click",a=>{a.stopPropagation(),this.toggleMute()}),r&&this.volumePopupEl){let a;r.addEventListener("mouseenter",()=>{clearTimeout(a),this.openVolumePopup()}),r.addEventListener("mouseleave",()=>{a=setTimeout(()=>this.closeVolumePopup(),300)})}this.volumeSliderEl&&this.volumeSliderEl.addEventListener("input",a=>{a.stopPropagation(),this.setVolume(parseInt(a.target.value)/100)}),document.addEventListener("click",a=>{this.volumePopupEl?.classList.contains("wb-volume-open")&&!this.barEl.querySelector(".wb-volume")?.contains(a.target)&&this.closeVolumePopup()}),this.favBtnEl&&this.favBtnEl.addEventListener("click",()=>this.toggleFavorite()),this.cartBtnEl&&this.cartBtnEl.addEventListener("click",()=>this.addToCart()),this.config.showTrackLink&&this.barEl.querySelector(".wb-track").addEventListener("click",()=>{let a=this.getCurrentTrack();a&&a.link&&(window.location.href=a.link)})}_createQueue(){this.config.showQueue&&(this.queueEl=q(),this._resolvedTheme==="light"&&this.queueEl.classList.add("wb-light"),document.body.appendChild(this.queueEl),this.queueBodyEl=this.queueEl.querySelector(".wb-queue-body"),this.queueCountEl=this.queueEl.querySelector(".wb-queue-count"),this.queueEl.querySelector(".wb-queue-clear").addEventListener("click",()=>this.clearQueue()),document.addEventListener("click",t=>{this.queueOpen&&!this.queueEl.contains(t.target)&&!this.queueBtnEl.contains(t.target)&&this.closeQueuePanel()}))}_initPlayer(){let t={showControls:!1,showInfo:!1,waveformStyle:this.config.waveformStyle,height:this.config.waveformHeight,barWidth:this.config.barWidth,barSpacing:this.config.barSpacing,singlePlay:!1,onPlay:()=>{this.isPlaying=!0,this._updatePlayButton(),this._syncPageState(),this._pumpExternalPlayState(!0);let e=this.getCurrentTrack();this._emit("play",{track:e}),this.config.onPlay&&this.config.onPlay(e)},onPause:()=>{this.isPlaying=!1,this._updatePlayButton(),this._syncPageState(),this._pumpExternalPlayState(!1),this._saveState();let e=this.getCurrentTrack();this._emit("pause",{track:e}),this.config.onPause&&this.config.onPause(e)},onEnd:()=>{if(this.isPlaying=!1,this._updatePlayButton(),this._syncPageState(),this._pumpExternalPlayState(!1),this.timeCurrentEl&&(this.timeCurrentEl.textContent="0:00"),this.repeat==="one"){this.player&&(this.player.seekTo(0),this.player.play().catch(()=>{}));return}this.config.continuous&&this.currentIndex<this.queue.length-1?(this.currentIndex++,this._loadCurrentTrack()):this.repeat==="all"&&this.queue.length>0&&(this.currentIndex=0,this._loadCurrentTrack())},onTimeUpdate:(e,i)=>{this._lastPosition=e,this.timeCurrentEl&&(this.timeCurrentEl.textContent=v(e)),this.timeTotalEl&&(this.timeTotalEl.textContent=v(i)),this._pumpExternalProgress(e,i),(!this._lastSaveTime||e-this._lastSaveTime>2)&&(this._lastSaveTime=e,this._saveState()),this._activeMarkers&&this._checkMarkerBoundary(e)},onLoad:null};this.config.waveformColor&&(t.waveformColor=this.config.waveformColor),this.config.progressColor&&(t.progressColor=this.config.progressColor),this.player=new window.WaveformPlayer(this.waveformContainer,t),this.player.setVolume(this.volume)}_bindTriggers(){document.querySelectorAll("[data-wb-play]").forEach(t=>{t._wbBound||(t._wbBound=!0,t.addEventListener("click",e=>{e.preventDefault();let i=p(t);i&&this.play(i)}))}),document.querySelectorAll("[data-wb-queue]").forEach(t=>{t._wbBound||(t._wbBound=!0,t.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation();let i=p(t);i&&this.addToQueue(i)}))}),this._attachExternalPlayers()}_attachExternalPlayers(){this._externalListenersBound||(this._externalListenersBound=!0,document.addEventListener("waveformplayer:request-play",r=>{let a=r.detail;!a||!a.url||(r.preventDefault(),this.play(a))}),document.addEventListener("waveformplayer:request-pause",r=>{let a=r.detail;if(!a||!a.url)return;let l=this.getCurrentTrack();l&&l.url===a.url&&(r.preventDefault(),this.isPlaying&&this.togglePlay())}),document.addEventListener("waveformplayer:request-seek",r=>{let a=r.detail;if(!a||!a.url||typeof a.percent!="number")return;let l=this.getCurrentTrack();l&&l.url===a.url&&this.player&&this.player.audio&&(r.preventDefault(),this.player.seekToPercent(a.percent))}));let t=this._externalPlayers||new Map;this._externalPlayers=new Map;let e=window.WaveformPlayer;if(!e||!e.instances)return;let i=[];if(document.querySelectorAll('[data-waveform-player][data-audio-mode="external"]').forEach(r=>{let a=e.instances.get(r.id);if(!a||!a.options||!a.options.url)return;let l=a.options.url;this._externalPlayers.has(l)||this._externalPlayers.set(l,new Set),this._externalPlayers.get(l).add(a),t.get(l)&&t.get(l).has(a)||i.push({inst:a,url:l})}),i.length){let r=this.getCurrentTrack(),a=r?r.url:null;i.forEach(({inst:l,url:u})=>{let o=u===a;typeof l.setPlayingState=="function"&&l.setPlayingState(o&&this.isPlaying),o&&typeof l.setProgress=="function"&&this.player&&this.player.audio&&l.setProgress(this.player.audio.currentTime||0,this.player.audio.duration||0)})}}_pumpExternalPlayState(t){if(!this._externalPlayers||this._externalPlayers.size===0)return;let e=this.getCurrentTrack(),i=e?e.url:null;this._externalPlayers.forEach((r,a)=>{let l=a===i;r.forEach(u=>{typeof u.setPlayingState=="function"&&u.setPlayingState(l&&t)})})}_pumpExternalProgress(t,e){if(!this._externalPlayers||this._externalPlayers.size===0)return;let i=this.getCurrentTrack();if(!i)return;let r=this._externalPlayers.get(i.url);r&&r.forEach(a=>{typeof a.setProgress=="function"&&a.setProgress(t,e)})}_observeDOM(){typeof MutationObserver>"u"||(this._observer=new MutationObserver(()=>{this._bindTriggers(),this._syncPageState()}),this._observer.observe(document.body,{childList:!0,subtree:!0}))}play(t){let e=typeof t=="string"?{url:t,id:t,title:d(t)}:t;if(!e||!e.url)return this;let i=this.getCurrentTrack();if(i&&i.url===e.url)return this.togglePlay(),this;let r=this.queue.findIndex(a=>a.url===e.url);if(r>=0)this.queue[r]={...this.queue[r],...e},this.currentIndex=r;else{let a=this.currentIndex+1;this.queue.splice(a,0,e),this.currentIndex=a}return this._loadCurrentTrack(),this}addToQueue(t){let e=typeof t=="string"?{url:t,id:t,title:d(t)}:t;return!e||!e.url?this:this.queue.find(i=>i.url===e.url)?this:(this.queue.push(e),this._renderQueue(),this._saveState(),this._updateNavButtons(),this.currentIndex===-1&&(this.currentIndex=0,this._loadCurrentTrack()),this.config.onQueueChange&&this.config.onQueueChange(this.queue,this.currentIndex),this)}togglePlay(){return this.player?(this.isPlaying?this.player.pause():this.player.play(),this):this}pause(){return this.player&&this.isPlaying&&this.player.pause(),this}next(){return this.currentIndex<this.queue.length-1?(this.currentIndex++,this._loadCurrentTrack()):this.repeat==="all"&&this.queue.length>0&&(this.currentIndex=0,this._loadCurrentTrack()),this}previous(){return this.player&&this.player.audio&&this.player.audio.currentTime>3?(this.player.seekTo(0),this):(this.currentIndex>0?(this.currentIndex--,this._loadCurrentTrack()):this.repeat==="all"&&this.queue.length>0&&(this.currentIndex=this.queue.length-1,this._loadCurrentTrack()),this)}skipTo(t){return t<0||t>=this.queue.length?this:t===this.currentIndex?(this.togglePlay(),this):(this.currentIndex=t,this._loadCurrentTrack(),this)}seekToMarker(t){if(!this._activeMarkers||t<0||t>=this._activeMarkers.length)return this;let e=this._activeMarkers[t];return this.player&&(this.player.seekTo(e.time),this.isPlaying||this.togglePlay()),this}seekToMarkerByLabel(t){if(!this._activeMarkers)return this;let e=this._activeMarkers.findIndex(i=>(i.label||i.title||"").toLowerCase()===t.toLowerCase());return e>=0&&this.seekToMarker(e),this}setVolume(t){return this.volume=Math.max(0,Math.min(1,t)),this.isMuted=this.volume===0,this.player&&this.player.setVolume(this.volume),this._updateVolumeUI(),y(this.config.storageKey,this.volume,this.isMuted,this._volumeBeforeMute),this._emit("volumechange",{volume:this.volume}),this.config.onVolumeChange&&this.config.onVolumeChange(this.volume),this}getVolume(){return this.volume}toggleMute(){return this.isMuted?this.setVolume(this._volumeBeforeMute||1):(this._volumeBeforeMute=this.volume,this.isMuted=!0,this.player&&this.player.setVolume(0),this._updateVolumeUI()),this}toggleFavorite(){let t=this.getCurrentTrack();if(!t)return this;let e=t.id||t.url,i=this._favorites.has(e);return i?this._favorites.delete(e):this._favorites.add(e),this._updateFavoriteUI(),this._syncFavoriteAttributes(t.url,!i),m(this.config.storageKey,this._favorites),this._emit("favorite",{track:t,favorited:!i}),this.config.onFavorite&&this.config.onFavorite(t,!i),this.config.actions?.favorite&&b(this.config.actions.favorite,{action:"favorite",id:e,url:t.url,title:t.title,favorited:!i}),this}addToCart(){let t=this.getCurrentTrack();if(!t)return this;let e=t.id||t.url;return this._cartItems.add(e),this.cartBtnEl&&(this.cartBtnEl.classList.add("wb-action-done"),setTimeout(()=>this.cartBtnEl.classList.remove("wb-action-done"),1500)),this._syncCartAttributes(t.url,!0),this._emit("cart",{track:t}),this.config.onCart&&this.config.onCart(t),this.config.actions?.cart&&b(this.config.actions.cart,{action:"cart",id:e,url:t.url,title:t.title}),this}isFavorited(t){if(!t){let e=this.getCurrentTrack();t=e?e.id||e.url:null}return t?this._favorites.has(t):!1}isInCart(t){if(!t){let e=this.getCurrentTrack();t=e?e.id||e.url:null}return t?this._cartItems.has(t):!1}removeFromQueue(t){return t<0||t>=this.queue.length||t===this.currentIndex?this:(this.queue.splice(t,1),t<this.currentIndex&&this.currentIndex--,this._renderQueue(),this._saveState(),this._updateNavButtons(),this._emit("queuechange",{queue:this.queue,currentIndex:this.currentIndex}),this.config.onQueueChange&&this.config.onQueueChange(this.queue,this.currentIndex),this)}clearQueue(){let t=this.getCurrentTrack();return this.queue=t?[t]:[],this.currentIndex=t?0:-1,this._renderQueue(),this._saveState(),this._updateNavButtons(),this._emit("queuechange",{queue:this.queue,currentIndex:this.currentIndex}),this.config.onQueueChange&&this.config.onQueueChange(this.queue,this.currentIndex),this}getCurrentTrack(){return this.currentIndex>=0&&this.currentIndex<this.queue.length?this.queue[this.currentIndex]:null}getQueue(){return[...this.queue]}getCurrentIndex(){return this.currentIndex}isCurrentlyPlaying(t){let e=this.getCurrentTrack();return this.isPlaying&&e&&e.url===t}isCurrentTrack(t){let e=this.getCurrentTrack();return e&&e.url===t}getPlayer(){return this.player}_emit(t,e={}){this.barEl&&this.barEl.dispatchEvent(new CustomEvent("waveformbar:"+t,{bubbles:!0,detail:e}))}show(){return this.barEl&&this.barEl.classList.add("wb-active"),this}hide(){return this.barEl&&this.barEl.classList.remove("wb-active"),this.closeQueuePanel(),this.closeVolumePopup(),this}toggleQueuePanel(){return this.queueOpen?this.closeQueuePanel():this.openQueuePanel()}openQueuePanel(){if(!this.queueEl)return this;if(this.queueOpen=!0,this.closeVolumePopup(),this.queueBtnEl){let t=this.queueBtnEl.getBoundingClientRect();this.queueEl.style.right=window.innerWidth-t.right+"px"}return this.queueEl.classList.add("wb-queue-open"),this.queueBtnEl&&this.queueBtnEl.classList.add("wb-active"),this._renderQueue(),this}closeQueuePanel(){return this.queueEl?(this.queueOpen=!1,this.queueEl.classList.remove("wb-queue-open"),this.queueBtnEl&&this.queueBtnEl.classList.remove("wb-active"),this):this}toggleVolumePopup(){return this.volumePopupEl?.classList.contains("wb-volume-open")?this.closeVolumePopup():this.openVolumePopup(),this}openVolumePopup(){return this.volumePopupEl?(this.closeQueuePanel(),this.volumePopupEl.classList.add("wb-volume-open"),this.muteBtnEl&&this.muteBtnEl.classList.add("wb-active"),this):this}closeVolumePopup(){return this.volumePopupEl?(this.volumePopupEl.classList.remove("wb-volume-open"),this.muteBtnEl&&this.muteBtnEl.classList.remove("wb-active"),this):this}_loadCurrentTrack(){let t=this.getCurrentTrack();if(!t||!this.player)return;this._pumpExternalPlayState(!1),this.show(),this._updateTrackDisplay(t),this._updateFavoriteUI();let e={artwork:t.artwork,album:t.album};if(t.waveform&&(e.waveform=t.waveform),t.markers&&t.markers.length){let i=this.config.markerColor;e.markers=t.markers.map(r=>({...r,color:r.color||i}))}else e.markers=[];this.player.loadTrack(t.url,t.title,t.artist,e),this._activeMarkers=t.markers&&t.markers.length?t.markers:null,this._currentMarkerIndex=-1,this.player&&this.player.setVolume(this.isMuted?0:this.volume),this._renderQueue(),this._syncPageState(),this._saveState(),this._updateNavButtons(),this._emit("trackchange",{track:t,index:this.currentIndex}),this.config.onTrackChange&&this.config.onTrackChange(t,this.currentIndex)}_updateTrackDisplay(t){this.titleEl&&this._setScrollText(this.titleEl,t.title||"Untitled"),this.artistEl&&this._setScrollText(this.artistEl,t.artist||"");let e=this.barEl.querySelector(".wb-artwork");if(e){let r=t.artwork||this.config.defaultArtwork;e.innerHTML=r?`<img src="${h(r)}" alt="${h(t.title)}" />`:n.music}this.metaEl&&this.config.showMeta&&this._renderMeta(t);let i=this.barEl.querySelector(".wb-track");i&&(i.style.cursor=t.link?"pointer":"default"),this.timeCurrentEl&&(this.timeCurrentEl.textContent="0:00"),this.timeTotalEl&&(this.timeTotalEl.textContent="0:00")}_setScrollText(t,e){t.classList.remove("wb-scrolling"),t.textContent=e,requestAnimationFrame(()=>{if(t.scrollWidth>t.clientWidth){let i=t.scrollWidth-t.clientWidth,r=Math.max(4,i/20);t.innerHTML=`<span class="wb-scroll-inner">${h(e)}</span>`,t.style.setProperty("--wb-scroll-distance",`-${i+48}px`),t.style.setProperty("--wb-scroll-duration",`${r}s`),t.classList.add("wb-scrolling")}})}_renderMeta(t){if(!this.metaEl)return;let e=[];if(t.bpm&&e.push({label:t.bpm+" BPM",type:"bpm"}),t.key&&e.push({label:t.key,type:"key"}),t.duration&&e.push({label:t.duration,type:"duration"}),t.meta)for(let[r,a]of Object.entries(t.meta))a&&e.length<this.config.maxMeta&&e.push({label:String(a),type:r});let i=e.slice(0,this.config.maxMeta);this.metaEl.style.display=i.length?"flex":"none",this.metaEl.innerHTML=i.map(r=>`<span class="wb-tag wb-tag-${h(r.type)}">${h(r.label)}</span>`).join("")}_updatePlayButton(){if(!this.playBtnEl)return;let t=this.playBtnEl.querySelector(".wb-icon-play"),e=this.playBtnEl.querySelector(".wb-icon-pause");t&&(t.style.display=this.isPlaying?"none":"block"),e&&(e.style.display=this.isPlaying?"block":"none"),this.playBtnEl.title=this.isPlaying?"Pause":"Play"}_updateNavButtons(){let t=this.barEl?.querySelector(".wb-prev"),e=this.barEl?.querySelector(".wb-next");this.repeat==="all"?(t&&t.classList.remove("wb-disabled"),e&&e.classList.remove("wb-disabled")):(t&&t.classList.toggle("wb-disabled",this.currentIndex<=0),e&&e.classList.toggle("wb-disabled",this.currentIndex>=this.queue.length-1))}cycleRepeat(){let t=["off","all","one"],e=t.indexOf(this.repeat);return this.repeat=t[(e+1)%t.length],this._updateRepeatButton(),this._updateNavButtons(),this._emit("repeatchange",{mode:this.repeat}),this}setRepeat(t){return["off","all","one"].includes(t)&&(this.repeat=t,this._updateRepeatButton(),this._updateNavButtons(),this._emit("repeatchange",{mode:this.repeat})),this}_updateRepeatButton(){if(!this.repeatBtnEl)return;let t={off:n.repeatOff,all:n.repeatAll,one:n.repeatOne},e={off:"Repeat: Off",all:"Repeat: All",one:"Repeat: One"};this.repeatBtnEl.innerHTML=t[this.repeat],this.repeatBtnEl.title=e[this.repeat],this.repeatBtnEl.classList.toggle("wb-repeat-active",this.repeat!=="off")}_checkMarkerBoundary(t){if(!this._activeMarkers)return;let e=-1;for(let l=this._activeMarkers.length-1;l>=0;l--)if(t>=this._activeMarkers[l].time){e=l;break}if(e===this._currentMarkerIndex||(this._currentMarkerIndex=e,e<0))return;let i=this._activeMarkers[e],r=this.getCurrentTrack();i.title&&this.titleEl&&this._setScrollText(this.titleEl,i.title),i.artist&&this.artistEl&&this._setScrollText(this.artistEl,i.artist);let a=this.waveformContainer?.querySelectorAll(".waveform-marker");if(a&&a.forEach((l,u)=>l.classList.toggle("wb-marker-active",u===e)),i.artwork){let l=this.barEl.querySelector(".wb-artwork");l&&(l.innerHTML=`<img src="${i.artwork}" alt="${i.title||""}" />`)}if(this.metaEl&&(i.bpm||i.key)){let l={...r||{},bpm:i.bpm||"",key:i.key||""};this._renderMeta(l)}this._emit("markerchange",{marker:i,index:e,track:r})}_updateVolumeUI(){this.volumeSliderEl&&(this.volumeSliderEl.value=this.isMuted?0:Math.round(this.volume*100)),this.muteBtnEl&&(this.isMuted||this.volume===0?(this.muteBtnEl.innerHTML=n.volMute,this.muteBtnEl.classList.add("wb-muted"),this.muteBtnEl.title="Unmute"):this.volume<.5?(this.muteBtnEl.innerHTML=n.volLow,this.muteBtnEl.classList.remove("wb-muted"),this.muteBtnEl.title="Mute"):(this.muteBtnEl.innerHTML=n.volHigh,this.muteBtnEl.classList.remove("wb-muted"),this.muteBtnEl.title="Mute"))}_detectTheme(){let t=document.documentElement,e=document.body,i=["dark","dark-mode","theme-dark"],r=["light","light-mode","theme-light"];for(let a of i)if(t.classList.contains(a)||e.classList.contains(a))return"dark";if(t.getAttribute("data-theme")==="dark"||e.getAttribute("data-theme")==="dark")return"dark";for(let a of r)if(t.classList.contains(a)||e.classList.contains(a))return"light";if(t.getAttribute("data-theme")==="light"||e.getAttribute("data-theme")==="light")return"light";try{let l=getComputedStyle(e).backgroundColor.match(/\d+/g);if(l&&l.length>=3){let u=(l[0]*299+l[1]*587+l[2]*114)/1e3;if(u>128)return"light";if(u<128)return"dark"}}catch{}return window.matchMedia?.("(prefers-color-scheme: light)").matches?"light":"dark"}_updateFavoriteUI(){if(!this.favBtnEl)return;let t=this.isFavorited();this.favBtnEl.innerHTML=t?n.heartFilled:n.heart,this.favBtnEl.classList.toggle("wb-fav-active",t)}_renderQueue(){x(this.queueBodyEl,this.queueCountEl,this.queue,this.currentIndex,{onSkipTo:t=>this.skipTo(t),onRemove:t=>this.removeFromQueue(t)})}_syncPageState(){let t=this.getCurrentTrack(),e=t?t.url:null;document.querySelectorAll("[data-wb-play]").forEach(i=>{let r=i.dataset.wbUrl||i.dataset.url,a=i.dataset.wbId||i.dataset.id||r,l=r&&r===e;i.classList.toggle("wb-current",l),i.classList.toggle("wb-playing",l&&this.isPlaying),i.classList.toggle("wb-favorited",this._favorites.has(a)),i.classList.toggle("wb-in-cart",this._cartItems.has(a))})}_seedFromAttributes(){let t=!1,e=!1;document.querySelectorAll("[data-wb-play]").forEach(i=>{let r=i.dataset.wbId||i.dataset.id||i.dataset.wbUrl||i.dataset.url;r&&(i.dataset.wbFavorited==="true"&&(this._favorites.add(r),t=!0),i.dataset.wbInCart==="true"&&(this._cartItems.add(r),e=!0))}),t&&m(this.config.storageKey,this._favorites)}_syncFavoriteAttributes(t,e){document.querySelectorAll("[data-wb-play]").forEach(i=>{(i.dataset.wbUrl||i.dataset.url)===t&&(i.dataset.wbFavorited=e?"true":"false",i.classList.toggle("wb-favorited",e))})}_syncCartAttributes(t,e){document.querySelectorAll("[data-wb-play]").forEach(i=>{(i.dataset.wbUrl||i.dataset.url)===t&&(i.dataset.wbInCart=e?"true":"false",i.classList.toggle("wb-in-cart",e))})}_saveState(){this.config.persist&&g(this.config.storageKey,{queue:this.queue,currentIndex:this.currentIndex,position:this._lastPosition||0,isPlaying:this.isPlaying})}_restoreState(){if(!this.config.persist)return;let t=w(this.config.storageKey);if(!t)return;this.queue=t.queue,this.currentIndex=t.currentIndex;let e=this.getCurrentTrack();if(e){if(this.show(),this._updateTrackDisplay(e),this._updateFavoriteUI(),this._updateNavButtons(),e.waveform&&(this.player.options.waveform=e.waveform),this.player.options.title=e.title||"",this.player.options.subtitle=e.artist||"",e.markers&&e.markers.length){let i=this.config.markerColor;this.player.options.markers=e.markers.map(r=>({...r,color:r.color||i})),this._activeMarkers=e.markers}else this.player.options.markers=[],this._activeMarkers=null;this._currentMarkerIndex=-1,this.player.load(e.url).then(()=>{if(this.player&&this.player.setVolume(this.isMuted?0:this.volume),t.isPlaying&&this.config.autoResume)try{let i=this.player.play();i&&typeof i.catch=="function"&&i.catch(()=>{this.isPlaying=!1,this._updatePlayButton(),this._syncPageState()})}catch{this.isPlaying=!1,this._updatePlayButton(),this._syncPageState()}t.position>0&&setTimeout(()=>{this.player&&(this.player.seekTo(t.position),this._lastPosition=t.position)},100)}).catch(()=>{}),this._renderQueue(),this._syncPageState()}}_restoreVolume(){let t=E(this.config.storageKey);t&&(this.volume=t.volume,this.isMuted=t.muted,this._volumeBeforeMute=t.volumeBeforeMute,this.player&&this.player.setVolume(this.isMuted?0:this.volume),this._updateVolumeUI())}_restoreFavorites(){this._favorites=_(this.config.storageKey)}};var P=new f;typeof window<"u"&&(window.WaveformBar=P);var W=P;})();
|
|
45
45
|
/**
|
|
46
46
|
* WaveformBar v1.0.0
|
|
47
47
|
* Persistent bottom audio player bar for WaveformPlayer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arraypress/waveform-bar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Persistent bottom audio player bar for WaveformPlayer - queue management, page persistence, and seamless playback.",
|
|
5
5
|
"main": "dist/waveform-bar.js",
|
|
6
6
|
"module": "dist/waveform-bar.esm.js",
|
package/src/js/core.js
CHANGED
|
@@ -311,6 +311,7 @@ export class WaveformBar {
|
|
|
311
311
|
this.isPlaying = true;
|
|
312
312
|
this._updatePlayButton();
|
|
313
313
|
this._syncPageState();
|
|
314
|
+
this._pumpExternalPlayState(true);
|
|
314
315
|
const track = this.getCurrentTrack();
|
|
315
316
|
this._emit('play', {track});
|
|
316
317
|
if (this.config.onPlay) this.config.onPlay(track);
|
|
@@ -319,6 +320,7 @@ export class WaveformBar {
|
|
|
319
320
|
this.isPlaying = false;
|
|
320
321
|
this._updatePlayButton();
|
|
321
322
|
this._syncPageState();
|
|
323
|
+
this._pumpExternalPlayState(false);
|
|
322
324
|
this._saveState();
|
|
323
325
|
const track = this.getCurrentTrack();
|
|
324
326
|
this._emit('pause', {track});
|
|
@@ -328,6 +330,7 @@ export class WaveformBar {
|
|
|
328
330
|
this.isPlaying = false;
|
|
329
331
|
this._updatePlayButton();
|
|
330
332
|
this._syncPageState();
|
|
333
|
+
this._pumpExternalPlayState(false);
|
|
331
334
|
|
|
332
335
|
// Reset time display
|
|
333
336
|
if (this.timeCurrentEl) this.timeCurrentEl.textContent = '0:00';
|
|
@@ -358,6 +361,11 @@ export class WaveformBar {
|
|
|
358
361
|
if (this.timeCurrentEl) this.timeCurrentEl.textContent = formatTime(currentTime);
|
|
359
362
|
if (this.timeTotalEl) this.timeTotalEl.textContent = formatTime(duration);
|
|
360
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
|
+
|
|
361
369
|
// Save state periodically during playback
|
|
362
370
|
if (!this._lastSaveTime || currentTime - this._lastSaveTime > 2) {
|
|
363
371
|
this._lastSaveTime = currentTime;
|
|
@@ -404,6 +412,149 @@ export class WaveformBar {
|
|
|
404
412
|
if (track) this.addToQueue(track);
|
|
405
413
|
});
|
|
406
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
|
+
const previous = this._externalPlayers || new Map();
|
|
477
|
+
this._externalPlayers = new Map();
|
|
478
|
+
const WP = window.WaveformPlayer;
|
|
479
|
+
if (!WP || !WP.instances) return;
|
|
480
|
+
|
|
481
|
+
const newlyDiscovered = [];
|
|
482
|
+
document.querySelectorAll('[data-waveform-player][data-audio-mode="external"]').forEach((el) => {
|
|
483
|
+
const inst = WP.instances.get(el.id);
|
|
484
|
+
if (!inst || !inst.options || !inst.options.url) return;
|
|
485
|
+
const url = inst.options.url;
|
|
486
|
+
if (!this._externalPlayers.has(url)) this._externalPlayers.set(url, new Set());
|
|
487
|
+
this._externalPlayers.get(url).add(inst);
|
|
488
|
+
// Track players that weren't in the previous map — those need
|
|
489
|
+
// initial state seeded so cross-page navigation (bar already
|
|
490
|
+
// playing when an inline player mounts) syncs correctly.
|
|
491
|
+
const wasKnown = previous.get(url) && previous.get(url).has(inst);
|
|
492
|
+
if (!wasKnown) newlyDiscovered.push({inst, url});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Seed newly-discovered players with current bar state. Without
|
|
496
|
+
// this, an inline player that mounts AFTER the bar is already
|
|
497
|
+
// playing would show "paused" until the next state event fires
|
|
498
|
+
// (which might never happen if the user just sits on the page).
|
|
499
|
+
if (newlyDiscovered.length) {
|
|
500
|
+
const current = this.getCurrentTrack();
|
|
501
|
+
const currentUrl = current ? current.url : null;
|
|
502
|
+
newlyDiscovered.forEach(({inst, url}) => {
|
|
503
|
+
const isCurrent = url === currentUrl;
|
|
504
|
+
if (typeof inst.setPlayingState === 'function') {
|
|
505
|
+
inst.setPlayingState(isCurrent && this.isPlaying);
|
|
506
|
+
}
|
|
507
|
+
if (isCurrent && typeof inst.setProgress === 'function' && this.player && this.player.audio) {
|
|
508
|
+
inst.setProgress(this.player.audio.currentTime || 0, this.player.audio.duration || 0);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Push playing-state into every external-mode player whose URL
|
|
516
|
+
* matches the currently playing track. Other URLs get set to
|
|
517
|
+
* false (paused) — covers the case where the bar switched tracks
|
|
518
|
+
* and the previously-current external player should stop showing
|
|
519
|
+
* its play indicator.
|
|
520
|
+
*
|
|
521
|
+
* @private
|
|
522
|
+
* @param {boolean} playing
|
|
523
|
+
*/
|
|
524
|
+
_pumpExternalPlayState(playing) {
|
|
525
|
+
if (!this._externalPlayers || this._externalPlayers.size === 0) return;
|
|
526
|
+
const current = this.getCurrentTrack();
|
|
527
|
+
const currentUrl = current ? current.url : null;
|
|
528
|
+
this._externalPlayers.forEach((set, url) => {
|
|
529
|
+
const isCurrent = url === currentUrl;
|
|
530
|
+
set.forEach((player) => {
|
|
531
|
+
if (typeof player.setPlayingState === 'function') {
|
|
532
|
+
player.setPlayingState(isCurrent && playing);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Push progress (currentTime + duration) into the external-mode
|
|
540
|
+
* player(s) tracking the current URL. Called on every timeupdate
|
|
541
|
+
* tick of the internal player.
|
|
542
|
+
*
|
|
543
|
+
* @private
|
|
544
|
+
* @param {number} currentTime
|
|
545
|
+
* @param {number} duration
|
|
546
|
+
*/
|
|
547
|
+
_pumpExternalProgress(currentTime, duration) {
|
|
548
|
+
if (!this._externalPlayers || this._externalPlayers.size === 0) return;
|
|
549
|
+
const current = this.getCurrentTrack();
|
|
550
|
+
if (!current) return;
|
|
551
|
+
const set = this._externalPlayers.get(current.url);
|
|
552
|
+
if (!set) return;
|
|
553
|
+
set.forEach((player) => {
|
|
554
|
+
if (typeof player.setProgress === 'function') {
|
|
555
|
+
player.setProgress(currentTime, duration);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
407
558
|
}
|
|
408
559
|
|
|
409
560
|
_observeDOM() {
|
|
@@ -826,6 +977,12 @@ export class WaveformBar {
|
|
|
826
977
|
const track = this.getCurrentTrack();
|
|
827
978
|
if (!track || !this.player) return;
|
|
828
979
|
|
|
980
|
+
// Reset any previously-current external player so its UI flips
|
|
981
|
+
// back to "paused" while the new track loads. The new track's
|
|
982
|
+
// onPlay callback will set the matching external to playing
|
|
983
|
+
// once playback actually starts.
|
|
984
|
+
this._pumpExternalPlayState(false);
|
|
985
|
+
|
|
829
986
|
this.show();
|
|
830
987
|
this._updateTrackDisplay(track);
|
|
831
988
|
this._updateFavoriteUI();
|