@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 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`)
@@ -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();
@@ -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();
@@ -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 u(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 m(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 p(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">
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 B(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}">
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">${u(o.title)}</div>
29
- <div class="wb-queue-item-artist">${u(o.artist)}</div>
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 c=!1;for(let o=i+1;o<e.length;o++){c||(l+='<div class="wb-queue-label">Next Up</div>',c=!0);let h=e[o];l+=`<div class="wb-queue-item" data-qi="${o}">
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">${u(h.title)}</div>
35
- <div class="wb-queue-item-artist">${u(h.artist)}</div>
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 h=e[o];l+=`<div class="wb-queue-item wb-queue-played" data-qi="${o}">
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">${u(h.title)}</div>
42
- <div class="wb-queue-item-artist">${u(h.artist)}</div>
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.2.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();