diff --git a/README.md b/README.md
index 79a39cc..9c69c8f 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ Load from a CDN and it auto-initializes every `[data-waveform-player]` on the pa
-
+
```
Or drive it from JavaScript:
@@ -35,7 +35,7 @@ Or drive it from JavaScript:
```js
import WaveformPlayer from '@arraypress/waveform-player';
-new WaveformPlayer('#player', { url: 'track.mp3', title: 'My Song', artist: 'The Artist' });
+new WaveformPlayer('#player', { url: 'track.mp3', title: 'My Song', artist: 'The Artist', album: 'The Album', showAlbum: true });
```
### Initializing only what you control
diff --git a/dist/waveform-player.cjs b/dist/waveform-player.cjs
index e71aa25..5ecac33 100644
--- a/dist/waveform-player.cjs
+++ b/dist/waveform-player.cjs
@@ -153,6 +153,7 @@ function parseDataAttributes(element) {
setBool("autoplay");
setBool("showControls");
setBool("showInfo");
+ setBool("showAlbum");
setBool("showTime");
setBool("showHoverTime");
setBool("seekHandle");
@@ -834,6 +835,7 @@ var DEFAULT_OPTIONS = {
autoplay: false,
showControls: true,
showInfo: true,
+ showAlbum: false,
showTime: true,
showHoverTime: false,
// Show a draggable circle handle + hover brightness-lift on the SEEKBAR
@@ -946,6 +948,7 @@ var BOOLEANS = [
"autoplay",
"showControls",
"showInfo",
+ "showAlbum",
"showTime",
"showHoverTime",
"seekHandle",
@@ -1186,7 +1189,7 @@ var WaveformPlayer = class _WaveformPlayer {
*
* Clears the container, resolves button alignment (`auto` → `bottom` for
* the `bars` style, `center` otherwise), and conditionally renders the play
- * button, info row (artwork/title/artist), BPM badge, playback-speed
+ * button, info row (artwork/title/artist/album), BPM badge, playback-speed
* menu, and time display based on the relevant `show*` options. Caches the
* canvas, controls, and text elements onto `this`, then sizes the canvas.
* @private
@@ -1239,6 +1242,7 @@ var WaveformPlayer = class _WaveformPlayer {
${this.options.artist ? `${escapeHtml(this.options.artist)} ` : ""}
+ ${this.options.showAlbum && this.options.album ? `${escapeHtml(this.options.album)} ` : ""}
${this.options.showBPM ? `
@@ -1291,6 +1295,7 @@ var WaveformPlayer = class _WaveformPlayer {
this.ctx = this.canvas.getContext("2d");
this.titleEl = this.container.querySelector(".waveform-title");
this.artistEl = this.container.querySelector(".waveform-artist");
+ this.albumEl = this.container.querySelector(".waveform-album");
this.artworkEl = this.container.querySelector(".waveform-artwork, .waveform-btn-artwork");
this.bindArtworkFallback(this.artworkEl);
this.currentTimeEl = this.container.querySelector(".time-current");
@@ -1363,6 +1368,17 @@ var WaveformPlayer = class _WaveformPlayer {
span.className = "waveform-artist";
return span;
}
+ /**
+ * Create an album text element matching the initial player markup.
+ *
+ * @returns {HTMLSpanElement} Album text element.
+ * @private
+ */
+ createAlbumElement() {
+ const span = document.createElement("span");
+ span.className = "waveform-album";
+ return span;
+ }
/**
* Reconcile artist metadata and markup for the current track.
*
@@ -1386,6 +1402,33 @@ var WaveformPlayer = class _WaveformPlayer {
this.artistEl.textContent = artist;
this.artistEl.style.display = "";
}
+ /**
+ * Reconcile album metadata and markup for the current track.
+ *
+ * @param {string|null} album - Album text, or a falsy value to remove it.
+ * @private
+ */
+ syncAlbum(album) {
+ this.options.album = album || "";
+ if (!this.options.showInfo || !this.options.showAlbum) {
+ this.albumEl?.remove();
+ this.albumEl = null;
+ return;
+ }
+ if (!album) {
+ this.albumEl?.remove();
+ this.albumEl = null;
+ return;
+ }
+ if (!this.albumEl) {
+ const anchorEl = this.artistEl || this.container.querySelector(".waveform-title");
+ if (!anchorEl) return;
+ this.albumEl = this.createAlbumElement();
+ anchorEl.after(this.albumEl);
+ }
+ this.albumEl.textContent = album;
+ this.albumEl.style.display = "";
+ }
/**
* Reconcile the play button's artwork image (`artworkPosition: 'button'`).
*
@@ -1997,7 +2040,7 @@ var WaveformPlayer = class _WaveformPlayer {
*
* Pauses any current playback, fully resets the audio element (self mode),
* clears error/marker/progress state, merges the new metadata into
- * `this.options`, updates the artist/artwork DOM, then calls
+ * `this.options`, updates the artist/album/artwork DOM, then calls
* {@link WaveformPlayer#load}. Auto-plays the new track unless
* `options.autoplay === false`.
* @param {string} url - Audio URL.
@@ -2012,6 +2055,8 @@ var WaveformPlayer = class _WaveformPlayer {
async loadTrack(url, title = null, artist = null, options = {}) {
const hasArtworkOption = Object.prototype.hasOwnProperty.call(options, "artwork");
const hasArtworkAltOption = Object.prototype.hasOwnProperty.call(options, "artworkAlt");
+ const hasAlbumOption = Object.prototype.hasOwnProperty.call(options, "album");
+ const hasShowAlbumOption = Object.prototype.hasOwnProperty.call(options, "showAlbum");
if (this.isPlaying) {
this.pause();
}
@@ -2054,6 +2099,9 @@ var WaveformPlayer = class _WaveformPlayer {
if (artist !== null) {
this.syncArtist(artist);
}
+ if (hasAlbumOption || hasShowAlbumOption) {
+ this.syncAlbum(this.options.album);
+ }
if (hasArtworkOption || hasArtworkAltOption) {
this.syncArtwork(
hasArtworkOption ? options.artwork : this.options.artwork,
@@ -2717,13 +2765,14 @@ var WaveformPlayer = class _WaveformPlayer {
* directly: `WaveformBar.play(event.detail)`.
*
* @private
- * @return {{url:string,title:?string,artist:?string,artwork:?string,player:WaveformPlayer}}
+ * @return {{url:string,title:?string,artist:?string,album:string,artwork:?string,player:WaveformPlayer}}
*/
_buildTrackDetail() {
return {
url: this.options.url,
title: this.options.title,
artist: this.options.artist,
+ album: this.options.album,
artwork: this.options.artwork,
markers: this.options.markers,
waveform: this.options.waveform,
diff --git a/dist/waveform-player.css b/dist/waveform-player.css
index 1481620..b861ed9 100644
--- a/dist/waveform-player.css
+++ b/dist/waveform-player.css
@@ -1 +1 @@
-.waveform-player{font-family:inherit;color:inherit;line-height:var(--waveform-line-height, 1.4);--wfp-accent: #71717a;--wfp-button-color: rgba(255, 255, 255, .9);--wfp-text-color: #ffffff;--wfp-text-secondary-color: rgba(255, 255, 255, .6);--wfp-btn-artwork-color: rgba(255, 255, 255, .9);--wfp-btn-artwork-scrim: rgba(0, 0, 0, .5)}.waveform-player.waveform-theme-light{--wfp-button-color: rgba(0, 0, 0, .8);--wfp-text-color: #333333;--wfp-text-secondary-color: rgba(0, 0, 0, .6)}.waveform-player *{box-sizing:border-box}.waveform-body{display:flex;flex-direction:column;gap:var(--waveform-body-gap, 8px)}.waveform-track{display:flex;align-items:center;gap:var(--waveform-track-gap, 12px);position:relative}.waveform-btn{width:var(--wfp-btn-size, 36px);height:var(--wfp-btn-size, 36px);min-width:var(--wfp-btn-size, 36px);border-radius:var(--wfp-btn-radius, 50%);border:2px solid currentColor;background:transparent;color:var(--wfp-button-color, inherit);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;padding:0;opacity:.9;flex-shrink:0}.waveform-btn:hover:not(:disabled){opacity:1;transform:scale(1.05)}.waveform-btn-minimal{width:calc(var(--wfp-btn-size, 36px) * 1.1);height:calc(var(--wfp-btn-size, 36px) * 1.1);min-width:calc(var(--wfp-btn-size, 36px) * 1.1);border:none;border-radius:0;opacity:.7}.waveform-btn-minimal:hover:not(:disabled){opacity:1;transform:none}.waveform-btn.waveform-btn-minimal svg{width:calc(var(--wfp-btn-size, 36px) * .94);height:calc(var(--wfp-btn-size, 36px) * .94)}.waveform-btn:disabled{cursor:not-allowed;opacity:.3}.waveform-btn>*{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.waveform-btn svg{width:calc(var(--wfp-btn-size, 36px) * .45);height:calc(var(--wfp-btn-size, 36px) * .45);fill:currentColor;display:block}.waveform-btn-has-artwork{--wfp-btn-size: 64px;--wfp-btn-radius: 8px;position:relative;overflow:hidden;border:none;color:var(--wfp-btn-artwork-color)}.waveform-btn>.waveform-btn-artwork{display:block;position:absolute;inset:0;width:100%;height:100%;object-fit:cover;pointer-events:none;z-index:0}.waveform-btn-has-artwork:after{content:"";position:absolute;inset:0;background:var(--wfp-btn-artwork-scrim);pointer-events:none;z-index:1}.waveform-btn-has-artwork>:not(.waveform-btn-artwork){position:relative;z-index:2}.waveform-icon-play svg{margin-left:1px}.waveform-container{flex:1;position:relative;min-height:60px;cursor:pointer;min-width:0;width:100%}.waveform-container:focus-visible{outline:2px solid currentColor}.waveform-container canvas{display:block;width:100%;height:100%;max-width:100%;transition:opacity .3s ease;position:relative;z-index:1}.waveform-info{display:flex;align-items:center;gap:8px;font-size:13px;min-height:20px}.waveform-text{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.waveform-title{color:var(--wfp-text-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}.waveform-artist{color:var(--wfp-text-secondary-color);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.waveform-time{color:var(--wfp-text-secondary-color);font-size:11px;white-space:nowrap;flex-shrink:0}.waveform-bpm{color:var(--wfp-text-secondary-color);font-size:11px;white-space:nowrap;flex-shrink:0;display:inline-flex;align-items:center;gap:4px}.waveform-loading{position:absolute;inset:0;background:#0000001a;z-index:10}.waveform-error{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#0003;z-index:10}.waveform-error-text{font-size:12px;opacity:.7;text-align:center;padding:0 20px}.waveform-markers{position:absolute;inset:0;pointer-events:none;z-index:5}.waveform-marker{position:absolute;top:0;width:2px;height:100%;background:#ffffff80;border:none;padding:0;cursor:pointer;pointer-events:all;transition:all .2s}.waveform-marker:hover{width:4px;z-index:20}.waveform-marker.active{width:4px;background:currentColor;z-index:10}.waveform-marker-tooltip{position:absolute;bottom:calc(100% + 4px);left:50%;transform:translate(-50%);background:#000000e6;color:#fff;padding:4px 8px;border-radius:4px;font-size:11px;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .2s;z-index:1000}.waveform-marker:hover .waveform-marker-tooltip,.waveform-marker.show-label .waveform-marker-tooltip{opacity:1}.waveform-hover-time{position:absolute;bottom:calc(100% + 4px);transform:translate(-50%);background:#000000e6;color:#fff;padding:3px 7px;border-radius:4px;font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .15s;z-index:1000}.waveform-seek-handle{position:absolute;top:50%;left:0;width:11px;height:11px;border-radius:50%;background:#fff;box-shadow:0 1px 4px #00000059;transform:translate(-50%,-50%) scale(0);opacity:0;transition:opacity .12s ease,transform .12s ease;pointer-events:none;z-index:6}.waveform-seek-handle.is-visible{opacity:1;transform:translate(-50%,-50%) scale(1)}.waveform-seek-handle.is-active{transform:translate(-50%,-50%) scale(1.25)}.waveform-btn:focus-visible{outline:2px solid currentColor;outline-offset:2px}.waveform-marker:focus-visible{outline:2px solid currentColor;outline-offset:1px;width:4px}.waveform-speed{position:relative;flex-shrink:0}.speed-btn{background:transparent;border:1px solid rgba(255,255,255,.2);border-radius:4px;padding:4px 8px;color:inherit;font-size:11px;cursor:pointer;transition:all .2s;min-width:40px}.speed-btn:hover{background:#ffffff0d;border-color:#ffffff4d}.speed-value{font-weight:600}.speed-menu{position:absolute;bottom:100%;right:0;margin-bottom:4px;background:#000000f2;border:1px solid rgba(255,255,255,.2);border-radius:6px;padding:4px;z-index:100;min-width:60px}.speed-option{display:block;width:100%;background:transparent;border:none;color:#ffffffb3;padding:6px 12px;font-size:12px;cursor:pointer;transition:all .2s;text-align:left;border-radius:4px}.speed-option:hover{background:#ffffff1a;color:#fff}.speed-option.active{background:#ffffff29;color:#fff;font-weight:600}.waveform-player.waveform-focused{outline:2px solid var(--wfp-accent);outline-offset:2px;border-radius:4px}.waveform-player:focus{outline:none}.waveform-player:focus-visible{outline:1px solid var(--wfp-accent);outline-offset:1px}.waveform-layout-preview .waveform-meta{display:none!important}.waveform-layout-preview .waveform-info{justify-content:center}.waveform-layout-preview .waveform-text{flex:0 1 auto;align-items:center;text-align:center}.waveform-player.waveform-focused{outline:none}.waveform-track.waveform-align-top{align-items:flex-start}.waveform-track.waveform-align-top .waveform-btn{margin-top:5px}.waveform-track.waveform-align-center{align-items:center}.waveform-track.waveform-align-bottom{align-items:flex-end}.waveform-track.waveform-align-bottom .waveform-btn{margin-bottom:5px}@media(max-width:480px){.waveform-btn{width:var(--wfp-btn-size, 32px);height:var(--wfp-btn-size, 32px);min-width:var(--wfp-btn-size, 32px)}.waveform-container{min-height:50px}.waveform-info{font-size:12px}.waveform-artist,.waveform-time,.waveform-bpm{font-size:10px}}@media(prefers-reduced-motion:reduce){.waveform-player *,.waveform-player *:before,.waveform-player *:after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}
+.waveform-player{font-family:inherit;color:inherit;line-height:var(--waveform-line-height, 1.4);--wfp-accent: #71717a;--wfp-button-color: rgba(255, 255, 255, .9);--wfp-text-color: #ffffff;--wfp-text-secondary-color: rgba(255, 255, 255, .6);--wfp-btn-artwork-color: rgba(255, 255, 255, .9);--wfp-btn-artwork-scrim: rgba(0, 0, 0, .5)}.waveform-player.waveform-theme-light{--wfp-button-color: rgba(0, 0, 0, .8);--wfp-text-color: #333333;--wfp-text-secondary-color: rgba(0, 0, 0, .6)}.waveform-player *{box-sizing:border-box}.waveform-body{display:flex;flex-direction:column;gap:var(--waveform-body-gap, 8px)}.waveform-track{display:flex;align-items:center;gap:var(--waveform-track-gap, 12px);position:relative}.waveform-btn{width:var(--wfp-btn-size, 36px);height:var(--wfp-btn-size, 36px);min-width:var(--wfp-btn-size, 36px);border-radius:var(--wfp-btn-radius, 50%);border:2px solid currentColor;background:transparent;color:var(--wfp-button-color, inherit);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;padding:0;opacity:.9;flex-shrink:0}.waveform-btn:hover:not(:disabled){opacity:1;transform:scale(1.05)}.waveform-btn-minimal{width:calc(var(--wfp-btn-size, 36px) * 1.1);height:calc(var(--wfp-btn-size, 36px) * 1.1);min-width:calc(var(--wfp-btn-size, 36px) * 1.1);border:none;border-radius:0;opacity:.7}.waveform-btn-minimal:hover:not(:disabled){opacity:1;transform:none}.waveform-btn.waveform-btn-minimal svg{width:calc(var(--wfp-btn-size, 36px) * .94);height:calc(var(--wfp-btn-size, 36px) * .94)}.waveform-btn:disabled{cursor:not-allowed;opacity:.3}.waveform-btn>*{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.waveform-btn svg{width:calc(var(--wfp-btn-size, 36px) * .45);height:calc(var(--wfp-btn-size, 36px) * .45);fill:currentColor;display:block}.waveform-btn-has-artwork{--wfp-btn-size: 64px;--wfp-btn-radius: 8px;position:relative;overflow:hidden;border:none;color:var(--wfp-btn-artwork-color)}.waveform-btn>.waveform-btn-artwork{display:block;position:absolute;inset:0;width:100%;height:100%;object-fit:cover;pointer-events:none;z-index:0}.waveform-btn-has-artwork:after{content:"";position:absolute;inset:0;background:var(--wfp-btn-artwork-scrim);pointer-events:none;z-index:1}.waveform-btn-has-artwork>:not(.waveform-btn-artwork){position:relative;z-index:2}.waveform-icon-play svg{margin-left:1px}.waveform-container{flex:1;position:relative;min-height:60px;cursor:pointer;min-width:0;width:100%}.waveform-container:focus-visible{outline:2px solid currentColor}.waveform-container canvas{display:block;width:100%;height:100%;max-width:100%;transition:opacity .3s ease;position:relative;z-index:1}.waveform-info{display:flex;align-items:center;gap:8px;font-size:13px;min-height:20px}.waveform-text{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.waveform-title{color:var(--wfp-text-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}.waveform-artist,.waveform-album{color:var(--wfp-text-secondary-color);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.waveform-time{color:var(--wfp-text-secondary-color);font-size:11px;white-space:nowrap;flex-shrink:0}.waveform-bpm{color:var(--wfp-text-secondary-color);font-size:11px;white-space:nowrap;flex-shrink:0;display:inline-flex;align-items:center;gap:4px}.waveform-loading{position:absolute;inset:0;background:#0000001a;z-index:10}.waveform-error{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#0003;z-index:10}.waveform-error-text{font-size:12px;opacity:.7;text-align:center;padding:0 20px}.waveform-markers{position:absolute;inset:0;pointer-events:none;z-index:5}.waveform-marker{position:absolute;top:0;width:2px;height:100%;background:#ffffff80;border:none;padding:0;cursor:pointer;pointer-events:all;transition:all .2s}.waveform-marker:hover{width:4px;z-index:20}.waveform-marker.active{width:4px;background:currentColor;z-index:10}.waveform-marker-tooltip{position:absolute;bottom:calc(100% + 4px);left:50%;transform:translate(-50%);background:#000000e6;color:#fff;padding:4px 8px;border-radius:4px;font-size:11px;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .2s;z-index:1000}.waveform-marker:hover .waveform-marker-tooltip,.waveform-marker.show-label .waveform-marker-tooltip{opacity:1}.waveform-hover-time{position:absolute;bottom:calc(100% + 4px);transform:translate(-50%);background:#000000e6;color:#fff;padding:3px 7px;border-radius:4px;font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .15s;z-index:1000}.waveform-seek-handle{position:absolute;top:50%;left:0;width:11px;height:11px;border-radius:50%;background:#fff;box-shadow:0 1px 4px #00000059;transform:translate(-50%,-50%) scale(0);opacity:0;transition:opacity .12s ease,transform .12s ease;pointer-events:none;z-index:6}.waveform-seek-handle.is-visible{opacity:1;transform:translate(-50%,-50%) scale(1)}.waveform-seek-handle.is-active{transform:translate(-50%,-50%) scale(1.25)}.waveform-btn:focus-visible{outline:2px solid currentColor;outline-offset:2px}.waveform-marker:focus-visible{outline:2px solid currentColor;outline-offset:1px;width:4px}.waveform-speed{position:relative;flex-shrink:0}.speed-btn{background:transparent;border:1px solid rgba(255,255,255,.2);border-radius:4px;padding:4px 8px;color:inherit;font-size:11px;cursor:pointer;transition:all .2s;min-width:40px}.speed-btn:hover{background:#ffffff0d;border-color:#ffffff4d}.speed-value{font-weight:600}.speed-menu{position:absolute;bottom:100%;right:0;margin-bottom:4px;background:#000000f2;border:1px solid rgba(255,255,255,.2);border-radius:6px;padding:4px;z-index:100;min-width:60px}.speed-option{display:block;width:100%;background:transparent;border:none;color:#ffffffb3;padding:6px 12px;font-size:12px;cursor:pointer;transition:all .2s;text-align:left;border-radius:4px}.speed-option:hover{background:#ffffff1a;color:#fff}.speed-option.active{background:#ffffff29;color:#fff;font-weight:600}.waveform-player.waveform-focused{outline:2px solid var(--wfp-accent);outline-offset:2px;border-radius:4px}.waveform-player:focus{outline:none}.waveform-player:focus-visible{outline:1px solid var(--wfp-accent);outline-offset:1px}.waveform-layout-preview .waveform-meta{display:none!important}.waveform-layout-preview .waveform-info{justify-content:center}.waveform-layout-preview .waveform-text{flex:0 1 auto;align-items:center;text-align:center}.waveform-player.waveform-focused{outline:none}.waveform-track.waveform-align-top{align-items:flex-start}.waveform-track.waveform-align-top .waveform-btn{margin-top:5px}.waveform-track.waveform-align-center{align-items:center}.waveform-track.waveform-align-bottom{align-items:flex-end}.waveform-track.waveform-align-bottom .waveform-btn{margin-bottom:5px}@media(max-width:480px){.waveform-btn{width:var(--wfp-btn-size, 32px);height:var(--wfp-btn-size, 32px);min-width:var(--wfp-btn-size, 32px)}.waveform-container{min-height:50px}.waveform-info{font-size:12px}.waveform-artist,.waveform-album,.waveform-time,.waveform-bpm{font-size:10px}}@media(prefers-reduced-motion:reduce){.waveform-player *,.waveform-player *:before,.waveform-player *:after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}
diff --git a/dist/waveform-player.esm.js b/dist/waveform-player.esm.js
index c0387ed..6c30c9e 100644
--- a/dist/waveform-player.esm.js
+++ b/dist/waveform-player.esm.js
@@ -1,4 +1,4 @@
-function $(e){let t=-1/0;for(let i=0;i
t&&(t=e[i]);return t}function S(e){return String(e??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function q(e){return S(typeof e=="number"?`${e}px`:e)}function st(e){if(typeof e!="string"||e==="")return!1;try{let t=new URL(e,"http://localhost/");return t.protocol==="http:"||t.protocol==="https:"}catch{return!1}}function m(e,t=0,i=1){return Math.max(t,Math.min(e,i))}function _(e,t=null,i={}){let{min:s=-1/0,max:r=1/0,integer:a=!1}=i,o=typeof e=="number"?e:typeof e=="string"&&e.trim()!==""?Number(e):NaN;if(!Number.isFinite(o))return t;let n=m(o,s,r);return a?Math.round(n):n}function H(e,t=null){if(Array.isArray(e))return e;if(typeof e=="string"&&e.trim().startsWith("["))try{let i=JSON.parse(e);if(Array.isArray(i))return i}catch{}return t}function D(e,t={}){let{min:i=-1/0,max:s=1/0,fallback:r=null}=t,a=H(e);if(!a&&typeof e=="string"&&e.trim()!==""&&(a=e.split(/[,\s]+/)),!a)return r;let o=a.map(n=>_(n)).filter(n=>n!==null&&n>=i&&n<=s);return o.length?o:r}function rt(e,t,i=null){return t.includes(e)?e:i}function at(e){if(typeof e=="string"){let t=e.trim().toLowerCase();return t!==""&&t!=="false"&&t!=="0"}return!!e}function vt(e){return e===void 0?void 0:e==="true"}function it(e){if(typeof e=="string"&&e.trim().startsWith("["))try{return JSON.parse(e)}catch{}return e}function I(e){let t={},i=(o,n=o)=>{let l=vt(e.dataset[n]);l!==void 0&&(t[o]=l)},s=(o,n=o,l=!1)=>{let h=e.dataset[n];h&&(t[o]=l?parseFloat(h):parseInt(h,10))},r=(o,n=o)=>{let l=e.dataset[n];l&&(t[o]=/^\d+(\.\d+)?$/.test(l.trim())?parseFloat(l):l)},a=(o,n=o)=>{let l=e.dataset[n];if(!l)return;let h=H(l);h?t[o]=h:console.warn(`[WaveformPlayer] Invalid ${n} attribute, expected a JSON array:`,l)};if(e.dataset.src&&(t.url=e.dataset.src),e.dataset.url&&(t.url=e.dataset.url),s("height"),s("samples"),e.dataset.preload&&(t.preload=e.dataset.preload),e.dataset.crossOrigin&&(t.crossOrigin=e.dataset.crossOrigin),e.dataset.audioMode&&(t.audioMode=e.dataset.audioMode),e.dataset.style&&(t.waveformStyle=e.dataset.style),e.dataset.waveformStyle&&(t.waveformStyle=e.dataset.waveformStyle),e.dataset.waveformGradient&&(t.waveformGradient=e.dataset.waveformGradient),s("barWidth"),s("barSpacing"),s("barRadius"),e.dataset.buttonAlign&&(t.buttonAlign=e.dataset.buttonAlign),e.dataset.layout&&(t.layout=e.dataset.layout),e.dataset.buttonStyle&&(t.buttonStyle=e.dataset.buttonStyle),r("buttonSize"),r("buttonRadius"),e.dataset.colorPreset&&(t.colorPreset=e.dataset.colorPreset),e.dataset.waveformColor&&(t.waveformColor=it(e.dataset.waveformColor)),e.dataset.progressColor&&(t.progressColor=it(e.dataset.progressColor)),e.dataset.color&&(t.waveformColor=e.dataset.color),e.dataset.theme&&(t.colorPreset=e.dataset.theme),i("autoplay"),i("showControls"),i("showInfo"),i("showTime"),i("showHoverTime"),i("seekHandle"),i("showBPM","showBpm"),s("bpm"),i("singlePlay"),i("playOnSeek"),e.dataset.title&&(t.title=e.dataset.title),e.dataset.artist&&(t.artist=e.dataset.artist),e.dataset.album&&(t.album=e.dataset.album),e.dataset.artwork&&(t.artwork=e.dataset.artwork),e.dataset.artworkPosition&&(t.artworkPosition=e.dataset.artworkPosition),e.dataset.waveform&&(t.waveform=e.dataset.waveform),a("markers"),s("playbackRate","playbackRate",!0),i("showPlaybackSpeed"),e.dataset.playbackRates){let o=D(e.dataset.playbackRates);o?t.playbackRates=o:console.warn("[WaveformPlayer] Invalid playbackRates attribute:",e.dataset.playbackRates)}return i("enableMediaSession"),i("showMarkers"),i("accessibleSeek"),e.dataset.seekLabel&&(t.seekLabel=e.dataset.seekLabel),e.dataset.seekValueText&&(t.seekValueText=e.dataset.seekValueText),e.dataset.errorText&&(t.errorText=e.dataset.errorText),e.dataset.playPauseLabel&&(t.playPauseLabel=e.dataset.playPauseLabel),e.dataset.speedLabel&&(t.speedLabel=e.dataset.speedLabel),e.dataset.artworkAlt&&(t.artworkAlt=e.dataset.artworkAlt),e.dataset.unknownTrackText&&(t.unknownTrackText=e.dataset.unknownTrackText),e.dataset.playIcon&&(t.playIcon=e.dataset.playIcon),e.dataset.pauseIcon&&(t.pauseIcon=e.dataset.pauseIcon),t}function ot(e,...t){let i=0;return e.replace(/%(?:(\d+)\$)?s/g,(s,r)=>{let a=r?Number(r)-1:i++;return t[a]??s})}function E(e){let t=Number(e);if(!t||!Number.isFinite(t)||t<0)return"0:00";let i=Math.floor(t/3600),s=Math.floor(t%3600/60),r=Math.floor(t%60);return i>0?`${i}:${s.toString().padStart(2,"0")}:${r.toString().padStart(2,"0")}`:`${s}:${r.toString().padStart(2,"0")}`}var St=0;function nt(e){let t=e||"audio",i=5381;for(let s=0;s>>0).toString(36)}_${(St++).toString(36)}`}function O(e){if(!e)return"Audio";let t=e.split("/");return t[t.length-1].split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,r=>r.toUpperCase())}function U(e){if(typeof e!="string")return null;let t=e.match(/rgba?\(\s*([\d.]+)\s*[,\s]\s*([\d.]+)\s*[,\s]\s*([\d.]+)\s*(?:[,/]\s*([\d.]+)(%?))?/i);if(!t)return null;let i=Number(t[1]),s=Number(t[2]),r=Number(t[3]);if(!Number.isFinite(i)||!Number.isFinite(s)||!Number.isFinite(r))return null;let a=t[4]===void 0?1:Number(t[4]);return Number.isFinite(a)?(t[5]==="%"&&(a/=100),{r:i,g:s,b:r,a:m(a,0,1)}):null}function lt(e){let t=U(e);return!t||t.a<=0?null:(t.r*299+t.g*587+t.b*114)/1e3}function j(...e){let t={};for(let i of e)for(let s in i)i[s]!==null&&i[s]!==void 0&&(t[s]=i[s]);return t}function ht(e,t){let i;return function(...r){let a=()=>{clearTimeout(i),e(...r)};clearTimeout(i),i=setTimeout(a,t)}}function B(e,t){if(e.length===t)return e;if(e.length===0||t===0)return[];let i=[];if(t>e.length){let s=(e.length-1)/(t-1);for(let r=0;r=e.length)i.push(e[e.length-1]);else if(o===n)i.push(e[o]);else{let h=e[o]*(1-l)+e[n]*l;i.push(h)}}}else{let s=e.length/t;for(let r=0;rn&&(n=e[h]),l++;if(l===0){let h=Math.min(Math.round(r*s),e.length-1);n=e[h]}i.push(n)}}return i}function P(e,t,i,s){if(!Array.isArray(t))return t;if(t.length<2)return t[0];let r=i.width,a=i.height,o=s&&s.waveformGradient,[n,l,h,c]=o==="horizontal"?[0,0,r,0]:o==="diagonal"?[0,0,r,a]:[0,0,0,a];try{let d=e.createLinearGradient(n,l,h,c);return t.forEach((b,y)=>d.addColorStop(y/(t.length-1),b)),d}catch{return t[0]}}function x(e,t,i,s,r,a){if((Array.isArray(a)?a.some(n=>n>0):a>0)&&typeof e.roundRect=="function"){let n=Math.min(s/2,Math.abs(r)/2),l=h=>m(h,0,n);e.beginPath(),e.roundRect(t,i,s,r,Array.isArray(a)?a.map(l):l(a)),e.fill()}else e.fillRect(t,i,s,r)}function pt(e,t){return(e.barRadius||0)*t}function Et(e,t){let i=pt(e,t);return[i,i,0,0]}function ct(e,t,i,s,r){let a=r/2;e.beginPath(),e.moveTo(t,s-a),e.lineTo(i-a,s-a),e.arc(i-a,s,a,-Math.PI/2,Math.PI/2),e.lineTo(t,s+a),e.arc(t,s,a,Math.PI/2,-Math.PI/2),e.closePath()}function V(e,t,i,s,r){let a=window.devicePixelRatio||1,o=r.barWidth*a,n=r.barSpacing*a,l=Math.floor(t.width/(o+n)),h=B(i,l),c=t.height,d=s*t.width,b=Et(r,a),y=P(e,r.color,t,r),w=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height),e.fillStyle=y;for(let f=0;ft.width)break;let g=h[f]*c*.9,u=c-g;x(e,p,u,o,g,b)}e.save(),e.beginPath(),e.rect(0,0,d,c),e.clip(),e.fillStyle=w;for(let f=0;fd)break;let g=h[f]*c*.9,u=c-g;x(e,p,u,o,g,b)}e.restore()}function Pt(e,t,i,s,r){let a=window.devicePixelRatio||1,o=r.barWidth*a,n=r.barSpacing*a,l=Math.floor(t.width/(o+n)),h=B(i,l),c=t.height,d=c/2,b=s*t.width,y=pt(r,a),w=[y,y,0,0],f=[0,0,y,y],p=P(e,r.color,t,r),g=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height),e.fillStyle=p;for(let u=0;ut.width)break;let v=h[u]*c*.45;x(e,k,d-v,o,v,w),x(e,k,d,o,v,f)}e.save(),e.beginPath(),e.rect(0,0,b,c),e.clip(),e.fillStyle=g;for(let u=0;ub)break;let v=h[u]*c*.45;x(e,k,d-v,o,v,w),x(e,k,d,o,v,f)}e.restore()}function Tt(e,t,i,s,r){let a=t.width,o=t.height,n=o/2,l=o*.35;e.clearRect(0,0,a,o);let h=(c,d,b=1,y=!1)=>{let w=P(e,c,t,r),f=Array.isArray(c)?c[c.length-1]:c;y&&(e.shadowBlur=12,e.shadowColor=f),e.strokeStyle=w,e.lineWidth=d,e.lineCap="round",e.lineJoin="round",e.beginPath(),e.moveTo(0,n);let p=[],g=Math.floor(i.length*b);for(let u=0;u0&&h(r.progressColor,3,s,!0)}function ut(e,t,i,s,r){let a=window.devicePixelRatio||1,o=(r.barWidth||3)*a,n=(r.barSpacing||1)*a,l=Math.floor(t.width/(o+n)),h=B(i,l),c=t.height,d=4*a,b=2*a,y=s*t.width,w=c/2,f=P(e,r.color,t,r),p=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height);for(let g=0;gt.width)break;let k=h[g]*c*.9,v=Math.floor(k/(d+b));e.fillStyle=u0&&e.fillRect(u,w+L,o,d)}}}function dt(e,t,i,s,r){let a=window.devicePixelRatio||1,o=(r.barWidth||2)*a,n=(r.barSpacing||3)*a,l=Math.floor(t.width/(o+n)),h=B(i,l),c=t.height,d=Math.max(1.5*a,o/2),b=s*t.width,y=c/2,w=P(e,r.color,t,r),f=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height);for(let p=0;pt.width)break;let u=h[p]*c*.9;e.fillStyle=g0){let d=Math.max(h*2,s*a);e.save(),e.globalAlpha=r.seekHandle&&!c?.7:1,e.fillStyle=P(e,r.progressColor,t,r)||"rgba(255, 255, 255, 0.9)",ct(e,h,d,n,l),e.fill(),e.restore()}}var Mt={bars:V,bar:V,mirror:Pt,line:Tt,blocks:ut,block:ut,dots:dt,dot:dt,seekbar:At};function ft(e,t,i,s,r){(Mt[r.waveformStyle]||V)(e,t,i,s,r)}function mt(e){try{let t=e.getChannelData(0),i=e.sampleRate,s=Ct(t,i);if(s.length<2)return 120;let r=[];for(let l=1;l{let h=60/l,c=Math.round(h/3)*3;c>60&&c<200&&(a[c]=(a[c]||0)+1)});let o=0,n=120;for(let[l,h]of Object.entries(a))h>o&&(o=h,n=parseInt(l));return n<70&&a[n*2]?n*=2:n>160&&a[Math.round(n/2)]&&(n=Math.round(n/2)),n-1}catch(t){return console.warn("[WaveformPlayer] BPM detection failed:",t),null}}function Ct(e,t){let r=[],a=0;for(let o=0;oh&&n>.01){let c=r[r.length-1]||0,d=t*.15;o-c>d&&r.push(o)}a=n*.8+a*.2}return r}function Lt(e,t=1800){let i=e.length/t,s=e.numberOfChannels,r=[];for(let o=0;ob&&(b=f),fr[l])&&(r[l]=y)}}let a=$(r);return a>0?r.map(o=>o/a):r}async function G(e,t=1800,i=!1){let s;try{let r=window.AudioContext||window.webkitAudioContext;s=new r;let o=await(await fetch(e)).arrayBuffer(),n=await s.decodeAudioData(o),l=Lt(n,t);l=_t(l);let h=null;return i&&(h=mt(n)),{peaks:l,bpm:h}}finally{s&&s.close()}}function yt(e=1800){let t=[];for(let i=0;it)return e;let s=t/i;return e.map(r=>r*s)}var K=128;function gt(e){let t=document.documentElement,i=document.body;return t.classList.contains(e)||t.classList.contains(`${e}-mode`)||t.classList.contains(`theme-${e}`)||t.getAttribute("data-theme")===e||t.getAttribute("data-color-scheme")===e||i.classList.contains(e)||i.classList.contains(`${e}-mode`)||i.getAttribute("data-theme")===e}function xt(e){let t=0,i=0;for(let s=e;s&&s.nodeType===1&&i<.995;s=s.parentElement){let r=U(getComputedStyle(s).backgroundColor);if(!r||r.a<=0)continue;let a=r.a*(1-i);t+=(r.r*299+r.g*587+r.b*114)/1e3*a,i+=a}return{sum:t,alpha:i}}function Rt(){let e=lt(getComputedStyle(document.body).color);if(e!==null)return e>K?"dark":"light";if(window.matchMedia){if(window.matchMedia("(prefers-color-scheme: dark)").matches)return"dark";if(window.matchMedia("(prefers-color-scheme: light)").matches)return"light"}return"dark"}function R(e){if(gt("dark"))return"dark";if(gt("light"))return"light";try{let t=e&&e.nodeType===1?e:document.body,{sum:i,alpha:s}=xt(t),r=Rt(),a=i+(r==="dark"?0:255)*(1-s);return a>K?"light":a ',pauseIcon:' ',onLoad:null,onPlay:null,onPause:null,onEnd:null,onError:null,onTimeUpdate:null,onNextTrack:null,onPreviousTrack:null},X={bars:{barWidth:3,barSpacing:1},mirror:{barWidth:2,barSpacing:2},line:{barWidth:2,barSpacing:0},blocks:{barWidth:4,barSpacing:2},dots:{barWidth:3,barSpacing:3},seekbar:{barWidth:1,barSpacing:0}},Q=["auto","top","center","bottom"],N=.25,F=4,Dt={buttonAlign:Q,layout:["default","preview"],buttonStyle:["circle","minimal"],artworkPosition:["info","button"],waveformStyle:Object.keys(X),waveformGradient:["vertical","horizontal","diagonal"],audioMode:["self","external"],preload:["none","metadata","auto"],colorPreset:Object.keys(C),crossOrigin:["anonymous","use-credentials"]},Bt={height:{min:1,integer:!0},samples:{min:1,integer:!0},barWidth:{min:0},barSpacing:{min:0},barRadius:{min:0},bpm:{min:1},playbackRate:{min:N,max:F}},Ht=["autoplay","showControls","showInfo","showTime","showHoverTime","seekHandle","showBPM","singlePlay","playOnSeek","enableMediaSession","showMarkers","accessibleSeek","showPlaybackSpeed"],It=["onLoad","onPlay","onPause","onEnd","onError","onTimeUpdate","onNextTrack","onPreviousTrack"];function Y(e,t){console.warn(`[WaveformPlayer] Invalid ${e} option, using default:`,t)}function z(e){let t=H(e);return t?t.reduce((i,s)=>{let r=s&&typeof s=="object"?_(s.time,null,{min:0}):null;return r===null?(Y("marker",s),i):(i.push({...s,time:r,label:s.label==null?"":s.label}),i)},[]):(e!=null&&Y("markers",e),[])}function Z(e){let t=s=>e[s]!=null,i=s=>{Y(s,e[s]),e[s]=W[s]};for(let[s,r]of Object.entries(Bt)){if(!t(s))continue;let a=_(e[s],null,r);a===null?i(s):e[s]=a}for(let[s,r]of Object.entries(Dt))t(s)&&rt(e[s],r)===null&&i(s);for(let s of Ht)e[s]=at(e[s]);for(let s of It)t(s)&&typeof e[s]!="function"&&i(s);if(t("playbackRates")){let s=D(e.playbackRates,{min:N,max:F,fallback:null});s===null?i("playbackRates"):e.playbackRates=s}e.markers=z(e.markers);for(let s of["buttonSize","buttonRadius"]){if(!t(s))continue;let r=e[s];(typeof r=="number"?Number.isFinite(r):typeof r=="string"&&r.trim()!=="")||i(s)}for(let s of["waveformColor","progressColor"]){if(!t(s))continue;let r=e[s];!(typeof r=="string"&&r.trim()!=="")&&!Array.isArray(r)&&i(s)}return e}var Ot="data:image/svg+xml,"+encodeURIComponent(' '),bt=5,wt=10,Wt='button, a[href], input, [role="slider"]',T=class e{static instances=new Map;static currentlyPlaying=null;constructor(t,i={}){if(this.container=typeof t=="string"?document.querySelector(t):t,!this.container)throw new Error("[WaveformPlayer] Container element not found");let s=I(this.container),r={...i};r.style&&!r.waveformStyle&&(r.waveformStyle=r.style),r.src&&!r.url&&(r.url=r.src),this.options=Z(j(W,s,r));let a=J(this.options.colorPreset,this.container);this._autoTheme=this.options.colorPreset==null||!C[this.options.colorPreset],this._presetKeys=[],this._scheme=this.options.colorPreset&&C[this.options.colorPreset]?this.options.colorPreset:R(this.container);for(let[n,l]of Object.entries(a))(this.options[n]===null||this.options[n]===void 0)&&(this.options[n]=l,this._presetKeys.push(n));let o=X[this.options.waveformStyle];o&&(s.barWidth===void 0&&i.barWidth===void 0&&(this.options.barWidth=o.barWidth),s.barSpacing===void 0&&i.barSpacing===void 0&&(this.options.barSpacing=o.barSpacing)),this.audio=null,this.canvas=null,this.ctx=null,this.waveformData=[],this.progress=0,this._activeMarkerIndex=-1,this._markerLabelTimer=null,this.isPlaying=!1,this.isLoading=!1,this.hasError=!1,this.updateTimer=null,this.resizeObserver=null,this._ac=new AbortController,this.id=this.container.id||nt(this.options.url),e.instances.set(this.id,this),e._watchTheme();try{this.init()}catch(n){throw e.instances.delete(this.id),this._ac.abort(),n}setTimeout(()=>{this._emit("waveformplayer:ready",{player:this,url:this.options.url})},100)}_emit(t,i,s=!1){let r=new CustomEvent(t,{bubbles:!0,cancelable:s,detail:i});return this.container.dispatchEvent(r),r}_requestSeek(t){this._emit("waveformplayer:request-seek",{...this._buildTrackDetail(),percent:t},!0).defaultPrevented||(this.progress=t,this.drawWaveform?.())}init(){this.createDOM(),this.createAudio(),this.initPlaybackSpeed(),this.initKeyboardControls(),this.initSeekControl(),this.bindEvents(),this.setupResizeObserver(),requestAnimationFrame(()=>{this.resizeCanvas(),this.options.url&&this.load(this.options.url).then(()=>{this.options.autoplay&&this.play()?.catch(()=>{})}).catch(t=>{console.error("[WaveformPlayer] Failed to load audio:",t)})})}createDOM(){this.container.innerHTML="",this.container.className="waveform-player";let t=Q.includes(this.options.buttonAlign)?this.options.buttonAlign:"auto";t==="auto"&&(this.options.waveformStyle==="bars"?t="bottom":t="center"),this.options.layout==="preview"&&this.container.classList.add("waveform-layout-preview"),this.container.classList.toggle("waveform-theme-light",this._scheme==="light");let s=[];this.options.buttonSize!=null&&s.push(`--wfp-btn-size: ${q(this.options.buttonSize)}`),this.options.buttonRadius!=null&&s.push(`--wfp-btn-radius: ${q(this.options.buttonRadius)}`);let r=s.length?` style="${s.join("; ")};"`:"",a=this.options.artworkPosition==="button"&&this.options.artwork,o=a?` `:"",n=this.options.showControls?`
+function z(e){let t=-1/0;for(let i=0;it&&(t=e[i]);return t}function S(e){return String(e??"").replace(/&/g,"&").replace(/ /g,">").replace(/"/g,""").replace(/'/g,"'")}function q(e){return S(typeof e=="number"?`${e}px`:e)}function st(e){if(typeof e!="string"||e==="")return!1;try{let t=new URL(e,"http://localhost/");return t.protocol==="http:"||t.protocol==="https:"}catch{return!1}}function m(e,t=0,i=1){return Math.max(t,Math.min(e,i))}function _(e,t=null,i={}){let{min:s=-1/0,max:r=1/0,integer:a=!1}=i,o=typeof e=="number"?e:typeof e=="string"&&e.trim()!==""?Number(e):NaN;if(!Number.isFinite(o))return t;let n=m(o,s,r);return a?Math.round(n):n}function B(e,t=null){if(Array.isArray(e))return e;if(typeof e=="string"&&e.trim().startsWith("["))try{let i=JSON.parse(e);if(Array.isArray(i))return i}catch{}return t}function D(e,t={}){let{min:i=-1/0,max:s=1/0,fallback:r=null}=t,a=B(e);if(!a&&typeof e=="string"&&e.trim()!==""&&(a=e.split(/[,\s]+/)),!a)return r;let o=a.map(n=>_(n)).filter(n=>n!==null&&n>=i&&n<=s);return o.length?o:r}function rt(e,t,i=null){return t.includes(e)?e:i}function at(e){if(typeof e=="string"){let t=e.trim().toLowerCase();return t!==""&&t!=="false"&&t!=="0"}return!!e}function vt(e){return e===void 0?void 0:e==="true"}function it(e){if(typeof e=="string"&&e.trim().startsWith("["))try{return JSON.parse(e)}catch{}return e}function H(e){let t={},i=(o,n=o)=>{let l=vt(e.dataset[n]);l!==void 0&&(t[o]=l)},s=(o,n=o,l=!1)=>{let h=e.dataset[n];h&&(t[o]=l?parseFloat(h):parseInt(h,10))},r=(o,n=o)=>{let l=e.dataset[n];l&&(t[o]=/^\d+(\.\d+)?$/.test(l.trim())?parseFloat(l):l)},a=(o,n=o)=>{let l=e.dataset[n];if(!l)return;let h=B(l);h?t[o]=h:console.warn(`[WaveformPlayer] Invalid ${n} attribute, expected a JSON array:`,l)};if(e.dataset.src&&(t.url=e.dataset.src),e.dataset.url&&(t.url=e.dataset.url),s("height"),s("samples"),e.dataset.preload&&(t.preload=e.dataset.preload),e.dataset.crossOrigin&&(t.crossOrigin=e.dataset.crossOrigin),e.dataset.audioMode&&(t.audioMode=e.dataset.audioMode),e.dataset.style&&(t.waveformStyle=e.dataset.style),e.dataset.waveformStyle&&(t.waveformStyle=e.dataset.waveformStyle),e.dataset.waveformGradient&&(t.waveformGradient=e.dataset.waveformGradient),s("barWidth"),s("barSpacing"),s("barRadius"),e.dataset.buttonAlign&&(t.buttonAlign=e.dataset.buttonAlign),e.dataset.layout&&(t.layout=e.dataset.layout),e.dataset.buttonStyle&&(t.buttonStyle=e.dataset.buttonStyle),r("buttonSize"),r("buttonRadius"),e.dataset.colorPreset&&(t.colorPreset=e.dataset.colorPreset),e.dataset.waveformColor&&(t.waveformColor=it(e.dataset.waveformColor)),e.dataset.progressColor&&(t.progressColor=it(e.dataset.progressColor)),e.dataset.color&&(t.waveformColor=e.dataset.color),e.dataset.theme&&(t.colorPreset=e.dataset.theme),i("autoplay"),i("showControls"),i("showInfo"),i("showAlbum"),i("showTime"),i("showHoverTime"),i("seekHandle"),i("showBPM","showBpm"),s("bpm"),i("singlePlay"),i("playOnSeek"),e.dataset.title&&(t.title=e.dataset.title),e.dataset.artist&&(t.artist=e.dataset.artist),e.dataset.album&&(t.album=e.dataset.album),e.dataset.artwork&&(t.artwork=e.dataset.artwork),e.dataset.artworkPosition&&(t.artworkPosition=e.dataset.artworkPosition),e.dataset.waveform&&(t.waveform=e.dataset.waveform),a("markers"),s("playbackRate","playbackRate",!0),i("showPlaybackSpeed"),e.dataset.playbackRates){let o=D(e.dataset.playbackRates);o?t.playbackRates=o:console.warn("[WaveformPlayer] Invalid playbackRates attribute:",e.dataset.playbackRates)}return i("enableMediaSession"),i("showMarkers"),i("accessibleSeek"),e.dataset.seekLabel&&(t.seekLabel=e.dataset.seekLabel),e.dataset.seekValueText&&(t.seekValueText=e.dataset.seekValueText),e.dataset.errorText&&(t.errorText=e.dataset.errorText),e.dataset.playPauseLabel&&(t.playPauseLabel=e.dataset.playPauseLabel),e.dataset.speedLabel&&(t.speedLabel=e.dataset.speedLabel),e.dataset.artworkAlt&&(t.artworkAlt=e.dataset.artworkAlt),e.dataset.unknownTrackText&&(t.unknownTrackText=e.dataset.unknownTrackText),e.dataset.playIcon&&(t.playIcon=e.dataset.playIcon),e.dataset.pauseIcon&&(t.pauseIcon=e.dataset.pauseIcon),t}function ot(e,...t){let i=0;return e.replace(/%(?:(\d+)\$)?s/g,(s,r)=>{let a=r?Number(r)-1:i++;return t[a]??s})}function E(e){let t=Number(e);if(!t||!Number.isFinite(t)||t<0)return"0:00";let i=Math.floor(t/3600),s=Math.floor(t%3600/60),r=Math.floor(t%60);return i>0?`${i}:${s.toString().padStart(2,"0")}:${r.toString().padStart(2,"0")}`:`${s}:${r.toString().padStart(2,"0")}`}var St=0;function nt(e){let t=e||"audio",i=5381;for(let s=0;s>>0).toString(36)}_${(St++).toString(36)}`}function I(e){if(!e)return"Audio";let t=e.split("/");return t[t.length-1].split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,r=>r.toUpperCase())}function U(e){if(typeof e!="string")return null;let t=e.match(/rgba?\(\s*([\d.]+)\s*[,\s]\s*([\d.]+)\s*[,\s]\s*([\d.]+)\s*(?:[,/]\s*([\d.]+)(%?))?/i);if(!t)return null;let i=Number(t[1]),s=Number(t[2]),r=Number(t[3]);if(!Number.isFinite(i)||!Number.isFinite(s)||!Number.isFinite(r))return null;let a=t[4]===void 0?1:Number(t[4]);return Number.isFinite(a)?(t[5]==="%"&&(a/=100),{r:i,g:s,b:r,a:m(a,0,1)}):null}function lt(e){let t=U(e);return!t||t.a<=0?null:(t.r*299+t.g*587+t.b*114)/1e3}function j(...e){let t={};for(let i of e)for(let s in i)i[s]!==null&&i[s]!==void 0&&(t[s]=i[s]);return t}function ht(e,t){let i;return function(...r){let a=()=>{clearTimeout(i),e(...r)};clearTimeout(i),i=setTimeout(a,t)}}function O(e,t){if(e.length===t)return e;if(e.length===0||t===0)return[];let i=[];if(t>e.length){let s=(e.length-1)/(t-1);for(let r=0;r=e.length)i.push(e[e.length-1]);else if(o===n)i.push(e[o]);else{let h=e[o]*(1-l)+e[n]*l;i.push(h)}}}else{let s=e.length/t;for(let r=0;rn&&(n=e[h]),l++;if(l===0){let h=Math.min(Math.round(r*s),e.length-1);n=e[h]}i.push(n)}}return i}function P(e,t,i,s){if(!Array.isArray(t))return t;if(t.length<2)return t[0];let r=i.width,a=i.height,o=s&&s.waveformGradient,[n,l,h,c]=o==="horizontal"?[0,0,r,0]:o==="diagonal"?[0,0,r,a]:[0,0,0,a];try{let d=e.createLinearGradient(n,l,h,c);return t.forEach((g,y)=>d.addColorStop(y/(t.length-1),g)),d}catch{return t[0]}}function x(e,t,i,s,r,a){if((Array.isArray(a)?a.some(n=>n>0):a>0)&&typeof e.roundRect=="function"){let n=Math.min(s/2,Math.abs(r)/2),l=h=>m(h,0,n);e.beginPath(),e.roundRect(t,i,s,r,Array.isArray(a)?a.map(l):l(a)),e.fill()}else e.fillRect(t,i,s,r)}function pt(e,t){return(e.barRadius||0)*t}function Et(e,t){let i=pt(e,t);return[i,i,0,0]}function ct(e,t,i,s,r){let a=r/2;e.beginPath(),e.moveTo(t,s-a),e.lineTo(i-a,s-a),e.arc(i-a,s,a,-Math.PI/2,Math.PI/2),e.lineTo(t,s+a),e.arc(t,s,a,Math.PI/2,-Math.PI/2),e.closePath()}function V(e,t,i,s,r){let a=window.devicePixelRatio||1,o=r.barWidth*a,n=r.barSpacing*a,l=Math.floor(t.width/(o+n)),h=O(i,l),c=t.height,d=s*t.width,g=Et(r,a),y=P(e,r.color,t,r),w=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height),e.fillStyle=y;for(let f=0;ft.width)break;let b=h[f]*c*.9,u=c-b;x(e,p,u,o,b,g)}e.save(),e.beginPath(),e.rect(0,0,d,c),e.clip(),e.fillStyle=w;for(let f=0;fd)break;let b=h[f]*c*.9,u=c-b;x(e,p,u,o,b,g)}e.restore()}function Pt(e,t,i,s,r){let a=window.devicePixelRatio||1,o=r.barWidth*a,n=r.barSpacing*a,l=Math.floor(t.width/(o+n)),h=O(i,l),c=t.height,d=c/2,g=s*t.width,y=pt(r,a),w=[y,y,0,0],f=[0,0,y,y],p=P(e,r.color,t,r),b=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height),e.fillStyle=p;for(let u=0;ut.width)break;let v=h[u]*c*.45;x(e,k,d-v,o,v,w),x(e,k,d,o,v,f)}e.save(),e.beginPath(),e.rect(0,0,g,c),e.clip(),e.fillStyle=b;for(let u=0;ug)break;let v=h[u]*c*.45;x(e,k,d-v,o,v,w),x(e,k,d,o,v,f)}e.restore()}function At(e,t,i,s,r){let a=t.width,o=t.height,n=o/2,l=o*.35;e.clearRect(0,0,a,o);let h=(c,d,g=1,y=!1)=>{let w=P(e,c,t,r),f=Array.isArray(c)?c[c.length-1]:c;y&&(e.shadowBlur=12,e.shadowColor=f),e.strokeStyle=w,e.lineWidth=d,e.lineCap="round",e.lineJoin="round",e.beginPath(),e.moveTo(0,n);let p=[],b=Math.floor(i.length*g);for(let u=0;u0&&h(r.progressColor,3,s,!0)}function ut(e,t,i,s,r){let a=window.devicePixelRatio||1,o=(r.barWidth||3)*a,n=(r.barSpacing||1)*a,l=Math.floor(t.width/(o+n)),h=O(i,l),c=t.height,d=4*a,g=2*a,y=s*t.width,w=c/2,f=P(e,r.color,t,r),p=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height);for(let b=0;bt.width)break;let k=h[b]*c*.9,v=Math.floor(k/(d+g));e.fillStyle=u0&&e.fillRect(u,w+L,o,d)}}}function dt(e,t,i,s,r){let a=window.devicePixelRatio||1,o=(r.barWidth||2)*a,n=(r.barSpacing||3)*a,l=Math.floor(t.width/(o+n)),h=O(i,l),c=t.height,d=Math.max(1.5*a,o/2),g=s*t.width,y=c/2,w=P(e,r.color,t,r),f=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height);for(let p=0;pt.width)break;let u=h[p]*c*.9;e.fillStyle=b0){let d=Math.max(h*2,s*a);e.save(),e.globalAlpha=r.seekHandle&&!c?.7:1,e.fillStyle=P(e,r.progressColor,t,r)||"rgba(255, 255, 255, 0.9)",ct(e,h,d,n,l),e.fill(),e.restore()}}var Mt={bars:V,bar:V,mirror:Pt,line:At,blocks:ut,block:ut,dots:dt,dot:dt,seekbar:Tt};function ft(e,t,i,s,r){(Mt[r.waveformStyle]||V)(e,t,i,s,r)}function mt(e){try{let t=e.getChannelData(0),i=e.sampleRate,s=Ct(t,i);if(s.length<2)return 120;let r=[];for(let l=1;l{let h=60/l,c=Math.round(h/3)*3;c>60&&c<200&&(a[c]=(a[c]||0)+1)});let o=0,n=120;for(let[l,h]of Object.entries(a))h>o&&(o=h,n=parseInt(l));return n<70&&a[n*2]?n*=2:n>160&&a[Math.round(n/2)]&&(n=Math.round(n/2)),n-1}catch(t){return console.warn("[WaveformPlayer] BPM detection failed:",t),null}}function Ct(e,t){let r=[],a=0;for(let o=0;oh&&n>.01){let c=r[r.length-1]||0,d=t*.15;o-c>d&&r.push(o)}a=n*.8+a*.2}return r}function Lt(e,t=1800){let i=e.length/t,s=e.numberOfChannels,r=[];for(let o=0;og&&(g=f),fr[l])&&(r[l]=y)}}let a=z(r);return a>0?r.map(o=>o/a):r}async function G(e,t=1800,i=!1){let s;try{let r=window.AudioContext||window.webkitAudioContext;s=new r;let o=await(await fetch(e)).arrayBuffer(),n=await s.decodeAudioData(o),l=Lt(n,t);l=_t(l);let h=null;return i&&(h=mt(n)),{peaks:l,bpm:h}}finally{s&&s.close()}}function yt(e=1800){let t=[];for(let i=0;it)return e;let s=t/i;return e.map(r=>r*s)}var K=128;function bt(e){let t=document.documentElement,i=document.body;return t.classList.contains(e)||t.classList.contains(`${e}-mode`)||t.classList.contains(`theme-${e}`)||t.getAttribute("data-theme")===e||t.getAttribute("data-color-scheme")===e||i.classList.contains(e)||i.classList.contains(`${e}-mode`)||i.getAttribute("data-theme")===e}function xt(e){let t=0,i=0;for(let s=e;s&&s.nodeType===1&&i<.995;s=s.parentElement){let r=U(getComputedStyle(s).backgroundColor);if(!r||r.a<=0)continue;let a=r.a*(1-i);t+=(r.r*299+r.g*587+r.b*114)/1e3*a,i+=a}return{sum:t,alpha:i}}function Rt(){let e=lt(getComputedStyle(document.body).color);if(e!==null)return e>K?"dark":"light";if(window.matchMedia){if(window.matchMedia("(prefers-color-scheme: dark)").matches)return"dark";if(window.matchMedia("(prefers-color-scheme: light)").matches)return"light"}return"dark"}function R(e){if(bt("dark"))return"dark";if(bt("light"))return"light";try{let t=e&&e.nodeType===1?e:document.body,{sum:i,alpha:s}=xt(t),r=Rt(),a=i+(r==="dark"?0:255)*(1-s);return a>K?"light":a ',pauseIcon:' ',onLoad:null,onPlay:null,onPause:null,onEnd:null,onError:null,onTimeUpdate:null,onNextTrack:null,onPreviousTrack:null},X={bars:{barWidth:3,barSpacing:1},mirror:{barWidth:2,barSpacing:2},line:{barWidth:2,barSpacing:0},blocks:{barWidth:4,barSpacing:2},dots:{barWidth:3,barSpacing:3},seekbar:{barWidth:1,barSpacing:0}},Q=["auto","top","center","bottom"],N=.25,F=4,Dt={buttonAlign:Q,layout:["default","preview"],buttonStyle:["circle","minimal"],artworkPosition:["info","button"],waveformStyle:Object.keys(X),waveformGradient:["vertical","horizontal","diagonal"],audioMode:["self","external"],preload:["none","metadata","auto"],colorPreset:Object.keys(C),crossOrigin:["anonymous","use-credentials"]},Ot={height:{min:1,integer:!0},samples:{min:1,integer:!0},barWidth:{min:0},barSpacing:{min:0},barRadius:{min:0},bpm:{min:1},playbackRate:{min:N,max:F}},Bt=["autoplay","showControls","showInfo","showAlbum","showTime","showHoverTime","seekHandle","showBPM","singlePlay","playOnSeek","enableMediaSession","showMarkers","accessibleSeek","showPlaybackSpeed"],Ht=["onLoad","onPlay","onPause","onEnd","onError","onTimeUpdate","onNextTrack","onPreviousTrack"];function Y(e,t){console.warn(`[WaveformPlayer] Invalid ${e} option, using default:`,t)}function $(e){let t=B(e);return t?t.reduce((i,s)=>{let r=s&&typeof s=="object"?_(s.time,null,{min:0}):null;return r===null?(Y("marker",s),i):(i.push({...s,time:r,label:s.label==null?"":s.label}),i)},[]):(e!=null&&Y("markers",e),[])}function Z(e){let t=s=>e[s]!=null,i=s=>{Y(s,e[s]),e[s]=W[s]};for(let[s,r]of Object.entries(Ot)){if(!t(s))continue;let a=_(e[s],null,r);a===null?i(s):e[s]=a}for(let[s,r]of Object.entries(Dt))t(s)&&rt(e[s],r)===null&&i(s);for(let s of Bt)e[s]=at(e[s]);for(let s of Ht)t(s)&&typeof e[s]!="function"&&i(s);if(t("playbackRates")){let s=D(e.playbackRates,{min:N,max:F,fallback:null});s===null?i("playbackRates"):e.playbackRates=s}e.markers=$(e.markers);for(let s of["buttonSize","buttonRadius"]){if(!t(s))continue;let r=e[s];(typeof r=="number"?Number.isFinite(r):typeof r=="string"&&r.trim()!=="")||i(s)}for(let s of["waveformColor","progressColor"]){if(!t(s))continue;let r=e[s];!(typeof r=="string"&&r.trim()!=="")&&!Array.isArray(r)&&i(s)}return e}var It="data:image/svg+xml,"+encodeURIComponent(' '),gt=5,wt=10,Wt='button, a[href], input, [role="slider"]',A=class e{static instances=new Map;static currentlyPlaying=null;constructor(t,i={}){if(this.container=typeof t=="string"?document.querySelector(t):t,!this.container)throw new Error("[WaveformPlayer] Container element not found");let s=H(this.container),r={...i};r.style&&!r.waveformStyle&&(r.waveformStyle=r.style),r.src&&!r.url&&(r.url=r.src),this.options=Z(j(W,s,r));let a=J(this.options.colorPreset,this.container);this._autoTheme=this.options.colorPreset==null||!C[this.options.colorPreset],this._presetKeys=[],this._scheme=this.options.colorPreset&&C[this.options.colorPreset]?this.options.colorPreset:R(this.container);for(let[n,l]of Object.entries(a))(this.options[n]===null||this.options[n]===void 0)&&(this.options[n]=l,this._presetKeys.push(n));let o=X[this.options.waveformStyle];o&&(s.barWidth===void 0&&i.barWidth===void 0&&(this.options.barWidth=o.barWidth),s.barSpacing===void 0&&i.barSpacing===void 0&&(this.options.barSpacing=o.barSpacing)),this.audio=null,this.canvas=null,this.ctx=null,this.waveformData=[],this.progress=0,this._activeMarkerIndex=-1,this._markerLabelTimer=null,this.isPlaying=!1,this.isLoading=!1,this.hasError=!1,this.updateTimer=null,this.resizeObserver=null,this._ac=new AbortController,this.id=this.container.id||nt(this.options.url),e.instances.set(this.id,this),e._watchTheme();try{this.init()}catch(n){throw e.instances.delete(this.id),this._ac.abort(),n}setTimeout(()=>{this._emit("waveformplayer:ready",{player:this,url:this.options.url})},100)}_emit(t,i,s=!1){let r=new CustomEvent(t,{bubbles:!0,cancelable:s,detail:i});return this.container.dispatchEvent(r),r}_requestSeek(t){this._emit("waveformplayer:request-seek",{...this._buildTrackDetail(),percent:t},!0).defaultPrevented||(this.progress=t,this.drawWaveform?.())}init(){this.createDOM(),this.createAudio(),this.initPlaybackSpeed(),this.initKeyboardControls(),this.initSeekControl(),this.bindEvents(),this.setupResizeObserver(),requestAnimationFrame(()=>{this.resizeCanvas(),this.options.url&&this.load(this.options.url).then(()=>{this.options.autoplay&&this.play()?.catch(()=>{})}).catch(t=>{console.error("[WaveformPlayer] Failed to load audio:",t)})})}createDOM(){this.container.innerHTML="",this.container.className="waveform-player";let t=Q.includes(this.options.buttonAlign)?this.options.buttonAlign:"auto";t==="auto"&&(this.options.waveformStyle==="bars"?t="bottom":t="center"),this.options.layout==="preview"&&this.container.classList.add("waveform-layout-preview"),this.container.classList.toggle("waveform-theme-light",this._scheme==="light");let s=[];this.options.buttonSize!=null&&s.push(`--wfp-btn-size: ${q(this.options.buttonSize)}`),this.options.buttonRadius!=null&&s.push(`--wfp-btn-radius: ${q(this.options.buttonRadius)}`);let r=s.length?` style="${s.join("; ")};"`:"",a=this.options.artworkPosition==="button"&&this.options.artwork,o=a?` `:"",n=this.options.showControls?`
${o}
${this.options.playIcon}
@@ -18,6 +18,7 @@ function $(e){let t=-1/0;for(let i=0;it&&(t=e[i]);return t}fu
${this.options.artist?`${S(this.options.artist)} `:""}
+ ${this.options.showAlbum&&this.options.album?`${S(this.options.album)} `:""}
${this.options.showBPM?`
@@ -61,4 +62,4 @@ function $(e){let t=-1/0;for(let i=0;it&&(t=e[i]);return t}fu
${l}
-`,this.playBtn=this.container.querySelector(".waveform-btn"),this.canvas=this.container.querySelector("canvas"),this.ctx=this.canvas.getContext("2d"),this.titleEl=this.container.querySelector(".waveform-title"),this.artistEl=this.container.querySelector(".waveform-artist"),this.artworkEl=this.container.querySelector(".waveform-artwork, .waveform-btn-artwork"),this.bindArtworkFallback(this.artworkEl),this.currentTimeEl=this.container.querySelector(".time-current"),this.totalTimeEl=this.container.querySelector(".time-total"),this.bpmEl=this.container.querySelector(".waveform-bpm"),this.bpmValueEl=this.container.querySelector(".bpm-value"),this.loadingEl=this.container.querySelector(".waveform-loading"),this.errorEl=this.container.querySelector(".waveform-error"),this.markersContainer=this.container.querySelector(".waveform-markers"),this.speedBtn=this.container.querySelector(".speed-btn"),this.speedMenu=this.container.querySelector(".speed-menu"),this.resizeCanvas(),this.updateBPMDisplay()}bindArtworkFallback(t){t&&t.addEventListener("error",()=>{t.src.startsWith("data:")||(t.src=Ot)},{signal:this._ac.signal})}createArtworkElement(){let t=document.createElement("img");return t.className="waveform-artwork",t.style.width="40px",t.style.height="40px",t.style.borderRadius="4px",t.style.objectFit="cover",t.style.flexShrink="0",this.bindArtworkFallback(t),t}createButtonArtworkElement(){let t=document.createElement("img");return t.className="waveform-btn-artwork",t.alt="",t.setAttribute("aria-hidden","true"),this.bindArtworkFallback(t),t}createArtistElement(){let t=document.createElement("span");return t.className="waveform-artist",t}syncArtist(t){if(this.options.artist=t||null,!!this.options.showInfo){if(!t){this.artistEl?.remove(),this.artistEl=null;return}if(!this.artistEl){let i=this.container.querySelector(".waveform-title");if(!i)return;this.artistEl=this.createArtistElement(),i.after(this.artistEl)}this.artistEl.textContent=t,this.artistEl.style.display=""}}syncButtonArtwork(t){if(this.playBtn){if(!t){this.artworkEl?.remove(),this.artworkEl=null,this.playBtn.classList.remove("waveform-btn-has-artwork");return}this.artworkEl||(this.artworkEl=this.createButtonArtworkElement(),this.playBtn.prepend(this.artworkEl)),this.artworkEl.src=t,this.playBtn.classList.add("waveform-btn-has-artwork")}}syncArtwork(t,i=""){if(this.options.artwork=t||null,this.options.artworkAlt=i||"",this.options.artworkPosition==="button"){this.syncButtonArtwork(this.options.artwork);return}if(this.options.showInfo){if(!t){this.artworkEl?.remove(),this.artworkEl=null;return}if(!this.artworkEl){let s=this.container.querySelector(".waveform-text");if(!s)return;this.artworkEl=this.createArtworkElement(),s.before(this.artworkEl)}this.artworkEl.src=t,this.artworkEl.alt=i||""}}createAudio(){if(this.options.audioMode==="external"){this.audio=null;return}this.audio=new Audio,this.audio.preload=this.options.preload||"metadata",this.options.crossOrigin&&(this.audio.crossOrigin=this.options.crossOrigin)}initPlaybackSpeed(){this.audio&&this.options.playbackRate&&this.options.playbackRate!==1&&(this.audio.playbackRate=this.options.playbackRate),this.options.showPlaybackSpeed&&this.initSpeedControls()}initSpeedControls(){let t=this.container.querySelector(".speed-btn"),i=this.container.querySelector(".speed-menu");if(!t||!i)return;let s=()=>Array.from(i.querySelectorAll(".speed-option")),r=()=>i.style.display!=="none",a=l=>{if(i.style.display=l?"block":"none",t.setAttribute("aria-expanded",l?"true":"false"),l){let h=s();(h.find(c=>c.getAttribute("aria-checked")==="true")||h[0])?.focus()}},o=l=>{let h=s();h.length&&h[(l+h.length)%h.length].focus()},n=l=>{this.setPlaybackRate(parseFloat(l.dataset.rate)),a(!1),t.focus()};t.addEventListener("click",l=>{l.stopPropagation(),a(!r())},{signal:this._ac.signal}),document.addEventListener("click",()=>a(!1),{signal:this._ac.signal}),i.addEventListener("click",l=>{l.stopPropagation();let h=l.target.closest(".speed-option");h&&n(h)},{signal:this._ac.signal}),t.closest(".waveform-speed")?.addEventListener("keydown",l=>{let h=s(),c=h.indexOf(document.activeElement);if(!r()){(l.key==="ArrowDown"||l.key==="ArrowUp")&&document.activeElement===t&&(l.preventDefault(),a(!0));return}switch(l.key){case"ArrowDown":l.preventDefault(),o(c<0?0:c+1);break;case"ArrowUp":l.preventDefault(),o(c<0?h.length-1:c-1);break;case"Home":l.preventDefault(),o(0);break;case"End":l.preventDefault(),o(h.length-1);break;case"Escape":l.preventDefault(),a(!1),t.focus();break;case"Tab":a(!1);break}},{signal:this._ac.signal}),this.updateSpeedUI()}initKeyboardControls(){this.container.setAttribute("tabindex","-1"),this.container.addEventListener("click",t=>{t.target.closest(Wt)||(e.getAllInstances().forEach(i=>{i!==this&&i.container.setAttribute("tabindex","-1")}),this.container.setAttribute("tabindex","0"),this.container.focus())},{signal:this._ac.signal}),this.container.addEventListener("keydown",t=>{if(document.activeElement!==this.container)return;let i=t.key,s=!!this.audio,r=s?this.audio.currentTime:0;if(s&&i>="0"&&i<="9"){t.preventDefault(),this.seekToPercent(parseInt(i)/10);return}let a={" ":()=>this.togglePlay()};s&&(a.ArrowLeft=()=>this.seekTo(m(r-5,0,this.audio.duration)),a.ArrowRight=()=>this.seekTo(m(r+5,0,this.audio.duration)),a.ArrowUp=()=>this.setVolume(m(this.audio.volume+.1)),a.ArrowDown=()=>this.setVolume(m(this.audio.volume-.1)),a.m=a.M=()=>this.audio.muted=!this.audio.muted),a[i]&&(t.preventDefault(),a[i]())},{signal:this._ac.signal})}initSeekControl(){this.options.accessibleSeek&&(this.seekEl=this.container.querySelector(".waveform-container"),this.seekEl&&(this.seekEl.setAttribute("role","slider"),this.seekEl.setAttribute("tabindex","0"),this.seekEl.setAttribute("aria-valuemin","0"),this.applySeekLabel(),this.updateSeekAccessibility(),this.seekEl.addEventListener("keydown",t=>{if(t.key===" "||t.key==="Spacebar"){t.preventDefault(),t.stopPropagation(),this.togglePlay();return}let i=this.getSeekDuration();if(!i)return;let s=this.getSeekCurrentTime(),r;switch(t.key){case"ArrowLeft":case"ArrowDown":r=s-bt;break;case"ArrowRight":case"ArrowUp":r=s+bt;break;case"PageDown":r=s-wt;break;case"PageUp":r=s+wt;break;case"Home":r=0;break;case"End":r=i;break;default:return}t.preventDefault(),t.stopPropagation(),this.seekToSeconds(r)},{signal:this._ac.signal})))}getSeekDuration(){return this.options.audioMode==="external"?this._extDuration||0:this.audio&&Number.isFinite(this.audio.duration)?this.audio.duration:0}getSeekCurrentTime(){return this.options.audioMode==="external"?this.progress*(this._extDuration||0):this.audio&&Number.isFinite(this.audio.currentTime)?this.audio.currentTime:0}seekToSeconds(t){let i=this.getSeekDuration();if(!i)return;let s=m(t,0,i);if(this.options.audioMode==="external"){this._requestSeek(s/i),this.updateSeekAccessibility();return}this.seekTo(s)}applySeekLabel(t=this.options.title){if(!this.seekEl)return;let i=this.options.seekLabel||t||"Seek";this.seekEl.setAttribute("aria-label",i)}updateSeekAccessibility(){if(!this.seekEl)return;let t=this.getSeekDuration(),i=Math.min(this.getSeekCurrentTime(),t);this.seekEl.setAttribute("aria-valuemax",String(Math.round(t))),this.seekEl.setAttribute("aria-valuenow",String(Math.round(i))),this.seekEl.setAttribute("aria-valuetext",ot(this.options.seekValueText||"%1$s of %2$s",E(i),E(t)))}initMediaSession(){if(!("mediaSession"in navigator)||!this.options.enableMediaSession||!this.audio)return;this._applyMediaMetadata(),navigator.mediaSession.setActionHandler("play",()=>this.play()),navigator.mediaSession.setActionHandler("pause",()=>this.pause()),navigator.mediaSession.setActionHandler("seekbackward",()=>{this.seekTo(m(this.audio.currentTime-10,0,this.audio.duration))}),navigator.mediaSession.setActionHandler("seekforward",()=>{this.seekTo(m(this.audio.currentTime+10,0,this.audio.duration))}),navigator.mediaSession.setActionHandler("seekto",s=>{s.seekTime!==null&&this.seekTo(s.seekTime)});let t=this.options.onNextTrack,i=this.options.onPreviousTrack;try{navigator.mediaSession.setActionHandler("nexttrack",typeof t=="function"?()=>t(this):null)}catch{}try{navigator.mediaSession.setActionHandler("previoustrack",typeof i=="function"?()=>i(this):null)}catch{}}_applyMediaMetadata(){!("mediaSession"in navigator)||!this.options.enableMediaSession||(navigator.mediaSession.metadata=new MediaMetadata({title:this.options.title||this.options.unknownTrackText,artist:this.options.artist||"",album:this.options.album||"",artwork:this.options.artwork?[{src:this.options.artwork,sizes:"512x512",type:"image/jpeg"}]:[]}))}_updateMediaSession(t){if(!(!("mediaSession"in navigator)||!this.options.enableMediaSession||!this.audio))try{t==="playing"&&this.initMediaSession(),navigator.mediaSession.playbackState=t;let i=this.audio.duration;navigator.mediaSession.setPositionState&&i&&isFinite(i)&&navigator.mediaSession.setPositionState({duration:i,playbackRate:this.audio.playbackRate||1,position:m(this.audio.currentTime,0,i)})}catch{}}bindEvents(){this.playBtn&&this.playBtn.addEventListener("click",()=>this.togglePlay()),this.audio&&(this.audio.addEventListener("loadstart",()=>this.setLoading(!0)),this.audio.addEventListener("loadedmetadata",()=>this.onMetadataLoaded()),this.audio.addEventListener("canplay",()=>this.setLoading(!1)),this.audio.addEventListener("play",()=>this.onPlay()),this.audio.addEventListener("pause",()=>this.onPause()),this.audio.addEventListener("ended",()=>this.onEnded()),this.audio.addEventListener("error",i=>this.onError(i))),this.canvas.addEventListener("click",i=>this.handleCanvasClick(i)),this._dragging=!1,this._seekHover=!1,this._handleNear=!1,this.canvas.addEventListener("pointerenter",()=>{this._seekHover=!0,this.drawWaveform(),this._updateSeekHandle()}),this.canvas.addEventListener("pointerleave",()=>{this._seekHover=!1,this._handleNear=!1,this._dragging||this._hideHoverTip(),this.drawWaveform(),this._updateSeekHandle()}),this.canvas.addEventListener("pointerdown",i=>{if(!(i.pointerType==="mouse"&&i.button!==0)){this._dragging=!0;try{this.canvas.setPointerCapture(i.pointerId)}catch{}this._scrubTo(i.clientX)}}),this.canvas.addEventListener("pointermove",i=>{if(this._dragging){this._scrubTo(i.clientX);return}let s=this.canvas.getBoundingClientRect();s.width&&(this._handleNear=Math.abs(i.clientX-s.left-this.progress*s.width)<=10,this._updateSeekHandle())});let t=i=>{if(this._dragging){this._dragging=!1,this._suppressClick=!0;try{this.canvas.releasePointerCapture(i.pointerId)}catch{}this._seekFromPointer(i.clientX),!this._seekHover&&!this.options.showHoverTime&&this._hideHoverTip(),this._updateSeekHandle()}};this.canvas.addEventListener("pointerup",t),this.canvas.addEventListener("pointercancel",t),this.setupHoverTime(),this.setupSeekHandle(),this.resizeHandler=ht(()=>this.resizeCanvas(),100),window.addEventListener("resize",this.resizeHandler)}setupResizeObserver(){"ResizeObserver"in window&&(this.resizeObserver=new ResizeObserver(()=>{this.resizeCanvas()}),this.canvas?.parentElement&&this.resizeObserver.observe(this.canvas.parentElement))}async load(t){try{this.setLoading(!0),this.progress=0,this.hasError=!1,this.container.classList.remove("waveform-is-placeholder");let i=!!this.options.waveform;i&&this.setWaveformData(this.options.waveform);let s=this.options.title||O(t);if(this.titleEl&&(this.titleEl.textContent=s),this.applySeekLabel(s),this.audio&&(this.audio.src=t,this.audio.preload!=="none"&&await new Promise((r,a)=>{let o=()=>{this.audio.removeEventListener("loadedmetadata",o),this.audio.removeEventListener("error",n),r()},n=l=>{this.audio.removeEventListener("loadedmetadata",o),this.audio.removeEventListener("error",n),a(l)};this.audio.addEventListener("loadedmetadata",o),this.audio.addEventListener("error",n)})),!i)try{let r=await G(t,this.options.samples,this.options.showBPM);this.waveformData=r.peaks,r.bpm&&(this.detectedBPM=r.bpm,this.updateBPMDisplay())}catch(r){console.warn("[WaveformPlayer] Using placeholder waveform:",r),this.waveformData=yt(this.options.samples),this.container.classList.add("waveform-is-placeholder")}this.drawWaveform(),this.renderMarkers(),this.options.onLoad&&this.options.onLoad(this)}catch(i){this.onError(i)}finally{this.setLoading(!1)}}async loadTrack(t,i=null,s=null,r={}){let a=Object.prototype.hasOwnProperty.call(r,"artwork"),o=Object.prototype.hasOwnProperty.call(r,"artworkAlt");this.isPlaying&&this.pause(),this.audio&&(this.audio.src="",this.audio.load()),this.hasError=!1,this.errorEl&&(this.errorEl.style.display="none"),this.canvas&&(this.canvas.style.opacity="1"),this.playBtn&&(this.playBtn.disabled=!1),this.progress=0,this.waveformData=[],this.options=Z(j(this.options,{url:t,title:i===null?this.options.title:i,artist:s===null?this.options.artist:s,...r})),a&&(this.options.artwork=r.artwork||null),o?this.options.artworkAlt=r.artworkAlt||"":a&&(this.options.artworkAlt=this.options.artwork?W.artworkAlt:""),r.preload&&this.audio&&(this.audio.preload=this.options.preload),r.crossOrigin&&this.audio&&(this.audio.crossOrigin=this.options.crossOrigin),s!==null&&this.syncArtist(s),(a||o)&&this.syncArtwork(a?r.artwork:this.options.artwork,o?r.artworkAlt:this.options.artworkAlt),this.options.markers=r.markers?z(r.markers):[],this.options.waveform=r.waveform||null,await this.load(t),r.autoplay!==!1&&this.play()?.catch(()=>{})}setWaveformData(t){if(typeof t=="string"&&t.trim().endsWith(".json")){fetch(t.trim()).then(i=>i.json()).then(i=>{this.waveformData=Array.isArray(i)?i:i.peaks||[],i.markers&&!this.options.markers?.length&&(this.options.markers=z(i.markers),this.renderMarkers()),this.drawWaveform()}).catch(()=>{});return}this.waveformData=D(t,{fallback:[]}),this.drawWaveform()}drawWaveform(){!this.ctx||this.waveformData.length===0||ft(this.ctx,this.canvas,this.waveformData,this.progress,{...this.options,waveformStyle:this.options.waveformStyle||"bars",color:this.options.waveformColor,progressColor:this.options.progressColor,seekActive:this._seekHover||this._dragging})}resizeCanvas(){if(!this.canvas||this.isDestroying)return;let t=window.devicePixelRatio||1,i=this.canvas.parentElement.getBoundingClientRect();this.canvas.width=i.width*t,this.canvas.height=this.options.height*t,this.canvas.parentElement.style.height=this.options.height+"px",this.drawWaveform()}renderMarkers(){if(!this.markersContainer||(this.markersContainer.innerHTML="",this._activeMarkerIndex=-1,clearTimeout(this._markerLabelTimer),!this.options.showMarkers||!this.options.markers?.length))return;let t=this.getSeekDuration();t&&this.options.markers.forEach((i,s)=>{if(i.time>t){console.warn(`[WaveformPlayer] Marker "${i.label}" at ${i.time}s exceeds audio duration of ${t}s`);return}let r=i.time/t*100,a=document.createElement("button");a.className="waveform-marker",a.style.left=`${r}%`,a.style.backgroundColor=i.color||"rgba(255, 255, 255, 0.5)",a.setAttribute("aria-label",i.label),a.setAttribute("data-time",i.time);let o=document.createElement("span");o.className="waveform-marker-tooltip",o.textContent=i.label,a.appendChild(o),a.addEventListener("click",n=>{n.stopPropagation(),this.seekTo(i.time),this.options.playOnSeek&&!this.isPlaying&&this.play()}),this.markersContainer.appendChild(a)})}setActiveMarker(t){if(!this.markersContainer)return;this.markersContainer.querySelectorAll(".waveform-marker").forEach((s,r)=>s.classList.toggle("active",r===t))}updateActiveMarker(){if(!this.markersContainer)return;let t=this.markersContainer.querySelectorAll(".waveform-marker");if(!t.length)return;let i=this.getSeekDuration(),s=i?this.progress*i:0,r=-1,a=-1/0;t.forEach((o,n)=>{let l=parseFloat(o.getAttribute("data-time"));Number.isFinite(l)&&l<=s+.05&&l>a&&(a=l,r=n)}),r!==this._activeMarkerIndex&&(this._activeMarkerIndex=r,this.setActiveMarker(r),clearTimeout(this._markerLabelTimer),t.forEach((o,n)=>o.classList.toggle("show-label",n===r)),r>=0&&(this._markerLabelTimer=setTimeout(()=>{this.markersContainer?.querySelectorAll(".waveform-marker").forEach(o=>o.classList.remove("show-label"))},2500)))}setupHoverTime(){if(!this.seekEl)return;let t=document.createElement("div");t.className="waveform-hover-time",t.setAttribute("aria-hidden","true"),this.seekEl.appendChild(t),this.hoverTimeEl=t,this.options.showHoverTime&&(this.seekEl.addEventListener("pointermove",i=>{this._dragging||this._updateHoverTip(i.clientX)}),this.seekEl.addEventListener("pointerleave",()=>{this._dragging||this._hideHoverTip()}))}_updateHoverTip(t){let i=this.hoverTimeEl;if(!i)return;let s=this.getSeekDuration();if(!s){i.style.opacity="0";return}let r=this.canvas.getBoundingClientRect(),a=m((t-r.left)/r.width);i.textContent=E(a*s),i.style.left=a*100+"%",i.style.opacity="1"}_hideHoverTip(){this.hoverTimeEl&&(this.hoverTimeEl.style.opacity="0")}_scrubTo(t){let i=this.canvas.getBoundingClientRect();if(!i.width)return;this.progress=m((t-i.left)/i.width),this.drawWaveform(),this._updateSeekHandle();let s=this.getSeekDuration();s&&this.currentTimeEl?(this.currentTimeEl.textContent=E(this.progress*s),this._hideHoverTip()):this._updateHoverTip(t)}setupSeekHandle(){if(!this.options.seekHandle||this.options.waveformStyle!=="seekbar"||!this.seekEl)return;let t=document.createElement("div");t.className="waveform-seek-handle",t.setAttribute("aria-hidden","true"),this.seekEl.appendChild(t),this.seekHandleEl=t}_updateSeekHandle(){let t=this.seekHandleEl;t&&(t.style.left=this.progress*100+"%",t.classList.toggle("is-visible",this._seekHover||this._dragging),t.classList.toggle("is-active",this._dragging||this._handleNear))}handleCanvasClick(t){if(this._suppressClick){this._suppressClick=!1;return}this._seekFromPointer(t.clientX)}_seekFromPointer(t){let i=this.canvas.getBoundingClientRect();if(!i.width)return;let s=m((t-i.left)/i.width);if(this.options.audioMode==="external"){this._requestSeek(s);return}!this.audio||!this.audio.duration||this.seekToPercent(s)}setLoading(t){if(this.isLoading=t,this.loadingEl){let i=t&&this.waveformData.length===0;this.loadingEl.style.display=i?"block":"none"}this.seekEl&&this.seekEl.setAttribute("aria-busy",t?"true":"false")}onMetadataLoaded(){this.isDestroying||(this.totalTimeEl&&(this.totalTimeEl.textContent=E(this.audio.duration)),this.renderMarkers(),this.updateSeekAccessibility())}setPlayButtonState(t){if(!this.playBtn)return;this.playBtn.classList.toggle("playing",t);let i=this.playBtn.querySelector(".waveform-icon-play"),s=this.playBtn.querySelector(".waveform-icon-pause");i&&(i.style.display=t?"none":"flex"),s&&(s.style.display=t?"flex":"none")}onPlay(){this.isDestroying||(this.isPlaying=!0,this.setPlayButtonState(!0),this.startSmoothUpdate(),this._updateMediaSession("playing"),this._emit("waveformplayer:play",{player:this,url:this.options.url}),this.options.onPlay&&this.options.onPlay(this))}onPause(){this.isDestroying||(this.isPlaying=!1,this.setPlayButtonState(!1),this.stopSmoothUpdate(),this._updateMediaSession("paused"),this._emit("waveformplayer:pause",{player:this,url:this.options.url}),this.options.onPause&&this.options.onPause(this))}onEnded(){if(this.isDestroying)return;let t=this.audio.duration;this.progress=0,this.audio.currentTime=0,this.drawWaveform(),this.currentTimeEl&&(this.currentTimeEl.textContent="0:00"),this._emit("waveformplayer:ended",{player:this,url:this.options.url,currentTime:t,duration:t}),this.onPause(),this.options.onEnd&&this.options.onEnd(this)}onError(t){this.isDestroying||(console.error("[WaveformPlayer] Audio error:",t),this.hasError=!0,this.setLoading(!1),this.errorEl&&(this.errorEl.style.display="flex"),this.canvas&&(this.canvas.style.opacity="0.2"),this.playBtn&&(this.playBtn.disabled=!0),this.options.onError&&this.options.onError(t,this))}startSmoothUpdate(){this.stopSmoothUpdate();let t=()=>{this.isPlaying&&this.audio&&this.audio.duration&&(this.updateProgress(),this.updateTimer=requestAnimationFrame(t))};this.updateTimer=requestAnimationFrame(t)}stopSmoothUpdate(){this.updateTimer&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null)}updateProgress(){if(!this.audio||!this.audio.duration||this._dragging)return;let t=this.audio.currentTime/this.audio.duration;Math.abs(t-this.progress)>.001&&(this.progress=t,this.drawWaveform(),this._updateSeekHandle()),this.currentTimeEl&&(this.currentTimeEl.textContent=E(this.audio.currentTime)),this._emit("waveformplayer:timeupdate",{player:this,currentTime:this.audio.currentTime,duration:this.audio.duration,progress:this.progress,url:this.options.url}),this.options.onTimeUpdate&&this.options.onTimeUpdate(this.audio.currentTime,this.audio.duration,this),this.updateActiveMarker(),this.updateSeekAccessibility()}updateBPMDisplay(){let t=this.options.bpm||this.detectedBPM;this.bpmEl&&this.bpmValueEl&&t&&(this.bpmValueEl.textContent=Math.round(t),this.bpmEl.style.display="inline-flex")}refreshTheme(){if(!this._autoTheme)return;this._scheme=R(this.container);let t=J(this.options.colorPreset,this.container);for(let i of this._presetKeys||[])i in t&&(this.options[i]=t[i]);this._applyThemeColors()}_applyThemeColors(){this.container.classList.toggle("waveform-theme-light",this._scheme==="light"),this.canvas&&this.drawWaveform()}static _watchTheme(){if(e._themeWatch||typeof document>"u")return;let t=()=>requestAnimationFrame(()=>{e.instances.forEach(a=>{try{a.refreshTheme()}catch{}})}),i={attributes:!0,attributeFilter:["class","data-theme","data-color-scheme","style"]},s=new MutationObserver(t);s.observe(document.documentElement,i),document.body&&s.observe(document.body,i);let r=null;try{r=window.matchMedia("(prefers-color-scheme: dark)"),r.addEventListener("change",t)}catch{}e._themeWatch={obs:s,mq:r,refresh:t}}updateSpeedUI(){if(!this.audio)return;let t=this.container.querySelector(".speed-value");if(t){let i=this.audio.playbackRate;t.textContent=i===1?"1x":`${i}x`}this.container.querySelectorAll(".speed-option").forEach(i=>{let s=parseFloat(i.dataset.rate)===this.audio.playbackRate;i.classList.toggle("active",s),i.setAttribute("aria-checked",s?"true":"false")})}play(){if(this.options.singlePlay&&e.currentlyPlaying&&e.currentlyPlaying!==this&&e.currentlyPlaying.pause(),this.options.audioMode==="external"){this._emit("waveformplayer:request-play",this._buildTrackDetail(),!0).defaultPrevented||(e.currentlyPlaying=this);return}return e.currentlyPlaying=this,this.audio.play()}pause(){if(e.currentlyPlaying===this&&(e.currentlyPlaying=null),this.options.audioMode==="external"){this._emit("waveformplayer:request-pause",this._buildTrackDetail(),!0);return}this.audio.pause()}_buildTrackDetail(){return{url:this.options.url,title:this.options.title,artist:this.options.artist,artwork:this.options.artwork,markers:this.options.markers,waveform:this.options.waveform,id:this.id,player:this}}setPlayingState(t){let i=this.isPlaying;this.isPlaying=!!t,this.setPlayButtonState(this.isPlaying),this.isPlaying&&!i?(this.startSmoothUpdate?.(),this._emit("waveformplayer:play",{player:this,url:this.options.url}),this.options.onPlay&&this.options.onPlay(this)):!this.isPlaying&&i&&(this.stopSmoothUpdate?.(),this._emit("waveformplayer:pause",{player:this,url:this.options.url}),this.options.onPause&&this.options.onPause(this))}setProgress(t,i){!i||i<=0||(this.progress=m(t/i),this.currentTimeEl&&(this.currentTimeEl.textContent=E(t)),this._extDuration=i,this.totalTimeEl&&(!this.totalTimeEl.dataset._extSet||this.totalTimeEl.dataset._extDur!==String(i))&&(this.totalTimeEl.textContent=E(i),this.totalTimeEl.dataset._extSet="1",this.totalTimeEl.dataset._extDur=String(i)),this.drawWaveform?.(),this.updateActiveMarker(),this._emit("waveformplayer:timeupdate",{player:this,currentTime:t,duration:i,progress:this.progress,url:this.options.url}),this.options.onTimeUpdate&&this.options.onTimeUpdate(t,i,this),this.progress>=1?this._extEnded||(this._extEnded=!0,this._emit("waveformplayer:ended",{player:this,url:this.options.url,currentTime:i,duration:i}),this.options.onEnd&&this.options.onEnd(this)):this._extEnded=!1,this.updateSeekAccessibility())}togglePlay(){this.isPlaying?this.pause():this.play()}seekTo(t){this.audio&&this.audio.duration&&(this.audio.currentTime=m(t,0,this.audio.duration),this.updateProgress())}seekToPercent(t){this.audio&&this.audio.duration&&(this.audio.currentTime=this.audio.duration*m(t),this.updateProgress())}setVolume(t){let i=Number(t);this.audio&&Number.isFinite(i)&&(this.audio.volume=m(i))}setPlaybackRate(t){if(!this.audio)return;let i=_(t,null,{min:N,max:F});i!==null&&(this.audio.playbackRate=i,this.options.playbackRate=i,this.updateSpeedUI())}destroy(){this.isDestroying=!0,this._emit("waveformplayer:destroy",{player:this,url:this.options.url}),this.pause(),this.stopSmoothUpdate(),clearTimeout(this._markerLabelTimer),this._ac?.abort(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),e.instances.delete(this.id),e.currentlyPlaying===this&&(e.currentlyPlaying=null),this.audio&&(this.audio.pause(),this.audio.src="",this.audio.load(),this.audio=null),this.container.innerHTML="",delete this.container.dataset.waveformInitialized,this.canvas=null,this.ctx=null,this.playBtn=null,this.waveformData=[]}static getInstance(t){if(typeof t=="string"){let i=this.instances.get(t);if(i)return i;let s=document.getElementById(t);if(s)return Array.from(this.instances.values()).find(r=>r.container===s)}if(t instanceof HTMLElement)return Array.from(this.instances.values()).find(i=>i.container===t)}static getAllInstances(){return Array.from(this.instances.values())}static destroyAll(){this.instances.forEach(t=>t.destroy()),this.instances.clear()}static async generateWaveformData(t,i=1800){try{return(await G(t,i)).peaks}catch(s){throw console.error("[WaveformPlayer] Failed to generate waveform:",s),s}}static getPeaksUrl(t){if(!t)return;let i=t.replace(/\.(mp3|wav|ogg|flac|m4a|aac)(\?[^#]*)?(#.*)?$/i,".json$2$3");return i===t?void 0:i}};T.utils={formatTime:E,extractTitleFromUrl:O,escapeHtml:S,isSafeHref:st,parseDataAttributes:I,detectColorScheme:R};var et=()=>typeof window<"u"&&typeof document<"u",Nt=()=>document.documentElement?.dataset.waveformAutoinit==="false";function kt(e){if(!(e.dataset.waveformInitialized==="true"||T.getInstance(e)))try{new T(e),e.dataset.waveformInitialized="true"}catch(t){console.error("[WaveformPlayer] Failed to initialize:",t,e)}}function tt(e=document){if(!et())return;let t=e||document;t.matches?.("[data-waveform-player]")&&kt(t),t.querySelectorAll("[data-waveform-player]").forEach(kt)}et()&&!Nt()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>tt()):tt());T.init=tt;et()&&(window.WaveformPlayer=T);var se=T;export{T as WaveformPlayer,se as default};
+`,this.playBtn=this.container.querySelector(".waveform-btn"),this.canvas=this.container.querySelector("canvas"),this.ctx=this.canvas.getContext("2d"),this.titleEl=this.container.querySelector(".waveform-title"),this.artistEl=this.container.querySelector(".waveform-artist"),this.albumEl=this.container.querySelector(".waveform-album"),this.artworkEl=this.container.querySelector(".waveform-artwork, .waveform-btn-artwork"),this.bindArtworkFallback(this.artworkEl),this.currentTimeEl=this.container.querySelector(".time-current"),this.totalTimeEl=this.container.querySelector(".time-total"),this.bpmEl=this.container.querySelector(".waveform-bpm"),this.bpmValueEl=this.container.querySelector(".bpm-value"),this.loadingEl=this.container.querySelector(".waveform-loading"),this.errorEl=this.container.querySelector(".waveform-error"),this.markersContainer=this.container.querySelector(".waveform-markers"),this.speedBtn=this.container.querySelector(".speed-btn"),this.speedMenu=this.container.querySelector(".speed-menu"),this.resizeCanvas(),this.updateBPMDisplay()}bindArtworkFallback(t){t&&t.addEventListener("error",()=>{t.src.startsWith("data:")||(t.src=It)},{signal:this._ac.signal})}createArtworkElement(){let t=document.createElement("img");return t.className="waveform-artwork",t.style.width="40px",t.style.height="40px",t.style.borderRadius="4px",t.style.objectFit="cover",t.style.flexShrink="0",this.bindArtworkFallback(t),t}createButtonArtworkElement(){let t=document.createElement("img");return t.className="waveform-btn-artwork",t.alt="",t.setAttribute("aria-hidden","true"),this.bindArtworkFallback(t),t}createArtistElement(){let t=document.createElement("span");return t.className="waveform-artist",t}createAlbumElement(){let t=document.createElement("span");return t.className="waveform-album",t}syncArtist(t){if(this.options.artist=t||null,!!this.options.showInfo){if(!t){this.artistEl?.remove(),this.artistEl=null;return}if(!this.artistEl){let i=this.container.querySelector(".waveform-title");if(!i)return;this.artistEl=this.createArtistElement(),i.after(this.artistEl)}this.artistEl.textContent=t,this.artistEl.style.display=""}}syncAlbum(t){if(this.options.album=t||"",!this.options.showInfo||!this.options.showAlbum){this.albumEl?.remove(),this.albumEl=null;return}if(!t){this.albumEl?.remove(),this.albumEl=null;return}if(!this.albumEl){let i=this.artistEl||this.container.querySelector(".waveform-title");if(!i)return;this.albumEl=this.createAlbumElement(),i.after(this.albumEl)}this.albumEl.textContent=t,this.albumEl.style.display=""}syncButtonArtwork(t){if(this.playBtn){if(!t){this.artworkEl?.remove(),this.artworkEl=null,this.playBtn.classList.remove("waveform-btn-has-artwork");return}this.artworkEl||(this.artworkEl=this.createButtonArtworkElement(),this.playBtn.prepend(this.artworkEl)),this.artworkEl.src=t,this.playBtn.classList.add("waveform-btn-has-artwork")}}syncArtwork(t,i=""){if(this.options.artwork=t||null,this.options.artworkAlt=i||"",this.options.artworkPosition==="button"){this.syncButtonArtwork(this.options.artwork);return}if(this.options.showInfo){if(!t){this.artworkEl?.remove(),this.artworkEl=null;return}if(!this.artworkEl){let s=this.container.querySelector(".waveform-text");if(!s)return;this.artworkEl=this.createArtworkElement(),s.before(this.artworkEl)}this.artworkEl.src=t,this.artworkEl.alt=i||""}}createAudio(){if(this.options.audioMode==="external"){this.audio=null;return}this.audio=new Audio,this.audio.preload=this.options.preload||"metadata",this.options.crossOrigin&&(this.audio.crossOrigin=this.options.crossOrigin)}initPlaybackSpeed(){this.audio&&this.options.playbackRate&&this.options.playbackRate!==1&&(this.audio.playbackRate=this.options.playbackRate),this.options.showPlaybackSpeed&&this.initSpeedControls()}initSpeedControls(){let t=this.container.querySelector(".speed-btn"),i=this.container.querySelector(".speed-menu");if(!t||!i)return;let s=()=>Array.from(i.querySelectorAll(".speed-option")),r=()=>i.style.display!=="none",a=l=>{if(i.style.display=l?"block":"none",t.setAttribute("aria-expanded",l?"true":"false"),l){let h=s();(h.find(c=>c.getAttribute("aria-checked")==="true")||h[0])?.focus()}},o=l=>{let h=s();h.length&&h[(l+h.length)%h.length].focus()},n=l=>{this.setPlaybackRate(parseFloat(l.dataset.rate)),a(!1),t.focus()};t.addEventListener("click",l=>{l.stopPropagation(),a(!r())},{signal:this._ac.signal}),document.addEventListener("click",()=>a(!1),{signal:this._ac.signal}),i.addEventListener("click",l=>{l.stopPropagation();let h=l.target.closest(".speed-option");h&&n(h)},{signal:this._ac.signal}),t.closest(".waveform-speed")?.addEventListener("keydown",l=>{let h=s(),c=h.indexOf(document.activeElement);if(!r()){(l.key==="ArrowDown"||l.key==="ArrowUp")&&document.activeElement===t&&(l.preventDefault(),a(!0));return}switch(l.key){case"ArrowDown":l.preventDefault(),o(c<0?0:c+1);break;case"ArrowUp":l.preventDefault(),o(c<0?h.length-1:c-1);break;case"Home":l.preventDefault(),o(0);break;case"End":l.preventDefault(),o(h.length-1);break;case"Escape":l.preventDefault(),a(!1),t.focus();break;case"Tab":a(!1);break}},{signal:this._ac.signal}),this.updateSpeedUI()}initKeyboardControls(){this.container.setAttribute("tabindex","-1"),this.container.addEventListener("click",t=>{t.target.closest(Wt)||(e.getAllInstances().forEach(i=>{i!==this&&i.container.setAttribute("tabindex","-1")}),this.container.setAttribute("tabindex","0"),this.container.focus())},{signal:this._ac.signal}),this.container.addEventListener("keydown",t=>{if(document.activeElement!==this.container)return;let i=t.key,s=!!this.audio,r=s?this.audio.currentTime:0;if(s&&i>="0"&&i<="9"){t.preventDefault(),this.seekToPercent(parseInt(i)/10);return}let a={" ":()=>this.togglePlay()};s&&(a.ArrowLeft=()=>this.seekTo(m(r-5,0,this.audio.duration)),a.ArrowRight=()=>this.seekTo(m(r+5,0,this.audio.duration)),a.ArrowUp=()=>this.setVolume(m(this.audio.volume+.1)),a.ArrowDown=()=>this.setVolume(m(this.audio.volume-.1)),a.m=a.M=()=>this.audio.muted=!this.audio.muted),a[i]&&(t.preventDefault(),a[i]())},{signal:this._ac.signal})}initSeekControl(){this.options.accessibleSeek&&(this.seekEl=this.container.querySelector(".waveform-container"),this.seekEl&&(this.seekEl.setAttribute("role","slider"),this.seekEl.setAttribute("tabindex","0"),this.seekEl.setAttribute("aria-valuemin","0"),this.applySeekLabel(),this.updateSeekAccessibility(),this.seekEl.addEventListener("keydown",t=>{if(t.key===" "||t.key==="Spacebar"){t.preventDefault(),t.stopPropagation(),this.togglePlay();return}let i=this.getSeekDuration();if(!i)return;let s=this.getSeekCurrentTime(),r;switch(t.key){case"ArrowLeft":case"ArrowDown":r=s-gt;break;case"ArrowRight":case"ArrowUp":r=s+gt;break;case"PageDown":r=s-wt;break;case"PageUp":r=s+wt;break;case"Home":r=0;break;case"End":r=i;break;default:return}t.preventDefault(),t.stopPropagation(),this.seekToSeconds(r)},{signal:this._ac.signal})))}getSeekDuration(){return this.options.audioMode==="external"?this._extDuration||0:this.audio&&Number.isFinite(this.audio.duration)?this.audio.duration:0}getSeekCurrentTime(){return this.options.audioMode==="external"?this.progress*(this._extDuration||0):this.audio&&Number.isFinite(this.audio.currentTime)?this.audio.currentTime:0}seekToSeconds(t){let i=this.getSeekDuration();if(!i)return;let s=m(t,0,i);if(this.options.audioMode==="external"){this._requestSeek(s/i),this.updateSeekAccessibility();return}this.seekTo(s)}applySeekLabel(t=this.options.title){if(!this.seekEl)return;let i=this.options.seekLabel||t||"Seek";this.seekEl.setAttribute("aria-label",i)}updateSeekAccessibility(){if(!this.seekEl)return;let t=this.getSeekDuration(),i=Math.min(this.getSeekCurrentTime(),t);this.seekEl.setAttribute("aria-valuemax",String(Math.round(t))),this.seekEl.setAttribute("aria-valuenow",String(Math.round(i))),this.seekEl.setAttribute("aria-valuetext",ot(this.options.seekValueText||"%1$s of %2$s",E(i),E(t)))}initMediaSession(){if(!("mediaSession"in navigator)||!this.options.enableMediaSession||!this.audio)return;this._applyMediaMetadata(),navigator.mediaSession.setActionHandler("play",()=>this.play()),navigator.mediaSession.setActionHandler("pause",()=>this.pause()),navigator.mediaSession.setActionHandler("seekbackward",()=>{this.seekTo(m(this.audio.currentTime-10,0,this.audio.duration))}),navigator.mediaSession.setActionHandler("seekforward",()=>{this.seekTo(m(this.audio.currentTime+10,0,this.audio.duration))}),navigator.mediaSession.setActionHandler("seekto",s=>{s.seekTime!==null&&this.seekTo(s.seekTime)});let t=this.options.onNextTrack,i=this.options.onPreviousTrack;try{navigator.mediaSession.setActionHandler("nexttrack",typeof t=="function"?()=>t(this):null)}catch{}try{navigator.mediaSession.setActionHandler("previoustrack",typeof i=="function"?()=>i(this):null)}catch{}}_applyMediaMetadata(){!("mediaSession"in navigator)||!this.options.enableMediaSession||(navigator.mediaSession.metadata=new MediaMetadata({title:this.options.title||this.options.unknownTrackText,artist:this.options.artist||"",album:this.options.album||"",artwork:this.options.artwork?[{src:this.options.artwork,sizes:"512x512",type:"image/jpeg"}]:[]}))}_updateMediaSession(t){if(!(!("mediaSession"in navigator)||!this.options.enableMediaSession||!this.audio))try{t==="playing"&&this.initMediaSession(),navigator.mediaSession.playbackState=t;let i=this.audio.duration;navigator.mediaSession.setPositionState&&i&&isFinite(i)&&navigator.mediaSession.setPositionState({duration:i,playbackRate:this.audio.playbackRate||1,position:m(this.audio.currentTime,0,i)})}catch{}}bindEvents(){this.playBtn&&this.playBtn.addEventListener("click",()=>this.togglePlay()),this.audio&&(this.audio.addEventListener("loadstart",()=>this.setLoading(!0)),this.audio.addEventListener("loadedmetadata",()=>this.onMetadataLoaded()),this.audio.addEventListener("canplay",()=>this.setLoading(!1)),this.audio.addEventListener("play",()=>this.onPlay()),this.audio.addEventListener("pause",()=>this.onPause()),this.audio.addEventListener("ended",()=>this.onEnded()),this.audio.addEventListener("error",i=>this.onError(i))),this.canvas.addEventListener("click",i=>this.handleCanvasClick(i)),this._dragging=!1,this._seekHover=!1,this._handleNear=!1,this.canvas.addEventListener("pointerenter",()=>{this._seekHover=!0,this.drawWaveform(),this._updateSeekHandle()}),this.canvas.addEventListener("pointerleave",()=>{this._seekHover=!1,this._handleNear=!1,this._dragging||this._hideHoverTip(),this.drawWaveform(),this._updateSeekHandle()}),this.canvas.addEventListener("pointerdown",i=>{if(!(i.pointerType==="mouse"&&i.button!==0)){this._dragging=!0;try{this.canvas.setPointerCapture(i.pointerId)}catch{}this._scrubTo(i.clientX)}}),this.canvas.addEventListener("pointermove",i=>{if(this._dragging){this._scrubTo(i.clientX);return}let s=this.canvas.getBoundingClientRect();s.width&&(this._handleNear=Math.abs(i.clientX-s.left-this.progress*s.width)<=10,this._updateSeekHandle())});let t=i=>{if(this._dragging){this._dragging=!1,this._suppressClick=!0;try{this.canvas.releasePointerCapture(i.pointerId)}catch{}this._seekFromPointer(i.clientX),!this._seekHover&&!this.options.showHoverTime&&this._hideHoverTip(),this._updateSeekHandle()}};this.canvas.addEventListener("pointerup",t),this.canvas.addEventListener("pointercancel",t),this.setupHoverTime(),this.setupSeekHandle(),this.resizeHandler=ht(()=>this.resizeCanvas(),100),window.addEventListener("resize",this.resizeHandler)}setupResizeObserver(){"ResizeObserver"in window&&(this.resizeObserver=new ResizeObserver(()=>{this.resizeCanvas()}),this.canvas?.parentElement&&this.resizeObserver.observe(this.canvas.parentElement))}async load(t){try{this.setLoading(!0),this.progress=0,this.hasError=!1,this.container.classList.remove("waveform-is-placeholder");let i=!!this.options.waveform;i&&this.setWaveformData(this.options.waveform);let s=this.options.title||I(t);if(this.titleEl&&(this.titleEl.textContent=s),this.applySeekLabel(s),this.audio&&(this.audio.src=t,this.audio.preload!=="none"&&await new Promise((r,a)=>{let o=()=>{this.audio.removeEventListener("loadedmetadata",o),this.audio.removeEventListener("error",n),r()},n=l=>{this.audio.removeEventListener("loadedmetadata",o),this.audio.removeEventListener("error",n),a(l)};this.audio.addEventListener("loadedmetadata",o),this.audio.addEventListener("error",n)})),!i)try{let r=await G(t,this.options.samples,this.options.showBPM);this.waveformData=r.peaks,r.bpm&&(this.detectedBPM=r.bpm,this.updateBPMDisplay())}catch(r){console.warn("[WaveformPlayer] Using placeholder waveform:",r),this.waveformData=yt(this.options.samples),this.container.classList.add("waveform-is-placeholder")}this.drawWaveform(),this.renderMarkers(),this.options.onLoad&&this.options.onLoad(this)}catch(i){this.onError(i)}finally{this.setLoading(!1)}}async loadTrack(t,i=null,s=null,r={}){let a=Object.prototype.hasOwnProperty.call(r,"artwork"),o=Object.prototype.hasOwnProperty.call(r,"artworkAlt"),n=Object.prototype.hasOwnProperty.call(r,"album"),l=Object.prototype.hasOwnProperty.call(r,"showAlbum");this.isPlaying&&this.pause(),this.audio&&(this.audio.src="",this.audio.load()),this.hasError=!1,this.errorEl&&(this.errorEl.style.display="none"),this.canvas&&(this.canvas.style.opacity="1"),this.playBtn&&(this.playBtn.disabled=!1),this.progress=0,this.waveformData=[],this.options=Z(j(this.options,{url:t,title:i===null?this.options.title:i,artist:s===null?this.options.artist:s,...r})),a&&(this.options.artwork=r.artwork||null),o?this.options.artworkAlt=r.artworkAlt||"":a&&(this.options.artworkAlt=this.options.artwork?W.artworkAlt:""),r.preload&&this.audio&&(this.audio.preload=this.options.preload),r.crossOrigin&&this.audio&&(this.audio.crossOrigin=this.options.crossOrigin),s!==null&&this.syncArtist(s),(n||l)&&this.syncAlbum(this.options.album),(a||o)&&this.syncArtwork(a?r.artwork:this.options.artwork,o?r.artworkAlt:this.options.artworkAlt),this.options.markers=r.markers?$(r.markers):[],this.options.waveform=r.waveform||null,await this.load(t),r.autoplay!==!1&&this.play()?.catch(()=>{})}setWaveformData(t){if(typeof t=="string"&&t.trim().endsWith(".json")){fetch(t.trim()).then(i=>i.json()).then(i=>{this.waveformData=Array.isArray(i)?i:i.peaks||[],i.markers&&!this.options.markers?.length&&(this.options.markers=$(i.markers),this.renderMarkers()),this.drawWaveform()}).catch(()=>{});return}this.waveformData=D(t,{fallback:[]}),this.drawWaveform()}drawWaveform(){!this.ctx||this.waveformData.length===0||ft(this.ctx,this.canvas,this.waveformData,this.progress,{...this.options,waveformStyle:this.options.waveformStyle||"bars",color:this.options.waveformColor,progressColor:this.options.progressColor,seekActive:this._seekHover||this._dragging})}resizeCanvas(){if(!this.canvas||this.isDestroying)return;let t=window.devicePixelRatio||1,i=this.canvas.parentElement.getBoundingClientRect();this.canvas.width=i.width*t,this.canvas.height=this.options.height*t,this.canvas.parentElement.style.height=this.options.height+"px",this.drawWaveform()}renderMarkers(){if(!this.markersContainer||(this.markersContainer.innerHTML="",this._activeMarkerIndex=-1,clearTimeout(this._markerLabelTimer),!this.options.showMarkers||!this.options.markers?.length))return;let t=this.getSeekDuration();t&&this.options.markers.forEach((i,s)=>{if(i.time>t){console.warn(`[WaveformPlayer] Marker "${i.label}" at ${i.time}s exceeds audio duration of ${t}s`);return}let r=i.time/t*100,a=document.createElement("button");a.className="waveform-marker",a.style.left=`${r}%`,a.style.backgroundColor=i.color||"rgba(255, 255, 255, 0.5)",a.setAttribute("aria-label",i.label),a.setAttribute("data-time",i.time);let o=document.createElement("span");o.className="waveform-marker-tooltip",o.textContent=i.label,a.appendChild(o),a.addEventListener("click",n=>{n.stopPropagation(),this.seekTo(i.time),this.options.playOnSeek&&!this.isPlaying&&this.play()}),this.markersContainer.appendChild(a)})}setActiveMarker(t){if(!this.markersContainer)return;this.markersContainer.querySelectorAll(".waveform-marker").forEach((s,r)=>s.classList.toggle("active",r===t))}updateActiveMarker(){if(!this.markersContainer)return;let t=this.markersContainer.querySelectorAll(".waveform-marker");if(!t.length)return;let i=this.getSeekDuration(),s=i?this.progress*i:0,r=-1,a=-1/0;t.forEach((o,n)=>{let l=parseFloat(o.getAttribute("data-time"));Number.isFinite(l)&&l<=s+.05&&l>a&&(a=l,r=n)}),r!==this._activeMarkerIndex&&(this._activeMarkerIndex=r,this.setActiveMarker(r),clearTimeout(this._markerLabelTimer),t.forEach((o,n)=>o.classList.toggle("show-label",n===r)),r>=0&&(this._markerLabelTimer=setTimeout(()=>{this.markersContainer?.querySelectorAll(".waveform-marker").forEach(o=>o.classList.remove("show-label"))},2500)))}setupHoverTime(){if(!this.seekEl)return;let t=document.createElement("div");t.className="waveform-hover-time",t.setAttribute("aria-hidden","true"),this.seekEl.appendChild(t),this.hoverTimeEl=t,this.options.showHoverTime&&(this.seekEl.addEventListener("pointermove",i=>{this._dragging||this._updateHoverTip(i.clientX)}),this.seekEl.addEventListener("pointerleave",()=>{this._dragging||this._hideHoverTip()}))}_updateHoverTip(t){let i=this.hoverTimeEl;if(!i)return;let s=this.getSeekDuration();if(!s){i.style.opacity="0";return}let r=this.canvas.getBoundingClientRect(),a=m((t-r.left)/r.width);i.textContent=E(a*s),i.style.left=a*100+"%",i.style.opacity="1"}_hideHoverTip(){this.hoverTimeEl&&(this.hoverTimeEl.style.opacity="0")}_scrubTo(t){let i=this.canvas.getBoundingClientRect();if(!i.width)return;this.progress=m((t-i.left)/i.width),this.drawWaveform(),this._updateSeekHandle();let s=this.getSeekDuration();s&&this.currentTimeEl?(this.currentTimeEl.textContent=E(this.progress*s),this._hideHoverTip()):this._updateHoverTip(t)}setupSeekHandle(){if(!this.options.seekHandle||this.options.waveformStyle!=="seekbar"||!this.seekEl)return;let t=document.createElement("div");t.className="waveform-seek-handle",t.setAttribute("aria-hidden","true"),this.seekEl.appendChild(t),this.seekHandleEl=t}_updateSeekHandle(){let t=this.seekHandleEl;t&&(t.style.left=this.progress*100+"%",t.classList.toggle("is-visible",this._seekHover||this._dragging),t.classList.toggle("is-active",this._dragging||this._handleNear))}handleCanvasClick(t){if(this._suppressClick){this._suppressClick=!1;return}this._seekFromPointer(t.clientX)}_seekFromPointer(t){let i=this.canvas.getBoundingClientRect();if(!i.width)return;let s=m((t-i.left)/i.width);if(this.options.audioMode==="external"){this._requestSeek(s);return}!this.audio||!this.audio.duration||this.seekToPercent(s)}setLoading(t){if(this.isLoading=t,this.loadingEl){let i=t&&this.waveformData.length===0;this.loadingEl.style.display=i?"block":"none"}this.seekEl&&this.seekEl.setAttribute("aria-busy",t?"true":"false")}onMetadataLoaded(){this.isDestroying||(this.totalTimeEl&&(this.totalTimeEl.textContent=E(this.audio.duration)),this.renderMarkers(),this.updateSeekAccessibility())}setPlayButtonState(t){if(!this.playBtn)return;this.playBtn.classList.toggle("playing",t);let i=this.playBtn.querySelector(".waveform-icon-play"),s=this.playBtn.querySelector(".waveform-icon-pause");i&&(i.style.display=t?"none":"flex"),s&&(s.style.display=t?"flex":"none")}onPlay(){this.isDestroying||(this.isPlaying=!0,this.setPlayButtonState(!0),this.startSmoothUpdate(),this._updateMediaSession("playing"),this._emit("waveformplayer:play",{player:this,url:this.options.url}),this.options.onPlay&&this.options.onPlay(this))}onPause(){this.isDestroying||(this.isPlaying=!1,this.setPlayButtonState(!1),this.stopSmoothUpdate(),this._updateMediaSession("paused"),this._emit("waveformplayer:pause",{player:this,url:this.options.url}),this.options.onPause&&this.options.onPause(this))}onEnded(){if(this.isDestroying)return;let t=this.audio.duration;this.progress=0,this.audio.currentTime=0,this.drawWaveform(),this.currentTimeEl&&(this.currentTimeEl.textContent="0:00"),this._emit("waveformplayer:ended",{player:this,url:this.options.url,currentTime:t,duration:t}),this.onPause(),this.options.onEnd&&this.options.onEnd(this)}onError(t){this.isDestroying||(console.error("[WaveformPlayer] Audio error:",t),this.hasError=!0,this.setLoading(!1),this.errorEl&&(this.errorEl.style.display="flex"),this.canvas&&(this.canvas.style.opacity="0.2"),this.playBtn&&(this.playBtn.disabled=!0),this.options.onError&&this.options.onError(t,this))}startSmoothUpdate(){this.stopSmoothUpdate();let t=()=>{this.isPlaying&&this.audio&&this.audio.duration&&(this.updateProgress(),this.updateTimer=requestAnimationFrame(t))};this.updateTimer=requestAnimationFrame(t)}stopSmoothUpdate(){this.updateTimer&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null)}updateProgress(){if(!this.audio||!this.audio.duration||this._dragging)return;let t=this.audio.currentTime/this.audio.duration;Math.abs(t-this.progress)>.001&&(this.progress=t,this.drawWaveform(),this._updateSeekHandle()),this.currentTimeEl&&(this.currentTimeEl.textContent=E(this.audio.currentTime)),this._emit("waveformplayer:timeupdate",{player:this,currentTime:this.audio.currentTime,duration:this.audio.duration,progress:this.progress,url:this.options.url}),this.options.onTimeUpdate&&this.options.onTimeUpdate(this.audio.currentTime,this.audio.duration,this),this.updateActiveMarker(),this.updateSeekAccessibility()}updateBPMDisplay(){let t=this.options.bpm||this.detectedBPM;this.bpmEl&&this.bpmValueEl&&t&&(this.bpmValueEl.textContent=Math.round(t),this.bpmEl.style.display="inline-flex")}refreshTheme(){if(!this._autoTheme)return;this._scheme=R(this.container);let t=J(this.options.colorPreset,this.container);for(let i of this._presetKeys||[])i in t&&(this.options[i]=t[i]);this._applyThemeColors()}_applyThemeColors(){this.container.classList.toggle("waveform-theme-light",this._scheme==="light"),this.canvas&&this.drawWaveform()}static _watchTheme(){if(e._themeWatch||typeof document>"u")return;let t=()=>requestAnimationFrame(()=>{e.instances.forEach(a=>{try{a.refreshTheme()}catch{}})}),i={attributes:!0,attributeFilter:["class","data-theme","data-color-scheme","style"]},s=new MutationObserver(t);s.observe(document.documentElement,i),document.body&&s.observe(document.body,i);let r=null;try{r=window.matchMedia("(prefers-color-scheme: dark)"),r.addEventListener("change",t)}catch{}e._themeWatch={obs:s,mq:r,refresh:t}}updateSpeedUI(){if(!this.audio)return;let t=this.container.querySelector(".speed-value");if(t){let i=this.audio.playbackRate;t.textContent=i===1?"1x":`${i}x`}this.container.querySelectorAll(".speed-option").forEach(i=>{let s=parseFloat(i.dataset.rate)===this.audio.playbackRate;i.classList.toggle("active",s),i.setAttribute("aria-checked",s?"true":"false")})}play(){if(this.options.singlePlay&&e.currentlyPlaying&&e.currentlyPlaying!==this&&e.currentlyPlaying.pause(),this.options.audioMode==="external"){this._emit("waveformplayer:request-play",this._buildTrackDetail(),!0).defaultPrevented||(e.currentlyPlaying=this);return}return e.currentlyPlaying=this,this.audio.play()}pause(){if(e.currentlyPlaying===this&&(e.currentlyPlaying=null),this.options.audioMode==="external"){this._emit("waveformplayer:request-pause",this._buildTrackDetail(),!0);return}this.audio.pause()}_buildTrackDetail(){return{url:this.options.url,title:this.options.title,artist:this.options.artist,album:this.options.album,artwork:this.options.artwork,markers:this.options.markers,waveform:this.options.waveform,id:this.id,player:this}}setPlayingState(t){let i=this.isPlaying;this.isPlaying=!!t,this.setPlayButtonState(this.isPlaying),this.isPlaying&&!i?(this.startSmoothUpdate?.(),this._emit("waveformplayer:play",{player:this,url:this.options.url}),this.options.onPlay&&this.options.onPlay(this)):!this.isPlaying&&i&&(this.stopSmoothUpdate?.(),this._emit("waveformplayer:pause",{player:this,url:this.options.url}),this.options.onPause&&this.options.onPause(this))}setProgress(t,i){!i||i<=0||(this.progress=m(t/i),this.currentTimeEl&&(this.currentTimeEl.textContent=E(t)),this._extDuration=i,this.totalTimeEl&&(!this.totalTimeEl.dataset._extSet||this.totalTimeEl.dataset._extDur!==String(i))&&(this.totalTimeEl.textContent=E(i),this.totalTimeEl.dataset._extSet="1",this.totalTimeEl.dataset._extDur=String(i)),this.drawWaveform?.(),this.updateActiveMarker(),this._emit("waveformplayer:timeupdate",{player:this,currentTime:t,duration:i,progress:this.progress,url:this.options.url}),this.options.onTimeUpdate&&this.options.onTimeUpdate(t,i,this),this.progress>=1?this._extEnded||(this._extEnded=!0,this._emit("waveformplayer:ended",{player:this,url:this.options.url,currentTime:i,duration:i}),this.options.onEnd&&this.options.onEnd(this)):this._extEnded=!1,this.updateSeekAccessibility())}togglePlay(){this.isPlaying?this.pause():this.play()}seekTo(t){this.audio&&this.audio.duration&&(this.audio.currentTime=m(t,0,this.audio.duration),this.updateProgress())}seekToPercent(t){this.audio&&this.audio.duration&&(this.audio.currentTime=this.audio.duration*m(t),this.updateProgress())}setVolume(t){let i=Number(t);this.audio&&Number.isFinite(i)&&(this.audio.volume=m(i))}setPlaybackRate(t){if(!this.audio)return;let i=_(t,null,{min:N,max:F});i!==null&&(this.audio.playbackRate=i,this.options.playbackRate=i,this.updateSpeedUI())}destroy(){this.isDestroying=!0,this._emit("waveformplayer:destroy",{player:this,url:this.options.url}),this.pause(),this.stopSmoothUpdate(),clearTimeout(this._markerLabelTimer),this._ac?.abort(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),e.instances.delete(this.id),e.currentlyPlaying===this&&(e.currentlyPlaying=null),this.audio&&(this.audio.pause(),this.audio.src="",this.audio.load(),this.audio=null),this.container.innerHTML="",delete this.container.dataset.waveformInitialized,this.canvas=null,this.ctx=null,this.playBtn=null,this.waveformData=[]}static getInstance(t){if(typeof t=="string"){let i=this.instances.get(t);if(i)return i;let s=document.getElementById(t);if(s)return Array.from(this.instances.values()).find(r=>r.container===s)}if(t instanceof HTMLElement)return Array.from(this.instances.values()).find(i=>i.container===t)}static getAllInstances(){return Array.from(this.instances.values())}static destroyAll(){this.instances.forEach(t=>t.destroy()),this.instances.clear()}static async generateWaveformData(t,i=1800){try{return(await G(t,i)).peaks}catch(s){throw console.error("[WaveformPlayer] Failed to generate waveform:",s),s}}static getPeaksUrl(t){if(!t)return;let i=t.replace(/\.(mp3|wav|ogg|flac|m4a|aac)(\?[^#]*)?(#.*)?$/i,".json$2$3");return i===t?void 0:i}};A.utils={formatTime:E,extractTitleFromUrl:I,escapeHtml:S,isSafeHref:st,parseDataAttributes:H,detectColorScheme:R};var et=()=>typeof window<"u"&&typeof document<"u",Nt=()=>document.documentElement?.dataset.waveformAutoinit==="false";function kt(e){if(!(e.dataset.waveformInitialized==="true"||A.getInstance(e)))try{new A(e),e.dataset.waveformInitialized="true"}catch(t){console.error("[WaveformPlayer] Failed to initialize:",t,e)}}function tt(e=document){if(!et())return;let t=e||document;t.matches?.("[data-waveform-player]")&&kt(t),t.querySelectorAll("[data-waveform-player]").forEach(kt)}et()&&!Nt()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>tt()):tt());A.init=tt;et()&&(window.WaveformPlayer=A);var se=A;export{A as WaveformPlayer,se as default};
diff --git a/dist/waveform-player.js b/dist/waveform-player.js
index 219b478..f39107a 100644
--- a/dist/waveform-player.js
+++ b/dist/waveform-player.js
@@ -128,6 +128,7 @@
setBool("autoplay");
setBool("showControls");
setBool("showInfo");
+ setBool("showAlbum");
setBool("showTime");
setBool("showHoverTime");
setBool("seekHandle");
@@ -809,6 +810,7 @@
autoplay: false,
showControls: true,
showInfo: true,
+ showAlbum: false,
showTime: true,
showHoverTime: false,
// Show a draggable circle handle + hover brightness-lift on the SEEKBAR
@@ -921,6 +923,7 @@
"autoplay",
"showControls",
"showInfo",
+ "showAlbum",
"showTime",
"showHoverTime",
"seekHandle",
@@ -1161,7 +1164,7 @@
*
* Clears the container, resolves button alignment (`auto` → `bottom` for
* the `bars` style, `center` otherwise), and conditionally renders the play
- * button, info row (artwork/title/artist), BPM badge, playback-speed
+ * button, info row (artwork/title/artist/album), BPM badge, playback-speed
* menu, and time display based on the relevant `show*` options. Caches the
* canvas, controls, and text elements onto `this`, then sizes the canvas.
* @private
@@ -1214,6 +1217,7 @@
${this.options.artist ? `${escapeHtml(this.options.artist)} ` : ""}
+ ${this.options.showAlbum && this.options.album ? `${escapeHtml(this.options.album)} ` : ""}
${this.options.showBPM ? `
@@ -1266,6 +1270,7 @@
this.ctx = this.canvas.getContext("2d");
this.titleEl = this.container.querySelector(".waveform-title");
this.artistEl = this.container.querySelector(".waveform-artist");
+ this.albumEl = this.container.querySelector(".waveform-album");
this.artworkEl = this.container.querySelector(".waveform-artwork, .waveform-btn-artwork");
this.bindArtworkFallback(this.artworkEl);
this.currentTimeEl = this.container.querySelector(".time-current");
@@ -1338,6 +1343,17 @@
span.className = "waveform-artist";
return span;
}
+ /**
+ * Create an album text element matching the initial player markup.
+ *
+ * @returns {HTMLSpanElement} Album text element.
+ * @private
+ */
+ createAlbumElement() {
+ const span = document.createElement("span");
+ span.className = "waveform-album";
+ return span;
+ }
/**
* Reconcile artist metadata and markup for the current track.
*
@@ -1361,6 +1377,33 @@
this.artistEl.textContent = artist;
this.artistEl.style.display = "";
}
+ /**
+ * Reconcile album metadata and markup for the current track.
+ *
+ * @param {string|null} album - Album text, or a falsy value to remove it.
+ * @private
+ */
+ syncAlbum(album) {
+ this.options.album = album || "";
+ if (!this.options.showInfo || !this.options.showAlbum) {
+ this.albumEl?.remove();
+ this.albumEl = null;
+ return;
+ }
+ if (!album) {
+ this.albumEl?.remove();
+ this.albumEl = null;
+ return;
+ }
+ if (!this.albumEl) {
+ const anchorEl = this.artistEl || this.container.querySelector(".waveform-title");
+ if (!anchorEl) return;
+ this.albumEl = this.createAlbumElement();
+ anchorEl.after(this.albumEl);
+ }
+ this.albumEl.textContent = album;
+ this.albumEl.style.display = "";
+ }
/**
* Reconcile the play button's artwork image (`artworkPosition: 'button'`).
*
@@ -1972,7 +2015,7 @@
*
* Pauses any current playback, fully resets the audio element (self mode),
* clears error/marker/progress state, merges the new metadata into
- * `this.options`, updates the artist/artwork DOM, then calls
+ * `this.options`, updates the artist/album/artwork DOM, then calls
* {@link WaveformPlayer#load}. Auto-plays the new track unless
* `options.autoplay === false`.
* @param {string} url - Audio URL.
@@ -1987,6 +2030,8 @@
async loadTrack(url, title = null, artist = null, options = {}) {
const hasArtworkOption = Object.prototype.hasOwnProperty.call(options, "artwork");
const hasArtworkAltOption = Object.prototype.hasOwnProperty.call(options, "artworkAlt");
+ const hasAlbumOption = Object.prototype.hasOwnProperty.call(options, "album");
+ const hasShowAlbumOption = Object.prototype.hasOwnProperty.call(options, "showAlbum");
if (this.isPlaying) {
this.pause();
}
@@ -2029,6 +2074,9 @@
if (artist !== null) {
this.syncArtist(artist);
}
+ if (hasAlbumOption || hasShowAlbumOption) {
+ this.syncAlbum(this.options.album);
+ }
if (hasArtworkOption || hasArtworkAltOption) {
this.syncArtwork(
hasArtworkOption ? options.artwork : this.options.artwork,
@@ -2692,13 +2740,14 @@
* directly: `WaveformBar.play(event.detail)`.
*
* @private
- * @return {{url:string,title:?string,artist:?string,artwork:?string,player:WaveformPlayer}}
+ * @return {{url:string,title:?string,artist:?string,album:string,artwork:?string,player:WaveformPlayer}}
*/
_buildTrackDetail() {
return {
url: this.options.url,
title: this.options.title,
artist: this.options.artist,
+ album: this.options.album,
artwork: this.options.artwork,
markers: this.options.markers,
waveform: this.options.waveform,
diff --git a/dist/waveform-player.min.js b/dist/waveform-player.min.js
index 4b6e9ec..28c87d1 100644
--- a/dist/waveform-player.min.js
+++ b/dist/waveform-player.min.js
@@ -1,4 +1,4 @@
-(()=>{function $(e){let t=-1/0;for(let i=0;i
t&&(t=e[i]);return t}function S(e){return String(e??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function q(e){return S(typeof e=="number"?`${e}px`:e)}function st(e){if(typeof e!="string"||e==="")return!1;try{let t=new URL(e,"http://localhost/");return t.protocol==="http:"||t.protocol==="https:"}catch{return!1}}function m(e,t=0,i=1){return Math.max(t,Math.min(e,i))}function _(e,t=null,i={}){let{min:s=-1/0,max:r=1/0,integer:a=!1}=i,o=typeof e=="number"?e:typeof e=="string"&&e.trim()!==""?Number(e):NaN;if(!Number.isFinite(o))return t;let n=m(o,s,r);return a?Math.round(n):n}function H(e,t=null){if(Array.isArray(e))return e;if(typeof e=="string"&&e.trim().startsWith("["))try{let i=JSON.parse(e);if(Array.isArray(i))return i}catch{}return t}function D(e,t={}){let{min:i=-1/0,max:s=1/0,fallback:r=null}=t,a=H(e);if(!a&&typeof e=="string"&&e.trim()!==""&&(a=e.split(/[,\s]+/)),!a)return r;let o=a.map(n=>_(n)).filter(n=>n!==null&&n>=i&&n<=s);return o.length?o:r}function rt(e,t,i=null){return t.includes(e)?e:i}function at(e){if(typeof e=="string"){let t=e.trim().toLowerCase();return t!==""&&t!=="false"&&t!=="0"}return!!e}function vt(e){return e===void 0?void 0:e==="true"}function it(e){if(typeof e=="string"&&e.trim().startsWith("["))try{return JSON.parse(e)}catch{}return e}function I(e){let t={},i=(o,n=o)=>{let l=vt(e.dataset[n]);l!==void 0&&(t[o]=l)},s=(o,n=o,l=!1)=>{let h=e.dataset[n];h&&(t[o]=l?parseFloat(h):parseInt(h,10))},r=(o,n=o)=>{let l=e.dataset[n];l&&(t[o]=/^\d+(\.\d+)?$/.test(l.trim())?parseFloat(l):l)},a=(o,n=o)=>{let l=e.dataset[n];if(!l)return;let h=H(l);h?t[o]=h:console.warn(`[WaveformPlayer] Invalid ${n} attribute, expected a JSON array:`,l)};if(e.dataset.src&&(t.url=e.dataset.src),e.dataset.url&&(t.url=e.dataset.url),s("height"),s("samples"),e.dataset.preload&&(t.preload=e.dataset.preload),e.dataset.crossOrigin&&(t.crossOrigin=e.dataset.crossOrigin),e.dataset.audioMode&&(t.audioMode=e.dataset.audioMode),e.dataset.style&&(t.waveformStyle=e.dataset.style),e.dataset.waveformStyle&&(t.waveformStyle=e.dataset.waveformStyle),e.dataset.waveformGradient&&(t.waveformGradient=e.dataset.waveformGradient),s("barWidth"),s("barSpacing"),s("barRadius"),e.dataset.buttonAlign&&(t.buttonAlign=e.dataset.buttonAlign),e.dataset.layout&&(t.layout=e.dataset.layout),e.dataset.buttonStyle&&(t.buttonStyle=e.dataset.buttonStyle),r("buttonSize"),r("buttonRadius"),e.dataset.colorPreset&&(t.colorPreset=e.dataset.colorPreset),e.dataset.waveformColor&&(t.waveformColor=it(e.dataset.waveformColor)),e.dataset.progressColor&&(t.progressColor=it(e.dataset.progressColor)),e.dataset.color&&(t.waveformColor=e.dataset.color),e.dataset.theme&&(t.colorPreset=e.dataset.theme),i("autoplay"),i("showControls"),i("showInfo"),i("showTime"),i("showHoverTime"),i("seekHandle"),i("showBPM","showBpm"),s("bpm"),i("singlePlay"),i("playOnSeek"),e.dataset.title&&(t.title=e.dataset.title),e.dataset.artist&&(t.artist=e.dataset.artist),e.dataset.album&&(t.album=e.dataset.album),e.dataset.artwork&&(t.artwork=e.dataset.artwork),e.dataset.artworkPosition&&(t.artworkPosition=e.dataset.artworkPosition),e.dataset.waveform&&(t.waveform=e.dataset.waveform),a("markers"),s("playbackRate","playbackRate",!0),i("showPlaybackSpeed"),e.dataset.playbackRates){let o=D(e.dataset.playbackRates);o?t.playbackRates=o:console.warn("[WaveformPlayer] Invalid playbackRates attribute:",e.dataset.playbackRates)}return i("enableMediaSession"),i("showMarkers"),i("accessibleSeek"),e.dataset.seekLabel&&(t.seekLabel=e.dataset.seekLabel),e.dataset.seekValueText&&(t.seekValueText=e.dataset.seekValueText),e.dataset.errorText&&(t.errorText=e.dataset.errorText),e.dataset.playPauseLabel&&(t.playPauseLabel=e.dataset.playPauseLabel),e.dataset.speedLabel&&(t.speedLabel=e.dataset.speedLabel),e.dataset.artworkAlt&&(t.artworkAlt=e.dataset.artworkAlt),e.dataset.unknownTrackText&&(t.unknownTrackText=e.dataset.unknownTrackText),e.dataset.playIcon&&(t.playIcon=e.dataset.playIcon),e.dataset.pauseIcon&&(t.pauseIcon=e.dataset.pauseIcon),t}function ot(e,...t){let i=0;return e.replace(/%(?:(\d+)\$)?s/g,(s,r)=>{let a=r?Number(r)-1:i++;return t[a]??s})}function E(e){let t=Number(e);if(!t||!Number.isFinite(t)||t<0)return"0:00";let i=Math.floor(t/3600),s=Math.floor(t%3600/60),r=Math.floor(t%60);return i>0?`${i}:${s.toString().padStart(2,"0")}:${r.toString().padStart(2,"0")}`:`${s}:${r.toString().padStart(2,"0")}`}var St=0;function nt(e){let t=e||"audio",i=5381;for(let s=0;s>>0).toString(36)}_${(St++).toString(36)}`}function O(e){if(!e)return"Audio";let t=e.split("/");return t[t.length-1].split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,r=>r.toUpperCase())}function U(e){if(typeof e!="string")return null;let t=e.match(/rgba?\(\s*([\d.]+)\s*[,\s]\s*([\d.]+)\s*[,\s]\s*([\d.]+)\s*(?:[,/]\s*([\d.]+)(%?))?/i);if(!t)return null;let i=Number(t[1]),s=Number(t[2]),r=Number(t[3]);if(!Number.isFinite(i)||!Number.isFinite(s)||!Number.isFinite(r))return null;let a=t[4]===void 0?1:Number(t[4]);return Number.isFinite(a)?(t[5]==="%"&&(a/=100),{r:i,g:s,b:r,a:m(a,0,1)}):null}function lt(e){let t=U(e);return!t||t.a<=0?null:(t.r*299+t.g*587+t.b*114)/1e3}function j(...e){let t={};for(let i of e)for(let s in i)i[s]!==null&&i[s]!==void 0&&(t[s]=i[s]);return t}function ht(e,t){let i;return function(...r){let a=()=>{clearTimeout(i),e(...r)};clearTimeout(i),i=setTimeout(a,t)}}function B(e,t){if(e.length===t)return e;if(e.length===0||t===0)return[];let i=[];if(t>e.length){let s=(e.length-1)/(t-1);for(let r=0;r=e.length)i.push(e[e.length-1]);else if(o===n)i.push(e[o]);else{let h=e[o]*(1-l)+e[n]*l;i.push(h)}}}else{let s=e.length/t;for(let r=0;rn&&(n=e[h]),l++;if(l===0){let h=Math.min(Math.round(r*s),e.length-1);n=e[h]}i.push(n)}}return i}function P(e,t,i,s){if(!Array.isArray(t))return t;if(t.length<2)return t[0];let r=i.width,a=i.height,o=s&&s.waveformGradient,[n,l,h,c]=o==="horizontal"?[0,0,r,0]:o==="diagonal"?[0,0,r,a]:[0,0,0,a];try{let d=e.createLinearGradient(n,l,h,c);return t.forEach((b,y)=>d.addColorStop(y/(t.length-1),b)),d}catch{return t[0]}}function x(e,t,i,s,r,a){if((Array.isArray(a)?a.some(n=>n>0):a>0)&&typeof e.roundRect=="function"){let n=Math.min(s/2,Math.abs(r)/2),l=h=>m(h,0,n);e.beginPath(),e.roundRect(t,i,s,r,Array.isArray(a)?a.map(l):l(a)),e.fill()}else e.fillRect(t,i,s,r)}function pt(e,t){return(e.barRadius||0)*t}function Et(e,t){let i=pt(e,t);return[i,i,0,0]}function ct(e,t,i,s,r){let a=r/2;e.beginPath(),e.moveTo(t,s-a),e.lineTo(i-a,s-a),e.arc(i-a,s,a,-Math.PI/2,Math.PI/2),e.lineTo(t,s+a),e.arc(t,s,a,Math.PI/2,-Math.PI/2),e.closePath()}function V(e,t,i,s,r){let a=window.devicePixelRatio||1,o=r.barWidth*a,n=r.barSpacing*a,l=Math.floor(t.width/(o+n)),h=B(i,l),c=t.height,d=s*t.width,b=Et(r,a),y=P(e,r.color,t,r),w=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height),e.fillStyle=y;for(let f=0;ft.width)break;let g=h[f]*c*.9,u=c-g;x(e,p,u,o,g,b)}e.save(),e.beginPath(),e.rect(0,0,d,c),e.clip(),e.fillStyle=w;for(let f=0;fd)break;let g=h[f]*c*.9,u=c-g;x(e,p,u,o,g,b)}e.restore()}function Pt(e,t,i,s,r){let a=window.devicePixelRatio||1,o=r.barWidth*a,n=r.barSpacing*a,l=Math.floor(t.width/(o+n)),h=B(i,l),c=t.height,d=c/2,b=s*t.width,y=pt(r,a),w=[y,y,0,0],f=[0,0,y,y],p=P(e,r.color,t,r),g=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height),e.fillStyle=p;for(let u=0;ut.width)break;let v=h[u]*c*.45;x(e,k,d-v,o,v,w),x(e,k,d,o,v,f)}e.save(),e.beginPath(),e.rect(0,0,b,c),e.clip(),e.fillStyle=g;for(let u=0;ub)break;let v=h[u]*c*.45;x(e,k,d-v,o,v,w),x(e,k,d,o,v,f)}e.restore()}function Tt(e,t,i,s,r){let a=t.width,o=t.height,n=o/2,l=o*.35;e.clearRect(0,0,a,o);let h=(c,d,b=1,y=!1)=>{let w=P(e,c,t,r),f=Array.isArray(c)?c[c.length-1]:c;y&&(e.shadowBlur=12,e.shadowColor=f),e.strokeStyle=w,e.lineWidth=d,e.lineCap="round",e.lineJoin="round",e.beginPath(),e.moveTo(0,n);let p=[],g=Math.floor(i.length*b);for(let u=0;u0&&h(r.progressColor,3,s,!0)}function ut(e,t,i,s,r){let a=window.devicePixelRatio||1,o=(r.barWidth||3)*a,n=(r.barSpacing||1)*a,l=Math.floor(t.width/(o+n)),h=B(i,l),c=t.height,d=4*a,b=2*a,y=s*t.width,w=c/2,f=P(e,r.color,t,r),p=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height);for(let g=0;gt.width)break;let k=h[g]*c*.9,v=Math.floor(k/(d+b));e.fillStyle=u0&&e.fillRect(u,w+L,o,d)}}}function dt(e,t,i,s,r){let a=window.devicePixelRatio||1,o=(r.barWidth||2)*a,n=(r.barSpacing||3)*a,l=Math.floor(t.width/(o+n)),h=B(i,l),c=t.height,d=Math.max(1.5*a,o/2),b=s*t.width,y=c/2,w=P(e,r.color,t,r),f=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height);for(let p=0;pt.width)break;let u=h[p]*c*.9;e.fillStyle=g0){let d=Math.max(h*2,s*a);e.save(),e.globalAlpha=r.seekHandle&&!c?.7:1,e.fillStyle=P(e,r.progressColor,t,r)||"rgba(255, 255, 255, 0.9)",ct(e,h,d,n,l),e.fill(),e.restore()}}var Mt={bars:V,bar:V,mirror:Pt,line:Tt,blocks:ut,block:ut,dots:dt,dot:dt,seekbar:At};function ft(e,t,i,s,r){(Mt[r.waveformStyle]||V)(e,t,i,s,r)}function mt(e){try{let t=e.getChannelData(0),i=e.sampleRate,s=Ct(t,i);if(s.length<2)return 120;let r=[];for(let l=1;l{let h=60/l,c=Math.round(h/3)*3;c>60&&c<200&&(a[c]=(a[c]||0)+1)});let o=0,n=120;for(let[l,h]of Object.entries(a))h>o&&(o=h,n=parseInt(l));return n<70&&a[n*2]?n*=2:n>160&&a[Math.round(n/2)]&&(n=Math.round(n/2)),n-1}catch(t){return console.warn("[WaveformPlayer] BPM detection failed:",t),null}}function Ct(e,t){let r=[],a=0;for(let o=0;oh&&n>.01){let c=r[r.length-1]||0,d=t*.15;o-c>d&&r.push(o)}a=n*.8+a*.2}return r}function Lt(e,t=1800){let i=e.length/t,s=e.numberOfChannels,r=[];for(let o=0;ob&&(b=f),fr[l])&&(r[l]=y)}}let a=$(r);return a>0?r.map(o=>o/a):r}async function G(e,t=1800,i=!1){let s;try{let r=window.AudioContext||window.webkitAudioContext;s=new r;let o=await(await fetch(e)).arrayBuffer(),n=await s.decodeAudioData(o),l=Lt(n,t);l=_t(l);let h=null;return i&&(h=mt(n)),{peaks:l,bpm:h}}finally{s&&s.close()}}function yt(e=1800){let t=[];for(let i=0;it)return e;let s=t/i;return e.map(r=>r*s)}var K=128;function gt(e){let t=document.documentElement,i=document.body;return t.classList.contains(e)||t.classList.contains(`${e}-mode`)||t.classList.contains(`theme-${e}`)||t.getAttribute("data-theme")===e||t.getAttribute("data-color-scheme")===e||i.classList.contains(e)||i.classList.contains(`${e}-mode`)||i.getAttribute("data-theme")===e}function xt(e){let t=0,i=0;for(let s=e;s&&s.nodeType===1&&i<.995;s=s.parentElement){let r=U(getComputedStyle(s).backgroundColor);if(!r||r.a<=0)continue;let a=r.a*(1-i);t+=(r.r*299+r.g*587+r.b*114)/1e3*a,i+=a}return{sum:t,alpha:i}}function Rt(){let e=lt(getComputedStyle(document.body).color);if(e!==null)return e>K?"dark":"light";if(window.matchMedia){if(window.matchMedia("(prefers-color-scheme: dark)").matches)return"dark";if(window.matchMedia("(prefers-color-scheme: light)").matches)return"light"}return"dark"}function R(e){if(gt("dark"))return"dark";if(gt("light"))return"light";try{let t=e&&e.nodeType===1?e:document.body,{sum:i,alpha:s}=xt(t),r=Rt(),a=i+(r==="dark"?0:255)*(1-s);return a>K?"light":a ',pauseIcon:' ',onLoad:null,onPlay:null,onPause:null,onEnd:null,onError:null,onTimeUpdate:null,onNextTrack:null,onPreviousTrack:null},X={bars:{barWidth:3,barSpacing:1},mirror:{barWidth:2,barSpacing:2},line:{barWidth:2,barSpacing:0},blocks:{barWidth:4,barSpacing:2},dots:{barWidth:3,barSpacing:3},seekbar:{barWidth:1,barSpacing:0}},Q=["auto","top","center","bottom"],N=.25,F=4,Dt={buttonAlign:Q,layout:["default","preview"],buttonStyle:["circle","minimal"],artworkPosition:["info","button"],waveformStyle:Object.keys(X),waveformGradient:["vertical","horizontal","diagonal"],audioMode:["self","external"],preload:["none","metadata","auto"],colorPreset:Object.keys(C),crossOrigin:["anonymous","use-credentials"]},Bt={height:{min:1,integer:!0},samples:{min:1,integer:!0},barWidth:{min:0},barSpacing:{min:0},barRadius:{min:0},bpm:{min:1},playbackRate:{min:N,max:F}},Ht=["autoplay","showControls","showInfo","showTime","showHoverTime","seekHandle","showBPM","singlePlay","playOnSeek","enableMediaSession","showMarkers","accessibleSeek","showPlaybackSpeed"],It=["onLoad","onPlay","onPause","onEnd","onError","onTimeUpdate","onNextTrack","onPreviousTrack"];function Y(e,t){console.warn(`[WaveformPlayer] Invalid ${e} option, using default:`,t)}function z(e){let t=H(e);return t?t.reduce((i,s)=>{let r=s&&typeof s=="object"?_(s.time,null,{min:0}):null;return r===null?(Y("marker",s),i):(i.push({...s,time:r,label:s.label==null?"":s.label}),i)},[]):(e!=null&&Y("markers",e),[])}function Z(e){let t=s=>e[s]!=null,i=s=>{Y(s,e[s]),e[s]=W[s]};for(let[s,r]of Object.entries(Bt)){if(!t(s))continue;let a=_(e[s],null,r);a===null?i(s):e[s]=a}for(let[s,r]of Object.entries(Dt))t(s)&&rt(e[s],r)===null&&i(s);for(let s of Ht)e[s]=at(e[s]);for(let s of It)t(s)&&typeof e[s]!="function"&&i(s);if(t("playbackRates")){let s=D(e.playbackRates,{min:N,max:F,fallback:null});s===null?i("playbackRates"):e.playbackRates=s}e.markers=z(e.markers);for(let s of["buttonSize","buttonRadius"]){if(!t(s))continue;let r=e[s];(typeof r=="number"?Number.isFinite(r):typeof r=="string"&&r.trim()!=="")||i(s)}for(let s of["waveformColor","progressColor"]){if(!t(s))continue;let r=e[s];!(typeof r=="string"&&r.trim()!=="")&&!Array.isArray(r)&&i(s)}return e}var Ot="data:image/svg+xml,"+encodeURIComponent(' '),bt=5,wt=10,Wt='button, a[href], input, [role="slider"]',T=class e{static instances=new Map;static currentlyPlaying=null;constructor(t,i={}){if(this.container=typeof t=="string"?document.querySelector(t):t,!this.container)throw new Error("[WaveformPlayer] Container element not found");let s=I(this.container),r={...i};r.style&&!r.waveformStyle&&(r.waveformStyle=r.style),r.src&&!r.url&&(r.url=r.src),this.options=Z(j(W,s,r));let a=J(this.options.colorPreset,this.container);this._autoTheme=this.options.colorPreset==null||!C[this.options.colorPreset],this._presetKeys=[],this._scheme=this.options.colorPreset&&C[this.options.colorPreset]?this.options.colorPreset:R(this.container);for(let[n,l]of Object.entries(a))(this.options[n]===null||this.options[n]===void 0)&&(this.options[n]=l,this._presetKeys.push(n));let o=X[this.options.waveformStyle];o&&(s.barWidth===void 0&&i.barWidth===void 0&&(this.options.barWidth=o.barWidth),s.barSpacing===void 0&&i.barSpacing===void 0&&(this.options.barSpacing=o.barSpacing)),this.audio=null,this.canvas=null,this.ctx=null,this.waveformData=[],this.progress=0,this._activeMarkerIndex=-1,this._markerLabelTimer=null,this.isPlaying=!1,this.isLoading=!1,this.hasError=!1,this.updateTimer=null,this.resizeObserver=null,this._ac=new AbortController,this.id=this.container.id||nt(this.options.url),e.instances.set(this.id,this),e._watchTheme();try{this.init()}catch(n){throw e.instances.delete(this.id),this._ac.abort(),n}setTimeout(()=>{this._emit("waveformplayer:ready",{player:this,url:this.options.url})},100)}_emit(t,i,s=!1){let r=new CustomEvent(t,{bubbles:!0,cancelable:s,detail:i});return this.container.dispatchEvent(r),r}_requestSeek(t){this._emit("waveformplayer:request-seek",{...this._buildTrackDetail(),percent:t},!0).defaultPrevented||(this.progress=t,this.drawWaveform?.())}init(){this.createDOM(),this.createAudio(),this.initPlaybackSpeed(),this.initKeyboardControls(),this.initSeekControl(),this.bindEvents(),this.setupResizeObserver(),requestAnimationFrame(()=>{this.resizeCanvas(),this.options.url&&this.load(this.options.url).then(()=>{this.options.autoplay&&this.play()?.catch(()=>{})}).catch(t=>{console.error("[WaveformPlayer] Failed to load audio:",t)})})}createDOM(){this.container.innerHTML="",this.container.className="waveform-player";let t=Q.includes(this.options.buttonAlign)?this.options.buttonAlign:"auto";t==="auto"&&(this.options.waveformStyle==="bars"?t="bottom":t="center"),this.options.layout==="preview"&&this.container.classList.add("waveform-layout-preview"),this.container.classList.toggle("waveform-theme-light",this._scheme==="light");let s=[];this.options.buttonSize!=null&&s.push(`--wfp-btn-size: ${q(this.options.buttonSize)}`),this.options.buttonRadius!=null&&s.push(`--wfp-btn-radius: ${q(this.options.buttonRadius)}`);let r=s.length?` style="${s.join("; ")};"`:"",a=this.options.artworkPosition==="button"&&this.options.artwork,o=a?` `:"",n=this.options.showControls?`
+(()=>{function z(e){let t=-1/0;for(let i=0;it&&(t=e[i]);return t}function S(e){return String(e??"").replace(/&/g,"&").replace(/ /g,">").replace(/"/g,""").replace(/'/g,"'")}function q(e){return S(typeof e=="number"?`${e}px`:e)}function st(e){if(typeof e!="string"||e==="")return!1;try{let t=new URL(e,"http://localhost/");return t.protocol==="http:"||t.protocol==="https:"}catch{return!1}}function m(e,t=0,i=1){return Math.max(t,Math.min(e,i))}function _(e,t=null,i={}){let{min:s=-1/0,max:r=1/0,integer:a=!1}=i,o=typeof e=="number"?e:typeof e=="string"&&e.trim()!==""?Number(e):NaN;if(!Number.isFinite(o))return t;let n=m(o,s,r);return a?Math.round(n):n}function B(e,t=null){if(Array.isArray(e))return e;if(typeof e=="string"&&e.trim().startsWith("["))try{let i=JSON.parse(e);if(Array.isArray(i))return i}catch{}return t}function D(e,t={}){let{min:i=-1/0,max:s=1/0,fallback:r=null}=t,a=B(e);if(!a&&typeof e=="string"&&e.trim()!==""&&(a=e.split(/[,\s]+/)),!a)return r;let o=a.map(n=>_(n)).filter(n=>n!==null&&n>=i&&n<=s);return o.length?o:r}function rt(e,t,i=null){return t.includes(e)?e:i}function at(e){if(typeof e=="string"){let t=e.trim().toLowerCase();return t!==""&&t!=="false"&&t!=="0"}return!!e}function vt(e){return e===void 0?void 0:e==="true"}function it(e){if(typeof e=="string"&&e.trim().startsWith("["))try{return JSON.parse(e)}catch{}return e}function H(e){let t={},i=(o,n=o)=>{let l=vt(e.dataset[n]);l!==void 0&&(t[o]=l)},s=(o,n=o,l=!1)=>{let h=e.dataset[n];h&&(t[o]=l?parseFloat(h):parseInt(h,10))},r=(o,n=o)=>{let l=e.dataset[n];l&&(t[o]=/^\d+(\.\d+)?$/.test(l.trim())?parseFloat(l):l)},a=(o,n=o)=>{let l=e.dataset[n];if(!l)return;let h=B(l);h?t[o]=h:console.warn(`[WaveformPlayer] Invalid ${n} attribute, expected a JSON array:`,l)};if(e.dataset.src&&(t.url=e.dataset.src),e.dataset.url&&(t.url=e.dataset.url),s("height"),s("samples"),e.dataset.preload&&(t.preload=e.dataset.preload),e.dataset.crossOrigin&&(t.crossOrigin=e.dataset.crossOrigin),e.dataset.audioMode&&(t.audioMode=e.dataset.audioMode),e.dataset.style&&(t.waveformStyle=e.dataset.style),e.dataset.waveformStyle&&(t.waveformStyle=e.dataset.waveformStyle),e.dataset.waveformGradient&&(t.waveformGradient=e.dataset.waveformGradient),s("barWidth"),s("barSpacing"),s("barRadius"),e.dataset.buttonAlign&&(t.buttonAlign=e.dataset.buttonAlign),e.dataset.layout&&(t.layout=e.dataset.layout),e.dataset.buttonStyle&&(t.buttonStyle=e.dataset.buttonStyle),r("buttonSize"),r("buttonRadius"),e.dataset.colorPreset&&(t.colorPreset=e.dataset.colorPreset),e.dataset.waveformColor&&(t.waveformColor=it(e.dataset.waveformColor)),e.dataset.progressColor&&(t.progressColor=it(e.dataset.progressColor)),e.dataset.color&&(t.waveformColor=e.dataset.color),e.dataset.theme&&(t.colorPreset=e.dataset.theme),i("autoplay"),i("showControls"),i("showInfo"),i("showAlbum"),i("showTime"),i("showHoverTime"),i("seekHandle"),i("showBPM","showBpm"),s("bpm"),i("singlePlay"),i("playOnSeek"),e.dataset.title&&(t.title=e.dataset.title),e.dataset.artist&&(t.artist=e.dataset.artist),e.dataset.album&&(t.album=e.dataset.album),e.dataset.artwork&&(t.artwork=e.dataset.artwork),e.dataset.artworkPosition&&(t.artworkPosition=e.dataset.artworkPosition),e.dataset.waveform&&(t.waveform=e.dataset.waveform),a("markers"),s("playbackRate","playbackRate",!0),i("showPlaybackSpeed"),e.dataset.playbackRates){let o=D(e.dataset.playbackRates);o?t.playbackRates=o:console.warn("[WaveformPlayer] Invalid playbackRates attribute:",e.dataset.playbackRates)}return i("enableMediaSession"),i("showMarkers"),i("accessibleSeek"),e.dataset.seekLabel&&(t.seekLabel=e.dataset.seekLabel),e.dataset.seekValueText&&(t.seekValueText=e.dataset.seekValueText),e.dataset.errorText&&(t.errorText=e.dataset.errorText),e.dataset.playPauseLabel&&(t.playPauseLabel=e.dataset.playPauseLabel),e.dataset.speedLabel&&(t.speedLabel=e.dataset.speedLabel),e.dataset.artworkAlt&&(t.artworkAlt=e.dataset.artworkAlt),e.dataset.unknownTrackText&&(t.unknownTrackText=e.dataset.unknownTrackText),e.dataset.playIcon&&(t.playIcon=e.dataset.playIcon),e.dataset.pauseIcon&&(t.pauseIcon=e.dataset.pauseIcon),t}function ot(e,...t){let i=0;return e.replace(/%(?:(\d+)\$)?s/g,(s,r)=>{let a=r?Number(r)-1:i++;return t[a]??s})}function E(e){let t=Number(e);if(!t||!Number.isFinite(t)||t<0)return"0:00";let i=Math.floor(t/3600),s=Math.floor(t%3600/60),r=Math.floor(t%60);return i>0?`${i}:${s.toString().padStart(2,"0")}:${r.toString().padStart(2,"0")}`:`${s}:${r.toString().padStart(2,"0")}`}var St=0;function nt(e){let t=e||"audio",i=5381;for(let s=0;s>>0).toString(36)}_${(St++).toString(36)}`}function I(e){if(!e)return"Audio";let t=e.split("/");return t[t.length-1].split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,r=>r.toUpperCase())}function U(e){if(typeof e!="string")return null;let t=e.match(/rgba?\(\s*([\d.]+)\s*[,\s]\s*([\d.]+)\s*[,\s]\s*([\d.]+)\s*(?:[,/]\s*([\d.]+)(%?))?/i);if(!t)return null;let i=Number(t[1]),s=Number(t[2]),r=Number(t[3]);if(!Number.isFinite(i)||!Number.isFinite(s)||!Number.isFinite(r))return null;let a=t[4]===void 0?1:Number(t[4]);return Number.isFinite(a)?(t[5]==="%"&&(a/=100),{r:i,g:s,b:r,a:m(a,0,1)}):null}function lt(e){let t=U(e);return!t||t.a<=0?null:(t.r*299+t.g*587+t.b*114)/1e3}function j(...e){let t={};for(let i of e)for(let s in i)i[s]!==null&&i[s]!==void 0&&(t[s]=i[s]);return t}function ht(e,t){let i;return function(...r){let a=()=>{clearTimeout(i),e(...r)};clearTimeout(i),i=setTimeout(a,t)}}function O(e,t){if(e.length===t)return e;if(e.length===0||t===0)return[];let i=[];if(t>e.length){let s=(e.length-1)/(t-1);for(let r=0;r=e.length)i.push(e[e.length-1]);else if(o===n)i.push(e[o]);else{let h=e[o]*(1-l)+e[n]*l;i.push(h)}}}else{let s=e.length/t;for(let r=0;rn&&(n=e[h]),l++;if(l===0){let h=Math.min(Math.round(r*s),e.length-1);n=e[h]}i.push(n)}}return i}function P(e,t,i,s){if(!Array.isArray(t))return t;if(t.length<2)return t[0];let r=i.width,a=i.height,o=s&&s.waveformGradient,[n,l,h,c]=o==="horizontal"?[0,0,r,0]:o==="diagonal"?[0,0,r,a]:[0,0,0,a];try{let d=e.createLinearGradient(n,l,h,c);return t.forEach((g,y)=>d.addColorStop(y/(t.length-1),g)),d}catch{return t[0]}}function x(e,t,i,s,r,a){if((Array.isArray(a)?a.some(n=>n>0):a>0)&&typeof e.roundRect=="function"){let n=Math.min(s/2,Math.abs(r)/2),l=h=>m(h,0,n);e.beginPath(),e.roundRect(t,i,s,r,Array.isArray(a)?a.map(l):l(a)),e.fill()}else e.fillRect(t,i,s,r)}function pt(e,t){return(e.barRadius||0)*t}function Et(e,t){let i=pt(e,t);return[i,i,0,0]}function ct(e,t,i,s,r){let a=r/2;e.beginPath(),e.moveTo(t,s-a),e.lineTo(i-a,s-a),e.arc(i-a,s,a,-Math.PI/2,Math.PI/2),e.lineTo(t,s+a),e.arc(t,s,a,Math.PI/2,-Math.PI/2),e.closePath()}function V(e,t,i,s,r){let a=window.devicePixelRatio||1,o=r.barWidth*a,n=r.barSpacing*a,l=Math.floor(t.width/(o+n)),h=O(i,l),c=t.height,d=s*t.width,g=Et(r,a),y=P(e,r.color,t,r),w=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height),e.fillStyle=y;for(let f=0;ft.width)break;let b=h[f]*c*.9,u=c-b;x(e,p,u,o,b,g)}e.save(),e.beginPath(),e.rect(0,0,d,c),e.clip(),e.fillStyle=w;for(let f=0;fd)break;let b=h[f]*c*.9,u=c-b;x(e,p,u,o,b,g)}e.restore()}function Pt(e,t,i,s,r){let a=window.devicePixelRatio||1,o=r.barWidth*a,n=r.barSpacing*a,l=Math.floor(t.width/(o+n)),h=O(i,l),c=t.height,d=c/2,g=s*t.width,y=pt(r,a),w=[y,y,0,0],f=[0,0,y,y],p=P(e,r.color,t,r),b=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height),e.fillStyle=p;for(let u=0;ut.width)break;let v=h[u]*c*.45;x(e,k,d-v,o,v,w),x(e,k,d,o,v,f)}e.save(),e.beginPath(),e.rect(0,0,g,c),e.clip(),e.fillStyle=b;for(let u=0;ug)break;let v=h[u]*c*.45;x(e,k,d-v,o,v,w),x(e,k,d,o,v,f)}e.restore()}function At(e,t,i,s,r){let a=t.width,o=t.height,n=o/2,l=o*.35;e.clearRect(0,0,a,o);let h=(c,d,g=1,y=!1)=>{let w=P(e,c,t,r),f=Array.isArray(c)?c[c.length-1]:c;y&&(e.shadowBlur=12,e.shadowColor=f),e.strokeStyle=w,e.lineWidth=d,e.lineCap="round",e.lineJoin="round",e.beginPath(),e.moveTo(0,n);let p=[],b=Math.floor(i.length*g);for(let u=0;u0&&h(r.progressColor,3,s,!0)}function ut(e,t,i,s,r){let a=window.devicePixelRatio||1,o=(r.barWidth||3)*a,n=(r.barSpacing||1)*a,l=Math.floor(t.width/(o+n)),h=O(i,l),c=t.height,d=4*a,g=2*a,y=s*t.width,w=c/2,f=P(e,r.color,t,r),p=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height);for(let b=0;bt.width)break;let k=h[b]*c*.9,v=Math.floor(k/(d+g));e.fillStyle=u0&&e.fillRect(u,w+L,o,d)}}}function dt(e,t,i,s,r){let a=window.devicePixelRatio||1,o=(r.barWidth||2)*a,n=(r.barSpacing||3)*a,l=Math.floor(t.width/(o+n)),h=O(i,l),c=t.height,d=Math.max(1.5*a,o/2),g=s*t.width,y=c/2,w=P(e,r.color,t,r),f=P(e,r.progressColor,t,r);e.clearRect(0,0,t.width,t.height);for(let p=0;pt.width)break;let u=h[p]*c*.9;e.fillStyle=b0){let d=Math.max(h*2,s*a);e.save(),e.globalAlpha=r.seekHandle&&!c?.7:1,e.fillStyle=P(e,r.progressColor,t,r)||"rgba(255, 255, 255, 0.9)",ct(e,h,d,n,l),e.fill(),e.restore()}}var Mt={bars:V,bar:V,mirror:Pt,line:At,blocks:ut,block:ut,dots:dt,dot:dt,seekbar:Tt};function ft(e,t,i,s,r){(Mt[r.waveformStyle]||V)(e,t,i,s,r)}function mt(e){try{let t=e.getChannelData(0),i=e.sampleRate,s=Ct(t,i);if(s.length<2)return 120;let r=[];for(let l=1;l{let h=60/l,c=Math.round(h/3)*3;c>60&&c<200&&(a[c]=(a[c]||0)+1)});let o=0,n=120;for(let[l,h]of Object.entries(a))h>o&&(o=h,n=parseInt(l));return n<70&&a[n*2]?n*=2:n>160&&a[Math.round(n/2)]&&(n=Math.round(n/2)),n-1}catch(t){return console.warn("[WaveformPlayer] BPM detection failed:",t),null}}function Ct(e,t){let r=[],a=0;for(let o=0;oh&&n>.01){let c=r[r.length-1]||0,d=t*.15;o-c>d&&r.push(o)}a=n*.8+a*.2}return r}function Lt(e,t=1800){let i=e.length/t,s=e.numberOfChannels,r=[];for(let o=0;og&&(g=f),fr[l])&&(r[l]=y)}}let a=z(r);return a>0?r.map(o=>o/a):r}async function G(e,t=1800,i=!1){let s;try{let r=window.AudioContext||window.webkitAudioContext;s=new r;let o=await(await fetch(e)).arrayBuffer(),n=await s.decodeAudioData(o),l=Lt(n,t);l=_t(l);let h=null;return i&&(h=mt(n)),{peaks:l,bpm:h}}finally{s&&s.close()}}function yt(e=1800){let t=[];for(let i=0;it)return e;let s=t/i;return e.map(r=>r*s)}var K=128;function bt(e){let t=document.documentElement,i=document.body;return t.classList.contains(e)||t.classList.contains(`${e}-mode`)||t.classList.contains(`theme-${e}`)||t.getAttribute("data-theme")===e||t.getAttribute("data-color-scheme")===e||i.classList.contains(e)||i.classList.contains(`${e}-mode`)||i.getAttribute("data-theme")===e}function xt(e){let t=0,i=0;for(let s=e;s&&s.nodeType===1&&i<.995;s=s.parentElement){let r=U(getComputedStyle(s).backgroundColor);if(!r||r.a<=0)continue;let a=r.a*(1-i);t+=(r.r*299+r.g*587+r.b*114)/1e3*a,i+=a}return{sum:t,alpha:i}}function Rt(){let e=lt(getComputedStyle(document.body).color);if(e!==null)return e>K?"dark":"light";if(window.matchMedia){if(window.matchMedia("(prefers-color-scheme: dark)").matches)return"dark";if(window.matchMedia("(prefers-color-scheme: light)").matches)return"light"}return"dark"}function R(e){if(bt("dark"))return"dark";if(bt("light"))return"light";try{let t=e&&e.nodeType===1?e:document.body,{sum:i,alpha:s}=xt(t),r=Rt(),a=i+(r==="dark"?0:255)*(1-s);return a>K?"light":a ',pauseIcon:' ',onLoad:null,onPlay:null,onPause:null,onEnd:null,onError:null,onTimeUpdate:null,onNextTrack:null,onPreviousTrack:null},X={bars:{barWidth:3,barSpacing:1},mirror:{barWidth:2,barSpacing:2},line:{barWidth:2,barSpacing:0},blocks:{barWidth:4,barSpacing:2},dots:{barWidth:3,barSpacing:3},seekbar:{barWidth:1,barSpacing:0}},Q=["auto","top","center","bottom"],N=.25,F=4,Dt={buttonAlign:Q,layout:["default","preview"],buttonStyle:["circle","minimal"],artworkPosition:["info","button"],waveformStyle:Object.keys(X),waveformGradient:["vertical","horizontal","diagonal"],audioMode:["self","external"],preload:["none","metadata","auto"],colorPreset:Object.keys(C),crossOrigin:["anonymous","use-credentials"]},Ot={height:{min:1,integer:!0},samples:{min:1,integer:!0},barWidth:{min:0},barSpacing:{min:0},barRadius:{min:0},bpm:{min:1},playbackRate:{min:N,max:F}},Bt=["autoplay","showControls","showInfo","showAlbum","showTime","showHoverTime","seekHandle","showBPM","singlePlay","playOnSeek","enableMediaSession","showMarkers","accessibleSeek","showPlaybackSpeed"],Ht=["onLoad","onPlay","onPause","onEnd","onError","onTimeUpdate","onNextTrack","onPreviousTrack"];function Y(e,t){console.warn(`[WaveformPlayer] Invalid ${e} option, using default:`,t)}function $(e){let t=B(e);return t?t.reduce((i,s)=>{let r=s&&typeof s=="object"?_(s.time,null,{min:0}):null;return r===null?(Y("marker",s),i):(i.push({...s,time:r,label:s.label==null?"":s.label}),i)},[]):(e!=null&&Y("markers",e),[])}function Z(e){let t=s=>e[s]!=null,i=s=>{Y(s,e[s]),e[s]=W[s]};for(let[s,r]of Object.entries(Ot)){if(!t(s))continue;let a=_(e[s],null,r);a===null?i(s):e[s]=a}for(let[s,r]of Object.entries(Dt))t(s)&&rt(e[s],r)===null&&i(s);for(let s of Bt)e[s]=at(e[s]);for(let s of Ht)t(s)&&typeof e[s]!="function"&&i(s);if(t("playbackRates")){let s=D(e.playbackRates,{min:N,max:F,fallback:null});s===null?i("playbackRates"):e.playbackRates=s}e.markers=$(e.markers);for(let s of["buttonSize","buttonRadius"]){if(!t(s))continue;let r=e[s];(typeof r=="number"?Number.isFinite(r):typeof r=="string"&&r.trim()!=="")||i(s)}for(let s of["waveformColor","progressColor"]){if(!t(s))continue;let r=e[s];!(typeof r=="string"&&r.trim()!=="")&&!Array.isArray(r)&&i(s)}return e}var It="data:image/svg+xml,"+encodeURIComponent(' '),gt=5,wt=10,Wt='button, a[href], input, [role="slider"]',A=class e{static instances=new Map;static currentlyPlaying=null;constructor(t,i={}){if(this.container=typeof t=="string"?document.querySelector(t):t,!this.container)throw new Error("[WaveformPlayer] Container element not found");let s=H(this.container),r={...i};r.style&&!r.waveformStyle&&(r.waveformStyle=r.style),r.src&&!r.url&&(r.url=r.src),this.options=Z(j(W,s,r));let a=J(this.options.colorPreset,this.container);this._autoTheme=this.options.colorPreset==null||!C[this.options.colorPreset],this._presetKeys=[],this._scheme=this.options.colorPreset&&C[this.options.colorPreset]?this.options.colorPreset:R(this.container);for(let[n,l]of Object.entries(a))(this.options[n]===null||this.options[n]===void 0)&&(this.options[n]=l,this._presetKeys.push(n));let o=X[this.options.waveformStyle];o&&(s.barWidth===void 0&&i.barWidth===void 0&&(this.options.barWidth=o.barWidth),s.barSpacing===void 0&&i.barSpacing===void 0&&(this.options.barSpacing=o.barSpacing)),this.audio=null,this.canvas=null,this.ctx=null,this.waveformData=[],this.progress=0,this._activeMarkerIndex=-1,this._markerLabelTimer=null,this.isPlaying=!1,this.isLoading=!1,this.hasError=!1,this.updateTimer=null,this.resizeObserver=null,this._ac=new AbortController,this.id=this.container.id||nt(this.options.url),e.instances.set(this.id,this),e._watchTheme();try{this.init()}catch(n){throw e.instances.delete(this.id),this._ac.abort(),n}setTimeout(()=>{this._emit("waveformplayer:ready",{player:this,url:this.options.url})},100)}_emit(t,i,s=!1){let r=new CustomEvent(t,{bubbles:!0,cancelable:s,detail:i});return this.container.dispatchEvent(r),r}_requestSeek(t){this._emit("waveformplayer:request-seek",{...this._buildTrackDetail(),percent:t},!0).defaultPrevented||(this.progress=t,this.drawWaveform?.())}init(){this.createDOM(),this.createAudio(),this.initPlaybackSpeed(),this.initKeyboardControls(),this.initSeekControl(),this.bindEvents(),this.setupResizeObserver(),requestAnimationFrame(()=>{this.resizeCanvas(),this.options.url&&this.load(this.options.url).then(()=>{this.options.autoplay&&this.play()?.catch(()=>{})}).catch(t=>{console.error("[WaveformPlayer] Failed to load audio:",t)})})}createDOM(){this.container.innerHTML="",this.container.className="waveform-player";let t=Q.includes(this.options.buttonAlign)?this.options.buttonAlign:"auto";t==="auto"&&(this.options.waveformStyle==="bars"?t="bottom":t="center"),this.options.layout==="preview"&&this.container.classList.add("waveform-layout-preview"),this.container.classList.toggle("waveform-theme-light",this._scheme==="light");let s=[];this.options.buttonSize!=null&&s.push(`--wfp-btn-size: ${q(this.options.buttonSize)}`),this.options.buttonRadius!=null&&s.push(`--wfp-btn-radius: ${q(this.options.buttonRadius)}`);let r=s.length?` style="${s.join("; ")};"`:"",a=this.options.artworkPosition==="button"&&this.options.artwork,o=a?` `:"",n=this.options.showControls?`
${o}
${this.options.playIcon}
@@ -18,6 +18,7 @@
${this.options.artist?`${S(this.options.artist)} `:""}
+ ${this.options.showAlbum&&this.options.album?`${S(this.options.album)} `:""}
${this.options.showBPM?`
@@ -61,4 +62,4 @@
${l}
-`,this.playBtn=this.container.querySelector(".waveform-btn"),this.canvas=this.container.querySelector("canvas"),this.ctx=this.canvas.getContext("2d"),this.titleEl=this.container.querySelector(".waveform-title"),this.artistEl=this.container.querySelector(".waveform-artist"),this.artworkEl=this.container.querySelector(".waveform-artwork, .waveform-btn-artwork"),this.bindArtworkFallback(this.artworkEl),this.currentTimeEl=this.container.querySelector(".time-current"),this.totalTimeEl=this.container.querySelector(".time-total"),this.bpmEl=this.container.querySelector(".waveform-bpm"),this.bpmValueEl=this.container.querySelector(".bpm-value"),this.loadingEl=this.container.querySelector(".waveform-loading"),this.errorEl=this.container.querySelector(".waveform-error"),this.markersContainer=this.container.querySelector(".waveform-markers"),this.speedBtn=this.container.querySelector(".speed-btn"),this.speedMenu=this.container.querySelector(".speed-menu"),this.resizeCanvas(),this.updateBPMDisplay()}bindArtworkFallback(t){t&&t.addEventListener("error",()=>{t.src.startsWith("data:")||(t.src=Ot)},{signal:this._ac.signal})}createArtworkElement(){let t=document.createElement("img");return t.className="waveform-artwork",t.style.width="40px",t.style.height="40px",t.style.borderRadius="4px",t.style.objectFit="cover",t.style.flexShrink="0",this.bindArtworkFallback(t),t}createButtonArtworkElement(){let t=document.createElement("img");return t.className="waveform-btn-artwork",t.alt="",t.setAttribute("aria-hidden","true"),this.bindArtworkFallback(t),t}createArtistElement(){let t=document.createElement("span");return t.className="waveform-artist",t}syncArtist(t){if(this.options.artist=t||null,!!this.options.showInfo){if(!t){this.artistEl?.remove(),this.artistEl=null;return}if(!this.artistEl){let i=this.container.querySelector(".waveform-title");if(!i)return;this.artistEl=this.createArtistElement(),i.after(this.artistEl)}this.artistEl.textContent=t,this.artistEl.style.display=""}}syncButtonArtwork(t){if(this.playBtn){if(!t){this.artworkEl?.remove(),this.artworkEl=null,this.playBtn.classList.remove("waveform-btn-has-artwork");return}this.artworkEl||(this.artworkEl=this.createButtonArtworkElement(),this.playBtn.prepend(this.artworkEl)),this.artworkEl.src=t,this.playBtn.classList.add("waveform-btn-has-artwork")}}syncArtwork(t,i=""){if(this.options.artwork=t||null,this.options.artworkAlt=i||"",this.options.artworkPosition==="button"){this.syncButtonArtwork(this.options.artwork);return}if(this.options.showInfo){if(!t){this.artworkEl?.remove(),this.artworkEl=null;return}if(!this.artworkEl){let s=this.container.querySelector(".waveform-text");if(!s)return;this.artworkEl=this.createArtworkElement(),s.before(this.artworkEl)}this.artworkEl.src=t,this.artworkEl.alt=i||""}}createAudio(){if(this.options.audioMode==="external"){this.audio=null;return}this.audio=new Audio,this.audio.preload=this.options.preload||"metadata",this.options.crossOrigin&&(this.audio.crossOrigin=this.options.crossOrigin)}initPlaybackSpeed(){this.audio&&this.options.playbackRate&&this.options.playbackRate!==1&&(this.audio.playbackRate=this.options.playbackRate),this.options.showPlaybackSpeed&&this.initSpeedControls()}initSpeedControls(){let t=this.container.querySelector(".speed-btn"),i=this.container.querySelector(".speed-menu");if(!t||!i)return;let s=()=>Array.from(i.querySelectorAll(".speed-option")),r=()=>i.style.display!=="none",a=l=>{if(i.style.display=l?"block":"none",t.setAttribute("aria-expanded",l?"true":"false"),l){let h=s();(h.find(c=>c.getAttribute("aria-checked")==="true")||h[0])?.focus()}},o=l=>{let h=s();h.length&&h[(l+h.length)%h.length].focus()},n=l=>{this.setPlaybackRate(parseFloat(l.dataset.rate)),a(!1),t.focus()};t.addEventListener("click",l=>{l.stopPropagation(),a(!r())},{signal:this._ac.signal}),document.addEventListener("click",()=>a(!1),{signal:this._ac.signal}),i.addEventListener("click",l=>{l.stopPropagation();let h=l.target.closest(".speed-option");h&&n(h)},{signal:this._ac.signal}),t.closest(".waveform-speed")?.addEventListener("keydown",l=>{let h=s(),c=h.indexOf(document.activeElement);if(!r()){(l.key==="ArrowDown"||l.key==="ArrowUp")&&document.activeElement===t&&(l.preventDefault(),a(!0));return}switch(l.key){case"ArrowDown":l.preventDefault(),o(c<0?0:c+1);break;case"ArrowUp":l.preventDefault(),o(c<0?h.length-1:c-1);break;case"Home":l.preventDefault(),o(0);break;case"End":l.preventDefault(),o(h.length-1);break;case"Escape":l.preventDefault(),a(!1),t.focus();break;case"Tab":a(!1);break}},{signal:this._ac.signal}),this.updateSpeedUI()}initKeyboardControls(){this.container.setAttribute("tabindex","-1"),this.container.addEventListener("click",t=>{t.target.closest(Wt)||(e.getAllInstances().forEach(i=>{i!==this&&i.container.setAttribute("tabindex","-1")}),this.container.setAttribute("tabindex","0"),this.container.focus())},{signal:this._ac.signal}),this.container.addEventListener("keydown",t=>{if(document.activeElement!==this.container)return;let i=t.key,s=!!this.audio,r=s?this.audio.currentTime:0;if(s&&i>="0"&&i<="9"){t.preventDefault(),this.seekToPercent(parseInt(i)/10);return}let a={" ":()=>this.togglePlay()};s&&(a.ArrowLeft=()=>this.seekTo(m(r-5,0,this.audio.duration)),a.ArrowRight=()=>this.seekTo(m(r+5,0,this.audio.duration)),a.ArrowUp=()=>this.setVolume(m(this.audio.volume+.1)),a.ArrowDown=()=>this.setVolume(m(this.audio.volume-.1)),a.m=a.M=()=>this.audio.muted=!this.audio.muted),a[i]&&(t.preventDefault(),a[i]())},{signal:this._ac.signal})}initSeekControl(){this.options.accessibleSeek&&(this.seekEl=this.container.querySelector(".waveform-container"),this.seekEl&&(this.seekEl.setAttribute("role","slider"),this.seekEl.setAttribute("tabindex","0"),this.seekEl.setAttribute("aria-valuemin","0"),this.applySeekLabel(),this.updateSeekAccessibility(),this.seekEl.addEventListener("keydown",t=>{if(t.key===" "||t.key==="Spacebar"){t.preventDefault(),t.stopPropagation(),this.togglePlay();return}let i=this.getSeekDuration();if(!i)return;let s=this.getSeekCurrentTime(),r;switch(t.key){case"ArrowLeft":case"ArrowDown":r=s-bt;break;case"ArrowRight":case"ArrowUp":r=s+bt;break;case"PageDown":r=s-wt;break;case"PageUp":r=s+wt;break;case"Home":r=0;break;case"End":r=i;break;default:return}t.preventDefault(),t.stopPropagation(),this.seekToSeconds(r)},{signal:this._ac.signal})))}getSeekDuration(){return this.options.audioMode==="external"?this._extDuration||0:this.audio&&Number.isFinite(this.audio.duration)?this.audio.duration:0}getSeekCurrentTime(){return this.options.audioMode==="external"?this.progress*(this._extDuration||0):this.audio&&Number.isFinite(this.audio.currentTime)?this.audio.currentTime:0}seekToSeconds(t){let i=this.getSeekDuration();if(!i)return;let s=m(t,0,i);if(this.options.audioMode==="external"){this._requestSeek(s/i),this.updateSeekAccessibility();return}this.seekTo(s)}applySeekLabel(t=this.options.title){if(!this.seekEl)return;let i=this.options.seekLabel||t||"Seek";this.seekEl.setAttribute("aria-label",i)}updateSeekAccessibility(){if(!this.seekEl)return;let t=this.getSeekDuration(),i=Math.min(this.getSeekCurrentTime(),t);this.seekEl.setAttribute("aria-valuemax",String(Math.round(t))),this.seekEl.setAttribute("aria-valuenow",String(Math.round(i))),this.seekEl.setAttribute("aria-valuetext",ot(this.options.seekValueText||"%1$s of %2$s",E(i),E(t)))}initMediaSession(){if(!("mediaSession"in navigator)||!this.options.enableMediaSession||!this.audio)return;this._applyMediaMetadata(),navigator.mediaSession.setActionHandler("play",()=>this.play()),navigator.mediaSession.setActionHandler("pause",()=>this.pause()),navigator.mediaSession.setActionHandler("seekbackward",()=>{this.seekTo(m(this.audio.currentTime-10,0,this.audio.duration))}),navigator.mediaSession.setActionHandler("seekforward",()=>{this.seekTo(m(this.audio.currentTime+10,0,this.audio.duration))}),navigator.mediaSession.setActionHandler("seekto",s=>{s.seekTime!==null&&this.seekTo(s.seekTime)});let t=this.options.onNextTrack,i=this.options.onPreviousTrack;try{navigator.mediaSession.setActionHandler("nexttrack",typeof t=="function"?()=>t(this):null)}catch{}try{navigator.mediaSession.setActionHandler("previoustrack",typeof i=="function"?()=>i(this):null)}catch{}}_applyMediaMetadata(){!("mediaSession"in navigator)||!this.options.enableMediaSession||(navigator.mediaSession.metadata=new MediaMetadata({title:this.options.title||this.options.unknownTrackText,artist:this.options.artist||"",album:this.options.album||"",artwork:this.options.artwork?[{src:this.options.artwork,sizes:"512x512",type:"image/jpeg"}]:[]}))}_updateMediaSession(t){if(!(!("mediaSession"in navigator)||!this.options.enableMediaSession||!this.audio))try{t==="playing"&&this.initMediaSession(),navigator.mediaSession.playbackState=t;let i=this.audio.duration;navigator.mediaSession.setPositionState&&i&&isFinite(i)&&navigator.mediaSession.setPositionState({duration:i,playbackRate:this.audio.playbackRate||1,position:m(this.audio.currentTime,0,i)})}catch{}}bindEvents(){this.playBtn&&this.playBtn.addEventListener("click",()=>this.togglePlay()),this.audio&&(this.audio.addEventListener("loadstart",()=>this.setLoading(!0)),this.audio.addEventListener("loadedmetadata",()=>this.onMetadataLoaded()),this.audio.addEventListener("canplay",()=>this.setLoading(!1)),this.audio.addEventListener("play",()=>this.onPlay()),this.audio.addEventListener("pause",()=>this.onPause()),this.audio.addEventListener("ended",()=>this.onEnded()),this.audio.addEventListener("error",i=>this.onError(i))),this.canvas.addEventListener("click",i=>this.handleCanvasClick(i)),this._dragging=!1,this._seekHover=!1,this._handleNear=!1,this.canvas.addEventListener("pointerenter",()=>{this._seekHover=!0,this.drawWaveform(),this._updateSeekHandle()}),this.canvas.addEventListener("pointerleave",()=>{this._seekHover=!1,this._handleNear=!1,this._dragging||this._hideHoverTip(),this.drawWaveform(),this._updateSeekHandle()}),this.canvas.addEventListener("pointerdown",i=>{if(!(i.pointerType==="mouse"&&i.button!==0)){this._dragging=!0;try{this.canvas.setPointerCapture(i.pointerId)}catch{}this._scrubTo(i.clientX)}}),this.canvas.addEventListener("pointermove",i=>{if(this._dragging){this._scrubTo(i.clientX);return}let s=this.canvas.getBoundingClientRect();s.width&&(this._handleNear=Math.abs(i.clientX-s.left-this.progress*s.width)<=10,this._updateSeekHandle())});let t=i=>{if(this._dragging){this._dragging=!1,this._suppressClick=!0;try{this.canvas.releasePointerCapture(i.pointerId)}catch{}this._seekFromPointer(i.clientX),!this._seekHover&&!this.options.showHoverTime&&this._hideHoverTip(),this._updateSeekHandle()}};this.canvas.addEventListener("pointerup",t),this.canvas.addEventListener("pointercancel",t),this.setupHoverTime(),this.setupSeekHandle(),this.resizeHandler=ht(()=>this.resizeCanvas(),100),window.addEventListener("resize",this.resizeHandler)}setupResizeObserver(){"ResizeObserver"in window&&(this.resizeObserver=new ResizeObserver(()=>{this.resizeCanvas()}),this.canvas?.parentElement&&this.resizeObserver.observe(this.canvas.parentElement))}async load(t){try{this.setLoading(!0),this.progress=0,this.hasError=!1,this.container.classList.remove("waveform-is-placeholder");let i=!!this.options.waveform;i&&this.setWaveformData(this.options.waveform);let s=this.options.title||O(t);if(this.titleEl&&(this.titleEl.textContent=s),this.applySeekLabel(s),this.audio&&(this.audio.src=t,this.audio.preload!=="none"&&await new Promise((r,a)=>{let o=()=>{this.audio.removeEventListener("loadedmetadata",o),this.audio.removeEventListener("error",n),r()},n=l=>{this.audio.removeEventListener("loadedmetadata",o),this.audio.removeEventListener("error",n),a(l)};this.audio.addEventListener("loadedmetadata",o),this.audio.addEventListener("error",n)})),!i)try{let r=await G(t,this.options.samples,this.options.showBPM);this.waveformData=r.peaks,r.bpm&&(this.detectedBPM=r.bpm,this.updateBPMDisplay())}catch(r){console.warn("[WaveformPlayer] Using placeholder waveform:",r),this.waveformData=yt(this.options.samples),this.container.classList.add("waveform-is-placeholder")}this.drawWaveform(),this.renderMarkers(),this.options.onLoad&&this.options.onLoad(this)}catch(i){this.onError(i)}finally{this.setLoading(!1)}}async loadTrack(t,i=null,s=null,r={}){let a=Object.prototype.hasOwnProperty.call(r,"artwork"),o=Object.prototype.hasOwnProperty.call(r,"artworkAlt");this.isPlaying&&this.pause(),this.audio&&(this.audio.src="",this.audio.load()),this.hasError=!1,this.errorEl&&(this.errorEl.style.display="none"),this.canvas&&(this.canvas.style.opacity="1"),this.playBtn&&(this.playBtn.disabled=!1),this.progress=0,this.waveformData=[],this.options=Z(j(this.options,{url:t,title:i===null?this.options.title:i,artist:s===null?this.options.artist:s,...r})),a&&(this.options.artwork=r.artwork||null),o?this.options.artworkAlt=r.artworkAlt||"":a&&(this.options.artworkAlt=this.options.artwork?W.artworkAlt:""),r.preload&&this.audio&&(this.audio.preload=this.options.preload),r.crossOrigin&&this.audio&&(this.audio.crossOrigin=this.options.crossOrigin),s!==null&&this.syncArtist(s),(a||o)&&this.syncArtwork(a?r.artwork:this.options.artwork,o?r.artworkAlt:this.options.artworkAlt),this.options.markers=r.markers?z(r.markers):[],this.options.waveform=r.waveform||null,await this.load(t),r.autoplay!==!1&&this.play()?.catch(()=>{})}setWaveformData(t){if(typeof t=="string"&&t.trim().endsWith(".json")){fetch(t.trim()).then(i=>i.json()).then(i=>{this.waveformData=Array.isArray(i)?i:i.peaks||[],i.markers&&!this.options.markers?.length&&(this.options.markers=z(i.markers),this.renderMarkers()),this.drawWaveform()}).catch(()=>{});return}this.waveformData=D(t,{fallback:[]}),this.drawWaveform()}drawWaveform(){!this.ctx||this.waveformData.length===0||ft(this.ctx,this.canvas,this.waveformData,this.progress,{...this.options,waveformStyle:this.options.waveformStyle||"bars",color:this.options.waveformColor,progressColor:this.options.progressColor,seekActive:this._seekHover||this._dragging})}resizeCanvas(){if(!this.canvas||this.isDestroying)return;let t=window.devicePixelRatio||1,i=this.canvas.parentElement.getBoundingClientRect();this.canvas.width=i.width*t,this.canvas.height=this.options.height*t,this.canvas.parentElement.style.height=this.options.height+"px",this.drawWaveform()}renderMarkers(){if(!this.markersContainer||(this.markersContainer.innerHTML="",this._activeMarkerIndex=-1,clearTimeout(this._markerLabelTimer),!this.options.showMarkers||!this.options.markers?.length))return;let t=this.getSeekDuration();t&&this.options.markers.forEach((i,s)=>{if(i.time>t){console.warn(`[WaveformPlayer] Marker "${i.label}" at ${i.time}s exceeds audio duration of ${t}s`);return}let r=i.time/t*100,a=document.createElement("button");a.className="waveform-marker",a.style.left=`${r}%`,a.style.backgroundColor=i.color||"rgba(255, 255, 255, 0.5)",a.setAttribute("aria-label",i.label),a.setAttribute("data-time",i.time);let o=document.createElement("span");o.className="waveform-marker-tooltip",o.textContent=i.label,a.appendChild(o),a.addEventListener("click",n=>{n.stopPropagation(),this.seekTo(i.time),this.options.playOnSeek&&!this.isPlaying&&this.play()}),this.markersContainer.appendChild(a)})}setActiveMarker(t){if(!this.markersContainer)return;this.markersContainer.querySelectorAll(".waveform-marker").forEach((s,r)=>s.classList.toggle("active",r===t))}updateActiveMarker(){if(!this.markersContainer)return;let t=this.markersContainer.querySelectorAll(".waveform-marker");if(!t.length)return;let i=this.getSeekDuration(),s=i?this.progress*i:0,r=-1,a=-1/0;t.forEach((o,n)=>{let l=parseFloat(o.getAttribute("data-time"));Number.isFinite(l)&&l<=s+.05&&l>a&&(a=l,r=n)}),r!==this._activeMarkerIndex&&(this._activeMarkerIndex=r,this.setActiveMarker(r),clearTimeout(this._markerLabelTimer),t.forEach((o,n)=>o.classList.toggle("show-label",n===r)),r>=0&&(this._markerLabelTimer=setTimeout(()=>{this.markersContainer?.querySelectorAll(".waveform-marker").forEach(o=>o.classList.remove("show-label"))},2500)))}setupHoverTime(){if(!this.seekEl)return;let t=document.createElement("div");t.className="waveform-hover-time",t.setAttribute("aria-hidden","true"),this.seekEl.appendChild(t),this.hoverTimeEl=t,this.options.showHoverTime&&(this.seekEl.addEventListener("pointermove",i=>{this._dragging||this._updateHoverTip(i.clientX)}),this.seekEl.addEventListener("pointerleave",()=>{this._dragging||this._hideHoverTip()}))}_updateHoverTip(t){let i=this.hoverTimeEl;if(!i)return;let s=this.getSeekDuration();if(!s){i.style.opacity="0";return}let r=this.canvas.getBoundingClientRect(),a=m((t-r.left)/r.width);i.textContent=E(a*s),i.style.left=a*100+"%",i.style.opacity="1"}_hideHoverTip(){this.hoverTimeEl&&(this.hoverTimeEl.style.opacity="0")}_scrubTo(t){let i=this.canvas.getBoundingClientRect();if(!i.width)return;this.progress=m((t-i.left)/i.width),this.drawWaveform(),this._updateSeekHandle();let s=this.getSeekDuration();s&&this.currentTimeEl?(this.currentTimeEl.textContent=E(this.progress*s),this._hideHoverTip()):this._updateHoverTip(t)}setupSeekHandle(){if(!this.options.seekHandle||this.options.waveformStyle!=="seekbar"||!this.seekEl)return;let t=document.createElement("div");t.className="waveform-seek-handle",t.setAttribute("aria-hidden","true"),this.seekEl.appendChild(t),this.seekHandleEl=t}_updateSeekHandle(){let t=this.seekHandleEl;t&&(t.style.left=this.progress*100+"%",t.classList.toggle("is-visible",this._seekHover||this._dragging),t.classList.toggle("is-active",this._dragging||this._handleNear))}handleCanvasClick(t){if(this._suppressClick){this._suppressClick=!1;return}this._seekFromPointer(t.clientX)}_seekFromPointer(t){let i=this.canvas.getBoundingClientRect();if(!i.width)return;let s=m((t-i.left)/i.width);if(this.options.audioMode==="external"){this._requestSeek(s);return}!this.audio||!this.audio.duration||this.seekToPercent(s)}setLoading(t){if(this.isLoading=t,this.loadingEl){let i=t&&this.waveformData.length===0;this.loadingEl.style.display=i?"block":"none"}this.seekEl&&this.seekEl.setAttribute("aria-busy",t?"true":"false")}onMetadataLoaded(){this.isDestroying||(this.totalTimeEl&&(this.totalTimeEl.textContent=E(this.audio.duration)),this.renderMarkers(),this.updateSeekAccessibility())}setPlayButtonState(t){if(!this.playBtn)return;this.playBtn.classList.toggle("playing",t);let i=this.playBtn.querySelector(".waveform-icon-play"),s=this.playBtn.querySelector(".waveform-icon-pause");i&&(i.style.display=t?"none":"flex"),s&&(s.style.display=t?"flex":"none")}onPlay(){this.isDestroying||(this.isPlaying=!0,this.setPlayButtonState(!0),this.startSmoothUpdate(),this._updateMediaSession("playing"),this._emit("waveformplayer:play",{player:this,url:this.options.url}),this.options.onPlay&&this.options.onPlay(this))}onPause(){this.isDestroying||(this.isPlaying=!1,this.setPlayButtonState(!1),this.stopSmoothUpdate(),this._updateMediaSession("paused"),this._emit("waveformplayer:pause",{player:this,url:this.options.url}),this.options.onPause&&this.options.onPause(this))}onEnded(){if(this.isDestroying)return;let t=this.audio.duration;this.progress=0,this.audio.currentTime=0,this.drawWaveform(),this.currentTimeEl&&(this.currentTimeEl.textContent="0:00"),this._emit("waveformplayer:ended",{player:this,url:this.options.url,currentTime:t,duration:t}),this.onPause(),this.options.onEnd&&this.options.onEnd(this)}onError(t){this.isDestroying||(console.error("[WaveformPlayer] Audio error:",t),this.hasError=!0,this.setLoading(!1),this.errorEl&&(this.errorEl.style.display="flex"),this.canvas&&(this.canvas.style.opacity="0.2"),this.playBtn&&(this.playBtn.disabled=!0),this.options.onError&&this.options.onError(t,this))}startSmoothUpdate(){this.stopSmoothUpdate();let t=()=>{this.isPlaying&&this.audio&&this.audio.duration&&(this.updateProgress(),this.updateTimer=requestAnimationFrame(t))};this.updateTimer=requestAnimationFrame(t)}stopSmoothUpdate(){this.updateTimer&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null)}updateProgress(){if(!this.audio||!this.audio.duration||this._dragging)return;let t=this.audio.currentTime/this.audio.duration;Math.abs(t-this.progress)>.001&&(this.progress=t,this.drawWaveform(),this._updateSeekHandle()),this.currentTimeEl&&(this.currentTimeEl.textContent=E(this.audio.currentTime)),this._emit("waveformplayer:timeupdate",{player:this,currentTime:this.audio.currentTime,duration:this.audio.duration,progress:this.progress,url:this.options.url}),this.options.onTimeUpdate&&this.options.onTimeUpdate(this.audio.currentTime,this.audio.duration,this),this.updateActiveMarker(),this.updateSeekAccessibility()}updateBPMDisplay(){let t=this.options.bpm||this.detectedBPM;this.bpmEl&&this.bpmValueEl&&t&&(this.bpmValueEl.textContent=Math.round(t),this.bpmEl.style.display="inline-flex")}refreshTheme(){if(!this._autoTheme)return;this._scheme=R(this.container);let t=J(this.options.colorPreset,this.container);for(let i of this._presetKeys||[])i in t&&(this.options[i]=t[i]);this._applyThemeColors()}_applyThemeColors(){this.container.classList.toggle("waveform-theme-light",this._scheme==="light"),this.canvas&&this.drawWaveform()}static _watchTheme(){if(e._themeWatch||typeof document>"u")return;let t=()=>requestAnimationFrame(()=>{e.instances.forEach(a=>{try{a.refreshTheme()}catch{}})}),i={attributes:!0,attributeFilter:["class","data-theme","data-color-scheme","style"]},s=new MutationObserver(t);s.observe(document.documentElement,i),document.body&&s.observe(document.body,i);let r=null;try{r=window.matchMedia("(prefers-color-scheme: dark)"),r.addEventListener("change",t)}catch{}e._themeWatch={obs:s,mq:r,refresh:t}}updateSpeedUI(){if(!this.audio)return;let t=this.container.querySelector(".speed-value");if(t){let i=this.audio.playbackRate;t.textContent=i===1?"1x":`${i}x`}this.container.querySelectorAll(".speed-option").forEach(i=>{let s=parseFloat(i.dataset.rate)===this.audio.playbackRate;i.classList.toggle("active",s),i.setAttribute("aria-checked",s?"true":"false")})}play(){if(this.options.singlePlay&&e.currentlyPlaying&&e.currentlyPlaying!==this&&e.currentlyPlaying.pause(),this.options.audioMode==="external"){this._emit("waveformplayer:request-play",this._buildTrackDetail(),!0).defaultPrevented||(e.currentlyPlaying=this);return}return e.currentlyPlaying=this,this.audio.play()}pause(){if(e.currentlyPlaying===this&&(e.currentlyPlaying=null),this.options.audioMode==="external"){this._emit("waveformplayer:request-pause",this._buildTrackDetail(),!0);return}this.audio.pause()}_buildTrackDetail(){return{url:this.options.url,title:this.options.title,artist:this.options.artist,artwork:this.options.artwork,markers:this.options.markers,waveform:this.options.waveform,id:this.id,player:this}}setPlayingState(t){let i=this.isPlaying;this.isPlaying=!!t,this.setPlayButtonState(this.isPlaying),this.isPlaying&&!i?(this.startSmoothUpdate?.(),this._emit("waveformplayer:play",{player:this,url:this.options.url}),this.options.onPlay&&this.options.onPlay(this)):!this.isPlaying&&i&&(this.stopSmoothUpdate?.(),this._emit("waveformplayer:pause",{player:this,url:this.options.url}),this.options.onPause&&this.options.onPause(this))}setProgress(t,i){!i||i<=0||(this.progress=m(t/i),this.currentTimeEl&&(this.currentTimeEl.textContent=E(t)),this._extDuration=i,this.totalTimeEl&&(!this.totalTimeEl.dataset._extSet||this.totalTimeEl.dataset._extDur!==String(i))&&(this.totalTimeEl.textContent=E(i),this.totalTimeEl.dataset._extSet="1",this.totalTimeEl.dataset._extDur=String(i)),this.drawWaveform?.(),this.updateActiveMarker(),this._emit("waveformplayer:timeupdate",{player:this,currentTime:t,duration:i,progress:this.progress,url:this.options.url}),this.options.onTimeUpdate&&this.options.onTimeUpdate(t,i,this),this.progress>=1?this._extEnded||(this._extEnded=!0,this._emit("waveformplayer:ended",{player:this,url:this.options.url,currentTime:i,duration:i}),this.options.onEnd&&this.options.onEnd(this)):this._extEnded=!1,this.updateSeekAccessibility())}togglePlay(){this.isPlaying?this.pause():this.play()}seekTo(t){this.audio&&this.audio.duration&&(this.audio.currentTime=m(t,0,this.audio.duration),this.updateProgress())}seekToPercent(t){this.audio&&this.audio.duration&&(this.audio.currentTime=this.audio.duration*m(t),this.updateProgress())}setVolume(t){let i=Number(t);this.audio&&Number.isFinite(i)&&(this.audio.volume=m(i))}setPlaybackRate(t){if(!this.audio)return;let i=_(t,null,{min:N,max:F});i!==null&&(this.audio.playbackRate=i,this.options.playbackRate=i,this.updateSpeedUI())}destroy(){this.isDestroying=!0,this._emit("waveformplayer:destroy",{player:this,url:this.options.url}),this.pause(),this.stopSmoothUpdate(),clearTimeout(this._markerLabelTimer),this._ac?.abort(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),e.instances.delete(this.id),e.currentlyPlaying===this&&(e.currentlyPlaying=null),this.audio&&(this.audio.pause(),this.audio.src="",this.audio.load(),this.audio=null),this.container.innerHTML="",delete this.container.dataset.waveformInitialized,this.canvas=null,this.ctx=null,this.playBtn=null,this.waveformData=[]}static getInstance(t){if(typeof t=="string"){let i=this.instances.get(t);if(i)return i;let s=document.getElementById(t);if(s)return Array.from(this.instances.values()).find(r=>r.container===s)}if(t instanceof HTMLElement)return Array.from(this.instances.values()).find(i=>i.container===t)}static getAllInstances(){return Array.from(this.instances.values())}static destroyAll(){this.instances.forEach(t=>t.destroy()),this.instances.clear()}static async generateWaveformData(t,i=1800){try{return(await G(t,i)).peaks}catch(s){throw console.error("[WaveformPlayer] Failed to generate waveform:",s),s}}static getPeaksUrl(t){if(!t)return;let i=t.replace(/\.(mp3|wav|ogg|flac|m4a|aac)(\?[^#]*)?(#.*)?$/i,".json$2$3");return i===t?void 0:i}};T.utils={formatTime:E,extractTitleFromUrl:O,escapeHtml:S,isSafeHref:st,parseDataAttributes:I,detectColorScheme:R};var et=()=>typeof window<"u"&&typeof document<"u",Nt=()=>document.documentElement?.dataset.waveformAutoinit==="false";function kt(e){if(!(e.dataset.waveformInitialized==="true"||T.getInstance(e)))try{new T(e),e.dataset.waveformInitialized="true"}catch(t){console.error("[WaveformPlayer] Failed to initialize:",t,e)}}function tt(e=document){if(!et())return;let t=e||document;t.matches?.("[data-waveform-player]")&&kt(t),t.querySelectorAll("[data-waveform-player]").forEach(kt)}et()&&!Nt()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>tt()):tt());T.init=tt;et()&&(window.WaveformPlayer=T);var se=T;})();
+`,this.playBtn=this.container.querySelector(".waveform-btn"),this.canvas=this.container.querySelector("canvas"),this.ctx=this.canvas.getContext("2d"),this.titleEl=this.container.querySelector(".waveform-title"),this.artistEl=this.container.querySelector(".waveform-artist"),this.albumEl=this.container.querySelector(".waveform-album"),this.artworkEl=this.container.querySelector(".waveform-artwork, .waveform-btn-artwork"),this.bindArtworkFallback(this.artworkEl),this.currentTimeEl=this.container.querySelector(".time-current"),this.totalTimeEl=this.container.querySelector(".time-total"),this.bpmEl=this.container.querySelector(".waveform-bpm"),this.bpmValueEl=this.container.querySelector(".bpm-value"),this.loadingEl=this.container.querySelector(".waveform-loading"),this.errorEl=this.container.querySelector(".waveform-error"),this.markersContainer=this.container.querySelector(".waveform-markers"),this.speedBtn=this.container.querySelector(".speed-btn"),this.speedMenu=this.container.querySelector(".speed-menu"),this.resizeCanvas(),this.updateBPMDisplay()}bindArtworkFallback(t){t&&t.addEventListener("error",()=>{t.src.startsWith("data:")||(t.src=It)},{signal:this._ac.signal})}createArtworkElement(){let t=document.createElement("img");return t.className="waveform-artwork",t.style.width="40px",t.style.height="40px",t.style.borderRadius="4px",t.style.objectFit="cover",t.style.flexShrink="0",this.bindArtworkFallback(t),t}createButtonArtworkElement(){let t=document.createElement("img");return t.className="waveform-btn-artwork",t.alt="",t.setAttribute("aria-hidden","true"),this.bindArtworkFallback(t),t}createArtistElement(){let t=document.createElement("span");return t.className="waveform-artist",t}createAlbumElement(){let t=document.createElement("span");return t.className="waveform-album",t}syncArtist(t){if(this.options.artist=t||null,!!this.options.showInfo){if(!t){this.artistEl?.remove(),this.artistEl=null;return}if(!this.artistEl){let i=this.container.querySelector(".waveform-title");if(!i)return;this.artistEl=this.createArtistElement(),i.after(this.artistEl)}this.artistEl.textContent=t,this.artistEl.style.display=""}}syncAlbum(t){if(this.options.album=t||"",!this.options.showInfo||!this.options.showAlbum){this.albumEl?.remove(),this.albumEl=null;return}if(!t){this.albumEl?.remove(),this.albumEl=null;return}if(!this.albumEl){let i=this.artistEl||this.container.querySelector(".waveform-title");if(!i)return;this.albumEl=this.createAlbumElement(),i.after(this.albumEl)}this.albumEl.textContent=t,this.albumEl.style.display=""}syncButtonArtwork(t){if(this.playBtn){if(!t){this.artworkEl?.remove(),this.artworkEl=null,this.playBtn.classList.remove("waveform-btn-has-artwork");return}this.artworkEl||(this.artworkEl=this.createButtonArtworkElement(),this.playBtn.prepend(this.artworkEl)),this.artworkEl.src=t,this.playBtn.classList.add("waveform-btn-has-artwork")}}syncArtwork(t,i=""){if(this.options.artwork=t||null,this.options.artworkAlt=i||"",this.options.artworkPosition==="button"){this.syncButtonArtwork(this.options.artwork);return}if(this.options.showInfo){if(!t){this.artworkEl?.remove(),this.artworkEl=null;return}if(!this.artworkEl){let s=this.container.querySelector(".waveform-text");if(!s)return;this.artworkEl=this.createArtworkElement(),s.before(this.artworkEl)}this.artworkEl.src=t,this.artworkEl.alt=i||""}}createAudio(){if(this.options.audioMode==="external"){this.audio=null;return}this.audio=new Audio,this.audio.preload=this.options.preload||"metadata",this.options.crossOrigin&&(this.audio.crossOrigin=this.options.crossOrigin)}initPlaybackSpeed(){this.audio&&this.options.playbackRate&&this.options.playbackRate!==1&&(this.audio.playbackRate=this.options.playbackRate),this.options.showPlaybackSpeed&&this.initSpeedControls()}initSpeedControls(){let t=this.container.querySelector(".speed-btn"),i=this.container.querySelector(".speed-menu");if(!t||!i)return;let s=()=>Array.from(i.querySelectorAll(".speed-option")),r=()=>i.style.display!=="none",a=l=>{if(i.style.display=l?"block":"none",t.setAttribute("aria-expanded",l?"true":"false"),l){let h=s();(h.find(c=>c.getAttribute("aria-checked")==="true")||h[0])?.focus()}},o=l=>{let h=s();h.length&&h[(l+h.length)%h.length].focus()},n=l=>{this.setPlaybackRate(parseFloat(l.dataset.rate)),a(!1),t.focus()};t.addEventListener("click",l=>{l.stopPropagation(),a(!r())},{signal:this._ac.signal}),document.addEventListener("click",()=>a(!1),{signal:this._ac.signal}),i.addEventListener("click",l=>{l.stopPropagation();let h=l.target.closest(".speed-option");h&&n(h)},{signal:this._ac.signal}),t.closest(".waveform-speed")?.addEventListener("keydown",l=>{let h=s(),c=h.indexOf(document.activeElement);if(!r()){(l.key==="ArrowDown"||l.key==="ArrowUp")&&document.activeElement===t&&(l.preventDefault(),a(!0));return}switch(l.key){case"ArrowDown":l.preventDefault(),o(c<0?0:c+1);break;case"ArrowUp":l.preventDefault(),o(c<0?h.length-1:c-1);break;case"Home":l.preventDefault(),o(0);break;case"End":l.preventDefault(),o(h.length-1);break;case"Escape":l.preventDefault(),a(!1),t.focus();break;case"Tab":a(!1);break}},{signal:this._ac.signal}),this.updateSpeedUI()}initKeyboardControls(){this.container.setAttribute("tabindex","-1"),this.container.addEventListener("click",t=>{t.target.closest(Wt)||(e.getAllInstances().forEach(i=>{i!==this&&i.container.setAttribute("tabindex","-1")}),this.container.setAttribute("tabindex","0"),this.container.focus())},{signal:this._ac.signal}),this.container.addEventListener("keydown",t=>{if(document.activeElement!==this.container)return;let i=t.key,s=!!this.audio,r=s?this.audio.currentTime:0;if(s&&i>="0"&&i<="9"){t.preventDefault(),this.seekToPercent(parseInt(i)/10);return}let a={" ":()=>this.togglePlay()};s&&(a.ArrowLeft=()=>this.seekTo(m(r-5,0,this.audio.duration)),a.ArrowRight=()=>this.seekTo(m(r+5,0,this.audio.duration)),a.ArrowUp=()=>this.setVolume(m(this.audio.volume+.1)),a.ArrowDown=()=>this.setVolume(m(this.audio.volume-.1)),a.m=a.M=()=>this.audio.muted=!this.audio.muted),a[i]&&(t.preventDefault(),a[i]())},{signal:this._ac.signal})}initSeekControl(){this.options.accessibleSeek&&(this.seekEl=this.container.querySelector(".waveform-container"),this.seekEl&&(this.seekEl.setAttribute("role","slider"),this.seekEl.setAttribute("tabindex","0"),this.seekEl.setAttribute("aria-valuemin","0"),this.applySeekLabel(),this.updateSeekAccessibility(),this.seekEl.addEventListener("keydown",t=>{if(t.key===" "||t.key==="Spacebar"){t.preventDefault(),t.stopPropagation(),this.togglePlay();return}let i=this.getSeekDuration();if(!i)return;let s=this.getSeekCurrentTime(),r;switch(t.key){case"ArrowLeft":case"ArrowDown":r=s-gt;break;case"ArrowRight":case"ArrowUp":r=s+gt;break;case"PageDown":r=s-wt;break;case"PageUp":r=s+wt;break;case"Home":r=0;break;case"End":r=i;break;default:return}t.preventDefault(),t.stopPropagation(),this.seekToSeconds(r)},{signal:this._ac.signal})))}getSeekDuration(){return this.options.audioMode==="external"?this._extDuration||0:this.audio&&Number.isFinite(this.audio.duration)?this.audio.duration:0}getSeekCurrentTime(){return this.options.audioMode==="external"?this.progress*(this._extDuration||0):this.audio&&Number.isFinite(this.audio.currentTime)?this.audio.currentTime:0}seekToSeconds(t){let i=this.getSeekDuration();if(!i)return;let s=m(t,0,i);if(this.options.audioMode==="external"){this._requestSeek(s/i),this.updateSeekAccessibility();return}this.seekTo(s)}applySeekLabel(t=this.options.title){if(!this.seekEl)return;let i=this.options.seekLabel||t||"Seek";this.seekEl.setAttribute("aria-label",i)}updateSeekAccessibility(){if(!this.seekEl)return;let t=this.getSeekDuration(),i=Math.min(this.getSeekCurrentTime(),t);this.seekEl.setAttribute("aria-valuemax",String(Math.round(t))),this.seekEl.setAttribute("aria-valuenow",String(Math.round(i))),this.seekEl.setAttribute("aria-valuetext",ot(this.options.seekValueText||"%1$s of %2$s",E(i),E(t)))}initMediaSession(){if(!("mediaSession"in navigator)||!this.options.enableMediaSession||!this.audio)return;this._applyMediaMetadata(),navigator.mediaSession.setActionHandler("play",()=>this.play()),navigator.mediaSession.setActionHandler("pause",()=>this.pause()),navigator.mediaSession.setActionHandler("seekbackward",()=>{this.seekTo(m(this.audio.currentTime-10,0,this.audio.duration))}),navigator.mediaSession.setActionHandler("seekforward",()=>{this.seekTo(m(this.audio.currentTime+10,0,this.audio.duration))}),navigator.mediaSession.setActionHandler("seekto",s=>{s.seekTime!==null&&this.seekTo(s.seekTime)});let t=this.options.onNextTrack,i=this.options.onPreviousTrack;try{navigator.mediaSession.setActionHandler("nexttrack",typeof t=="function"?()=>t(this):null)}catch{}try{navigator.mediaSession.setActionHandler("previoustrack",typeof i=="function"?()=>i(this):null)}catch{}}_applyMediaMetadata(){!("mediaSession"in navigator)||!this.options.enableMediaSession||(navigator.mediaSession.metadata=new MediaMetadata({title:this.options.title||this.options.unknownTrackText,artist:this.options.artist||"",album:this.options.album||"",artwork:this.options.artwork?[{src:this.options.artwork,sizes:"512x512",type:"image/jpeg"}]:[]}))}_updateMediaSession(t){if(!(!("mediaSession"in navigator)||!this.options.enableMediaSession||!this.audio))try{t==="playing"&&this.initMediaSession(),navigator.mediaSession.playbackState=t;let i=this.audio.duration;navigator.mediaSession.setPositionState&&i&&isFinite(i)&&navigator.mediaSession.setPositionState({duration:i,playbackRate:this.audio.playbackRate||1,position:m(this.audio.currentTime,0,i)})}catch{}}bindEvents(){this.playBtn&&this.playBtn.addEventListener("click",()=>this.togglePlay()),this.audio&&(this.audio.addEventListener("loadstart",()=>this.setLoading(!0)),this.audio.addEventListener("loadedmetadata",()=>this.onMetadataLoaded()),this.audio.addEventListener("canplay",()=>this.setLoading(!1)),this.audio.addEventListener("play",()=>this.onPlay()),this.audio.addEventListener("pause",()=>this.onPause()),this.audio.addEventListener("ended",()=>this.onEnded()),this.audio.addEventListener("error",i=>this.onError(i))),this.canvas.addEventListener("click",i=>this.handleCanvasClick(i)),this._dragging=!1,this._seekHover=!1,this._handleNear=!1,this.canvas.addEventListener("pointerenter",()=>{this._seekHover=!0,this.drawWaveform(),this._updateSeekHandle()}),this.canvas.addEventListener("pointerleave",()=>{this._seekHover=!1,this._handleNear=!1,this._dragging||this._hideHoverTip(),this.drawWaveform(),this._updateSeekHandle()}),this.canvas.addEventListener("pointerdown",i=>{if(!(i.pointerType==="mouse"&&i.button!==0)){this._dragging=!0;try{this.canvas.setPointerCapture(i.pointerId)}catch{}this._scrubTo(i.clientX)}}),this.canvas.addEventListener("pointermove",i=>{if(this._dragging){this._scrubTo(i.clientX);return}let s=this.canvas.getBoundingClientRect();s.width&&(this._handleNear=Math.abs(i.clientX-s.left-this.progress*s.width)<=10,this._updateSeekHandle())});let t=i=>{if(this._dragging){this._dragging=!1,this._suppressClick=!0;try{this.canvas.releasePointerCapture(i.pointerId)}catch{}this._seekFromPointer(i.clientX),!this._seekHover&&!this.options.showHoverTime&&this._hideHoverTip(),this._updateSeekHandle()}};this.canvas.addEventListener("pointerup",t),this.canvas.addEventListener("pointercancel",t),this.setupHoverTime(),this.setupSeekHandle(),this.resizeHandler=ht(()=>this.resizeCanvas(),100),window.addEventListener("resize",this.resizeHandler)}setupResizeObserver(){"ResizeObserver"in window&&(this.resizeObserver=new ResizeObserver(()=>{this.resizeCanvas()}),this.canvas?.parentElement&&this.resizeObserver.observe(this.canvas.parentElement))}async load(t){try{this.setLoading(!0),this.progress=0,this.hasError=!1,this.container.classList.remove("waveform-is-placeholder");let i=!!this.options.waveform;i&&this.setWaveformData(this.options.waveform);let s=this.options.title||I(t);if(this.titleEl&&(this.titleEl.textContent=s),this.applySeekLabel(s),this.audio&&(this.audio.src=t,this.audio.preload!=="none"&&await new Promise((r,a)=>{let o=()=>{this.audio.removeEventListener("loadedmetadata",o),this.audio.removeEventListener("error",n),r()},n=l=>{this.audio.removeEventListener("loadedmetadata",o),this.audio.removeEventListener("error",n),a(l)};this.audio.addEventListener("loadedmetadata",o),this.audio.addEventListener("error",n)})),!i)try{let r=await G(t,this.options.samples,this.options.showBPM);this.waveformData=r.peaks,r.bpm&&(this.detectedBPM=r.bpm,this.updateBPMDisplay())}catch(r){console.warn("[WaveformPlayer] Using placeholder waveform:",r),this.waveformData=yt(this.options.samples),this.container.classList.add("waveform-is-placeholder")}this.drawWaveform(),this.renderMarkers(),this.options.onLoad&&this.options.onLoad(this)}catch(i){this.onError(i)}finally{this.setLoading(!1)}}async loadTrack(t,i=null,s=null,r={}){let a=Object.prototype.hasOwnProperty.call(r,"artwork"),o=Object.prototype.hasOwnProperty.call(r,"artworkAlt"),n=Object.prototype.hasOwnProperty.call(r,"album"),l=Object.prototype.hasOwnProperty.call(r,"showAlbum");this.isPlaying&&this.pause(),this.audio&&(this.audio.src="",this.audio.load()),this.hasError=!1,this.errorEl&&(this.errorEl.style.display="none"),this.canvas&&(this.canvas.style.opacity="1"),this.playBtn&&(this.playBtn.disabled=!1),this.progress=0,this.waveformData=[],this.options=Z(j(this.options,{url:t,title:i===null?this.options.title:i,artist:s===null?this.options.artist:s,...r})),a&&(this.options.artwork=r.artwork||null),o?this.options.artworkAlt=r.artworkAlt||"":a&&(this.options.artworkAlt=this.options.artwork?W.artworkAlt:""),r.preload&&this.audio&&(this.audio.preload=this.options.preload),r.crossOrigin&&this.audio&&(this.audio.crossOrigin=this.options.crossOrigin),s!==null&&this.syncArtist(s),(n||l)&&this.syncAlbum(this.options.album),(a||o)&&this.syncArtwork(a?r.artwork:this.options.artwork,o?r.artworkAlt:this.options.artworkAlt),this.options.markers=r.markers?$(r.markers):[],this.options.waveform=r.waveform||null,await this.load(t),r.autoplay!==!1&&this.play()?.catch(()=>{})}setWaveformData(t){if(typeof t=="string"&&t.trim().endsWith(".json")){fetch(t.trim()).then(i=>i.json()).then(i=>{this.waveformData=Array.isArray(i)?i:i.peaks||[],i.markers&&!this.options.markers?.length&&(this.options.markers=$(i.markers),this.renderMarkers()),this.drawWaveform()}).catch(()=>{});return}this.waveformData=D(t,{fallback:[]}),this.drawWaveform()}drawWaveform(){!this.ctx||this.waveformData.length===0||ft(this.ctx,this.canvas,this.waveformData,this.progress,{...this.options,waveformStyle:this.options.waveformStyle||"bars",color:this.options.waveformColor,progressColor:this.options.progressColor,seekActive:this._seekHover||this._dragging})}resizeCanvas(){if(!this.canvas||this.isDestroying)return;let t=window.devicePixelRatio||1,i=this.canvas.parentElement.getBoundingClientRect();this.canvas.width=i.width*t,this.canvas.height=this.options.height*t,this.canvas.parentElement.style.height=this.options.height+"px",this.drawWaveform()}renderMarkers(){if(!this.markersContainer||(this.markersContainer.innerHTML="",this._activeMarkerIndex=-1,clearTimeout(this._markerLabelTimer),!this.options.showMarkers||!this.options.markers?.length))return;let t=this.getSeekDuration();t&&this.options.markers.forEach((i,s)=>{if(i.time>t){console.warn(`[WaveformPlayer] Marker "${i.label}" at ${i.time}s exceeds audio duration of ${t}s`);return}let r=i.time/t*100,a=document.createElement("button");a.className="waveform-marker",a.style.left=`${r}%`,a.style.backgroundColor=i.color||"rgba(255, 255, 255, 0.5)",a.setAttribute("aria-label",i.label),a.setAttribute("data-time",i.time);let o=document.createElement("span");o.className="waveform-marker-tooltip",o.textContent=i.label,a.appendChild(o),a.addEventListener("click",n=>{n.stopPropagation(),this.seekTo(i.time),this.options.playOnSeek&&!this.isPlaying&&this.play()}),this.markersContainer.appendChild(a)})}setActiveMarker(t){if(!this.markersContainer)return;this.markersContainer.querySelectorAll(".waveform-marker").forEach((s,r)=>s.classList.toggle("active",r===t))}updateActiveMarker(){if(!this.markersContainer)return;let t=this.markersContainer.querySelectorAll(".waveform-marker");if(!t.length)return;let i=this.getSeekDuration(),s=i?this.progress*i:0,r=-1,a=-1/0;t.forEach((o,n)=>{let l=parseFloat(o.getAttribute("data-time"));Number.isFinite(l)&&l<=s+.05&&l>a&&(a=l,r=n)}),r!==this._activeMarkerIndex&&(this._activeMarkerIndex=r,this.setActiveMarker(r),clearTimeout(this._markerLabelTimer),t.forEach((o,n)=>o.classList.toggle("show-label",n===r)),r>=0&&(this._markerLabelTimer=setTimeout(()=>{this.markersContainer?.querySelectorAll(".waveform-marker").forEach(o=>o.classList.remove("show-label"))},2500)))}setupHoverTime(){if(!this.seekEl)return;let t=document.createElement("div");t.className="waveform-hover-time",t.setAttribute("aria-hidden","true"),this.seekEl.appendChild(t),this.hoverTimeEl=t,this.options.showHoverTime&&(this.seekEl.addEventListener("pointermove",i=>{this._dragging||this._updateHoverTip(i.clientX)}),this.seekEl.addEventListener("pointerleave",()=>{this._dragging||this._hideHoverTip()}))}_updateHoverTip(t){let i=this.hoverTimeEl;if(!i)return;let s=this.getSeekDuration();if(!s){i.style.opacity="0";return}let r=this.canvas.getBoundingClientRect(),a=m((t-r.left)/r.width);i.textContent=E(a*s),i.style.left=a*100+"%",i.style.opacity="1"}_hideHoverTip(){this.hoverTimeEl&&(this.hoverTimeEl.style.opacity="0")}_scrubTo(t){let i=this.canvas.getBoundingClientRect();if(!i.width)return;this.progress=m((t-i.left)/i.width),this.drawWaveform(),this._updateSeekHandle();let s=this.getSeekDuration();s&&this.currentTimeEl?(this.currentTimeEl.textContent=E(this.progress*s),this._hideHoverTip()):this._updateHoverTip(t)}setupSeekHandle(){if(!this.options.seekHandle||this.options.waveformStyle!=="seekbar"||!this.seekEl)return;let t=document.createElement("div");t.className="waveform-seek-handle",t.setAttribute("aria-hidden","true"),this.seekEl.appendChild(t),this.seekHandleEl=t}_updateSeekHandle(){let t=this.seekHandleEl;t&&(t.style.left=this.progress*100+"%",t.classList.toggle("is-visible",this._seekHover||this._dragging),t.classList.toggle("is-active",this._dragging||this._handleNear))}handleCanvasClick(t){if(this._suppressClick){this._suppressClick=!1;return}this._seekFromPointer(t.clientX)}_seekFromPointer(t){let i=this.canvas.getBoundingClientRect();if(!i.width)return;let s=m((t-i.left)/i.width);if(this.options.audioMode==="external"){this._requestSeek(s);return}!this.audio||!this.audio.duration||this.seekToPercent(s)}setLoading(t){if(this.isLoading=t,this.loadingEl){let i=t&&this.waveformData.length===0;this.loadingEl.style.display=i?"block":"none"}this.seekEl&&this.seekEl.setAttribute("aria-busy",t?"true":"false")}onMetadataLoaded(){this.isDestroying||(this.totalTimeEl&&(this.totalTimeEl.textContent=E(this.audio.duration)),this.renderMarkers(),this.updateSeekAccessibility())}setPlayButtonState(t){if(!this.playBtn)return;this.playBtn.classList.toggle("playing",t);let i=this.playBtn.querySelector(".waveform-icon-play"),s=this.playBtn.querySelector(".waveform-icon-pause");i&&(i.style.display=t?"none":"flex"),s&&(s.style.display=t?"flex":"none")}onPlay(){this.isDestroying||(this.isPlaying=!0,this.setPlayButtonState(!0),this.startSmoothUpdate(),this._updateMediaSession("playing"),this._emit("waveformplayer:play",{player:this,url:this.options.url}),this.options.onPlay&&this.options.onPlay(this))}onPause(){this.isDestroying||(this.isPlaying=!1,this.setPlayButtonState(!1),this.stopSmoothUpdate(),this._updateMediaSession("paused"),this._emit("waveformplayer:pause",{player:this,url:this.options.url}),this.options.onPause&&this.options.onPause(this))}onEnded(){if(this.isDestroying)return;let t=this.audio.duration;this.progress=0,this.audio.currentTime=0,this.drawWaveform(),this.currentTimeEl&&(this.currentTimeEl.textContent="0:00"),this._emit("waveformplayer:ended",{player:this,url:this.options.url,currentTime:t,duration:t}),this.onPause(),this.options.onEnd&&this.options.onEnd(this)}onError(t){this.isDestroying||(console.error("[WaveformPlayer] Audio error:",t),this.hasError=!0,this.setLoading(!1),this.errorEl&&(this.errorEl.style.display="flex"),this.canvas&&(this.canvas.style.opacity="0.2"),this.playBtn&&(this.playBtn.disabled=!0),this.options.onError&&this.options.onError(t,this))}startSmoothUpdate(){this.stopSmoothUpdate();let t=()=>{this.isPlaying&&this.audio&&this.audio.duration&&(this.updateProgress(),this.updateTimer=requestAnimationFrame(t))};this.updateTimer=requestAnimationFrame(t)}stopSmoothUpdate(){this.updateTimer&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null)}updateProgress(){if(!this.audio||!this.audio.duration||this._dragging)return;let t=this.audio.currentTime/this.audio.duration;Math.abs(t-this.progress)>.001&&(this.progress=t,this.drawWaveform(),this._updateSeekHandle()),this.currentTimeEl&&(this.currentTimeEl.textContent=E(this.audio.currentTime)),this._emit("waveformplayer:timeupdate",{player:this,currentTime:this.audio.currentTime,duration:this.audio.duration,progress:this.progress,url:this.options.url}),this.options.onTimeUpdate&&this.options.onTimeUpdate(this.audio.currentTime,this.audio.duration,this),this.updateActiveMarker(),this.updateSeekAccessibility()}updateBPMDisplay(){let t=this.options.bpm||this.detectedBPM;this.bpmEl&&this.bpmValueEl&&t&&(this.bpmValueEl.textContent=Math.round(t),this.bpmEl.style.display="inline-flex")}refreshTheme(){if(!this._autoTheme)return;this._scheme=R(this.container);let t=J(this.options.colorPreset,this.container);for(let i of this._presetKeys||[])i in t&&(this.options[i]=t[i]);this._applyThemeColors()}_applyThemeColors(){this.container.classList.toggle("waveform-theme-light",this._scheme==="light"),this.canvas&&this.drawWaveform()}static _watchTheme(){if(e._themeWatch||typeof document>"u")return;let t=()=>requestAnimationFrame(()=>{e.instances.forEach(a=>{try{a.refreshTheme()}catch{}})}),i={attributes:!0,attributeFilter:["class","data-theme","data-color-scheme","style"]},s=new MutationObserver(t);s.observe(document.documentElement,i),document.body&&s.observe(document.body,i);let r=null;try{r=window.matchMedia("(prefers-color-scheme: dark)"),r.addEventListener("change",t)}catch{}e._themeWatch={obs:s,mq:r,refresh:t}}updateSpeedUI(){if(!this.audio)return;let t=this.container.querySelector(".speed-value");if(t){let i=this.audio.playbackRate;t.textContent=i===1?"1x":`${i}x`}this.container.querySelectorAll(".speed-option").forEach(i=>{let s=parseFloat(i.dataset.rate)===this.audio.playbackRate;i.classList.toggle("active",s),i.setAttribute("aria-checked",s?"true":"false")})}play(){if(this.options.singlePlay&&e.currentlyPlaying&&e.currentlyPlaying!==this&&e.currentlyPlaying.pause(),this.options.audioMode==="external"){this._emit("waveformplayer:request-play",this._buildTrackDetail(),!0).defaultPrevented||(e.currentlyPlaying=this);return}return e.currentlyPlaying=this,this.audio.play()}pause(){if(e.currentlyPlaying===this&&(e.currentlyPlaying=null),this.options.audioMode==="external"){this._emit("waveformplayer:request-pause",this._buildTrackDetail(),!0);return}this.audio.pause()}_buildTrackDetail(){return{url:this.options.url,title:this.options.title,artist:this.options.artist,album:this.options.album,artwork:this.options.artwork,markers:this.options.markers,waveform:this.options.waveform,id:this.id,player:this}}setPlayingState(t){let i=this.isPlaying;this.isPlaying=!!t,this.setPlayButtonState(this.isPlaying),this.isPlaying&&!i?(this.startSmoothUpdate?.(),this._emit("waveformplayer:play",{player:this,url:this.options.url}),this.options.onPlay&&this.options.onPlay(this)):!this.isPlaying&&i&&(this.stopSmoothUpdate?.(),this._emit("waveformplayer:pause",{player:this,url:this.options.url}),this.options.onPause&&this.options.onPause(this))}setProgress(t,i){!i||i<=0||(this.progress=m(t/i),this.currentTimeEl&&(this.currentTimeEl.textContent=E(t)),this._extDuration=i,this.totalTimeEl&&(!this.totalTimeEl.dataset._extSet||this.totalTimeEl.dataset._extDur!==String(i))&&(this.totalTimeEl.textContent=E(i),this.totalTimeEl.dataset._extSet="1",this.totalTimeEl.dataset._extDur=String(i)),this.drawWaveform?.(),this.updateActiveMarker(),this._emit("waveformplayer:timeupdate",{player:this,currentTime:t,duration:i,progress:this.progress,url:this.options.url}),this.options.onTimeUpdate&&this.options.onTimeUpdate(t,i,this),this.progress>=1?this._extEnded||(this._extEnded=!0,this._emit("waveformplayer:ended",{player:this,url:this.options.url,currentTime:i,duration:i}),this.options.onEnd&&this.options.onEnd(this)):this._extEnded=!1,this.updateSeekAccessibility())}togglePlay(){this.isPlaying?this.pause():this.play()}seekTo(t){this.audio&&this.audio.duration&&(this.audio.currentTime=m(t,0,this.audio.duration),this.updateProgress())}seekToPercent(t){this.audio&&this.audio.duration&&(this.audio.currentTime=this.audio.duration*m(t),this.updateProgress())}setVolume(t){let i=Number(t);this.audio&&Number.isFinite(i)&&(this.audio.volume=m(i))}setPlaybackRate(t){if(!this.audio)return;let i=_(t,null,{min:N,max:F});i!==null&&(this.audio.playbackRate=i,this.options.playbackRate=i,this.updateSpeedUI())}destroy(){this.isDestroying=!0,this._emit("waveformplayer:destroy",{player:this,url:this.options.url}),this.pause(),this.stopSmoothUpdate(),clearTimeout(this._markerLabelTimer),this._ac?.abort(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),e.instances.delete(this.id),e.currentlyPlaying===this&&(e.currentlyPlaying=null),this.audio&&(this.audio.pause(),this.audio.src="",this.audio.load(),this.audio=null),this.container.innerHTML="",delete this.container.dataset.waveformInitialized,this.canvas=null,this.ctx=null,this.playBtn=null,this.waveformData=[]}static getInstance(t){if(typeof t=="string"){let i=this.instances.get(t);if(i)return i;let s=document.getElementById(t);if(s)return Array.from(this.instances.values()).find(r=>r.container===s)}if(t instanceof HTMLElement)return Array.from(this.instances.values()).find(i=>i.container===t)}static getAllInstances(){return Array.from(this.instances.values())}static destroyAll(){this.instances.forEach(t=>t.destroy()),this.instances.clear()}static async generateWaveformData(t,i=1800){try{return(await G(t,i)).peaks}catch(s){throw console.error("[WaveformPlayer] Failed to generate waveform:",s),s}}static getPeaksUrl(t){if(!t)return;let i=t.replace(/\.(mp3|wav|ogg|flac|m4a|aac)(\?[^#]*)?(#.*)?$/i,".json$2$3");return i===t?void 0:i}};A.utils={formatTime:E,extractTitleFromUrl:I,escapeHtml:S,isSafeHref:st,parseDataAttributes:H,detectColorScheme:R};var et=()=>typeof window<"u"&&typeof document<"u",Nt=()=>document.documentElement?.dataset.waveformAutoinit==="false";function kt(e){if(!(e.dataset.waveformInitialized==="true"||A.getInstance(e)))try{new A(e),e.dataset.waveformInitialized="true"}catch(t){console.error("[WaveformPlayer] Failed to initialize:",t,e)}}function tt(e=document){if(!et())return;let t=e||document;t.matches?.("[data-waveform-player]")&&kt(t),t.querySelectorAll("[data-waveform-player]").forEach(kt)}et()&&!Nt()&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>tt()):tt());A.init=tt;et()&&(window.WaveformPlayer=A);var se=A;})();
diff --git a/index.d.ts b/index.d.ts
index f2b7fe8..10e7e65 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -189,6 +189,8 @@ export interface WaveformPlayerOptions {
artworkPosition?: 'info' | 'button';
/** Show the info (title/artist) block. @default true */
showInfo?: boolean;
+ /** Show album name under the artist in the info block (requires `showInfo` and `album`). @default false */
+ showAlbum?: boolean;
/** Show current/total time. @default true */
showTime?: boolean;
/** Show a time tooltip on hover. @default false */
@@ -291,6 +293,7 @@ export interface WaveformTrackDetail {
url: string;
title: string | null;
artist: string | null;
+ album: string;
artwork: string | null;
/** Chapter markers for the track (forwarded so controllers don't re-fetch). */
markers?: WaveformMarker[];
diff --git a/src/css/waveform-player.css b/src/css/waveform-player.css
index 27379cb..da59434 100644
--- a/src/css/waveform-player.css
+++ b/src/css/waveform-player.css
@@ -236,7 +236,8 @@
font-weight: 500;
}
-.waveform-artist {
+.waveform-artist,
+.waveform-album {
color: var(--wfp-text-secondary-color);
font-size: 11px;
white-space: nowrap;
@@ -560,6 +561,7 @@
}
.waveform-artist,
+ .waveform-album,
.waveform-time,
.waveform-bpm {
font-size: 10px;
@@ -575,4 +577,4 @@
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
-}
\ No newline at end of file
+}
diff --git a/src/js/core.js b/src/js/core.js
index 8888475..7892ae3 100644
--- a/src/js/core.js
+++ b/src/js/core.js
@@ -268,7 +268,7 @@ export class WaveformPlayer {
*
* Clears the container, resolves button alignment (`auto` → `bottom` for
* the `bars` style, `center` otherwise), and conditionally renders the play
- * button, info row (artwork/title/artist), BPM badge, playback-speed
+ * button, info row (artwork/title/artist/album), BPM badge, playback-speed
* menu, and time display based on the relevant `show*` options. Caches the
* canvas, controls, and text elements onto `this`, then sizes the canvas.
* @private
@@ -347,6 +347,7 @@ export class WaveformPlayer {
${this.options.artist ? `${escapeHtml(this.options.artist)} ` : ''}
+ ${this.options.showAlbum && this.options.album ? `${escapeHtml(this.options.album)} ` : ''}
${this.options.showBPM ? `
@@ -403,6 +404,7 @@ export class WaveformPlayer {
this.ctx = this.canvas.getContext('2d');
this.titleEl = this.container.querySelector('.waveform-title');
this.artistEl = this.container.querySelector('.waveform-artist');
+ this.albumEl = this.container.querySelector('.waveform-album');
// One reference for either placement — artworkPosition is structural
// (like showInfo/buttonStyle) and fixed for the player's lifetime, so
// the image only ever has one home.
@@ -489,6 +491,18 @@ export class WaveformPlayer {
return span;
}
+ /**
+ * Create an album text element matching the initial player markup.
+ *
+ * @returns {HTMLSpanElement} Album text element.
+ * @private
+ */
+ createAlbumElement() {
+ const span = document.createElement('span');
+ span.className = 'waveform-album';
+ return span;
+ }
+
/**
* Reconcile artist metadata and markup for the current track.
*
@@ -517,6 +531,38 @@ export class WaveformPlayer {
this.artistEl.style.display = '';
}
+ /**
+ * Reconcile album metadata and markup for the current track.
+ *
+ * @param {string|null} album - Album text, or a falsy value to remove it.
+ * @private
+ */
+ syncAlbum(album) {
+ this.options.album = album || '';
+
+ if (!this.options.showInfo || !this.options.showAlbum) {
+ this.albumEl?.remove();
+ this.albumEl = null;
+ return;
+ }
+
+ if (!album) {
+ this.albumEl?.remove();
+ this.albumEl = null;
+ return;
+ }
+
+ if (!this.albumEl) {
+ const anchorEl = this.artistEl || this.container.querySelector('.waveform-title');
+ if (!anchorEl) return;
+ this.albumEl = this.createAlbumElement();
+ anchorEl.after(this.albumEl);
+ }
+
+ this.albumEl.textContent = album;
+ this.albumEl.style.display = '';
+ }
+
/**
* Reconcile the play button's artwork image (`artworkPosition: 'button'`).
*
@@ -1289,7 +1335,7 @@ export class WaveformPlayer {
*
* Pauses any current playback, fully resets the audio element (self mode),
* clears error/marker/progress state, merges the new metadata into
- * `this.options`, updates the artist/artwork DOM, then calls
+ * `this.options`, updates the artist/album/artwork DOM, then calls
* {@link WaveformPlayer#load}. Auto-plays the new track unless
* `options.autoplay === false`.
* @param {string} url - Audio URL.
@@ -1304,6 +1350,8 @@ export class WaveformPlayer {
async loadTrack(url, title = null, artist = null, options = {}) {
const hasArtworkOption = Object.prototype.hasOwnProperty.call(options, 'artwork');
const hasArtworkAltOption = Object.prototype.hasOwnProperty.call(options, 'artworkAlt');
+ const hasAlbumOption = Object.prototype.hasOwnProperty.call(options, 'album');
+ const hasShowAlbumOption = Object.prototype.hasOwnProperty.call(options, 'showAlbum');
// Stop current playback and clear state
if (this.isPlaying) {
@@ -1368,6 +1416,13 @@ export class WaveformPlayer {
this.syncArtist(artist);
}
+ // Update album when explicitly provided in the options bag. Album is
+ // not part of the legacy positional signature, so null/undefined means
+ // "leave it alone" while an empty string removes the displayed line.
+ if (hasAlbumOption || hasShowAlbumOption) {
+ this.syncAlbum(this.options.album);
+ }
+
// Update artwork when explicitly provided. The caller can pass an empty
// value to remove existing artwork from the in-place player.
if (hasArtworkOption || hasArtworkAltOption) {
@@ -2202,13 +2257,14 @@ export class WaveformPlayer {
* directly: `WaveformBar.play(event.detail)`.
*
* @private
- * @return {{url:string,title:?string,artist:?string,artwork:?string,player:WaveformPlayer}}
+ * @return {{url:string,title:?string,artist:?string,album:string,artwork:?string,player:WaveformPlayer}}
*/
_buildTrackDetail() {
return {
url: this.options.url,
title: this.options.title,
artist: this.options.artist,
+ album: this.options.album,
artwork: this.options.artwork,
markers: this.options.markers,
waveform: this.options.waveform,
diff --git a/src/js/themes.js b/src/js/themes.js
index c02515a..2f47c13 100644
--- a/src/js/themes.js
+++ b/src/js/themes.js
@@ -313,6 +313,7 @@ export const DEFAULT_OPTIONS = {
autoplay: false,
showControls: true,
showInfo: true,
+ showAlbum: false,
showTime: true,
showHoverTime: false,
// Show a draggable circle handle + hover brightness-lift on the SEEKBAR
@@ -480,7 +481,7 @@ const NUMBERS = {
* @private
*/
const BOOLEANS = [
- 'autoplay', 'showControls', 'showInfo', 'showTime', 'showHoverTime',
+ 'autoplay', 'showControls', 'showInfo', 'showAlbum', 'showTime', 'showHoverTime',
'seekHandle', 'showBPM', 'singlePlay', 'playOnSeek', 'enableMediaSession',
'showMarkers', 'accessibleSeek', 'showPlaybackSpeed'
];
@@ -628,4 +629,4 @@ export function normalizeOptions(options) {
}
return options;
-}
\ No newline at end of file
+}
diff --git a/src/js/utils.js b/src/js/utils.js
index e84f92f..5cf889c 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -342,6 +342,7 @@ export function parseDataAttributes(element) {
setBool('autoplay');
setBool('showControls');
setBool('showInfo');
+ setBool('showAlbum');
setBool('showTime');
setBool('showHoverTime');
setBool('seekHandle');
@@ -661,4 +662,4 @@ export function resampleData(data, targetLength) {
}
return result;
-}
\ No newline at end of file
+}
diff --git a/test/player.test.js b/test/player.test.js
index aa193f4..0280de9 100644
--- a/test/player.test.js
+++ b/test/player.test.js
@@ -371,6 +371,41 @@ describe('lifecycle + external events', () => {
expect(player.options.artist).toBe('Artist');
});
+ it('shows album only when showAlbum is enabled', () => {
+ const hidden = track(mount({ album: 'Hidden LP' }));
+ expect(hidden.el.querySelector('.waveform-album')).toBe(null);
+
+ const shown = track(mount({ album: 'Visible LP', showAlbum: true }));
+ expect(shown.el.querySelector('.waveform-album').textContent).toBe('Visible LP');
+ });
+
+ it('loadTrack updates existing album text', async () => {
+ const { el, player } = track(mount({ album: 'First LP', showAlbum: true }));
+ const albumEl = el.querySelector('.waveform-album');
+
+ await player.loadTrack('next.mp3', 'Next', null, {
+ album: 'Second LP',
+ autoplay: false,
+ });
+
+ expect(el.querySelector('.waveform-album')).toBe(albumEl);
+ expect(albumEl.textContent).toBe('Second LP');
+ expect(player.options.album).toBe('Second LP');
+ });
+
+ it('loadTrack removes existing album when album is empty', async () => {
+ const { el, player } = track(mount({ album: 'Album', showAlbum: true }));
+ expect(el.querySelector('.waveform-album')).toBeTruthy();
+
+ await player.loadTrack('next.mp3', 'Next', null, {
+ album: '',
+ autoplay: false,
+ });
+
+ expect(el.querySelector('.waveform-album')).toBe(null);
+ expect(player.options.album).toBe('');
+ });
+
it('loadTrack removes existing artwork when artwork is empty', async () => {
const { el, player } = track(mount({
artwork: 'cover.jpg',
@@ -442,12 +477,13 @@ describe('core additions for controllers (v1.8.0)', () => {
expect(span.textContent).toContain('
{
- const { el, player } = track(mount({ artist: 'DJ Foo' }));
+ it('request-play detail carries the artist and album', () => {
+ const { el, player } = track(mount({ artist: 'DJ Foo', album: 'Club Vol. 1' }));
let detail = null;
el.addEventListener('waveformplayer:request-play', (e) => { detail = e.detail; });
player.play();
expect(detail.artist).toBe('DJ Foo');
+ expect(detail.album).toBe('Club Vol. 1');
});
});
@@ -680,6 +716,16 @@ describe('_build escapes author-supplied values', () => {
.toBe('
');
});
+ it('renders album as text, not markup', () => {
+ const { el } = track(mount({
+ album: '
',
+ showAlbum: true,
+ }));
+ expect(el.querySelector('.x')).toBe(null);
+ expect(el.querySelector('.waveform-album').textContent)
+ .toBe('
');
+ });
+
// An ampersand is the one thing artist genuinely has to carry ("David &
// John"). Escaping writes `&` into the markup, which the parser decodes
// straight back — so the reader sees a single `&`, never `&`. Guards
diff --git a/test/utils.test.js b/test/utils.test.js
index 5c95a9b..b1d8a47 100644
--- a/test/utils.test.js
+++ b/test/utils.test.js
@@ -179,6 +179,7 @@ describe('parseDataAttributes', () => {
Object.assign(el.dataset, {
url: 'a.mp3', audioMode: 'external', showMarkers: 'false',
accessibleSeek: 'false', seekLabel: 'Scrub', barRadius: '4',
+ showAlbum: 'true', album: 'LP',
});
const o = parseDataAttributes(el);
expect(o.url).toBe('a.mp3');
@@ -187,6 +188,8 @@ describe('parseDataAttributes', () => {
expect(o.accessibleSeek).toBe(false);
expect(o.seekLabel).toBe('Scrub');
expect(o.barRadius).toBe(4);
+ expect(o.showAlbum).toBe(true);
+ expect(o.album).toBe('LP');
});
it('reads data-artwork-position', () => {
diff --git a/test/validation.test.js b/test/validation.test.js
index 260303f..f7a048a 100644
--- a/test/validation.test.js
+++ b/test/validation.test.js
@@ -448,6 +448,7 @@ describe('valid configuration passes through untouched', () => {
autoplay: false,
showControls: true,
showInfo: true,
+ showAlbum: true,
showTime: false,
showHoverTime: true,
seekHandle: true,