waveframe 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
1
  "use client";
2
- 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var A=class{audio;listeners=new Set;_state;constructor(){this.audio=new Audio,this._state={isPlaying:false,currentTime:0,duration:0,volume:1,muted:false},this.initListeners();}initListeners(){this.audio.addEventListener("play",()=>this.updateState({isPlaying:true})),this.audio.addEventListener("pause",()=>this.updateState({isPlaying:false})),this.audio.addEventListener("timeupdate",()=>this.updateState({currentTime:this.audio.currentTime})),this.audio.addEventListener("durationchange",()=>this.updateState({duration:this.audio.duration})),this.audio.addEventListener("volumechange",()=>this.updateState({volume:this.audio.volume,muted:this.audio.muted})),this.audio.addEventListener("ended",()=>this.updateState({isPlaying:false}));}updateState(t){this._state={...this._state,...t},this.notify();}notify(){this.listeners.forEach(t=>t(this._state));}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}get state(){return this._state}setSource(t){this.audio.src=t,this.audio.load();}play(){return this.audio.play()}pause(){this.audio.pause();}togglePlay(){this._state.isPlaying?this.pause():this.play();}seek(t){this.audio.currentTime=t;}setVolume(t){this.audio.volume=t;}setMuted(t){this.audio.muted=t;}dispose(){this.pause(),this.audio.src="",this.listeners.clear();}};var C=class{audioCtx=null;constructor(){}getContext(){return this.audioCtx||(this.audioCtx=new(window.AudioContext||window.webkitAudioContext)),this.audioCtx}async generatePeaks(t,e=512){try{let o;if(typeof t=="string"){let c=await fetch(t);if(!c.ok)throw new Error(`Failed to fetch audio: ${c.statusText}`);o=await c.arrayBuffer();}else o=await t.arrayBuffer();let n=(await this.getContext().decodeAudioData(o)).getChannelData(0),s=Math.floor(n.length/e),l=[];for(let c=0;c<e;c++){let u=0,g=c*s,d=g+s;for(let y=g;y<d;y++){let S=Math.abs(n[y]);S>u&&(u=S);}l.push(u);}let x=Math.max(...l);return l.map(c=>c/(x||1))}catch(o){throw console.error("PeakAnalyzer Error:",o),o}}dispose(){this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null);}};var W=class{player;analyzer;listeners=new Set;_state;_media=null;_objectUrl=null;constructor(){this.player=new A,this.analyzer=new C,this._state={...this.player.state,peaks:[],isAnalyzing:false,error:null},this.player.subscribe(t=>{this.updateState({...t});});}updateState(t){this._state={...this._state,...t},this.notify();}notify(){this.listeners.forEach(t=>t(this._state));}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}getSnapshot(){return this._state}revokeOldSource(){this._objectUrl&&(URL.revokeObjectURL(this._objectUrl),this._objectUrl=null);}load(t,e){if(this._media!==t){this.revokeOldSource(),this._media=t;let i;typeof t=="string"?i=t:(this._objectUrl=URL.createObjectURL(t),i=this._objectUrl),this.player.setSource(i);let a=e&&e.length>0;this.updateState({peaks:e||[],isAnalyzing:false,error:null}),a||this.analyze();}else e&&e!==this._state.peaks&&this.updateState({peaks:e});}async analyze(t=512){if(!this._media){this.updateState({error:"No media loaded to analyze"});return}this.updateState({isAnalyzing:true,error:null});try{let e=await this.analyzer.generatePeaks(this._media,t);this.updateState({peaks:e,isAnalyzing:!1});}catch(e){this.updateState({isAnalyzing:false,error:e instanceof Error?e.message:"Analysis failed"});}}togglePlay(){this.player.togglePlay();}play(){this.player.play();}pause(){this.player.pause();}seek(t){let{duration:e}=this._state;e&&this.player.seek(t*e);}setVolume(t){this.player.setVolume(t);}setMuted(t){this.player.setMuted(t);}dispose(){this.revokeOldSource(),this.player.dispose(),this.analyzer.dispose(),this.listeners.clear();}};var q=r=>react.useSyncExternalStore(t=>r.subscribe(t),()=>r.getSnapshot());var Q=(r,t={})=>{let{peaks:e,engine:o}=t,i=react.useMemo(()=>o||new W,[o]),a=o||i,n=q(a);return react.useEffect(()=>{r&&a.load(r,e);},[a,r,e]),react.useEffect(()=>()=>{o||i.dispose();},[i,o]),{state:n,engine:a,togglePlay:()=>a.togglePlay(),play:()=>a.play(),pause:()=>a.pause(),seek:s=>a.seek(s),setVolume:s=>a.setVolume(s),setMuted:s=>a.setMuted(s),analyze:s=>a.analyze(s)}};var Nt=async(r,t=512)=>{let e=new C;try{return await e.generatePeaks(r,t)}finally{e.dispose();}},jt=async r=>{let e=await(await fetch(r)).blob();return URL.createObjectURL(e)},At=r=>{r&&r.startsWith("blob:")&&URL.revokeObjectURL(r);};var V=r=>{if(isNaN(r))return "0:00";let t=Math.floor(r/60),e=Math.floor(r%60);return `${t}:${e.toString().padStart(2,"0")}`},Y=(r,t)=>{if(r.length===0)return [];if(r.length===t)return r;let e=new Array(t),o=r.length/t;if(o>1)for(let i=0;i<t;i++){let a=0,n=Math.floor(i*o),s=Math.floor((i+1)*o);for(let l=n;l<s;l++)r[l]>a&&(a=r[l]);e[i]=a;}else for(let i=0;i<t;i++){let a=i*o,n=Math.floor(a),s=Math.min(n+1,r.length-1),l=a-n;e[i]=r[n]+(r[s]-r[n])*l;}return e},Tt=r=>r.split(`
3
- `).map(t=>{let e=t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),o={},i=0,a=(n,s)=>{let l=`__TOKEN_${i++}__`;return o[l]=`<span class="${s}">${n}</span>`,l};return e=e.replace(/("(?:[^"\\]|\\.)*")/g,n=>a(n,"text-[#ce9178]")),e=e.replace(/\b(\d+(\.\d+)?)\b/g,n=>a(n,"text-[#b5cea8]")),e=e.replace(/\b(WaveframePlayer)\b/g,n=>a(n,"text-[#4ec9b0]")),e=e.replace(/\b([a-z][a-zA-Z0-9]+)(?==)/g,n=>a(n,"text-[#9cdcfe]")),e=e.replace(/(&lt;|&gt;|\{|\}|\/|:|,)/g,'<span class="text-gray-500">$1</span>'),Object.entries(o).forEach(([n,s])=>{e=e.replace(n,s);}),e});var tt=(r,t)=>react.useMemo(()=>Y(r,t),[r,t]);var T=r=>{let[t,e]=react.useState(0);return react.useEffect(()=>{if(!r.current)return;let o=new ResizeObserver(i=>{for(let a of i)e(a.contentRect.width);});return o.observe(r.current),()=>o.disconnect()},[r]),t};var D=react.memo(({artworkUrl:r,title:t,isLoading:e})=>jsxRuntime.jsxs("div",{className:"relative flex-shrink-0 w-32 h-32 md:w-40 md:h-40 overflow-hidden rounded-[var(--wf-artwork-rounded,0.75rem)] shadow-lg group/artwork",children:[jsxRuntime.jsx("div",{className:`w-full h-full transition-all duration-700 ${e?"blur-md scale-110":""}`,children:r?jsxRuntime.jsx("img",{src:r,alt:t,className:"w-full h-full object-cover transition-transform duration-500 group-hover/artwork:scale-110"}):jsxRuntime.jsx("div",{className:"w-full h-full bg-gradient-to-br from-[var(--wf-placeholder-from,#fb923c)] to-[var(--wf-placeholder-to,#ec4899)] flex items-center justify-center",children:jsxRuntime.jsx("svg",{className:"w-16 h-16 text-white opacity-50",fill:"currentColor",viewBox:"0 0 24 24",children:jsxRuntime.jsx("path",{d:"M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"})})})}),e&&jsxRuntime.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-black/10 backdrop-blur-[1px]",children:jsxRuntime.jsx("div",{className:"w-8 h-8 border-4 border-white/30 border-t-white rounded-full animate-spin"})})]}));D.displayName="ArtworkOverlay";var G=react.memo(({peaks:r,currentTime:t,duration:e,waveColor:o,progressColor:i,height:a,onSeek:n,resolution:s="auto",barWidth:l=2,barGap:x=1})=>{let c=react.useRef(null),u=react.useRef(null),g=react.useRef(null),d=T(g);react.useEffect(()=>{let b=c.current,f=u.current;if(!b||!f)return;let h=b.getContext("2d"),p=f.getContext("2d");if(!h||!p)return;let k=window.devicePixelRatio||1,P=b.getBoundingClientRect(),_=P.width*k,L=P.height*k;[b,f].forEach(w=>{(w.width!==_||w.height!==L)&&(w.width=_,w.height=L);}),(()=>{if(r.length===0)return;let{width:w,height:m}=b;h.clearRect(0,0,w,m),p.clearRect(0,0,w,m);let O=r.length,N=w/O,E=typeof s=="number"?N*.7:l*k,B=typeof s=="number"?N*.3:x*k;h.lineCap="round",h.lineWidth=E,p.lineCap="round",p.lineWidth=E,r.forEach(($,R)=>{let j=R*(E+B)+E/2,X=$*m*.8,H=(m-X)/2,Z=H+X;h.beginPath(),h.strokeStyle=o,h.moveTo(j,H),h.lineTo(j,Z),h.stroke(),p.beginPath(),p.strokeStyle=i,p.moveTo(j,H),p.lineTo(j,Z),p.stroke();});})();},[r,o,i,s,l,x,a]);let y=b=>{if(g.current&&e){let f=g.current.getBoundingClientRect(),h=b.clientX-f.left,p=Math.max(0,Math.min(1,h/f.width));n(p);}},S=e?t/e*100:0;return jsxRuntime.jsxs("div",{ref:g,className:"relative w-full cursor-pointer overflow-hidden",style:{height:`${a}px`},onClick:y,children:[jsxRuntime.jsx("canvas",{ref:c,className:"absolute inset-0 w-full h-full"}),jsxRuntime.jsx("div",{className:"absolute inset-0 h-full overflow-hidden transition-[width] duration-100 ease-linear pointer-events-none",style:{width:`${S}%`},children:jsxRuntime.jsx("canvas",{ref:u,className:"absolute h-full",style:{width:`${d}px`}})})]})});G.displayName="Waveform";var ut=react.memo(({media:r,peaks:t,artwork:e,title:o,artist:i,waveColor:a,progressColor:n,height:s=80,className:l="",style:x,resolution:c="auto",barWidth:u=2,barGap:g=1,theme:d,engine:y})=>{let{state:S,togglePlay:b,seek:f}=Q(r,{peaks:t,engine:y}),{isPlaying:h,currentTime:p,duration:k,peaks:P,isAnalyzing:_}=S,[L,U]=react.useState(typeof e=="string"?e:void 0);react.useEffect(()=>{if(e instanceof Blob){let R=URL.createObjectURL(e);return U(R),()=>URL.revokeObjectURL(R)}else U(e);},[e]);let w=react.useRef(null),m=T(w),O=react.useMemo(()=>typeof c=="number"?c:m>0?Math.max(1,Math.floor(m/(u+g))):P.length||1,[c,m,u,g,P.length]),N=tt(P,O),E=react.useMemo(()=>a||(d?d.bg==="#ffffff"?"#e5e7eb":"#374151":"#e5e7eb"),[a,d]),B=n||d?.primary||"#3b82f6",$=react.useMemo(()=>({...{"--wf-bg-color":d?.bg||"white","--wf-border-color":d?.border||"#f3f4f6","--wf-title-color":d?.text||"#111827","--wf-artist-color":d?.text||"#6b7280","--wf-time-color":d?.text||"#9ca3af","--wf-play-btn-bg":d?.primary||"#3b82f6","--wf-placeholder-from":d?.primary||"#fb923c","--wf-placeholder-to":d?.bg||"#ec4899"},...x}),[d,x]);return jsxRuntime.jsxs("div",{className:`group relative flex flex-col md:flex-row items-stretch gap-6 p-6 bg-[var(--wf-bg-color,white)] border border-[var(--wf-border-color,#f3f4f6)] rounded-[var(--wf-rounded,1rem)] shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden ${l}`,style:$,children:[jsxRuntime.jsx(D,{artworkUrl:L,title:o,isLoading:_}),jsxRuntime.jsxs("div",{className:"flex-1 w-full flex flex-col min-w-0",children:[jsxRuntime.jsxs("div",{className:"flex items-center gap-4 mb-6",children:[jsxRuntime.jsx("button",{onClick:b,className:"w-12 h-12 md:w-14 md:h-14 flex-shrink-0 flex items-center justify-center rounded-full bg-[var(--wf-play-btn-bg,#3b82f6)] text-white shadow-[0_4px_12px_rgba(0,0,0,0.15)] hover:shadow-[0_6px_16px_rgba(0,0,0,0.2)] transition-all hover:scale-105 active:scale-95 cursor-pointer border-none outline-none group/play",children:h?jsxRuntime.jsx("svg",{className:"w-6 h-6 md:w-7 md:h-7",fill:"currentColor",viewBox:"0 0 24 24",children:jsxRuntime.jsx("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"})}):jsxRuntime.jsx("svg",{className:"w-6 h-6 md:w-7 md:h-7 ml-1",fill:"currentColor",viewBox:"0 0 24 24",children:jsxRuntime.jsx("path",{d:"M8 5v14l11-7z"})})}),jsxRuntime.jsxs("div",{className:"flex-1 flex flex-col min-w-0",children:[jsxRuntime.jsxs("div",{className:"flex items-center justify-between gap-4",children:[i&&jsxRuntime.jsx("p",{className:"text-[10px] md:text-xs font-bold uppercase text-[var(--wf-artist-color,#6b7280)] opacity-60 tracking-[0.1em] line-clamp-1",children:i}),jsxRuntime.jsxs("div",{className:"text-[10px] font-mono text-[var(--wf-time-color,#9ca3af)] tabular-nums flex-shrink-0",children:[V(p)," / ",V(k)]})]}),o&&jsxRuntime.jsx("h3",{className:"text-lg md:text-xl font-black text-[var(--wf-title-color,#111827)] tracking-tight line-clamp-1 mt-0.5 leading-tight",children:o})]})]}),jsxRuntime.jsx("div",{className:"mt-auto",ref:w,children:jsxRuntime.jsx(G,{peaks:N,currentTime:p,duration:k,waveColor:E,progressColor:B,height:s,onSeek:f,resolution:c,barWidth:u,barGap:g})})]})]})});ut.displayName="WaveframePlayer";exports.PeakAnalyzer=C;exports.PlayerCore=A;exports.WaveframeEngine=W;exports.WaveframePlayer=ut;exports.formatTime=V;exports.generatePeaks=Nt;exports.highlightCode=Tt;exports.loadAudioToMemory=jt;exports.resamplePeaks=Y;exports.revokeAudioMemory=At;exports.useWaveframeStore=q;//# sourceMappingURL=index.cjs.map
2
+ 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var T=class{audio;listeners=new Set;_state;constructor(){this.audio=new Audio,this._state={isPlaying:false,currentTime:0,duration:0,volume:1,muted:false,error:null},this.initListeners();}initListeners(){this.audio.addEventListener("play",()=>this.updateState({isPlaying:true,error:null})),this.audio.addEventListener("pause",()=>this.updateState({isPlaying:false})),this.audio.addEventListener("timeupdate",()=>this.updateState({currentTime:this.audio.currentTime})),this.audio.addEventListener("durationchange",()=>this.updateState({duration:this.audio.duration})),this.audio.addEventListener("volumechange",()=>this.updateState({volume:this.audio.volume,muted:this.audio.muted})),this.audio.addEventListener("ended",()=>this.updateState({isPlaying:false})),this.audio.addEventListener("error",()=>{let t=this.audio.error,e="Unknown audio error";if(t)switch(t.code){case t.MEDIA_ERR_ABORTED:e="Playback aborted";break;case t.MEDIA_ERR_NETWORK:e="Network error";break;case t.MEDIA_ERR_DECODE:e="Audio decoding failed";break;case t.MEDIA_ERR_SRC_NOT_SUPPORTED:e="Audio format not supported";break}this.updateState({isPlaying:false,error:e});});}updateState(t){this._state={...this._state,...t},this.notify();}notify(){this.listeners.forEach(t=>t(this._state));}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}get state(){return this._state}setSource(t){this.audio.src=t,this.audio.load(),this.updateState({error:null,currentTime:0,duration:0});}async play(){try{await this.audio.play();}catch(t){let e=t instanceof Error?t.message:"Playback failed";throw this.updateState({isPlaying:false,error:e}),t}}pause(){this.audio.pause();}async togglePlay(){if(this._state.isPlaying)this.pause();else try{await this.play();}catch{}}seek(t){this.audio.currentTime=t;}setVolume(t){this.audio.volume=t;}setMuted(t){this.audio.muted=t;}dispose(){this.pause(),this.audio.src="",this.listeners.clear();}};var R=class{audioCtx=null;constructor(){}getContext(){return this.audioCtx||(this.audioCtx=new(window.AudioContext||window.webkitAudioContext)),this.audioCtx}async generatePeaks(t,e=512){try{let o;if(typeof t=="string"){let c=await fetch(t);if(!c.ok)throw new Error(`Failed to fetch audio: ${c.statusText}`);o=await c.arrayBuffer();}else o=await t.arrayBuffer();let n=(await this.getContext().decodeAudioData(o)).getChannelData(0),s=Math.floor(n.length/e),l=[];for(let c=0;c<e;c++){let u=0,g=c*s,d=g+s;for(let y=g;y<d;y++){let S=Math.abs(n[y]);S>u&&(u=S);}l.push(u);}let x=Math.max(...l);return l.map(c=>c/(x||1))}catch(o){throw console.error("PeakAnalyzer Error:",o),o}}dispose(){this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null);}};var W=class{player;analyzer;listeners=new Set;_state;_media=null;_objectUrl=null;constructor(){this.player=new T,this.analyzer=new R,this._state={...this.player.state,peaks:[],isAnalyzing:false,error:null},this.player.subscribe(t=>{this.updateState({...t});});}updateState(t){this._state={...this._state,...t},this.notify();}notify(){this.listeners.forEach(t=>t(this._state));}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}getSnapshot(){return this._state}revokeOldSource(){this._objectUrl&&(URL.revokeObjectURL(this._objectUrl),this._objectUrl=null);}load(t,e){if(this._media!==t){this.revokeOldSource(),this._media=t;let i;typeof t=="string"?i=t:(this._objectUrl=URL.createObjectURL(t),i=this._objectUrl),this.player.setSource(i);let a=e&&e.length>0;this.updateState({peaks:e||[],isAnalyzing:false,error:null}),a||this.analyze();}else e&&e.length!==this._state.peaks.length&&this.updateState({peaks:e});}async analyze(t=512){if(!this._media){this.updateState({error:"No media loaded to analyze"});return}this.updateState({isAnalyzing:true,error:null});try{let e=await this.analyzer.generatePeaks(this._media,t);this.updateState({peaks:e,isAnalyzing:!1});}catch(e){this.updateState({isAnalyzing:false,error:e instanceof Error?e.message:"Analysis failed"});}}togglePlay(){this.player.togglePlay();}play(){this.player.play();}pause(){this.player.pause();}seek(t){let{duration:e}=this._state;e&&this.player.seek(t*e);}setVolume(t){this.player.setVolume(t);}setMuted(t){this.player.setMuted(t);}dispose(){this.revokeOldSource(),this.player.dispose(),this.analyzer.dispose(),this.listeners.clear();}};var q=r=>react.useSyncExternalStore(t=>r.subscribe(t),()=>r.getSnapshot());var Q=(r,t={})=>{let{peaks:e,engine:o}=t,i=react.useMemo(()=>o||new W,[o]),a=o||i,n=q(a);return react.useEffect(()=>{r&&a.load(r,e);},[a,r,e]),react.useEffect(()=>()=>{o||i.dispose();},[i,o]),{state:n,engine:a,togglePlay:()=>a.togglePlay(),play:()=>a.play(),pause:()=>a.pause(),seek:s=>a.seek(s),setVolume:s=>a.setVolume(s),setMuted:s=>a.setMuted(s),analyze:s=>a.analyze(s)}};var At=async(r,t=512)=>{let e=new R;try{return await e.generatePeaks(r,t)}finally{e.dispose();}},Lt=async r=>{let e=await(await fetch(r)).blob();return URL.createObjectURL(e)},jt=r=>{r&&r.startsWith("blob:")&&URL.revokeObjectURL(r);};var H=r=>{if(isNaN(r))return "0:00";let t=Math.floor(r/60),e=Math.floor(r%60);return `${t}:${e.toString().padStart(2,"0")}`},Y=(r,t)=>{if(r.length===0)return [];if(r.length===t)return r;let e=new Array(t),o=r.length/t;if(o>1)for(let i=0;i<t;i++){let a=0,n=Math.floor(i*o),s=Math.floor((i+1)*o);for(let l=n;l<s;l++)r[l]>a&&(a=r[l]);e[i]=a;}else for(let i=0;i<t;i++){let a=i*o,n=Math.floor(a),s=Math.min(n+1,r.length-1),l=a-n;e[i]=r[n]+(r[s]-r[n])*l;}return e},Wt=r=>r.split(`
3
+ `).map(t=>{let e=t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),o={},i=0,a=(n,s)=>{let l=`__TOKEN_${i++}__`;return o[l]=`<span class="${s}">${n}</span>`,l};return e=e.replace(/("(?:[^"\\]|\\.)*")/g,n=>a(n,"text-[#ce9178]")),e=e.replace(/\b(\d+(\.\d+)?)\b/g,n=>a(n,"text-[#b5cea8]")),e=e.replace(/\b(WaveframePlayer)\b/g,n=>a(n,"text-[#4ec9b0]")),e=e.replace(/\b([a-z][a-zA-Z0-9]+)(?==)/g,n=>a(n,"text-[#9cdcfe]")),e=e.replace(/(&lt;|&gt;|\{|\}|\/|:|,)/g,'<span class="text-gray-500">$1</span>'),Object.entries(o).forEach(([n,s])=>{e=e.replace(n,s);}),e});var tt=(r,t)=>react.useMemo(()=>Y(r,t),[r,t]);var U=r=>{let[t,e]=react.useState(0);return react.useEffect(()=>{if(!r.current)return;let o=new ResizeObserver(i=>{for(let a of i)e(a.contentRect.width);});return o.observe(r.current),()=>o.disconnect()},[r]),t};var I=react.memo(({artworkUrl:r,title:t,isLoading:e})=>jsxRuntime.jsxs("div",{className:"relative flex-shrink-0 w-32 h-32 md:w-40 md:h-40 overflow-hidden rounded-[var(--wf-artwork-rounded,0.75rem)] shadow-lg group/artwork",children:[jsxRuntime.jsx("div",{className:`w-full h-full transition-all duration-700 ${e?"blur-md scale-110":""}`,children:r?jsxRuntime.jsx("img",{src:r,alt:t,className:"w-full h-full object-cover transition-transform duration-500 group-hover/artwork:scale-110"}):jsxRuntime.jsx("div",{className:"w-full h-full bg-gradient-to-br from-[var(--wf-placeholder-from,#fb923c)] to-[var(--wf-placeholder-to,#ec4899)] flex items-center justify-center",children:jsxRuntime.jsx("svg",{className:"w-16 h-16 text-white opacity-50",fill:"currentColor",viewBox:"0 0 24 24",children:jsxRuntime.jsx("path",{d:"M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"})})})}),e&&jsxRuntime.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-black/10 backdrop-blur-[1px]",children:jsxRuntime.jsx("div",{className:"w-8 h-8 border-4 border-white/30 border-t-white rounded-full animate-spin"})})]}));I.displayName="ArtworkOverlay";var K=react.memo(({peaks:r,currentTime:t,duration:e,waveColor:o,progressColor:i,height:a,onSeek:n,resolution:s="auto",barWidth:l=2,barGap:x=1})=>{let c=react.useRef(null),u=react.useRef(null),g=react.useRef(null),d=U(g);react.useEffect(()=>{let f=c.current,b=u.current;if(!f||!b)return;let h=f.getContext("2d"),p=b.getContext("2d");if(!h||!p)return;let k=window.devicePixelRatio||1,E=f.getBoundingClientRect(),M=E.width*k,N=E.height*k;[f,b].forEach(w=>{(w.width!==M||w.height!==N)&&(w.width=M,w.height=N);}),(()=>{if(r.length===0)return;let{width:w,height:m}=f;h.clearRect(0,0,w,m),p.clearRect(0,0,w,m);let B=r.length,A=w/B,P=typeof s=="number"?A*.7:l*k,D=typeof s=="number"?A*.3:x*k;h.lineCap="round",h.lineWidth=P,p.lineCap="round",p.lineWidth=P,r.forEach((L,C)=>{if(L<=0)return;let j=C*(P+D)+P/2,X=L*m*.8,$=(m-X)/2,Z=$+X;h.beginPath(),h.strokeStyle=o,h.moveTo(j,$),h.lineTo(j,Z),h.stroke(),p.beginPath(),p.strokeStyle=i,p.moveTo(j,$),p.lineTo(j,Z),p.stroke();});})();},[r,o,i,s,l,x,a]);let y=f=>{if(g.current&&e){let b=g.current.getBoundingClientRect(),h=f.clientX-b.left,p=Math.max(0,Math.min(1,h/b.width));n(p);}},S=e?t/e*100:0;return jsxRuntime.jsxs("div",{ref:g,className:"relative w-full cursor-pointer overflow-hidden",style:{height:`${a}px`},onClick:y,children:[jsxRuntime.jsx("canvas",{ref:c,className:"absolute inset-0 w-full h-full"}),jsxRuntime.jsx("div",{className:"absolute inset-0 h-full overflow-hidden transition-[width] duration-100 ease-linear pointer-events-none",style:{width:`${S}%`},children:jsxRuntime.jsx("canvas",{ref:u,className:"absolute h-full",style:{width:`${d}px`}})})]})});K.displayName="Waveform";var ut=react.memo(({media:r,peaks:t,artwork:e,title:o,artist:i,waveColor:a,progressColor:n,height:s=80,className:l="",style:x,resolution:c="auto",barWidth:u=2,barGap:g=1,theme:d,engine:y})=>{let{state:S,togglePlay:f,seek:b}=Q(r,{peaks:t,engine:y}),{isPlaying:h,currentTime:p,duration:k,peaks:E,isAnalyzing:M}=S,[N,O]=react.useState(typeof e=="string"?e:void 0);react.useEffect(()=>{if(e instanceof Blob){let C=URL.createObjectURL(e);return O(C),()=>URL.revokeObjectURL(C)}else O(e);},[e]);let w=react.useRef(null),m=U(w),B=react.useMemo(()=>typeof c=="number"?c:m>0?Math.max(1,Math.floor(m/(u+g))):E.length||1,[c,m,u,g,E.length]),A=tt(E,B),P=react.useMemo(()=>a||(d?d.bg==="#ffffff"?"#e5e7eb":"#374151":"#e5e7eb"),[a,d]),D=n||d?.primary||"#3b82f6",L=react.useMemo(()=>({...{"--wf-bg-color":d?.bg||"white","--wf-border-color":d?.border||"#f3f4f6","--wf-title-color":d?.text||"#111827","--wf-artist-color":d?.text||"#6b7280","--wf-time-color":d?.text||"#9ca3af","--wf-play-btn-bg":d?.primary||"#3b82f6","--wf-placeholder-from":d?.primary||"#fb923c","--wf-placeholder-to":d?.bg||"#ec4899"},...x}),[d,x]);return jsxRuntime.jsxs("div",{className:`group relative flex flex-col md:flex-row items-stretch gap-6 p-6 bg-[var(--wf-bg-color,white)] border border-[var(--wf-border-color,#f3f4f6)] rounded-[var(--wf-rounded,1rem)] shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden ${l}`,style:L,children:[jsxRuntime.jsx(I,{artworkUrl:N,title:o,isLoading:M}),jsxRuntime.jsxs("div",{className:"flex-1 w-full flex flex-col min-w-0",children:[jsxRuntime.jsxs("div",{className:"flex items-center gap-4 mb-6",children:[jsxRuntime.jsx("button",{onClick:f,className:"w-12 h-12 md:w-14 md:h-14 flex-shrink-0 flex items-center justify-center rounded-full bg-[var(--wf-play-btn-bg,#3b82f6)] text-white shadow-[0_4px_12px_rgba(0,0,0,0.15)] hover:shadow-[0_6px_16px_rgba(0,0,0,0.2)] transition-all hover:scale-105 active:scale-95 cursor-pointer border-none outline-none group/play",children:h?jsxRuntime.jsx("svg",{className:"w-6 h-6 md:w-7 md:h-7",fill:"currentColor",viewBox:"0 0 24 24",children:jsxRuntime.jsx("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"})}):jsxRuntime.jsx("svg",{className:"w-6 h-6 md:w-7 md:h-7 ml-1",fill:"currentColor",viewBox:"0 0 24 24",children:jsxRuntime.jsx("path",{d:"M8 5v14l11-7z"})})}),jsxRuntime.jsxs("div",{className:"flex-1 flex flex-col min-w-0",children:[jsxRuntime.jsxs("div",{className:"flex items-center justify-between gap-4",children:[i&&jsxRuntime.jsx("p",{className:"text-[10px] md:text-xs font-bold uppercase text-[var(--wf-artist-color,#6b7280)] opacity-60 tracking-[0.1em] line-clamp-1",children:i}),jsxRuntime.jsxs("div",{className:"text-[10px] font-mono text-[var(--wf-time-color,#9ca3af)] tabular-nums flex-shrink-0",children:[H(p)," / ",H(k)]})]}),o&&jsxRuntime.jsx("h3",{className:"text-lg md:text-xl font-black text-[var(--wf-title-color,#111827)] tracking-tight line-clamp-1 mt-0.5 leading-tight",children:o})]})]}),jsxRuntime.jsx("div",{className:"mt-auto",ref:w,children:jsxRuntime.jsx(K,{peaks:A,currentTime:p,duration:k,waveColor:P,progressColor:D,height:s,onSeek:b,resolution:c,barWidth:u,barGap:g})})]})]})});ut.displayName="WaveframePlayer";exports.PeakAnalyzer=R;exports.PlayerCore=T;exports.Waveform=K;exports.WaveframeEngine=W;exports.WaveframePlayer=ut;exports.formatTime=H;exports.generatePeaks=At;exports.highlightCode=Wt;exports.loadAudioToMemory=Lt;exports.resamplePeaks=Y;exports.revokeAudioMemory=jt;exports.useResampledPeaks=tt;exports.useResizeObserver=U;exports.useWaveframe=Q;exports.useWaveframeStore=q;//# sourceMappingURL=index.cjs.map
4
4
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/PlayerCore.ts","../src/core/PeakAnalyzer.ts","../src/core/WaveframeEngine.ts","../src/hooks/useWaveframeStore.ts","../src/hooks/useWaveframe.ts","../src/utils/audio.ts","../src/utils/index.ts","../src/hooks/useResampledPeaks.ts","../src/hooks/useResizeObserver.ts","../src/molecules/ArtworkOverlay.tsx","../src/organisms/Waveform.tsx","../src/components/WaveframePlayer.tsx"],"names":["PlayerCore","patch","l","listener","url","time","volume","muted","PeakAnalyzer","media","samples","arrayBuffer","response","channelData","blockSize","peaks","i","max","start","end","j","val","maxPeak","p","error","WaveframeEngine","playerState","sourceUrl","hasPeaks","percentage","duration","useWaveframeStore","engine","useSyncExternalStore","callback","useWaveframe","options","providedEngine","internalEngine","useMemo","state","useEffect","v","m","generatePeaks","audioUrl","analyzer","loadAudioToMemory","blob","revokeAudioMemory","formatTime","seconds","min","sec","resamplePeaks","targetCount","resampled","ratio","position","index","nextIndex","fraction","highlightCode","code","line","h","tokens","counter","addToken","cls","id","html","useResampledPeaks","useResizeObserver","ref","width","setWidth","useState","observer","entries","entry","ArtworkOverlay","memo","artworkUrl","title","isLoading","jsxs","jsx","Waveform","currentTime","waveColor","progressColor","height","onSeek","resolution","barWidth","barGap","canvasRef","useRef","progressCanvasRef","containerRef","containerWidth","canvas","progressCanvas","ctx","pCtx","dpr","rect","targetWidth","targetHeight","c","barCount","actualBarTotalWidth","actualBarWidth","actualBarGap","peak","x","barHeight","yStart","yEnd","handleSeek","e","progressPercent","WaveframePlayer","propPeaks","artwork","artist","propWaveColor","propProgressColor","className","propStyle","theme","togglePlay","seek","isPlaying","isAnalyzing","resolvedArtworkUrl","setResolvedArtworkUrl","resampledPeaks","mergedStyle"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Represents the low-level playback state of the audio element.\n */\nexport type PlayerState = {\n /** Whether the audio is currently playing */\n isPlaying: boolean;\n /** The current playback time in seconds */\n currentTime: number;\n /** The total duration of the track in seconds */\n duration: number;\n /** The current volume level (0 to 1) */\n volume: number;\n /** Whether the audio is currently muted */\n muted: boolean;\n};\n\n/**\n * A callback function that receives the latest PlayerState.\n */\nexport type PlayerListener = (state: PlayerState) => void;\n\n/**\n * The internal core class responsible for managing the HTMLAudioElement.\n * \n * It handles raw playback logic, volume control, and synchronizes the \n * internal `PlayerState` with DOM events from the underlying `Audio` instance.\n */\nexport class PlayerCore {\n private audio: HTMLAudioElement;\n private listeners: Set<PlayerListener> = new Set();\n private _state: PlayerState;\n\n /**\n * Initializes a new PlayerCore instance and sets up event listeners on a new Audio object.\n */\n constructor() {\n this.audio = new Audio();\n this._state = {\n isPlaying: false,\n currentTime: 0,\n duration: 0,\n volume: 1,\n muted: false,\n };\n\n this.initListeners();\n }\n\n /**\n * Subscribes to various HTMLMediaElement events to keep the internal state in sync.\n */\n private initListeners() {\n this.audio.addEventListener('play', () => this.updateState({ isPlaying: true }));\n this.audio.addEventListener('pause', () => this.updateState({ isPlaying: false }));\n this.audio.addEventListener('timeupdate', () => this.updateState({ currentTime: this.audio.currentTime }));\n this.audio.addEventListener('durationchange', () => this.updateState({ duration: this.audio.duration }));\n this.audio.addEventListener('volumechange', () => this.updateState({ \n volume: this.audio.volume, \n muted: this.audio.muted \n }));\n this.audio.addEventListener('ended', () => this.updateState({ isPlaying: false }));\n }\n\n /**\n * Updates the internal state and notifies subscribers.\n */\n private updateState(patch: Partial<PlayerState>) {\n this._state = { ...this._state, ...patch };\n this.notify();\n }\n\n /**\n * Triggers all registered listener callbacks.\n */\n private notify() {\n this.listeners.forEach(l => l(this._state));\n }\n\n /**\n * Registers a listener for state updates.\n * @param listener The callback function.\n * @returns An unsubscribe function.\n */\n public subscribe(listener: PlayerListener) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Returns the current playback state.\n */\n public get state() {\n return this._state;\n }\n\n /**\n * Updates the source URL of the underlying audio element.\n * @param url The audio source URL.\n */\n public setSource(url: string) {\n this.audio.src = url;\n this.audio.load();\n }\n\n /**\n * Starts playback. Returns a promise that resolves when playback begins.\n */\n public play() {\n return this.audio.play();\n }\n\n /**\n * Pauses playback.\n */\n public pause() {\n this.audio.pause();\n }\n\n /**\n * Toggles between play and pause states.\n */\n public togglePlay() {\n if (this._state.isPlaying) {\n this.pause();\n } else {\n this.play();\n }\n }\n\n /**\n * Seeks to a specific time.\n * @param time Time in seconds.\n */\n public seek(time: number) {\n this.audio.currentTime = time;\n }\n\n /**\n * Sets the volume level.\n * @param volume Level from 0 to 1.\n */\n public setVolume(volume: number) {\n this.audio.volume = volume;\n }\n\n /**\n * Mutes or unmutes the audio element.\n * @param muted Mute status.\n */\n public setMuted(muted: boolean) {\n this.audio.muted = muted;\n }\n\n /**\n * Cleans up the audio element and removes all listeners.\n */\n public dispose() {\n this.pause();\n this.audio.src = '';\n this.listeners.clear();\n }\n}\n","/**\n * A specialized class for decoding audio data and generating waveform peaks.\n * \n * It leverages the Web Audio API (`AudioContext`) to process audio buffers \n * and extract amplitude data for visualization.\n */\nexport class PeakAnalyzer {\n private audioCtx: AudioContext | null = null;\n\n /**\n * Initializes the analyzer. AudioContext creation is deferred to the first use \n * to comply with browser autoplay and resource management policies.\n */\n constructor() {}\n\n /**\n * Lazily creates or returns the existing AudioContext.\n */\n private getContext(): AudioContext {\n if (!this.audioCtx) {\n this.audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n return this.audioCtx;\n }\n\n /**\n * Processes media (URL or Blob) and generates a set of normalized peaks.\n * \n * @param media The URL string or Blob object to analyze.\n * @param samples The number of peaks (bars) to generate. Defaults to 512.\n * @returns A promise resolving to an array of normalized peak values (0 to 1).\n * \n * @example\n * ```typescript\n * const analyzer = new PeakAnalyzer();\n * \n * // Analyze from URL\n * const peaksFromUrl = await analyzer.generatePeaks('https://example.com/audio.mp3');\n * \n * // Analyze from Blob\n * const peaksFromBlob = await analyzer.generatePeaks(myAudioBlob);\n * ```\n */\n public async generatePeaks(media: string | Blob, samples: number = 512): Promise<number[]> {\n try {\n let arrayBuffer: ArrayBuffer;\n\n if (typeof media === 'string') {\n const response = await fetch(media);\n if (!response.ok) throw new Error(`Failed to fetch audio: ${response.statusText}`);\n arrayBuffer = await response.arrayBuffer();\n } else {\n arrayBuffer = await media.arrayBuffer();\n }\n\n const audioCtx = this.getContext();\n const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);\n \n const channelData = audioBuffer.getChannelData(0); // Use left channel\n const blockSize = Math.floor(channelData.length / samples);\n const peaks = [];\n\n for (let i = 0; i < samples; i++) {\n let max = 0;\n const start = i * blockSize;\n const end = start + blockSize;\n \n for (let j = start; j < end; j++) {\n const val = Math.abs(channelData[j]);\n if (val > max) max = val;\n }\n peaks.push(max);\n }\n \n // Normalize peaks to 0-1 range\n const maxPeak = Math.max(...peaks);\n return peaks.map(p => p / (maxPeak || 1));\n } catch (error) {\n console.error('PeakAnalyzer Error:', error);\n throw error;\n }\n }\n\n /**\n * Closes the AudioContext and releases system audio resources.\n */\n public dispose() {\n if (this.audioCtx) {\n this.audioCtx.close();\n this.audioCtx = null;\n }\n }\n}\n","import { PlayerCore, PlayerState } from './PlayerCore';\nimport { PeakAnalyzer } from './PeakAnalyzer';\n\n/**\n * Represents the complete state of the Waveframe engine, combining playback and analysis.\n */\nexport type EngineState = PlayerState & {\n /** The current set of generated or provided waveform peaks (0-1 range) */\n peaks: number[];\n /** Whether an audio analysis process is currently in progress */\n isAnalyzing: boolean;\n /** Any error message encountered during playback or analysis */\n error: string | null;\n};\n\n/**\n * A callback function that receives the latest EngineState.\n */\nexport type EngineListener = (state: EngineState) => void;\n\n/**\n * The orchestrator class for Waveframe. \n * \n * It manages the lifecycle of audio playback and waveform analysis, providing a unified \n * store-like interface that can be easily consumed by React or other frameworks.\n * \n * @example\n * ```typescript\n * const engine = new WaveframeEngine();\n * \n * // Load from URL (automatic analysis if peaks omitted)\n * engine.load('https://example.com/audio.mp3');\n * \n * // Load from Blob with pre-computed peaks\n * engine.load(myBlob, [0.1, 0.5, 0.8]);\n * \n * // Subscription\n * const unsubscribe = engine.subscribe((state) => {\n * console.log('Current time:', state.currentTime);\n * });\n * ```\n */\nexport class WaveframeEngine {\n private player: PlayerCore;\n private analyzer: PeakAnalyzer;\n private listeners: Set<EngineListener> = new Set();\n private _state: EngineState;\n\n private _media: string | Blob | null = null;\n private _objectUrl: string | null = null;\n\n /**\n * Creates a new instance of the WaveframeEngine.\n * Initializes internal PlayerCore and PeakAnalyzer.\n */\n constructor() {\n this.player = new PlayerCore();\n this.analyzer = new PeakAnalyzer();\n \n this._state = {\n ...this.player.state,\n peaks: [],\n isAnalyzing: false,\n error: null,\n };\n\n // Subscribe to player updates\n this.player.subscribe((playerState) => {\n this.updateState({ ...playerState });\n });\n }\n\n /**\n * Internal method to update the state and notify all subscribers.\n */\n private updateState(patch: Partial<EngineState>) {\n this._state = { ...this._state, ...patch };\n this.notify();\n }\n\n /**\n * Notifies all registered listeners of a state change.\n */\n private notify() {\n this.listeners.forEach(l => l(this._state));\n }\n\n // --- Store Interface ---\n\n /**\n * Registers a listener to be called whenever the engine state changes.\n * @param listener The callback function.\n * @returns An unsubscribe function.\n */\n public subscribe(listener: EngineListener) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Returns a snapshot of the current engine state.\n * Useful for `useSyncExternalStore`.\n */\n public getSnapshot(): EngineState {\n return this._state;\n }\n\n // --- Actions ---\n\n /**\n * Revokes any existing Object URLs to prevent memory leaks.\n */\n private revokeOldSource() {\n if (this._objectUrl) {\n URL.revokeObjectURL(this._objectUrl);\n this._objectUrl = null;\n }\n }\n\n /**\n * Loads media (URL or Blob) into the player.\n * \n * If a string is passed, it's treated as a URL and used directly for playback.\n * If a Blob is passed, an Object URL is created for playback.\n * \n * If `peaks` are not provided, it automatically triggers an analysis.\n * \n * @param media The audio source (URL string or Blob/File object).\n * @param peaks Optional pre-generated peaks for the waveform.\n */\n public load(media: string | Blob, peaks?: number[]) {\n const isNewMedia = this._media !== media;\n \n if (isNewMedia) {\n this.revokeOldSource();\n this._media = media;\n\n let sourceUrl: string;\n if (typeof media === 'string') {\n sourceUrl = media;\n } else {\n this._objectUrl = URL.createObjectURL(media);\n sourceUrl = this._objectUrl;\n }\n\n this.player.setSource(sourceUrl);\n \n const hasPeaks = peaks && peaks.length > 0;\n this.updateState({ \n peaks: peaks || [], \n isAnalyzing: false, \n error: null \n });\n\n // Automatic analysis if peaks are missing\n if (!hasPeaks) {\n this.analyze();\n }\n } else if (peaks && peaks !== this._state.peaks) {\n // Update peaks if they change even if media is same\n this.updateState({ peaks });\n }\n }\n\n /**\n * Analyzes the current media to generate waveform peaks.\n * @param samples The number of peaks to generate. Defaults to 512.\n */\n public async analyze(samples: number = 512) {\n if (!this._media) {\n this.updateState({ error: 'No media loaded to analyze' });\n return;\n }\n\n this.updateState({ isAnalyzing: true, error: null });\n try {\n const peaks = await this.analyzer.generatePeaks(this._media, samples);\n this.updateState({ peaks, isAnalyzing: false });\n } catch (e) {\n this.updateState({ \n isAnalyzing: false, \n error: e instanceof Error ? e.message : 'Analysis failed' \n });\n }\n }\n\n /**\n * Toggles playback between playing and paused.\n */\n public togglePlay() {\n this.player.togglePlay();\n }\n\n /**\n * Starts audio playback.\n */\n public play() {\n this.player.play();\n }\n\n /**\n * Pauses audio playback.\n */\n public pause() {\n this.player.pause();\n }\n\n /**\n * Seeks to a specific position in the track.\n * @param percentage The seek position as a decimal (0 to 1).\n */\n public seek(percentage: number) {\n const { duration } = this._state;\n if (duration) {\n this.player.seek(percentage * duration);\n }\n }\n\n /**\n * Sets the playback volume.\n * @param volume The volume level (0 to 1).\n */\n public setVolume(volume: number) {\n this.player.setVolume(volume);\n }\n\n /**\n * Mutes or unmutes the audio.\n * @param muted Whether the audio should be muted.\n */\n public setMuted(muted: boolean) {\n this.player.setMuted(muted);\n }\n\n /**\n * Disposes of the engine, pausing playback and clearing all listeners and resources.\n */\n public dispose() {\n this.revokeOldSource();\n this.player.dispose();\n this.analyzer.dispose();\n this.listeners.clear();\n }\n}\n","import { useSyncExternalStore } from 'react';\nimport { WaveframeEngine, EngineState } from '../core/WaveframeEngine';\n\n/**\n * A React hook that synchronizes a WaveframeEngine's state with a React component.\n * \n * It uses `useSyncExternalStore` for high-performance updates, ensuring that \n * the component only re-renders when the engine's state snapshot actually changes.\n * \n * @param engine The WaveframeEngine instance to subscribe to.\n * @returns The current EngineState (isPlaying, currentTime, peaks, etc.).\n * \n * @example\n * ```tsx\n * const MyPlayer = ({ engine }: { engine: WaveframeEngine }) => {\n * const { isPlaying, currentTime, duration } = useWaveframeStore(engine);\n * \n * return (\n * <div>\n * <button onClick={() => engine.togglePlay()}>\n * {isPlaying ? 'Pause' : 'Play'}\n * </button>\n * <p>{currentTime.toFixed(2)} / {duration.toFixed(2)}</p>\n * </div>\n * );\n * };\n * ```\n */\nexport const useWaveframeStore = (engine: WaveframeEngine): EngineState => {\n return useSyncExternalStore(\n (callback) => engine.subscribe(callback),\n () => engine.getSnapshot()\n );\n};\n","import { useMemo, useEffect } from 'react';\nimport { WaveframeEngine, EngineState } from '../core/WaveframeEngine';\nimport { useWaveframeStore } from './useWaveframeStore';\n\n/**\n * Configuration options for the `useWaveframe` hook.\n */\nexport interface UseWaveframeOptions {\n /** Optional pre-computed peaks to skip automatic analysis */\n peaks?: number[];\n /** Optional external engine instance for shared playback across components */\n engine?: WaveframeEngine;\n}\n\n/**\n * A headless hook that provides full control over the Waveframe engine.\n * \n * It manages the engine's lifecycle, loads the provided media, and returns \n * the current state along with playback controls.\n * \n * @param media The audio source (URL string or Blob/File object).\n * @param options Additional configuration and an optional external engine.\n * \n * @example\n * ```tsx\n * const { state, togglePlay, seek } = useWaveframe('https://example.com/audio.mp3');\n * \n * return (\n * <div>\n * <button onClick={togglePlay}>{state.isPlaying ? 'Pause' : 'Play'}</button>\n * <div onClick={(e) => seek(0.5)}>Seek to Middle</div>\n * </div>\n * );\n * ```\n */\nexport const useWaveframe = (media: string | Blob | undefined, options: UseWaveframeOptions = {}) => {\n const { peaks, engine: providedEngine } = options;\n\n // Initialize engine (only once)\n const internalEngine = useMemo(() => providedEngine || new WaveframeEngine(), [providedEngine]);\n const engine = providedEngine || internalEngine;\n\n // Subscribe to engine state\n const state = useWaveframeStore(engine);\n\n // Sync media with engine\n useEffect(() => {\n if (media) {\n engine.load(media, peaks);\n }\n }, [engine, media, peaks]);\n\n // Handle disposal\n useEffect(() => {\n return () => {\n // Only dispose if we created it internally\n if (!providedEngine) {\n internalEngine.dispose();\n }\n };\n }, [internalEngine, providedEngine]);\n\n return {\n /** The current reactive state of the engine */\n state,\n /** The raw WaveframeEngine instance for advanced usage */\n engine,\n /** Toggles playback between playing and paused */\n togglePlay: () => engine.togglePlay(),\n /** Starts audio playback */\n play: () => engine.play(),\n /** Pauses audio playback */\n pause: () => engine.pause(),\n /** Seeks to a specific percentage (0-1) */\n seek: (percentage: number) => engine.seek(percentage),\n /** Sets the playback volume (0-1) */\n setVolume: (v: number) => engine.setVolume(v),\n /** Mutes or unmutes the audio */\n setMuted: (m: boolean) => engine.setMuted(m),\n /** Manually triggers a re-analysis of the current media */\n analyze: (samples?: number) => engine.analyze(samples),\n };\n};\n","/**\n * Advanced Audio Utilities using Web Audio API\n */\nimport { PeakAnalyzer } from '../core/PeakAnalyzer';\n\n/**\n * Loads audio from a URL, decodes it, and generates a specific number of peaks (samples).\n * \n * This is a high-level utility function that internally manages a `PeakAnalyzer` instance.\n * \n * @param audioUrl The URL of the audio file to analyze.\n * @param samples The number of peaks (bars) to generate. Defaults to 512.\n * @returns A promise resolving to an array of normalized peak values (0 to 1).\n * \n * @example\n * ```typescript\n * const peaks = await generatePeaks('https://example.com/audio.mp3', 256);\n * ```\n */\nexport const generatePeaks = async (audioUrl: string, samples: number = 512): Promise<number[]> => {\n const analyzer = new PeakAnalyzer();\n try {\n return await analyzer.generatePeaks(audioUrl, samples);\n } finally {\n analyzer.dispose();\n }\n};\n\n/**\n * Loads audio into memory as a Blob and returns a temporary Object URL.\n * \n * Useful for ensuring audio data is fully loaded locally before starting \n * playback or analysis, which can help with CORS issues or slow networks.\n * \n * @param url The URL of the remote audio file.\n * @returns A promise resolving to a temporary `blob:` URL.\n */\nexport const loadAudioToMemory = async (url: string): Promise<string> => {\n const response = await fetch(url);\n const blob = await response.blob();\n return URL.createObjectURL(blob);\n};\n\n/**\n * Cleanup function to prevent memory leaks from Object URLs.\n * \n * Call this when a `blob:` URL is no longer needed (e.g., when the component unmounts).\n * \n * @param url The Object URL to revoke.\n */\nexport const revokeAudioMemory = (url: string) => {\n if (url && url.startsWith('blob:')) {\n URL.revokeObjectURL(url);\n }\n};\n","/**\n * Formats seconds into a M:SS string\n */\nexport const formatTime = (seconds: number): string => {\n if (isNaN(seconds)) return '0:00';\n const min = Math.floor(seconds / 60);\n const sec = Math.floor(seconds % 60);\n return `${min}:${sec.toString().padStart(2, '0')}`;\n};\n\n/**\n * Resamples an array of peaks to a target count using bucket-max or linear interpolation\n */\nexport const resamplePeaks = (peaks: number[], targetCount: number): number[] => {\n if (peaks.length === 0) return [];\n if (peaks.length === targetCount) return peaks;\n\n const resampled = new Array(targetCount);\n const ratio = peaks.length / targetCount;\n\n if (ratio > 1) {\n // Downsampling: Bucket Max\n for (let i = 0; i < targetCount; i++) {\n let max = 0;\n const start = Math.floor(i * ratio);\n const end = Math.floor((i + 1) * ratio);\n for (let j = start; j < end; j++) {\n if (peaks[j] > max) max = peaks[j];\n }\n resampled[i] = max;\n }\n } else {\n // Upsampling: Linear Interpolation\n for (let i = 0; i < targetCount; i++) {\n const position = i * ratio;\n const index = Math.floor(position);\n const nextIndex = Math.min(index + 1, peaks.length - 1);\n const fraction = position - index;\n resampled[i] = peaks[index] + (peaks[nextIndex] - peaks[index]) * fraction;\n }\n }\n return resampled;\n};\n\n/**\n * High-performance token-based syntax highlighter for React snippets\n */\nexport const highlightCode = (code: string): string[] => {\n return code.split('\\n').map((line) => {\n // 1. Escape basic HTML\n let h = line.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\n // 2. Identify and tokenize segments to avoid nested replacement issues\n const tokens: { [key: string]: string } = {};\n let counter = 0;\n const addToken = (val: string, cls: string) => {\n const id = `__TOKEN_${counter++}__`;\n tokens[id] = `<span class=\"${cls}\">${val}</span>`;\n return id;\n };\n\n // Strings\n h = h.replace(/(\"(?:[^\"\\\\]|\\\\.)*\")/g, (m) => addToken(m, 'text-[#ce9178]'));\n // Numbers\n h = h.replace(/\\b(\\d+(\\.\\d+)?)\\b/g, (m) => addToken(m, 'text-[#b5cea8]'));\n // Component Name\n h = h.replace(/\\b(WaveframePlayer)\\b/g, (m) => addToken(m, 'text-[#4ec9b0]'));\n // Props (anything followed by =)\n h = h.replace(/\\b([a-z][a-zA-Z0-9]+)(?==)/g, (m) => addToken(m, 'text-[#9cdcfe]'));\n\n // 3. Style remaining symbols\n h = h.replace(/(&lt;|&gt;|\\{|\\}|\\/|:|,)/g, '<span class=\"text-gray-500\">$1</span>');\n\n // 4. In-place token resolution\n Object.entries(tokens).forEach(([id, html]) => {\n h = h.replace(id, html);\n });\n\n return h;\n });\n};\n\nexport * from './audio';\n","import { useMemo } from 'react';\nimport { resamplePeaks } from '../utils';\n\nexport const useResampledPeaks = (peaks: number[], targetCount: number) => {\n return useMemo(() => resamplePeaks(peaks, targetCount), [peaks, targetCount]);\n};\n","import { useState, useEffect } from 'react';\n\nexport const useResizeObserver = (ref: React.RefObject<HTMLElement | null>) => {\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n if (!ref.current) return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setWidth(entry.contentRect.width);\n }\n });\n\n observer.observe(ref.current);\n return () => observer.disconnect();\n }, [ref]);\n\n return width;\n};\n","import React, { memo } from 'react';\n\n/**\n * Props for the ArtworkOverlay component.\n */\ninterface ArtworkOverlayProps {\n /** The URL or Object URL of the artwork image */\n artworkUrl?: string;\n /** The title of the track (used for alt text) */\n title?: string;\n /** Whether the artwork is currently being processed or the audio is analyzing */\n isLoading?: boolean;\n}\n\n/**\n * A purely visual component for displaying track artwork.\n * \n * It handles loading states with a blur effect and provides a consistent \n * container for the track image.\n */\nexport const ArtworkOverlay: React.FC<ArtworkOverlayProps> = memo(({ \n artworkUrl, \n title, \n isLoading\n}) => {\n return (\n <div className=\"relative flex-shrink-0 w-32 h-32 md:w-40 md:h-40 overflow-hidden rounded-[var(--wf-artwork-rounded,0.75rem)] shadow-lg group/artwork\">\n <div className={`w-full h-full transition-all duration-700 ${isLoading ? 'blur-md scale-110' : ''}`}>\n {artworkUrl ? (\n <img\n src={artworkUrl}\n alt={title}\n className=\"w-full h-full object-cover transition-transform duration-500 group-hover/artwork:scale-110\"\n />\n ) : (\n <div className=\"w-full h-full bg-gradient-to-br from-[var(--wf-placeholder-from,#fb923c)] to-[var(--wf-placeholder-to,#ec4899)] flex items-center justify-center\">\n <svg className=\"w-16 h-16 text-white opacity-50\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z\" />\n </svg>\n </div>\n )}\n </div>\n\n {isLoading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/10 backdrop-blur-[1px]\">\n <div className=\"w-8 h-8 border-4 border-white/30 border-t-white rounded-full animate-spin\" />\n </div>\n )}\n </div>\n );\n});\n\nArtworkOverlay.displayName = 'ArtworkOverlay';\n","import React, { useRef, useEffect, memo } from 'react';\nimport { useResizeObserver } from '../hooks/useResizeObserver';\n\ninterface WaveformProps {\n peaks: number[];\n currentTime: number;\n duration: number;\n waveColor: string;\n progressColor: string;\n height: number;\n onSeek: (percentage: number) => void;\n resolution?: number | 'auto';\n barWidth?: number;\n barGap?: number;\n}\n\nexport const Waveform: React.FC<WaveformProps> = memo(({\n peaks,\n currentTime,\n duration,\n waveColor,\n progressColor,\n height,\n onSeek,\n resolution = 'auto',\n barWidth = 2,\n barGap = 1,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const progressCanvasRef = useRef<HTMLCanvasElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const containerWidth = useResizeObserver(containerRef);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n const progressCanvas = progressCanvasRef.current;\n if (!canvas || !progressCanvas) return;\n\n const ctx = canvas.getContext('2d');\n const pCtx = progressCanvas.getContext('2d');\n if (!ctx || !pCtx) return;\n\n const dpr = window.devicePixelRatio || 1;\n const rect = canvas.getBoundingClientRect();\n const targetWidth = rect.width * dpr;\n const targetHeight = rect.height * dpr;\n\n [canvas, progressCanvas].forEach(c => {\n if (c.width !== targetWidth || c.height !== targetHeight) {\n c.width = targetWidth;\n c.height = targetHeight;\n }\n });\n\n const draw = () => {\n if (peaks.length === 0) return;\n const { width, height } = canvas;\n \n ctx.clearRect(0, 0, width, height);\n pCtx.clearRect(0, 0, width, height);\n\n const barCount = peaks.length;\n const actualBarTotalWidth = width / barCount;\n const actualBarWidth = typeof resolution === 'number' \n ? actualBarTotalWidth * 0.7 \n : barWidth * dpr;\n const actualBarGap = typeof resolution === 'number'\n ? actualBarTotalWidth * 0.3\n : barGap * dpr;\n\n ctx.lineCap = 'round';\n ctx.lineWidth = actualBarWidth;\n pCtx.lineCap = 'round';\n pCtx.lineWidth = actualBarWidth;\n\n peaks.forEach((peak, index) => {\n const x = index * (actualBarWidth + actualBarGap) + actualBarWidth / 2;\n const barHeight = peak * height * 0.8;\n const yStart = (height - barHeight) / 2;\n const yEnd = yStart + barHeight;\n\n ctx.beginPath();\n ctx.strokeStyle = waveColor;\n ctx.moveTo(x, yStart);\n ctx.lineTo(x, yEnd);\n ctx.stroke();\n\n pCtx.beginPath();\n pCtx.strokeStyle = progressColor;\n pCtx.moveTo(x, yStart);\n pCtx.lineTo(x, yEnd);\n pCtx.stroke();\n });\n };\n\n draw();\n }, [peaks, waveColor, progressColor, resolution, barWidth, barGap, height]);\n\n const handleSeek = (e: React.MouseEvent<HTMLDivElement>) => {\n if (containerRef.current && duration) {\n const rect = containerRef.current.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const percentage = Math.max(0, Math.min(1, x / rect.width));\n onSeek(percentage);\n }\n };\n\n const progressPercent = duration ? (currentTime / duration) * 100 : 0;\n\n return (\n <div \n ref={containerRef} \n className=\"relative w-full cursor-pointer overflow-hidden\" \n style={{ height: `${height}px` }}\n onClick={handleSeek}\n >\n <canvas ref={canvasRef} className=\"absolute inset-0 w-full h-full\" />\n <div \n className=\"absolute inset-0 h-full overflow-hidden transition-[width] duration-100 ease-linear pointer-events-none\"\n style={{ width: `${progressPercent}%` }}\n >\n <canvas ref={progressCanvasRef} className=\"absolute h-full\" style={{ width: `${containerWidth}px` }} />\n </div>\n </div>\n );\n});\n\nWaveform.displayName = 'Waveform';\n","import React, { memo, useMemo, useRef, useState, useEffect } from 'react';\nimport { WaveframeTheme } from '../types';\nimport { WaveframeEngine } from '../core/WaveframeEngine';\nimport { useWaveframe } from '../hooks/useWaveframe';\nimport { useResampledPeaks } from '../hooks/useResampledPeaks';\nimport { useResizeObserver } from '../hooks/useResizeObserver';\nimport { ArtworkOverlay } from '../molecules/ArtworkOverlay';\nimport { Waveform } from '../organisms/Waveform';\nimport { formatTime } from '../utils';\n\n/**\n * Props for the WaveframePlayer component\n */\nexport interface WaveframePlayerProps {\n /**\n * The audio source to play. Can be a URL string or a Blob/File object.\n */\n media?: string | Blob;\n /**\n * Optional pre-generated peaks for the waveform (0-1 range).\n * If omitted or empty, the player will automatically analyze the media.\n */\n peaks?: number[];\n /**\n * The artwork source to display. Can be a URL string or a Blob/File object.\n */\n artwork?: string | Blob;\n /**\n * The title of the track\n */\n title?: string;\n /**\n * The artist of the track\n */\n artist?: string;\n /**\n * The base color of the waveform bars\n * @default \"#e5e7eb\" (light) or \"#374151\" (dark)\n */\n waveColor?: string;\n /**\n * The color of the played progress part of the waveform\n * @default theme.primary or \"#3b82f6\"\n */\n progressColor?: string;\n /**\n * The height of the waveform in pixels\n * @default 80\n */\n height?: number;\n /**\n * Additional CSS classes for the container\n */\n className?: string;\n /**\n * Inline styles for the container\n */\n style?: React.CSSProperties;\n /**\n * The number of bars to render. Use 'auto' to fit the container width.\n * @default \"auto\"\n */\n resolution?: number | 'auto';\n /**\n * The width of each bar in pixels (if resolution is 'auto')\n * @default 2\n */\n barWidth?: number;\n /**\n * The gap between bars in pixels (if resolution is 'auto')\n * @default 1\n */\n barGap?: number;\n /**\n * Custom theme configuration\n */\n theme?: WaveframeTheme;\n /**\n * Optional WaveframeEngine instance for external control.\n * If provided, the player will sync with this engine instead of creating its own.\n */\n engine?: WaveframeEngine;\n}\n\n/**\n * The standard \"all-in-one\" Waveframe player component.\n * \n * This component features a SoundCloud-inspired layout with a prominent \n * play/pause button positioned next to the track metadata.\n */\nexport const WaveframePlayer: React.FC<WaveframePlayerProps> = memo(({\n media,\n peaks: propPeaks,\n artwork,\n title,\n artist,\n waveColor: propWaveColor,\n progressColor: propProgressColor,\n height = 80,\n className = '',\n style: propStyle,\n resolution = 'auto',\n barWidth = 2,\n barGap = 1,\n theme,\n engine: providedEngine,\n}) => {\n // Use the headless hook for state and controls\n const { state, togglePlay, seek } = useWaveframe(media, {\n peaks: propPeaks,\n engine: providedEngine,\n });\n\n const { isPlaying, currentTime, duration, peaks, isAnalyzing } = state;\n\n // Handle Artwork Blob -> Object URL\n const [resolvedArtworkUrl, setResolvedArtworkUrl] = useState<string | undefined>(\n typeof artwork === 'string' ? artwork : undefined\n );\n\n useEffect(() => {\n if (artwork instanceof Blob) {\n const url = URL.createObjectURL(artwork);\n setResolvedArtworkUrl(url);\n return () => URL.revokeObjectURL(url);\n } else {\n setResolvedArtworkUrl(artwork);\n }\n }, [artwork]);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const containerWidth = useResizeObserver(containerRef);\n\n const targetCount = useMemo(() => {\n if (typeof resolution === 'number') return resolution;\n if (containerWidth > 0) {\n return Math.max(1, Math.floor(containerWidth / (barWidth + barGap)));\n }\n return peaks.length || 1;\n }, [resolution, containerWidth, barWidth, barGap, peaks.length]);\n\n const resampledPeaks = useResampledPeaks(peaks, targetCount);\n\n const waveColor = useMemo(() => {\n if (propWaveColor) return propWaveColor;\n if (theme) return theme.bg === '#ffffff' ? '#e5e7eb' : '#374151';\n return '#e5e7eb';\n }, [propWaveColor, theme]);\n\n const progressColor = propProgressColor || theme?.primary || '#3b82f6';\n\n const mergedStyle = useMemo(() => {\n const baseStyle = {\n '--wf-bg-color': theme?.bg || 'white',\n '--wf-border-color': theme?.border || '#f3f4f6',\n '--wf-title-color': theme?.text || '#111827',\n '--wf-artist-color': theme?.text || '#6b7280',\n '--wf-time-color': theme?.text || '#9ca3af',\n '--wf-play-btn-bg': theme?.primary || '#3b82f6',\n '--wf-placeholder-from': theme?.primary || '#fb923c',\n '--wf-placeholder-to': theme?.bg || '#ec4899',\n };\n return { ...baseStyle, ...propStyle } as React.CSSProperties;\n }, [theme, propStyle]);\n\n return (\n <div\n className={`group relative flex flex-col md:flex-row items-stretch gap-6 p-6 bg-[var(--wf-bg-color,white)] border border-[var(--wf-border-color,#f3f4f6)] rounded-[var(--wf-rounded,1rem)] shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden ${className}`}\n style={mergedStyle}\n >\n <ArtworkOverlay \n artworkUrl={resolvedArtworkUrl} \n title={title} \n isLoading={isAnalyzing}\n />\n\n <div className=\"flex-1 w-full flex flex-col min-w-0\">\n <div className=\"flex items-center gap-4 mb-6\">\n {/* SoundCloud-style circular play button */}\n <button\n onClick={togglePlay}\n className=\"w-12 h-12 md:w-14 md:h-14 flex-shrink-0 flex items-center justify-center rounded-full bg-[var(--wf-play-btn-bg,#3b82f6)] text-white shadow-[0_4px_12px_rgba(0,0,0,0.15)] hover:shadow-[0_6px_16px_rgba(0,0,0,0.2)] transition-all hover:scale-105 active:scale-95 cursor-pointer border-none outline-none group/play\"\n >\n {isPlaying ? (\n <svg className=\"w-6 h-6 md:w-7 md:h-7\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\n </svg>\n ) : (\n <svg className=\"w-6 h-6 md:w-7 md:h-7 ml-1\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n\n <div className=\"flex-1 flex flex-col min-w-0\">\n <div className=\"flex items-center justify-between gap-4\">\n {artist && (\n <p className=\"text-[10px] md:text-xs font-bold uppercase text-[var(--wf-artist-color,#6b7280)] opacity-60 tracking-[0.1em] line-clamp-1\">\n {artist}\n </p>\n )}\n <div className=\"text-[10px] font-mono text-[var(--wf-time-color,#9ca3af)] tabular-nums flex-shrink-0\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </div>\n </div>\n {title && (\n <h3 className=\"text-lg md:text-xl font-black text-[var(--wf-title-color,#111827)] tracking-tight line-clamp-1 mt-0.5 leading-tight\">\n {title}\n </h3>\n )}\n </div>\n </div>\n\n <div className=\"mt-auto\" ref={containerRef}>\n <Waveform \n peaks={resampledPeaks}\n currentTime={currentTime}\n duration={duration}\n waveColor={waveColor}\n progressColor={progressColor}\n height={height}\n onSeek={seek}\n resolution={resolution}\n barWidth={barWidth}\n barGap={barGap}\n />\n </div>\n </div>\n </div>\n );\n});\n\nWaveframePlayer.displayName = 'WaveframePlayer';\n"]}
1
+ {"version":3,"sources":["../src/core/PlayerCore.ts","../src/core/PeakAnalyzer.ts","../src/core/WaveframeEngine.ts","../src/hooks/useWaveframeStore.ts","../src/hooks/useWaveframe.ts","../src/utils/audio.ts","../src/utils/index.ts","../src/hooks/useResampledPeaks.ts","../src/hooks/useResizeObserver.ts","../src/molecules/ArtworkOverlay.tsx","../src/organisms/Waveform.tsx","../src/components/WaveframePlayer.tsx"],"names":["PlayerCore","error","message","patch","l","listener","url","e","time","volume","muted","PeakAnalyzer","media","samples","arrayBuffer","response","channelData","blockSize","peaks","i","max","start","end","j","val","maxPeak","p","WaveframeEngine","playerState","sourceUrl","hasPeaks","percentage","duration","useWaveframeStore","engine","useSyncExternalStore","callback","useWaveframe","options","providedEngine","internalEngine","useMemo","state","useEffect","v","m","generatePeaks","audioUrl","analyzer","loadAudioToMemory","blob","revokeAudioMemory","formatTime","seconds","min","sec","resamplePeaks","targetCount","resampled","ratio","position","index","nextIndex","fraction","highlightCode","code","line","h","tokens","counter","addToken","cls","id","html","useResampledPeaks","useResizeObserver","ref","width","setWidth","useState","observer","entries","entry","ArtworkOverlay","memo","artworkUrl","title","isLoading","jsxs","jsx","Waveform","currentTime","waveColor","progressColor","height","onSeek","resolution","barWidth","barGap","canvasRef","useRef","progressCanvasRef","containerRef","containerWidth","canvas","progressCanvas","ctx","pCtx","dpr","rect","targetWidth","targetHeight","c","barCount","actualBarTotalWidth","actualBarWidth","actualBarGap","peak","x","barHeight","yStart","yEnd","handleSeek","progressPercent","WaveframePlayer","propPeaks","artwork","artist","propWaveColor","propProgressColor","className","propStyle","theme","togglePlay","seek","isPlaying","isAnalyzing","resolvedArtworkUrl","setResolvedArtworkUrl","resampledPeaks","mergedStyle"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Represents the low-level playback state of the audio element.\n */\nexport type PlayerState = {\n /** Whether the audio is currently playing */\n isPlaying: boolean;\n /** The current playback time in seconds */\n currentTime: number;\n /** The total duration of the track in seconds */\n duration: number;\n /** The current volume level (0 to 1) */\n volume: number;\n /** Whether the audio is currently muted */\n muted: boolean;\n /** Any error reported by the audio element */\n error: string | null;\n};\n\n/**\n * A callback function that receives the latest PlayerState.\n */\nexport type PlayerListener = (state: PlayerState) => void;\n\n/**\n * The internal core class responsible for managing the HTMLAudioElement.\n * \n * It handles raw playback logic, volume control, and synchronizes the \n * internal `PlayerState` with DOM events from the underlying `Audio` instance.\n */\nexport class PlayerCore {\n private audio: HTMLAudioElement;\n private listeners: Set<PlayerListener> = new Set();\n private _state: PlayerState;\n\n /**\n * Initializes a new PlayerCore instance and sets up event listeners on a new Audio object.\n */\n constructor() {\n this.audio = new Audio();\n this._state = {\n isPlaying: false,\n currentTime: 0,\n duration: 0,\n volume: 1,\n muted: false,\n error: null,\n };\n\n this.initListeners();\n }\n\n /**\n * Subscribes to various HTMLMediaElement events to keep the internal state in sync.\n */\n private initListeners() {\n this.audio.addEventListener('play', () => this.updateState({ isPlaying: true, error: null }));\n this.audio.addEventListener('pause', () => this.updateState({ isPlaying: false }));\n this.audio.addEventListener('timeupdate', () => this.updateState({ currentTime: this.audio.currentTime }));\n this.audio.addEventListener('durationchange', () => this.updateState({ duration: this.audio.duration }));\n this.audio.addEventListener('volumechange', () => this.updateState({ \n volume: this.audio.volume, \n muted: this.audio.muted \n }));\n this.audio.addEventListener('ended', () => this.updateState({ isPlaying: false }));\n this.audio.addEventListener('error', () => {\n const error = this.audio.error;\n let message = 'Unknown audio error';\n if (error) {\n switch (error.code) {\n case error.MEDIA_ERR_ABORTED: message = 'Playback aborted'; break;\n case error.MEDIA_ERR_NETWORK: message = 'Network error'; break;\n case error.MEDIA_ERR_DECODE: message = 'Audio decoding failed'; break;\n case error.MEDIA_ERR_SRC_NOT_SUPPORTED: message = 'Audio format not supported'; break;\n }\n }\n this.updateState({ isPlaying: false, error: message });\n });\n }\n\n /**\n * Updates the internal state and notifies subscribers.\n */\n private updateState(patch: Partial<PlayerState>) {\n this._state = { ...this._state, ...patch };\n this.notify();\n }\n\n /**\n * Triggers all registered listener callbacks.\n */\n private notify() {\n this.listeners.forEach(l => l(this._state));\n }\n\n /**\n * Registers a listener for state updates.\n * @param listener The callback function.\n * @returns An unsubscribe function.\n */\n public subscribe(listener: PlayerListener) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Returns the current playback state.\n */\n public get state() {\n return this._state;\n }\n\n /**\n * Updates the source URL of the underlying audio element.\n * @param url The audio source URL.\n */\n public setSource(url: string) {\n this.audio.src = url;\n this.audio.load();\n this.updateState({ error: null, currentTime: 0, duration: 0 });\n }\n\n /**\n * Starts playback. Returns a promise that resolves when playback begins.\n */\n public async play() {\n try {\n await this.audio.play();\n } catch (e) {\n const message = e instanceof Error ? e.message : 'Playback failed';\n this.updateState({ isPlaying: false, error: message });\n throw e;\n }\n }\n\n /**\n * Pauses playback.\n */\n public pause() {\n this.audio.pause();\n }\n\n /**\n * Toggles between play and pause states.\n */\n public async togglePlay() {\n if (this._state.isPlaying) {\n this.pause();\n } else {\n try {\n await this.play();\n } catch {\n // Error handled in play()\n }\n }\n }\n\n /**\n * Seeks to a specific time.\n * @param time Time in seconds.\n */\n public seek(time: number) {\n this.audio.currentTime = time;\n }\n\n /**\n * Sets the volume level.\n * @param volume Level from 0 to 1.\n */\n public setVolume(volume: number) {\n this.audio.volume = volume;\n }\n\n /**\n * Mutes or unmutes the audio element.\n * @param muted Mute status.\n */\n public setMuted(muted: boolean) {\n this.audio.muted = muted;\n }\n\n /**\n * Cleans up the audio element and removes all listeners.\n */\n public dispose() {\n this.pause();\n this.audio.src = '';\n this.listeners.clear();\n }\n}\n","/**\n * A specialized class for decoding audio data and generating waveform peaks.\n * \n * It leverages the Web Audio API (`AudioContext`) to process audio buffers \n * and extract amplitude data for visualization.\n */\nexport class PeakAnalyzer {\n private audioCtx: AudioContext | null = null;\n\n /**\n * Initializes the analyzer. AudioContext creation is deferred to the first use \n * to comply with browser autoplay and resource management policies.\n */\n constructor() {}\n\n /**\n * Lazily creates or returns the existing AudioContext.\n */\n private getContext(): AudioContext {\n if (!this.audioCtx) {\n this.audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n return this.audioCtx;\n }\n\n /**\n * Processes media (URL or Blob) and generates a set of normalized peaks.\n * \n * @param media The URL string or Blob object to analyze.\n * @param samples The number of peaks (bars) to generate. Defaults to 512.\n * @returns A promise resolving to an array of normalized peak values (0 to 1).\n * \n * @example\n * ```typescript\n * const analyzer = new PeakAnalyzer();\n * \n * // Analyze from URL\n * const peaksFromUrl = await analyzer.generatePeaks('https://example.com/audio.mp3');\n * \n * // Analyze from Blob\n * const peaksFromBlob = await analyzer.generatePeaks(myAudioBlob);\n * ```\n */\n public async generatePeaks(media: string | Blob, samples: number = 512): Promise<number[]> {\n try {\n let arrayBuffer: ArrayBuffer;\n\n if (typeof media === 'string') {\n const response = await fetch(media);\n if (!response.ok) throw new Error(`Failed to fetch audio: ${response.statusText}`);\n arrayBuffer = await response.arrayBuffer();\n } else {\n arrayBuffer = await media.arrayBuffer();\n }\n\n const audioCtx = this.getContext();\n const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);\n \n const channelData = audioBuffer.getChannelData(0); // Use left channel\n const blockSize = Math.floor(channelData.length / samples);\n const peaks = [];\n\n for (let i = 0; i < samples; i++) {\n let max = 0;\n const start = i * blockSize;\n const end = start + blockSize;\n \n for (let j = start; j < end; j++) {\n const val = Math.abs(channelData[j]);\n if (val > max) max = val;\n }\n peaks.push(max);\n }\n \n // Normalize peaks to 0-1 range\n const maxPeak = Math.max(...peaks);\n return peaks.map(p => p / (maxPeak || 1));\n } catch (error) {\n console.error('PeakAnalyzer Error:', error);\n throw error;\n }\n }\n\n /**\n * Closes the AudioContext and releases system audio resources.\n */\n public dispose() {\n if (this.audioCtx) {\n this.audioCtx.close();\n this.audioCtx = null;\n }\n }\n}\n","import { PlayerCore, PlayerState } from './PlayerCore';\nimport { PeakAnalyzer } from './PeakAnalyzer';\n\n/**\n * Represents the complete state of the Waveframe engine, combining playback and analysis.\n */\nexport type EngineState = PlayerState & {\n /** The current set of generated or provided waveform peaks (0-1 range) */\n peaks: number[];\n /** Whether an audio analysis process is currently in progress */\n isAnalyzing: boolean;\n /** Any error message encountered during playback or analysis */\n error: string | null;\n};\n\n/**\n * A callback function that receives the latest EngineState.\n */\nexport type EngineListener = (state: EngineState) => void;\n\n/**\n * The orchestrator class for Waveframe. \n * \n * It manages the lifecycle of audio playback and waveform analysis, providing a unified \n * store-like interface that can be easily consumed by React or other frameworks.\n * \n * @example\n * ```typescript\n * const engine = new WaveframeEngine();\n * \n * // Load from URL (automatic analysis if peaks omitted)\n * engine.load('https://example.com/audio.mp3');\n * \n * // Load from Blob with pre-computed peaks\n * engine.load(myBlob, [0.1, 0.5, 0.8]);\n * \n * // Subscription\n * const unsubscribe = engine.subscribe((state) => {\n * console.log('Current time:', state.currentTime);\n * });\n * ```\n */\nexport class WaveframeEngine {\n private player: PlayerCore;\n private analyzer: PeakAnalyzer;\n private listeners: Set<EngineListener> = new Set();\n private _state: EngineState;\n\n private _media: string | Blob | null = null;\n private _objectUrl: string | null = null;\n\n /**\n * Creates a new instance of the WaveframeEngine.\n * Initializes internal PlayerCore and PeakAnalyzer.\n */\n constructor() {\n this.player = new PlayerCore();\n this.analyzer = new PeakAnalyzer();\n \n this._state = {\n ...this.player.state,\n peaks: [],\n isAnalyzing: false,\n error: null,\n };\n\n // Subscribe to player updates\n this.player.subscribe((playerState) => {\n this.updateState({ ...playerState });\n });\n }\n\n /**\n * Internal method to update the state and notify all subscribers.\n */\n private updateState(patch: Partial<EngineState>) {\n this._state = { ...this._state, ...patch };\n this.notify();\n }\n\n /**\n * Notifies all registered listeners of a state change.\n */\n private notify() {\n this.listeners.forEach(l => l(this._state));\n }\n\n // --- Store Interface ---\n\n /**\n * Registers a listener to be called whenever the engine state changes.\n * @param listener The callback function.\n * @returns An unsubscribe function.\n */\n public subscribe(listener: EngineListener) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Returns a snapshot of the current engine state.\n * Useful for `useSyncExternalStore`.\n */\n public getSnapshot(): EngineState {\n return this._state;\n }\n\n // --- Actions ---\n\n /**\n * Revokes any existing Object URLs to prevent memory leaks.\n */\n private revokeOldSource() {\n if (this._objectUrl) {\n URL.revokeObjectURL(this._objectUrl);\n this._objectUrl = null;\n }\n }\n\n /**\n * Loads media (URL or Blob) into the player.\n * \n * If a string is passed, it's treated as a URL and used directly for playback.\n * If a Blob is passed, an Object URL is created for playback.\n * \n * If `peaks` are not provided, it automatically triggers an analysis.\n * \n * @param media The audio source (URL string or Blob/File object).\n * @param peaks Optional pre-generated peaks for the waveform.\n */\n public load(media: string | Blob, peaks?: number[]) {\n const isNewMedia = this._media !== media;\n \n if (isNewMedia) {\n this.revokeOldSource();\n this._media = media;\n\n let sourceUrl: string;\n if (typeof media === 'string') {\n sourceUrl = media;\n } else {\n this._objectUrl = URL.createObjectURL(media);\n sourceUrl = this._objectUrl;\n }\n\n this.player.setSource(sourceUrl);\n \n const hasPeaks = peaks && peaks.length > 0;\n this.updateState({ \n peaks: peaks || [], \n isAnalyzing: false, \n error: null \n });\n\n // Automatic analysis if peaks are missing\n if (!hasPeaks) {\n this.analyze();\n }\n } else if (peaks && (peaks.length !== this._state.peaks.length)) {\n // Update peaks only if they actually look different (length check as a simple proxy for deep comparison)\n // This helps avoid reloads if the parent passes a new array reference with same content\n this.updateState({ peaks });\n }\n }\n\n /**\n * Analyzes the current media to generate waveform peaks.\n * @param samples The number of peaks to generate. Defaults to 512.\n */\n public async analyze(samples: number = 512) {\n if (!this._media) {\n this.updateState({ error: 'No media loaded to analyze' });\n return;\n }\n\n this.updateState({ isAnalyzing: true, error: null });\n try {\n const peaks = await this.analyzer.generatePeaks(this._media, samples);\n this.updateState({ peaks, isAnalyzing: false });\n } catch (e) {\n this.updateState({ \n isAnalyzing: false, \n error: e instanceof Error ? e.message : 'Analysis failed' \n });\n }\n }\n\n /**\n * Toggles playback between playing and paused.\n */\n public togglePlay() {\n this.player.togglePlay();\n }\n\n /**\n * Starts audio playback.\n */\n public play() {\n this.player.play();\n }\n\n /**\n * Pauses audio playback.\n */\n public pause() {\n this.player.pause();\n }\n\n /**\n * Seeks to a specific position in the track.\n * @param percentage The seek position as a decimal (0 to 1).\n */\n public seek(percentage: number) {\n const { duration } = this._state;\n if (duration) {\n this.player.seek(percentage * duration);\n }\n }\n\n /**\n * Sets the playback volume.\n * @param volume The volume level (0 to 1).\n */\n public setVolume(volume: number) {\n this.player.setVolume(volume);\n }\n\n /**\n * Mutes or unmutes the audio.\n * @param muted Whether the audio should be muted.\n */\n public setMuted(muted: boolean) {\n this.player.setMuted(muted);\n }\n\n /**\n * Disposes of the engine, pausing playback and clearing all listeners and resources.\n */\n public dispose() {\n this.revokeOldSource();\n this.player.dispose();\n this.analyzer.dispose();\n this.listeners.clear();\n }\n}\n","import { useSyncExternalStore } from 'react';\nimport { WaveframeEngine, EngineState } from '../core/WaveframeEngine';\n\n/**\n * A React hook that synchronizes a WaveframeEngine's state with a React component.\n * \n * It uses `useSyncExternalStore` for high-performance updates, ensuring that \n * the component only re-renders when the engine's state snapshot actually changes.\n * \n * @param engine The WaveframeEngine instance to subscribe to.\n * @returns The current EngineState (isPlaying, currentTime, peaks, etc.).\n * \n * @example\n * ```tsx\n * const MyPlayer = ({ engine }: { engine: WaveframeEngine }) => {\n * const { isPlaying, currentTime, duration } = useWaveframeStore(engine);\n * \n * return (\n * <div>\n * <button onClick={() => engine.togglePlay()}>\n * {isPlaying ? 'Pause' : 'Play'}\n * </button>\n * <p>{currentTime.toFixed(2)} / {duration.toFixed(2)}</p>\n * </div>\n * );\n * };\n * ```\n */\nexport const useWaveframeStore = (engine: WaveframeEngine): EngineState => {\n return useSyncExternalStore(\n (callback) => engine.subscribe(callback),\n () => engine.getSnapshot()\n );\n};\n","import { useMemo, useEffect } from 'react';\nimport { WaveframeEngine, EngineState } from '../core/WaveframeEngine';\nimport { useWaveframeStore } from './useWaveframeStore';\n\n/**\n * Configuration options for the `useWaveframe` hook.\n */\nexport interface UseWaveframeOptions {\n /** Optional pre-computed peaks to skip automatic analysis */\n peaks?: number[];\n /** Optional external engine instance for shared playback across components */\n engine?: WaveframeEngine;\n}\n\n/**\n * A headless hook that provides full control over the Waveframe engine.\n * \n * It manages the engine's lifecycle, loads the provided media, and returns \n * the current state along with playback controls.\n * \n * @param media The audio source (URL string or Blob/File object).\n * @param options Additional configuration and an optional external engine.\n * \n * @example\n * ```tsx\n * const { state, togglePlay, seek } = useWaveframe('https://example.com/audio.mp3');\n * \n * return (\n * <div>\n * <button onClick={togglePlay}>{state.isPlaying ? 'Pause' : 'Play'}</button>\n * <div onClick={(e) => seek(0.5)}>Seek to Middle</div>\n * </div>\n * );\n * ```\n */\nexport const useWaveframe = (media: string | Blob | undefined, options: UseWaveframeOptions = {}) => {\n const { peaks, engine: providedEngine } = options;\n\n // Initialize engine (only once)\n const internalEngine = useMemo(() => providedEngine || new WaveframeEngine(), [providedEngine]);\n const engine = providedEngine || internalEngine;\n\n // Subscribe to engine state\n const state = useWaveframeStore(engine);\n\n // Sync media with engine\n useEffect(() => {\n if (media) {\n engine.load(media, peaks);\n }\n }, [engine, media, peaks]);\n\n // Handle disposal\n useEffect(() => {\n return () => {\n // Only dispose if we created it internally\n if (!providedEngine) {\n internalEngine.dispose();\n }\n };\n }, [internalEngine, providedEngine]);\n\n return {\n /** The current reactive state of the engine */\n state,\n /** The raw WaveframeEngine instance for advanced usage */\n engine,\n /** Toggles playback between playing and paused */\n togglePlay: () => engine.togglePlay(),\n /** Starts audio playback */\n play: () => engine.play(),\n /** Pauses audio playback */\n pause: () => engine.pause(),\n /** Seeks to a specific percentage (0-1) */\n seek: (percentage: number) => engine.seek(percentage),\n /** Sets the playback volume (0-1) */\n setVolume: (v: number) => engine.setVolume(v),\n /** Mutes or unmutes the audio */\n setMuted: (m: boolean) => engine.setMuted(m),\n /** Manually triggers a re-analysis of the current media */\n analyze: (samples?: number) => engine.analyze(samples),\n };\n};\n","/**\n * Advanced Audio Utilities using Web Audio API\n */\nimport { PeakAnalyzer } from '../core/PeakAnalyzer';\n\n/**\n * Loads audio from a URL, decodes it, and generates a specific number of peaks (samples).\n * \n * This is a high-level utility function that internally manages a `PeakAnalyzer` instance.\n * \n * @param audioUrl The URL of the audio file to analyze.\n * @param samples The number of peaks (bars) to generate. Defaults to 512.\n * @returns A promise resolving to an array of normalized peak values (0 to 1).\n * \n * @example\n * ```typescript\n * const peaks = await generatePeaks('https://example.com/audio.mp3', 256);\n * ```\n */\nexport const generatePeaks = async (audioUrl: string, samples: number = 512): Promise<number[]> => {\n const analyzer = new PeakAnalyzer();\n try {\n return await analyzer.generatePeaks(audioUrl, samples);\n } finally {\n analyzer.dispose();\n }\n};\n\n/**\n * Loads audio into memory as a Blob and returns a temporary Object URL.\n * \n * Useful for ensuring audio data is fully loaded locally before starting \n * playback or analysis, which can help with CORS issues or slow networks.\n * \n * @param url The URL of the remote audio file.\n * @returns A promise resolving to a temporary `blob:` URL.\n */\nexport const loadAudioToMemory = async (url: string): Promise<string> => {\n const response = await fetch(url);\n const blob = await response.blob();\n return URL.createObjectURL(blob);\n};\n\n/**\n * Cleanup function to prevent memory leaks from Object URLs.\n * \n * Call this when a `blob:` URL is no longer needed (e.g., when the component unmounts).\n * \n * @param url The Object URL to revoke.\n */\nexport const revokeAudioMemory = (url: string) => {\n if (url && url.startsWith('blob:')) {\n URL.revokeObjectURL(url);\n }\n};\n","/**\n * Formats seconds into a M:SS string\n */\nexport const formatTime = (seconds: number): string => {\n if (isNaN(seconds)) return '0:00';\n const min = Math.floor(seconds / 60);\n const sec = Math.floor(seconds % 60);\n return `${min}:${sec.toString().padStart(2, '0')}`;\n};\n\n/**\n * Resamples an array of peaks to a target count using bucket-max or linear interpolation\n */\nexport const resamplePeaks = (peaks: number[], targetCount: number): number[] => {\n if (peaks.length === 0) return [];\n if (peaks.length === targetCount) return peaks;\n\n const resampled = new Array(targetCount);\n const ratio = peaks.length / targetCount;\n\n if (ratio > 1) {\n // Downsampling: Bucket Max\n for (let i = 0; i < targetCount; i++) {\n let max = 0;\n const start = Math.floor(i * ratio);\n const end = Math.floor((i + 1) * ratio);\n for (let j = start; j < end; j++) {\n if (peaks[j] > max) max = peaks[j];\n }\n resampled[i] = max;\n }\n } else {\n // Upsampling: Linear Interpolation\n for (let i = 0; i < targetCount; i++) {\n const position = i * ratio;\n const index = Math.floor(position);\n const nextIndex = Math.min(index + 1, peaks.length - 1);\n const fraction = position - index;\n resampled[i] = peaks[index] + (peaks[nextIndex] - peaks[index]) * fraction;\n }\n }\n return resampled;\n};\n\n/**\n * High-performance token-based syntax highlighter for React snippets\n */\nexport const highlightCode = (code: string): string[] => {\n return code.split('\\n').map((line) => {\n // 1. Escape basic HTML\n let h = line.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\n // 2. Identify and tokenize segments to avoid nested replacement issues\n const tokens: { [key: string]: string } = {};\n let counter = 0;\n const addToken = (val: string, cls: string) => {\n const id = `__TOKEN_${counter++}__`;\n tokens[id] = `<span class=\"${cls}\">${val}</span>`;\n return id;\n };\n\n // Strings\n h = h.replace(/(\"(?:[^\"\\\\]|\\\\.)*\")/g, (m) => addToken(m, 'text-[#ce9178]'));\n // Numbers\n h = h.replace(/\\b(\\d+(\\.\\d+)?)\\b/g, (m) => addToken(m, 'text-[#b5cea8]'));\n // Component Name\n h = h.replace(/\\b(WaveframePlayer)\\b/g, (m) => addToken(m, 'text-[#4ec9b0]'));\n // Props (anything followed by =)\n h = h.replace(/\\b([a-z][a-zA-Z0-9]+)(?==)/g, (m) => addToken(m, 'text-[#9cdcfe]'));\n\n // 3. Style remaining symbols\n h = h.replace(/(&lt;|&gt;|\\{|\\}|\\/|:|,)/g, '<span class=\"text-gray-500\">$1</span>');\n\n // 4. In-place token resolution\n Object.entries(tokens).forEach(([id, html]) => {\n h = h.replace(id, html);\n });\n\n return h;\n });\n};\n\nexport * from './audio';\n","import { useMemo } from 'react';\nimport { resamplePeaks } from '../utils';\n\nexport const useResampledPeaks = (peaks: number[], targetCount: number) => {\n return useMemo(() => resamplePeaks(peaks, targetCount), [peaks, targetCount]);\n};\n","import { useState, useEffect } from 'react';\n\nexport const useResizeObserver = (ref: React.RefObject<HTMLElement | null>) => {\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n if (!ref.current) return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setWidth(entry.contentRect.width);\n }\n });\n\n observer.observe(ref.current);\n return () => observer.disconnect();\n }, [ref]);\n\n return width;\n};\n","import React, { memo } from 'react';\n\n/**\n * Props for the ArtworkOverlay component.\n */\ninterface ArtworkOverlayProps {\n /** The URL or Object URL of the artwork image */\n artworkUrl?: string;\n /** The title of the track (used for alt text) */\n title?: string;\n /** Whether the artwork is currently being processed or the audio is analyzing */\n isLoading?: boolean;\n}\n\n/**\n * A purely visual component for displaying track artwork.\n * \n * It handles loading states with a blur effect and provides a consistent \n * container for the track image.\n */\nexport const ArtworkOverlay: React.FC<ArtworkOverlayProps> = memo(({ \n artworkUrl, \n title, \n isLoading\n}) => {\n return (\n <div className=\"relative flex-shrink-0 w-32 h-32 md:w-40 md:h-40 overflow-hidden rounded-[var(--wf-artwork-rounded,0.75rem)] shadow-lg group/artwork\">\n <div className={`w-full h-full transition-all duration-700 ${isLoading ? 'blur-md scale-110' : ''}`}>\n {artworkUrl ? (\n <img\n src={artworkUrl}\n alt={title}\n className=\"w-full h-full object-cover transition-transform duration-500 group-hover/artwork:scale-110\"\n />\n ) : (\n <div className=\"w-full h-full bg-gradient-to-br from-[var(--wf-placeholder-from,#fb923c)] to-[var(--wf-placeholder-to,#ec4899)] flex items-center justify-center\">\n <svg className=\"w-16 h-16 text-white opacity-50\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z\" />\n </svg>\n </div>\n )}\n </div>\n\n {isLoading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/10 backdrop-blur-[1px]\">\n <div className=\"w-8 h-8 border-4 border-white/30 border-t-white rounded-full animate-spin\" />\n </div>\n )}\n </div>\n );\n});\n\nArtworkOverlay.displayName = 'ArtworkOverlay';\n","import React, { useRef, useEffect, memo } from 'react';\nimport { useResizeObserver } from '../hooks/useResizeObserver';\n\ninterface WaveformProps {\n peaks: number[];\n currentTime: number;\n duration: number;\n waveColor: string;\n progressColor: string;\n height: number;\n onSeek: (percentage: number) => void;\n resolution?: number | 'auto';\n barWidth?: number;\n barGap?: number;\n}\n\nexport const Waveform: React.FC<WaveformProps> = memo(({\n peaks,\n currentTime,\n duration,\n waveColor,\n progressColor,\n height,\n onSeek,\n resolution = 'auto',\n barWidth = 2,\n barGap = 1,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const progressCanvasRef = useRef<HTMLCanvasElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const containerWidth = useResizeObserver(containerRef);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n const progressCanvas = progressCanvasRef.current;\n if (!canvas || !progressCanvas) return;\n\n const ctx = canvas.getContext('2d');\n const pCtx = progressCanvas.getContext('2d');\n if (!ctx || !pCtx) return;\n\n const dpr = window.devicePixelRatio || 1;\n const rect = canvas.getBoundingClientRect();\n const targetWidth = rect.width * dpr;\n const targetHeight = rect.height * dpr;\n\n [canvas, progressCanvas].forEach(c => {\n if (c.width !== targetWidth || c.height !== targetHeight) {\n c.width = targetWidth;\n c.height = targetHeight;\n }\n });\n\n const draw = () => {\n if (peaks.length === 0) return;\n const { width, height } = canvas;\n \n ctx.clearRect(0, 0, width, height);\n pCtx.clearRect(0, 0, width, height);\n\n const barCount = peaks.length;\n const actualBarTotalWidth = width / barCount;\n const actualBarWidth = typeof resolution === 'number' \n ? actualBarTotalWidth * 0.7 \n : barWidth * dpr;\n const actualBarGap = typeof resolution === 'number'\n ? actualBarTotalWidth * 0.3\n : barGap * dpr;\n\n ctx.lineCap = 'round';\n ctx.lineWidth = actualBarWidth;\n pCtx.lineCap = 'round';\n pCtx.lineWidth = actualBarWidth;\n\n peaks.forEach((peak, index) => {\n if (peak <= 0) return;\n \n const x = index * (actualBarWidth + actualBarGap) + actualBarWidth / 2;\n const barHeight = peak * height * 0.8;\n const yStart = (height - barHeight) / 2;\n const yEnd = yStart + barHeight;\n\n ctx.beginPath();\n ctx.strokeStyle = waveColor;\n ctx.moveTo(x, yStart);\n ctx.lineTo(x, yEnd);\n ctx.stroke();\n\n pCtx.beginPath();\n pCtx.strokeStyle = progressColor;\n pCtx.moveTo(x, yStart);\n pCtx.lineTo(x, yEnd);\n pCtx.stroke();\n });\n };\n\n draw();\n }, [peaks, waveColor, progressColor, resolution, barWidth, barGap, height]);\n\n const handleSeek = (e: React.MouseEvent<HTMLDivElement>) => {\n if (containerRef.current && duration) {\n const rect = containerRef.current.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const percentage = Math.max(0, Math.min(1, x / rect.width));\n onSeek(percentage);\n }\n };\n\n const progressPercent = duration ? (currentTime / duration) * 100 : 0;\n\n return (\n <div \n ref={containerRef} \n className=\"relative w-full cursor-pointer overflow-hidden\" \n style={{ height: `${height}px` }}\n onClick={handleSeek}\n >\n <canvas ref={canvasRef} className=\"absolute inset-0 w-full h-full\" />\n <div \n className=\"absolute inset-0 h-full overflow-hidden transition-[width] duration-100 ease-linear pointer-events-none\"\n style={{ width: `${progressPercent}%` }}\n >\n <canvas ref={progressCanvasRef} className=\"absolute h-full\" style={{ width: `${containerWidth}px` }} />\n </div>\n </div>\n );\n});\n\nWaveform.displayName = 'Waveform';\n","import React, { memo, useMemo, useRef, useState, useEffect } from 'react';\nimport { WaveframeTheme } from '../types';\nimport { WaveframeEngine } from '../core/WaveframeEngine';\nimport { useWaveframe } from '../hooks/useWaveframe';\nimport { useResampledPeaks } from '../hooks/useResampledPeaks';\nimport { useResizeObserver } from '../hooks/useResizeObserver';\nimport { ArtworkOverlay } from '../molecules/ArtworkOverlay';\nimport { Waveform } from '../organisms/Waveform';\nimport { formatTime } from '../utils';\n\n/**\n * Props for the WaveframePlayer component\n */\nexport interface WaveframePlayerProps {\n /**\n * The audio source to play. Can be a URL string or a Blob/File object.\n */\n media?: string | Blob;\n /**\n * Optional pre-generated peaks for the waveform (0-1 range).\n * If omitted or empty, the player will automatically analyze the media.\n */\n peaks?: number[];\n /**\n * The artwork source to display. Can be a URL string or a Blob/File object.\n */\n artwork?: string | Blob;\n /**\n * The title of the track\n */\n title?: string;\n /**\n * The artist of the track\n */\n artist?: string;\n /**\n * The base color of the waveform bars\n * @default \"#e5e7eb\" (light) or \"#374151\" (dark)\n */\n waveColor?: string;\n /**\n * The color of the played progress part of the waveform\n * @default theme.primary or \"#3b82f6\"\n */\n progressColor?: string;\n /**\n * The height of the waveform in pixels\n * @default 80\n */\n height?: number;\n /**\n * Additional CSS classes for the container\n */\n className?: string;\n /**\n * Inline styles for the container\n */\n style?: React.CSSProperties;\n /**\n * The number of bars to render. Use 'auto' to fit the container width.\n * @default \"auto\"\n */\n resolution?: number | 'auto';\n /**\n * The width of each bar in pixels (if resolution is 'auto')\n * @default 2\n */\n barWidth?: number;\n /**\n * The gap between bars in pixels (if resolution is 'auto')\n * @default 1\n */\n barGap?: number;\n /**\n * Custom theme configuration\n */\n theme?: WaveframeTheme;\n /**\n * Optional WaveframeEngine instance for external control.\n * If provided, the player will sync with this engine instead of creating its own.\n */\n engine?: WaveframeEngine;\n}\n\n/**\n * The standard \"all-in-one\" Waveframe player component.\n * \n * This component features a SoundCloud-inspired layout with a prominent \n * play/pause button positioned next to the track metadata.\n */\nexport const WaveframePlayer: React.FC<WaveframePlayerProps> = memo(({\n media,\n peaks: propPeaks,\n artwork,\n title,\n artist,\n waveColor: propWaveColor,\n progressColor: propProgressColor,\n height = 80,\n className = '',\n style: propStyle,\n resolution = 'auto',\n barWidth = 2,\n barGap = 1,\n theme,\n engine: providedEngine,\n}) => {\n // Use the headless hook for state and controls\n const { state, togglePlay, seek } = useWaveframe(media, {\n peaks: propPeaks,\n engine: providedEngine,\n });\n\n const { isPlaying, currentTime, duration, peaks, isAnalyzing } = state;\n\n // Handle Artwork Blob -> Object URL\n const [resolvedArtworkUrl, setResolvedArtworkUrl] = useState<string | undefined>(\n typeof artwork === 'string' ? artwork : undefined\n );\n\n useEffect(() => {\n if (artwork instanceof Blob) {\n const url = URL.createObjectURL(artwork);\n setResolvedArtworkUrl(url);\n return () => URL.revokeObjectURL(url);\n } else {\n setResolvedArtworkUrl(artwork);\n }\n }, [artwork]);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const containerWidth = useResizeObserver(containerRef);\n\n const targetCount = useMemo(() => {\n if (typeof resolution === 'number') return resolution;\n if (containerWidth > 0) {\n return Math.max(1, Math.floor(containerWidth / (barWidth + barGap)));\n }\n return peaks.length || 1;\n }, [resolution, containerWidth, barWidth, barGap, peaks.length]);\n\n const resampledPeaks = useResampledPeaks(peaks, targetCount);\n\n const waveColor = useMemo(() => {\n if (propWaveColor) return propWaveColor;\n if (theme) return theme.bg === '#ffffff' ? '#e5e7eb' : '#374151';\n return '#e5e7eb';\n }, [propWaveColor, theme]);\n\n const progressColor = propProgressColor || theme?.primary || '#3b82f6';\n\n const mergedStyle = useMemo(() => {\n const baseStyle = {\n '--wf-bg-color': theme?.bg || 'white',\n '--wf-border-color': theme?.border || '#f3f4f6',\n '--wf-title-color': theme?.text || '#111827',\n '--wf-artist-color': theme?.text || '#6b7280',\n '--wf-time-color': theme?.text || '#9ca3af',\n '--wf-play-btn-bg': theme?.primary || '#3b82f6',\n '--wf-placeholder-from': theme?.primary || '#fb923c',\n '--wf-placeholder-to': theme?.bg || '#ec4899',\n };\n return { ...baseStyle, ...propStyle } as React.CSSProperties;\n }, [theme, propStyle]);\n\n return (\n <div\n className={`group relative flex flex-col md:flex-row items-stretch gap-6 p-6 bg-[var(--wf-bg-color,white)] border border-[var(--wf-border-color,#f3f4f6)] rounded-[var(--wf-rounded,1rem)] shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden ${className}`}\n style={mergedStyle}\n >\n <ArtworkOverlay \n artworkUrl={resolvedArtworkUrl} \n title={title} \n isLoading={isAnalyzing}\n />\n\n <div className=\"flex-1 w-full flex flex-col min-w-0\">\n <div className=\"flex items-center gap-4 mb-6\">\n {/* SoundCloud-style circular play button */}\n <button\n onClick={togglePlay}\n className=\"w-12 h-12 md:w-14 md:h-14 flex-shrink-0 flex items-center justify-center rounded-full bg-[var(--wf-play-btn-bg,#3b82f6)] text-white shadow-[0_4px_12px_rgba(0,0,0,0.15)] hover:shadow-[0_6px_16px_rgba(0,0,0,0.2)] transition-all hover:scale-105 active:scale-95 cursor-pointer border-none outline-none group/play\"\n >\n {isPlaying ? (\n <svg className=\"w-6 h-6 md:w-7 md:h-7\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\n </svg>\n ) : (\n <svg className=\"w-6 h-6 md:w-7 md:h-7 ml-1\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n\n <div className=\"flex-1 flex flex-col min-w-0\">\n <div className=\"flex items-center justify-between gap-4\">\n {artist && (\n <p className=\"text-[10px] md:text-xs font-bold uppercase text-[var(--wf-artist-color,#6b7280)] opacity-60 tracking-[0.1em] line-clamp-1\">\n {artist}\n </p>\n )}\n <div className=\"text-[10px] font-mono text-[var(--wf-time-color,#9ca3af)] tabular-nums flex-shrink-0\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </div>\n </div>\n {title && (\n <h3 className=\"text-lg md:text-xl font-black text-[var(--wf-title-color,#111827)] tracking-tight line-clamp-1 mt-0.5 leading-tight\">\n {title}\n </h3>\n )}\n </div>\n </div>\n\n <div className=\"mt-auto\" ref={containerRef}>\n <Waveform \n peaks={resampledPeaks}\n currentTime={currentTime}\n duration={duration}\n waveColor={waveColor}\n progressColor={progressColor}\n height={height}\n onSeek={seek}\n resolution={resolution}\n barWidth={barWidth}\n barGap={barGap}\n />\n </div>\n </div>\n </div>\n );\n});\n\nWaveframePlayer.displayName = 'WaveframePlayer';\n"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React$1 from 'react';
2
2
 
3
3
  /**
4
4
  * Defines the core color palette for the Waveframe component.
@@ -90,6 +90,8 @@ type PlayerState = {
90
90
  volume: number;
91
91
  /** Whether the audio is currently muted */
92
92
  muted: boolean;
93
+ /** Any error reported by the audio element */
94
+ error: string | null;
93
95
  };
94
96
  /**
95
97
  * A callback function that receives the latest PlayerState.
@@ -147,7 +149,7 @@ declare class PlayerCore {
147
149
  /**
148
150
  * Toggles between play and pause states.
149
151
  */
150
- togglePlay(): void;
152
+ togglePlay(): Promise<void>;
151
153
  /**
152
154
  * Seeks to a specific time.
153
155
  * @param time Time in seconds.
@@ -338,7 +340,7 @@ interface WaveframePlayerProps {
338
340
  /**
339
341
  * Inline styles for the container
340
342
  */
341
- style?: React.CSSProperties;
343
+ style?: React$1.CSSProperties;
342
344
  /**
343
345
  * The number of bars to render. Use 'auto' to fit the container width.
344
346
  * @default "auto"
@@ -370,7 +372,21 @@ interface WaveframePlayerProps {
370
372
  * This component features a SoundCloud-inspired layout with a prominent
371
373
  * play/pause button positioned next to the track metadata.
372
374
  */
373
- declare const WaveframePlayer: React.FC<WaveframePlayerProps>;
375
+ declare const WaveframePlayer: React$1.FC<WaveframePlayerProps>;
376
+
377
+ interface WaveformProps {
378
+ peaks: number[];
379
+ currentTime: number;
380
+ duration: number;
381
+ waveColor: string;
382
+ progressColor: string;
383
+ height: number;
384
+ onSeek: (percentage: number) => void;
385
+ resolution?: number | 'auto';
386
+ barWidth?: number;
387
+ barGap?: number;
388
+ }
389
+ declare const Waveform: React$1.FC<WaveformProps>;
374
390
 
375
391
  /**
376
392
  * A specialized class for decoding audio data and generating waveform peaks.
@@ -414,6 +430,57 @@ declare class PeakAnalyzer {
414
430
  dispose(): void;
415
431
  }
416
432
 
433
+ /**
434
+ * Configuration options for the `useWaveframe` hook.
435
+ */
436
+ interface UseWaveframeOptions {
437
+ /** Optional pre-computed peaks to skip automatic analysis */
438
+ peaks?: number[];
439
+ /** Optional external engine instance for shared playback across components */
440
+ engine?: WaveframeEngine;
441
+ }
442
+ /**
443
+ * A headless hook that provides full control over the Waveframe engine.
444
+ *
445
+ * It manages the engine's lifecycle, loads the provided media, and returns
446
+ * the current state along with playback controls.
447
+ *
448
+ * @param media The audio source (URL string or Blob/File object).
449
+ * @param options Additional configuration and an optional external engine.
450
+ *
451
+ * @example
452
+ * ```tsx
453
+ * const { state, togglePlay, seek } = useWaveframe('https://example.com/audio.mp3');
454
+ *
455
+ * return (
456
+ * <div>
457
+ * <button onClick={togglePlay}>{state.isPlaying ? 'Pause' : 'Play'}</button>
458
+ * <div onClick={(e) => seek(0.5)}>Seek to Middle</div>
459
+ * </div>
460
+ * );
461
+ * ```
462
+ */
463
+ declare const useWaveframe: (media: string | Blob | undefined, options?: UseWaveframeOptions) => {
464
+ /** The current reactive state of the engine */
465
+ state: EngineState;
466
+ /** The raw WaveframeEngine instance for advanced usage */
467
+ engine: WaveframeEngine;
468
+ /** Toggles playback between playing and paused */
469
+ togglePlay: () => void;
470
+ /** Starts audio playback */
471
+ play: () => void;
472
+ /** Pauses audio playback */
473
+ pause: () => void;
474
+ /** Seeks to a specific percentage (0-1) */
475
+ seek: (percentage: number) => void;
476
+ /** Sets the playback volume (0-1) */
477
+ setVolume: (v: number) => void;
478
+ /** Mutes or unmutes the audio */
479
+ setMuted: (m: boolean) => void;
480
+ /** Manually triggers a re-analysis of the current media */
481
+ analyze: (samples?: number) => Promise<void>;
482
+ };
483
+
417
484
  /**
418
485
  * A React hook that synchronizes a WaveframeEngine's state with a React component.
419
486
  *
@@ -441,6 +508,10 @@ declare class PeakAnalyzer {
441
508
  */
442
509
  declare const useWaveframeStore: (engine: WaveframeEngine) => EngineState;
443
510
 
511
+ declare const useResampledPeaks: (peaks: number[], targetCount: number) => number[];
512
+
513
+ declare const useResizeObserver: (ref: React.RefObject<HTMLElement | null>) => number;
514
+
444
515
  /**
445
516
  * Loads audio from a URL, decodes it, and generates a specific number of peaks (samples).
446
517
  *
@@ -488,4 +559,4 @@ declare const resamplePeaks: (peaks: number[], targetCount: number) => number[];
488
559
  */
489
560
  declare const highlightCode: (code: string) => string[];
490
561
 
491
- export { type EngineListener, type EngineState, PeakAnalyzer, PlayerCore, type PlayerListener, type PlayerState, type Resolution, type TrackInfo, type WaveformConfig, WaveframeEngine, WaveframePlayer, type WaveframePlayerProps, type WaveframeTheme, formatTime, generatePeaks, highlightCode, loadAudioToMemory, resamplePeaks, revokeAudioMemory, useWaveframeStore };
562
+ export { type EngineListener, type EngineState, PeakAnalyzer, PlayerCore, type PlayerListener, type PlayerState, type Resolution, type TrackInfo, type UseWaveframeOptions, Waveform, type WaveformConfig, WaveframeEngine, WaveframePlayer, type WaveframePlayerProps, type WaveframeTheme, formatTime, generatePeaks, highlightCode, loadAudioToMemory, resamplePeaks, revokeAudioMemory, useResampledPeaks, useResizeObserver, useWaveframe, useWaveframeStore };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React$1 from 'react';
2
2
 
3
3
  /**
4
4
  * Defines the core color palette for the Waveframe component.
@@ -90,6 +90,8 @@ type PlayerState = {
90
90
  volume: number;
91
91
  /** Whether the audio is currently muted */
92
92
  muted: boolean;
93
+ /** Any error reported by the audio element */
94
+ error: string | null;
93
95
  };
94
96
  /**
95
97
  * A callback function that receives the latest PlayerState.
@@ -147,7 +149,7 @@ declare class PlayerCore {
147
149
  /**
148
150
  * Toggles between play and pause states.
149
151
  */
150
- togglePlay(): void;
152
+ togglePlay(): Promise<void>;
151
153
  /**
152
154
  * Seeks to a specific time.
153
155
  * @param time Time in seconds.
@@ -338,7 +340,7 @@ interface WaveframePlayerProps {
338
340
  /**
339
341
  * Inline styles for the container
340
342
  */
341
- style?: React.CSSProperties;
343
+ style?: React$1.CSSProperties;
342
344
  /**
343
345
  * The number of bars to render. Use 'auto' to fit the container width.
344
346
  * @default "auto"
@@ -370,7 +372,21 @@ interface WaveframePlayerProps {
370
372
  * This component features a SoundCloud-inspired layout with a prominent
371
373
  * play/pause button positioned next to the track metadata.
372
374
  */
373
- declare const WaveframePlayer: React.FC<WaveframePlayerProps>;
375
+ declare const WaveframePlayer: React$1.FC<WaveframePlayerProps>;
376
+
377
+ interface WaveformProps {
378
+ peaks: number[];
379
+ currentTime: number;
380
+ duration: number;
381
+ waveColor: string;
382
+ progressColor: string;
383
+ height: number;
384
+ onSeek: (percentage: number) => void;
385
+ resolution?: number | 'auto';
386
+ barWidth?: number;
387
+ barGap?: number;
388
+ }
389
+ declare const Waveform: React$1.FC<WaveformProps>;
374
390
 
375
391
  /**
376
392
  * A specialized class for decoding audio data and generating waveform peaks.
@@ -414,6 +430,57 @@ declare class PeakAnalyzer {
414
430
  dispose(): void;
415
431
  }
416
432
 
433
+ /**
434
+ * Configuration options for the `useWaveframe` hook.
435
+ */
436
+ interface UseWaveframeOptions {
437
+ /** Optional pre-computed peaks to skip automatic analysis */
438
+ peaks?: number[];
439
+ /** Optional external engine instance for shared playback across components */
440
+ engine?: WaveframeEngine;
441
+ }
442
+ /**
443
+ * A headless hook that provides full control over the Waveframe engine.
444
+ *
445
+ * It manages the engine's lifecycle, loads the provided media, and returns
446
+ * the current state along with playback controls.
447
+ *
448
+ * @param media The audio source (URL string or Blob/File object).
449
+ * @param options Additional configuration and an optional external engine.
450
+ *
451
+ * @example
452
+ * ```tsx
453
+ * const { state, togglePlay, seek } = useWaveframe('https://example.com/audio.mp3');
454
+ *
455
+ * return (
456
+ * <div>
457
+ * <button onClick={togglePlay}>{state.isPlaying ? 'Pause' : 'Play'}</button>
458
+ * <div onClick={(e) => seek(0.5)}>Seek to Middle</div>
459
+ * </div>
460
+ * );
461
+ * ```
462
+ */
463
+ declare const useWaveframe: (media: string | Blob | undefined, options?: UseWaveframeOptions) => {
464
+ /** The current reactive state of the engine */
465
+ state: EngineState;
466
+ /** The raw WaveframeEngine instance for advanced usage */
467
+ engine: WaveframeEngine;
468
+ /** Toggles playback between playing and paused */
469
+ togglePlay: () => void;
470
+ /** Starts audio playback */
471
+ play: () => void;
472
+ /** Pauses audio playback */
473
+ pause: () => void;
474
+ /** Seeks to a specific percentage (0-1) */
475
+ seek: (percentage: number) => void;
476
+ /** Sets the playback volume (0-1) */
477
+ setVolume: (v: number) => void;
478
+ /** Mutes or unmutes the audio */
479
+ setMuted: (m: boolean) => void;
480
+ /** Manually triggers a re-analysis of the current media */
481
+ analyze: (samples?: number) => Promise<void>;
482
+ };
483
+
417
484
  /**
418
485
  * A React hook that synchronizes a WaveframeEngine's state with a React component.
419
486
  *
@@ -441,6 +508,10 @@ declare class PeakAnalyzer {
441
508
  */
442
509
  declare const useWaveframeStore: (engine: WaveframeEngine) => EngineState;
443
510
 
511
+ declare const useResampledPeaks: (peaks: number[], targetCount: number) => number[];
512
+
513
+ declare const useResizeObserver: (ref: React.RefObject<HTMLElement | null>) => number;
514
+
444
515
  /**
445
516
  * Loads audio from a URL, decodes it, and generates a specific number of peaks (samples).
446
517
  *
@@ -488,4 +559,4 @@ declare const resamplePeaks: (peaks: number[], targetCount: number) => number[];
488
559
  */
489
560
  declare const highlightCode: (code: string) => string[];
490
561
 
491
- export { type EngineListener, type EngineState, PeakAnalyzer, PlayerCore, type PlayerListener, type PlayerState, type Resolution, type TrackInfo, type WaveformConfig, WaveframeEngine, WaveframePlayer, type WaveframePlayerProps, type WaveframeTheme, formatTime, generatePeaks, highlightCode, loadAudioToMemory, resamplePeaks, revokeAudioMemory, useWaveframeStore };
562
+ export { type EngineListener, type EngineState, PeakAnalyzer, PlayerCore, type PlayerListener, type PlayerState, type Resolution, type TrackInfo, type UseWaveframeOptions, Waveform, type WaveformConfig, WaveframeEngine, WaveframePlayer, type WaveframePlayerProps, type WaveframeTheme, formatTime, generatePeaks, highlightCode, loadAudioToMemory, resamplePeaks, revokeAudioMemory, useResampledPeaks, useResizeObserver, useWaveframe, useWaveframeStore };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use client";
2
- import {memo,useRef,useEffect,useState,useMemo,useSyncExternalStore}from'react';import {jsxs,jsx}from'react/jsx-runtime';var A=class{audio;listeners=new Set;_state;constructor(){this.audio=new Audio,this._state={isPlaying:false,currentTime:0,duration:0,volume:1,muted:false},this.initListeners();}initListeners(){this.audio.addEventListener("play",()=>this.updateState({isPlaying:true})),this.audio.addEventListener("pause",()=>this.updateState({isPlaying:false})),this.audio.addEventListener("timeupdate",()=>this.updateState({currentTime:this.audio.currentTime})),this.audio.addEventListener("durationchange",()=>this.updateState({duration:this.audio.duration})),this.audio.addEventListener("volumechange",()=>this.updateState({volume:this.audio.volume,muted:this.audio.muted})),this.audio.addEventListener("ended",()=>this.updateState({isPlaying:false}));}updateState(t){this._state={...this._state,...t},this.notify();}notify(){this.listeners.forEach(t=>t(this._state));}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}get state(){return this._state}setSource(t){this.audio.src=t,this.audio.load();}play(){return this.audio.play()}pause(){this.audio.pause();}togglePlay(){this._state.isPlaying?this.pause():this.play();}seek(t){this.audio.currentTime=t;}setVolume(t){this.audio.volume=t;}setMuted(t){this.audio.muted=t;}dispose(){this.pause(),this.audio.src="",this.listeners.clear();}};var C=class{audioCtx=null;constructor(){}getContext(){return this.audioCtx||(this.audioCtx=new(window.AudioContext||window.webkitAudioContext)),this.audioCtx}async generatePeaks(t,e=512){try{let o;if(typeof t=="string"){let c=await fetch(t);if(!c.ok)throw new Error(`Failed to fetch audio: ${c.statusText}`);o=await c.arrayBuffer();}else o=await t.arrayBuffer();let n=(await this.getContext().decodeAudioData(o)).getChannelData(0),s=Math.floor(n.length/e),l=[];for(let c=0;c<e;c++){let u=0,g=c*s,d=g+s;for(let y=g;y<d;y++){let S=Math.abs(n[y]);S>u&&(u=S);}l.push(u);}let x=Math.max(...l);return l.map(c=>c/(x||1))}catch(o){throw console.error("PeakAnalyzer Error:",o),o}}dispose(){this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null);}};var W=class{player;analyzer;listeners=new Set;_state;_media=null;_objectUrl=null;constructor(){this.player=new A,this.analyzer=new C,this._state={...this.player.state,peaks:[],isAnalyzing:false,error:null},this.player.subscribe(t=>{this.updateState({...t});});}updateState(t){this._state={...this._state,...t},this.notify();}notify(){this.listeners.forEach(t=>t(this._state));}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}getSnapshot(){return this._state}revokeOldSource(){this._objectUrl&&(URL.revokeObjectURL(this._objectUrl),this._objectUrl=null);}load(t,e){if(this._media!==t){this.revokeOldSource(),this._media=t;let i;typeof t=="string"?i=t:(this._objectUrl=URL.createObjectURL(t),i=this._objectUrl),this.player.setSource(i);let a=e&&e.length>0;this.updateState({peaks:e||[],isAnalyzing:false,error:null}),a||this.analyze();}else e&&e!==this._state.peaks&&this.updateState({peaks:e});}async analyze(t=512){if(!this._media){this.updateState({error:"No media loaded to analyze"});return}this.updateState({isAnalyzing:true,error:null});try{let e=await this.analyzer.generatePeaks(this._media,t);this.updateState({peaks:e,isAnalyzing:!1});}catch(e){this.updateState({isAnalyzing:false,error:e instanceof Error?e.message:"Analysis failed"});}}togglePlay(){this.player.togglePlay();}play(){this.player.play();}pause(){this.player.pause();}seek(t){let{duration:e}=this._state;e&&this.player.seek(t*e);}setVolume(t){this.player.setVolume(t);}setMuted(t){this.player.setMuted(t);}dispose(){this.revokeOldSource(),this.player.dispose(),this.analyzer.dispose(),this.listeners.clear();}};var q=r=>useSyncExternalStore(t=>r.subscribe(t),()=>r.getSnapshot());var Q=(r,t={})=>{let{peaks:e,engine:o}=t,i=useMemo(()=>o||new W,[o]),a=o||i,n=q(a);return useEffect(()=>{r&&a.load(r,e);},[a,r,e]),useEffect(()=>()=>{o||i.dispose();},[i,o]),{state:n,engine:a,togglePlay:()=>a.togglePlay(),play:()=>a.play(),pause:()=>a.pause(),seek:s=>a.seek(s),setVolume:s=>a.setVolume(s),setMuted:s=>a.setMuted(s),analyze:s=>a.analyze(s)}};var Nt=async(r,t=512)=>{let e=new C;try{return await e.generatePeaks(r,t)}finally{e.dispose();}},jt=async r=>{let e=await(await fetch(r)).blob();return URL.createObjectURL(e)},At=r=>{r&&r.startsWith("blob:")&&URL.revokeObjectURL(r);};var V=r=>{if(isNaN(r))return "0:00";let t=Math.floor(r/60),e=Math.floor(r%60);return `${t}:${e.toString().padStart(2,"0")}`},Y=(r,t)=>{if(r.length===0)return [];if(r.length===t)return r;let e=new Array(t),o=r.length/t;if(o>1)for(let i=0;i<t;i++){let a=0,n=Math.floor(i*o),s=Math.floor((i+1)*o);for(let l=n;l<s;l++)r[l]>a&&(a=r[l]);e[i]=a;}else for(let i=0;i<t;i++){let a=i*o,n=Math.floor(a),s=Math.min(n+1,r.length-1),l=a-n;e[i]=r[n]+(r[s]-r[n])*l;}return e},Tt=r=>r.split(`
3
- `).map(t=>{let e=t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),o={},i=0,a=(n,s)=>{let l=`__TOKEN_${i++}__`;return o[l]=`<span class="${s}">${n}</span>`,l};return e=e.replace(/("(?:[^"\\]|\\.)*")/g,n=>a(n,"text-[#ce9178]")),e=e.replace(/\b(\d+(\.\d+)?)\b/g,n=>a(n,"text-[#b5cea8]")),e=e.replace(/\b(WaveframePlayer)\b/g,n=>a(n,"text-[#4ec9b0]")),e=e.replace(/\b([a-z][a-zA-Z0-9]+)(?==)/g,n=>a(n,"text-[#9cdcfe]")),e=e.replace(/(&lt;|&gt;|\{|\}|\/|:|,)/g,'<span class="text-gray-500">$1</span>'),Object.entries(o).forEach(([n,s])=>{e=e.replace(n,s);}),e});var tt=(r,t)=>useMemo(()=>Y(r,t),[r,t]);var T=r=>{let[t,e]=useState(0);return useEffect(()=>{if(!r.current)return;let o=new ResizeObserver(i=>{for(let a of i)e(a.contentRect.width);});return o.observe(r.current),()=>o.disconnect()},[r]),t};var D=memo(({artworkUrl:r,title:t,isLoading:e})=>jsxs("div",{className:"relative flex-shrink-0 w-32 h-32 md:w-40 md:h-40 overflow-hidden rounded-[var(--wf-artwork-rounded,0.75rem)] shadow-lg group/artwork",children:[jsx("div",{className:`w-full h-full transition-all duration-700 ${e?"blur-md scale-110":""}`,children:r?jsx("img",{src:r,alt:t,className:"w-full h-full object-cover transition-transform duration-500 group-hover/artwork:scale-110"}):jsx("div",{className:"w-full h-full bg-gradient-to-br from-[var(--wf-placeholder-from,#fb923c)] to-[var(--wf-placeholder-to,#ec4899)] flex items-center justify-center",children:jsx("svg",{className:"w-16 h-16 text-white opacity-50",fill:"currentColor",viewBox:"0 0 24 24",children:jsx("path",{d:"M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"})})})}),e&&jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-black/10 backdrop-blur-[1px]",children:jsx("div",{className:"w-8 h-8 border-4 border-white/30 border-t-white rounded-full animate-spin"})})]}));D.displayName="ArtworkOverlay";var G=memo(({peaks:r,currentTime:t,duration:e,waveColor:o,progressColor:i,height:a,onSeek:n,resolution:s="auto",barWidth:l=2,barGap:x=1})=>{let c=useRef(null),u=useRef(null),g=useRef(null),d=T(g);useEffect(()=>{let b=c.current,f=u.current;if(!b||!f)return;let h=b.getContext("2d"),p=f.getContext("2d");if(!h||!p)return;let k=window.devicePixelRatio||1,P=b.getBoundingClientRect(),_=P.width*k,L=P.height*k;[b,f].forEach(w=>{(w.width!==_||w.height!==L)&&(w.width=_,w.height=L);}),(()=>{if(r.length===0)return;let{width:w,height:m}=b;h.clearRect(0,0,w,m),p.clearRect(0,0,w,m);let O=r.length,N=w/O,E=typeof s=="number"?N*.7:l*k,B=typeof s=="number"?N*.3:x*k;h.lineCap="round",h.lineWidth=E,p.lineCap="round",p.lineWidth=E,r.forEach(($,R)=>{let j=R*(E+B)+E/2,X=$*m*.8,H=(m-X)/2,Z=H+X;h.beginPath(),h.strokeStyle=o,h.moveTo(j,H),h.lineTo(j,Z),h.stroke(),p.beginPath(),p.strokeStyle=i,p.moveTo(j,H),p.lineTo(j,Z),p.stroke();});})();},[r,o,i,s,l,x,a]);let y=b=>{if(g.current&&e){let f=g.current.getBoundingClientRect(),h=b.clientX-f.left,p=Math.max(0,Math.min(1,h/f.width));n(p);}},S=e?t/e*100:0;return jsxs("div",{ref:g,className:"relative w-full cursor-pointer overflow-hidden",style:{height:`${a}px`},onClick:y,children:[jsx("canvas",{ref:c,className:"absolute inset-0 w-full h-full"}),jsx("div",{className:"absolute inset-0 h-full overflow-hidden transition-[width] duration-100 ease-linear pointer-events-none",style:{width:`${S}%`},children:jsx("canvas",{ref:u,className:"absolute h-full",style:{width:`${d}px`}})})]})});G.displayName="Waveform";var ut=memo(({media:r,peaks:t,artwork:e,title:o,artist:i,waveColor:a,progressColor:n,height:s=80,className:l="",style:x,resolution:c="auto",barWidth:u=2,barGap:g=1,theme:d,engine:y})=>{let{state:S,togglePlay:b,seek:f}=Q(r,{peaks:t,engine:y}),{isPlaying:h,currentTime:p,duration:k,peaks:P,isAnalyzing:_}=S,[L,U]=useState(typeof e=="string"?e:void 0);useEffect(()=>{if(e instanceof Blob){let R=URL.createObjectURL(e);return U(R),()=>URL.revokeObjectURL(R)}else U(e);},[e]);let w=useRef(null),m=T(w),O=useMemo(()=>typeof c=="number"?c:m>0?Math.max(1,Math.floor(m/(u+g))):P.length||1,[c,m,u,g,P.length]),N=tt(P,O),E=useMemo(()=>a||(d?d.bg==="#ffffff"?"#e5e7eb":"#374151":"#e5e7eb"),[a,d]),B=n||d?.primary||"#3b82f6",$=useMemo(()=>({...{"--wf-bg-color":d?.bg||"white","--wf-border-color":d?.border||"#f3f4f6","--wf-title-color":d?.text||"#111827","--wf-artist-color":d?.text||"#6b7280","--wf-time-color":d?.text||"#9ca3af","--wf-play-btn-bg":d?.primary||"#3b82f6","--wf-placeholder-from":d?.primary||"#fb923c","--wf-placeholder-to":d?.bg||"#ec4899"},...x}),[d,x]);return jsxs("div",{className:`group relative flex flex-col md:flex-row items-stretch gap-6 p-6 bg-[var(--wf-bg-color,white)] border border-[var(--wf-border-color,#f3f4f6)] rounded-[var(--wf-rounded,1rem)] shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden ${l}`,style:$,children:[jsx(D,{artworkUrl:L,title:o,isLoading:_}),jsxs("div",{className:"flex-1 w-full flex flex-col min-w-0",children:[jsxs("div",{className:"flex items-center gap-4 mb-6",children:[jsx("button",{onClick:b,className:"w-12 h-12 md:w-14 md:h-14 flex-shrink-0 flex items-center justify-center rounded-full bg-[var(--wf-play-btn-bg,#3b82f6)] text-white shadow-[0_4px_12px_rgba(0,0,0,0.15)] hover:shadow-[0_6px_16px_rgba(0,0,0,0.2)] transition-all hover:scale-105 active:scale-95 cursor-pointer border-none outline-none group/play",children:h?jsx("svg",{className:"w-6 h-6 md:w-7 md:h-7",fill:"currentColor",viewBox:"0 0 24 24",children:jsx("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"})}):jsx("svg",{className:"w-6 h-6 md:w-7 md:h-7 ml-1",fill:"currentColor",viewBox:"0 0 24 24",children:jsx("path",{d:"M8 5v14l11-7z"})})}),jsxs("div",{className:"flex-1 flex flex-col min-w-0",children:[jsxs("div",{className:"flex items-center justify-between gap-4",children:[i&&jsx("p",{className:"text-[10px] md:text-xs font-bold uppercase text-[var(--wf-artist-color,#6b7280)] opacity-60 tracking-[0.1em] line-clamp-1",children:i}),jsxs("div",{className:"text-[10px] font-mono text-[var(--wf-time-color,#9ca3af)] tabular-nums flex-shrink-0",children:[V(p)," / ",V(k)]})]}),o&&jsx("h3",{className:"text-lg md:text-xl font-black text-[var(--wf-title-color,#111827)] tracking-tight line-clamp-1 mt-0.5 leading-tight",children:o})]})]}),jsx("div",{className:"mt-auto",ref:w,children:jsx(G,{peaks:N,currentTime:p,duration:k,waveColor:E,progressColor:B,height:s,onSeek:f,resolution:c,barWidth:u,barGap:g})})]})]})});ut.displayName="WaveframePlayer";export{C as PeakAnalyzer,A as PlayerCore,W as WaveframeEngine,ut as WaveframePlayer,V as formatTime,Nt as generatePeaks,Tt as highlightCode,jt as loadAudioToMemory,Y as resamplePeaks,At as revokeAudioMemory,q as useWaveframeStore};//# sourceMappingURL=index.js.map
2
+ import {memo,useRef,useEffect,useState,useMemo,useSyncExternalStore}from'react';import {jsxs,jsx}from'react/jsx-runtime';var T=class{audio;listeners=new Set;_state;constructor(){this.audio=new Audio,this._state={isPlaying:false,currentTime:0,duration:0,volume:1,muted:false,error:null},this.initListeners();}initListeners(){this.audio.addEventListener("play",()=>this.updateState({isPlaying:true,error:null})),this.audio.addEventListener("pause",()=>this.updateState({isPlaying:false})),this.audio.addEventListener("timeupdate",()=>this.updateState({currentTime:this.audio.currentTime})),this.audio.addEventListener("durationchange",()=>this.updateState({duration:this.audio.duration})),this.audio.addEventListener("volumechange",()=>this.updateState({volume:this.audio.volume,muted:this.audio.muted})),this.audio.addEventListener("ended",()=>this.updateState({isPlaying:false})),this.audio.addEventListener("error",()=>{let t=this.audio.error,e="Unknown audio error";if(t)switch(t.code){case t.MEDIA_ERR_ABORTED:e="Playback aborted";break;case t.MEDIA_ERR_NETWORK:e="Network error";break;case t.MEDIA_ERR_DECODE:e="Audio decoding failed";break;case t.MEDIA_ERR_SRC_NOT_SUPPORTED:e="Audio format not supported";break}this.updateState({isPlaying:false,error:e});});}updateState(t){this._state={...this._state,...t},this.notify();}notify(){this.listeners.forEach(t=>t(this._state));}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}get state(){return this._state}setSource(t){this.audio.src=t,this.audio.load(),this.updateState({error:null,currentTime:0,duration:0});}async play(){try{await this.audio.play();}catch(t){let e=t instanceof Error?t.message:"Playback failed";throw this.updateState({isPlaying:false,error:e}),t}}pause(){this.audio.pause();}async togglePlay(){if(this._state.isPlaying)this.pause();else try{await this.play();}catch{}}seek(t){this.audio.currentTime=t;}setVolume(t){this.audio.volume=t;}setMuted(t){this.audio.muted=t;}dispose(){this.pause(),this.audio.src="",this.listeners.clear();}};var R=class{audioCtx=null;constructor(){}getContext(){return this.audioCtx||(this.audioCtx=new(window.AudioContext||window.webkitAudioContext)),this.audioCtx}async generatePeaks(t,e=512){try{let o;if(typeof t=="string"){let c=await fetch(t);if(!c.ok)throw new Error(`Failed to fetch audio: ${c.statusText}`);o=await c.arrayBuffer();}else o=await t.arrayBuffer();let n=(await this.getContext().decodeAudioData(o)).getChannelData(0),s=Math.floor(n.length/e),l=[];for(let c=0;c<e;c++){let u=0,g=c*s,d=g+s;for(let y=g;y<d;y++){let S=Math.abs(n[y]);S>u&&(u=S);}l.push(u);}let x=Math.max(...l);return l.map(c=>c/(x||1))}catch(o){throw console.error("PeakAnalyzer Error:",o),o}}dispose(){this.audioCtx&&(this.audioCtx.close(),this.audioCtx=null);}};var W=class{player;analyzer;listeners=new Set;_state;_media=null;_objectUrl=null;constructor(){this.player=new T,this.analyzer=new R,this._state={...this.player.state,peaks:[],isAnalyzing:false,error:null},this.player.subscribe(t=>{this.updateState({...t});});}updateState(t){this._state={...this._state,...t},this.notify();}notify(){this.listeners.forEach(t=>t(this._state));}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}getSnapshot(){return this._state}revokeOldSource(){this._objectUrl&&(URL.revokeObjectURL(this._objectUrl),this._objectUrl=null);}load(t,e){if(this._media!==t){this.revokeOldSource(),this._media=t;let i;typeof t=="string"?i=t:(this._objectUrl=URL.createObjectURL(t),i=this._objectUrl),this.player.setSource(i);let a=e&&e.length>0;this.updateState({peaks:e||[],isAnalyzing:false,error:null}),a||this.analyze();}else e&&e.length!==this._state.peaks.length&&this.updateState({peaks:e});}async analyze(t=512){if(!this._media){this.updateState({error:"No media loaded to analyze"});return}this.updateState({isAnalyzing:true,error:null});try{let e=await this.analyzer.generatePeaks(this._media,t);this.updateState({peaks:e,isAnalyzing:!1});}catch(e){this.updateState({isAnalyzing:false,error:e instanceof Error?e.message:"Analysis failed"});}}togglePlay(){this.player.togglePlay();}play(){this.player.play();}pause(){this.player.pause();}seek(t){let{duration:e}=this._state;e&&this.player.seek(t*e);}setVolume(t){this.player.setVolume(t);}setMuted(t){this.player.setMuted(t);}dispose(){this.revokeOldSource(),this.player.dispose(),this.analyzer.dispose(),this.listeners.clear();}};var q=r=>useSyncExternalStore(t=>r.subscribe(t),()=>r.getSnapshot());var Q=(r,t={})=>{let{peaks:e,engine:o}=t,i=useMemo(()=>o||new W,[o]),a=o||i,n=q(a);return useEffect(()=>{r&&a.load(r,e);},[a,r,e]),useEffect(()=>()=>{o||i.dispose();},[i,o]),{state:n,engine:a,togglePlay:()=>a.togglePlay(),play:()=>a.play(),pause:()=>a.pause(),seek:s=>a.seek(s),setVolume:s=>a.setVolume(s),setMuted:s=>a.setMuted(s),analyze:s=>a.analyze(s)}};var At=async(r,t=512)=>{let e=new R;try{return await e.generatePeaks(r,t)}finally{e.dispose();}},Lt=async r=>{let e=await(await fetch(r)).blob();return URL.createObjectURL(e)},jt=r=>{r&&r.startsWith("blob:")&&URL.revokeObjectURL(r);};var H=r=>{if(isNaN(r))return "0:00";let t=Math.floor(r/60),e=Math.floor(r%60);return `${t}:${e.toString().padStart(2,"0")}`},Y=(r,t)=>{if(r.length===0)return [];if(r.length===t)return r;let e=new Array(t),o=r.length/t;if(o>1)for(let i=0;i<t;i++){let a=0,n=Math.floor(i*o),s=Math.floor((i+1)*o);for(let l=n;l<s;l++)r[l]>a&&(a=r[l]);e[i]=a;}else for(let i=0;i<t;i++){let a=i*o,n=Math.floor(a),s=Math.min(n+1,r.length-1),l=a-n;e[i]=r[n]+(r[s]-r[n])*l;}return e},Wt=r=>r.split(`
3
+ `).map(t=>{let e=t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),o={},i=0,a=(n,s)=>{let l=`__TOKEN_${i++}__`;return o[l]=`<span class="${s}">${n}</span>`,l};return e=e.replace(/("(?:[^"\\]|\\.)*")/g,n=>a(n,"text-[#ce9178]")),e=e.replace(/\b(\d+(\.\d+)?)\b/g,n=>a(n,"text-[#b5cea8]")),e=e.replace(/\b(WaveframePlayer)\b/g,n=>a(n,"text-[#4ec9b0]")),e=e.replace(/\b([a-z][a-zA-Z0-9]+)(?==)/g,n=>a(n,"text-[#9cdcfe]")),e=e.replace(/(&lt;|&gt;|\{|\}|\/|:|,)/g,'<span class="text-gray-500">$1</span>'),Object.entries(o).forEach(([n,s])=>{e=e.replace(n,s);}),e});var tt=(r,t)=>useMemo(()=>Y(r,t),[r,t]);var U=r=>{let[t,e]=useState(0);return useEffect(()=>{if(!r.current)return;let o=new ResizeObserver(i=>{for(let a of i)e(a.contentRect.width);});return o.observe(r.current),()=>o.disconnect()},[r]),t};var I=memo(({artworkUrl:r,title:t,isLoading:e})=>jsxs("div",{className:"relative flex-shrink-0 w-32 h-32 md:w-40 md:h-40 overflow-hidden rounded-[var(--wf-artwork-rounded,0.75rem)] shadow-lg group/artwork",children:[jsx("div",{className:`w-full h-full transition-all duration-700 ${e?"blur-md scale-110":""}`,children:r?jsx("img",{src:r,alt:t,className:"w-full h-full object-cover transition-transform duration-500 group-hover/artwork:scale-110"}):jsx("div",{className:"w-full h-full bg-gradient-to-br from-[var(--wf-placeholder-from,#fb923c)] to-[var(--wf-placeholder-to,#ec4899)] flex items-center justify-center",children:jsx("svg",{className:"w-16 h-16 text-white opacity-50",fill:"currentColor",viewBox:"0 0 24 24",children:jsx("path",{d:"M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"})})})}),e&&jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-black/10 backdrop-blur-[1px]",children:jsx("div",{className:"w-8 h-8 border-4 border-white/30 border-t-white rounded-full animate-spin"})})]}));I.displayName="ArtworkOverlay";var K=memo(({peaks:r,currentTime:t,duration:e,waveColor:o,progressColor:i,height:a,onSeek:n,resolution:s="auto",barWidth:l=2,barGap:x=1})=>{let c=useRef(null),u=useRef(null),g=useRef(null),d=U(g);useEffect(()=>{let f=c.current,b=u.current;if(!f||!b)return;let h=f.getContext("2d"),p=b.getContext("2d");if(!h||!p)return;let k=window.devicePixelRatio||1,E=f.getBoundingClientRect(),M=E.width*k,N=E.height*k;[f,b].forEach(w=>{(w.width!==M||w.height!==N)&&(w.width=M,w.height=N);}),(()=>{if(r.length===0)return;let{width:w,height:m}=f;h.clearRect(0,0,w,m),p.clearRect(0,0,w,m);let B=r.length,A=w/B,P=typeof s=="number"?A*.7:l*k,D=typeof s=="number"?A*.3:x*k;h.lineCap="round",h.lineWidth=P,p.lineCap="round",p.lineWidth=P,r.forEach((L,C)=>{if(L<=0)return;let j=C*(P+D)+P/2,X=L*m*.8,$=(m-X)/2,Z=$+X;h.beginPath(),h.strokeStyle=o,h.moveTo(j,$),h.lineTo(j,Z),h.stroke(),p.beginPath(),p.strokeStyle=i,p.moveTo(j,$),p.lineTo(j,Z),p.stroke();});})();},[r,o,i,s,l,x,a]);let y=f=>{if(g.current&&e){let b=g.current.getBoundingClientRect(),h=f.clientX-b.left,p=Math.max(0,Math.min(1,h/b.width));n(p);}},S=e?t/e*100:0;return jsxs("div",{ref:g,className:"relative w-full cursor-pointer overflow-hidden",style:{height:`${a}px`},onClick:y,children:[jsx("canvas",{ref:c,className:"absolute inset-0 w-full h-full"}),jsx("div",{className:"absolute inset-0 h-full overflow-hidden transition-[width] duration-100 ease-linear pointer-events-none",style:{width:`${S}%`},children:jsx("canvas",{ref:u,className:"absolute h-full",style:{width:`${d}px`}})})]})});K.displayName="Waveform";var ut=memo(({media:r,peaks:t,artwork:e,title:o,artist:i,waveColor:a,progressColor:n,height:s=80,className:l="",style:x,resolution:c="auto",barWidth:u=2,barGap:g=1,theme:d,engine:y})=>{let{state:S,togglePlay:f,seek:b}=Q(r,{peaks:t,engine:y}),{isPlaying:h,currentTime:p,duration:k,peaks:E,isAnalyzing:M}=S,[N,O]=useState(typeof e=="string"?e:void 0);useEffect(()=>{if(e instanceof Blob){let C=URL.createObjectURL(e);return O(C),()=>URL.revokeObjectURL(C)}else O(e);},[e]);let w=useRef(null),m=U(w),B=useMemo(()=>typeof c=="number"?c:m>0?Math.max(1,Math.floor(m/(u+g))):E.length||1,[c,m,u,g,E.length]),A=tt(E,B),P=useMemo(()=>a||(d?d.bg==="#ffffff"?"#e5e7eb":"#374151":"#e5e7eb"),[a,d]),D=n||d?.primary||"#3b82f6",L=useMemo(()=>({...{"--wf-bg-color":d?.bg||"white","--wf-border-color":d?.border||"#f3f4f6","--wf-title-color":d?.text||"#111827","--wf-artist-color":d?.text||"#6b7280","--wf-time-color":d?.text||"#9ca3af","--wf-play-btn-bg":d?.primary||"#3b82f6","--wf-placeholder-from":d?.primary||"#fb923c","--wf-placeholder-to":d?.bg||"#ec4899"},...x}),[d,x]);return jsxs("div",{className:`group relative flex flex-col md:flex-row items-stretch gap-6 p-6 bg-[var(--wf-bg-color,white)] border border-[var(--wf-border-color,#f3f4f6)] rounded-[var(--wf-rounded,1rem)] shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden ${l}`,style:L,children:[jsx(I,{artworkUrl:N,title:o,isLoading:M}),jsxs("div",{className:"flex-1 w-full flex flex-col min-w-0",children:[jsxs("div",{className:"flex items-center gap-4 mb-6",children:[jsx("button",{onClick:f,className:"w-12 h-12 md:w-14 md:h-14 flex-shrink-0 flex items-center justify-center rounded-full bg-[var(--wf-play-btn-bg,#3b82f6)] text-white shadow-[0_4px_12px_rgba(0,0,0,0.15)] hover:shadow-[0_6px_16px_rgba(0,0,0,0.2)] transition-all hover:scale-105 active:scale-95 cursor-pointer border-none outline-none group/play",children:h?jsx("svg",{className:"w-6 h-6 md:w-7 md:h-7",fill:"currentColor",viewBox:"0 0 24 24",children:jsx("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"})}):jsx("svg",{className:"w-6 h-6 md:w-7 md:h-7 ml-1",fill:"currentColor",viewBox:"0 0 24 24",children:jsx("path",{d:"M8 5v14l11-7z"})})}),jsxs("div",{className:"flex-1 flex flex-col min-w-0",children:[jsxs("div",{className:"flex items-center justify-between gap-4",children:[i&&jsx("p",{className:"text-[10px] md:text-xs font-bold uppercase text-[var(--wf-artist-color,#6b7280)] opacity-60 tracking-[0.1em] line-clamp-1",children:i}),jsxs("div",{className:"text-[10px] font-mono text-[var(--wf-time-color,#9ca3af)] tabular-nums flex-shrink-0",children:[H(p)," / ",H(k)]})]}),o&&jsx("h3",{className:"text-lg md:text-xl font-black text-[var(--wf-title-color,#111827)] tracking-tight line-clamp-1 mt-0.5 leading-tight",children:o})]})]}),jsx("div",{className:"mt-auto",ref:w,children:jsx(K,{peaks:A,currentTime:p,duration:k,waveColor:P,progressColor:D,height:s,onSeek:b,resolution:c,barWidth:u,barGap:g})})]})]})});ut.displayName="WaveframePlayer";export{R as PeakAnalyzer,T as PlayerCore,K as Waveform,W as WaveframeEngine,ut as WaveframePlayer,H as formatTime,At as generatePeaks,Wt as highlightCode,Lt as loadAudioToMemory,Y as resamplePeaks,jt as revokeAudioMemory,tt as useResampledPeaks,U as useResizeObserver,Q as useWaveframe,q as useWaveframeStore};//# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/PlayerCore.ts","../src/core/PeakAnalyzer.ts","../src/core/WaveframeEngine.ts","../src/hooks/useWaveframeStore.ts","../src/hooks/useWaveframe.ts","../src/utils/audio.ts","../src/utils/index.ts","../src/hooks/useResampledPeaks.ts","../src/hooks/useResizeObserver.ts","../src/molecules/ArtworkOverlay.tsx","../src/organisms/Waveform.tsx","../src/components/WaveframePlayer.tsx"],"names":["PlayerCore","patch","l","listener","url","time","volume","muted","PeakAnalyzer","media","samples","arrayBuffer","response","channelData","blockSize","peaks","i","max","start","end","j","val","maxPeak","p","error","WaveframeEngine","playerState","sourceUrl","hasPeaks","percentage","duration","useWaveframeStore","engine","useSyncExternalStore","callback","useWaveframe","options","providedEngine","internalEngine","useMemo","state","useEffect","v","m","generatePeaks","audioUrl","analyzer","loadAudioToMemory","blob","revokeAudioMemory","formatTime","seconds","min","sec","resamplePeaks","targetCount","resampled","ratio","position","index","nextIndex","fraction","highlightCode","code","line","h","tokens","counter","addToken","cls","id","html","useResampledPeaks","useResizeObserver","ref","width","setWidth","useState","observer","entries","entry","ArtworkOverlay","memo","artworkUrl","title","isLoading","jsxs","jsx","Waveform","currentTime","waveColor","progressColor","height","onSeek","resolution","barWidth","barGap","canvasRef","useRef","progressCanvasRef","containerRef","containerWidth","canvas","progressCanvas","ctx","pCtx","dpr","rect","targetWidth","targetHeight","c","barCount","actualBarTotalWidth","actualBarWidth","actualBarGap","peak","x","barHeight","yStart","yEnd","handleSeek","e","progressPercent","WaveframePlayer","propPeaks","artwork","artist","propWaveColor","propProgressColor","className","propStyle","theme","togglePlay","seek","isPlaying","isAnalyzing","resolvedArtworkUrl","setResolvedArtworkUrl","resampledPeaks","mergedStyle"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Represents the low-level playback state of the audio element.\n */\nexport type PlayerState = {\n /** Whether the audio is currently playing */\n isPlaying: boolean;\n /** The current playback time in seconds */\n currentTime: number;\n /** The total duration of the track in seconds */\n duration: number;\n /** The current volume level (0 to 1) */\n volume: number;\n /** Whether the audio is currently muted */\n muted: boolean;\n};\n\n/**\n * A callback function that receives the latest PlayerState.\n */\nexport type PlayerListener = (state: PlayerState) => void;\n\n/**\n * The internal core class responsible for managing the HTMLAudioElement.\n * \n * It handles raw playback logic, volume control, and synchronizes the \n * internal `PlayerState` with DOM events from the underlying `Audio` instance.\n */\nexport class PlayerCore {\n private audio: HTMLAudioElement;\n private listeners: Set<PlayerListener> = new Set();\n private _state: PlayerState;\n\n /**\n * Initializes a new PlayerCore instance and sets up event listeners on a new Audio object.\n */\n constructor() {\n this.audio = new Audio();\n this._state = {\n isPlaying: false,\n currentTime: 0,\n duration: 0,\n volume: 1,\n muted: false,\n };\n\n this.initListeners();\n }\n\n /**\n * Subscribes to various HTMLMediaElement events to keep the internal state in sync.\n */\n private initListeners() {\n this.audio.addEventListener('play', () => this.updateState({ isPlaying: true }));\n this.audio.addEventListener('pause', () => this.updateState({ isPlaying: false }));\n this.audio.addEventListener('timeupdate', () => this.updateState({ currentTime: this.audio.currentTime }));\n this.audio.addEventListener('durationchange', () => this.updateState({ duration: this.audio.duration }));\n this.audio.addEventListener('volumechange', () => this.updateState({ \n volume: this.audio.volume, \n muted: this.audio.muted \n }));\n this.audio.addEventListener('ended', () => this.updateState({ isPlaying: false }));\n }\n\n /**\n * Updates the internal state and notifies subscribers.\n */\n private updateState(patch: Partial<PlayerState>) {\n this._state = { ...this._state, ...patch };\n this.notify();\n }\n\n /**\n * Triggers all registered listener callbacks.\n */\n private notify() {\n this.listeners.forEach(l => l(this._state));\n }\n\n /**\n * Registers a listener for state updates.\n * @param listener The callback function.\n * @returns An unsubscribe function.\n */\n public subscribe(listener: PlayerListener) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Returns the current playback state.\n */\n public get state() {\n return this._state;\n }\n\n /**\n * Updates the source URL of the underlying audio element.\n * @param url The audio source URL.\n */\n public setSource(url: string) {\n this.audio.src = url;\n this.audio.load();\n }\n\n /**\n * Starts playback. Returns a promise that resolves when playback begins.\n */\n public play() {\n return this.audio.play();\n }\n\n /**\n * Pauses playback.\n */\n public pause() {\n this.audio.pause();\n }\n\n /**\n * Toggles between play and pause states.\n */\n public togglePlay() {\n if (this._state.isPlaying) {\n this.pause();\n } else {\n this.play();\n }\n }\n\n /**\n * Seeks to a specific time.\n * @param time Time in seconds.\n */\n public seek(time: number) {\n this.audio.currentTime = time;\n }\n\n /**\n * Sets the volume level.\n * @param volume Level from 0 to 1.\n */\n public setVolume(volume: number) {\n this.audio.volume = volume;\n }\n\n /**\n * Mutes or unmutes the audio element.\n * @param muted Mute status.\n */\n public setMuted(muted: boolean) {\n this.audio.muted = muted;\n }\n\n /**\n * Cleans up the audio element and removes all listeners.\n */\n public dispose() {\n this.pause();\n this.audio.src = '';\n this.listeners.clear();\n }\n}\n","/**\n * A specialized class for decoding audio data and generating waveform peaks.\n * \n * It leverages the Web Audio API (`AudioContext`) to process audio buffers \n * and extract amplitude data for visualization.\n */\nexport class PeakAnalyzer {\n private audioCtx: AudioContext | null = null;\n\n /**\n * Initializes the analyzer. AudioContext creation is deferred to the first use \n * to comply with browser autoplay and resource management policies.\n */\n constructor() {}\n\n /**\n * Lazily creates or returns the existing AudioContext.\n */\n private getContext(): AudioContext {\n if (!this.audioCtx) {\n this.audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n return this.audioCtx;\n }\n\n /**\n * Processes media (URL or Blob) and generates a set of normalized peaks.\n * \n * @param media The URL string or Blob object to analyze.\n * @param samples The number of peaks (bars) to generate. Defaults to 512.\n * @returns A promise resolving to an array of normalized peak values (0 to 1).\n * \n * @example\n * ```typescript\n * const analyzer = new PeakAnalyzer();\n * \n * // Analyze from URL\n * const peaksFromUrl = await analyzer.generatePeaks('https://example.com/audio.mp3');\n * \n * // Analyze from Blob\n * const peaksFromBlob = await analyzer.generatePeaks(myAudioBlob);\n * ```\n */\n public async generatePeaks(media: string | Blob, samples: number = 512): Promise<number[]> {\n try {\n let arrayBuffer: ArrayBuffer;\n\n if (typeof media === 'string') {\n const response = await fetch(media);\n if (!response.ok) throw new Error(`Failed to fetch audio: ${response.statusText}`);\n arrayBuffer = await response.arrayBuffer();\n } else {\n arrayBuffer = await media.arrayBuffer();\n }\n\n const audioCtx = this.getContext();\n const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);\n \n const channelData = audioBuffer.getChannelData(0); // Use left channel\n const blockSize = Math.floor(channelData.length / samples);\n const peaks = [];\n\n for (let i = 0; i < samples; i++) {\n let max = 0;\n const start = i * blockSize;\n const end = start + blockSize;\n \n for (let j = start; j < end; j++) {\n const val = Math.abs(channelData[j]);\n if (val > max) max = val;\n }\n peaks.push(max);\n }\n \n // Normalize peaks to 0-1 range\n const maxPeak = Math.max(...peaks);\n return peaks.map(p => p / (maxPeak || 1));\n } catch (error) {\n console.error('PeakAnalyzer Error:', error);\n throw error;\n }\n }\n\n /**\n * Closes the AudioContext and releases system audio resources.\n */\n public dispose() {\n if (this.audioCtx) {\n this.audioCtx.close();\n this.audioCtx = null;\n }\n }\n}\n","import { PlayerCore, PlayerState } from './PlayerCore';\nimport { PeakAnalyzer } from './PeakAnalyzer';\n\n/**\n * Represents the complete state of the Waveframe engine, combining playback and analysis.\n */\nexport type EngineState = PlayerState & {\n /** The current set of generated or provided waveform peaks (0-1 range) */\n peaks: number[];\n /** Whether an audio analysis process is currently in progress */\n isAnalyzing: boolean;\n /** Any error message encountered during playback or analysis */\n error: string | null;\n};\n\n/**\n * A callback function that receives the latest EngineState.\n */\nexport type EngineListener = (state: EngineState) => void;\n\n/**\n * The orchestrator class for Waveframe. \n * \n * It manages the lifecycle of audio playback and waveform analysis, providing a unified \n * store-like interface that can be easily consumed by React or other frameworks.\n * \n * @example\n * ```typescript\n * const engine = new WaveframeEngine();\n * \n * // Load from URL (automatic analysis if peaks omitted)\n * engine.load('https://example.com/audio.mp3');\n * \n * // Load from Blob with pre-computed peaks\n * engine.load(myBlob, [0.1, 0.5, 0.8]);\n * \n * // Subscription\n * const unsubscribe = engine.subscribe((state) => {\n * console.log('Current time:', state.currentTime);\n * });\n * ```\n */\nexport class WaveframeEngine {\n private player: PlayerCore;\n private analyzer: PeakAnalyzer;\n private listeners: Set<EngineListener> = new Set();\n private _state: EngineState;\n\n private _media: string | Blob | null = null;\n private _objectUrl: string | null = null;\n\n /**\n * Creates a new instance of the WaveframeEngine.\n * Initializes internal PlayerCore and PeakAnalyzer.\n */\n constructor() {\n this.player = new PlayerCore();\n this.analyzer = new PeakAnalyzer();\n \n this._state = {\n ...this.player.state,\n peaks: [],\n isAnalyzing: false,\n error: null,\n };\n\n // Subscribe to player updates\n this.player.subscribe((playerState) => {\n this.updateState({ ...playerState });\n });\n }\n\n /**\n * Internal method to update the state and notify all subscribers.\n */\n private updateState(patch: Partial<EngineState>) {\n this._state = { ...this._state, ...patch };\n this.notify();\n }\n\n /**\n * Notifies all registered listeners of a state change.\n */\n private notify() {\n this.listeners.forEach(l => l(this._state));\n }\n\n // --- Store Interface ---\n\n /**\n * Registers a listener to be called whenever the engine state changes.\n * @param listener The callback function.\n * @returns An unsubscribe function.\n */\n public subscribe(listener: EngineListener) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Returns a snapshot of the current engine state.\n * Useful for `useSyncExternalStore`.\n */\n public getSnapshot(): EngineState {\n return this._state;\n }\n\n // --- Actions ---\n\n /**\n * Revokes any existing Object URLs to prevent memory leaks.\n */\n private revokeOldSource() {\n if (this._objectUrl) {\n URL.revokeObjectURL(this._objectUrl);\n this._objectUrl = null;\n }\n }\n\n /**\n * Loads media (URL or Blob) into the player.\n * \n * If a string is passed, it's treated as a URL and used directly for playback.\n * If a Blob is passed, an Object URL is created for playback.\n * \n * If `peaks` are not provided, it automatically triggers an analysis.\n * \n * @param media The audio source (URL string or Blob/File object).\n * @param peaks Optional pre-generated peaks for the waveform.\n */\n public load(media: string | Blob, peaks?: number[]) {\n const isNewMedia = this._media !== media;\n \n if (isNewMedia) {\n this.revokeOldSource();\n this._media = media;\n\n let sourceUrl: string;\n if (typeof media === 'string') {\n sourceUrl = media;\n } else {\n this._objectUrl = URL.createObjectURL(media);\n sourceUrl = this._objectUrl;\n }\n\n this.player.setSource(sourceUrl);\n \n const hasPeaks = peaks && peaks.length > 0;\n this.updateState({ \n peaks: peaks || [], \n isAnalyzing: false, \n error: null \n });\n\n // Automatic analysis if peaks are missing\n if (!hasPeaks) {\n this.analyze();\n }\n } else if (peaks && peaks !== this._state.peaks) {\n // Update peaks if they change even if media is same\n this.updateState({ peaks });\n }\n }\n\n /**\n * Analyzes the current media to generate waveform peaks.\n * @param samples The number of peaks to generate. Defaults to 512.\n */\n public async analyze(samples: number = 512) {\n if (!this._media) {\n this.updateState({ error: 'No media loaded to analyze' });\n return;\n }\n\n this.updateState({ isAnalyzing: true, error: null });\n try {\n const peaks = await this.analyzer.generatePeaks(this._media, samples);\n this.updateState({ peaks, isAnalyzing: false });\n } catch (e) {\n this.updateState({ \n isAnalyzing: false, \n error: e instanceof Error ? e.message : 'Analysis failed' \n });\n }\n }\n\n /**\n * Toggles playback between playing and paused.\n */\n public togglePlay() {\n this.player.togglePlay();\n }\n\n /**\n * Starts audio playback.\n */\n public play() {\n this.player.play();\n }\n\n /**\n * Pauses audio playback.\n */\n public pause() {\n this.player.pause();\n }\n\n /**\n * Seeks to a specific position in the track.\n * @param percentage The seek position as a decimal (0 to 1).\n */\n public seek(percentage: number) {\n const { duration } = this._state;\n if (duration) {\n this.player.seek(percentage * duration);\n }\n }\n\n /**\n * Sets the playback volume.\n * @param volume The volume level (0 to 1).\n */\n public setVolume(volume: number) {\n this.player.setVolume(volume);\n }\n\n /**\n * Mutes or unmutes the audio.\n * @param muted Whether the audio should be muted.\n */\n public setMuted(muted: boolean) {\n this.player.setMuted(muted);\n }\n\n /**\n * Disposes of the engine, pausing playback and clearing all listeners and resources.\n */\n public dispose() {\n this.revokeOldSource();\n this.player.dispose();\n this.analyzer.dispose();\n this.listeners.clear();\n }\n}\n","import { useSyncExternalStore } from 'react';\nimport { WaveframeEngine, EngineState } from '../core/WaveframeEngine';\n\n/**\n * A React hook that synchronizes a WaveframeEngine's state with a React component.\n * \n * It uses `useSyncExternalStore` for high-performance updates, ensuring that \n * the component only re-renders when the engine's state snapshot actually changes.\n * \n * @param engine The WaveframeEngine instance to subscribe to.\n * @returns The current EngineState (isPlaying, currentTime, peaks, etc.).\n * \n * @example\n * ```tsx\n * const MyPlayer = ({ engine }: { engine: WaveframeEngine }) => {\n * const { isPlaying, currentTime, duration } = useWaveframeStore(engine);\n * \n * return (\n * <div>\n * <button onClick={() => engine.togglePlay()}>\n * {isPlaying ? 'Pause' : 'Play'}\n * </button>\n * <p>{currentTime.toFixed(2)} / {duration.toFixed(2)}</p>\n * </div>\n * );\n * };\n * ```\n */\nexport const useWaveframeStore = (engine: WaveframeEngine): EngineState => {\n return useSyncExternalStore(\n (callback) => engine.subscribe(callback),\n () => engine.getSnapshot()\n );\n};\n","import { useMemo, useEffect } from 'react';\nimport { WaveframeEngine, EngineState } from '../core/WaveframeEngine';\nimport { useWaveframeStore } from './useWaveframeStore';\n\n/**\n * Configuration options for the `useWaveframe` hook.\n */\nexport interface UseWaveframeOptions {\n /** Optional pre-computed peaks to skip automatic analysis */\n peaks?: number[];\n /** Optional external engine instance for shared playback across components */\n engine?: WaveframeEngine;\n}\n\n/**\n * A headless hook that provides full control over the Waveframe engine.\n * \n * It manages the engine's lifecycle, loads the provided media, and returns \n * the current state along with playback controls.\n * \n * @param media The audio source (URL string or Blob/File object).\n * @param options Additional configuration and an optional external engine.\n * \n * @example\n * ```tsx\n * const { state, togglePlay, seek } = useWaveframe('https://example.com/audio.mp3');\n * \n * return (\n * <div>\n * <button onClick={togglePlay}>{state.isPlaying ? 'Pause' : 'Play'}</button>\n * <div onClick={(e) => seek(0.5)}>Seek to Middle</div>\n * </div>\n * );\n * ```\n */\nexport const useWaveframe = (media: string | Blob | undefined, options: UseWaveframeOptions = {}) => {\n const { peaks, engine: providedEngine } = options;\n\n // Initialize engine (only once)\n const internalEngine = useMemo(() => providedEngine || new WaveframeEngine(), [providedEngine]);\n const engine = providedEngine || internalEngine;\n\n // Subscribe to engine state\n const state = useWaveframeStore(engine);\n\n // Sync media with engine\n useEffect(() => {\n if (media) {\n engine.load(media, peaks);\n }\n }, [engine, media, peaks]);\n\n // Handle disposal\n useEffect(() => {\n return () => {\n // Only dispose if we created it internally\n if (!providedEngine) {\n internalEngine.dispose();\n }\n };\n }, [internalEngine, providedEngine]);\n\n return {\n /** The current reactive state of the engine */\n state,\n /** The raw WaveframeEngine instance for advanced usage */\n engine,\n /** Toggles playback between playing and paused */\n togglePlay: () => engine.togglePlay(),\n /** Starts audio playback */\n play: () => engine.play(),\n /** Pauses audio playback */\n pause: () => engine.pause(),\n /** Seeks to a specific percentage (0-1) */\n seek: (percentage: number) => engine.seek(percentage),\n /** Sets the playback volume (0-1) */\n setVolume: (v: number) => engine.setVolume(v),\n /** Mutes or unmutes the audio */\n setMuted: (m: boolean) => engine.setMuted(m),\n /** Manually triggers a re-analysis of the current media */\n analyze: (samples?: number) => engine.analyze(samples),\n };\n};\n","/**\n * Advanced Audio Utilities using Web Audio API\n */\nimport { PeakAnalyzer } from '../core/PeakAnalyzer';\n\n/**\n * Loads audio from a URL, decodes it, and generates a specific number of peaks (samples).\n * \n * This is a high-level utility function that internally manages a `PeakAnalyzer` instance.\n * \n * @param audioUrl The URL of the audio file to analyze.\n * @param samples The number of peaks (bars) to generate. Defaults to 512.\n * @returns A promise resolving to an array of normalized peak values (0 to 1).\n * \n * @example\n * ```typescript\n * const peaks = await generatePeaks('https://example.com/audio.mp3', 256);\n * ```\n */\nexport const generatePeaks = async (audioUrl: string, samples: number = 512): Promise<number[]> => {\n const analyzer = new PeakAnalyzer();\n try {\n return await analyzer.generatePeaks(audioUrl, samples);\n } finally {\n analyzer.dispose();\n }\n};\n\n/**\n * Loads audio into memory as a Blob and returns a temporary Object URL.\n * \n * Useful for ensuring audio data is fully loaded locally before starting \n * playback or analysis, which can help with CORS issues or slow networks.\n * \n * @param url The URL of the remote audio file.\n * @returns A promise resolving to a temporary `blob:` URL.\n */\nexport const loadAudioToMemory = async (url: string): Promise<string> => {\n const response = await fetch(url);\n const blob = await response.blob();\n return URL.createObjectURL(blob);\n};\n\n/**\n * Cleanup function to prevent memory leaks from Object URLs.\n * \n * Call this when a `blob:` URL is no longer needed (e.g., when the component unmounts).\n * \n * @param url The Object URL to revoke.\n */\nexport const revokeAudioMemory = (url: string) => {\n if (url && url.startsWith('blob:')) {\n URL.revokeObjectURL(url);\n }\n};\n","/**\n * Formats seconds into a M:SS string\n */\nexport const formatTime = (seconds: number): string => {\n if (isNaN(seconds)) return '0:00';\n const min = Math.floor(seconds / 60);\n const sec = Math.floor(seconds % 60);\n return `${min}:${sec.toString().padStart(2, '0')}`;\n};\n\n/**\n * Resamples an array of peaks to a target count using bucket-max or linear interpolation\n */\nexport const resamplePeaks = (peaks: number[], targetCount: number): number[] => {\n if (peaks.length === 0) return [];\n if (peaks.length === targetCount) return peaks;\n\n const resampled = new Array(targetCount);\n const ratio = peaks.length / targetCount;\n\n if (ratio > 1) {\n // Downsampling: Bucket Max\n for (let i = 0; i < targetCount; i++) {\n let max = 0;\n const start = Math.floor(i * ratio);\n const end = Math.floor((i + 1) * ratio);\n for (let j = start; j < end; j++) {\n if (peaks[j] > max) max = peaks[j];\n }\n resampled[i] = max;\n }\n } else {\n // Upsampling: Linear Interpolation\n for (let i = 0; i < targetCount; i++) {\n const position = i * ratio;\n const index = Math.floor(position);\n const nextIndex = Math.min(index + 1, peaks.length - 1);\n const fraction = position - index;\n resampled[i] = peaks[index] + (peaks[nextIndex] - peaks[index]) * fraction;\n }\n }\n return resampled;\n};\n\n/**\n * High-performance token-based syntax highlighter for React snippets\n */\nexport const highlightCode = (code: string): string[] => {\n return code.split('\\n').map((line) => {\n // 1. Escape basic HTML\n let h = line.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\n // 2. Identify and tokenize segments to avoid nested replacement issues\n const tokens: { [key: string]: string } = {};\n let counter = 0;\n const addToken = (val: string, cls: string) => {\n const id = `__TOKEN_${counter++}__`;\n tokens[id] = `<span class=\"${cls}\">${val}</span>`;\n return id;\n };\n\n // Strings\n h = h.replace(/(\"(?:[^\"\\\\]|\\\\.)*\")/g, (m) => addToken(m, 'text-[#ce9178]'));\n // Numbers\n h = h.replace(/\\b(\\d+(\\.\\d+)?)\\b/g, (m) => addToken(m, 'text-[#b5cea8]'));\n // Component Name\n h = h.replace(/\\b(WaveframePlayer)\\b/g, (m) => addToken(m, 'text-[#4ec9b0]'));\n // Props (anything followed by =)\n h = h.replace(/\\b([a-z][a-zA-Z0-9]+)(?==)/g, (m) => addToken(m, 'text-[#9cdcfe]'));\n\n // 3. Style remaining symbols\n h = h.replace(/(&lt;|&gt;|\\{|\\}|\\/|:|,)/g, '<span class=\"text-gray-500\">$1</span>');\n\n // 4. In-place token resolution\n Object.entries(tokens).forEach(([id, html]) => {\n h = h.replace(id, html);\n });\n\n return h;\n });\n};\n\nexport * from './audio';\n","import { useMemo } from 'react';\nimport { resamplePeaks } from '../utils';\n\nexport const useResampledPeaks = (peaks: number[], targetCount: number) => {\n return useMemo(() => resamplePeaks(peaks, targetCount), [peaks, targetCount]);\n};\n","import { useState, useEffect } from 'react';\n\nexport const useResizeObserver = (ref: React.RefObject<HTMLElement | null>) => {\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n if (!ref.current) return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setWidth(entry.contentRect.width);\n }\n });\n\n observer.observe(ref.current);\n return () => observer.disconnect();\n }, [ref]);\n\n return width;\n};\n","import React, { memo } from 'react';\n\n/**\n * Props for the ArtworkOverlay component.\n */\ninterface ArtworkOverlayProps {\n /** The URL or Object URL of the artwork image */\n artworkUrl?: string;\n /** The title of the track (used for alt text) */\n title?: string;\n /** Whether the artwork is currently being processed or the audio is analyzing */\n isLoading?: boolean;\n}\n\n/**\n * A purely visual component for displaying track artwork.\n * \n * It handles loading states with a blur effect and provides a consistent \n * container for the track image.\n */\nexport const ArtworkOverlay: React.FC<ArtworkOverlayProps> = memo(({ \n artworkUrl, \n title, \n isLoading\n}) => {\n return (\n <div className=\"relative flex-shrink-0 w-32 h-32 md:w-40 md:h-40 overflow-hidden rounded-[var(--wf-artwork-rounded,0.75rem)] shadow-lg group/artwork\">\n <div className={`w-full h-full transition-all duration-700 ${isLoading ? 'blur-md scale-110' : ''}`}>\n {artworkUrl ? (\n <img\n src={artworkUrl}\n alt={title}\n className=\"w-full h-full object-cover transition-transform duration-500 group-hover/artwork:scale-110\"\n />\n ) : (\n <div className=\"w-full h-full bg-gradient-to-br from-[var(--wf-placeholder-from,#fb923c)] to-[var(--wf-placeholder-to,#ec4899)] flex items-center justify-center\">\n <svg className=\"w-16 h-16 text-white opacity-50\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z\" />\n </svg>\n </div>\n )}\n </div>\n\n {isLoading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/10 backdrop-blur-[1px]\">\n <div className=\"w-8 h-8 border-4 border-white/30 border-t-white rounded-full animate-spin\" />\n </div>\n )}\n </div>\n );\n});\n\nArtworkOverlay.displayName = 'ArtworkOverlay';\n","import React, { useRef, useEffect, memo } from 'react';\nimport { useResizeObserver } from '../hooks/useResizeObserver';\n\ninterface WaveformProps {\n peaks: number[];\n currentTime: number;\n duration: number;\n waveColor: string;\n progressColor: string;\n height: number;\n onSeek: (percentage: number) => void;\n resolution?: number | 'auto';\n barWidth?: number;\n barGap?: number;\n}\n\nexport const Waveform: React.FC<WaveformProps> = memo(({\n peaks,\n currentTime,\n duration,\n waveColor,\n progressColor,\n height,\n onSeek,\n resolution = 'auto',\n barWidth = 2,\n barGap = 1,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const progressCanvasRef = useRef<HTMLCanvasElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const containerWidth = useResizeObserver(containerRef);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n const progressCanvas = progressCanvasRef.current;\n if (!canvas || !progressCanvas) return;\n\n const ctx = canvas.getContext('2d');\n const pCtx = progressCanvas.getContext('2d');\n if (!ctx || !pCtx) return;\n\n const dpr = window.devicePixelRatio || 1;\n const rect = canvas.getBoundingClientRect();\n const targetWidth = rect.width * dpr;\n const targetHeight = rect.height * dpr;\n\n [canvas, progressCanvas].forEach(c => {\n if (c.width !== targetWidth || c.height !== targetHeight) {\n c.width = targetWidth;\n c.height = targetHeight;\n }\n });\n\n const draw = () => {\n if (peaks.length === 0) return;\n const { width, height } = canvas;\n \n ctx.clearRect(0, 0, width, height);\n pCtx.clearRect(0, 0, width, height);\n\n const barCount = peaks.length;\n const actualBarTotalWidth = width / barCount;\n const actualBarWidth = typeof resolution === 'number' \n ? actualBarTotalWidth * 0.7 \n : barWidth * dpr;\n const actualBarGap = typeof resolution === 'number'\n ? actualBarTotalWidth * 0.3\n : barGap * dpr;\n\n ctx.lineCap = 'round';\n ctx.lineWidth = actualBarWidth;\n pCtx.lineCap = 'round';\n pCtx.lineWidth = actualBarWidth;\n\n peaks.forEach((peak, index) => {\n const x = index * (actualBarWidth + actualBarGap) + actualBarWidth / 2;\n const barHeight = peak * height * 0.8;\n const yStart = (height - barHeight) / 2;\n const yEnd = yStart + barHeight;\n\n ctx.beginPath();\n ctx.strokeStyle = waveColor;\n ctx.moveTo(x, yStart);\n ctx.lineTo(x, yEnd);\n ctx.stroke();\n\n pCtx.beginPath();\n pCtx.strokeStyle = progressColor;\n pCtx.moveTo(x, yStart);\n pCtx.lineTo(x, yEnd);\n pCtx.stroke();\n });\n };\n\n draw();\n }, [peaks, waveColor, progressColor, resolution, barWidth, barGap, height]);\n\n const handleSeek = (e: React.MouseEvent<HTMLDivElement>) => {\n if (containerRef.current && duration) {\n const rect = containerRef.current.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const percentage = Math.max(0, Math.min(1, x / rect.width));\n onSeek(percentage);\n }\n };\n\n const progressPercent = duration ? (currentTime / duration) * 100 : 0;\n\n return (\n <div \n ref={containerRef} \n className=\"relative w-full cursor-pointer overflow-hidden\" \n style={{ height: `${height}px` }}\n onClick={handleSeek}\n >\n <canvas ref={canvasRef} className=\"absolute inset-0 w-full h-full\" />\n <div \n className=\"absolute inset-0 h-full overflow-hidden transition-[width] duration-100 ease-linear pointer-events-none\"\n style={{ width: `${progressPercent}%` }}\n >\n <canvas ref={progressCanvasRef} className=\"absolute h-full\" style={{ width: `${containerWidth}px` }} />\n </div>\n </div>\n );\n});\n\nWaveform.displayName = 'Waveform';\n","import React, { memo, useMemo, useRef, useState, useEffect } from 'react';\nimport { WaveframeTheme } from '../types';\nimport { WaveframeEngine } from '../core/WaveframeEngine';\nimport { useWaveframe } from '../hooks/useWaveframe';\nimport { useResampledPeaks } from '../hooks/useResampledPeaks';\nimport { useResizeObserver } from '../hooks/useResizeObserver';\nimport { ArtworkOverlay } from '../molecules/ArtworkOverlay';\nimport { Waveform } from '../organisms/Waveform';\nimport { formatTime } from '../utils';\n\n/**\n * Props for the WaveframePlayer component\n */\nexport interface WaveframePlayerProps {\n /**\n * The audio source to play. Can be a URL string or a Blob/File object.\n */\n media?: string | Blob;\n /**\n * Optional pre-generated peaks for the waveform (0-1 range).\n * If omitted or empty, the player will automatically analyze the media.\n */\n peaks?: number[];\n /**\n * The artwork source to display. Can be a URL string or a Blob/File object.\n */\n artwork?: string | Blob;\n /**\n * The title of the track\n */\n title?: string;\n /**\n * The artist of the track\n */\n artist?: string;\n /**\n * The base color of the waveform bars\n * @default \"#e5e7eb\" (light) or \"#374151\" (dark)\n */\n waveColor?: string;\n /**\n * The color of the played progress part of the waveform\n * @default theme.primary or \"#3b82f6\"\n */\n progressColor?: string;\n /**\n * The height of the waveform in pixels\n * @default 80\n */\n height?: number;\n /**\n * Additional CSS classes for the container\n */\n className?: string;\n /**\n * Inline styles for the container\n */\n style?: React.CSSProperties;\n /**\n * The number of bars to render. Use 'auto' to fit the container width.\n * @default \"auto\"\n */\n resolution?: number | 'auto';\n /**\n * The width of each bar in pixels (if resolution is 'auto')\n * @default 2\n */\n barWidth?: number;\n /**\n * The gap between bars in pixels (if resolution is 'auto')\n * @default 1\n */\n barGap?: number;\n /**\n * Custom theme configuration\n */\n theme?: WaveframeTheme;\n /**\n * Optional WaveframeEngine instance for external control.\n * If provided, the player will sync with this engine instead of creating its own.\n */\n engine?: WaveframeEngine;\n}\n\n/**\n * The standard \"all-in-one\" Waveframe player component.\n * \n * This component features a SoundCloud-inspired layout with a prominent \n * play/pause button positioned next to the track metadata.\n */\nexport const WaveframePlayer: React.FC<WaveframePlayerProps> = memo(({\n media,\n peaks: propPeaks,\n artwork,\n title,\n artist,\n waveColor: propWaveColor,\n progressColor: propProgressColor,\n height = 80,\n className = '',\n style: propStyle,\n resolution = 'auto',\n barWidth = 2,\n barGap = 1,\n theme,\n engine: providedEngine,\n}) => {\n // Use the headless hook for state and controls\n const { state, togglePlay, seek } = useWaveframe(media, {\n peaks: propPeaks,\n engine: providedEngine,\n });\n\n const { isPlaying, currentTime, duration, peaks, isAnalyzing } = state;\n\n // Handle Artwork Blob -> Object URL\n const [resolvedArtworkUrl, setResolvedArtworkUrl] = useState<string | undefined>(\n typeof artwork === 'string' ? artwork : undefined\n );\n\n useEffect(() => {\n if (artwork instanceof Blob) {\n const url = URL.createObjectURL(artwork);\n setResolvedArtworkUrl(url);\n return () => URL.revokeObjectURL(url);\n } else {\n setResolvedArtworkUrl(artwork);\n }\n }, [artwork]);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const containerWidth = useResizeObserver(containerRef);\n\n const targetCount = useMemo(() => {\n if (typeof resolution === 'number') return resolution;\n if (containerWidth > 0) {\n return Math.max(1, Math.floor(containerWidth / (barWidth + barGap)));\n }\n return peaks.length || 1;\n }, [resolution, containerWidth, barWidth, barGap, peaks.length]);\n\n const resampledPeaks = useResampledPeaks(peaks, targetCount);\n\n const waveColor = useMemo(() => {\n if (propWaveColor) return propWaveColor;\n if (theme) return theme.bg === '#ffffff' ? '#e5e7eb' : '#374151';\n return '#e5e7eb';\n }, [propWaveColor, theme]);\n\n const progressColor = propProgressColor || theme?.primary || '#3b82f6';\n\n const mergedStyle = useMemo(() => {\n const baseStyle = {\n '--wf-bg-color': theme?.bg || 'white',\n '--wf-border-color': theme?.border || '#f3f4f6',\n '--wf-title-color': theme?.text || '#111827',\n '--wf-artist-color': theme?.text || '#6b7280',\n '--wf-time-color': theme?.text || '#9ca3af',\n '--wf-play-btn-bg': theme?.primary || '#3b82f6',\n '--wf-placeholder-from': theme?.primary || '#fb923c',\n '--wf-placeholder-to': theme?.bg || '#ec4899',\n };\n return { ...baseStyle, ...propStyle } as React.CSSProperties;\n }, [theme, propStyle]);\n\n return (\n <div\n className={`group relative flex flex-col md:flex-row items-stretch gap-6 p-6 bg-[var(--wf-bg-color,white)] border border-[var(--wf-border-color,#f3f4f6)] rounded-[var(--wf-rounded,1rem)] shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden ${className}`}\n style={mergedStyle}\n >\n <ArtworkOverlay \n artworkUrl={resolvedArtworkUrl} \n title={title} \n isLoading={isAnalyzing}\n />\n\n <div className=\"flex-1 w-full flex flex-col min-w-0\">\n <div className=\"flex items-center gap-4 mb-6\">\n {/* SoundCloud-style circular play button */}\n <button\n onClick={togglePlay}\n className=\"w-12 h-12 md:w-14 md:h-14 flex-shrink-0 flex items-center justify-center rounded-full bg-[var(--wf-play-btn-bg,#3b82f6)] text-white shadow-[0_4px_12px_rgba(0,0,0,0.15)] hover:shadow-[0_6px_16px_rgba(0,0,0,0.2)] transition-all hover:scale-105 active:scale-95 cursor-pointer border-none outline-none group/play\"\n >\n {isPlaying ? (\n <svg className=\"w-6 h-6 md:w-7 md:h-7\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\n </svg>\n ) : (\n <svg className=\"w-6 h-6 md:w-7 md:h-7 ml-1\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n\n <div className=\"flex-1 flex flex-col min-w-0\">\n <div className=\"flex items-center justify-between gap-4\">\n {artist && (\n <p className=\"text-[10px] md:text-xs font-bold uppercase text-[var(--wf-artist-color,#6b7280)] opacity-60 tracking-[0.1em] line-clamp-1\">\n {artist}\n </p>\n )}\n <div className=\"text-[10px] font-mono text-[var(--wf-time-color,#9ca3af)] tabular-nums flex-shrink-0\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </div>\n </div>\n {title && (\n <h3 className=\"text-lg md:text-xl font-black text-[var(--wf-title-color,#111827)] tracking-tight line-clamp-1 mt-0.5 leading-tight\">\n {title}\n </h3>\n )}\n </div>\n </div>\n\n <div className=\"mt-auto\" ref={containerRef}>\n <Waveform \n peaks={resampledPeaks}\n currentTime={currentTime}\n duration={duration}\n waveColor={waveColor}\n progressColor={progressColor}\n height={height}\n onSeek={seek}\n resolution={resolution}\n barWidth={barWidth}\n barGap={barGap}\n />\n </div>\n </div>\n </div>\n );\n});\n\nWaveframePlayer.displayName = 'WaveframePlayer';\n"]}
1
+ {"version":3,"sources":["../src/core/PlayerCore.ts","../src/core/PeakAnalyzer.ts","../src/core/WaveframeEngine.ts","../src/hooks/useWaveframeStore.ts","../src/hooks/useWaveframe.ts","../src/utils/audio.ts","../src/utils/index.ts","../src/hooks/useResampledPeaks.ts","../src/hooks/useResizeObserver.ts","../src/molecules/ArtworkOverlay.tsx","../src/organisms/Waveform.tsx","../src/components/WaveframePlayer.tsx"],"names":["PlayerCore","error","message","patch","l","listener","url","e","time","volume","muted","PeakAnalyzer","media","samples","arrayBuffer","response","channelData","blockSize","peaks","i","max","start","end","j","val","maxPeak","p","WaveframeEngine","playerState","sourceUrl","hasPeaks","percentage","duration","useWaveframeStore","engine","useSyncExternalStore","callback","useWaveframe","options","providedEngine","internalEngine","useMemo","state","useEffect","v","m","generatePeaks","audioUrl","analyzer","loadAudioToMemory","blob","revokeAudioMemory","formatTime","seconds","min","sec","resamplePeaks","targetCount","resampled","ratio","position","index","nextIndex","fraction","highlightCode","code","line","h","tokens","counter","addToken","cls","id","html","useResampledPeaks","useResizeObserver","ref","width","setWidth","useState","observer","entries","entry","ArtworkOverlay","memo","artworkUrl","title","isLoading","jsxs","jsx","Waveform","currentTime","waveColor","progressColor","height","onSeek","resolution","barWidth","barGap","canvasRef","useRef","progressCanvasRef","containerRef","containerWidth","canvas","progressCanvas","ctx","pCtx","dpr","rect","targetWidth","targetHeight","c","barCount","actualBarTotalWidth","actualBarWidth","actualBarGap","peak","x","barHeight","yStart","yEnd","handleSeek","progressPercent","WaveframePlayer","propPeaks","artwork","artist","propWaveColor","propProgressColor","className","propStyle","theme","togglePlay","seek","isPlaying","isAnalyzing","resolvedArtworkUrl","setResolvedArtworkUrl","resampledPeaks","mergedStyle"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Represents the low-level playback state of the audio element.\n */\nexport type PlayerState = {\n /** Whether the audio is currently playing */\n isPlaying: boolean;\n /** The current playback time in seconds */\n currentTime: number;\n /** The total duration of the track in seconds */\n duration: number;\n /** The current volume level (0 to 1) */\n volume: number;\n /** Whether the audio is currently muted */\n muted: boolean;\n /** Any error reported by the audio element */\n error: string | null;\n};\n\n/**\n * A callback function that receives the latest PlayerState.\n */\nexport type PlayerListener = (state: PlayerState) => void;\n\n/**\n * The internal core class responsible for managing the HTMLAudioElement.\n * \n * It handles raw playback logic, volume control, and synchronizes the \n * internal `PlayerState` with DOM events from the underlying `Audio` instance.\n */\nexport class PlayerCore {\n private audio: HTMLAudioElement;\n private listeners: Set<PlayerListener> = new Set();\n private _state: PlayerState;\n\n /**\n * Initializes a new PlayerCore instance and sets up event listeners on a new Audio object.\n */\n constructor() {\n this.audio = new Audio();\n this._state = {\n isPlaying: false,\n currentTime: 0,\n duration: 0,\n volume: 1,\n muted: false,\n error: null,\n };\n\n this.initListeners();\n }\n\n /**\n * Subscribes to various HTMLMediaElement events to keep the internal state in sync.\n */\n private initListeners() {\n this.audio.addEventListener('play', () => this.updateState({ isPlaying: true, error: null }));\n this.audio.addEventListener('pause', () => this.updateState({ isPlaying: false }));\n this.audio.addEventListener('timeupdate', () => this.updateState({ currentTime: this.audio.currentTime }));\n this.audio.addEventListener('durationchange', () => this.updateState({ duration: this.audio.duration }));\n this.audio.addEventListener('volumechange', () => this.updateState({ \n volume: this.audio.volume, \n muted: this.audio.muted \n }));\n this.audio.addEventListener('ended', () => this.updateState({ isPlaying: false }));\n this.audio.addEventListener('error', () => {\n const error = this.audio.error;\n let message = 'Unknown audio error';\n if (error) {\n switch (error.code) {\n case error.MEDIA_ERR_ABORTED: message = 'Playback aborted'; break;\n case error.MEDIA_ERR_NETWORK: message = 'Network error'; break;\n case error.MEDIA_ERR_DECODE: message = 'Audio decoding failed'; break;\n case error.MEDIA_ERR_SRC_NOT_SUPPORTED: message = 'Audio format not supported'; break;\n }\n }\n this.updateState({ isPlaying: false, error: message });\n });\n }\n\n /**\n * Updates the internal state and notifies subscribers.\n */\n private updateState(patch: Partial<PlayerState>) {\n this._state = { ...this._state, ...patch };\n this.notify();\n }\n\n /**\n * Triggers all registered listener callbacks.\n */\n private notify() {\n this.listeners.forEach(l => l(this._state));\n }\n\n /**\n * Registers a listener for state updates.\n * @param listener The callback function.\n * @returns An unsubscribe function.\n */\n public subscribe(listener: PlayerListener) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Returns the current playback state.\n */\n public get state() {\n return this._state;\n }\n\n /**\n * Updates the source URL of the underlying audio element.\n * @param url The audio source URL.\n */\n public setSource(url: string) {\n this.audio.src = url;\n this.audio.load();\n this.updateState({ error: null, currentTime: 0, duration: 0 });\n }\n\n /**\n * Starts playback. Returns a promise that resolves when playback begins.\n */\n public async play() {\n try {\n await this.audio.play();\n } catch (e) {\n const message = e instanceof Error ? e.message : 'Playback failed';\n this.updateState({ isPlaying: false, error: message });\n throw e;\n }\n }\n\n /**\n * Pauses playback.\n */\n public pause() {\n this.audio.pause();\n }\n\n /**\n * Toggles between play and pause states.\n */\n public async togglePlay() {\n if (this._state.isPlaying) {\n this.pause();\n } else {\n try {\n await this.play();\n } catch {\n // Error handled in play()\n }\n }\n }\n\n /**\n * Seeks to a specific time.\n * @param time Time in seconds.\n */\n public seek(time: number) {\n this.audio.currentTime = time;\n }\n\n /**\n * Sets the volume level.\n * @param volume Level from 0 to 1.\n */\n public setVolume(volume: number) {\n this.audio.volume = volume;\n }\n\n /**\n * Mutes or unmutes the audio element.\n * @param muted Mute status.\n */\n public setMuted(muted: boolean) {\n this.audio.muted = muted;\n }\n\n /**\n * Cleans up the audio element and removes all listeners.\n */\n public dispose() {\n this.pause();\n this.audio.src = '';\n this.listeners.clear();\n }\n}\n","/**\n * A specialized class for decoding audio data and generating waveform peaks.\n * \n * It leverages the Web Audio API (`AudioContext`) to process audio buffers \n * and extract amplitude data for visualization.\n */\nexport class PeakAnalyzer {\n private audioCtx: AudioContext | null = null;\n\n /**\n * Initializes the analyzer. AudioContext creation is deferred to the first use \n * to comply with browser autoplay and resource management policies.\n */\n constructor() {}\n\n /**\n * Lazily creates or returns the existing AudioContext.\n */\n private getContext(): AudioContext {\n if (!this.audioCtx) {\n this.audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();\n }\n return this.audioCtx;\n }\n\n /**\n * Processes media (URL or Blob) and generates a set of normalized peaks.\n * \n * @param media The URL string or Blob object to analyze.\n * @param samples The number of peaks (bars) to generate. Defaults to 512.\n * @returns A promise resolving to an array of normalized peak values (0 to 1).\n * \n * @example\n * ```typescript\n * const analyzer = new PeakAnalyzer();\n * \n * // Analyze from URL\n * const peaksFromUrl = await analyzer.generatePeaks('https://example.com/audio.mp3');\n * \n * // Analyze from Blob\n * const peaksFromBlob = await analyzer.generatePeaks(myAudioBlob);\n * ```\n */\n public async generatePeaks(media: string | Blob, samples: number = 512): Promise<number[]> {\n try {\n let arrayBuffer: ArrayBuffer;\n\n if (typeof media === 'string') {\n const response = await fetch(media);\n if (!response.ok) throw new Error(`Failed to fetch audio: ${response.statusText}`);\n arrayBuffer = await response.arrayBuffer();\n } else {\n arrayBuffer = await media.arrayBuffer();\n }\n\n const audioCtx = this.getContext();\n const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);\n \n const channelData = audioBuffer.getChannelData(0); // Use left channel\n const blockSize = Math.floor(channelData.length / samples);\n const peaks = [];\n\n for (let i = 0; i < samples; i++) {\n let max = 0;\n const start = i * blockSize;\n const end = start + blockSize;\n \n for (let j = start; j < end; j++) {\n const val = Math.abs(channelData[j]);\n if (val > max) max = val;\n }\n peaks.push(max);\n }\n \n // Normalize peaks to 0-1 range\n const maxPeak = Math.max(...peaks);\n return peaks.map(p => p / (maxPeak || 1));\n } catch (error) {\n console.error('PeakAnalyzer Error:', error);\n throw error;\n }\n }\n\n /**\n * Closes the AudioContext and releases system audio resources.\n */\n public dispose() {\n if (this.audioCtx) {\n this.audioCtx.close();\n this.audioCtx = null;\n }\n }\n}\n","import { PlayerCore, PlayerState } from './PlayerCore';\nimport { PeakAnalyzer } from './PeakAnalyzer';\n\n/**\n * Represents the complete state of the Waveframe engine, combining playback and analysis.\n */\nexport type EngineState = PlayerState & {\n /** The current set of generated or provided waveform peaks (0-1 range) */\n peaks: number[];\n /** Whether an audio analysis process is currently in progress */\n isAnalyzing: boolean;\n /** Any error message encountered during playback or analysis */\n error: string | null;\n};\n\n/**\n * A callback function that receives the latest EngineState.\n */\nexport type EngineListener = (state: EngineState) => void;\n\n/**\n * The orchestrator class for Waveframe. \n * \n * It manages the lifecycle of audio playback and waveform analysis, providing a unified \n * store-like interface that can be easily consumed by React or other frameworks.\n * \n * @example\n * ```typescript\n * const engine = new WaveframeEngine();\n * \n * // Load from URL (automatic analysis if peaks omitted)\n * engine.load('https://example.com/audio.mp3');\n * \n * // Load from Blob with pre-computed peaks\n * engine.load(myBlob, [0.1, 0.5, 0.8]);\n * \n * // Subscription\n * const unsubscribe = engine.subscribe((state) => {\n * console.log('Current time:', state.currentTime);\n * });\n * ```\n */\nexport class WaveframeEngine {\n private player: PlayerCore;\n private analyzer: PeakAnalyzer;\n private listeners: Set<EngineListener> = new Set();\n private _state: EngineState;\n\n private _media: string | Blob | null = null;\n private _objectUrl: string | null = null;\n\n /**\n * Creates a new instance of the WaveframeEngine.\n * Initializes internal PlayerCore and PeakAnalyzer.\n */\n constructor() {\n this.player = new PlayerCore();\n this.analyzer = new PeakAnalyzer();\n \n this._state = {\n ...this.player.state,\n peaks: [],\n isAnalyzing: false,\n error: null,\n };\n\n // Subscribe to player updates\n this.player.subscribe((playerState) => {\n this.updateState({ ...playerState });\n });\n }\n\n /**\n * Internal method to update the state and notify all subscribers.\n */\n private updateState(patch: Partial<EngineState>) {\n this._state = { ...this._state, ...patch };\n this.notify();\n }\n\n /**\n * Notifies all registered listeners of a state change.\n */\n private notify() {\n this.listeners.forEach(l => l(this._state));\n }\n\n // --- Store Interface ---\n\n /**\n * Registers a listener to be called whenever the engine state changes.\n * @param listener The callback function.\n * @returns An unsubscribe function.\n */\n public subscribe(listener: EngineListener) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Returns a snapshot of the current engine state.\n * Useful for `useSyncExternalStore`.\n */\n public getSnapshot(): EngineState {\n return this._state;\n }\n\n // --- Actions ---\n\n /**\n * Revokes any existing Object URLs to prevent memory leaks.\n */\n private revokeOldSource() {\n if (this._objectUrl) {\n URL.revokeObjectURL(this._objectUrl);\n this._objectUrl = null;\n }\n }\n\n /**\n * Loads media (URL or Blob) into the player.\n * \n * If a string is passed, it's treated as a URL and used directly for playback.\n * If a Blob is passed, an Object URL is created for playback.\n * \n * If `peaks` are not provided, it automatically triggers an analysis.\n * \n * @param media The audio source (URL string or Blob/File object).\n * @param peaks Optional pre-generated peaks for the waveform.\n */\n public load(media: string | Blob, peaks?: number[]) {\n const isNewMedia = this._media !== media;\n \n if (isNewMedia) {\n this.revokeOldSource();\n this._media = media;\n\n let sourceUrl: string;\n if (typeof media === 'string') {\n sourceUrl = media;\n } else {\n this._objectUrl = URL.createObjectURL(media);\n sourceUrl = this._objectUrl;\n }\n\n this.player.setSource(sourceUrl);\n \n const hasPeaks = peaks && peaks.length > 0;\n this.updateState({ \n peaks: peaks || [], \n isAnalyzing: false, \n error: null \n });\n\n // Automatic analysis if peaks are missing\n if (!hasPeaks) {\n this.analyze();\n }\n } else if (peaks && (peaks.length !== this._state.peaks.length)) {\n // Update peaks only if they actually look different (length check as a simple proxy for deep comparison)\n // This helps avoid reloads if the parent passes a new array reference with same content\n this.updateState({ peaks });\n }\n }\n\n /**\n * Analyzes the current media to generate waveform peaks.\n * @param samples The number of peaks to generate. Defaults to 512.\n */\n public async analyze(samples: number = 512) {\n if (!this._media) {\n this.updateState({ error: 'No media loaded to analyze' });\n return;\n }\n\n this.updateState({ isAnalyzing: true, error: null });\n try {\n const peaks = await this.analyzer.generatePeaks(this._media, samples);\n this.updateState({ peaks, isAnalyzing: false });\n } catch (e) {\n this.updateState({ \n isAnalyzing: false, \n error: e instanceof Error ? e.message : 'Analysis failed' \n });\n }\n }\n\n /**\n * Toggles playback between playing and paused.\n */\n public togglePlay() {\n this.player.togglePlay();\n }\n\n /**\n * Starts audio playback.\n */\n public play() {\n this.player.play();\n }\n\n /**\n * Pauses audio playback.\n */\n public pause() {\n this.player.pause();\n }\n\n /**\n * Seeks to a specific position in the track.\n * @param percentage The seek position as a decimal (0 to 1).\n */\n public seek(percentage: number) {\n const { duration } = this._state;\n if (duration) {\n this.player.seek(percentage * duration);\n }\n }\n\n /**\n * Sets the playback volume.\n * @param volume The volume level (0 to 1).\n */\n public setVolume(volume: number) {\n this.player.setVolume(volume);\n }\n\n /**\n * Mutes or unmutes the audio.\n * @param muted Whether the audio should be muted.\n */\n public setMuted(muted: boolean) {\n this.player.setMuted(muted);\n }\n\n /**\n * Disposes of the engine, pausing playback and clearing all listeners and resources.\n */\n public dispose() {\n this.revokeOldSource();\n this.player.dispose();\n this.analyzer.dispose();\n this.listeners.clear();\n }\n}\n","import { useSyncExternalStore } from 'react';\nimport { WaveframeEngine, EngineState } from '../core/WaveframeEngine';\n\n/**\n * A React hook that synchronizes a WaveframeEngine's state with a React component.\n * \n * It uses `useSyncExternalStore` for high-performance updates, ensuring that \n * the component only re-renders when the engine's state snapshot actually changes.\n * \n * @param engine The WaveframeEngine instance to subscribe to.\n * @returns The current EngineState (isPlaying, currentTime, peaks, etc.).\n * \n * @example\n * ```tsx\n * const MyPlayer = ({ engine }: { engine: WaveframeEngine }) => {\n * const { isPlaying, currentTime, duration } = useWaveframeStore(engine);\n * \n * return (\n * <div>\n * <button onClick={() => engine.togglePlay()}>\n * {isPlaying ? 'Pause' : 'Play'}\n * </button>\n * <p>{currentTime.toFixed(2)} / {duration.toFixed(2)}</p>\n * </div>\n * );\n * };\n * ```\n */\nexport const useWaveframeStore = (engine: WaveframeEngine): EngineState => {\n return useSyncExternalStore(\n (callback) => engine.subscribe(callback),\n () => engine.getSnapshot()\n );\n};\n","import { useMemo, useEffect } from 'react';\nimport { WaveframeEngine, EngineState } from '../core/WaveframeEngine';\nimport { useWaveframeStore } from './useWaveframeStore';\n\n/**\n * Configuration options for the `useWaveframe` hook.\n */\nexport interface UseWaveframeOptions {\n /** Optional pre-computed peaks to skip automatic analysis */\n peaks?: number[];\n /** Optional external engine instance for shared playback across components */\n engine?: WaveframeEngine;\n}\n\n/**\n * A headless hook that provides full control over the Waveframe engine.\n * \n * It manages the engine's lifecycle, loads the provided media, and returns \n * the current state along with playback controls.\n * \n * @param media The audio source (URL string or Blob/File object).\n * @param options Additional configuration and an optional external engine.\n * \n * @example\n * ```tsx\n * const { state, togglePlay, seek } = useWaveframe('https://example.com/audio.mp3');\n * \n * return (\n * <div>\n * <button onClick={togglePlay}>{state.isPlaying ? 'Pause' : 'Play'}</button>\n * <div onClick={(e) => seek(0.5)}>Seek to Middle</div>\n * </div>\n * );\n * ```\n */\nexport const useWaveframe = (media: string | Blob | undefined, options: UseWaveframeOptions = {}) => {\n const { peaks, engine: providedEngine } = options;\n\n // Initialize engine (only once)\n const internalEngine = useMemo(() => providedEngine || new WaveframeEngine(), [providedEngine]);\n const engine = providedEngine || internalEngine;\n\n // Subscribe to engine state\n const state = useWaveframeStore(engine);\n\n // Sync media with engine\n useEffect(() => {\n if (media) {\n engine.load(media, peaks);\n }\n }, [engine, media, peaks]);\n\n // Handle disposal\n useEffect(() => {\n return () => {\n // Only dispose if we created it internally\n if (!providedEngine) {\n internalEngine.dispose();\n }\n };\n }, [internalEngine, providedEngine]);\n\n return {\n /** The current reactive state of the engine */\n state,\n /** The raw WaveframeEngine instance for advanced usage */\n engine,\n /** Toggles playback between playing and paused */\n togglePlay: () => engine.togglePlay(),\n /** Starts audio playback */\n play: () => engine.play(),\n /** Pauses audio playback */\n pause: () => engine.pause(),\n /** Seeks to a specific percentage (0-1) */\n seek: (percentage: number) => engine.seek(percentage),\n /** Sets the playback volume (0-1) */\n setVolume: (v: number) => engine.setVolume(v),\n /** Mutes or unmutes the audio */\n setMuted: (m: boolean) => engine.setMuted(m),\n /** Manually triggers a re-analysis of the current media */\n analyze: (samples?: number) => engine.analyze(samples),\n };\n};\n","/**\n * Advanced Audio Utilities using Web Audio API\n */\nimport { PeakAnalyzer } from '../core/PeakAnalyzer';\n\n/**\n * Loads audio from a URL, decodes it, and generates a specific number of peaks (samples).\n * \n * This is a high-level utility function that internally manages a `PeakAnalyzer` instance.\n * \n * @param audioUrl The URL of the audio file to analyze.\n * @param samples The number of peaks (bars) to generate. Defaults to 512.\n * @returns A promise resolving to an array of normalized peak values (0 to 1).\n * \n * @example\n * ```typescript\n * const peaks = await generatePeaks('https://example.com/audio.mp3', 256);\n * ```\n */\nexport const generatePeaks = async (audioUrl: string, samples: number = 512): Promise<number[]> => {\n const analyzer = new PeakAnalyzer();\n try {\n return await analyzer.generatePeaks(audioUrl, samples);\n } finally {\n analyzer.dispose();\n }\n};\n\n/**\n * Loads audio into memory as a Blob and returns a temporary Object URL.\n * \n * Useful for ensuring audio data is fully loaded locally before starting \n * playback or analysis, which can help with CORS issues or slow networks.\n * \n * @param url The URL of the remote audio file.\n * @returns A promise resolving to a temporary `blob:` URL.\n */\nexport const loadAudioToMemory = async (url: string): Promise<string> => {\n const response = await fetch(url);\n const blob = await response.blob();\n return URL.createObjectURL(blob);\n};\n\n/**\n * Cleanup function to prevent memory leaks from Object URLs.\n * \n * Call this when a `blob:` URL is no longer needed (e.g., when the component unmounts).\n * \n * @param url The Object URL to revoke.\n */\nexport const revokeAudioMemory = (url: string) => {\n if (url && url.startsWith('blob:')) {\n URL.revokeObjectURL(url);\n }\n};\n","/**\n * Formats seconds into a M:SS string\n */\nexport const formatTime = (seconds: number): string => {\n if (isNaN(seconds)) return '0:00';\n const min = Math.floor(seconds / 60);\n const sec = Math.floor(seconds % 60);\n return `${min}:${sec.toString().padStart(2, '0')}`;\n};\n\n/**\n * Resamples an array of peaks to a target count using bucket-max or linear interpolation\n */\nexport const resamplePeaks = (peaks: number[], targetCount: number): number[] => {\n if (peaks.length === 0) return [];\n if (peaks.length === targetCount) return peaks;\n\n const resampled = new Array(targetCount);\n const ratio = peaks.length / targetCount;\n\n if (ratio > 1) {\n // Downsampling: Bucket Max\n for (let i = 0; i < targetCount; i++) {\n let max = 0;\n const start = Math.floor(i * ratio);\n const end = Math.floor((i + 1) * ratio);\n for (let j = start; j < end; j++) {\n if (peaks[j] > max) max = peaks[j];\n }\n resampled[i] = max;\n }\n } else {\n // Upsampling: Linear Interpolation\n for (let i = 0; i < targetCount; i++) {\n const position = i * ratio;\n const index = Math.floor(position);\n const nextIndex = Math.min(index + 1, peaks.length - 1);\n const fraction = position - index;\n resampled[i] = peaks[index] + (peaks[nextIndex] - peaks[index]) * fraction;\n }\n }\n return resampled;\n};\n\n/**\n * High-performance token-based syntax highlighter for React snippets\n */\nexport const highlightCode = (code: string): string[] => {\n return code.split('\\n').map((line) => {\n // 1. Escape basic HTML\n let h = line.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\n // 2. Identify and tokenize segments to avoid nested replacement issues\n const tokens: { [key: string]: string } = {};\n let counter = 0;\n const addToken = (val: string, cls: string) => {\n const id = `__TOKEN_${counter++}__`;\n tokens[id] = `<span class=\"${cls}\">${val}</span>`;\n return id;\n };\n\n // Strings\n h = h.replace(/(\"(?:[^\"\\\\]|\\\\.)*\")/g, (m) => addToken(m, 'text-[#ce9178]'));\n // Numbers\n h = h.replace(/\\b(\\d+(\\.\\d+)?)\\b/g, (m) => addToken(m, 'text-[#b5cea8]'));\n // Component Name\n h = h.replace(/\\b(WaveframePlayer)\\b/g, (m) => addToken(m, 'text-[#4ec9b0]'));\n // Props (anything followed by =)\n h = h.replace(/\\b([a-z][a-zA-Z0-9]+)(?==)/g, (m) => addToken(m, 'text-[#9cdcfe]'));\n\n // 3. Style remaining symbols\n h = h.replace(/(&lt;|&gt;|\\{|\\}|\\/|:|,)/g, '<span class=\"text-gray-500\">$1</span>');\n\n // 4. In-place token resolution\n Object.entries(tokens).forEach(([id, html]) => {\n h = h.replace(id, html);\n });\n\n return h;\n });\n};\n\nexport * from './audio';\n","import { useMemo } from 'react';\nimport { resamplePeaks } from '../utils';\n\nexport const useResampledPeaks = (peaks: number[], targetCount: number) => {\n return useMemo(() => resamplePeaks(peaks, targetCount), [peaks, targetCount]);\n};\n","import { useState, useEffect } from 'react';\n\nexport const useResizeObserver = (ref: React.RefObject<HTMLElement | null>) => {\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n if (!ref.current) return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setWidth(entry.contentRect.width);\n }\n });\n\n observer.observe(ref.current);\n return () => observer.disconnect();\n }, [ref]);\n\n return width;\n};\n","import React, { memo } from 'react';\n\n/**\n * Props for the ArtworkOverlay component.\n */\ninterface ArtworkOverlayProps {\n /** The URL or Object URL of the artwork image */\n artworkUrl?: string;\n /** The title of the track (used for alt text) */\n title?: string;\n /** Whether the artwork is currently being processed or the audio is analyzing */\n isLoading?: boolean;\n}\n\n/**\n * A purely visual component for displaying track artwork.\n * \n * It handles loading states with a blur effect and provides a consistent \n * container for the track image.\n */\nexport const ArtworkOverlay: React.FC<ArtworkOverlayProps> = memo(({ \n artworkUrl, \n title, \n isLoading\n}) => {\n return (\n <div className=\"relative flex-shrink-0 w-32 h-32 md:w-40 md:h-40 overflow-hidden rounded-[var(--wf-artwork-rounded,0.75rem)] shadow-lg group/artwork\">\n <div className={`w-full h-full transition-all duration-700 ${isLoading ? 'blur-md scale-110' : ''}`}>\n {artworkUrl ? (\n <img\n src={artworkUrl}\n alt={title}\n className=\"w-full h-full object-cover transition-transform duration-500 group-hover/artwork:scale-110\"\n />\n ) : (\n <div className=\"w-full h-full bg-gradient-to-br from-[var(--wf-placeholder-from,#fb923c)] to-[var(--wf-placeholder-to,#ec4899)] flex items-center justify-center\">\n <svg className=\"w-16 h-16 text-white opacity-50\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z\" />\n </svg>\n </div>\n )}\n </div>\n\n {isLoading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/10 backdrop-blur-[1px]\">\n <div className=\"w-8 h-8 border-4 border-white/30 border-t-white rounded-full animate-spin\" />\n </div>\n )}\n </div>\n );\n});\n\nArtworkOverlay.displayName = 'ArtworkOverlay';\n","import React, { useRef, useEffect, memo } from 'react';\nimport { useResizeObserver } from '../hooks/useResizeObserver';\n\ninterface WaveformProps {\n peaks: number[];\n currentTime: number;\n duration: number;\n waveColor: string;\n progressColor: string;\n height: number;\n onSeek: (percentage: number) => void;\n resolution?: number | 'auto';\n barWidth?: number;\n barGap?: number;\n}\n\nexport const Waveform: React.FC<WaveformProps> = memo(({\n peaks,\n currentTime,\n duration,\n waveColor,\n progressColor,\n height,\n onSeek,\n resolution = 'auto',\n barWidth = 2,\n barGap = 1,\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const progressCanvasRef = useRef<HTMLCanvasElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const containerWidth = useResizeObserver(containerRef);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n const progressCanvas = progressCanvasRef.current;\n if (!canvas || !progressCanvas) return;\n\n const ctx = canvas.getContext('2d');\n const pCtx = progressCanvas.getContext('2d');\n if (!ctx || !pCtx) return;\n\n const dpr = window.devicePixelRatio || 1;\n const rect = canvas.getBoundingClientRect();\n const targetWidth = rect.width * dpr;\n const targetHeight = rect.height * dpr;\n\n [canvas, progressCanvas].forEach(c => {\n if (c.width !== targetWidth || c.height !== targetHeight) {\n c.width = targetWidth;\n c.height = targetHeight;\n }\n });\n\n const draw = () => {\n if (peaks.length === 0) return;\n const { width, height } = canvas;\n \n ctx.clearRect(0, 0, width, height);\n pCtx.clearRect(0, 0, width, height);\n\n const barCount = peaks.length;\n const actualBarTotalWidth = width / barCount;\n const actualBarWidth = typeof resolution === 'number' \n ? actualBarTotalWidth * 0.7 \n : barWidth * dpr;\n const actualBarGap = typeof resolution === 'number'\n ? actualBarTotalWidth * 0.3\n : barGap * dpr;\n\n ctx.lineCap = 'round';\n ctx.lineWidth = actualBarWidth;\n pCtx.lineCap = 'round';\n pCtx.lineWidth = actualBarWidth;\n\n peaks.forEach((peak, index) => {\n if (peak <= 0) return;\n \n const x = index * (actualBarWidth + actualBarGap) + actualBarWidth / 2;\n const barHeight = peak * height * 0.8;\n const yStart = (height - barHeight) / 2;\n const yEnd = yStart + barHeight;\n\n ctx.beginPath();\n ctx.strokeStyle = waveColor;\n ctx.moveTo(x, yStart);\n ctx.lineTo(x, yEnd);\n ctx.stroke();\n\n pCtx.beginPath();\n pCtx.strokeStyle = progressColor;\n pCtx.moveTo(x, yStart);\n pCtx.lineTo(x, yEnd);\n pCtx.stroke();\n });\n };\n\n draw();\n }, [peaks, waveColor, progressColor, resolution, barWidth, barGap, height]);\n\n const handleSeek = (e: React.MouseEvent<HTMLDivElement>) => {\n if (containerRef.current && duration) {\n const rect = containerRef.current.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const percentage = Math.max(0, Math.min(1, x / rect.width));\n onSeek(percentage);\n }\n };\n\n const progressPercent = duration ? (currentTime / duration) * 100 : 0;\n\n return (\n <div \n ref={containerRef} \n className=\"relative w-full cursor-pointer overflow-hidden\" \n style={{ height: `${height}px` }}\n onClick={handleSeek}\n >\n <canvas ref={canvasRef} className=\"absolute inset-0 w-full h-full\" />\n <div \n className=\"absolute inset-0 h-full overflow-hidden transition-[width] duration-100 ease-linear pointer-events-none\"\n style={{ width: `${progressPercent}%` }}\n >\n <canvas ref={progressCanvasRef} className=\"absolute h-full\" style={{ width: `${containerWidth}px` }} />\n </div>\n </div>\n );\n});\n\nWaveform.displayName = 'Waveform';\n","import React, { memo, useMemo, useRef, useState, useEffect } from 'react';\nimport { WaveframeTheme } from '../types';\nimport { WaveframeEngine } from '../core/WaveframeEngine';\nimport { useWaveframe } from '../hooks/useWaveframe';\nimport { useResampledPeaks } from '../hooks/useResampledPeaks';\nimport { useResizeObserver } from '../hooks/useResizeObserver';\nimport { ArtworkOverlay } from '../molecules/ArtworkOverlay';\nimport { Waveform } from '../organisms/Waveform';\nimport { formatTime } from '../utils';\n\n/**\n * Props for the WaveframePlayer component\n */\nexport interface WaveframePlayerProps {\n /**\n * The audio source to play. Can be a URL string or a Blob/File object.\n */\n media?: string | Blob;\n /**\n * Optional pre-generated peaks for the waveform (0-1 range).\n * If omitted or empty, the player will automatically analyze the media.\n */\n peaks?: number[];\n /**\n * The artwork source to display. Can be a URL string or a Blob/File object.\n */\n artwork?: string | Blob;\n /**\n * The title of the track\n */\n title?: string;\n /**\n * The artist of the track\n */\n artist?: string;\n /**\n * The base color of the waveform bars\n * @default \"#e5e7eb\" (light) or \"#374151\" (dark)\n */\n waveColor?: string;\n /**\n * The color of the played progress part of the waveform\n * @default theme.primary or \"#3b82f6\"\n */\n progressColor?: string;\n /**\n * The height of the waveform in pixels\n * @default 80\n */\n height?: number;\n /**\n * Additional CSS classes for the container\n */\n className?: string;\n /**\n * Inline styles for the container\n */\n style?: React.CSSProperties;\n /**\n * The number of bars to render. Use 'auto' to fit the container width.\n * @default \"auto\"\n */\n resolution?: number | 'auto';\n /**\n * The width of each bar in pixels (if resolution is 'auto')\n * @default 2\n */\n barWidth?: number;\n /**\n * The gap between bars in pixels (if resolution is 'auto')\n * @default 1\n */\n barGap?: number;\n /**\n * Custom theme configuration\n */\n theme?: WaveframeTheme;\n /**\n * Optional WaveframeEngine instance for external control.\n * If provided, the player will sync with this engine instead of creating its own.\n */\n engine?: WaveframeEngine;\n}\n\n/**\n * The standard \"all-in-one\" Waveframe player component.\n * \n * This component features a SoundCloud-inspired layout with a prominent \n * play/pause button positioned next to the track metadata.\n */\nexport const WaveframePlayer: React.FC<WaveframePlayerProps> = memo(({\n media,\n peaks: propPeaks,\n artwork,\n title,\n artist,\n waveColor: propWaveColor,\n progressColor: propProgressColor,\n height = 80,\n className = '',\n style: propStyle,\n resolution = 'auto',\n barWidth = 2,\n barGap = 1,\n theme,\n engine: providedEngine,\n}) => {\n // Use the headless hook for state and controls\n const { state, togglePlay, seek } = useWaveframe(media, {\n peaks: propPeaks,\n engine: providedEngine,\n });\n\n const { isPlaying, currentTime, duration, peaks, isAnalyzing } = state;\n\n // Handle Artwork Blob -> Object URL\n const [resolvedArtworkUrl, setResolvedArtworkUrl] = useState<string | undefined>(\n typeof artwork === 'string' ? artwork : undefined\n );\n\n useEffect(() => {\n if (artwork instanceof Blob) {\n const url = URL.createObjectURL(artwork);\n setResolvedArtworkUrl(url);\n return () => URL.revokeObjectURL(url);\n } else {\n setResolvedArtworkUrl(artwork);\n }\n }, [artwork]);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const containerWidth = useResizeObserver(containerRef);\n\n const targetCount = useMemo(() => {\n if (typeof resolution === 'number') return resolution;\n if (containerWidth > 0) {\n return Math.max(1, Math.floor(containerWidth / (barWidth + barGap)));\n }\n return peaks.length || 1;\n }, [resolution, containerWidth, barWidth, barGap, peaks.length]);\n\n const resampledPeaks = useResampledPeaks(peaks, targetCount);\n\n const waveColor = useMemo(() => {\n if (propWaveColor) return propWaveColor;\n if (theme) return theme.bg === '#ffffff' ? '#e5e7eb' : '#374151';\n return '#e5e7eb';\n }, [propWaveColor, theme]);\n\n const progressColor = propProgressColor || theme?.primary || '#3b82f6';\n\n const mergedStyle = useMemo(() => {\n const baseStyle = {\n '--wf-bg-color': theme?.bg || 'white',\n '--wf-border-color': theme?.border || '#f3f4f6',\n '--wf-title-color': theme?.text || '#111827',\n '--wf-artist-color': theme?.text || '#6b7280',\n '--wf-time-color': theme?.text || '#9ca3af',\n '--wf-play-btn-bg': theme?.primary || '#3b82f6',\n '--wf-placeholder-from': theme?.primary || '#fb923c',\n '--wf-placeholder-to': theme?.bg || '#ec4899',\n };\n return { ...baseStyle, ...propStyle } as React.CSSProperties;\n }, [theme, propStyle]);\n\n return (\n <div\n className={`group relative flex flex-col md:flex-row items-stretch gap-6 p-6 bg-[var(--wf-bg-color,white)] border border-[var(--wf-border-color,#f3f4f6)] rounded-[var(--wf-rounded,1rem)] shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden ${className}`}\n style={mergedStyle}\n >\n <ArtworkOverlay \n artworkUrl={resolvedArtworkUrl} \n title={title} \n isLoading={isAnalyzing}\n />\n\n <div className=\"flex-1 w-full flex flex-col min-w-0\">\n <div className=\"flex items-center gap-4 mb-6\">\n {/* SoundCloud-style circular play button */}\n <button\n onClick={togglePlay}\n className=\"w-12 h-12 md:w-14 md:h-14 flex-shrink-0 flex items-center justify-center rounded-full bg-[var(--wf-play-btn-bg,#3b82f6)] text-white shadow-[0_4px_12px_rgba(0,0,0,0.15)] hover:shadow-[0_6px_16px_rgba(0,0,0,0.2)] transition-all hover:scale-105 active:scale-95 cursor-pointer border-none outline-none group/play\"\n >\n {isPlaying ? (\n <svg className=\"w-6 h-6 md:w-7 md:h-7\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\n </svg>\n ) : (\n <svg className=\"w-6 h-6 md:w-7 md:h-7 ml-1\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n\n <div className=\"flex-1 flex flex-col min-w-0\">\n <div className=\"flex items-center justify-between gap-4\">\n {artist && (\n <p className=\"text-[10px] md:text-xs font-bold uppercase text-[var(--wf-artist-color,#6b7280)] opacity-60 tracking-[0.1em] line-clamp-1\">\n {artist}\n </p>\n )}\n <div className=\"text-[10px] font-mono text-[var(--wf-time-color,#9ca3af)] tabular-nums flex-shrink-0\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </div>\n </div>\n {title && (\n <h3 className=\"text-lg md:text-xl font-black text-[var(--wf-title-color,#111827)] tracking-tight line-clamp-1 mt-0.5 leading-tight\">\n {title}\n </h3>\n )}\n </div>\n </div>\n\n <div className=\"mt-auto\" ref={containerRef}>\n <Waveform \n peaks={resampledPeaks}\n currentTime={currentTime}\n duration={duration}\n waveColor={waveColor}\n progressColor={progressColor}\n height={height}\n onSeek={seek}\n resolution={resolution}\n barWidth={barWidth}\n barGap={barGap}\n />\n </div>\n </div>\n </div>\n );\n});\n\nWaveframePlayer.displayName = 'WaveframePlayer';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waveframe",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A high-performance, customizable React audio player with SoundCloud-style waveforms and built-in audio analysis.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",