@arraypress/waveform-player 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -228,6 +228,50 @@ Choose from 6 built-in styles:
228
228
  | `markers` | array | `[]` | Chapter markers array |
229
229
  | `waveform` | array | `null` | Pre-generated waveform data |
230
230
  | `enableMediaSession` | boolean | `true` | Enable system media controls |
231
+ | `audioMode` | string | `'self'` | `'self'` (own `<audio>`) or `'external'` (delegate) |
232
+
233
+ ### External audio mode
234
+
235
+ When `audioMode: 'external'` the player skips creating its own `<audio>` element and becomes a visualization-only surface. Play / pause / seek interactions dispatch cancelable events on the container, and an external controller (e.g. [`@arraypress/waveform-bar`](https://github.com/arraypress/waveform-bar) 1.3+) handles audio + pumps state back via `setPlayingState()` / `setProgress()`.
236
+
237
+ ```html
238
+ <!-- The bar discovers this on init, listens for request-play events,
239
+ and mirrors its own playback progress back into the canvas. -->
240
+ <div data-waveform-player
241
+ data-audio-mode="external"
242
+ data-url="song.mp3"
243
+ data-waveform-style="bars"
244
+ data-wb-play
245
+ data-wb-url="song.mp3"
246
+ data-wb-title="..."></div>
247
+ ```
248
+
249
+ ```js
250
+ // Or construct directly:
251
+ const player = new WaveformPlayer(el, { audioMode: 'external' });
252
+
253
+ // Listen for the play action and route to your own audio source:
254
+ el.addEventListener('waveformplayer:request-play', (e) => {
255
+ // e.detail = { url, title, subtitle, artist, artwork, id, player }
256
+ yourAudio.src = e.detail.url;
257
+ yourAudio.play();
258
+ });
259
+
260
+ // Drive the visualization from your audio's timeupdate:
261
+ yourAudio.addEventListener('timeupdate', () => {
262
+ player.setProgress(yourAudio.currentTime, yourAudio.duration);
263
+ });
264
+ yourAudio.addEventListener('play', () => player.setPlayingState(true));
265
+ yourAudio.addEventListener('pause', () => player.setPlayingState(false));
266
+ ```
267
+
268
+ Events dispatched in external mode (all bubble; all cancelable):
269
+
270
+ | Event | Detail |
271
+ |------------------------------------|-----------------------------------------------------|
272
+ | `waveformplayer:request-play` | `{ url, title, subtitle, artist, artwork, id, player }` |
273
+ | `waveformplayer:request-pause` | Same shape as request-play |
274
+ | `waveformplayer:request-seek` | Same shape + `{ percent: 0..1 }` |
231
275
 
232
276
  ## API Methods
233
277
 
@@ -247,6 +291,10 @@ player.setVolume(0.8); // 80% volume
247
291
  // Speed
248
292
  player.setPlaybackRate(1.5); // 1.5x speed
249
293
 
294
+ // External-mode state pumps (only useful when audioMode === 'external')
295
+ player.setPlayingState(true); // toggle play/pause UI state
296
+ player.setProgress(currentTime, duration); // sync canvas + time displays
297
+
250
298
  // Dynamic loading
251
299
  await player.loadTrack('new-song.mp3', 'New Title', 'New Artist', {
252
300
  markers: [{time: 30, label: 'Chorus'}],
@@ -1,4 +1,4 @@
1
- function A(t){let e={};if(t.dataset.url&&(e.url=t.dataset.url),t.dataset.height&&(e.height=parseInt(t.dataset.height)),t.dataset.samples&&(e.samples=parseInt(t.dataset.samples)),t.dataset.preload&&(e.preload=t.dataset.preload),t.dataset.waveformStyle&&(e.waveformStyle=t.dataset.waveformStyle),t.dataset.barWidth&&(e.barWidth=parseInt(t.dataset.barWidth)),t.dataset.barSpacing&&(e.barSpacing=parseInt(t.dataset.barSpacing)),t.dataset.buttonAlign&&(e.buttonAlign=t.dataset.buttonAlign),t.dataset.colorPreset&&(e.colorPreset=t.dataset.colorPreset),t.dataset.waveformColor&&(e.waveformColor=t.dataset.waveformColor),t.dataset.progressColor&&(e.progressColor=t.dataset.progressColor),t.dataset.buttonColor&&(e.buttonColor=t.dataset.buttonColor),t.dataset.buttonHoverColor&&(e.buttonHoverColor=t.dataset.buttonHoverColor),t.dataset.textColor&&(e.textColor=t.dataset.textColor),t.dataset.textSecondaryColor&&(e.textSecondaryColor=t.dataset.textSecondaryColor),t.dataset.backgroundColor&&(e.backgroundColor=t.dataset.backgroundColor),t.dataset.borderColor&&(e.borderColor=t.dataset.borderColor),t.dataset.color&&(e.waveformColor=t.dataset.color),t.dataset.theme&&(e.colorPreset=t.dataset.theme),t.dataset.autoplay&&(e.autoplay=t.dataset.autoplay==="true"),t.dataset.showControls!==void 0&&(e.showControls=t.dataset.showControls==="true"),t.dataset.showInfo!==void 0&&(e.showInfo=t.dataset.showInfo==="true"),t.dataset.showTime&&(e.showTime=t.dataset.showTime==="true"),t.dataset.showHoverTime&&(e.showHoverTime=t.dataset.showHoverTime==="true"),t.dataset.showBpm&&(e.showBPM=t.dataset.showBpm==="true"),t.dataset.singlePlay&&(e.singlePlay=t.dataset.singlePlay==="true"),t.dataset.playOnSeek&&(e.playOnSeek=t.dataset.playOnSeek==="true"),t.dataset.title&&(e.title=t.dataset.title),t.dataset.subtitle&&(e.subtitle=t.dataset.subtitle),t.dataset.album&&(e.album=t.dataset.album),t.dataset.artwork&&(e.artwork=t.dataset.artwork),t.dataset.waveform&&(e.waveform=t.dataset.waveform),t.dataset.markers)try{e.markers=JSON.parse(t.dataset.markers)}catch(i){console.warn("Invalid markers JSON:",i)}if(t.dataset.playbackRate&&(e.playbackRate=parseFloat(t.dataset.playbackRate)),t.dataset.showPlaybackSpeed!==void 0&&(e.showPlaybackSpeed=t.dataset.showPlaybackSpeed==="true"),t.dataset.playbackRates)try{e.playbackRates=JSON.parse(t.dataset.playbackRates)}catch(i){console.warn("Invalid playbackRates JSON:",i)}return t.dataset.enableMediaSession!==void 0&&(e.enableMediaSession=t.dataset.enableMediaSession==="true"),e}function C(t){if(!t||isNaN(t))return"0:00";let e=Math.floor(t/60),i=Math.floor(t%60);return`${e}:${i.toString().padStart(2,"0")}`}function R(t){let e=t||Math.random().toString();return btoa(e.substring(0,10)).replace(/[^a-zA-Z0-9]/g,"")}function B(t){if(!t)return"Audio";let e=t.split("/");return e[e.length-1].split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,s=>s.toUpperCase())}function P(...t){let e={};for(let i of t)for(let o in i)i[o]!==null&&i[o]!==void 0&&(e[o]=i[o]);return e}function I(t,e){let i;return function(...s){let n=()=>{clearTimeout(i),t(...s)};clearTimeout(i),i=setTimeout(n,e)}}function S(t,e){if(t.length===e)return t;if(t.length===0||e===0)return[];let i=[];if(e>t.length){let o=(t.length-1)/(e-1);for(let s=0;s<e;s++){let n=s*o,r=Math.floor(n),a=Math.ceil(n),h=n-r;if(a>=t.length)i.push(t[t.length-1]);else if(r===a)i.push(t[r]);else{let l=t[r]*(1-h)+t[a]*h;i.push(l)}}}else{let o=t.length/e;for(let s=0;s<e;s++){let n=Math.floor(s*o),r=Math.floor((s+1)*o),a=0,h=0;for(let l=n;l<=r&&l<t.length;l++)t[l]>a&&(a=t[l]),h++;if(h===0){let l=Math.min(Math.round(s*o),t.length-1);a=t[l]}i.push(a)}}return i}function M(t,e,i,o,s){let n=window.devicePixelRatio||1,r=s.barWidth*n,a=s.barSpacing*n,h=Math.floor(e.width/(r+a)),l=S(i,h),d=e.height,p=o*e.width;t.clearRect(0,0,e.width,e.height);for(let y=0;y<l.length;y++){let f=y*(r+a);if(f+r>e.width)break;let c=l[y]*d*.9,m=d-c;t.fillStyle=s.color,t.fillRect(f,m,r,c)}t.save(),t.beginPath(),t.rect(0,0,p,d),t.clip();for(let y=0;y<l.length;y++){let f=y*(r+a);if(f>p)break;let c=l[y]*d*.9,m=d-c;t.fillStyle=s.progressColor,t.fillRect(f,m,r,c)}t.restore()}function U(t,e,i,o,s){let n=window.devicePixelRatio||1,r=s.barWidth*n,a=s.barSpacing*n,h=Math.floor(e.width/(r+a)),l=S(i,h),d=e.height,p=d/2,y=o*e.width;t.clearRect(0,0,e.width,e.height);for(let f=0;f<l.length;f++){let c=f*(r+a);if(c+r>e.width)break;let m=l[f]*d*.45;t.fillStyle=s.color,t.fillRect(c,p-m,r,m),t.fillRect(c,p,r,m)}t.save(),t.beginPath(),t.rect(0,0,y,d),t.clip();for(let f=0;f<l.length;f++){let c=f*(r+a);if(c>y)break;let m=l[f]*d*.45;t.fillStyle=s.progressColor,t.fillRect(c,p-m,r,m),t.fillRect(c,p,r,m)}t.restore()}function F(t,e,i,o,s){let n=e.width,r=e.height,a=r/2,h=r*.35;t.clearRect(0,0,n,r);let l=(d,p,y=1,f=!1)=>{f&&(t.shadowBlur=12,t.shadowColor=d),t.strokeStyle=d,t.lineWidth=p,t.lineCap="round",t.lineJoin="round",t.beginPath(),t.moveTo(0,a);let c=[],m=Math.floor(i.length*y);for(let u=0;u<m;u++){let v=u/(i.length-1)*n,k=i[u],b=Math.sin(u*.1)*k,w=a+b*h;c.push({x:v,y:w})}for(let u=0;u<c.length-1;u++){let v=c[u].x+(c[u+1].x-c[u].x)*.5,k=c[u].y,b=c[u+1].x-(c[u+1].x-c[u].x)*.5,w=c[u+1].y;t.bezierCurveTo(v,k,b,w,c[u+1].x,c[u+1].y)}t.stroke(),f&&(t.shadowBlur=0)};t.strokeStyle="rgba(255, 255, 255, 0.03)",t.lineWidth=.5,t.beginPath(),t.moveTo(0,a),t.lineTo(n,a),t.stroke();for(let d=0;d<=10;d++){let p=n/10*d;t.beginPath(),t.moveTo(p,0),t.lineTo(p,r),t.stroke()}l(s.color,2,1,!1),o>0&&l(s.progressColor,3,o,!0)}function W(t,e,i,o,s){let n=window.devicePixelRatio||1,r=(s.barWidth||3)*n,a=(s.barSpacing||1)*n,h=Math.floor(e.width/(r+a)),l=S(i,h),d=e.height,p=4*n,y=2*n,f=o*e.width,c=d/2;t.clearRect(0,0,e.width,e.height);for(let m=0;m<l.length;m++){let u=m*(r+a);if(u+r>e.width)break;let v=l[m]*d*.9,k=Math.floor(v/(p+y));t.fillStyle=u<f?s.progressColor:s.color;for(let b=0;b<k;b++){let w=b*(p+y);t.fillRect(u,c-w-p,r,p),b>0&&t.fillRect(u,c+w,r,p)}}}function x(t,e,i,o,s){let n=window.devicePixelRatio||1,r=(s.barWidth||2)*n,a=(s.barSpacing||3)*n,h=Math.floor(e.width/(r+a)),l=S(i,h),d=e.height,p=Math.max(1.5*n,r/2),y=o*e.width,f=d/2;t.clearRect(0,0,e.width,e.height);for(let c=0;c<l.length;c++){let m=c*(r+a)+r/2;if(m>e.width)break;let u=l[c]*d*.9;t.fillStyle=m<y?s.progressColor:s.color,t.beginPath(),t.arc(m,f-u/2,p,0,Math.PI*2),t.fill(),t.beginPath(),t.arc(m,f+u/2,p,0,Math.PI*2),t.fill()}}function N(t,e,i,o,s){let n=e.width,r=e.height,a=r/2,h=4,l=h/2;if(t.clearRect(0,0,n,r),t.fillStyle=s.color||"rgba(255, 255, 255, 0.2)",t.beginPath(),t.moveTo(l,a-h/2),t.lineTo(n-l,a-h/2),t.arc(n-l,a,h/2,-Math.PI/2,Math.PI/2),t.lineTo(l,a+h/2),t.arc(l,a,h/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),o>0){let d=Math.max(l*2,o*n);t.shadowBlur=8,t.shadowColor=s.progressColor,t.fillStyle=s.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.moveTo(l,a-h/2),t.lineTo(d-l,a-h/2),t.arc(d-l,a,h/2,-Math.PI/2,Math.PI/2),t.lineTo(l,a+h/2),t.arc(l,a,h/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),t.shadowBlur=0;let p=8,y=d;t.shadowBlur=4,t.shadowColor="rgba(0, 0, 0, 0.3)",t.shadowOffsetY=2,t.fillStyle="#ffffff",t.beginPath(),t.arc(y,a,p,0,Math.PI*2),t.fill(),t.shadowBlur=0,t.shadowOffsetY=0,t.fillStyle=s.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.arc(y,a,p*.4,0,Math.PI*2),t.fill()}}var Y={bars:M,bar:M,mirror:U,line:F,blocks:W,block:W,dots:x,dot:x,seekbar:N};function D(t,e,i,o,s){(Y[s.waveformStyle]||M)(t,e,i,o,s)}function z(t){try{let e=t.getChannelData(0),i=t.sampleRate,o=V(e,i);if(o.length<2)return 120;let s=[];for(let h=1;h<o.length;h++)s.push((o[h]-o[h-1])/i);let n={};s.forEach(h=>{let l=60/h,d=Math.round(l/3)*3;d>60&&d<200&&(n[d]=(n[d]||0)+1)});let r=0,a=120;for(let[h,l]of Object.entries(n))l>r&&(r=l,a=parseInt(h));return a<70&&n[a*2]?a*=2:a>160&&n[Math.round(a/2)]&&(a=Math.round(a/2)),a-1}catch(e){return console.warn("BPM detection failed:",e),null}}function V(t,e){let s=[],n=0;for(let r=0;r<t.length-2048;r+=1024){let a=0;for(let d=r;d<r+2048;d++)a+=t[d]*t[d];a=a/2048;let h=a-n,l=n*1.8+.01;if(h>l&&a>.01){let d=s[s.length-1]||0,p=e*.15;r-d>p&&s.push(r)}n=a*.8+n*.2}return s}function j(t,e=200){let i=t.length/e,o=~~(i/10)||1,s=t.numberOfChannels,n=[];for(let a=0;a<s;a++){let h=t.getChannelData(a);for(let l=0;l<e;l++){let d=~~(l*i),p=~~(d+i),y=0,f=0;for(let m=d;m<p;m+=o){let u=h[m];u>f&&(f=u),u<y&&(y=u)}let c=Math.max(Math.abs(f),Math.abs(y));(a===0||c>n[l])&&(n[l]=c)}}let r=Math.max(...n);return r>0?n.map(a=>a/r):n}async function E(t,e=200,i=!1){try{let o=new(window.AudioContext||window.webkitAudioContext),n=await(await fetch(t)).arrayBuffer(),r=await o.decodeAudioData(n),a=j(r,e);a=J(a);let h=null;return i&&(h=await z(r)),o.close(),{peaks:a,bpm:h}}catch(o){throw console.error("Failed to generate waveform:",o),o}}function O(t=200){let e=[];for(let i=0;i<t;i++){let o=Math.random()*.5+.3,s=Math.sin(i/t*Math.PI*4)*.2;e.push(Math.max(.1,Math.min(1,o+s)))}return e}function J(t,e=.95){let i=Math.max(...t);if(i===0||i>e)return t;let o=e/i;return t.map(s=>s*o)}function G(){let t=document.documentElement,e=document.body;if(t.classList.contains("dark")||t.classList.contains("dark-mode")||t.classList.contains("theme-dark")||t.getAttribute("data-theme")==="dark"||t.getAttribute("data-color-scheme")==="dark"||e.classList.contains("dark")||e.classList.contains("dark-mode")||e.getAttribute("data-theme")==="dark")return"dark";if(t.classList.contains("light")||t.classList.contains("light-mode")||t.classList.contains("theme-light")||t.getAttribute("data-theme")==="light"||t.getAttribute("data-color-scheme")==="light"||e.classList.contains("light")||e.classList.contains("light-mode")||e.getAttribute("data-theme")==="light")return"light";try{let o=getComputedStyle(document.body).backgroundColor.match(/\d+/g);if(o&&o.length>=3){let[s,n,r]=o.map(Number),a=(s*299+n*587+r*114)/1e3;if(a>128)return"light";if(a<128)return"dark"}}catch{}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"}var T={dark:{waveformColor:"rgba(255, 255, 255, 0.3)",progressColor:"rgba(255, 255, 255, 0.9)",buttonColor:"rgba(255, 255, 255, 0.9)",buttonHoverColor:"rgba(255, 255, 255, 1)",textColor:"#ffffff",textSecondaryColor:"rgba(255, 255, 255, 0.6)",backgroundColor:"rgba(255, 255, 255, 0.03)",borderColor:"rgba(255, 255, 255, 0.1)"},light:{waveformColor:"rgba(0, 0, 0, 0.2)",progressColor:"rgba(0, 0, 0, 0.8)",buttonColor:"rgba(0, 0, 0, 0.8)",buttonHoverColor:"rgba(0, 0, 0, 0.9)",textColor:"#333333",textSecondaryColor:"rgba(0, 0, 0, 0.6)",backgroundColor:"rgba(0, 0, 0, 0.02)",borderColor:"rgba(0, 0, 0, 0.1)"}};function H(t){if(t&&T[t])return T[t];let e=G();return T[e]}var $={url:"",height:60,samples:200,preload:"metadata",playbackRate:1,showPlaybackSpeed:!1,playbackRates:[.5,.75,1,1.25,1.5,1.75,2],buttonAlign:"auto",waveformStyle:"mirror",barWidth:2,barSpacing:0,colorPreset:null,waveformColor:null,progressColor:null,buttonColor:null,buttonHoverColor:null,textColor:null,textSecondaryColor:null,backgroundColor:null,borderColor:null,autoplay:!1,showControls:!0,showInfo:!0,showTime:!0,showHoverTime:!1,showBPM:!1,singlePlay:!0,playOnSeek:!0,enableMediaSession:!0,markers:[],showMarkers:!0,title:null,subtitle:null,artwork:null,album:"",playIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M8 5v14l11-7z"/></svg>',pauseIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg>',onLoad:null,onPlay:null,onPause:null,onEnd:null,onError:null,onTimeUpdate:null},q={bars:{barWidth:3,barSpacing:1},mirror:{barWidth:2,barSpacing:0},line:{barWidth:2,barSpacing:0},blocks:{barWidth:4,barSpacing:2},dots:{barWidth:3,barSpacing:3},seekbar:{barWidth:1,barSpacing:0}};var g=class t{static instances=new Map;static currentlyPlaying=null;constructor(e,i={}){if(this.container=typeof e=="string"?document.querySelector(e):e,!this.container)throw new Error("WaveformPlayer: Container element not found");let o=A(this.container);this.options=P($,o,i);let s=H(this.options.colorPreset);for(let[r,a]of Object.entries(s))(this.options[r]===null||this.options[r]===void 0)&&(this.options[r]=a);let n=q[this.options.waveformStyle];n&&(o.barWidth===void 0&&i.barWidth===void 0&&(this.options.barWidth=n.barWidth),o.barSpacing===void 0&&i.barSpacing===void 0&&(this.options.barSpacing=n.barSpacing)),this.audio=null,this.canvas=null,this.ctx=null,this.waveformData=[],this.progress=0,this.isPlaying=!1,this.isLoading=!1,this.hasError=!1,this.updateTimer=null,this.resizeObserver=null,this.id=this.container.id||R(this.options.url),t.instances.set(this.id,this),this.init(),setTimeout(()=>{this.container.dispatchEvent(new CustomEvent("waveformplayer:ready",{bubbles:!0,detail:{player:this,url:this.options.url}}))},100)}init(){this.createDOM(),this.createAudio(),this.initPlaybackSpeed(),this.initKeyboardControls(),this.bindEvents(),this.setupResizeObserver(),requestAnimationFrame(()=>{this.resizeCanvas(),this.options.url&&this.load(this.options.url).then(()=>{this.options.autoplay&&this.play()}).catch(e=>{console.error("Failed to load audio:",e)})})}createDOM(){this.container.innerHTML="",this.container.className="waveform-player";let e=this.options.buttonAlign;e==="auto"&&(this.options.waveformStyle==="bars"?e="bottom":e="center");let i=this.options.showControls?`
1
+ function A(t){let e={};if(t.dataset.url&&(e.url=t.dataset.url),t.dataset.height&&(e.height=parseInt(t.dataset.height)),t.dataset.samples&&(e.samples=parseInt(t.dataset.samples)),t.dataset.preload&&(e.preload=t.dataset.preload),t.dataset.waveformStyle&&(e.waveformStyle=t.dataset.waveformStyle),t.dataset.barWidth&&(e.barWidth=parseInt(t.dataset.barWidth)),t.dataset.barSpacing&&(e.barSpacing=parseInt(t.dataset.barSpacing)),t.dataset.buttonAlign&&(e.buttonAlign=t.dataset.buttonAlign),t.dataset.colorPreset&&(e.colorPreset=t.dataset.colorPreset),t.dataset.waveformColor&&(e.waveformColor=t.dataset.waveformColor),t.dataset.progressColor&&(e.progressColor=t.dataset.progressColor),t.dataset.buttonColor&&(e.buttonColor=t.dataset.buttonColor),t.dataset.buttonHoverColor&&(e.buttonHoverColor=t.dataset.buttonHoverColor),t.dataset.textColor&&(e.textColor=t.dataset.textColor),t.dataset.textSecondaryColor&&(e.textSecondaryColor=t.dataset.textSecondaryColor),t.dataset.backgroundColor&&(e.backgroundColor=t.dataset.backgroundColor),t.dataset.borderColor&&(e.borderColor=t.dataset.borderColor),t.dataset.color&&(e.waveformColor=t.dataset.color),t.dataset.theme&&(e.colorPreset=t.dataset.theme),t.dataset.autoplay&&(e.autoplay=t.dataset.autoplay==="true"),t.dataset.showControls!==void 0&&(e.showControls=t.dataset.showControls==="true"),t.dataset.showInfo!==void 0&&(e.showInfo=t.dataset.showInfo==="true"),t.dataset.showTime&&(e.showTime=t.dataset.showTime==="true"),t.dataset.showHoverTime&&(e.showHoverTime=t.dataset.showHoverTime==="true"),t.dataset.showBpm&&(e.showBPM=t.dataset.showBpm==="true"),t.dataset.singlePlay&&(e.singlePlay=t.dataset.singlePlay==="true"),t.dataset.playOnSeek&&(e.playOnSeek=t.dataset.playOnSeek==="true"),t.dataset.title&&(e.title=t.dataset.title),t.dataset.subtitle&&(e.subtitle=t.dataset.subtitle),t.dataset.album&&(e.album=t.dataset.album),t.dataset.artwork&&(e.artwork=t.dataset.artwork),t.dataset.waveform&&(e.waveform=t.dataset.waveform),t.dataset.markers)try{e.markers=JSON.parse(t.dataset.markers)}catch(i){console.warn("Invalid markers JSON:",i)}if(t.dataset.playbackRate&&(e.playbackRate=parseFloat(t.dataset.playbackRate)),t.dataset.showPlaybackSpeed!==void 0&&(e.showPlaybackSpeed=t.dataset.showPlaybackSpeed==="true"),t.dataset.playbackRates)try{e.playbackRates=JSON.parse(t.dataset.playbackRates)}catch(i){console.warn("Invalid playbackRates JSON:",i)}return t.dataset.enableMediaSession!==void 0&&(e.enableMediaSession=t.dataset.enableMediaSession==="true"),e}function S(t){if(!t||isNaN(t))return"0:00";let e=Math.floor(t/60),i=Math.floor(t%60);return`${e}:${i.toString().padStart(2,"0")}`}function x(t){let e=t||Math.random().toString();return btoa(e.substring(0,10)).replace(/[^a-zA-Z0-9]/g,"")}function B(t){if(!t)return"Audio";let e=t.split("/");return e[e.length-1].split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,s=>s.toUpperCase())}function C(...t){let e={};for(let i of t)for(let o in i)i[o]!==null&&i[o]!==void 0&&(e[o]=i[o]);return e}function R(t,e){let i;return function(...s){let a=()=>{clearTimeout(i),t(...s)};clearTimeout(i),i=setTimeout(a,e)}}function P(t,e){if(t.length===e)return t;if(t.length===0||e===0)return[];let i=[];if(e>t.length){let o=(t.length-1)/(e-1);for(let s=0;s<e;s++){let a=s*o,n=Math.floor(a),r=Math.ceil(a),h=a-n;if(r>=t.length)i.push(t[t.length-1]);else if(n===r)i.push(t[n]);else{let l=t[n]*(1-h)+t[r]*h;i.push(l)}}}else{let o=t.length/e;for(let s=0;s<e;s++){let a=Math.floor(s*o),n=Math.floor((s+1)*o),r=0,h=0;for(let l=a;l<=n&&l<t.length;l++)t[l]>r&&(r=t[l]),h++;if(h===0){let l=Math.min(Math.round(s*o),t.length-1);r=t[l]}i.push(r)}}return i}function E(t,e,i,o,s){let a=window.devicePixelRatio||1,n=s.barWidth*a,r=s.barSpacing*a,h=Math.floor(e.width/(n+r)),l=P(i,h),d=e.height,p=o*e.width;t.clearRect(0,0,e.width,e.height);for(let m=0;m<l.length;m++){let f=m*(n+r);if(f+n>e.width)break;let c=l[m]*d*.9,y=d-c;t.fillStyle=s.color,t.fillRect(f,y,n,c)}t.save(),t.beginPath(),t.rect(0,0,p,d),t.clip();for(let m=0;m<l.length;m++){let f=m*(n+r);if(f>p)break;let c=l[m]*d*.9,y=d-c;t.fillStyle=s.progressColor,t.fillRect(f,y,n,c)}t.restore()}function U(t,e,i,o,s){let a=window.devicePixelRatio||1,n=s.barWidth*a,r=s.barSpacing*a,h=Math.floor(e.width/(n+r)),l=P(i,h),d=e.height,p=d/2,m=o*e.width;t.clearRect(0,0,e.width,e.height);for(let f=0;f<l.length;f++){let c=f*(n+r);if(c+n>e.width)break;let y=l[f]*d*.45;t.fillStyle=s.color,t.fillRect(c,p-y,n,y),t.fillRect(c,p,n,y)}t.save(),t.beginPath(),t.rect(0,0,m,d),t.clip();for(let f=0;f<l.length;f++){let c=f*(n+r);if(c>m)break;let y=l[f]*d*.45;t.fillStyle=s.progressColor,t.fillRect(c,p-y,n,y),t.fillRect(c,p,n,y)}t.restore()}function F(t,e,i,o,s){let a=e.width,n=e.height,r=n/2,h=n*.35;t.clearRect(0,0,a,n);let l=(d,p,m=1,f=!1)=>{f&&(t.shadowBlur=12,t.shadowColor=d),t.strokeStyle=d,t.lineWidth=p,t.lineCap="round",t.lineJoin="round",t.beginPath(),t.moveTo(0,r);let c=[],y=Math.floor(i.length*m);for(let u=0;u<y;u++){let v=u/(i.length-1)*a,k=i[u],b=Math.sin(u*.1)*k,w=r+b*h;c.push({x:v,y:w})}for(let u=0;u<c.length-1;u++){let v=c[u].x+(c[u+1].x-c[u].x)*.5,k=c[u].y,b=c[u+1].x-(c[u+1].x-c[u].x)*.5,w=c[u+1].y;t.bezierCurveTo(v,k,b,w,c[u+1].x,c[u+1].y)}t.stroke(),f&&(t.shadowBlur=0)};t.strokeStyle="rgba(255, 255, 255, 0.03)",t.lineWidth=.5,t.beginPath(),t.moveTo(0,r),t.lineTo(a,r),t.stroke();for(let d=0;d<=10;d++){let p=a/10*d;t.beginPath(),t.moveTo(p,0),t.lineTo(p,n),t.stroke()}l(s.color,2,1,!1),o>0&&l(s.progressColor,3,o,!0)}function I(t,e,i,o,s){let a=window.devicePixelRatio||1,n=(s.barWidth||3)*a,r=(s.barSpacing||1)*a,h=Math.floor(e.width/(n+r)),l=P(i,h),d=e.height,p=4*a,m=2*a,f=o*e.width,c=d/2;t.clearRect(0,0,e.width,e.height);for(let y=0;y<l.length;y++){let u=y*(n+r);if(u+n>e.width)break;let v=l[y]*d*.9,k=Math.floor(v/(p+m));t.fillStyle=u<f?s.progressColor:s.color;for(let b=0;b<k;b++){let w=b*(p+m);t.fillRect(u,c-w-p,n,p),b>0&&t.fillRect(u,c+w,n,p)}}}function D(t,e,i,o,s){let a=window.devicePixelRatio||1,n=(s.barWidth||2)*a,r=(s.barSpacing||3)*a,h=Math.floor(e.width/(n+r)),l=P(i,h),d=e.height,p=Math.max(1.5*a,n/2),m=o*e.width,f=d/2;t.clearRect(0,0,e.width,e.height);for(let c=0;c<l.length;c++){let y=c*(n+r)+n/2;if(y>e.width)break;let u=l[c]*d*.9;t.fillStyle=y<m?s.progressColor:s.color,t.beginPath(),t.arc(y,f-u/2,p,0,Math.PI*2),t.fill(),t.beginPath(),t.arc(y,f+u/2,p,0,Math.PI*2),t.fill()}}function N(t,e,i,o,s){let a=e.width,n=e.height,r=n/2,h=4,l=h/2;if(t.clearRect(0,0,a,n),t.fillStyle=s.color||"rgba(255, 255, 255, 0.2)",t.beginPath(),t.moveTo(l,r-h/2),t.lineTo(a-l,r-h/2),t.arc(a-l,r,h/2,-Math.PI/2,Math.PI/2),t.lineTo(l,r+h/2),t.arc(l,r,h/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),o>0){let d=Math.max(l*2,o*a);t.shadowBlur=8,t.shadowColor=s.progressColor,t.fillStyle=s.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.moveTo(l,r-h/2),t.lineTo(d-l,r-h/2),t.arc(d-l,r,h/2,-Math.PI/2,Math.PI/2),t.lineTo(l,r+h/2),t.arc(l,r,h/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),t.shadowBlur=0;let p=8,m=d;t.shadowBlur=4,t.shadowColor="rgba(0, 0, 0, 0.3)",t.shadowOffsetY=2,t.fillStyle="#ffffff",t.beginPath(),t.arc(m,r,p,0,Math.PI*2),t.fill(),t.shadowBlur=0,t.shadowOffsetY=0,t.fillStyle=s.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.arc(m,r,p*.4,0,Math.PI*2),t.fill()}}var Y={bars:E,bar:E,mirror:U,line:F,blocks:I,block:I,dots:D,dot:D,seekbar:N};function W(t,e,i,o,s){(Y[s.waveformStyle]||E)(t,e,i,o,s)}function z(t){try{let e=t.getChannelData(0),i=t.sampleRate,o=V(e,i);if(o.length<2)return 120;let s=[];for(let h=1;h<o.length;h++)s.push((o[h]-o[h-1])/i);let a={};s.forEach(h=>{let l=60/h,d=Math.round(l/3)*3;d>60&&d<200&&(a[d]=(a[d]||0)+1)});let n=0,r=120;for(let[h,l]of Object.entries(a))l>n&&(n=l,r=parseInt(h));return r<70&&a[r*2]?r*=2:r>160&&a[Math.round(r/2)]&&(r=Math.round(r/2)),r-1}catch(e){return console.warn("BPM detection failed:",e),null}}function V(t,e){let s=[],a=0;for(let n=0;n<t.length-2048;n+=1024){let r=0;for(let d=n;d<n+2048;d++)r+=t[d]*t[d];r=r/2048;let h=r-a,l=a*1.8+.01;if(h>l&&r>.01){let d=s[s.length-1]||0,p=e*.15;n-d>p&&s.push(n)}a=r*.8+a*.2}return s}function j(t,e=200){let i=t.length/e,o=~~(i/10)||1,s=t.numberOfChannels,a=[];for(let r=0;r<s;r++){let h=t.getChannelData(r);for(let l=0;l<e;l++){let d=~~(l*i),p=~~(d+i),m=0,f=0;for(let y=d;y<p;y+=o){let u=h[y];u>f&&(f=u),u<m&&(m=u)}let c=Math.max(Math.abs(f),Math.abs(m));(r===0||c>a[l])&&(a[l]=c)}}let n=Math.max(...a);return n>0?a.map(r=>r/n):a}async function M(t,e=200,i=!1){try{let o=new(window.AudioContext||window.webkitAudioContext),a=await(await fetch(t)).arrayBuffer(),n=await o.decodeAudioData(a),r=j(n,e);r=J(r);let h=null;return i&&(h=await z(n)),o.close(),{peaks:r,bpm:h}}catch(o){throw console.error("Failed to generate waveform:",o),o}}function O(t=200){let e=[];for(let i=0;i<t;i++){let o=Math.random()*.5+.3,s=Math.sin(i/t*Math.PI*4)*.2;e.push(Math.max(.1,Math.min(1,o+s)))}return e}function J(t,e=.95){let i=Math.max(...t);if(i===0||i>e)return t;let o=e/i;return t.map(s=>s*o)}function _(){let t=document.documentElement,e=document.body;if(t.classList.contains("dark")||t.classList.contains("dark-mode")||t.classList.contains("theme-dark")||t.getAttribute("data-theme")==="dark"||t.getAttribute("data-color-scheme")==="dark"||e.classList.contains("dark")||e.classList.contains("dark-mode")||e.getAttribute("data-theme")==="dark")return"dark";if(t.classList.contains("light")||t.classList.contains("light-mode")||t.classList.contains("theme-light")||t.getAttribute("data-theme")==="light"||t.getAttribute("data-color-scheme")==="light"||e.classList.contains("light")||e.classList.contains("light-mode")||e.getAttribute("data-theme")==="light")return"light";try{let o=getComputedStyle(document.body).backgroundColor.match(/\d+/g);if(o&&o.length>=3){let[s,a,n]=o.map(Number),r=(s*299+a*587+n*114)/1e3;if(r>128)return"light";if(r<128)return"dark"}}catch{}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"}var T={dark:{waveformColor:"rgba(255, 255, 255, 0.3)",progressColor:"rgba(255, 255, 255, 0.9)",buttonColor:"rgba(255, 255, 255, 0.9)",buttonHoverColor:"rgba(255, 255, 255, 1)",textColor:"#ffffff",textSecondaryColor:"rgba(255, 255, 255, 0.6)",backgroundColor:"rgba(255, 255, 255, 0.03)",borderColor:"rgba(255, 255, 255, 0.1)"},light:{waveformColor:"rgba(0, 0, 0, 0.2)",progressColor:"rgba(0, 0, 0, 0.8)",buttonColor:"rgba(0, 0, 0, 0.8)",buttonHoverColor:"rgba(0, 0, 0, 0.9)",textColor:"#333333",textSecondaryColor:"rgba(0, 0, 0, 0.6)",backgroundColor:"rgba(0, 0, 0, 0.02)",borderColor:"rgba(0, 0, 0, 0.1)"}};function H(t){if(t&&T[t])return T[t];let e=_();return T[e]}var q={url:"",height:60,samples:200,preload:"metadata",audioMode:"self",playbackRate:1,showPlaybackSpeed:!1,playbackRates:[.5,.75,1,1.25,1.5,1.75,2],buttonAlign:"auto",waveformStyle:"mirror",barWidth:2,barSpacing:0,colorPreset:null,waveformColor:null,progressColor:null,buttonColor:null,buttonHoverColor:null,textColor:null,textSecondaryColor:null,backgroundColor:null,borderColor:null,autoplay:!1,showControls:!0,showInfo:!0,showTime:!0,showHoverTime:!1,showBPM:!1,singlePlay:!0,playOnSeek:!0,enableMediaSession:!0,markers:[],showMarkers:!0,title:null,subtitle:null,artwork:null,album:"",playIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M8 5v14l11-7z"/></svg>',pauseIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg>',onLoad:null,onPlay:null,onPause:null,onEnd:null,onError:null,onTimeUpdate:null},$={bars:{barWidth:3,barSpacing:1},mirror:{barWidth:2,barSpacing:0},line:{barWidth:2,barSpacing:0},blocks:{barWidth:4,barSpacing:2},dots:{barWidth:3,barSpacing:3},seekbar:{barWidth:1,barSpacing:0}};var g=class t{static instances=new Map;static currentlyPlaying=null;constructor(e,i={}){if(this.container=typeof e=="string"?document.querySelector(e):e,!this.container)throw new Error("WaveformPlayer: Container element not found");let o=A(this.container);this.options=C(q,o,i);let s=H(this.options.colorPreset);for(let[n,r]of Object.entries(s))(this.options[n]===null||this.options[n]===void 0)&&(this.options[n]=r);let a=$[this.options.waveformStyle];a&&(o.barWidth===void 0&&i.barWidth===void 0&&(this.options.barWidth=a.barWidth),o.barSpacing===void 0&&i.barSpacing===void 0&&(this.options.barSpacing=a.barSpacing)),this.audio=null,this.canvas=null,this.ctx=null,this.waveformData=[],this.progress=0,this.isPlaying=!1,this.isLoading=!1,this.hasError=!1,this.updateTimer=null,this.resizeObserver=null,this.id=this.container.id||x(this.options.url),t.instances.set(this.id,this),this.init(),setTimeout(()=>{this.container.dispatchEvent(new CustomEvent("waveformplayer:ready",{bubbles:!0,detail:{player:this,url:this.options.url}}))},100)}init(){this.createDOM(),this.createAudio(),this.initPlaybackSpeed(),this.initKeyboardControls(),this.bindEvents(),this.setupResizeObserver(),requestAnimationFrame(()=>{this.resizeCanvas(),this.options.url&&this.load(this.options.url).then(()=>{this.options.autoplay&&this.play()}).catch(e=>{console.error("Failed to load audio:",e)})})}createDOM(){this.container.innerHTML="",this.container.className="waveform-player";let e=this.options.buttonAlign;e==="auto"&&(this.options.waveformStyle==="bars"?e="bottom":e="center");let i=this.options.showControls?`
2
2
  <button class="waveform-btn" aria-label="Play/Pause" style="
3
3
  border-color: ${this.options.buttonColor};
4
4
  color: ${this.options.buttonColor};
@@ -63,4 +63,4 @@ function A(t){let e={};if(t.dataset.url&&(e.url=t.dataset.url),t.dataset.height&
63
63
  ${o}
64
64
  </div>
65
65
  </div>
66
- `,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.subtitleEl=this.container.querySelector(".waveform-subtitle"),this.artworkEl=this.container.querySelector(".waveform-artwork"),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()}createAudio(){this.audio=new Audio,this.audio.preload=this.options.preload||"metadata",this.audio.crossOrigin="anonymous"}initPlaybackSpeed(){this.options.playbackRate&&this.options.playbackRate!==1&&(this.audio.playbackRate=this.options.playbackRate),this.options.showPlaybackSpeed&&this.initSpeedControls()}initSpeedControls(){let e=this.container.querySelector(".speed-btn"),i=this.container.querySelector(".speed-menu");!e||!i||(e.addEventListener("click",o=>{o.stopPropagation(),i.style.display=i.style.display==="none"?"block":"none"}),document.addEventListener("click",()=>{i.style.display="none"}),i.addEventListener("click",o=>{if(o.stopPropagation(),o.target.classList.contains("speed-option")){let s=parseFloat(o.target.dataset.rate);this.setPlaybackRate(s),i.style.display="none"}}),this.updateSpeedUI())}initKeyboardControls(){this.container.setAttribute("tabindex","-1"),this.container.addEventListener("click",()=>{t.getAllInstances().forEach(e=>{e!==this&&e.container.setAttribute("tabindex","-1")}),this.container.setAttribute("tabindex","0"),this.container.focus()}),this.container.addEventListener("keydown",e=>{if(document.activeElement!==this.container)return;let i=e.key,o=this.audio.currentTime;if(i>="0"&&i<="9"){e.preventDefault(),this.seekToPercent(parseInt(i)/10);return}let s={" ":()=>this.togglePlay(),ArrowLeft:()=>this.seekTo(Math.max(0,o-5)),ArrowRight:()=>this.seekTo(Math.min(this.audio.duration,o+5)),ArrowUp:()=>this.setVolume(Math.min(1,this.audio.volume+.1)),ArrowDown:()=>this.setVolume(Math.max(0,this.audio.volume-.1)),m:()=>this.audio.muted=!this.audio.muted,M:()=>this.audio.muted=!this.audio.muted};s[i]&&(e.preventDefault(),s[i]())})}initMediaSession(){!("mediaSession"in navigator)||!this.options.enableMediaSession||(navigator.mediaSession.metadata=new MediaMetadata({title:this.options.title||"Unknown Track",artist:this.options.subtitle||"",album:this.options.album||"",artwork:this.options.artwork?[{src:this.options.artwork,sizes:"512x512",type:"image/jpeg"}]:[]}),navigator.mediaSession.setActionHandler("play",()=>this.play()),navigator.mediaSession.setActionHandler("pause",()=>this.pause()),navigator.mediaSession.setActionHandler("seekbackward",()=>{this.seekTo(Math.max(0,this.audio.currentTime-10))}),navigator.mediaSession.setActionHandler("seekforward",()=>{this.seekTo(Math.min(this.audio.duration,this.audio.currentTime+10))}),navigator.mediaSession.setActionHandler("seekto",e=>{e.seekTime!==null&&this.seekTo(e.seekTime)}))}bindEvents(){this.playBtn&&this.playBtn.addEventListener("click",()=>this.togglePlay()),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",e=>this.onError(e)),this.canvas.addEventListener("click",e=>this.handleCanvasClick(e)),this.resizeHandler=I(()=>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(e){try{this.setLoading(!0),this.progress=0,this.hasError=!1,this.audio.src=e,await new Promise((o,s)=>{let n=()=>{this.audio.removeEventListener("loadedmetadata",n),this.audio.removeEventListener("error",r),o()},r=a=>{this.audio.removeEventListener("loadedmetadata",n),this.audio.removeEventListener("error",r),s(a)};this.audio.addEventListener("loadedmetadata",n),this.audio.addEventListener("error",r)});let i=this.options.title||B(e);if(this.titleEl&&(this.titleEl.textContent=i),this.options.waveform)this.setWaveformData(this.options.waveform);else try{let o=await E(e,this.options.samples,this.options.showBPM);this.waveformData=o.peaks,o.bpm&&(this.detectedBPM=o.bpm,this.updateBPMDisplay())}catch(o){console.warn("Using placeholder waveform:",o),this.waveformData=O(this.options.samples)}this.drawWaveform(),this.renderMarkers(),this.initMediaSession(),this.options.onLoad&&this.options.onLoad(this)}catch(i){console.error("Failed to load audio:",i),this.onError(i)}finally{this.setLoading(!1)}}async loadTrack(e,i=null,o=null,s={}){this.isPlaying&&this.pause(),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=P(this.options,{url:e,title:i||this.options.title,subtitle:o||this.options.subtitle,...s}),s.preload&&(this.audio.preload=s.preload),this.subtitleEl&&(o?(this.subtitleEl.textContent=o,this.subtitleEl.style.display=""):o===""&&(this.subtitleEl.style.display="none")),s.artwork&&this.artworkEl&&(this.artworkEl.src=s.artwork),this.options.markers=s.markers||[],await this.load(e),this.play().catch(()=>{})}setWaveformData(e){if(typeof e=="string"&&e.trim().endsWith(".json")){fetch(e.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}if(typeof e=="string")try{let i=JSON.parse(e);this.waveformData=Array.isArray(i)?i:[]}catch{this.waveformData=e.split(",").map(Number)}else this.waveformData=Array.isArray(e)?e:[];this.drawWaveform()}drawWaveform(){!this.ctx||this.waveformData.length===0||D(this.ctx,this.canvas,this.waveformData,this.progress,{...this.options,waveformStyle:this.options.waveformStyle||"bars",color:this.options.waveformColor,progressColor:this.options.progressColor})}resizeCanvas(){if(!this.canvas||this.isDestroying)return;let e=window.devicePixelRatio||1,i=this.canvas.parentElement.getBoundingClientRect();this.canvas.width=i.width*e,this.canvas.height=this.options.height*e,this.canvas.parentElement.style.height=this.options.height+"px",this.drawWaveform()}renderMarkers(){this.markersContainer&&(this.markersContainer.innerHTML="",!(!this.options.showMarkers||!this.options.markers?.length)&&(!this.audio||!this.audio.duration||this.audio.duration===0||this.options.markers.forEach((e,i)=>{if(e.time>this.audio.duration){console.warn(`Marker "${e.label}" at ${e.time}s exceeds audio duration of ${this.audio.duration}s`);return}let o=e.time/this.audio.duration*100,s=document.createElement("button");s.className="waveform-marker",s.style.left=`${o}%`,s.style.backgroundColor=e.color||"rgba(255, 255, 255, 0.5)",s.setAttribute("aria-label",e.label),s.setAttribute("data-time",e.time);let n=document.createElement("span");n.className="waveform-marker-tooltip",n.textContent=e.label,s.appendChild(n),s.addEventListener("click",r=>{r.stopPropagation(),this.seekTo(e.time),this.options.playOnSeek&&!this.isPlaying&&this.play()}),this.markersContainer.appendChild(s)})))}handleCanvasClick(e){if(!this.audio.duration)return;let i=this.canvas.getBoundingClientRect(),o=e.clientX-i.left,s=Math.max(0,Math.min(1,o/i.width));this.seekToPercent(s)}setLoading(e){this.isLoading=e,this.loadingEl&&(this.loadingEl.style.display=e?"block":"none")}onMetadataLoaded(){this.isDestroying||(this.totalTimeEl&&(this.totalTimeEl.textContent=C(this.audio.duration)),this.renderMarkers())}onPlay(){if(!this.isDestroying){if(this.isPlaying=!0,this.playBtn){this.playBtn.classList.add("playing");let e=this.playBtn.querySelector(".waveform-icon-play"),i=this.playBtn.querySelector(".waveform-icon-pause");e&&(e.style.display="none"),i&&(i.style.display="flex")}this.startSmoothUpdate(),this.container.dispatchEvent(new CustomEvent("waveformplayer:play",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPlay&&this.options.onPlay(this)}}onPause(){if(!this.isDestroying){if(this.isPlaying=!1,this.playBtn){this.playBtn.classList.remove("playing");let e=this.playBtn.querySelector(".waveform-icon-play"),i=this.playBtn.querySelector(".waveform-icon-pause");e&&(e.style.display="flex"),i&&(i.style.display="none")}this.stopSmoothUpdate(),this.container.dispatchEvent(new CustomEvent("waveformplayer:pause",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPause&&this.options.onPause(this)}}onEnded(){this.isDestroying||(this.progress=0,this.audio.currentTime=0,this.drawWaveform(),this.currentTimeEl&&(this.currentTimeEl.textContent="0:00"),this.container.dispatchEvent(new CustomEvent("waveformplayer:ended",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.onPause(),this.options.onEnd&&this.options.onEnd(this))}onError(e){this.isDestroying||(console.error("Audio error:",e),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(e,this))}startSmoothUpdate(){this.stopSmoothUpdate();let e=()=>{this.isPlaying&&this.audio.duration&&(this.updateProgress(),this.updateTimer=requestAnimationFrame(e))};this.updateTimer=requestAnimationFrame(e)}stopSmoothUpdate(){this.updateTimer&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null)}updateProgress(){if(!this.audio.duration)return;let e=this.audio.currentTime/this.audio.duration;Math.abs(e-this.progress)>.001&&(this.progress=e,this.drawWaveform()),this.currentTimeEl&&(this.currentTimeEl.textContent=C(this.audio.currentTime)),this.container.dispatchEvent(new CustomEvent("waveformplayer:timeupdate",{bubbles:!0,detail:{player:this,currentTime:this.audio.currentTime,duration:this.audio.duration,url:this.options.url}})),this.options.onTimeUpdate&&this.options.onTimeUpdate(this.audio.currentTime,this.audio.duration,this)}updateBPMDisplay(){this.bpmEl&&this.bpmValueEl&&this.detectedBPM&&(this.bpmValueEl.textContent=Math.round(this.detectedBPM),this.bpmEl.style.display="inline-flex")}updateSpeedUI(){let e=this.container.querySelector(".speed-value");if(e){let i=this.audio.playbackRate;e.textContent=i===1?"1x":`${i}x`}this.container.querySelectorAll(".speed-option").forEach(i=>{i.classList.toggle("active",parseFloat(i.dataset.rate)===this.audio.playbackRate)})}play(){return this.options.singlePlay&&t.currentlyPlaying&&t.currentlyPlaying!==this&&t.currentlyPlaying.pause(),t.currentlyPlaying=this,this.audio.play()}pause(){t.currentlyPlaying===this&&(t.currentlyPlaying=null),this.audio.pause()}togglePlay(){this.isPlaying?this.pause():this.play()}seekTo(e){this.audio&&this.audio.duration&&(this.audio.currentTime=Math.max(0,Math.min(e,this.audio.duration)),this.updateProgress())}seekToPercent(e){this.audio&&this.audio.duration&&(this.audio.currentTime=this.audio.duration*Math.max(0,Math.min(1,e)),this.updateProgress())}setVolume(e){this.audio&&(this.audio.volume=Math.max(0,Math.min(1,e)))}setPlaybackRate(e){if(!this.audio)return;let i=Math.max(.5,Math.min(2,e));this.audio.playbackRate=i,this.options.playbackRate=i,this.updateSpeedUI()}destroy(){this.isDestroying=!0,this.pause(),this.stopSmoothUpdate(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),t.instances.delete(this.id),t.currentlyPlaying===this&&(t.currentlyPlaying=null),this.audio&&(this.audio.pause(),this.audio.src="",this.audio.load(),this.audio=null),this.container.innerHTML="",this.canvas=null,this.ctx=null,this.playBtn=null,this.waveformData=[]}static getInstance(e){if(typeof e=="string"){let i=this.instances.get(e);if(i)return i;let o=document.getElementById(e);if(o)return Array.from(this.instances.values()).find(s=>s.container===o)}if(e instanceof HTMLElement)return Array.from(this.instances.values()).find(i=>i.container===e)}static getAllInstances(){return Array.from(this.instances.values())}static destroyAll(){this.instances.forEach(e=>e.destroy()),this.instances.clear()}static async generateWaveformData(e,i=200){try{return(await E(e,i)).peaks}catch(o){throw console.error("Failed to generate waveform:",o),o}}};function L(){if(typeof document>"u")return;document.querySelectorAll("[data-waveform-player]").forEach(e=>{if(e.dataset.waveformInitialized!=="true")try{new g(e),e.dataset.waveformInitialized="true"}catch(i){console.error("Failed to initialize WaveformPlayer:",i,e)}})}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",L):L());g.init=L;typeof window<"u"&&(window.WaveformPlayer=g);var lt=g;export{g as WaveformPlayer,lt as default};
66
+ `,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.subtitleEl=this.container.querySelector(".waveform-subtitle"),this.artworkEl=this.container.querySelector(".waveform-artwork"),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()}createAudio(){if(this.options.audioMode==="external"){this.audio=null;return}this.audio=new Audio,this.audio.preload=this.options.preload||"metadata",this.audio.crossOrigin="anonymous"}initPlaybackSpeed(){this.audio&&this.options.playbackRate&&this.options.playbackRate!==1&&(this.audio.playbackRate=this.options.playbackRate),this.options.showPlaybackSpeed&&this.initSpeedControls()}initSpeedControls(){let e=this.container.querySelector(".speed-btn"),i=this.container.querySelector(".speed-menu");!e||!i||(e.addEventListener("click",o=>{o.stopPropagation(),i.style.display=i.style.display==="none"?"block":"none"}),document.addEventListener("click",()=>{i.style.display="none"}),i.addEventListener("click",o=>{if(o.stopPropagation(),o.target.classList.contains("speed-option")){let s=parseFloat(o.target.dataset.rate);this.setPlaybackRate(s),i.style.display="none"}}),this.updateSpeedUI())}initKeyboardControls(){this.container.setAttribute("tabindex","-1"),this.container.addEventListener("click",()=>{t.getAllInstances().forEach(e=>{e!==this&&e.container.setAttribute("tabindex","-1")}),this.container.setAttribute("tabindex","0"),this.container.focus()}),this.container.addEventListener("keydown",e=>{if(document.activeElement!==this.container)return;let i=e.key,o=!!this.audio,s=o?this.audio.currentTime:0;if(o&&i>="0"&&i<="9"){e.preventDefault(),this.seekToPercent(parseInt(i)/10);return}let a={" ":()=>this.togglePlay()};o&&(a.ArrowLeft=()=>this.seekTo(Math.max(0,s-5)),a.ArrowRight=()=>this.seekTo(Math.min(this.audio.duration,s+5)),a.ArrowUp=()=>this.setVolume(Math.min(1,this.audio.volume+.1)),a.ArrowDown=()=>this.setVolume(Math.max(0,this.audio.volume-.1)),a.m=a.M=()=>this.audio.muted=!this.audio.muted),a[i]&&(e.preventDefault(),a[i]())})}initMediaSession(){!("mediaSession"in navigator)||!this.options.enableMediaSession||this.audio&&(navigator.mediaSession.metadata=new MediaMetadata({title:this.options.title||"Unknown Track",artist:this.options.subtitle||"",album:this.options.album||"",artwork:this.options.artwork?[{src:this.options.artwork,sizes:"512x512",type:"image/jpeg"}]:[]}),navigator.mediaSession.setActionHandler("play",()=>this.play()),navigator.mediaSession.setActionHandler("pause",()=>this.pause()),navigator.mediaSession.setActionHandler("seekbackward",()=>{this.seekTo(Math.max(0,this.audio.currentTime-10))}),navigator.mediaSession.setActionHandler("seekforward",()=>{this.seekTo(Math.min(this.audio.duration,this.audio.currentTime+10))}),navigator.mediaSession.setActionHandler("seekto",e=>{e.seekTime!==null&&this.seekTo(e.seekTime)}))}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",e=>this.onError(e))),this.canvas.addEventListener("click",e=>this.handleCanvasClick(e)),this.resizeHandler=R(()=>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(e){try{this.setLoading(!0),this.progress=0,this.hasError=!1,this.audio&&(this.audio.src=e,await new Promise((o,s)=>{let a=()=>{this.audio.removeEventListener("loadedmetadata",a),this.audio.removeEventListener("error",n),o()},n=r=>{this.audio.removeEventListener("loadedmetadata",a),this.audio.removeEventListener("error",n),s(r)};this.audio.addEventListener("loadedmetadata",a),this.audio.addEventListener("error",n)}));let i=this.options.title||B(e);if(this.titleEl&&(this.titleEl.textContent=i),this.options.waveform)this.setWaveformData(this.options.waveform);else try{let o=await M(e,this.options.samples,this.options.showBPM);this.waveformData=o.peaks,o.bpm&&(this.detectedBPM=o.bpm,this.updateBPMDisplay())}catch(o){console.warn("Using placeholder waveform:",o),this.waveformData=O(this.options.samples)}this.drawWaveform(),this.renderMarkers(),this.initMediaSession(),this.options.onLoad&&this.options.onLoad(this)}catch(i){console.error("Failed to load audio:",i),this.onError(i)}finally{this.setLoading(!1)}}async loadTrack(e,i=null,o=null,s={}){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=C(this.options,{url:e,title:i||this.options.title,subtitle:o||this.options.subtitle,...s}),s.preload&&this.audio&&(this.audio.preload=s.preload),this.subtitleEl&&(o?(this.subtitleEl.textContent=o,this.subtitleEl.style.display=""):o===""&&(this.subtitleEl.style.display="none")),s.artwork&&this.artworkEl&&(this.artworkEl.src=s.artwork),this.options.markers=s.markers||[],await this.load(e),this.play().catch(()=>{})}setWaveformData(e){if(typeof e=="string"&&e.trim().endsWith(".json")){fetch(e.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}if(typeof e=="string")try{let i=JSON.parse(e);this.waveformData=Array.isArray(i)?i:[]}catch{this.waveformData=e.split(",").map(Number)}else this.waveformData=Array.isArray(e)?e:[];this.drawWaveform()}drawWaveform(){!this.ctx||this.waveformData.length===0||W(this.ctx,this.canvas,this.waveformData,this.progress,{...this.options,waveformStyle:this.options.waveformStyle||"bars",color:this.options.waveformColor,progressColor:this.options.progressColor})}resizeCanvas(){if(!this.canvas||this.isDestroying)return;let e=window.devicePixelRatio||1,i=this.canvas.parentElement.getBoundingClientRect();this.canvas.width=i.width*e,this.canvas.height=this.options.height*e,this.canvas.parentElement.style.height=this.options.height+"px",this.drawWaveform()}renderMarkers(){this.markersContainer&&(this.markersContainer.innerHTML="",!(!this.options.showMarkers||!this.options.markers?.length)&&(!this.audio||!this.audio.duration||this.audio.duration===0||this.options.markers.forEach((e,i)=>{if(e.time>this.audio.duration){console.warn(`Marker "${e.label}" at ${e.time}s exceeds audio duration of ${this.audio.duration}s`);return}let o=e.time/this.audio.duration*100,s=document.createElement("button");s.className="waveform-marker",s.style.left=`${o}%`,s.style.backgroundColor=e.color||"rgba(255, 255, 255, 0.5)",s.setAttribute("aria-label",e.label),s.setAttribute("data-time",e.time);let a=document.createElement("span");a.className="waveform-marker-tooltip",a.textContent=e.label,s.appendChild(a),s.addEventListener("click",n=>{n.stopPropagation(),this.seekTo(e.time),this.options.playOnSeek&&!this.isPlaying&&this.play()}),this.markersContainer.appendChild(s)})))}handleCanvasClick(e){let i=this.canvas.getBoundingClientRect(),o=e.clientX-i.left,s=Math.max(0,Math.min(1,o/i.width));if(this.options.audioMode==="external"){let a=new CustomEvent("waveformplayer:request-seek",{bubbles:!0,cancelable:!0,detail:{...this._buildTrackDetail(),percent:s}});this.container.dispatchEvent(a),a.defaultPrevented||(this.progress=s,this.drawWaveform?.());return}!this.audio||!this.audio.duration||this.seekToPercent(s)}setLoading(e){this.isLoading=e,this.loadingEl&&(this.loadingEl.style.display=e?"block":"none")}onMetadataLoaded(){this.isDestroying||(this.totalTimeEl&&(this.totalTimeEl.textContent=S(this.audio.duration)),this.renderMarkers())}onPlay(){if(!this.isDestroying){if(this.isPlaying=!0,this.playBtn){this.playBtn.classList.add("playing");let e=this.playBtn.querySelector(".waveform-icon-play"),i=this.playBtn.querySelector(".waveform-icon-pause");e&&(e.style.display="none"),i&&(i.style.display="flex")}this.startSmoothUpdate(),this.container.dispatchEvent(new CustomEvent("waveformplayer:play",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPlay&&this.options.onPlay(this)}}onPause(){if(!this.isDestroying){if(this.isPlaying=!1,this.playBtn){this.playBtn.classList.remove("playing");let e=this.playBtn.querySelector(".waveform-icon-play"),i=this.playBtn.querySelector(".waveform-icon-pause");e&&(e.style.display="flex"),i&&(i.style.display="none")}this.stopSmoothUpdate(),this.container.dispatchEvent(new CustomEvent("waveformplayer:pause",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPause&&this.options.onPause(this)}}onEnded(){this.isDestroying||(this.progress=0,this.audio.currentTime=0,this.drawWaveform(),this.currentTimeEl&&(this.currentTimeEl.textContent="0:00"),this.container.dispatchEvent(new CustomEvent("waveformplayer:ended",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.onPause(),this.options.onEnd&&this.options.onEnd(this))}onError(e){this.isDestroying||(console.error("Audio error:",e),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(e,this))}startSmoothUpdate(){this.stopSmoothUpdate();let e=()=>{this.isPlaying&&this.audio&&this.audio.duration&&(this.updateProgress(),this.updateTimer=requestAnimationFrame(e))};this.updateTimer=requestAnimationFrame(e)}stopSmoothUpdate(){this.updateTimer&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null)}updateProgress(){if(!this.audio||!this.audio.duration)return;let e=this.audio.currentTime/this.audio.duration;Math.abs(e-this.progress)>.001&&(this.progress=e,this.drawWaveform()),this.currentTimeEl&&(this.currentTimeEl.textContent=S(this.audio.currentTime)),this.container.dispatchEvent(new CustomEvent("waveformplayer:timeupdate",{bubbles:!0,detail:{player:this,currentTime:this.audio.currentTime,duration:this.audio.duration,url:this.options.url}})),this.options.onTimeUpdate&&this.options.onTimeUpdate(this.audio.currentTime,this.audio.duration,this)}updateBPMDisplay(){this.bpmEl&&this.bpmValueEl&&this.detectedBPM&&(this.bpmValueEl.textContent=Math.round(this.detectedBPM),this.bpmEl.style.display="inline-flex")}updateSpeedUI(){let e=this.container.querySelector(".speed-value");if(e){let i=this.audio.playbackRate;e.textContent=i===1?"1x":`${i}x`}this.container.querySelectorAll(".speed-option").forEach(i=>{i.classList.toggle("active",parseFloat(i.dataset.rate)===this.audio.playbackRate)})}play(){if(this.options.singlePlay&&t.currentlyPlaying&&t.currentlyPlaying!==this&&t.currentlyPlaying.pause(),this.options.audioMode==="external"){let e=new CustomEvent("waveformplayer:request-play",{bubbles:!0,cancelable:!0,detail:this._buildTrackDetail()});this.container.dispatchEvent(e),e.defaultPrevented||(t.currentlyPlaying=this);return}return t.currentlyPlaying=this,this.audio.play()}pause(){if(t.currentlyPlaying===this&&(t.currentlyPlaying=null),this.options.audioMode==="external"){this.container.dispatchEvent(new CustomEvent("waveformplayer:request-pause",{bubbles:!0,cancelable:!0,detail:this._buildTrackDetail()}));return}this.audio.pause()}_buildTrackDetail(){return{url:this.options.url,title:this.options.title,subtitle:this.options.subtitle,artist:this.options.artist,artwork:this.options.artwork,id:this.id,player:this}}setPlayingState(e){let i=this.isPlaying;if(this.isPlaying=!!e,this.playBtn){this.playBtn.classList.toggle("playing",this.isPlaying);let o=this.playBtn.querySelector(".waveform-icon-play"),s=this.playBtn.querySelector(".waveform-icon-pause");o&&(o.style.display=this.isPlaying?"none":"flex"),s&&(s.style.display=this.isPlaying?"flex":"none")}this.isPlaying&&!i?(this.startSmoothUpdate?.(),this.container.dispatchEvent(new CustomEvent("waveformplayer:play",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPlay&&this.options.onPlay(this)):!this.isPlaying&&i&&(this.stopSmoothUpdate?.(),this.container.dispatchEvent(new CustomEvent("waveformplayer:pause",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPause&&this.options.onPause(this))}setProgress(e,i){!i||i<=0||(this.progress=Math.max(0,Math.min(1,e/i)),this.currentTimeEl&&(this.currentTimeEl.textContent=S(e)),this.totalTimeEl&&(!this.totalTimeEl.dataset._extSet||this._extDuration!==i)&&(this.totalTimeEl.textContent=S(i),this.totalTimeEl.dataset._extSet="1",this._extDuration=i),this.drawWaveform?.(),this.container.dispatchEvent(new CustomEvent("waveformplayer:timeupdate",{bubbles:!0,detail:{player:this,currentTime:e,duration:i,progress:this.progress}})),this.options.onTimeUpdate&&this.options.onTimeUpdate(this,e,i))}togglePlay(){this.isPlaying?this.pause():this.play()}seekTo(e){this.audio&&this.audio.duration&&(this.audio.currentTime=Math.max(0,Math.min(e,this.audio.duration)),this.updateProgress())}seekToPercent(e){this.audio&&this.audio.duration&&(this.audio.currentTime=this.audio.duration*Math.max(0,Math.min(1,e)),this.updateProgress())}setVolume(e){this.audio&&(this.audio.volume=Math.max(0,Math.min(1,e)))}setPlaybackRate(e){if(!this.audio)return;let i=Math.max(.5,Math.min(2,e));this.audio.playbackRate=i,this.options.playbackRate=i,this.updateSpeedUI()}destroy(){this.isDestroying=!0,this.pause(),this.stopSmoothUpdate(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),t.instances.delete(this.id),t.currentlyPlaying===this&&(t.currentlyPlaying=null),this.audio&&(this.audio.pause(),this.audio.src="",this.audio.load(),this.audio=null),this.container.innerHTML="",this.canvas=null,this.ctx=null,this.playBtn=null,this.waveformData=[]}static getInstance(e){if(typeof e=="string"){let i=this.instances.get(e);if(i)return i;let o=document.getElementById(e);if(o)return Array.from(this.instances.values()).find(s=>s.container===o)}if(e instanceof HTMLElement)return Array.from(this.instances.values()).find(i=>i.container===e)}static getAllInstances(){return Array.from(this.instances.values())}static destroyAll(){this.instances.forEach(e=>e.destroy()),this.instances.clear()}static async generateWaveformData(e,i=200){try{return(await M(e,i)).peaks}catch(o){throw console.error("Failed to generate waveform:",o),o}}};function L(){if(typeof document>"u")return;document.querySelectorAll("[data-waveform-player]").forEach(e=>{if(e.dataset.waveformInitialized!=="true")try{new g(e),e.dataset.waveformInitialized="true"}catch(i){console.error("Failed to initialize WaveformPlayer:",i,e)}})}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",L):L());g.init=L;typeof window<"u"&&(window.WaveformPlayer=g);var lt=g;export{g as WaveformPlayer,lt as default};
@@ -577,6 +577,12 @@
577
577
  height: 60,
578
578
  samples: 200,
579
579
  preload: "metadata",
580
+ // Audio mode — 'self' = player owns the <audio> element (default, current
581
+ // behavior). 'external' = player is a visualization-only surface; no audio
582
+ // element is created, play() dispatches `waveformplayer:request-play`
583
+ // instead of calling audio.play(), and setPlayingState/setProgress are
584
+ // expected to be driven by an external controller (e.g. WaveformBar).
585
+ audioMode: "self",
580
586
  // Playback
581
587
  playbackRate: 1,
582
588
  showPlaybackSpeed: false,
@@ -822,8 +828,18 @@
822
828
  /**
823
829
  * Create audio element
824
830
  * @private
831
+ *
832
+ * No-op in `audioMode: 'external'` — the player has no audio of its
833
+ * own; an external controller (e.g. WaveformBar) owns playback and
834
+ * pushes state in via setPlayingState() / setProgress(). The
835
+ * `this.audio` field stays null in that mode; downstream code must
836
+ * null-check it.
825
837
  */
826
838
  createAudio() {
839
+ if (this.options.audioMode === "external") {
840
+ this.audio = null;
841
+ return;
842
+ }
827
843
  this.audio = new Audio();
828
844
  this.audio.preload = this.options.preload || "metadata";
829
845
  this.audio.crossOrigin = "anonymous";
@@ -836,7 +852,7 @@
836
852
  * @private
837
853
  */
838
854
  initPlaybackSpeed() {
839
- if (this.options.playbackRate && this.options.playbackRate !== 1) {
855
+ if (this.audio && this.options.playbackRate && this.options.playbackRate !== 1) {
840
856
  this.audio.playbackRate = this.options.playbackRate;
841
857
  }
842
858
  if (this.options.showPlaybackSpeed) {
@@ -886,21 +902,23 @@
886
902
  this.container.addEventListener("keydown", (e) => {
887
903
  if (document.activeElement !== this.container) return;
888
904
  const key = e.key;
889
- const currentTime = this.audio.currentTime;
890
- if (key >= "0" && key <= "9") {
905
+ const hasAudio = !!this.audio;
906
+ const currentTime = hasAudio ? this.audio.currentTime : 0;
907
+ if (hasAudio && key >= "0" && key <= "9") {
891
908
  e.preventDefault();
892
909
  this.seekToPercent(parseInt(key) / 10);
893
910
  return;
894
911
  }
895
912
  const actions = {
896
- " ": () => this.togglePlay(),
897
- "ArrowLeft": () => this.seekTo(Math.max(0, currentTime - 5)),
898
- "ArrowRight": () => this.seekTo(Math.min(this.audio.duration, currentTime + 5)),
899
- "ArrowUp": () => this.setVolume(Math.min(1, this.audio.volume + 0.1)),
900
- "ArrowDown": () => this.setVolume(Math.max(0, this.audio.volume - 0.1)),
901
- "m": () => this.audio.muted = !this.audio.muted,
902
- "M": () => this.audio.muted = !this.audio.muted
913
+ " ": () => this.togglePlay()
903
914
  };
915
+ if (hasAudio) {
916
+ actions["ArrowLeft"] = () => this.seekTo(Math.max(0, currentTime - 5));
917
+ actions["ArrowRight"] = () => this.seekTo(Math.min(this.audio.duration, currentTime + 5));
918
+ actions["ArrowUp"] = () => this.setVolume(Math.min(1, this.audio.volume + 0.1));
919
+ actions["ArrowDown"] = () => this.setVolume(Math.max(0, this.audio.volume - 0.1));
920
+ actions["m"] = actions["M"] = () => this.audio.muted = !this.audio.muted;
921
+ }
904
922
  if (actions[key]) {
905
923
  e.preventDefault();
906
924
  actions[key]();
@@ -913,6 +931,7 @@
913
931
  */
914
932
  initMediaSession() {
915
933
  if (!("mediaSession" in navigator) || !this.options.enableMediaSession) return;
934
+ if (!this.audio) return;
916
935
  navigator.mediaSession.metadata = new MediaMetadata({
917
936
  title: this.options.title || "Unknown Track",
918
937
  artist: this.options.subtitle || "",
@@ -946,13 +965,15 @@
946
965
  if (this.playBtn) {
947
966
  this.playBtn.addEventListener("click", () => this.togglePlay());
948
967
  }
949
- this.audio.addEventListener("loadstart", () => this.setLoading(true));
950
- this.audio.addEventListener("loadedmetadata", () => this.onMetadataLoaded());
951
- this.audio.addEventListener("canplay", () => this.setLoading(false));
952
- this.audio.addEventListener("play", () => this.onPlay());
953
- this.audio.addEventListener("pause", () => this.onPause());
954
- this.audio.addEventListener("ended", () => this.onEnded());
955
- this.audio.addEventListener("error", (e) => this.onError(e));
968
+ if (this.audio) {
969
+ this.audio.addEventListener("loadstart", () => this.setLoading(true));
970
+ this.audio.addEventListener("loadedmetadata", () => this.onMetadataLoaded());
971
+ this.audio.addEventListener("canplay", () => this.setLoading(false));
972
+ this.audio.addEventListener("play", () => this.onPlay());
973
+ this.audio.addEventListener("pause", () => this.onPause());
974
+ this.audio.addEventListener("ended", () => this.onEnded());
975
+ this.audio.addEventListener("error", (e) => this.onError(e));
976
+ }
956
977
  this.canvas.addEventListener("click", (e) => this.handleCanvasClick(e));
957
978
  this.resizeHandler = debounce(() => this.resizeCanvas(), 100);
958
979
  window.addEventListener("resize", this.resizeHandler);
@@ -984,21 +1005,23 @@
984
1005
  this.setLoading(true);
985
1006
  this.progress = 0;
986
1007
  this.hasError = false;
987
- this.audio.src = url;
988
- await new Promise((resolve, reject) => {
989
- const metadataHandler = () => {
990
- this.audio.removeEventListener("loadedmetadata", metadataHandler);
991
- this.audio.removeEventListener("error", errorHandler);
992
- resolve();
993
- };
994
- const errorHandler = (e) => {
995
- this.audio.removeEventListener("loadedmetadata", metadataHandler);
996
- this.audio.removeEventListener("error", errorHandler);
997
- reject(e);
998
- };
999
- this.audio.addEventListener("loadedmetadata", metadataHandler);
1000
- this.audio.addEventListener("error", errorHandler);
1001
- });
1008
+ if (this.audio) {
1009
+ this.audio.src = url;
1010
+ await new Promise((resolve, reject) => {
1011
+ const metadataHandler = () => {
1012
+ this.audio.removeEventListener("loadedmetadata", metadataHandler);
1013
+ this.audio.removeEventListener("error", errorHandler);
1014
+ resolve();
1015
+ };
1016
+ const errorHandler = (e) => {
1017
+ this.audio.removeEventListener("loadedmetadata", metadataHandler);
1018
+ this.audio.removeEventListener("error", errorHandler);
1019
+ reject(e);
1020
+ };
1021
+ this.audio.addEventListener("loadedmetadata", metadataHandler);
1022
+ this.audio.addEventListener("error", errorHandler);
1023
+ });
1024
+ }
1002
1025
  const title = this.options.title || extractTitleFromUrl(url);
1003
1026
  if (this.titleEl) {
1004
1027
  this.titleEl.textContent = title;
@@ -1043,8 +1066,10 @@
1043
1066
  if (this.isPlaying) {
1044
1067
  this.pause();
1045
1068
  }
1046
- this.audio.src = "";
1047
- this.audio.load();
1069
+ if (this.audio) {
1070
+ this.audio.src = "";
1071
+ this.audio.load();
1072
+ }
1048
1073
  this.hasError = false;
1049
1074
  if (this.errorEl) {
1050
1075
  this.errorEl.style.display = "none";
@@ -1063,7 +1088,7 @@
1063
1088
  subtitle: subtitle || this.options.subtitle,
1064
1089
  ...options
1065
1090
  });
1066
- if (options.preload) {
1091
+ if (options.preload && this.audio) {
1067
1092
  this.audio.preload = options.preload;
1068
1093
  }
1069
1094
  if (this.subtitleEl) {
@@ -1187,10 +1212,23 @@
1187
1212
  * @private
1188
1213
  */
1189
1214
  handleCanvasClick(event) {
1190
- if (!this.audio.duration) return;
1191
1215
  const rect = this.canvas.getBoundingClientRect();
1192
1216
  const x = event.clientX - rect.left;
1193
1217
  const targetPercent = Math.max(0, Math.min(1, x / rect.width));
1218
+ if (this.options.audioMode === "external") {
1219
+ const evt = new CustomEvent("waveformplayer:request-seek", {
1220
+ bubbles: true,
1221
+ cancelable: true,
1222
+ detail: { ...this._buildTrackDetail(), percent: targetPercent }
1223
+ });
1224
+ this.container.dispatchEvent(evt);
1225
+ if (!evt.defaultPrevented) {
1226
+ this.progress = targetPercent;
1227
+ this.drawWaveform?.();
1228
+ }
1229
+ return;
1230
+ }
1231
+ if (!this.audio || !this.audio.duration) return;
1194
1232
  this.seekToPercent(targetPercent);
1195
1233
  }
1196
1234
  /**
@@ -1313,7 +1351,7 @@
1313
1351
  startSmoothUpdate() {
1314
1352
  this.stopSmoothUpdate();
1315
1353
  const update = () => {
1316
- if (this.isPlaying && this.audio.duration) {
1354
+ if (this.isPlaying && this.audio && this.audio.duration) {
1317
1355
  this.updateProgress();
1318
1356
  this.updateTimer = requestAnimationFrame(update);
1319
1357
  }
@@ -1335,7 +1373,7 @@
1335
1373
  * @private
1336
1374
  */
1337
1375
  updateProgress() {
1338
- if (!this.audio.duration) return;
1376
+ if (!this.audio || !this.audio.duration) return;
1339
1377
  const newProgress = this.audio.currentTime / this.audio.duration;
1340
1378
  if (Math.abs(newProgress - this.progress) > 1e-3) {
1341
1379
  this.progress = newProgress;
@@ -1388,25 +1426,137 @@
1388
1426
  // Public API
1389
1427
  // ============================================
1390
1428
  /**
1391
- * Play audio
1392
- * @return {Promise} The promise returned by HTMLMediaElement.play()
1429
+ * Play audio.
1430
+ *
1431
+ * In `audioMode: 'self'` (default): calls the underlying <audio>
1432
+ * element's play(). Returns the promise from HTMLMediaElement.play().
1433
+ *
1434
+ * In `audioMode: 'external'`: dispatches a cancelable
1435
+ * `waveformplayer:request-play` event with the track metadata and
1436
+ * does NOT touch any audio element. Returns `undefined`. An external
1437
+ * controller (e.g. WaveformBar) listens for this event and starts
1438
+ * playback on its own audio source, then pushes state back via
1439
+ * setPlayingState() / setProgress(). Calling preventDefault() on
1440
+ * the event lets the controller veto the play (state is unchanged).
1441
+ *
1442
+ * @return {Promise|undefined}
1393
1443
  */
1394
1444
  play() {
1395
1445
  if (this.options.singlePlay && _WaveformPlayer.currentlyPlaying && _WaveformPlayer.currentlyPlaying !== this) {
1396
1446
  _WaveformPlayer.currentlyPlaying.pause();
1397
1447
  }
1448
+ if (this.options.audioMode === "external") {
1449
+ const evt = new CustomEvent("waveformplayer:request-play", {
1450
+ bubbles: true,
1451
+ cancelable: true,
1452
+ detail: this._buildTrackDetail()
1453
+ });
1454
+ this.container.dispatchEvent(evt);
1455
+ if (!evt.defaultPrevented) {
1456
+ _WaveformPlayer.currentlyPlaying = this;
1457
+ }
1458
+ return void 0;
1459
+ }
1398
1460
  _WaveformPlayer.currentlyPlaying = this;
1399
1461
  return this.audio.play();
1400
1462
  }
1401
1463
  /**
1402
- * Pause audio
1464
+ * Pause audio.
1465
+ *
1466
+ * In `audioMode: 'external'`, dispatches `waveformplayer:request-pause`
1467
+ * (cancelable) and does NOT touch any audio element. See play().
1403
1468
  */
1404
1469
  pause() {
1405
1470
  if (_WaveformPlayer.currentlyPlaying === this) {
1406
1471
  _WaveformPlayer.currentlyPlaying = null;
1407
1472
  }
1473
+ if (this.options.audioMode === "external") {
1474
+ this.container.dispatchEvent(new CustomEvent("waveformplayer:request-pause", {
1475
+ bubbles: true,
1476
+ cancelable: true,
1477
+ detail: this._buildTrackDetail()
1478
+ }));
1479
+ return;
1480
+ }
1408
1481
  this.audio.pause();
1409
1482
  }
1483
+ /**
1484
+ * Build the track detail object dispatched by request-play /
1485
+ * request-pause events in external audio mode. Mirrors the shape
1486
+ * WaveformBar.play() accepts so a controller can forward it
1487
+ * directly: `WaveformBar.play(event.detail)`.
1488
+ *
1489
+ * @private
1490
+ * @return {{url:string,title:?string,subtitle:?string,artist:?string,artwork:?string,player:WaveformPlayer}}
1491
+ */
1492
+ _buildTrackDetail() {
1493
+ return {
1494
+ url: this.options.url,
1495
+ title: this.options.title,
1496
+ subtitle: this.options.subtitle,
1497
+ artist: this.options.artist,
1498
+ artwork: this.options.artwork,
1499
+ id: this.id,
1500
+ player: this
1501
+ };
1502
+ }
1503
+ /**
1504
+ * External-mode state pump: flip the play/pause visual state without
1505
+ * touching audio. Mirrors what onPlay()/onPause() do but skips the
1506
+ * audio-element interactions. Safe to call repeatedly — idempotent.
1507
+ *
1508
+ * @param {boolean} playing
1509
+ */
1510
+ setPlayingState(playing) {
1511
+ const wasPlaying = this.isPlaying;
1512
+ this.isPlaying = !!playing;
1513
+ if (this.playBtn) {
1514
+ this.playBtn.classList.toggle("playing", this.isPlaying);
1515
+ const playIcon = this.playBtn.querySelector(".waveform-icon-play");
1516
+ const pauseIcon = this.playBtn.querySelector(".waveform-icon-pause");
1517
+ if (playIcon) playIcon.style.display = this.isPlaying ? "none" : "flex";
1518
+ if (pauseIcon) pauseIcon.style.display = this.isPlaying ? "flex" : "none";
1519
+ }
1520
+ if (this.isPlaying && !wasPlaying) {
1521
+ this.startSmoothUpdate?.();
1522
+ this.container.dispatchEvent(new CustomEvent("waveformplayer:play", {
1523
+ bubbles: true,
1524
+ detail: { player: this, url: this.options.url }
1525
+ }));
1526
+ if (this.options.onPlay) this.options.onPlay(this);
1527
+ } else if (!this.isPlaying && wasPlaying) {
1528
+ this.stopSmoothUpdate?.();
1529
+ this.container.dispatchEvent(new CustomEvent("waveformplayer:pause", {
1530
+ bubbles: true,
1531
+ detail: { player: this, url: this.options.url }
1532
+ }));
1533
+ if (this.options.onPause) this.options.onPause(this);
1534
+ }
1535
+ }
1536
+ /**
1537
+ * External-mode state pump: update the visualization's progress
1538
+ * from an external clock (e.g. WaveformBar's audio element's
1539
+ * timeupdate). Drives the canvas redraw + the time displays.
1540
+ *
1541
+ * @param {number} currentTime Current playback position in seconds.
1542
+ * @param {number} duration Total track duration in seconds.
1543
+ */
1544
+ setProgress(currentTime, duration) {
1545
+ if (!duration || duration <= 0) return;
1546
+ this.progress = Math.max(0, Math.min(1, currentTime / duration));
1547
+ if (this.currentTimeEl) this.currentTimeEl.textContent = formatTime(currentTime);
1548
+ if (this.totalTimeEl && (!this.totalTimeEl.dataset._extSet || this._extDuration !== duration)) {
1549
+ this.totalTimeEl.textContent = formatTime(duration);
1550
+ this.totalTimeEl.dataset._extSet = "1";
1551
+ this._extDuration = duration;
1552
+ }
1553
+ this.drawWaveform?.();
1554
+ this.container.dispatchEvent(new CustomEvent("waveformplayer:timeupdate", {
1555
+ bubbles: true,
1556
+ detail: { player: this, currentTime, duration, progress: this.progress }
1557
+ }));
1558
+ if (this.options.onTimeUpdate) this.options.onTimeUpdate(this, currentTime, duration);
1559
+ }
1410
1560
  /**
1411
1561
  * Toggle play/pause
1412
1562
  */
@@ -1,4 +1,4 @@
1
- (()=>{function A(t){let e={};if(t.dataset.url&&(e.url=t.dataset.url),t.dataset.height&&(e.height=parseInt(t.dataset.height)),t.dataset.samples&&(e.samples=parseInt(t.dataset.samples)),t.dataset.preload&&(e.preload=t.dataset.preload),t.dataset.waveformStyle&&(e.waveformStyle=t.dataset.waveformStyle),t.dataset.barWidth&&(e.barWidth=parseInt(t.dataset.barWidth)),t.dataset.barSpacing&&(e.barSpacing=parseInt(t.dataset.barSpacing)),t.dataset.buttonAlign&&(e.buttonAlign=t.dataset.buttonAlign),t.dataset.colorPreset&&(e.colorPreset=t.dataset.colorPreset),t.dataset.waveformColor&&(e.waveformColor=t.dataset.waveformColor),t.dataset.progressColor&&(e.progressColor=t.dataset.progressColor),t.dataset.buttonColor&&(e.buttonColor=t.dataset.buttonColor),t.dataset.buttonHoverColor&&(e.buttonHoverColor=t.dataset.buttonHoverColor),t.dataset.textColor&&(e.textColor=t.dataset.textColor),t.dataset.textSecondaryColor&&(e.textSecondaryColor=t.dataset.textSecondaryColor),t.dataset.backgroundColor&&(e.backgroundColor=t.dataset.backgroundColor),t.dataset.borderColor&&(e.borderColor=t.dataset.borderColor),t.dataset.color&&(e.waveformColor=t.dataset.color),t.dataset.theme&&(e.colorPreset=t.dataset.theme),t.dataset.autoplay&&(e.autoplay=t.dataset.autoplay==="true"),t.dataset.showControls!==void 0&&(e.showControls=t.dataset.showControls==="true"),t.dataset.showInfo!==void 0&&(e.showInfo=t.dataset.showInfo==="true"),t.dataset.showTime&&(e.showTime=t.dataset.showTime==="true"),t.dataset.showHoverTime&&(e.showHoverTime=t.dataset.showHoverTime==="true"),t.dataset.showBpm&&(e.showBPM=t.dataset.showBpm==="true"),t.dataset.singlePlay&&(e.singlePlay=t.dataset.singlePlay==="true"),t.dataset.playOnSeek&&(e.playOnSeek=t.dataset.playOnSeek==="true"),t.dataset.title&&(e.title=t.dataset.title),t.dataset.subtitle&&(e.subtitle=t.dataset.subtitle),t.dataset.album&&(e.album=t.dataset.album),t.dataset.artwork&&(e.artwork=t.dataset.artwork),t.dataset.waveform&&(e.waveform=t.dataset.waveform),t.dataset.markers)try{e.markers=JSON.parse(t.dataset.markers)}catch(i){console.warn("Invalid markers JSON:",i)}if(t.dataset.playbackRate&&(e.playbackRate=parseFloat(t.dataset.playbackRate)),t.dataset.showPlaybackSpeed!==void 0&&(e.showPlaybackSpeed=t.dataset.showPlaybackSpeed==="true"),t.dataset.playbackRates)try{e.playbackRates=JSON.parse(t.dataset.playbackRates)}catch(i){console.warn("Invalid playbackRates JSON:",i)}return t.dataset.enableMediaSession!==void 0&&(e.enableMediaSession=t.dataset.enableMediaSession==="true"),e}function C(t){if(!t||isNaN(t))return"0:00";let e=Math.floor(t/60),i=Math.floor(t%60);return`${e}:${i.toString().padStart(2,"0")}`}function R(t){let e=t||Math.random().toString();return btoa(e.substring(0,10)).replace(/[^a-zA-Z0-9]/g,"")}function B(t){if(!t)return"Audio";let e=t.split("/");return e[e.length-1].split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,s=>s.toUpperCase())}function P(...t){let e={};for(let i of t)for(let o in i)i[o]!==null&&i[o]!==void 0&&(e[o]=i[o]);return e}function I(t,e){let i;return function(...s){let n=()=>{clearTimeout(i),t(...s)};clearTimeout(i),i=setTimeout(n,e)}}function S(t,e){if(t.length===e)return t;if(t.length===0||e===0)return[];let i=[];if(e>t.length){let o=(t.length-1)/(e-1);for(let s=0;s<e;s++){let n=s*o,r=Math.floor(n),a=Math.ceil(n),h=n-r;if(a>=t.length)i.push(t[t.length-1]);else if(r===a)i.push(t[r]);else{let l=t[r]*(1-h)+t[a]*h;i.push(l)}}}else{let o=t.length/e;for(let s=0;s<e;s++){let n=Math.floor(s*o),r=Math.floor((s+1)*o),a=0,h=0;for(let l=n;l<=r&&l<t.length;l++)t[l]>a&&(a=t[l]),h++;if(h===0){let l=Math.min(Math.round(s*o),t.length-1);a=t[l]}i.push(a)}}return i}function M(t,e,i,o,s){let n=window.devicePixelRatio||1,r=s.barWidth*n,a=s.barSpacing*n,h=Math.floor(e.width/(r+a)),l=S(i,h),d=e.height,p=o*e.width;t.clearRect(0,0,e.width,e.height);for(let y=0;y<l.length;y++){let f=y*(r+a);if(f+r>e.width)break;let c=l[y]*d*.9,m=d-c;t.fillStyle=s.color,t.fillRect(f,m,r,c)}t.save(),t.beginPath(),t.rect(0,0,p,d),t.clip();for(let y=0;y<l.length;y++){let f=y*(r+a);if(f>p)break;let c=l[y]*d*.9,m=d-c;t.fillStyle=s.progressColor,t.fillRect(f,m,r,c)}t.restore()}function U(t,e,i,o,s){let n=window.devicePixelRatio||1,r=s.barWidth*n,a=s.barSpacing*n,h=Math.floor(e.width/(r+a)),l=S(i,h),d=e.height,p=d/2,y=o*e.width;t.clearRect(0,0,e.width,e.height);for(let f=0;f<l.length;f++){let c=f*(r+a);if(c+r>e.width)break;let m=l[f]*d*.45;t.fillStyle=s.color,t.fillRect(c,p-m,r,m),t.fillRect(c,p,r,m)}t.save(),t.beginPath(),t.rect(0,0,y,d),t.clip();for(let f=0;f<l.length;f++){let c=f*(r+a);if(c>y)break;let m=l[f]*d*.45;t.fillStyle=s.progressColor,t.fillRect(c,p-m,r,m),t.fillRect(c,p,r,m)}t.restore()}function F(t,e,i,o,s){let n=e.width,r=e.height,a=r/2,h=r*.35;t.clearRect(0,0,n,r);let l=(d,p,y=1,f=!1)=>{f&&(t.shadowBlur=12,t.shadowColor=d),t.strokeStyle=d,t.lineWidth=p,t.lineCap="round",t.lineJoin="round",t.beginPath(),t.moveTo(0,a);let c=[],m=Math.floor(i.length*y);for(let u=0;u<m;u++){let v=u/(i.length-1)*n,k=i[u],b=Math.sin(u*.1)*k,w=a+b*h;c.push({x:v,y:w})}for(let u=0;u<c.length-1;u++){let v=c[u].x+(c[u+1].x-c[u].x)*.5,k=c[u].y,b=c[u+1].x-(c[u+1].x-c[u].x)*.5,w=c[u+1].y;t.bezierCurveTo(v,k,b,w,c[u+1].x,c[u+1].y)}t.stroke(),f&&(t.shadowBlur=0)};t.strokeStyle="rgba(255, 255, 255, 0.03)",t.lineWidth=.5,t.beginPath(),t.moveTo(0,a),t.lineTo(n,a),t.stroke();for(let d=0;d<=10;d++){let p=n/10*d;t.beginPath(),t.moveTo(p,0),t.lineTo(p,r),t.stroke()}l(s.color,2,1,!1),o>0&&l(s.progressColor,3,o,!0)}function W(t,e,i,o,s){let n=window.devicePixelRatio||1,r=(s.barWidth||3)*n,a=(s.barSpacing||1)*n,h=Math.floor(e.width/(r+a)),l=S(i,h),d=e.height,p=4*n,y=2*n,f=o*e.width,c=d/2;t.clearRect(0,0,e.width,e.height);for(let m=0;m<l.length;m++){let u=m*(r+a);if(u+r>e.width)break;let v=l[m]*d*.9,k=Math.floor(v/(p+y));t.fillStyle=u<f?s.progressColor:s.color;for(let b=0;b<k;b++){let w=b*(p+y);t.fillRect(u,c-w-p,r,p),b>0&&t.fillRect(u,c+w,r,p)}}}function x(t,e,i,o,s){let n=window.devicePixelRatio||1,r=(s.barWidth||2)*n,a=(s.barSpacing||3)*n,h=Math.floor(e.width/(r+a)),l=S(i,h),d=e.height,p=Math.max(1.5*n,r/2),y=o*e.width,f=d/2;t.clearRect(0,0,e.width,e.height);for(let c=0;c<l.length;c++){let m=c*(r+a)+r/2;if(m>e.width)break;let u=l[c]*d*.9;t.fillStyle=m<y?s.progressColor:s.color,t.beginPath(),t.arc(m,f-u/2,p,0,Math.PI*2),t.fill(),t.beginPath(),t.arc(m,f+u/2,p,0,Math.PI*2),t.fill()}}function N(t,e,i,o,s){let n=e.width,r=e.height,a=r/2,h=4,l=h/2;if(t.clearRect(0,0,n,r),t.fillStyle=s.color||"rgba(255, 255, 255, 0.2)",t.beginPath(),t.moveTo(l,a-h/2),t.lineTo(n-l,a-h/2),t.arc(n-l,a,h/2,-Math.PI/2,Math.PI/2),t.lineTo(l,a+h/2),t.arc(l,a,h/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),o>0){let d=Math.max(l*2,o*n);t.shadowBlur=8,t.shadowColor=s.progressColor,t.fillStyle=s.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.moveTo(l,a-h/2),t.lineTo(d-l,a-h/2),t.arc(d-l,a,h/2,-Math.PI/2,Math.PI/2),t.lineTo(l,a+h/2),t.arc(l,a,h/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),t.shadowBlur=0;let p=8,y=d;t.shadowBlur=4,t.shadowColor="rgba(0, 0, 0, 0.3)",t.shadowOffsetY=2,t.fillStyle="#ffffff",t.beginPath(),t.arc(y,a,p,0,Math.PI*2),t.fill(),t.shadowBlur=0,t.shadowOffsetY=0,t.fillStyle=s.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.arc(y,a,p*.4,0,Math.PI*2),t.fill()}}var Y={bars:M,bar:M,mirror:U,line:F,blocks:W,block:W,dots:x,dot:x,seekbar:N};function D(t,e,i,o,s){(Y[s.waveformStyle]||M)(t,e,i,o,s)}function z(t){try{let e=t.getChannelData(0),i=t.sampleRate,o=V(e,i);if(o.length<2)return 120;let s=[];for(let h=1;h<o.length;h++)s.push((o[h]-o[h-1])/i);let n={};s.forEach(h=>{let l=60/h,d=Math.round(l/3)*3;d>60&&d<200&&(n[d]=(n[d]||0)+1)});let r=0,a=120;for(let[h,l]of Object.entries(n))l>r&&(r=l,a=parseInt(h));return a<70&&n[a*2]?a*=2:a>160&&n[Math.round(a/2)]&&(a=Math.round(a/2)),a-1}catch(e){return console.warn("BPM detection failed:",e),null}}function V(t,e){let s=[],n=0;for(let r=0;r<t.length-2048;r+=1024){let a=0;for(let d=r;d<r+2048;d++)a+=t[d]*t[d];a=a/2048;let h=a-n,l=n*1.8+.01;if(h>l&&a>.01){let d=s[s.length-1]||0,p=e*.15;r-d>p&&s.push(r)}n=a*.8+n*.2}return s}function j(t,e=200){let i=t.length/e,o=~~(i/10)||1,s=t.numberOfChannels,n=[];for(let a=0;a<s;a++){let h=t.getChannelData(a);for(let l=0;l<e;l++){let d=~~(l*i),p=~~(d+i),y=0,f=0;for(let m=d;m<p;m+=o){let u=h[m];u>f&&(f=u),u<y&&(y=u)}let c=Math.max(Math.abs(f),Math.abs(y));(a===0||c>n[l])&&(n[l]=c)}}let r=Math.max(...n);return r>0?n.map(a=>a/r):n}async function E(t,e=200,i=!1){try{let o=new(window.AudioContext||window.webkitAudioContext),n=await(await fetch(t)).arrayBuffer(),r=await o.decodeAudioData(n),a=j(r,e);a=J(a);let h=null;return i&&(h=await z(r)),o.close(),{peaks:a,bpm:h}}catch(o){throw console.error("Failed to generate waveform:",o),o}}function O(t=200){let e=[];for(let i=0;i<t;i++){let o=Math.random()*.5+.3,s=Math.sin(i/t*Math.PI*4)*.2;e.push(Math.max(.1,Math.min(1,o+s)))}return e}function J(t,e=.95){let i=Math.max(...t);if(i===0||i>e)return t;let o=e/i;return t.map(s=>s*o)}function G(){let t=document.documentElement,e=document.body;if(t.classList.contains("dark")||t.classList.contains("dark-mode")||t.classList.contains("theme-dark")||t.getAttribute("data-theme")==="dark"||t.getAttribute("data-color-scheme")==="dark"||e.classList.contains("dark")||e.classList.contains("dark-mode")||e.getAttribute("data-theme")==="dark")return"dark";if(t.classList.contains("light")||t.classList.contains("light-mode")||t.classList.contains("theme-light")||t.getAttribute("data-theme")==="light"||t.getAttribute("data-color-scheme")==="light"||e.classList.contains("light")||e.classList.contains("light-mode")||e.getAttribute("data-theme")==="light")return"light";try{let o=getComputedStyle(document.body).backgroundColor.match(/\d+/g);if(o&&o.length>=3){let[s,n,r]=o.map(Number),a=(s*299+n*587+r*114)/1e3;if(a>128)return"light";if(a<128)return"dark"}}catch{}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"}var T={dark:{waveformColor:"rgba(255, 255, 255, 0.3)",progressColor:"rgba(255, 255, 255, 0.9)",buttonColor:"rgba(255, 255, 255, 0.9)",buttonHoverColor:"rgba(255, 255, 255, 1)",textColor:"#ffffff",textSecondaryColor:"rgba(255, 255, 255, 0.6)",backgroundColor:"rgba(255, 255, 255, 0.03)",borderColor:"rgba(255, 255, 255, 0.1)"},light:{waveformColor:"rgba(0, 0, 0, 0.2)",progressColor:"rgba(0, 0, 0, 0.8)",buttonColor:"rgba(0, 0, 0, 0.8)",buttonHoverColor:"rgba(0, 0, 0, 0.9)",textColor:"#333333",textSecondaryColor:"rgba(0, 0, 0, 0.6)",backgroundColor:"rgba(0, 0, 0, 0.02)",borderColor:"rgba(0, 0, 0, 0.1)"}};function H(t){if(t&&T[t])return T[t];let e=G();return T[e]}var $={url:"",height:60,samples:200,preload:"metadata",playbackRate:1,showPlaybackSpeed:!1,playbackRates:[.5,.75,1,1.25,1.5,1.75,2],buttonAlign:"auto",waveformStyle:"mirror",barWidth:2,barSpacing:0,colorPreset:null,waveformColor:null,progressColor:null,buttonColor:null,buttonHoverColor:null,textColor:null,textSecondaryColor:null,backgroundColor:null,borderColor:null,autoplay:!1,showControls:!0,showInfo:!0,showTime:!0,showHoverTime:!1,showBPM:!1,singlePlay:!0,playOnSeek:!0,enableMediaSession:!0,markers:[],showMarkers:!0,title:null,subtitle:null,artwork:null,album:"",playIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M8 5v14l11-7z"/></svg>',pauseIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg>',onLoad:null,onPlay:null,onPause:null,onEnd:null,onError:null,onTimeUpdate:null},q={bars:{barWidth:3,barSpacing:1},mirror:{barWidth:2,barSpacing:0},line:{barWidth:2,barSpacing:0},blocks:{barWidth:4,barSpacing:2},dots:{barWidth:3,barSpacing:3},seekbar:{barWidth:1,barSpacing:0}};var g=class t{static instances=new Map;static currentlyPlaying=null;constructor(e,i={}){if(this.container=typeof e=="string"?document.querySelector(e):e,!this.container)throw new Error("WaveformPlayer: Container element not found");let o=A(this.container);this.options=P($,o,i);let s=H(this.options.colorPreset);for(let[r,a]of Object.entries(s))(this.options[r]===null||this.options[r]===void 0)&&(this.options[r]=a);let n=q[this.options.waveformStyle];n&&(o.barWidth===void 0&&i.barWidth===void 0&&(this.options.barWidth=n.barWidth),o.barSpacing===void 0&&i.barSpacing===void 0&&(this.options.barSpacing=n.barSpacing)),this.audio=null,this.canvas=null,this.ctx=null,this.waveformData=[],this.progress=0,this.isPlaying=!1,this.isLoading=!1,this.hasError=!1,this.updateTimer=null,this.resizeObserver=null,this.id=this.container.id||R(this.options.url),t.instances.set(this.id,this),this.init(),setTimeout(()=>{this.container.dispatchEvent(new CustomEvent("waveformplayer:ready",{bubbles:!0,detail:{player:this,url:this.options.url}}))},100)}init(){this.createDOM(),this.createAudio(),this.initPlaybackSpeed(),this.initKeyboardControls(),this.bindEvents(),this.setupResizeObserver(),requestAnimationFrame(()=>{this.resizeCanvas(),this.options.url&&this.load(this.options.url).then(()=>{this.options.autoplay&&this.play()}).catch(e=>{console.error("Failed to load audio:",e)})})}createDOM(){this.container.innerHTML="",this.container.className="waveform-player";let e=this.options.buttonAlign;e==="auto"&&(this.options.waveformStyle==="bars"?e="bottom":e="center");let i=this.options.showControls?`
1
+ (()=>{function A(t){let e={};if(t.dataset.url&&(e.url=t.dataset.url),t.dataset.height&&(e.height=parseInt(t.dataset.height)),t.dataset.samples&&(e.samples=parseInt(t.dataset.samples)),t.dataset.preload&&(e.preload=t.dataset.preload),t.dataset.waveformStyle&&(e.waveformStyle=t.dataset.waveformStyle),t.dataset.barWidth&&(e.barWidth=parseInt(t.dataset.barWidth)),t.dataset.barSpacing&&(e.barSpacing=parseInt(t.dataset.barSpacing)),t.dataset.buttonAlign&&(e.buttonAlign=t.dataset.buttonAlign),t.dataset.colorPreset&&(e.colorPreset=t.dataset.colorPreset),t.dataset.waveformColor&&(e.waveformColor=t.dataset.waveformColor),t.dataset.progressColor&&(e.progressColor=t.dataset.progressColor),t.dataset.buttonColor&&(e.buttonColor=t.dataset.buttonColor),t.dataset.buttonHoverColor&&(e.buttonHoverColor=t.dataset.buttonHoverColor),t.dataset.textColor&&(e.textColor=t.dataset.textColor),t.dataset.textSecondaryColor&&(e.textSecondaryColor=t.dataset.textSecondaryColor),t.dataset.backgroundColor&&(e.backgroundColor=t.dataset.backgroundColor),t.dataset.borderColor&&(e.borderColor=t.dataset.borderColor),t.dataset.color&&(e.waveformColor=t.dataset.color),t.dataset.theme&&(e.colorPreset=t.dataset.theme),t.dataset.autoplay&&(e.autoplay=t.dataset.autoplay==="true"),t.dataset.showControls!==void 0&&(e.showControls=t.dataset.showControls==="true"),t.dataset.showInfo!==void 0&&(e.showInfo=t.dataset.showInfo==="true"),t.dataset.showTime&&(e.showTime=t.dataset.showTime==="true"),t.dataset.showHoverTime&&(e.showHoverTime=t.dataset.showHoverTime==="true"),t.dataset.showBpm&&(e.showBPM=t.dataset.showBpm==="true"),t.dataset.singlePlay&&(e.singlePlay=t.dataset.singlePlay==="true"),t.dataset.playOnSeek&&(e.playOnSeek=t.dataset.playOnSeek==="true"),t.dataset.title&&(e.title=t.dataset.title),t.dataset.subtitle&&(e.subtitle=t.dataset.subtitle),t.dataset.album&&(e.album=t.dataset.album),t.dataset.artwork&&(e.artwork=t.dataset.artwork),t.dataset.waveform&&(e.waveform=t.dataset.waveform),t.dataset.markers)try{e.markers=JSON.parse(t.dataset.markers)}catch(i){console.warn("Invalid markers JSON:",i)}if(t.dataset.playbackRate&&(e.playbackRate=parseFloat(t.dataset.playbackRate)),t.dataset.showPlaybackSpeed!==void 0&&(e.showPlaybackSpeed=t.dataset.showPlaybackSpeed==="true"),t.dataset.playbackRates)try{e.playbackRates=JSON.parse(t.dataset.playbackRates)}catch(i){console.warn("Invalid playbackRates JSON:",i)}return t.dataset.enableMediaSession!==void 0&&(e.enableMediaSession=t.dataset.enableMediaSession==="true"),e}function S(t){if(!t||isNaN(t))return"0:00";let e=Math.floor(t/60),i=Math.floor(t%60);return`${e}:${i.toString().padStart(2,"0")}`}function x(t){let e=t||Math.random().toString();return btoa(e.substring(0,10)).replace(/[^a-zA-Z0-9]/g,"")}function B(t){if(!t)return"Audio";let e=t.split("/");return e[e.length-1].split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,s=>s.toUpperCase())}function C(...t){let e={};for(let i of t)for(let o in i)i[o]!==null&&i[o]!==void 0&&(e[o]=i[o]);return e}function R(t,e){let i;return function(...s){let a=()=>{clearTimeout(i),t(...s)};clearTimeout(i),i=setTimeout(a,e)}}function P(t,e){if(t.length===e)return t;if(t.length===0||e===0)return[];let i=[];if(e>t.length){let o=(t.length-1)/(e-1);for(let s=0;s<e;s++){let a=s*o,n=Math.floor(a),r=Math.ceil(a),h=a-n;if(r>=t.length)i.push(t[t.length-1]);else if(n===r)i.push(t[n]);else{let l=t[n]*(1-h)+t[r]*h;i.push(l)}}}else{let o=t.length/e;for(let s=0;s<e;s++){let a=Math.floor(s*o),n=Math.floor((s+1)*o),r=0,h=0;for(let l=a;l<=n&&l<t.length;l++)t[l]>r&&(r=t[l]),h++;if(h===0){let l=Math.min(Math.round(s*o),t.length-1);r=t[l]}i.push(r)}}return i}function E(t,e,i,o,s){let a=window.devicePixelRatio||1,n=s.barWidth*a,r=s.barSpacing*a,h=Math.floor(e.width/(n+r)),l=P(i,h),d=e.height,p=o*e.width;t.clearRect(0,0,e.width,e.height);for(let m=0;m<l.length;m++){let f=m*(n+r);if(f+n>e.width)break;let c=l[m]*d*.9,y=d-c;t.fillStyle=s.color,t.fillRect(f,y,n,c)}t.save(),t.beginPath(),t.rect(0,0,p,d),t.clip();for(let m=0;m<l.length;m++){let f=m*(n+r);if(f>p)break;let c=l[m]*d*.9,y=d-c;t.fillStyle=s.progressColor,t.fillRect(f,y,n,c)}t.restore()}function U(t,e,i,o,s){let a=window.devicePixelRatio||1,n=s.barWidth*a,r=s.barSpacing*a,h=Math.floor(e.width/(n+r)),l=P(i,h),d=e.height,p=d/2,m=o*e.width;t.clearRect(0,0,e.width,e.height);for(let f=0;f<l.length;f++){let c=f*(n+r);if(c+n>e.width)break;let y=l[f]*d*.45;t.fillStyle=s.color,t.fillRect(c,p-y,n,y),t.fillRect(c,p,n,y)}t.save(),t.beginPath(),t.rect(0,0,m,d),t.clip();for(let f=0;f<l.length;f++){let c=f*(n+r);if(c>m)break;let y=l[f]*d*.45;t.fillStyle=s.progressColor,t.fillRect(c,p-y,n,y),t.fillRect(c,p,n,y)}t.restore()}function F(t,e,i,o,s){let a=e.width,n=e.height,r=n/2,h=n*.35;t.clearRect(0,0,a,n);let l=(d,p,m=1,f=!1)=>{f&&(t.shadowBlur=12,t.shadowColor=d),t.strokeStyle=d,t.lineWidth=p,t.lineCap="round",t.lineJoin="round",t.beginPath(),t.moveTo(0,r);let c=[],y=Math.floor(i.length*m);for(let u=0;u<y;u++){let v=u/(i.length-1)*a,k=i[u],b=Math.sin(u*.1)*k,w=r+b*h;c.push({x:v,y:w})}for(let u=0;u<c.length-1;u++){let v=c[u].x+(c[u+1].x-c[u].x)*.5,k=c[u].y,b=c[u+1].x-(c[u+1].x-c[u].x)*.5,w=c[u+1].y;t.bezierCurveTo(v,k,b,w,c[u+1].x,c[u+1].y)}t.stroke(),f&&(t.shadowBlur=0)};t.strokeStyle="rgba(255, 255, 255, 0.03)",t.lineWidth=.5,t.beginPath(),t.moveTo(0,r),t.lineTo(a,r),t.stroke();for(let d=0;d<=10;d++){let p=a/10*d;t.beginPath(),t.moveTo(p,0),t.lineTo(p,n),t.stroke()}l(s.color,2,1,!1),o>0&&l(s.progressColor,3,o,!0)}function I(t,e,i,o,s){let a=window.devicePixelRatio||1,n=(s.barWidth||3)*a,r=(s.barSpacing||1)*a,h=Math.floor(e.width/(n+r)),l=P(i,h),d=e.height,p=4*a,m=2*a,f=o*e.width,c=d/2;t.clearRect(0,0,e.width,e.height);for(let y=0;y<l.length;y++){let u=y*(n+r);if(u+n>e.width)break;let v=l[y]*d*.9,k=Math.floor(v/(p+m));t.fillStyle=u<f?s.progressColor:s.color;for(let b=0;b<k;b++){let w=b*(p+m);t.fillRect(u,c-w-p,n,p),b>0&&t.fillRect(u,c+w,n,p)}}}function D(t,e,i,o,s){let a=window.devicePixelRatio||1,n=(s.barWidth||2)*a,r=(s.barSpacing||3)*a,h=Math.floor(e.width/(n+r)),l=P(i,h),d=e.height,p=Math.max(1.5*a,n/2),m=o*e.width,f=d/2;t.clearRect(0,0,e.width,e.height);for(let c=0;c<l.length;c++){let y=c*(n+r)+n/2;if(y>e.width)break;let u=l[c]*d*.9;t.fillStyle=y<m?s.progressColor:s.color,t.beginPath(),t.arc(y,f-u/2,p,0,Math.PI*2),t.fill(),t.beginPath(),t.arc(y,f+u/2,p,0,Math.PI*2),t.fill()}}function N(t,e,i,o,s){let a=e.width,n=e.height,r=n/2,h=4,l=h/2;if(t.clearRect(0,0,a,n),t.fillStyle=s.color||"rgba(255, 255, 255, 0.2)",t.beginPath(),t.moveTo(l,r-h/2),t.lineTo(a-l,r-h/2),t.arc(a-l,r,h/2,-Math.PI/2,Math.PI/2),t.lineTo(l,r+h/2),t.arc(l,r,h/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),o>0){let d=Math.max(l*2,o*a);t.shadowBlur=8,t.shadowColor=s.progressColor,t.fillStyle=s.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.moveTo(l,r-h/2),t.lineTo(d-l,r-h/2),t.arc(d-l,r,h/2,-Math.PI/2,Math.PI/2),t.lineTo(l,r+h/2),t.arc(l,r,h/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),t.shadowBlur=0;let p=8,m=d;t.shadowBlur=4,t.shadowColor="rgba(0, 0, 0, 0.3)",t.shadowOffsetY=2,t.fillStyle="#ffffff",t.beginPath(),t.arc(m,r,p,0,Math.PI*2),t.fill(),t.shadowBlur=0,t.shadowOffsetY=0,t.fillStyle=s.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.arc(m,r,p*.4,0,Math.PI*2),t.fill()}}var Y={bars:E,bar:E,mirror:U,line:F,blocks:I,block:I,dots:D,dot:D,seekbar:N};function W(t,e,i,o,s){(Y[s.waveformStyle]||E)(t,e,i,o,s)}function z(t){try{let e=t.getChannelData(0),i=t.sampleRate,o=V(e,i);if(o.length<2)return 120;let s=[];for(let h=1;h<o.length;h++)s.push((o[h]-o[h-1])/i);let a={};s.forEach(h=>{let l=60/h,d=Math.round(l/3)*3;d>60&&d<200&&(a[d]=(a[d]||0)+1)});let n=0,r=120;for(let[h,l]of Object.entries(a))l>n&&(n=l,r=parseInt(h));return r<70&&a[r*2]?r*=2:r>160&&a[Math.round(r/2)]&&(r=Math.round(r/2)),r-1}catch(e){return console.warn("BPM detection failed:",e),null}}function V(t,e){let s=[],a=0;for(let n=0;n<t.length-2048;n+=1024){let r=0;for(let d=n;d<n+2048;d++)r+=t[d]*t[d];r=r/2048;let h=r-a,l=a*1.8+.01;if(h>l&&r>.01){let d=s[s.length-1]||0,p=e*.15;n-d>p&&s.push(n)}a=r*.8+a*.2}return s}function j(t,e=200){let i=t.length/e,o=~~(i/10)||1,s=t.numberOfChannels,a=[];for(let r=0;r<s;r++){let h=t.getChannelData(r);for(let l=0;l<e;l++){let d=~~(l*i),p=~~(d+i),m=0,f=0;for(let y=d;y<p;y+=o){let u=h[y];u>f&&(f=u),u<m&&(m=u)}let c=Math.max(Math.abs(f),Math.abs(m));(r===0||c>a[l])&&(a[l]=c)}}let n=Math.max(...a);return n>0?a.map(r=>r/n):a}async function M(t,e=200,i=!1){try{let o=new(window.AudioContext||window.webkitAudioContext),a=await(await fetch(t)).arrayBuffer(),n=await o.decodeAudioData(a),r=j(n,e);r=J(r);let h=null;return i&&(h=await z(n)),o.close(),{peaks:r,bpm:h}}catch(o){throw console.error("Failed to generate waveform:",o),o}}function O(t=200){let e=[];for(let i=0;i<t;i++){let o=Math.random()*.5+.3,s=Math.sin(i/t*Math.PI*4)*.2;e.push(Math.max(.1,Math.min(1,o+s)))}return e}function J(t,e=.95){let i=Math.max(...t);if(i===0||i>e)return t;let o=e/i;return t.map(s=>s*o)}function _(){let t=document.documentElement,e=document.body;if(t.classList.contains("dark")||t.classList.contains("dark-mode")||t.classList.contains("theme-dark")||t.getAttribute("data-theme")==="dark"||t.getAttribute("data-color-scheme")==="dark"||e.classList.contains("dark")||e.classList.contains("dark-mode")||e.getAttribute("data-theme")==="dark")return"dark";if(t.classList.contains("light")||t.classList.contains("light-mode")||t.classList.contains("theme-light")||t.getAttribute("data-theme")==="light"||t.getAttribute("data-color-scheme")==="light"||e.classList.contains("light")||e.classList.contains("light-mode")||e.getAttribute("data-theme")==="light")return"light";try{let o=getComputedStyle(document.body).backgroundColor.match(/\d+/g);if(o&&o.length>=3){let[s,a,n]=o.map(Number),r=(s*299+a*587+n*114)/1e3;if(r>128)return"light";if(r<128)return"dark"}}catch{}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"}var T={dark:{waveformColor:"rgba(255, 255, 255, 0.3)",progressColor:"rgba(255, 255, 255, 0.9)",buttonColor:"rgba(255, 255, 255, 0.9)",buttonHoverColor:"rgba(255, 255, 255, 1)",textColor:"#ffffff",textSecondaryColor:"rgba(255, 255, 255, 0.6)",backgroundColor:"rgba(255, 255, 255, 0.03)",borderColor:"rgba(255, 255, 255, 0.1)"},light:{waveformColor:"rgba(0, 0, 0, 0.2)",progressColor:"rgba(0, 0, 0, 0.8)",buttonColor:"rgba(0, 0, 0, 0.8)",buttonHoverColor:"rgba(0, 0, 0, 0.9)",textColor:"#333333",textSecondaryColor:"rgba(0, 0, 0, 0.6)",backgroundColor:"rgba(0, 0, 0, 0.02)",borderColor:"rgba(0, 0, 0, 0.1)"}};function H(t){if(t&&T[t])return T[t];let e=_();return T[e]}var q={url:"",height:60,samples:200,preload:"metadata",audioMode:"self",playbackRate:1,showPlaybackSpeed:!1,playbackRates:[.5,.75,1,1.25,1.5,1.75,2],buttonAlign:"auto",waveformStyle:"mirror",barWidth:2,barSpacing:0,colorPreset:null,waveformColor:null,progressColor:null,buttonColor:null,buttonHoverColor:null,textColor:null,textSecondaryColor:null,backgroundColor:null,borderColor:null,autoplay:!1,showControls:!0,showInfo:!0,showTime:!0,showHoverTime:!1,showBPM:!1,singlePlay:!0,playOnSeek:!0,enableMediaSession:!0,markers:[],showMarkers:!0,title:null,subtitle:null,artwork:null,album:"",playIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M8 5v14l11-7z"/></svg>',pauseIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg>',onLoad:null,onPlay:null,onPause:null,onEnd:null,onError:null,onTimeUpdate:null},$={bars:{barWidth:3,barSpacing:1},mirror:{barWidth:2,barSpacing:0},line:{barWidth:2,barSpacing:0},blocks:{barWidth:4,barSpacing:2},dots:{barWidth:3,barSpacing:3},seekbar:{barWidth:1,barSpacing:0}};var g=class t{static instances=new Map;static currentlyPlaying=null;constructor(e,i={}){if(this.container=typeof e=="string"?document.querySelector(e):e,!this.container)throw new Error("WaveformPlayer: Container element not found");let o=A(this.container);this.options=C(q,o,i);let s=H(this.options.colorPreset);for(let[n,r]of Object.entries(s))(this.options[n]===null||this.options[n]===void 0)&&(this.options[n]=r);let a=$[this.options.waveformStyle];a&&(o.barWidth===void 0&&i.barWidth===void 0&&(this.options.barWidth=a.barWidth),o.barSpacing===void 0&&i.barSpacing===void 0&&(this.options.barSpacing=a.barSpacing)),this.audio=null,this.canvas=null,this.ctx=null,this.waveformData=[],this.progress=0,this.isPlaying=!1,this.isLoading=!1,this.hasError=!1,this.updateTimer=null,this.resizeObserver=null,this.id=this.container.id||x(this.options.url),t.instances.set(this.id,this),this.init(),setTimeout(()=>{this.container.dispatchEvent(new CustomEvent("waveformplayer:ready",{bubbles:!0,detail:{player:this,url:this.options.url}}))},100)}init(){this.createDOM(),this.createAudio(),this.initPlaybackSpeed(),this.initKeyboardControls(),this.bindEvents(),this.setupResizeObserver(),requestAnimationFrame(()=>{this.resizeCanvas(),this.options.url&&this.load(this.options.url).then(()=>{this.options.autoplay&&this.play()}).catch(e=>{console.error("Failed to load audio:",e)})})}createDOM(){this.container.innerHTML="",this.container.className="waveform-player";let e=this.options.buttonAlign;e==="auto"&&(this.options.waveformStyle==="bars"?e="bottom":e="center");let i=this.options.showControls?`
2
2
  <button class="waveform-btn" aria-label="Play/Pause" style="
3
3
  border-color: ${this.options.buttonColor};
4
4
  color: ${this.options.buttonColor};
@@ -63,4 +63,4 @@
63
63
  ${o}
64
64
  </div>
65
65
  </div>
66
- `,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.subtitleEl=this.container.querySelector(".waveform-subtitle"),this.artworkEl=this.container.querySelector(".waveform-artwork"),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()}createAudio(){this.audio=new Audio,this.audio.preload=this.options.preload||"metadata",this.audio.crossOrigin="anonymous"}initPlaybackSpeed(){this.options.playbackRate&&this.options.playbackRate!==1&&(this.audio.playbackRate=this.options.playbackRate),this.options.showPlaybackSpeed&&this.initSpeedControls()}initSpeedControls(){let e=this.container.querySelector(".speed-btn"),i=this.container.querySelector(".speed-menu");!e||!i||(e.addEventListener("click",o=>{o.stopPropagation(),i.style.display=i.style.display==="none"?"block":"none"}),document.addEventListener("click",()=>{i.style.display="none"}),i.addEventListener("click",o=>{if(o.stopPropagation(),o.target.classList.contains("speed-option")){let s=parseFloat(o.target.dataset.rate);this.setPlaybackRate(s),i.style.display="none"}}),this.updateSpeedUI())}initKeyboardControls(){this.container.setAttribute("tabindex","-1"),this.container.addEventListener("click",()=>{t.getAllInstances().forEach(e=>{e!==this&&e.container.setAttribute("tabindex","-1")}),this.container.setAttribute("tabindex","0"),this.container.focus()}),this.container.addEventListener("keydown",e=>{if(document.activeElement!==this.container)return;let i=e.key,o=this.audio.currentTime;if(i>="0"&&i<="9"){e.preventDefault(),this.seekToPercent(parseInt(i)/10);return}let s={" ":()=>this.togglePlay(),ArrowLeft:()=>this.seekTo(Math.max(0,o-5)),ArrowRight:()=>this.seekTo(Math.min(this.audio.duration,o+5)),ArrowUp:()=>this.setVolume(Math.min(1,this.audio.volume+.1)),ArrowDown:()=>this.setVolume(Math.max(0,this.audio.volume-.1)),m:()=>this.audio.muted=!this.audio.muted,M:()=>this.audio.muted=!this.audio.muted};s[i]&&(e.preventDefault(),s[i]())})}initMediaSession(){!("mediaSession"in navigator)||!this.options.enableMediaSession||(navigator.mediaSession.metadata=new MediaMetadata({title:this.options.title||"Unknown Track",artist:this.options.subtitle||"",album:this.options.album||"",artwork:this.options.artwork?[{src:this.options.artwork,sizes:"512x512",type:"image/jpeg"}]:[]}),navigator.mediaSession.setActionHandler("play",()=>this.play()),navigator.mediaSession.setActionHandler("pause",()=>this.pause()),navigator.mediaSession.setActionHandler("seekbackward",()=>{this.seekTo(Math.max(0,this.audio.currentTime-10))}),navigator.mediaSession.setActionHandler("seekforward",()=>{this.seekTo(Math.min(this.audio.duration,this.audio.currentTime+10))}),navigator.mediaSession.setActionHandler("seekto",e=>{e.seekTime!==null&&this.seekTo(e.seekTime)}))}bindEvents(){this.playBtn&&this.playBtn.addEventListener("click",()=>this.togglePlay()),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",e=>this.onError(e)),this.canvas.addEventListener("click",e=>this.handleCanvasClick(e)),this.resizeHandler=I(()=>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(e){try{this.setLoading(!0),this.progress=0,this.hasError=!1,this.audio.src=e,await new Promise((o,s)=>{let n=()=>{this.audio.removeEventListener("loadedmetadata",n),this.audio.removeEventListener("error",r),o()},r=a=>{this.audio.removeEventListener("loadedmetadata",n),this.audio.removeEventListener("error",r),s(a)};this.audio.addEventListener("loadedmetadata",n),this.audio.addEventListener("error",r)});let i=this.options.title||B(e);if(this.titleEl&&(this.titleEl.textContent=i),this.options.waveform)this.setWaveformData(this.options.waveform);else try{let o=await E(e,this.options.samples,this.options.showBPM);this.waveformData=o.peaks,o.bpm&&(this.detectedBPM=o.bpm,this.updateBPMDisplay())}catch(o){console.warn("Using placeholder waveform:",o),this.waveformData=O(this.options.samples)}this.drawWaveform(),this.renderMarkers(),this.initMediaSession(),this.options.onLoad&&this.options.onLoad(this)}catch(i){console.error("Failed to load audio:",i),this.onError(i)}finally{this.setLoading(!1)}}async loadTrack(e,i=null,o=null,s={}){this.isPlaying&&this.pause(),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=P(this.options,{url:e,title:i||this.options.title,subtitle:o||this.options.subtitle,...s}),s.preload&&(this.audio.preload=s.preload),this.subtitleEl&&(o?(this.subtitleEl.textContent=o,this.subtitleEl.style.display=""):o===""&&(this.subtitleEl.style.display="none")),s.artwork&&this.artworkEl&&(this.artworkEl.src=s.artwork),this.options.markers=s.markers||[],await this.load(e),this.play().catch(()=>{})}setWaveformData(e){if(typeof e=="string"&&e.trim().endsWith(".json")){fetch(e.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}if(typeof e=="string")try{let i=JSON.parse(e);this.waveformData=Array.isArray(i)?i:[]}catch{this.waveformData=e.split(",").map(Number)}else this.waveformData=Array.isArray(e)?e:[];this.drawWaveform()}drawWaveform(){!this.ctx||this.waveformData.length===0||D(this.ctx,this.canvas,this.waveformData,this.progress,{...this.options,waveformStyle:this.options.waveformStyle||"bars",color:this.options.waveformColor,progressColor:this.options.progressColor})}resizeCanvas(){if(!this.canvas||this.isDestroying)return;let e=window.devicePixelRatio||1,i=this.canvas.parentElement.getBoundingClientRect();this.canvas.width=i.width*e,this.canvas.height=this.options.height*e,this.canvas.parentElement.style.height=this.options.height+"px",this.drawWaveform()}renderMarkers(){this.markersContainer&&(this.markersContainer.innerHTML="",!(!this.options.showMarkers||!this.options.markers?.length)&&(!this.audio||!this.audio.duration||this.audio.duration===0||this.options.markers.forEach((e,i)=>{if(e.time>this.audio.duration){console.warn(`Marker "${e.label}" at ${e.time}s exceeds audio duration of ${this.audio.duration}s`);return}let o=e.time/this.audio.duration*100,s=document.createElement("button");s.className="waveform-marker",s.style.left=`${o}%`,s.style.backgroundColor=e.color||"rgba(255, 255, 255, 0.5)",s.setAttribute("aria-label",e.label),s.setAttribute("data-time",e.time);let n=document.createElement("span");n.className="waveform-marker-tooltip",n.textContent=e.label,s.appendChild(n),s.addEventListener("click",r=>{r.stopPropagation(),this.seekTo(e.time),this.options.playOnSeek&&!this.isPlaying&&this.play()}),this.markersContainer.appendChild(s)})))}handleCanvasClick(e){if(!this.audio.duration)return;let i=this.canvas.getBoundingClientRect(),o=e.clientX-i.left,s=Math.max(0,Math.min(1,o/i.width));this.seekToPercent(s)}setLoading(e){this.isLoading=e,this.loadingEl&&(this.loadingEl.style.display=e?"block":"none")}onMetadataLoaded(){this.isDestroying||(this.totalTimeEl&&(this.totalTimeEl.textContent=C(this.audio.duration)),this.renderMarkers())}onPlay(){if(!this.isDestroying){if(this.isPlaying=!0,this.playBtn){this.playBtn.classList.add("playing");let e=this.playBtn.querySelector(".waveform-icon-play"),i=this.playBtn.querySelector(".waveform-icon-pause");e&&(e.style.display="none"),i&&(i.style.display="flex")}this.startSmoothUpdate(),this.container.dispatchEvent(new CustomEvent("waveformplayer:play",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPlay&&this.options.onPlay(this)}}onPause(){if(!this.isDestroying){if(this.isPlaying=!1,this.playBtn){this.playBtn.classList.remove("playing");let e=this.playBtn.querySelector(".waveform-icon-play"),i=this.playBtn.querySelector(".waveform-icon-pause");e&&(e.style.display="flex"),i&&(i.style.display="none")}this.stopSmoothUpdate(),this.container.dispatchEvent(new CustomEvent("waveformplayer:pause",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPause&&this.options.onPause(this)}}onEnded(){this.isDestroying||(this.progress=0,this.audio.currentTime=0,this.drawWaveform(),this.currentTimeEl&&(this.currentTimeEl.textContent="0:00"),this.container.dispatchEvent(new CustomEvent("waveformplayer:ended",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.onPause(),this.options.onEnd&&this.options.onEnd(this))}onError(e){this.isDestroying||(console.error("Audio error:",e),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(e,this))}startSmoothUpdate(){this.stopSmoothUpdate();let e=()=>{this.isPlaying&&this.audio.duration&&(this.updateProgress(),this.updateTimer=requestAnimationFrame(e))};this.updateTimer=requestAnimationFrame(e)}stopSmoothUpdate(){this.updateTimer&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null)}updateProgress(){if(!this.audio.duration)return;let e=this.audio.currentTime/this.audio.duration;Math.abs(e-this.progress)>.001&&(this.progress=e,this.drawWaveform()),this.currentTimeEl&&(this.currentTimeEl.textContent=C(this.audio.currentTime)),this.container.dispatchEvent(new CustomEvent("waveformplayer:timeupdate",{bubbles:!0,detail:{player:this,currentTime:this.audio.currentTime,duration:this.audio.duration,url:this.options.url}})),this.options.onTimeUpdate&&this.options.onTimeUpdate(this.audio.currentTime,this.audio.duration,this)}updateBPMDisplay(){this.bpmEl&&this.bpmValueEl&&this.detectedBPM&&(this.bpmValueEl.textContent=Math.round(this.detectedBPM),this.bpmEl.style.display="inline-flex")}updateSpeedUI(){let e=this.container.querySelector(".speed-value");if(e){let i=this.audio.playbackRate;e.textContent=i===1?"1x":`${i}x`}this.container.querySelectorAll(".speed-option").forEach(i=>{i.classList.toggle("active",parseFloat(i.dataset.rate)===this.audio.playbackRate)})}play(){return this.options.singlePlay&&t.currentlyPlaying&&t.currentlyPlaying!==this&&t.currentlyPlaying.pause(),t.currentlyPlaying=this,this.audio.play()}pause(){t.currentlyPlaying===this&&(t.currentlyPlaying=null),this.audio.pause()}togglePlay(){this.isPlaying?this.pause():this.play()}seekTo(e){this.audio&&this.audio.duration&&(this.audio.currentTime=Math.max(0,Math.min(e,this.audio.duration)),this.updateProgress())}seekToPercent(e){this.audio&&this.audio.duration&&(this.audio.currentTime=this.audio.duration*Math.max(0,Math.min(1,e)),this.updateProgress())}setVolume(e){this.audio&&(this.audio.volume=Math.max(0,Math.min(1,e)))}setPlaybackRate(e){if(!this.audio)return;let i=Math.max(.5,Math.min(2,e));this.audio.playbackRate=i,this.options.playbackRate=i,this.updateSpeedUI()}destroy(){this.isDestroying=!0,this.pause(),this.stopSmoothUpdate(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),t.instances.delete(this.id),t.currentlyPlaying===this&&(t.currentlyPlaying=null),this.audio&&(this.audio.pause(),this.audio.src="",this.audio.load(),this.audio=null),this.container.innerHTML="",this.canvas=null,this.ctx=null,this.playBtn=null,this.waveformData=[]}static getInstance(e){if(typeof e=="string"){let i=this.instances.get(e);if(i)return i;let o=document.getElementById(e);if(o)return Array.from(this.instances.values()).find(s=>s.container===o)}if(e instanceof HTMLElement)return Array.from(this.instances.values()).find(i=>i.container===e)}static getAllInstances(){return Array.from(this.instances.values())}static destroyAll(){this.instances.forEach(e=>e.destroy()),this.instances.clear()}static async generateWaveformData(e,i=200){try{return(await E(e,i)).peaks}catch(o){throw console.error("Failed to generate waveform:",o),o}}};function L(){if(typeof document>"u")return;document.querySelectorAll("[data-waveform-player]").forEach(e=>{if(e.dataset.waveformInitialized!=="true")try{new g(e),e.dataset.waveformInitialized="true"}catch(i){console.error("Failed to initialize WaveformPlayer:",i,e)}})}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",L):L());g.init=L;typeof window<"u"&&(window.WaveformPlayer=g);var lt=g;})();
66
+ `,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.subtitleEl=this.container.querySelector(".waveform-subtitle"),this.artworkEl=this.container.querySelector(".waveform-artwork"),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()}createAudio(){if(this.options.audioMode==="external"){this.audio=null;return}this.audio=new Audio,this.audio.preload=this.options.preload||"metadata",this.audio.crossOrigin="anonymous"}initPlaybackSpeed(){this.audio&&this.options.playbackRate&&this.options.playbackRate!==1&&(this.audio.playbackRate=this.options.playbackRate),this.options.showPlaybackSpeed&&this.initSpeedControls()}initSpeedControls(){let e=this.container.querySelector(".speed-btn"),i=this.container.querySelector(".speed-menu");!e||!i||(e.addEventListener("click",o=>{o.stopPropagation(),i.style.display=i.style.display==="none"?"block":"none"}),document.addEventListener("click",()=>{i.style.display="none"}),i.addEventListener("click",o=>{if(o.stopPropagation(),o.target.classList.contains("speed-option")){let s=parseFloat(o.target.dataset.rate);this.setPlaybackRate(s),i.style.display="none"}}),this.updateSpeedUI())}initKeyboardControls(){this.container.setAttribute("tabindex","-1"),this.container.addEventListener("click",()=>{t.getAllInstances().forEach(e=>{e!==this&&e.container.setAttribute("tabindex","-1")}),this.container.setAttribute("tabindex","0"),this.container.focus()}),this.container.addEventListener("keydown",e=>{if(document.activeElement!==this.container)return;let i=e.key,o=!!this.audio,s=o?this.audio.currentTime:0;if(o&&i>="0"&&i<="9"){e.preventDefault(),this.seekToPercent(parseInt(i)/10);return}let a={" ":()=>this.togglePlay()};o&&(a.ArrowLeft=()=>this.seekTo(Math.max(0,s-5)),a.ArrowRight=()=>this.seekTo(Math.min(this.audio.duration,s+5)),a.ArrowUp=()=>this.setVolume(Math.min(1,this.audio.volume+.1)),a.ArrowDown=()=>this.setVolume(Math.max(0,this.audio.volume-.1)),a.m=a.M=()=>this.audio.muted=!this.audio.muted),a[i]&&(e.preventDefault(),a[i]())})}initMediaSession(){!("mediaSession"in navigator)||!this.options.enableMediaSession||this.audio&&(navigator.mediaSession.metadata=new MediaMetadata({title:this.options.title||"Unknown Track",artist:this.options.subtitle||"",album:this.options.album||"",artwork:this.options.artwork?[{src:this.options.artwork,sizes:"512x512",type:"image/jpeg"}]:[]}),navigator.mediaSession.setActionHandler("play",()=>this.play()),navigator.mediaSession.setActionHandler("pause",()=>this.pause()),navigator.mediaSession.setActionHandler("seekbackward",()=>{this.seekTo(Math.max(0,this.audio.currentTime-10))}),navigator.mediaSession.setActionHandler("seekforward",()=>{this.seekTo(Math.min(this.audio.duration,this.audio.currentTime+10))}),navigator.mediaSession.setActionHandler("seekto",e=>{e.seekTime!==null&&this.seekTo(e.seekTime)}))}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",e=>this.onError(e))),this.canvas.addEventListener("click",e=>this.handleCanvasClick(e)),this.resizeHandler=R(()=>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(e){try{this.setLoading(!0),this.progress=0,this.hasError=!1,this.audio&&(this.audio.src=e,await new Promise((o,s)=>{let a=()=>{this.audio.removeEventListener("loadedmetadata",a),this.audio.removeEventListener("error",n),o()},n=r=>{this.audio.removeEventListener("loadedmetadata",a),this.audio.removeEventListener("error",n),s(r)};this.audio.addEventListener("loadedmetadata",a),this.audio.addEventListener("error",n)}));let i=this.options.title||B(e);if(this.titleEl&&(this.titleEl.textContent=i),this.options.waveform)this.setWaveformData(this.options.waveform);else try{let o=await M(e,this.options.samples,this.options.showBPM);this.waveformData=o.peaks,o.bpm&&(this.detectedBPM=o.bpm,this.updateBPMDisplay())}catch(o){console.warn("Using placeholder waveform:",o),this.waveformData=O(this.options.samples)}this.drawWaveform(),this.renderMarkers(),this.initMediaSession(),this.options.onLoad&&this.options.onLoad(this)}catch(i){console.error("Failed to load audio:",i),this.onError(i)}finally{this.setLoading(!1)}}async loadTrack(e,i=null,o=null,s={}){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=C(this.options,{url:e,title:i||this.options.title,subtitle:o||this.options.subtitle,...s}),s.preload&&this.audio&&(this.audio.preload=s.preload),this.subtitleEl&&(o?(this.subtitleEl.textContent=o,this.subtitleEl.style.display=""):o===""&&(this.subtitleEl.style.display="none")),s.artwork&&this.artworkEl&&(this.artworkEl.src=s.artwork),this.options.markers=s.markers||[],await this.load(e),this.play().catch(()=>{})}setWaveformData(e){if(typeof e=="string"&&e.trim().endsWith(".json")){fetch(e.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}if(typeof e=="string")try{let i=JSON.parse(e);this.waveformData=Array.isArray(i)?i:[]}catch{this.waveformData=e.split(",").map(Number)}else this.waveformData=Array.isArray(e)?e:[];this.drawWaveform()}drawWaveform(){!this.ctx||this.waveformData.length===0||W(this.ctx,this.canvas,this.waveformData,this.progress,{...this.options,waveformStyle:this.options.waveformStyle||"bars",color:this.options.waveformColor,progressColor:this.options.progressColor})}resizeCanvas(){if(!this.canvas||this.isDestroying)return;let e=window.devicePixelRatio||1,i=this.canvas.parentElement.getBoundingClientRect();this.canvas.width=i.width*e,this.canvas.height=this.options.height*e,this.canvas.parentElement.style.height=this.options.height+"px",this.drawWaveform()}renderMarkers(){this.markersContainer&&(this.markersContainer.innerHTML="",!(!this.options.showMarkers||!this.options.markers?.length)&&(!this.audio||!this.audio.duration||this.audio.duration===0||this.options.markers.forEach((e,i)=>{if(e.time>this.audio.duration){console.warn(`Marker "${e.label}" at ${e.time}s exceeds audio duration of ${this.audio.duration}s`);return}let o=e.time/this.audio.duration*100,s=document.createElement("button");s.className="waveform-marker",s.style.left=`${o}%`,s.style.backgroundColor=e.color||"rgba(255, 255, 255, 0.5)",s.setAttribute("aria-label",e.label),s.setAttribute("data-time",e.time);let a=document.createElement("span");a.className="waveform-marker-tooltip",a.textContent=e.label,s.appendChild(a),s.addEventListener("click",n=>{n.stopPropagation(),this.seekTo(e.time),this.options.playOnSeek&&!this.isPlaying&&this.play()}),this.markersContainer.appendChild(s)})))}handleCanvasClick(e){let i=this.canvas.getBoundingClientRect(),o=e.clientX-i.left,s=Math.max(0,Math.min(1,o/i.width));if(this.options.audioMode==="external"){let a=new CustomEvent("waveformplayer:request-seek",{bubbles:!0,cancelable:!0,detail:{...this._buildTrackDetail(),percent:s}});this.container.dispatchEvent(a),a.defaultPrevented||(this.progress=s,this.drawWaveform?.());return}!this.audio||!this.audio.duration||this.seekToPercent(s)}setLoading(e){this.isLoading=e,this.loadingEl&&(this.loadingEl.style.display=e?"block":"none")}onMetadataLoaded(){this.isDestroying||(this.totalTimeEl&&(this.totalTimeEl.textContent=S(this.audio.duration)),this.renderMarkers())}onPlay(){if(!this.isDestroying){if(this.isPlaying=!0,this.playBtn){this.playBtn.classList.add("playing");let e=this.playBtn.querySelector(".waveform-icon-play"),i=this.playBtn.querySelector(".waveform-icon-pause");e&&(e.style.display="none"),i&&(i.style.display="flex")}this.startSmoothUpdate(),this.container.dispatchEvent(new CustomEvent("waveformplayer:play",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPlay&&this.options.onPlay(this)}}onPause(){if(!this.isDestroying){if(this.isPlaying=!1,this.playBtn){this.playBtn.classList.remove("playing");let e=this.playBtn.querySelector(".waveform-icon-play"),i=this.playBtn.querySelector(".waveform-icon-pause");e&&(e.style.display="flex"),i&&(i.style.display="none")}this.stopSmoothUpdate(),this.container.dispatchEvent(new CustomEvent("waveformplayer:pause",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPause&&this.options.onPause(this)}}onEnded(){this.isDestroying||(this.progress=0,this.audio.currentTime=0,this.drawWaveform(),this.currentTimeEl&&(this.currentTimeEl.textContent="0:00"),this.container.dispatchEvent(new CustomEvent("waveformplayer:ended",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.onPause(),this.options.onEnd&&this.options.onEnd(this))}onError(e){this.isDestroying||(console.error("Audio error:",e),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(e,this))}startSmoothUpdate(){this.stopSmoothUpdate();let e=()=>{this.isPlaying&&this.audio&&this.audio.duration&&(this.updateProgress(),this.updateTimer=requestAnimationFrame(e))};this.updateTimer=requestAnimationFrame(e)}stopSmoothUpdate(){this.updateTimer&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null)}updateProgress(){if(!this.audio||!this.audio.duration)return;let e=this.audio.currentTime/this.audio.duration;Math.abs(e-this.progress)>.001&&(this.progress=e,this.drawWaveform()),this.currentTimeEl&&(this.currentTimeEl.textContent=S(this.audio.currentTime)),this.container.dispatchEvent(new CustomEvent("waveformplayer:timeupdate",{bubbles:!0,detail:{player:this,currentTime:this.audio.currentTime,duration:this.audio.duration,url:this.options.url}})),this.options.onTimeUpdate&&this.options.onTimeUpdate(this.audio.currentTime,this.audio.duration,this)}updateBPMDisplay(){this.bpmEl&&this.bpmValueEl&&this.detectedBPM&&(this.bpmValueEl.textContent=Math.round(this.detectedBPM),this.bpmEl.style.display="inline-flex")}updateSpeedUI(){let e=this.container.querySelector(".speed-value");if(e){let i=this.audio.playbackRate;e.textContent=i===1?"1x":`${i}x`}this.container.querySelectorAll(".speed-option").forEach(i=>{i.classList.toggle("active",parseFloat(i.dataset.rate)===this.audio.playbackRate)})}play(){if(this.options.singlePlay&&t.currentlyPlaying&&t.currentlyPlaying!==this&&t.currentlyPlaying.pause(),this.options.audioMode==="external"){let e=new CustomEvent("waveformplayer:request-play",{bubbles:!0,cancelable:!0,detail:this._buildTrackDetail()});this.container.dispatchEvent(e),e.defaultPrevented||(t.currentlyPlaying=this);return}return t.currentlyPlaying=this,this.audio.play()}pause(){if(t.currentlyPlaying===this&&(t.currentlyPlaying=null),this.options.audioMode==="external"){this.container.dispatchEvent(new CustomEvent("waveformplayer:request-pause",{bubbles:!0,cancelable:!0,detail:this._buildTrackDetail()}));return}this.audio.pause()}_buildTrackDetail(){return{url:this.options.url,title:this.options.title,subtitle:this.options.subtitle,artist:this.options.artist,artwork:this.options.artwork,id:this.id,player:this}}setPlayingState(e){let i=this.isPlaying;if(this.isPlaying=!!e,this.playBtn){this.playBtn.classList.toggle("playing",this.isPlaying);let o=this.playBtn.querySelector(".waveform-icon-play"),s=this.playBtn.querySelector(".waveform-icon-pause");o&&(o.style.display=this.isPlaying?"none":"flex"),s&&(s.style.display=this.isPlaying?"flex":"none")}this.isPlaying&&!i?(this.startSmoothUpdate?.(),this.container.dispatchEvent(new CustomEvent("waveformplayer:play",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPlay&&this.options.onPlay(this)):!this.isPlaying&&i&&(this.stopSmoothUpdate?.(),this.container.dispatchEvent(new CustomEvent("waveformplayer:pause",{bubbles:!0,detail:{player:this,url:this.options.url}})),this.options.onPause&&this.options.onPause(this))}setProgress(e,i){!i||i<=0||(this.progress=Math.max(0,Math.min(1,e/i)),this.currentTimeEl&&(this.currentTimeEl.textContent=S(e)),this.totalTimeEl&&(!this.totalTimeEl.dataset._extSet||this._extDuration!==i)&&(this.totalTimeEl.textContent=S(i),this.totalTimeEl.dataset._extSet="1",this._extDuration=i),this.drawWaveform?.(),this.container.dispatchEvent(new CustomEvent("waveformplayer:timeupdate",{bubbles:!0,detail:{player:this,currentTime:e,duration:i,progress:this.progress}})),this.options.onTimeUpdate&&this.options.onTimeUpdate(this,e,i))}togglePlay(){this.isPlaying?this.pause():this.play()}seekTo(e){this.audio&&this.audio.duration&&(this.audio.currentTime=Math.max(0,Math.min(e,this.audio.duration)),this.updateProgress())}seekToPercent(e){this.audio&&this.audio.duration&&(this.audio.currentTime=this.audio.duration*Math.max(0,Math.min(1,e)),this.updateProgress())}setVolume(e){this.audio&&(this.audio.volume=Math.max(0,Math.min(1,e)))}setPlaybackRate(e){if(!this.audio)return;let i=Math.max(.5,Math.min(2,e));this.audio.playbackRate=i,this.options.playbackRate=i,this.updateSpeedUI()}destroy(){this.isDestroying=!0,this.pause(),this.stopSmoothUpdate(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=null),t.instances.delete(this.id),t.currentlyPlaying===this&&(t.currentlyPlaying=null),this.audio&&(this.audio.pause(),this.audio.src="",this.audio.load(),this.audio=null),this.container.innerHTML="",this.canvas=null,this.ctx=null,this.playBtn=null,this.waveformData=[]}static getInstance(e){if(typeof e=="string"){let i=this.instances.get(e);if(i)return i;let o=document.getElementById(e);if(o)return Array.from(this.instances.values()).find(s=>s.container===o)}if(e instanceof HTMLElement)return Array.from(this.instances.values()).find(i=>i.container===e)}static getAllInstances(){return Array.from(this.instances.values())}static destroyAll(){this.instances.forEach(e=>e.destroy()),this.instances.clear()}static async generateWaveformData(e,i=200){try{return(await M(e,i)).peaks}catch(o){throw console.error("Failed to generate waveform:",o),o}}};function L(){if(typeof document>"u")return;document.querySelectorAll("[data-waveform-player]").forEach(e=>{if(e.dataset.waveformInitialized!=="true")try{new g(e),e.dataset.waveformInitialized="true"}catch(i){console.error("Failed to initialize WaveformPlayer:",i,e)}})}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",L):L());g.init=L;typeof window<"u"&&(window.WaveformPlayer=g);var lt=g;})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arraypress/waveform-player",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "description": "Lightweight, customizable audio player with waveform visualization",
5
5
  "main": "dist/waveform-player.js",
6
6
  "module": "dist/waveform-player.esm.js",
package/src/js/core.js CHANGED
@@ -253,8 +253,18 @@ export class WaveformPlayer {
253
253
  /**
254
254
  * Create audio element
255
255
  * @private
256
+ *
257
+ * No-op in `audioMode: 'external'` — the player has no audio of its
258
+ * own; an external controller (e.g. WaveformBar) owns playback and
259
+ * pushes state in via setPlayingState() / setProgress(). The
260
+ * `this.audio` field stays null in that mode; downstream code must
261
+ * null-check it.
256
262
  */
257
263
  createAudio() {
264
+ if (this.options.audioMode === 'external') {
265
+ this.audio = null;
266
+ return;
267
+ }
258
268
  this.audio = new Audio();
259
269
  this.audio.preload = this.options.preload || 'metadata';
260
270
  this.audio.crossOrigin = 'anonymous';
@@ -269,8 +279,12 @@ export class WaveformPlayer {
269
279
  * @private
270
280
  */
271
281
  initPlaybackSpeed() {
272
- // Set initial playback rate if specified
273
- if (this.options.playbackRate && this.options.playbackRate !== 1) {
282
+ // External mode has no <audio> element, so the speed control
283
+ // doesn't apply locally the external controller (e.g.
284
+ // WaveformBar) owns playback rate. Skip the audio init but
285
+ // still bind the speed control UI in case the controller
286
+ // wants to mirror rate changes via events later.
287
+ if (this.audio && this.options.playbackRate && this.options.playbackRate !== 1) {
274
288
  this.audio.playbackRate = this.options.playbackRate;
275
289
  }
276
290
 
@@ -336,30 +350,37 @@ export class WaveformPlayer {
336
350
  this.container.focus();
337
351
  });
338
352
 
339
- // Keyboard events
353
+ // Keyboard events. In external mode `this.audio` is null, so
354
+ // seek/volume/mute keys are no-ops (the external controller
355
+ // owns those). Space (togglePlay) still works because togglePlay
356
+ // routes through the request-play/pause events.
340
357
  this.container.addEventListener('keydown', (e) => {
341
358
  if (document.activeElement !== this.container) return;
342
359
 
343
360
  const key = e.key;
344
- const currentTime = this.audio.currentTime;
361
+ const hasAudio = !!this.audio;
362
+ const currentTime = hasAudio ? this.audio.currentTime : 0;
345
363
 
346
364
  // Handle number keys 0-9 for seeking
347
- if (key >= '0' && key <= '9') {
365
+ if (hasAudio && key >= '0' && key <= '9') {
348
366
  e.preventDefault();
349
367
  this.seekToPercent(parseInt(key) / 10);
350
368
  return;
351
369
  }
352
370
 
353
- // Handle other keys
371
+ // Handle other keys. Space always works (dispatches
372
+ // request-play in external mode); audio-bound keys only
373
+ // when we own the <audio> element.
354
374
  const actions = {
355
375
  ' ': () => this.togglePlay(),
356
- 'ArrowLeft': () => this.seekTo(Math.max(0, currentTime - 5)),
357
- 'ArrowRight': () => this.seekTo(Math.min(this.audio.duration, currentTime + 5)),
358
- 'ArrowUp': () => this.setVolume(Math.min(1, this.audio.volume + 0.1)),
359
- 'ArrowDown': () => this.setVolume(Math.max(0, this.audio.volume - 0.1)),
360
- 'm': () => this.audio.muted = !this.audio.muted,
361
- 'M': () => this.audio.muted = !this.audio.muted
362
376
  };
377
+ if (hasAudio) {
378
+ actions['ArrowLeft'] = () => this.seekTo(Math.max(0, currentTime - 5));
379
+ actions['ArrowRight'] = () => this.seekTo(Math.min(this.audio.duration, currentTime + 5));
380
+ actions['ArrowUp'] = () => this.setVolume(Math.min(1, this.audio.volume + 0.1));
381
+ actions['ArrowDown'] = () => this.setVolume(Math.max(0, this.audio.volume - 0.1));
382
+ actions['m'] = actions['M'] = () => this.audio.muted = !this.audio.muted;
383
+ }
363
384
 
364
385
  if (actions[key]) {
365
386
  e.preventDefault();
@@ -374,6 +395,10 @@ export class WaveformPlayer {
374
395
  */
375
396
  initMediaSession() {
376
397
  if (!('mediaSession' in navigator) || !this.options.enableMediaSession) return;
398
+ // Skip Media Session in external mode — the controller (e.g.
399
+ // WaveformBar) owns audio playback and registers its own Media
400
+ // Session handlers; ours would conflict with its.
401
+ if (!this.audio) return;
377
402
 
378
403
  // Set metadata
379
404
  navigator.mediaSession.metadata = new MediaMetadata({
@@ -410,21 +435,30 @@ export class WaveformPlayer {
410
435
  * @private
411
436
  */
412
437
  bindEvents() {
413
- // Play button (only if controls are shown)
438
+ // Play button (only if controls are shown). In external mode
439
+ // togglePlay() dispatches the request-play/pause events so the
440
+ // controller can decide what to do; the click still goes through
441
+ // here.
414
442
  if (this.playBtn) {
415
443
  this.playBtn.addEventListener('click', () => this.togglePlay());
416
444
  }
417
445
 
418
- // Audio events
419
- this.audio.addEventListener('loadstart', () => this.setLoading(true));
420
- this.audio.addEventListener('loadedmetadata', () => this.onMetadataLoaded());
421
- this.audio.addEventListener('canplay', () => this.setLoading(false));
422
- this.audio.addEventListener('play', () => this.onPlay());
423
- this.audio.addEventListener('pause', () => this.onPause());
424
- this.audio.addEventListener('ended', () => this.onEnded());
425
- this.audio.addEventListener('error', (e) => this.onError(e));
446
+ // Audio events — only when we own an <audio> element. External
447
+ // mode receives state via setPlayingState() / setProgress() from
448
+ // the controller, so we have nothing to listen to here.
449
+ if (this.audio) {
450
+ this.audio.addEventListener('loadstart', () => this.setLoading(true));
451
+ this.audio.addEventListener('loadedmetadata', () => this.onMetadataLoaded());
452
+ this.audio.addEventListener('canplay', () => this.setLoading(false));
453
+ this.audio.addEventListener('play', () => this.onPlay());
454
+ this.audio.addEventListener('pause', () => this.onPause());
455
+ this.audio.addEventListener('ended', () => this.onEnded());
456
+ this.audio.addEventListener('error', (e) => this.onError(e));
457
+ }
426
458
 
427
- // Canvas interactions
459
+ // Canvas interactions — seek-on-click. In external mode the
460
+ // canvas click dispatches a `waveformplayer:request-seek` event
461
+ // so the controller can position its own audio element.
428
462
  this.canvas.addEventListener('click', (e) => this.handleCanvasClick(e));
429
463
 
430
464
  // Window resize - store handler for cleanup
@@ -463,24 +497,31 @@ export class WaveformPlayer {
463
497
  this.progress = 0;
464
498
  this.hasError = false;
465
499
 
466
- // Set audio source
467
- this.audio.src = url;
468
-
469
- // Wait for metadata to load
470
- await new Promise((resolve, reject) => {
471
- const metadataHandler = () => {
472
- this.audio.removeEventListener('loadedmetadata', metadataHandler);
473
- this.audio.removeEventListener('error', errorHandler);
474
- resolve();
475
- };
476
- const errorHandler = (e) => {
477
- this.audio.removeEventListener('loadedmetadata', metadataHandler);
478
- this.audio.removeEventListener('error', errorHandler);
479
- reject(e);
480
- };
481
- this.audio.addEventListener('loadedmetadata', metadataHandler);
482
- this.audio.addEventListener('error', errorHandler);
483
- });
500
+ // In external mode we don't own an <audio> element — skip
501
+ // src assignment + metadata-wait, but still generate the
502
+ // waveform peaks so the canvas can render the visualization.
503
+ // Duration / current time come from the external controller
504
+ // via setProgress().
505
+ if (this.audio) {
506
+ // Set audio source
507
+ this.audio.src = url;
508
+
509
+ // Wait for metadata to load
510
+ await new Promise((resolve, reject) => {
511
+ const metadataHandler = () => {
512
+ this.audio.removeEventListener('loadedmetadata', metadataHandler);
513
+ this.audio.removeEventListener('error', errorHandler);
514
+ resolve();
515
+ };
516
+ const errorHandler = (e) => {
517
+ this.audio.removeEventListener('loadedmetadata', metadataHandler);
518
+ this.audio.removeEventListener('error', errorHandler);
519
+ reject(e);
520
+ };
521
+ this.audio.addEventListener('loadedmetadata', metadataHandler);
522
+ this.audio.addEventListener('error', errorHandler);
523
+ });
524
+ }
484
525
 
485
526
  // Set title
486
527
  const title = this.options.title || extractTitleFromUrl(url);
@@ -538,9 +579,11 @@ export class WaveformPlayer {
538
579
  this.pause();
539
580
  }
540
581
 
541
- // Reset audio element completely
542
- this.audio.src = '';
543
- this.audio.load();
582
+ // Reset audio element completely (only when we own one)
583
+ if (this.audio) {
584
+ this.audio.src = '';
585
+ this.audio.load();
586
+ }
544
587
 
545
588
  // Clear any errors
546
589
  this.hasError = false;
@@ -567,7 +610,7 @@ export class WaveformPlayer {
567
610
  });
568
611
 
569
612
  // Apply preload setting if it was changed
570
- if (options.preload) {
613
+ if (options.preload && this.audio) {
571
614
  this.audio.preload = options.preload;
572
615
  }
573
616
 
@@ -731,12 +774,31 @@ export class WaveformPlayer {
731
774
  * @private
732
775
  */
733
776
  handleCanvasClick(event) {
734
- if (!this.audio.duration) return;
735
-
777
+ // In external mode the player has no audio of its own —
778
+ // dispatch a cancelable `waveformplayer:request-seek` event
779
+ // with the target percentage so the controller can seek its
780
+ // own audio. Locally we just update the visual progress so
781
+ // the canvas paints the new position immediately (the
782
+ // controller's progress event will reconcile shortly after).
736
783
  const rect = this.canvas.getBoundingClientRect();
737
784
  const x = event.clientX - rect.left;
738
785
  const targetPercent = Math.max(0, Math.min(1, x / rect.width));
739
786
 
787
+ if (this.options.audioMode === 'external') {
788
+ const evt = new CustomEvent('waveformplayer:request-seek', {
789
+ bubbles: true,
790
+ cancelable: true,
791
+ detail: { ...this._buildTrackDetail(), percent: targetPercent }
792
+ });
793
+ this.container.dispatchEvent(evt);
794
+ if (!evt.defaultPrevented) {
795
+ this.progress = targetPercent;
796
+ this.drawWaveform?.();
797
+ }
798
+ return;
799
+ }
800
+
801
+ if (!this.audio || !this.audio.duration) return;
740
802
  this.seekToPercent(targetPercent);
741
803
  }
742
804
 
@@ -901,7 +963,10 @@ export class WaveformPlayer {
901
963
  this.stopSmoothUpdate();
902
964
 
903
965
  const update = () => {
904
- if (this.isPlaying && this.audio.duration) {
966
+ // In external mode the canvas redraws are driven by
967
+ // setProgress() pushes from the controller — no internal
968
+ // RAF needed. Self-mode keeps the smooth-update loop.
969
+ if (this.isPlaying && this.audio && this.audio.duration) {
905
970
  this.updateProgress();
906
971
  this.updateTimer = requestAnimationFrame(update);
907
972
  }
@@ -926,7 +991,9 @@ export class WaveformPlayer {
926
991
  * @private
927
992
  */
928
993
  updateProgress() {
929
- if (!this.audio.duration) return;
994
+ // Self-mode only — external mode receives progress via
995
+ // setProgress() from the controller and never calls this.
996
+ if (!this.audio || !this.audio.duration) return;
930
997
 
931
998
  const newProgress = this.audio.currentTime / this.audio.duration;
932
999
 
@@ -992,8 +1059,20 @@ export class WaveformPlayer {
992
1059
  // ============================================
993
1060
 
994
1061
  /**
995
- * Play audio
996
- * @return {Promise} The promise returned by HTMLMediaElement.play()
1062
+ * Play audio.
1063
+ *
1064
+ * In `audioMode: 'self'` (default): calls the underlying <audio>
1065
+ * element's play(). Returns the promise from HTMLMediaElement.play().
1066
+ *
1067
+ * In `audioMode: 'external'`: dispatches a cancelable
1068
+ * `waveformplayer:request-play` event with the track metadata and
1069
+ * does NOT touch any audio element. Returns `undefined`. An external
1070
+ * controller (e.g. WaveformBar) listens for this event and starts
1071
+ * playback on its own audio source, then pushes state back via
1072
+ * setPlayingState() / setProgress(). Calling preventDefault() on
1073
+ * the event lets the controller veto the play (state is unchanged).
1074
+ *
1075
+ * @return {Promise|undefined}
997
1076
  */
998
1077
  play() {
999
1078
  if (this.options.singlePlay && WaveformPlayer.currentlyPlaying &&
@@ -1001,20 +1080,128 @@ export class WaveformPlayer {
1001
1080
  WaveformPlayer.currentlyPlaying.pause();
1002
1081
  }
1003
1082
 
1083
+ if (this.options.audioMode === 'external') {
1084
+ const evt = new CustomEvent('waveformplayer:request-play', {
1085
+ bubbles: true,
1086
+ cancelable: true,
1087
+ detail: this._buildTrackDetail()
1088
+ });
1089
+ this.container.dispatchEvent(evt);
1090
+ // If the controller cancels (preventDefault), don't claim
1091
+ // "currentlyPlaying" — the controller didn't accept the play.
1092
+ if (!evt.defaultPrevented) {
1093
+ WaveformPlayer.currentlyPlaying = this;
1094
+ }
1095
+ return undefined;
1096
+ }
1097
+
1004
1098
  WaveformPlayer.currentlyPlaying = this;
1005
1099
  return this.audio.play();
1006
1100
  }
1007
1101
 
1008
1102
  /**
1009
- * Pause audio
1103
+ * Pause audio.
1104
+ *
1105
+ * In `audioMode: 'external'`, dispatches `waveformplayer:request-pause`
1106
+ * (cancelable) and does NOT touch any audio element. See play().
1010
1107
  */
1011
1108
  pause() {
1012
1109
  if (WaveformPlayer.currentlyPlaying === this) {
1013
1110
  WaveformPlayer.currentlyPlaying = null;
1014
1111
  }
1112
+ if (this.options.audioMode === 'external') {
1113
+ this.container.dispatchEvent(new CustomEvent('waveformplayer:request-pause', {
1114
+ bubbles: true,
1115
+ cancelable: true,
1116
+ detail: this._buildTrackDetail()
1117
+ }));
1118
+ return;
1119
+ }
1015
1120
  this.audio.pause();
1016
1121
  }
1017
1122
 
1123
+ /**
1124
+ * Build the track detail object dispatched by request-play /
1125
+ * request-pause events in external audio mode. Mirrors the shape
1126
+ * WaveformBar.play() accepts so a controller can forward it
1127
+ * directly: `WaveformBar.play(event.detail)`.
1128
+ *
1129
+ * @private
1130
+ * @return {{url:string,title:?string,subtitle:?string,artist:?string,artwork:?string,player:WaveformPlayer}}
1131
+ */
1132
+ _buildTrackDetail() {
1133
+ return {
1134
+ url: this.options.url,
1135
+ title: this.options.title,
1136
+ subtitle: this.options.subtitle,
1137
+ artist: this.options.artist,
1138
+ artwork: this.options.artwork,
1139
+ id: this.id,
1140
+ player: this
1141
+ };
1142
+ }
1143
+
1144
+ /**
1145
+ * External-mode state pump: flip the play/pause visual state without
1146
+ * touching audio. Mirrors what onPlay()/onPause() do but skips the
1147
+ * audio-element interactions. Safe to call repeatedly — idempotent.
1148
+ *
1149
+ * @param {boolean} playing
1150
+ */
1151
+ setPlayingState(playing) {
1152
+ const wasPlaying = this.isPlaying;
1153
+ this.isPlaying = !!playing;
1154
+ if (this.playBtn) {
1155
+ this.playBtn.classList.toggle('playing', this.isPlaying);
1156
+ const playIcon = this.playBtn.querySelector('.waveform-icon-play');
1157
+ const pauseIcon = this.playBtn.querySelector('.waveform-icon-pause');
1158
+ if (playIcon) playIcon.style.display = this.isPlaying ? 'none' : 'flex';
1159
+ if (pauseIcon) pauseIcon.style.display = this.isPlaying ? 'flex' : 'none';
1160
+ }
1161
+ if (this.isPlaying && !wasPlaying) {
1162
+ this.startSmoothUpdate?.();
1163
+ this.container.dispatchEvent(new CustomEvent('waveformplayer:play', {
1164
+ bubbles: true,
1165
+ detail: {player: this, url: this.options.url}
1166
+ }));
1167
+ if (this.options.onPlay) this.options.onPlay(this);
1168
+ } else if (!this.isPlaying && wasPlaying) {
1169
+ this.stopSmoothUpdate?.();
1170
+ this.container.dispatchEvent(new CustomEvent('waveformplayer:pause', {
1171
+ bubbles: true,
1172
+ detail: {player: this, url: this.options.url}
1173
+ }));
1174
+ if (this.options.onPause) this.options.onPause(this);
1175
+ }
1176
+ }
1177
+
1178
+ /**
1179
+ * External-mode state pump: update the visualization's progress
1180
+ * from an external clock (e.g. WaveformBar's audio element's
1181
+ * timeupdate). Drives the canvas redraw + the time displays.
1182
+ *
1183
+ * @param {number} currentTime Current playback position in seconds.
1184
+ * @param {number} duration Total track duration in seconds.
1185
+ */
1186
+ setProgress(currentTime, duration) {
1187
+ if (!duration || duration <= 0) return;
1188
+ this.progress = Math.max(0, Math.min(1, currentTime / duration));
1189
+ // Mirror the existing display update code so callers don't have
1190
+ // to know which DOM elements live where.
1191
+ if (this.currentTimeEl) this.currentTimeEl.textContent = formatTime(currentTime);
1192
+ if (this.totalTimeEl && (!this.totalTimeEl.dataset._extSet || this._extDuration !== duration)) {
1193
+ this.totalTimeEl.textContent = formatTime(duration);
1194
+ this.totalTimeEl.dataset._extSet = '1';
1195
+ this._extDuration = duration;
1196
+ }
1197
+ this.drawWaveform?.();
1198
+ this.container.dispatchEvent(new CustomEvent('waveformplayer:timeupdate', {
1199
+ bubbles: true,
1200
+ detail: {player: this, currentTime, duration, progress: this.progress}
1201
+ }));
1202
+ if (this.options.onTimeUpdate) this.options.onTimeUpdate(this, currentTime, duration);
1203
+ }
1204
+
1018
1205
  /**
1019
1206
  * Toggle play/pause
1020
1207
  */
package/src/js/themes.js CHANGED
@@ -125,6 +125,13 @@ export const DEFAULT_OPTIONS = {
125
125
  samples: 200,
126
126
  preload: 'metadata',
127
127
 
128
+ // Audio mode — 'self' = player owns the <audio> element (default, current
129
+ // behavior). 'external' = player is a visualization-only surface; no audio
130
+ // element is created, play() dispatches `waveformplayer:request-play`
131
+ // instead of calling audio.play(), and setPlayingState/setProgress are
132
+ // expected to be driven by an external controller (e.g. WaveformBar).
133
+ audioMode: 'self',
134
+
128
135
  // Playback
129
136
  playbackRate: 1,
130
137
  showPlaybackSpeed: false,