@arraypress/waveform-player 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -30
- package/dist/waveform-player.css +1 -185
- package/dist/waveform-player.esm.js +58 -38
- package/dist/waveform-player.js +384 -61
- package/dist/waveform-player.min.js +58 -38
- package/package.json +13 -8
- package/src/css/waveform-player.css +346 -0
- package/src/{audio.js → js/audio.js} +37 -19
- package/src/{core.js → js/core.js} +401 -40
- package/src/{themes.js → js/themes.js} +15 -0
- package/src/{utils.js → js/utils.js} +35 -1
- /package/src/{bpm.js → js/bpm.js} +0 -0
- /package/src/{drawing.js → js/drawing.js} +0 -0
- /package/src/{index.js → js/index.js} +0 -0
package/README.md
CHANGED
|
@@ -2,67 +2,122 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight, customizable audio player with waveform visualization. Under 6KB gzipped.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-

|
|
7
|
-

|
|
5
|
+
**[Live Demo](https://waveformplayer.com)** | **[Documentation](https://waveformplayer.com/#docs)** | **[NPM Package](https://www.npmjs.com/package/@arraypress/waveform-player)**
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
## Why WaveformPlayer?
|
|
15
|
+
|
|
16
|
+
- **Zero Config** - Just add `data-waveform-player` to any div. No JavaScript required.
|
|
17
|
+
- **Tiny** - 6KB gzipped vs 40KB+ for alternatives
|
|
18
|
+
- **Real Waveforms** - Actual audio analysis, not fake waves
|
|
19
|
+
- **No Dependencies** - No jQuery, no bloat, pure vanilla JS
|
|
20
|
+
- **Works Everywhere** - WordPress, Shopify, React, Vue, or plain HTML
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
Simplest possible usage:
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<!-- Just this. That's it. -->
|
|
28
|
+
<div data-waveform-player data-url="song.mp3"></div>
|
|
29
|
+
```
|
|
19
30
|
|
|
20
31
|
## Installation
|
|
21
32
|
|
|
22
33
|
### NPM
|
|
23
34
|
```bash
|
|
24
|
-
npm install waveform-player
|
|
35
|
+
npm install @arraypress/waveform-player
|
|
25
36
|
```
|
|
26
37
|
|
|
27
38
|
### CDN
|
|
28
39
|
```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>
|
|
40
|
+
<link rel="stylesheet" href="https://unpkg.com/@arraypress/waveform-player@latest/dist/waveform-player.css">
|
|
41
|
+
<script src="https://unpkg.com/@arraypress/waveform-player@latest/dist/waveform-player.min.js"></script>
|
|
31
42
|
```
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
### Download
|
|
45
|
+
```html
|
|
46
|
+
<link rel="stylesheet" href="waveform-player.css">
|
|
47
|
+
<script src="waveform-player.js"></script>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Features
|
|
34
51
|
|
|
35
|
-
|
|
52
|
+
- 🎨 **6 Visual Styles** - Bars, mirror, line, blocks, dots, seekbar
|
|
53
|
+
- 🎯 **Tiny Footprint** - Under 6KB gzipped
|
|
54
|
+
- ⚡ **Zero Dependencies** - Pure JavaScript
|
|
55
|
+
- 🎭 **Fully Customizable** - Colors, sizes, styles
|
|
56
|
+
- 📱 **Responsive** - Works on all devices
|
|
57
|
+
- 🎵 **BPM Detection** - Automatic tempo detection (optional)
|
|
58
|
+
- 💾 **Waveform Caching** - Pre-generate waveforms for performance
|
|
59
|
+
- 🌐 **Framework Agnostic** - Works with React, Vue, Angular, or vanilla JS
|
|
60
|
+
|
|
61
|
+
## Comparison
|
|
62
|
+
|
|
63
|
+
| Feature | WaveformPlayer | WaveSurfer.js | Amplitude.js |
|
|
64
|
+
|---------|---------------|---------------|--------------|
|
|
65
|
+
| Size (gzipped) | 6KB | 40KB+ | 35KB+ |
|
|
66
|
+
| Zero Config | ✅ | ❌ | ❌ |
|
|
67
|
+
| Dependencies | None | None | None |
|
|
68
|
+
| Waveform Styles | 6 | 3 | N/A |
|
|
69
|
+
| Setup Time | 30 seconds | 5+ minutes | 5+ minutes |
|
|
70
|
+
| Real Waveforms | ✅ | ✅ | ❌ |
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
### HTML (Zero JavaScript)
|
|
36
75
|
```html
|
|
37
76
|
<div data-waveform-player
|
|
38
77
|
data-url="audio.mp3"
|
|
39
|
-
data-title="My Song"
|
|
78
|
+
data-title="My Song"
|
|
79
|
+
data-subtitle="Artist Name"
|
|
80
|
+
data-waveform-style="mirror">
|
|
40
81
|
</div>
|
|
41
82
|
```
|
|
42
83
|
|
|
43
|
-
### JavaScript
|
|
84
|
+
### JavaScript API
|
|
44
85
|
```javascript
|
|
45
|
-
import WaveformPlayer from 'waveform-player';
|
|
86
|
+
import WaveformPlayer from '@arraypress/waveform-player';
|
|
46
87
|
|
|
47
88
|
const player = new WaveformPlayer('#player', {
|
|
48
89
|
url: 'audio.mp3',
|
|
49
90
|
waveformStyle: 'mirror',
|
|
50
|
-
height: 80
|
|
91
|
+
height: 80,
|
|
92
|
+
barWidth: 2,
|
|
93
|
+
barSpacing: 1
|
|
51
94
|
});
|
|
52
95
|
```
|
|
53
96
|
|
|
97
|
+
## Visual Styles
|
|
98
|
+
|
|
99
|
+
Choose from 6 built-in styles:
|
|
100
|
+
|
|
101
|
+
- **bars** - Classic waveform bars
|
|
102
|
+
- **mirror** - SoundCloud-style mirrored waveform
|
|
103
|
+
- **line** - Smooth oscilloscope line
|
|
104
|
+
- **blocks** - LED meter blocks
|
|
105
|
+
- **dots** - Circular dots
|
|
106
|
+
- **seekbar** - Minimal progress bar
|
|
107
|
+
|
|
54
108
|
## Options
|
|
55
109
|
|
|
56
110
|
| Option | Type | Default | Description |
|
|
57
111
|
|--------|------|---------|-------------|
|
|
58
112
|
| `url` | string | `''` | Audio file URL |
|
|
59
|
-
| `waveformStyle` | string | `'bars'` | Visual style: bars, mirror, line, blocks, dots |
|
|
113
|
+
| `waveformStyle` | string | `'bars'` | Visual style: bars, mirror, line, blocks, dots, seekbar |
|
|
60
114
|
| `height` | number | `60` | Waveform height in pixels |
|
|
61
115
|
| `barWidth` | number | `3` | Width of waveform bars |
|
|
62
116
|
| `barSpacing` | number | `1` | Space between bars |
|
|
63
117
|
| `samples` | number | `200` | Number of waveform samples |
|
|
64
118
|
| `waveformColor` | string | `'rgba(255,255,255,0.3)'` | Waveform color |
|
|
65
119
|
| `progressColor` | string | `'rgba(255,255,255,0.9)'` | Progress color |
|
|
120
|
+
| `buttonColor` | string | `'rgba(255,255,255,0.9)'` | Play button color |
|
|
66
121
|
| `showTime` | boolean | `true` | Show time display |
|
|
67
122
|
| `showBPM` | boolean | `false` | Enable BPM detection |
|
|
68
123
|
| `autoplay` | boolean | `false` | Autoplay on load |
|
|
@@ -110,10 +165,10 @@ new WaveformPlayer('#player', {
|
|
|
110
165
|
For better performance, generate waveform data server-side:
|
|
111
166
|
|
|
112
167
|
```javascript
|
|
113
|
-
// Generate waveform data
|
|
168
|
+
// Generate waveform data once
|
|
114
169
|
const waveformData = await WaveformPlayer.generateWaveformData('audio.mp3');
|
|
115
170
|
|
|
116
|
-
// Use pre-generated data
|
|
171
|
+
// Use pre-generated data for instant display
|
|
117
172
|
new WaveformPlayer('#player', {
|
|
118
173
|
url: 'audio.mp3',
|
|
119
174
|
waveform: waveformData // Bypass client-side processing
|
|
@@ -146,20 +201,62 @@ const player = WaveformPlayer.getInstance('my-player');
|
|
|
146
201
|
}
|
|
147
202
|
```
|
|
148
203
|
|
|
204
|
+
## Framework Integration
|
|
205
|
+
|
|
206
|
+
### React
|
|
207
|
+
```jsx
|
|
208
|
+
import { useEffect, useRef } from 'react';
|
|
209
|
+
import WaveformPlayer from '@arraypress/waveform-player';
|
|
210
|
+
|
|
211
|
+
function AudioPlayer({ url }) {
|
|
212
|
+
const playerRef = useRef();
|
|
213
|
+
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
const player = new WaveformPlayer(playerRef.current, { url });
|
|
216
|
+
return () => player.destroy();
|
|
217
|
+
}, [url]);
|
|
218
|
+
|
|
219
|
+
return <div ref={playerRef} />;
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Vue
|
|
224
|
+
```vue
|
|
225
|
+
<template>
|
|
226
|
+
<div ref="player"></div>
|
|
227
|
+
</template>
|
|
228
|
+
|
|
229
|
+
<script>
|
|
230
|
+
import WaveformPlayer from '@arraypress/waveform-player';
|
|
231
|
+
|
|
232
|
+
export default {
|
|
233
|
+
mounted() {
|
|
234
|
+
this.player = new WaveformPlayer(this.$refs.player, {
|
|
235
|
+
url: this.audioUrl
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
beforeDestroy() {
|
|
239
|
+
this.player?.destroy();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
</script>
|
|
243
|
+
```
|
|
244
|
+
|
|
149
245
|
## Browser Support
|
|
150
246
|
|
|
151
247
|
- Chrome/Edge 90+
|
|
152
248
|
- Firefox 88+
|
|
153
249
|
- Safari 14+
|
|
154
|
-
- Mobile browsers
|
|
250
|
+
- Mobile browsers (iOS Safari, Chrome Android)
|
|
155
251
|
|
|
156
252
|
## Examples
|
|
157
253
|
|
|
158
|
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
- Custom styling
|
|
254
|
+
See the [live demo](https://waveformplayer.com) for:
|
|
255
|
+
- All visual styles
|
|
256
|
+
- Custom styling examples
|
|
162
257
|
- Event handling
|
|
258
|
+
- Player builder
|
|
259
|
+
- BPM detection
|
|
163
260
|
- Pre-generated waveforms
|
|
164
261
|
|
|
165
262
|
## Development
|
|
@@ -180,7 +277,7 @@ npm run size
|
|
|
180
277
|
|
|
181
278
|
## License
|
|
182
279
|
|
|
183
|
-
MIT ©
|
|
280
|
+
MIT © [ArrayPress](https://github.com/arraypress)
|
|
184
281
|
|
|
185
282
|
## Credits
|
|
186
283
|
|
package/dist/waveform-player.css
CHANGED
|
@@ -1,185 +1 @@
|
|
|
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
|
-
}
|
|
1
|
+
.waveform-player{font-family:inherit;color:inherit;line-height:1.4}.waveform-player *{box-sizing:border-box}.waveform-player-inner{padding:12px;border-radius:4px}.waveform-body{display:flex;flex-direction:column;gap:8px}.waveform-track{display:flex;align-items:center;gap:12px;position:relative}.waveform-btn{width:36px;height:36px;min-width:36px;border-radius:50%;border:2px solid currentColor;background:transparent;color:inherit;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;padding:0;opacity:.9;flex-shrink:0}.waveform-btn:hover:not(:disabled){opacity:1;transform:scale(1.05)}.waveform-btn:disabled{cursor:not-allowed;opacity:.3}.waveform-btn>*{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.waveform-btn svg{width:16px;height:16px;fill:currentColor;display:block}.waveform-icon-play svg{margin-left:1px}.waveform-container{flex:1;position:relative;min-height:60px;cursor:pointer;min-width:0;width:100%}.waveform-container canvas{display:block;width:100%;height:100%;max-width:100%;transition:opacity .3s ease;position:relative;z-index:1}.waveform-info{display:flex;align-items:center;gap:8px;font-size:13px;min-height:20px}.waveform-text{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.waveform-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}.waveform-subtitle{font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.waveform-time{font-size:11px;white-space:nowrap;flex-shrink:0}.waveform-bpm{font-size:11px;white-space:nowrap;flex-shrink:0;display:inline-flex;align-items:center;gap:4px}.waveform-loading{position:absolute;inset:0;background:#0000001a;z-index:10}.waveform-error{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#0003;z-index:10}.waveform-error-text{font-size:12px;opacity:.7;text-align:center;padding:0 20px}.waveform-markers{position:absolute;inset:0;pointer-events:none;z-index:5}.waveform-marker{position:absolute;top:0;width:2px;height:100%;background:#ffffff80;border:none;padding:0;cursor:pointer;pointer-events:all;transition:all .2s}.waveform-marker:hover{width:4px;z-index:20}.waveform-marker-tooltip{position:absolute;bottom:calc(100% + 4px);left:50%;transform:translate(-50%);background:#000000e6;color:#fff;padding:4px 8px;border-radius:4px;font-size:11px;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .2s;z-index:1000}.waveform-marker:hover .waveform-marker-tooltip{opacity:1}.waveform-btn:focus-visible{outline:2px solid currentColor;outline-offset:2px}.waveform-marker:focus-visible{outline:2px solid currentColor;outline-offset:1px;width:4px}.waveform-speed{position:relative;flex-shrink:0}.speed-btn{background:transparent;border:1px solid rgba(255,255,255,.2);border-radius:4px;padding:4px 8px;color:inherit;font-size:11px;cursor:pointer;transition:all .2s;min-width:40px}.speed-btn:hover{background:#ffffff0d;border-color:#ffffff4d}.speed-value{font-weight:600}.speed-menu{position:absolute;bottom:100%;right:0;margin-bottom:4px;background:#000000f2;border:1px solid rgba(255,255,255,.2);border-radius:6px;padding:4px;z-index:100;min-width:60px}.speed-option{display:block;width:100%;background:transparent;border:none;color:#ffffffb3;padding:6px 12px;font-size:12px;cursor:pointer;transition:all .2s;text-align:left;border-radius:4px}.speed-option:hover{background:#ffffff1a;color:#fff}.speed-option.active{background:#a855f733;color:#a855f7;font-weight:600}.waveform-player.waveform-focused{outline:2px solid rgba(168,85,247,.5);outline-offset:2px;border-radius:4px}.waveform-player:focus{outline:none}.waveform-player:focus-visible{outline:1px solid rgba(168,85,247,.3);outline-offset:1px}.waveform-player.waveform-focused{outline:none}@media (max-width: 480px){.waveform-btn{width:32px;height:32px;min-width:32px}.waveform-container{min-height:50px}.waveform-info{font-size:12px}.waveform-subtitle,.waveform-time,.waveform-bpm{font-size:10px}}
|
|
@@ -1,42 +1,62 @@
|
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
</div>
|
|
1
|
+
function T(t){let e={};if(t.dataset.url&&(e.url=t.dataset.url),t.dataset.height&&(e.height=parseInt(t.dataset.height)),t.dataset.samples&&(e.samples=parseInt(t.dataset.samples)),t.dataset.preload&&(e.preload=t.dataset.preload),t.dataset.waveformStyle&&(e.waveformStyle=t.dataset.waveformStyle),t.dataset.barWidth&&(e.barWidth=parseInt(t.dataset.barWidth)),t.dataset.barSpacing&&(e.barSpacing=parseInt(t.dataset.barSpacing)),t.dataset.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.album&&(e.album=t.dataset.album),t.dataset.artwork&&(e.artwork=t.dataset.artwork),t.dataset.waveform&&(e.waveform=t.dataset.waveform),t.dataset.markers)try{e.markers=JSON.parse(t.dataset.markers)}catch(o){console.warn("Invalid markers JSON:",o)}if(t.dataset.playbackRate&&(e.playbackRate=parseFloat(t.dataset.playbackRate)),t.dataset.showPlaybackSpeed!==void 0&&(e.showPlaybackSpeed=t.dataset.showPlaybackSpeed==="true"),t.dataset.playbackRates)try{e.playbackRates=JSON.parse(t.dataset.playbackRates)}catch(o){console.warn("Invalid playbackRates JSON:",o)}return t.dataset.enableMediaSession!==void 0&&(e.enableMediaSession=t.dataset.enableMediaSession==="true"),e}function C(t){if(!t||isNaN(t))return"0:00";let e=Math.floor(t/60),o=Math.floor(t%60);return`${e}:${o.toString().padStart(2,"0")}`}function R(t){let e=t||Math.random().toString();return btoa(e.substring(0,10)).replace(/[^a-zA-Z0-9]/g,"")}function L(t){if(!t)return"Audio";let e=t.split("/");return e[e.length-1].split(".")[0].replace(/[-_]/g," ").replace(/\b\w/g,i=>i.toUpperCase())}function P(...t){let e={};for(let o of t)for(let a in o)o[a]!==null&&o[a]!==void 0&&(e[a]=o[a]);return e}function x(t,e){let o;return function(...i){let n=()=>{clearTimeout(o),t(...i)};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 a=(t.length-1)/(e-1);for(let i=0;i<e;i++){let n=i*a,r=Math.floor(n),s=Math.ceil(n),h=n-r;if(s>=t.length)o.push(t[t.length-1]);else if(r===s)o.push(t[r]);else{let l=t[r]*(1-h)+t[s]*h;o.push(l)}}}else{let a=t.length/e;for(let i=0;i<e;i++){let n=Math.floor(i*a),r=Math.floor((i+1)*a),s=0,h=0;for(let l=n;l<=r&&l<t.length;l++)t[l]>s&&(s=t[l]),h++;if(h===0){let l=Math.min(Math.round(i*a),t.length-1);s=t[l]}o.push(s)}}return o}function A(t,e,o,a,i){let n=window.devicePixelRatio||1,r=i.barWidth*n,s=i.barSpacing*n,h=Math.floor(e.width/(r+s)),l=S(o,h),d=e.height,p=a*e.width;t.clearRect(0,0,e.width,e.height);for(let y=0;y<l.length;y++){let f=y*(r+s);if(f+r>e.width)break;let c=l[y]*d*.9,m=d-c;t.fillStyle=i.color,t.fillRect(f,m,r,c)}t.save(),t.beginPath(),t.rect(0,0,p,d),t.clip();for(let y=0;y<l.length;y++){let f=y*(r+s);if(f>p)break;let c=l[y]*d*.9,m=d-c;t.fillStyle=i.progressColor,t.fillRect(f,m,r,c)}t.restore()}function D(t,e,o,a,i){let n=window.devicePixelRatio||1,r=i.barWidth*n,s=i.barSpacing*n,h=Math.floor(e.width/(r+s)),l=S(o,h),d=e.height,p=d/2,y=a*e.width;t.clearRect(0,0,e.width,e.height);for(let f=0;f<l.length;f++){let c=f*(r+s);if(c+r>e.width)break;let m=l[f]*d*.45;t.fillStyle=i.color,t.fillRect(c,p-m,r,m),t.fillRect(c,p,r,m)}t.save(),t.beginPath(),t.rect(0,0,y,d),t.clip();for(let f=0;f<l.length;f++){let c=f*(r+s);if(c>y)break;let m=l[f]*d*.45;t.fillStyle=i.progressColor,t.fillRect(c,p-m,r,m),t.fillRect(c,p,r,m)}t.restore()}function H(t,e,o,a,i){let n=e.width,r=e.height,s=r/2,h=r*.35;t.clearRect(0,0,n,r);let l=(d,p,y=1,f=!1)=>{f&&(t.shadowBlur=12,t.shadowColor=d),t.strokeStyle=d,t.lineWidth=p,t.lineCap="round",t.lineJoin="round",t.beginPath(),t.moveTo(0,s);let c=[],m=Math.floor(o.length*y);for(let u=0;u<m;u++){let v=u/(o.length-1)*n,k=o[u],b=Math.sin(u*.1)*k,w=s+b*h;c.push({x:v,y:w})}for(let u=0;u<c.length-1;u++){let v=c[u].x+(c[u+1].x-c[u].x)*.5,k=c[u].y,b=c[u+1].x-(c[u+1].x-c[u].x)*.5,w=c[u+1].y;t.bezierCurveTo(v,k,b,w,c[u+1].x,c[u+1].y)}t.stroke(),f&&(t.shadowBlur=0)};t.strokeStyle="rgba(255, 255, 255, 0.03)",t.lineWidth=.5,t.beginPath(),t.moveTo(0,s),t.lineTo(n,s),t.stroke();for(let d=0;d<=10;d++){let p=n/10*d;t.beginPath(),t.moveTo(p,0),t.lineTo(p,r),t.stroke()}l(i.color,2,1,!1),a>0&&l(i.progressColor,3,a,!0)}function q(t,e,o,a,i){let n=window.devicePixelRatio||1,r=(i.barWidth||3)*n,s=(i.barSpacing||1)*n,h=Math.floor(e.width/(r+s)),l=S(o,h),d=e.height,p=4*n,y=2*n,f=a*e.width,c=d/2;t.clearRect(0,0,e.width,e.height);for(let m=0;m<l.length;m++){let u=m*(r+s);if(u+r>e.width)break;let v=l[m]*d*.9,k=Math.floor(v/(p+y));t.fillStyle=u<f?i.progressColor:i.color;for(let b=0;b<k;b++){let w=b*(p+y);t.fillRect(u,c-w-p,r,p),b>0&&t.fillRect(u,c+w,r,p)}}}function U(t,e,o,a,i){let n=window.devicePixelRatio||1,r=(i.barWidth||2)*n,s=(i.barSpacing||3)*n,h=Math.floor(e.width/(r+s)),l=S(o,h),d=e.height,p=Math.max(1.5*n,r/2),y=a*e.width,f=d/2;t.clearRect(0,0,e.width,e.height);for(let c=0;c<l.length;c++){let m=c*(r+s)+r/2;if(m>e.width)break;let u=l[c]*d*.9;t.fillStyle=m<y?i.progressColor:i.color,t.beginPath(),t.arc(m,f-u/2,p,0,Math.PI*2),t.fill(),t.beginPath(),t.arc(m,f+u/2,p,0,Math.PI*2),t.fill()}}function F(t,e,o,a,i){let n=e.width,r=e.height,s=r/2,h=4,l=h/2;if(t.clearRect(0,0,n,r),t.fillStyle=i.color||"rgba(255, 255, 255, 0.2)",t.beginPath(),t.moveTo(l,s-h/2),t.lineTo(n-l,s-h/2),t.arc(n-l,s,h/2,-Math.PI/2,Math.PI/2),t.lineTo(l,s+h/2),t.arc(l,s,h/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),a>0){let d=Math.max(l*2,a*n);t.shadowBlur=8,t.shadowColor=i.progressColor,t.fillStyle=i.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.moveTo(l,s-h/2),t.lineTo(d-l,s-h/2),t.arc(d-l,s,h/2,-Math.PI/2,Math.PI/2),t.lineTo(l,s+h/2),t.arc(l,s,h/2,Math.PI/2,-Math.PI/2),t.closePath(),t.fill(),t.shadowBlur=0;let p=8,y=d;t.shadowBlur=4,t.shadowColor="rgba(0, 0, 0, 0.3)",t.shadowOffsetY=2,t.fillStyle="#ffffff",t.beginPath(),t.arc(y,s,p,0,Math.PI*2),t.fill(),t.shadowBlur=0,t.shadowOffsetY=0,t.fillStyle=i.progressColor||"rgba(255, 255, 255, 0.9)",t.beginPath(),t.arc(y,s,p*.4,0,Math.PI*2),t.fill()}}var $={bars:A,mirror:D,line:H,blocks:q,dots:U,seekbar:F};function B(t,e,o,a,i){($[i.waveformStyle]||A)(t,e,o,a,i)}function W(t){try{let e=t.getChannelData(0),o=t.sampleRate,a=N(e,o);if(a.length<2)return 120;let i=[];for(let h=1;h<a.length;h++)i.push((a[h]-a[h-1])/o);let n={};i.forEach(h=>{let l=60/h,d=Math.round(l/3)*3;d>60&&d<200&&(n[d]=(n[d]||0)+1)});let r=0,s=120;for(let[h,l]of Object.entries(n))l>r&&(r=l,s=parseInt(h));return s<70&&n[s*2]?s*=2:s>160&&n[Math.round(s/2)]&&(s=Math.round(s/2)),s-1}catch(e){return console.warn("BPM detection failed:",e),null}}function N(t,e){let i=[],n=0;for(let r=0;r<t.length-2048;r+=1024){let s=0;for(let d=r;d<r+2048;d++)s+=t[d]*t[d];s=s/2048;let h=s-n,l=n*1.8+.01;if(h>l&&s>.01){let d=i[i.length-1]||0,p=e*.15;r-d>p&&i.push(r)}n=s*.8+n*.2}return i}function Y(t,e=200){let o=t.length/e,a=~~(o/10)||1,i=t.numberOfChannels,n=[];for(let s=0;s<i;s++){let h=t.getChannelData(s);for(let l=0;l<e;l++){let d=~~(l*o),p=~~(d+o),y=0,f=0;for(let m=d;m<p;m+=a){let u=h[m];u>f&&(f=u),u<y&&(y=u)}let c=Math.max(Math.abs(f),Math.abs(y));(s===0||c>n[l])&&(n[l]=c)}}let r=Math.max(...n);return r>0?n.map(s=>s/r):n}async function M(t,e=200,o=!1){try{let a=new(window.AudioContext||window.webkitAudioContext),n=await(await fetch(t)).arrayBuffer(),r=await a.decodeAudioData(n),s=Y(r,e);s=j(s);let h=null;return o&&(h=await W(r)),a.close(),{peaks:s,bpm:h}}catch(a){throw console.error("Failed to generate waveform:",a),a}}function I(t=200){let e=[];for(let o=0;o<t;o++){let a=Math.random()*.5+.3,i=Math.sin(o/t*Math.PI*4)*.2;e.push(Math.max(.1,Math.min(1,a+i)))}return e}function j(t,e=.95){let o=Math.max(...t);if(o===0||o>e)return t;let a=e/o;return t.map(i=>i*a)}var O={url:"",height:60,samples:200,preload:"metadata",playbackRate:1,showPlaybackSpeed:!1,playbackRates:[.5,.75,1,1.25,1.5,1.75,2],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,enableMediaSession:!0,markers:[],showMarkers:!0,title:null,subtitle:null,artwork:null,album:"",playIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M8 5v14l11-7z"/></svg>',pauseIcon:'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg>',onLoad:null,onPlay:null,onPause:null,onEnd:null,onError:null,onTimeUpdate:null},z={bars:{barWidth:3,barSpacing:1},mirror:{barWidth:2,barSpacing:0},line:{barWidth:2,barSpacing:0},blocks:{barWidth:4,barSpacing:2},dots:{barWidth:3,barSpacing:3},seekbar:{barWidth:1,barSpacing:0}};var g=class t{static instances=new Map;static currentlyPlaying=null;constructor(e,o={}){if(this.container=typeof e=="string"?document.querySelector(e):e,!this.container)throw new Error("WaveformPlayer: Container element not found");let a=T(this.container);this.options=P(O,a,o);let i=z[this.options.waveformStyle];i&&(a.barWidth===void 0&&o.barWidth===void 0&&(this.options.barWidth=i.barWidth),a.barSpacing===void 0&&o.barSpacing===void 0&&(this.options.barSpacing=i.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||R(this.options.url),t.instances.set(this.id,this),this.init()}init(){this.createDOM(),this.createAudio(),this.initPlaybackSpeed(),this.initKeyboardControls(),this.bindEvents(),this.setupResizeObserver(),requestAnimationFrame(()=>{this.resizeCanvas(),this.options.url&&this.load(this.options.url).then(()=>{this.options.autoplay&&this.play()}).catch(e=>{console.error("Failed to load audio:",e)})})}createDOM(){this.container.innerHTML="",this.container.className="waveform-player",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-markers"></div>
|
|
16
|
+
<div class="waveform-loading" style="display:none;"></div>
|
|
17
|
+
<div class="waveform-error" style="display:none;">
|
|
18
|
+
<span class="waveform-error-text">Unable to load audio</span>
|
|
20
19
|
</div>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="waveform-info">
|
|
24
|
+
${this.options.artwork?`
|
|
25
|
+
<img class="waveform-artwork" src="${this.options.artwork}" alt="Album artwork" style="
|
|
26
|
+
width: 40px;
|
|
27
|
+
height: 40px;
|
|
28
|
+
border-radius: 4px;
|
|
29
|
+
object-fit: cover;
|
|
30
|
+
flex-shrink: 0;
|
|
31
|
+
">
|
|
32
|
+
`:""}
|
|
33
|
+
<div class="waveform-text">
|
|
34
|
+
<span class="waveform-title" style="color: ${this.options.textColor};"></span>
|
|
35
|
+
${this.options.subtitle?`<span class="waveform-subtitle" style="color: ${this.options.textSecondaryColor};">${this.options.subtitle}</span>`:""}
|
|
36
|
+
</div>
|
|
37
|
+
<div style="display: flex; align-items: center; gap: 1rem;">
|
|
38
|
+
${this.options.showBPM?`
|
|
39
|
+
<span class="waveform-bpm" style="color: ${this.options.textSecondaryColor}; display: none;">
|
|
40
|
+
<span class="bpm-value">--</span> BPM
|
|
41
|
+
</span>
|
|
42
|
+
`:""}
|
|
43
|
+
${this.options.showPlaybackSpeed?`
|
|
44
|
+
<div class="waveform-speed">
|
|
45
|
+
<button class="speed-btn" aria-label="Playback speed">
|
|
46
|
+
<span class="speed-value">1x</span>
|
|
47
|
+
</button>
|
|
48
|
+
<div class="speed-menu" style="display: none;">
|
|
49
|
+
${this.options.playbackRates.map(e=>`<button class="speed-option" data-rate="${e}">${e}x</button>`).join("")}
|
|
50
|
+
</div>
|
|
38
51
|
</div>
|
|
39
|
-
|
|
52
|
+
`:""}
|
|
53
|
+
${this.options.showTime?`
|
|
54
|
+
<span class="waveform-time" style="color: ${this.options.textSecondaryColor};">
|
|
55
|
+
<span class="time-current">0:00</span> / <span class="time-total">0:00</span>
|
|
56
|
+
</span>
|
|
57
|
+
`:""}
|
|
40
58
|
</div>
|
|
41
59
|
</div>
|
|
42
|
-
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
`,this.playBtn=this.container.querySelector(".waveform-btn"),this.canvas=this.container.querySelector("canvas"),this.ctx=this.canvas.getContext("2d"),this.titleEl=this.container.querySelector(".waveform-title"),this.subtitleEl=this.container.querySelector(".waveform-subtitle"),this.artworkEl=this.container.querySelector(".waveform-artwork"),this.currentTimeEl=this.container.querySelector(".time-current"),this.totalTimeEl=this.container.querySelector(".time-total"),this.bpmEl=this.container.querySelector(".waveform-bpm"),this.bpmValueEl=this.container.querySelector(".bpm-value"),this.loadingEl=this.container.querySelector(".waveform-loading"),this.errorEl=this.container.querySelector(".waveform-error"),this.markersContainer=this.container.querySelector(".waveform-markers"),this.speedBtn=this.container.querySelector(".speed-btn"),this.speedMenu=this.container.querySelector(".speed-menu"),this.resizeCanvas()}createAudio(){this.audio=new Audio,this.audio.preload=this.options.preload||"metadata",this.audio.crossOrigin="anonymous"}initPlaybackSpeed(){this.options.playbackRate&&this.options.playbackRate!==1&&(this.audio.playbackRate=this.options.playbackRate),this.options.showPlaybackSpeed&&this.initSpeedControls()}initSpeedControls(){let e=this.container.querySelector(".speed-btn"),o=this.container.querySelector(".speed-menu");!e||!o||(e.addEventListener("click",a=>{a.stopPropagation(),o.style.display=o.style.display==="none"?"block":"none"}),document.addEventListener("click",()=>{o.style.display="none"}),o.addEventListener("click",a=>{if(a.stopPropagation(),a.target.classList.contains("speed-option")){let i=parseFloat(a.target.dataset.rate);this.setPlaybackRate(i),o.style.display="none"}}),this.updateSpeedUI())}initKeyboardControls(){this.container.setAttribute("tabindex","-1"),this.container.addEventListener("click",()=>{t.getAllInstances().forEach(e=>{e!==this&&e.container.setAttribute("tabindex","-1")}),this.container.setAttribute("tabindex","0"),this.container.focus()}),this.container.addEventListener("keydown",e=>{if(document.activeElement!==this.container)return;let o=e.key,a=this.audio.currentTime;if(o>="0"&&o<="9"){e.preventDefault(),this.seekToPercent(parseInt(o)/10);return}let i={" ":()=>this.togglePlay(),ArrowLeft:()=>this.seekTo(Math.max(0,a-5)),ArrowRight:()=>this.seekTo(Math.min(this.audio.duration,a+5)),ArrowUp:()=>this.setVolume(Math.min(1,this.audio.volume+.1)),ArrowDown:()=>this.setVolume(Math.max(0,this.audio.volume-.1)),m:()=>this.audio.muted=!this.audio.muted,M:()=>this.audio.muted=!this.audio.muted};i[o]&&(e.preventDefault(),i[o]())})}initMediaSession(){!("mediaSession"in navigator)||!this.options.enableMediaSession||(navigator.mediaSession.metadata=new MediaMetadata({title:this.options.title||"Unknown Track",artist:this.options.subtitle||"",album:this.options.album||"",artwork:this.options.artwork?[{src:this.options.artwork,sizes:"512x512",type:"image/jpeg"}]:[]}),navigator.mediaSession.setActionHandler("play",()=>this.play()),navigator.mediaSession.setActionHandler("pause",()=>this.pause()),navigator.mediaSession.setActionHandler("seekbackward",()=>{this.seekTo(Math.max(0,this.audio.currentTime-10))}),navigator.mediaSession.setActionHandler("seekforward",()=>{this.seekTo(Math.min(this.audio.duration,this.audio.currentTime+10))}),navigator.mediaSession.setActionHandler("seekto",e=>{e.seekTime!==null&&this.seekTo(e.seekTime)}))}bindEvents(){this.playBtn.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",x(()=>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((a,i)=>{let n=()=>{this.audio.removeEventListener("loadedmetadata",n),this.audio.removeEventListener("error",r),a()},r=s=>{this.audio.removeEventListener("loadedmetadata",n),this.audio.removeEventListener("error",r),i(s)};this.audio.addEventListener("loadedmetadata",n),this.audio.addEventListener("error",r)});let o=this.options.title||L(e);if(this.titleEl&&(this.titleEl.textContent=o),this.options.waveform)this.setWaveformData(this.options.waveform);else try{let a=await M(e,this.options.samples,this.options.showBPM);this.waveformData=a.peaks,a.bpm&&(this.detectedBPM=a.bpm,this.updateBPMDisplay())}catch(a){console.warn("Using placeholder waveform:",a),this.waveformData=I(this.options.samples)}this.drawWaveform(),this.renderMarkers(),this.initMediaSession(),this.options.onLoad&&this.options.onLoad(this)}catch(o){console.error("Failed to load audio:",o),this.onError(o)}finally{this.setLoading(!1)}}async loadTrack(e,o=null,a=null,i={}){this.isPlaying&&this.pause(),this.audio.src="",this.audio.load(),this.hasError=!1,this.errorEl&&(this.errorEl.style.display="none"),this.canvas&&(this.canvas.style.opacity="1"),this.playBtn&&(this.playBtn.disabled=!1),this.progress=0,this.waveformData=[],this.options=P(this.options,{url:e,title:o||this.options.title,subtitle:a||this.options.subtitle,...i}),i.preload&&(this.audio.preload=i.preload),this.subtitleEl&&(a?(this.subtitleEl.textContent=a,this.subtitleEl.style.display=""):a===""&&(this.subtitleEl.style.display="none")),i.artwork&&this.artworkEl&&(this.artworkEl.src=i.artwork),i.markers&&(this.options.markers=i.markers),await this.load(e),this.play()}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||B(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()}renderMarkers(){!this.options.showMarkers||!this.options.markers?.length||!this.markersContainer||(this.markersContainer.innerHTML="",!(!this.audio||!this.audio.duration||this.audio.duration===0)&&this.options.markers.forEach((e,o)=>{let a=e.time/this.audio.duration*100,i=document.createElement("button");i.className="waveform-marker",i.style.left=`${a}%`,i.style.backgroundColor=e.color||"rgba(255, 255, 255, 0.5)",i.setAttribute("aria-label",e.label),i.setAttribute("data-time",e.time);let n=document.createElement("span");n.className="waveform-marker-tooltip",n.textContent=e.label,i.appendChild(n),i.addEventListener("click",r=>{r.stopPropagation(),this.seekTo(e.time),this.options.playOnSeek&&!this.isPlaying&&this.play()}),this.markersContainer.appendChild(i)}))}handleCanvasClick(e){if(!this.audio.duration)return;let o=this.canvas.getBoundingClientRect(),a=e.clientX-o.left,i=Math.max(0,Math.min(1,a/o.width));this.seekToPercent(i)}setLoading(e){this.isLoading=e,this.loadingEl&&(this.loadingEl.style.display=e?"block":"none")}onMetadataLoaded(){this.totalTimeEl&&(this.totalTimeEl.textContent=C(this.audio.duration)),this.renderMarkers()}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=C(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")}updateSpeedUI(){let e=this.container.querySelector(".speed-value");if(e){let o=this.audio.playbackRate;e.textContent=o===1?"1x":`${o}x`}this.container.querySelectorAll(".speed-option").forEach(o=>{o.classList.toggle("active",parseFloat(o.dataset.rate)===this.audio.playbackRate)})}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()}seekTo(e){this.audio&&this.audio.duration&&(this.audio.currentTime=Math.max(0,Math.min(e,this.audio.duration)),this.updateProgress())}seekToPercent(e){this.audio&&this.audio.duration&&(this.audio.currentTime=this.audio.duration*Math.max(0,Math.min(1,e)),this.updateProgress())}setVolume(e){this.audio&&(this.audio.volume=Math.max(0,Math.min(1,e)))}setPlaybackRate(e){if(!this.audio)return;let o=Math.max(.5,Math.min(2,e));this.audio.playbackRate=o,this.options.playbackRate=o,this.updateSpeedUI()}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 a=document.getElementById(e);if(a)return Array.from(this.instances.values()).find(i=>i.container===a)}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(a){throw console.error("Failed to generate waveform:",a),a}}};function E(){if(typeof document>"u")return;document.querySelectorAll("[data-waveform-player]").forEach(e=>{if(e.dataset.waveformInitialized!=="true")try{new g(e),e.dataset.waveformInitialized="true"}catch(o){console.error("Failed to initialize WaveformPlayer:",o,e)}})}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",E):E());g.init=E;typeof window<"u"&&(window.WaveformPlayer=g);var st=g;export{g as WaveformPlayer,st as default};
|