@arraypress/waveform-player 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # Waveform Player
2
+
3
+ A lightweight, customizable audio player with waveform visualization. Under 6KB gzipped.
4
+
5
+ ![Version](https://img.shields.io/npm/v/waveform-player)
6
+ ![Size](https://img.shields.io/badge/gzipped-6KB-brightgreen)
7
+ ![License](https://img.shields.io/npm/l/waveform-player)
8
+
9
+ ## Features
10
+
11
+ - 🎨 **6 Visual Styles** - Bars, mirror, line, blocks, dots
12
+ - 🎯 **Tiny Footprint** - Under 6KB gzipped
13
+ - ⚡ **Zero Dependencies** - Pure JavaScript
14
+ - 🎭 **Fully Customizable** - Colors, sizes, styles
15
+ - 📱 **Responsive** - Works on all devices
16
+ - 🎵 **BPM Detection** - Automatic tempo detection (optional)
17
+ - 💾 **Waveform Caching** - Pre-generate waveforms for performance
18
+ - 🌐 **Framework Agnostic** - Works with React, Vue, Angular, or vanilla JS
19
+
20
+ ## Installation
21
+
22
+ ### NPM
23
+ ```bash
24
+ npm install waveform-player
25
+ ```
26
+
27
+ ### CDN
28
+ ```html
29
+ <link rel="stylesheet" href="https://unpkg.com/waveform-player/dist/waveform-player.css">
30
+ <script src="https://unpkg.com/waveform-player/dist/waveform-player.min.js"></script>
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ### HTML
36
+ ```html
37
+ <div data-waveform-player
38
+ data-url="audio.mp3"
39
+ data-title="My Song">
40
+ </div>
41
+ ```
42
+
43
+ ### JavaScript
44
+ ```javascript
45
+ import WaveformPlayer from 'waveform-player';
46
+
47
+ const player = new WaveformPlayer('#player', {
48
+ url: 'audio.mp3',
49
+ waveformStyle: 'mirror',
50
+ height: 80
51
+ });
52
+ ```
53
+
54
+ ## Options
55
+
56
+ | Option | Type | Default | Description |
57
+ |--------|------|---------|-------------|
58
+ | `url` | string | `''` | Audio file URL |
59
+ | `waveformStyle` | string | `'bars'` | Visual style: bars, mirror, line, blocks, dots |
60
+ | `height` | number | `60` | Waveform height in pixels |
61
+ | `barWidth` | number | `3` | Width of waveform bars |
62
+ | `barSpacing` | number | `1` | Space between bars |
63
+ | `samples` | number | `200` | Number of waveform samples |
64
+ | `waveformColor` | string | `'rgba(255,255,255,0.3)'` | Waveform color |
65
+ | `progressColor` | string | `'rgba(255,255,255,0.9)'` | Progress color |
66
+ | `showTime` | boolean | `true` | Show time display |
67
+ | `showBPM` | boolean | `false` | Enable BPM detection |
68
+ | `autoplay` | boolean | `false` | Autoplay on load |
69
+ | `title` | string | `''` | Track title |
70
+ | `subtitle` | string | `''` | Track subtitle |
71
+
72
+ ## API Methods
73
+
74
+ ```javascript
75
+ // Control playback
76
+ player.play();
77
+ player.pause();
78
+ player.togglePlay();
79
+
80
+ // Seek
81
+ player.seekTo(30); // Seek to 30 seconds
82
+ player.seekToPercent(0.5); // Seek to 50%
83
+
84
+ // Volume
85
+ player.setVolume(0.8); // 80% volume
86
+
87
+ // Destroy
88
+ player.destroy();
89
+ ```
90
+
91
+ ## Events
92
+
93
+ ```javascript
94
+ new WaveformPlayer('#player', {
95
+ url: 'audio.mp3',
96
+ onLoad: (player) => console.log('Loaded'),
97
+ onPlay: (player) => console.log('Playing'),
98
+ onPause: (player) => console.log('Paused'),
99
+ onEnd: (player) => console.log('Ended'),
100
+ onTimeUpdate: (current, total, player) => {
101
+ console.log(`${current}/${total}`);
102
+ }
103
+ });
104
+ ```
105
+
106
+ ## Advanced Usage
107
+
108
+ ### Pre-generated Waveforms
109
+
110
+ For better performance, generate waveform data server-side:
111
+
112
+ ```javascript
113
+ // Generate waveform data
114
+ const waveformData = await WaveformPlayer.generateWaveformData('audio.mp3');
115
+
116
+ // Use pre-generated data
117
+ new WaveformPlayer('#player', {
118
+ url: 'audio.mp3',
119
+ waveform: waveformData // Bypass client-side processing
120
+ });
121
+ ```
122
+
123
+ ### Multiple Players
124
+
125
+ ```javascript
126
+ // Pause all players
127
+ WaveformPlayer.pauseAll();
128
+
129
+ // Get all instances
130
+ const players = WaveformPlayer.getAllInstances();
131
+
132
+ // Find specific player
133
+ const player = WaveformPlayer.getInstance('my-player');
134
+ ```
135
+
136
+ ### Custom Styling
137
+
138
+ ```css
139
+ .waveform-player {
140
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
141
+ border-radius: 12px;
142
+ }
143
+
144
+ .waveform-btn {
145
+ border-color: #fff;
146
+ }
147
+ ```
148
+
149
+ ## Browser Support
150
+
151
+ - Chrome/Edge 90+
152
+ - Firefox 88+
153
+ - Safari 14+
154
+ - Mobile browsers
155
+
156
+ ## Examples
157
+
158
+ Check the `/examples` directory for:
159
+ - Basic player setup
160
+ - Multiple players
161
+ - Custom styling
162
+ - Event handling
163
+ - Pre-generated waveforms
164
+
165
+ ## Development
166
+
167
+ ```bash
168
+ # Install dependencies
169
+ npm install
170
+
171
+ # Development mode
172
+ npm run dev
173
+
174
+ # Build
175
+ npm run build
176
+
177
+ # Check size
178
+ npm run size
179
+ ```
180
+
181
+ ## License
182
+
183
+ MIT © David Sherlock / ArrayPress
184
+
185
+ ## Credits
186
+
187
+ Created by [David Sherlock](https://github.com/arraypress)
@@ -0,0 +1,185 @@
1
+ /**
2
+ * WaveformPlayer.css
3
+ * Ultra-minimal styles
4
+ */
5
+
6
+ /* Base structure */
7
+ .waveform-player {
8
+ font-family: inherit;
9
+ color: inherit;
10
+ line-height: 1.4;
11
+ }
12
+
13
+ .waveform-player * {
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ .waveform-player-inner {
18
+ padding: 12px;
19
+ border-radius: 4px;
20
+ }
21
+
22
+ /* Body container */
23
+ .waveform-body {
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: 8px;
27
+ }
28
+
29
+ /* Track row */
30
+ .waveform-track {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: 12px;
34
+ position: relative;
35
+ }
36
+
37
+ /* Play button */
38
+ .waveform-btn {
39
+ width: 36px;
40
+ height: 36px;
41
+ min-width: 36px;
42
+ border-radius: 50%;
43
+ border: 2px solid currentColor;
44
+ background: transparent;
45
+ color: inherit;
46
+ cursor: pointer;
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ transition: all 0.2s ease;
51
+ padding: 0;
52
+ opacity: 0.9;
53
+ flex-shrink: 0;
54
+ }
55
+
56
+ .waveform-btn:hover:not(:disabled) {
57
+ opacity: 1;
58
+ transform: scale(1.05);
59
+ }
60
+
61
+ .waveform-btn:disabled {
62
+ cursor: not-allowed;
63
+ opacity: 0.3;
64
+ }
65
+
66
+ /* Icons */
67
+ .waveform-btn > * {
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ width: 100%;
72
+ height: 100%;
73
+ }
74
+
75
+ .waveform-btn svg {
76
+ width: 16px;
77
+ height: 16px;
78
+ fill: currentColor;
79
+ display: block;
80
+ }
81
+
82
+ .waveform-icon-play svg {
83
+ margin-left: 1px;
84
+ }
85
+
86
+ /* Waveform container */
87
+ .waveform-container {
88
+ flex: 1;
89
+ position: relative;
90
+ min-height: 60px;
91
+ cursor: pointer;
92
+ overflow: hidden;
93
+ min-width: 0;
94
+ width: 100%;
95
+ }
96
+
97
+ .waveform-container canvas {
98
+ display: block;
99
+ width: 100%;
100
+ height: 100%;
101
+ max-width: 100%;
102
+ transition: opacity 0.3s ease;
103
+ }
104
+
105
+ /* Info section */
106
+ .waveform-info {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 8px;
110
+ font-size: 13px;
111
+ min-height: 20px;
112
+ }
113
+
114
+ .waveform-text {
115
+ flex: 1;
116
+ display: flex;
117
+ flex-direction: column;
118
+ gap: 2px;
119
+ min-width: 0;
120
+ }
121
+
122
+ .waveform-title {
123
+ white-space: nowrap;
124
+ overflow: hidden;
125
+ text-overflow: ellipsis;
126
+ font-weight: 500;
127
+ }
128
+
129
+ .waveform-subtitle {
130
+ font-size: 11px;
131
+ white-space: nowrap;
132
+ overflow: hidden;
133
+ text-overflow: ellipsis;
134
+ }
135
+
136
+ .waveform-time {
137
+ font-size: 11px;
138
+ white-space: nowrap;
139
+ flex-shrink: 0;
140
+ }
141
+
142
+ /* Loading state - simplified */
143
+ .waveform-loading {
144
+ position: absolute;
145
+ inset: 0;
146
+ background: rgba(0, 0, 0, 0.1);
147
+ z-index: 1;
148
+ }
149
+
150
+ /* Error state */
151
+ .waveform-error {
152
+ position: absolute;
153
+ inset: 0;
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ background: rgba(0, 0, 0, 0.2);
158
+ z-index: 1;
159
+ }
160
+
161
+ .waveform-error-text {
162
+ font-size: 12px;
163
+ opacity: 0.7;
164
+ text-align: center;
165
+ padding: 0 20px;
166
+ }
167
+
168
+ /* Minimal responsive */
169
+ @media (max-width: 480px) {
170
+ .waveform-btn {
171
+ width: 32px;
172
+ height: 32px;
173
+ min-width: 32px;
174
+ }
175
+
176
+ .waveform-container {
177
+ min-height: 50px;
178
+ }
179
+ }
180
+
181
+ /* Accessibility */
182
+ .waveform-btn:focus-visible {
183
+ outline: 2px solid currentColor;
184
+ outline-offset: 2px;
185
+ }
@@ -0,0 +1,42 @@
1
+ function T(t){let e={};return t.dataset.url&&(e.url=t.dataset.url),t.dataset.height&&(e.height=parseInt(t.dataset.height)),t.dataset.samples&&(e.samples=parseInt(t.dataset.samples)),t.dataset.waveformStyle&&(e.waveformStyle=t.dataset.waveformStyle),t.dataset.barWidth&&(e.barWidth=parseInt(t.dataset.barWidth)),t.dataset.barSpacing&&(e.barSpacing=parseInt(t.dataset.barSpacing)),t.dataset.colorPreset&&(e.colorPreset=t.dataset.colorPreset),t.dataset.waveformColor&&(e.waveformColor=t.dataset.waveformColor),t.dataset.progressColor&&(e.progressColor=t.dataset.progressColor),t.dataset.buttonColor&&(e.buttonColor=t.dataset.buttonColor),t.dataset.buttonHoverColor&&(e.buttonHoverColor=t.dataset.buttonHoverColor),t.dataset.textColor&&(e.textColor=t.dataset.textColor),t.dataset.textSecondaryColor&&(e.textSecondaryColor=t.dataset.textSecondaryColor),t.dataset.backgroundColor&&(e.backgroundColor=t.dataset.backgroundColor),t.dataset.borderColor&&(e.borderColor=t.dataset.borderColor),t.dataset.color&&(e.waveformColor=t.dataset.color),t.dataset.theme&&(e.colorPreset=t.dataset.theme),t.dataset.autoplay&&(e.autoplay=t.dataset.autoplay==="true"),t.dataset.showTime&&(e.showTime=t.dataset.showTime==="true"),t.dataset.showHoverTime&&(e.showHoverTime=t.dataset.showHoverTime==="true"),t.dataset.showBpm&&(e.showBPM=t.dataset.showBpm==="true"),t.dataset.singlePlay&&(e.singlePlay=t.dataset.singlePlay==="true"),t.dataset.playOnSeek&&(e.playOnSeek=t.dataset.playOnSeek==="true"),t.dataset.title&&(e.title=t.dataset.title),t.dataset.subtitle&&(e.subtitle=t.dataset.subtitle),t.dataset.waveform&&(e.waveform=t.dataset.waveform),e}function P(t){if(!t||isNaN(t))return"0:00";let e=Math.floor(t/60),o=Math.floor(t%60);return`${e}:${o.toString().padStart(2,"0")}`}function k(t){let e=t||Math.random().toString();return btoa(e.substring(0,10)).replace(/[^a-zA-Z0-9]/g,"")}function W(t){if(!t)return"Audio";let e=t.split("/");return e[e.length-1].split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,s=>s.toUpperCase())}function B(...t){let e={};for(let o of t)for(let r in o)o[r]!==null&&o[r]!==void 0&&(e[r]=o[r]);return e}function L(t,e){let o;return function(...s){let n=()=>{clearTimeout(o),t(...s)};clearTimeout(o),o=setTimeout(n,e)}}function S(t,e){if(t.length===e)return t;if(t.length===0||e===0)return[];let o=[];if(e>t.length){let r=(t.length-1)/(e-1);for(let s=0;s<e;s++){let n=s*r,a=Math.floor(n),i=Math.ceil(n),d=n-a;if(i>=t.length)o.push(t[t.length-1]);else if(a===i)o.push(t[a]);else{let l=t[a]*(1-d)+t[i]*d;o.push(l)}}}else{let r=t.length/e;for(let s=0;s<e;s++){let n=Math.floor(s*r),a=Math.floor((s+1)*r),i=0,d=0;for(let l=n;l<=a&&l<t.length;l++)t[l]>i&&(i=t[l]),d++;if(d===0){let l=Math.min(Math.round(s*r),t.length-1);i=t[l]}o.push(i)}}return o}function I(t,e,o,r,s){let n=window.devicePixelRatio||1,a=s.barWidth*n,i=s.barSpacing*n,d=Math.floor(e.width/(a+i)),l=S(o,d),h=e.height,f=r*e.width;t.clearRect(0,0,e.width,e.height);for(let m=0;m<l.length;m++){let p=m*(a+i);if(p+a>e.width)break;let c=l[m]*h*.9,g=h-c;t.fillStyle=s.color,t.fillRect(p,g,a,c)}t.save(),t.beginPath(),t.rect(0,0,f,h),t.clip();for(let m=0;m<l.length;m++){let p=m*(a+i);if(p>f)break;let c=l[m]*h*.9,g=h-c;t.fillStyle=s.progressColor,t.fillRect(p,g,a,c)}t.restore()}function x(t,e,o,r,s){let n=window.devicePixelRatio||1,a=s.barWidth*n,i=s.barSpacing*n,d=Math.floor(e.width/(a+i)),l=S(o,d),h=e.height,f=h/2,m=r*e.width;t.clearRect(0,0,e.width,e.height);for(let p=0;p<l.length;p++){let c=p*(a+i);if(c+a>e.width)break;let g=l[p]*h*.45;t.fillStyle=s.color,t.fillRect(c,f-g,a,g),t.fillRect(c,f,a,g)}t.save(),t.beginPath(),t.rect(0,0,m,h),t.clip();for(let p=0;p<l.length;p++){let c=p*(a+i);if(c>m)break;let g=l[p]*h*.45;t.fillStyle=s.progressColor,t.fillRect(c,f-g,a,g),t.fillRect(c,f,a,g)}t.restore()}function H(t,e,o,r,s){let n=e.width,a=e.height,i=a/2,d=a*.35;t.clearRect(0,0,n,a);let l=(h,f,m=1,p=!1)=>{p&&(t.shadowBlur=12,t.shadowColor=h),t.strokeStyle=h,t.lineWidth=f,t.lineCap="round",t.lineJoin="round",t.beginPath(),t.moveTo(0,i);let c=[],g=Math.floor(o.length*m);for(let u=0;u<g;u++){let v=u/(o.length-1)*n,C=o[u],y=Math.sin(u*.1)*C,w=i+y*d;c.push({x:v,y:w})}for(let u=0;u<c.length-1;u++){let v=c[u].x+(c[u+1].x-c[u].x)*.5,C=c[u].y,y=c[u+1].x-(c[u+1].x-c[u].x)*.5,w=c[u+1].y;t.bezierCurveTo(v,C,y,w,c[u+1].x,c[u+1].y)}t.stroke(),p&&(t.shadowBlur=0)};t.strokeStyle="rgba(255, 255, 255, 0.03)",t.lineWidth=.5,t.beginPath(),t.moveTo(0,i),t.lineTo(n,i),t.stroke();for(let h=0;h<=10;h++){let f=n/10*h;t.beginPath(),t.moveTo(f,0),t.lineTo(f,a),t.stroke()}l(s.color,2,1,!1),r>0&&l(s.progressColor,3,r,!0)}function q(t,e,o,r,s){let n=window.devicePixelRatio||1,a=(s.barWidth||3)*n,i=(s.barSpacing||1)*n,d=Math.floor(e.width/(a+i)),l=S(o,d),h=e.height,f=4*n,m=2*n,p=r*e.width,c=h/2;t.clearRect(0,0,e.width,e.height);for(let g=0;g<l.length;g++){let u=g*(a+i);if(u+a>e.width)break;let v=l[g]*h*.9,C=Math.floor(v/(f+m));t.fillStyle=u<p?s.progressColor:s.color;for(let y=0;y<C;y++){let w=y*(f+m);t.fillRect(u,c-w-f,a,f),y>0&&t.fillRect(u,c+w,a,f)}}}function U(t,e,o,r,s){let n=window.devicePixelRatio||1,a=(s.barWidth||2)*n,i=(s.barSpacing||3)*n,d=Math.floor(e.width/(a+i)),l=S(o,d),h=e.height,f=Math.max(1.5*n,a/2),m=r*e.width,p=h/2;t.clearRect(0,0,e.width,e.height);for(let c=0;c<l.length;c++){let g=c*(a+i)+a/2;if(g>e.width)break;let u=l[c]*h*.9;t.fillStyle=g<m?s.progressColor:s.color,t.beginPath(),t.arc(g,p-u/2,f,0,Math.PI*2),t.fill(),t.beginPath(),t.arc(g,p+u/2,f,0,Math.PI*2),t.fill()}}function F(t,e,o,r,s){let n=e.width,a=e.height,i=a/2,d=4,l=d/2;if(t.clearRect(0,0,n,a),t.fillStyle=s.color||"rgba(255, 255, 255, 0.2)",t.beginPath(),t.moveTo(l,i-d/2),t.lineTo(n-l,i-d/2),t.arc(n-l,i,d/2,-Math.PI/2,Math.PI/2),t.lineTo(l,i+d/2),t.arc(l,i,d/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),r>0){let h=Math.max(l*2,r*n);t.shadowBlur=8,t.shadowColor=s.progressColor,t.fillStyle=s.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.moveTo(l,i-d/2),t.lineTo(h-l,i-d/2),t.arc(h-l,i,d/2,-Math.PI/2,Math.PI/2),t.lineTo(l,i+d/2),t.arc(l,i,d/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),t.shadowBlur=0;let f=8,m=h;t.shadowBlur=4,t.shadowColor="rgba(0, 0, 0, 0.3)",t.shadowOffsetY=2,t.fillStyle="#ffffff",t.beginPath(),t.arc(m,i,f,0,Math.PI*2),t.fill(),t.shadowBlur=0,t.shadowOffsetY=0,t.fillStyle=s.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.arc(m,i,f*.4,0,Math.PI*2),t.fill()}}var $={bars:I,mirror:x,line:H,blocks:q,dots:U,seekbar:F};function z(t,e,o,r,s){($[s.waveformStyle]||I)(t,e,o,r,s)}function R(t){try{let e=t.getChannelData(0),o=t.sampleRate,r=Y(e,o);if(r.length<2)return 120;let s=[];for(let d=1;d<r.length;d++)s.push((r[d]-r[d-1])/o);let n={};s.forEach(d=>{let l=60/d,h=Math.round(l/3)*3;h>60&&h<200&&(n[h]=(n[h]||0)+1)});let a=0,i=120;for(let[d,l]of Object.entries(n))l>a&&(a=l,i=parseInt(d));return i<70&&n[i*2]?i*=2:i>160&&n[Math.round(i/2)]&&(i=Math.round(i/2)),i-1}catch(e){return console.warn("BPM detection failed:",e),null}}function Y(t,e){let s=[],n=0;for(let a=0;a<t.length-2048;a+=1024){let i=0;for(let h=a;h<a+2048;h++)i+=t[h]*t[h];i=i/2048;let d=i-n,l=n*1.8+.01;if(d>l&&i>.01){let h=s[s.length-1]||0,f=e*.15;a-h>f&&s.push(a)}n=i*.8+n*.2}return s}function N(t,e=200){let o=t.length/e,r=~~(o/10)||1,s=t.numberOfChannels,n=[];for(let i=0;i<s;i++){let d=t.getChannelData(i);for(let l=0;l<e;l++){let h=~~(l*o),f=~~(h+o),m=0,p=0;for(let g=h;g<f;g+=r){let u=d[g];u>p&&(p=u),u<m&&(m=u)}let c=Math.max(Math.abs(p),Math.abs(m));(i===0||c>n[l])&&(n[l]=c)}}let a=Math.max(...n);return a>0?n.map(i=>i/a):n}async function M(t,e=200,o=!1){let r=await fetch(t);if(!r.ok)throw new Error(`HTTP error! status: ${r.status}`);let s=await r.arrayBuffer(),n=window.AudioContext||window.webkitAudioContext,a=new n;try{let i=await a.decodeAudioData(s),l={peaks:N(i,e)};return o&&(l.bpm=R(i)),l}finally{await a.close()}}function A(t=200){let e=[];for(let o=0;o<t;o++){let r=Math.random()*.5+.3,s=Math.sin(o/t*Math.PI*4)*.2;e.push(Math.max(.1,Math.min(1,r+s)))}return e}var D={url:"",height:60,samples:200,waveformStyle:"mirror",barWidth:2,barSpacing:0,colorPreset:"dark",waveformColor:null,progressColor:null,buttonColor:null,buttonHoverColor:null,textColor:null,textSecondaryColor:null,backgroundColor:null,borderColor:null,autoplay:!1,showTime:!0,showHoverTime:!1,showBPM:!1,singlePlay:!0,playOnSeek:!0,title:null,subtitle:null,playIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M8 5v14l11-7z"/></svg>',pauseIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg>',onLoad:null,onPlay:null,onPause:null,onEnd:null,onError:null,onTimeUpdate:null},O={bars:{barWidth:3,barSpacing:1},mirror:{barWidth:2,barSpacing:0},line:{barWidth:2,barSpacing:0},blocks:{barWidth:4,barSpacing:2},dots:{barWidth:3,barSpacing:3},seekbar:{barWidth:1,barSpacing:0}};var b=class t{static instances=new Map;static currentlyPlaying=null;constructor(e,o={}){if(this.container=typeof e=="string"?document.querySelector(e):e,!this.container)throw new Error("WaveformPlayer: Container element not found");let r=T(this.container);this.options=B(D,r,o);let s=O[this.options.waveformStyle];s&&(r.barWidth===void 0&&o.barWidth===void 0&&(this.options.barWidth=s.barWidth),r.barSpacing===void 0&&o.barSpacing===void 0&&(this.options.barSpacing=s.barSpacing)),this.options.waveformColor=this.options.waveformColor||"rgba(255, 255, 255, 0.3)",this.options.progressColor=this.options.progressColor||"rgba(255, 255, 255, 0.9)",this.options.buttonColor=this.options.buttonColor||"rgba(255, 255, 255, 0.9)",this.options.textColor=this.options.textColor||"#ffffff",this.options.textSecondaryColor=this.options.textSecondaryColor||"rgba(255, 255, 255, 0.6)",this.audio=null,this.canvas=null,this.ctx=null,this.waveformData=[],this.progress=0,this.isPlaying=!1,this.isLoading=!1,this.hasError=!1,this.updateTimer=null,this.resizeObserver=null,this.id=this.container.id||k(this.options.url),t.instances.set(this.id,this),this.init()}init(){this.createDOM(),this.createAudio(),this.bindEvents(),this.setupResizeObserver(),requestAnimationFrame(()=>{this.resizeCanvas(),this.options.url&&this.load(this.options.url).then(()=>{this.options.autoplay&&this.play()}).catch(e=>{console.error("Failed to load audio:",e)})})}createDOM(){this.container.innerHTML="",this.container.className="waveform-player",this.container.innerHTML=`
2
+ <div class="waveform-player-inner">
3
+ <div class="waveform-body">
4
+ <div class="waveform-track">
5
+ <button class="waveform-btn" aria-label="Play/Pause" style="
6
+ border-color: ${this.options.buttonColor};
7
+ color: ${this.options.buttonColor};
8
+ ">
9
+ <span class="waveform-icon-play">${this.options.playIcon}</span>
10
+ <span class="waveform-icon-pause" style="display:none;">${this.options.pauseIcon}</span>
11
+ </button>
12
+
13
+ <div class="waveform-container">
14
+ <canvas></canvas>
15
+ <div class="waveform-loading" style="display:none;"></div>
16
+ <div class="waveform-error" style="display:none;">
17
+ <span class="waveform-error-text">Unable to load audio</span>
18
+ </div>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="waveform-info">
23
+ <div class="waveform-text">
24
+ <span class="waveform-title" style="color: ${this.options.textColor};"></span>
25
+ ${this.options.subtitle?`<span class="waveform-subtitle" style="color: ${this.options.textSecondaryColor};">${this.options.subtitle}</span>`:""}
26
+ </div>
27
+ <div style="display: flex; align-items: center; gap: 1rem;">
28
+ ${this.options.showBPM?`
29
+ <span class="waveform-bpm" style="color: ${this.options.textSecondaryColor}; display: none;">
30
+ <span class="bpm-value">--</span> BPM
31
+ </span>
32
+ `:""}
33
+ ${this.options.showTime?`
34
+ <span class="waveform-time" style="color: ${this.options.textSecondaryColor};">
35
+ <span class="time-current">0:00</span> / <span class="time-total">0:00</span>
36
+ </span>
37
+ `:""}
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ `,this.playBtn=this.container.querySelector(".waveform-btn"),this.canvas=this.container.querySelector("canvas"),this.ctx=this.canvas.getContext("2d"),this.titleEl=this.container.querySelector(".waveform-title"),this.subtitleEl=this.container.querySelector(".waveform-subtitle"),this.currentTimeEl=this.container.querySelector(".time-current"),this.totalTimeEl=this.container.querySelector(".time-total"),this.bpmEl=this.container.querySelector(".waveform-bpm"),this.bpmValueEl=this.container.querySelector(".bpm-value"),this.loadingEl=this.container.querySelector(".waveform-loading"),this.errorEl=this.container.querySelector(".waveform-error"),this.resizeCanvas()}createAudio(){this.audio=new Audio,this.audio.preload="metadata",this.audio.crossOrigin="anonymous"}bindEvents(){this.playBtn.addEventListener("click",()=>this.togglePlay()),this.audio.addEventListener("loadstart",()=>this.setLoading(!0)),this.audio.addEventListener("loadedmetadata",()=>this.onMetadataLoaded()),this.audio.addEventListener("canplay",()=>this.setLoading(!1)),this.audio.addEventListener("play",()=>this.onPlay()),this.audio.addEventListener("pause",()=>this.onPause()),this.audio.addEventListener("ended",()=>this.onEnded()),this.audio.addEventListener("error",e=>this.onError(e)),this.canvas.addEventListener("click",e=>this.handleCanvasClick(e)),window.addEventListener("resize",L(()=>this.resizeCanvas(),100))}setupResizeObserver(){"ResizeObserver"in window&&(this.resizeObserver=new ResizeObserver(()=>{this.resizeCanvas()}),this.canvas?.parentElement&&this.resizeObserver.observe(this.canvas.parentElement))}async load(e){try{this.setLoading(!0),this.progress=0,this.hasError=!1,this.audio.src=e,await new Promise((r,s)=>{let n=()=>{this.audio.removeEventListener("loadedmetadata",n),this.audio.removeEventListener("error",a),r()},a=i=>{this.audio.removeEventListener("loadedmetadata",n),this.audio.removeEventListener("error",a),s(i)};this.audio.addEventListener("loadedmetadata",n),this.audio.addEventListener("error",a)});let o=this.options.title||W(e);if(this.titleEl&&(this.titleEl.textContent=o),this.options.waveform)this.setWaveformData(this.options.waveform);else try{let r=await M(e,this.options.samples,this.options.showBPM);this.waveformData=r.peaks,r.bpm&&(this.detectedBPM=r.bpm,this.updateBPMDisplay())}catch(r){console.warn("Using placeholder waveform:",r),this.waveformData=A(this.options.samples)}this.drawWaveform(),this.options.onLoad&&this.options.onLoad(this)}catch(o){console.error("Failed to load audio:",o),this.onError(o)}finally{this.setLoading(!1)}}setWaveformData(e){if(typeof e=="string")try{let o=JSON.parse(e);this.waveformData=Array.isArray(o)?o:[]}catch{this.waveformData=e.split(",").map(Number)}else this.waveformData=Array.isArray(e)?e:[];this.drawWaveform()}drawWaveform(){!this.ctx||this.waveformData.length===0||z(this.ctx,this.canvas,this.waveformData,this.progress,{...this.options,waveformStyle:this.options.waveformStyle||"bars",color:this.options.waveformColor,progressColor:this.options.progressColor})}resizeCanvas(){let e=window.devicePixelRatio||1,o=this.canvas.getBoundingClientRect();this.canvas.width=o.width*e,this.canvas.height=this.options.height*e,this.canvas.style.height=this.options.height+"px",this.canvas.parentElement.style.height=this.options.height+"px",this.drawWaveform()}handleCanvasClick(e){if(!this.audio.duration)return;let o=this.canvas.getBoundingClientRect(),r=e.clientX-o.left,s=Math.max(0,Math.min(1,r/o.width));this.seekToPercent(s)}setLoading(e){this.isLoading=e,this.loadingEl&&(this.loadingEl.style.display=e?"block":"none")}onMetadataLoaded(){this.totalTimeEl&&(this.totalTimeEl.textContent=P(this.audio.duration))}onPlay(){this.isPlaying=!0,this.playBtn.classList.add("playing");let e=this.playBtn.querySelector(".waveform-icon-play"),o=this.playBtn.querySelector(".waveform-icon-pause");e&&(e.style.display="none"),o&&(o.style.display="flex"),this.startSmoothUpdate(),this.options.onPlay&&this.options.onPlay(this)}onPause(){this.isPlaying=!1,this.playBtn.classList.remove("playing");let e=this.playBtn.querySelector(".waveform-icon-play"),o=this.playBtn.querySelector(".waveform-icon-pause");e&&(e.style.display="flex"),o&&(o.style.display="none"),this.stopSmoothUpdate(),this.options.onPause&&this.options.onPause(this)}onEnded(){this.progress=0,this.audio.currentTime=0,this.drawWaveform(),this.currentTimeEl&&(this.currentTimeEl.textContent="0:00"),this.onPause(),this.options.onEnd&&this.options.onEnd(this)}onError(e){console.error("Audio error:",e),this.hasError=!0,this.setLoading(!1),this.errorEl&&(this.errorEl.style.display="flex"),this.canvas&&(this.canvas.style.opacity="0.2"),this.playBtn&&(this.playBtn.disabled=!0),this.options.onError&&this.options.onError(e,this)}startSmoothUpdate(){this.stopSmoothUpdate();let e=()=>{this.isPlaying&&this.audio.duration&&(this.updateProgress(),this.updateTimer=requestAnimationFrame(e))};this.updateTimer=requestAnimationFrame(e)}stopSmoothUpdate(){this.updateTimer&&(cancelAnimationFrame(this.updateTimer),this.updateTimer=null)}updateProgress(){if(!this.audio.duration)return;let e=this.audio.currentTime/this.audio.duration;Math.abs(e-this.progress)>.001&&(this.progress=e,this.drawWaveform()),this.currentTimeEl&&(this.currentTimeEl.textContent=P(this.audio.currentTime)),this.options.onTimeUpdate&&this.options.onTimeUpdate(this.audio.currentTime,this.audio.duration,this)}updateBPMDisplay(){this.bpmEl&&this.bpmValueEl&&this.detectedBPM&&(this.bpmValueEl.textContent=Math.round(this.detectedBPM),this.bpmEl.style.display="inline-flex")}play(){this.options.singlePlay&&t.currentlyPlaying&&t.currentlyPlaying!==this&&t.currentlyPlaying.pause(),t.currentlyPlaying=this,this.audio.play()}pause(){t.currentlyPlaying===this&&(t.currentlyPlaying=null),this.audio.pause()}togglePlay(){this.isPlaying?this.pause():this.play()}seekToPercent(e){this.audio&&this.audio.duration&&(this.audio.currentTime=this.audio.duration*Math.max(0,Math.min(1,e)),this.updateProgress())}setVolume(e){this.audio&&(this.audio.volume=Math.max(0,Math.min(1,e)))}destroy(){this.pause(),this.stopSmoothUpdate(),this.resizeObserver&&this.resizeObserver.disconnect(),t.instances.delete(this.id),this.audio&&(this.audio.src=""),this.container.innerHTML=""}static getInstance(e){if(typeof e=="string"){let o=this.instances.get(e);if(o)return o;let r=document.getElementById(e);if(r)return Array.from(this.instances.values()).find(s=>s.container===r)}if(e instanceof HTMLElement)return Array.from(this.instances.values()).find(o=>o.container===e)}static getAllInstances(){return Array.from(this.instances.values())}static destroyAll(){this.instances.forEach(e=>e.destroy()),this.instances.clear()}static async generateWaveformData(e,o=200){try{return(await M(e,o)).peaks}catch(r){throw console.error("Failed to generate waveform:",r),r}}};function E(){if(typeof document>"u")return;document.querySelectorAll("[data-waveform-player]").forEach(e=>{if(e.dataset.waveformInitialized!=="true")try{new b(e),e.dataset.waveformInitialized="true"}catch(o){console.error("Failed to initialize WaveformPlayer:",o,e)}})}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",E):E());b.init=E;typeof window<"u"&&(window.WaveformPlayer=b);var st=b;export{b as WaveformPlayer,st as default};