smooth-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.
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TypedEventEmitter = void 0;
4
+ class TypedEventEmitter {
5
+ constructor() {
6
+ this.listeners = new Map();
7
+ }
8
+ on(event, listener) {
9
+ const eventListeners = this.listeners.get(event) ?? new Set();
10
+ eventListeners.add(listener);
11
+ this.listeners.set(event, eventListeners);
12
+ return () => this.off(event, listener);
13
+ }
14
+ off(event, listener) {
15
+ this.listeners.get(event)?.delete(listener);
16
+ }
17
+ emit(event, payload) {
18
+ this.listeners.get(event)?.forEach((listener) => listener(payload));
19
+ }
20
+ removeAllListeners() {
21
+ this.listeners.clear();
22
+ }
23
+ }
24
+ exports.TypedEventEmitter = TypedEventEmitter;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CanvasWaveformVisualizer = exports.CanvasSpectrumVisualizer = exports.CanvasRadialVisualizer = exports.mountStandardPlayerUI = exports.SmoothPlayer = void 0;
4
+ var SmoothPlayer_js_1 = require("./SmoothPlayer.js");
5
+ Object.defineProperty(exports, "SmoothPlayer", { enumerable: true, get: function () { return SmoothPlayer_js_1.SmoothPlayer; } });
6
+ var ui_js_1 = require("./ui.js");
7
+ Object.defineProperty(exports, "mountStandardPlayerUI", { enumerable: true, get: function () { return ui_js_1.mountStandardPlayerUI; } });
8
+ var visualizers_js_1 = require("./visualizers.js");
9
+ Object.defineProperty(exports, "CanvasRadialVisualizer", { enumerable: true, get: function () { return visualizers_js_1.CanvasRadialVisualizer; } });
10
+ Object.defineProperty(exports, "CanvasSpectrumVisualizer", { enumerable: true, get: function () { return visualizers_js_1.CanvasSpectrumVisualizer; } });
11
+ Object.defineProperty(exports, "CanvasWaveformVisualizer", { enumerable: true, get: function () { return visualizers_js_1.CanvasWaveformVisualizer; } });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist-cjs/ui.js ADDED
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mountStandardPlayerUI = mountStandardPlayerUI;
4
+ const visualizers_js_1 = require("./visualizers.js");
5
+ function requiredElement(scope, selector) {
6
+ const element = scope.querySelector(selector);
7
+ if (!element) {
8
+ throw new Error(`Missing required element: ${selector}`);
9
+ }
10
+ return element;
11
+ }
12
+ function mountStandardPlayerUI(player, root, options = {}) {
13
+ const doc = root.ownerDocument ?? document;
14
+ const debugEnabled = options.debugEnabled ?? player.getDebug();
15
+ const title = requiredElement(root, "#title");
16
+ const artist = requiredElement(root, "#artist");
17
+ const playlistTitle = requiredElement(root, ".smooth-player__top-title");
18
+ const progress = requiredElement(root, "#progress");
19
+ const timeCurrent = requiredElement(root, "#time-current");
20
+ const timeDuration = requiredElement(root, "#time-duration");
21
+ const playButton = requiredElement(root, "#play");
22
+ const playText = requiredElement(root, "#play-text");
23
+ const prevButton = requiredElement(root, "#prev");
24
+ const nextButton = requiredElement(root, "#next");
25
+ const playlistToggle = requiredElement(root, "#playlist-toggle");
26
+ const shuffleToggle = requiredElement(root, "#shuffle-toggle");
27
+ const shuffleText = requiredElement(root, "#shuffle-text");
28
+ const playlistPanel = requiredElement(root, "#playlist-panel");
29
+ const playlistHead = requiredElement(root, ".smooth-player__playlist-head");
30
+ const playlistClose = requiredElement(root, "#playlist-close");
31
+ const playlistList = requiredElement(root, "#playlist-list");
32
+ const radialCanvas = requiredElement(root, "#radial-visualizer");
33
+ const progressRing = requiredElement(root, "#progress-ring");
34
+ const unmounts = [];
35
+ let radial = null;
36
+ const rebuildVisualizer = () => {
37
+ radial?.stop();
38
+ const mode = player.getVisualizer();
39
+ radialCanvas.hidden = mode === "none";
40
+ if (mode === "none") {
41
+ radial = null;
42
+ return;
43
+ }
44
+ radial = new visualizers_js_1.CanvasRadialVisualizer(radialCanvas, player, {
45
+ mode,
46
+ color: player.getAccentColor(),
47
+ background: "transparent",
48
+ });
49
+ radial.start();
50
+ };
51
+ const playlistPanelController = player.mountPlaylistPanel({
52
+ root,
53
+ toggleButton: playlistToggle,
54
+ panel: playlistPanel,
55
+ closeButton: playlistClose,
56
+ });
57
+ unmounts.push(() => playlistPanelController.destroy());
58
+ unmounts.push(player.mountPlaylistTitle(playlistTitle));
59
+ unmounts.push(player.mountShuffleToggle({
60
+ button: shuffleToggle,
61
+ labelElement: shuffleText,
62
+ initialEnabled: false,
63
+ }));
64
+ unmounts.push(player.mountTransportControls({
65
+ previousButton: prevButton,
66
+ nextButton: nextButton,
67
+ }));
68
+ if (debugEnabled) {
69
+ const debugPanel = requiredElement(doc, "#debug-panel");
70
+ const dbgSrc = requiredElement(doc, "#dbg-src");
71
+ const dbgCurrentTime = requiredElement(doc, "#dbg-current-time");
72
+ const dbgDuration = requiredElement(doc, "#dbg-duration");
73
+ const dbgReadyState = requiredElement(doc, "#dbg-ready-state");
74
+ const dbgNetworkState = requiredElement(doc, "#dbg-network-state");
75
+ const dbgPaused = requiredElement(doc, "#dbg-paused");
76
+ const dbgEvents = requiredElement(doc, "#dbg-events");
77
+ unmounts.push(player.mountDebugPanel({
78
+ enabled: true,
79
+ panel: debugPanel,
80
+ sourceElement: dbgSrc,
81
+ currentTimeElement: dbgCurrentTime,
82
+ durationElement: dbgDuration,
83
+ readyStateElement: dbgReadyState,
84
+ networkStateElement: dbgNetworkState,
85
+ pausedElement: dbgPaused,
86
+ eventsElement: dbgEvents,
87
+ }));
88
+ }
89
+ player.applyAccentColor(root);
90
+ unmounts.push(player.mountTrackInfo(title, artist));
91
+ unmounts.push(player.mountPlayButton(playButton, {
92
+ labelElement: playText,
93
+ playLabel: "Riproduci",
94
+ pauseLabel: "Pausa",
95
+ }));
96
+ unmounts.push(player.mountProgress({
97
+ range: progress,
98
+ currentTimeElement: timeCurrent,
99
+ durationElement: timeDuration,
100
+ progressRoot: root,
101
+ ringElement: progressRing,
102
+ }));
103
+ unmounts.push(player.mountPlaylist(playlistList, {
104
+ onSelect: () => playlistPanelController.setOpen(false),
105
+ }));
106
+ const switcher = doc.createElement("div");
107
+ switcher.className = "smooth-player__playlist-switcher";
108
+ playlistHead.insertAdjacentElement("afterend", switcher);
109
+ unmounts.push(player.mountPlaylistSwitcher(switcher));
110
+ rebuildVisualizer();
111
+ return {
112
+ rebuildVisualizer,
113
+ destroy: () => {
114
+ radial?.stop();
115
+ radial = null;
116
+ for (const unmount of unmounts) {
117
+ unmount();
118
+ }
119
+ switcher.remove();
120
+ },
121
+ };
122
+ }
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CanvasRadialVisualizer = exports.CanvasWaveformVisualizer = exports.CanvasSpectrumVisualizer = void 0;
4
+ class CanvasVisualizer {
5
+ constructor(canvas, player) {
6
+ this.canvas = canvas;
7
+ this.player = player;
8
+ this.frameId = 0;
9
+ }
10
+ start() {
11
+ this.stop();
12
+ this.draw();
13
+ }
14
+ stop() {
15
+ cancelAnimationFrame(this.frameId);
16
+ }
17
+ }
18
+ class CanvasSpectrumVisualizer extends CanvasVisualizer {
19
+ constructor(canvas, player, options = {}) {
20
+ super(canvas, player);
21
+ this.options = {
22
+ width: options.width ?? canvas.width ?? 640,
23
+ height: options.height ?? canvas.height ?? 160,
24
+ background: options.background ?? "#0b1220",
25
+ color: options.color ?? "#2db6c8",
26
+ barGap: options.barGap ?? 1,
27
+ barWidth: options.barWidth ?? 3,
28
+ };
29
+ this.canvas.width = this.options.width;
30
+ this.canvas.height = this.options.height;
31
+ }
32
+ draw() {
33
+ const ctx = this.canvas.getContext("2d");
34
+ if (!ctx)
35
+ return;
36
+ const data = this.player.getSpectrumData();
37
+ const { width, height, background, color, barGap, barWidth } = this.options;
38
+ ctx.fillStyle = background;
39
+ ctx.fillRect(0, 0, width, height);
40
+ let x = 0;
41
+ for (let i = 0; i < data.length && x < width; i += 1) {
42
+ const value = (data[i] ?? 0) / 255;
43
+ const barHeight = Math.max(2, value * height);
44
+ ctx.fillStyle = color;
45
+ ctx.fillRect(x, height - barHeight, barWidth, barHeight);
46
+ x += barWidth + barGap;
47
+ }
48
+ this.frameId = requestAnimationFrame(() => this.draw());
49
+ }
50
+ }
51
+ exports.CanvasSpectrumVisualizer = CanvasSpectrumVisualizer;
52
+ class CanvasWaveformVisualizer extends CanvasVisualizer {
53
+ constructor(canvas, player, options = {}) {
54
+ super(canvas, player);
55
+ this.options = {
56
+ width: options.width ?? canvas.width ?? 640,
57
+ height: options.height ?? canvas.height ?? 120,
58
+ background: options.background ?? "#0b1220",
59
+ color: options.color ?? "#f3f5f9",
60
+ lineWidth: options.lineWidth ?? 2,
61
+ };
62
+ this.canvas.width = this.options.width;
63
+ this.canvas.height = this.options.height;
64
+ }
65
+ draw() {
66
+ const ctx = this.canvas.getContext("2d");
67
+ if (!ctx)
68
+ return;
69
+ const data = this.player.getWaveformData();
70
+ const { width, height, background, color, lineWidth } = this.options;
71
+ ctx.fillStyle = background;
72
+ ctx.fillRect(0, 0, width, height);
73
+ ctx.lineWidth = lineWidth;
74
+ ctx.strokeStyle = color;
75
+ ctx.beginPath();
76
+ const sliceWidth = width / data.length;
77
+ let x = 0;
78
+ for (let i = 0; i < data.length; i += 1) {
79
+ const normalized = (data[i] ?? 0) / 128;
80
+ const y = (normalized * height) / 2;
81
+ if (i === 0) {
82
+ ctx.moveTo(x, y);
83
+ }
84
+ else {
85
+ ctx.lineTo(x, y);
86
+ }
87
+ x += sliceWidth;
88
+ }
89
+ ctx.lineTo(width, height / 2);
90
+ ctx.stroke();
91
+ this.frameId = requestAnimationFrame(() => this.draw());
92
+ }
93
+ }
94
+ exports.CanvasWaveformVisualizer = CanvasWaveformVisualizer;
95
+ class CanvasRadialVisualizer extends CanvasVisualizer {
96
+ constructor(canvas, player, options = {}) {
97
+ super(canvas, player);
98
+ this.options = {
99
+ width: options.width ?? canvas.width ?? 220,
100
+ height: options.height ?? canvas.height ?? 220,
101
+ background: options.background ?? "transparent",
102
+ color: options.color ?? "#2db6c8",
103
+ mode: options.mode ?? "spectrum",
104
+ innerRadiusRatio: options.innerRadiusRatio ?? 0.36,
105
+ outerRadiusRatio: options.outerRadiusRatio ?? 0.96,
106
+ lineWidth: options.lineWidth ?? 1.4,
107
+ waveformAmplitude: options.waveformAmplitude ?? 0.52,
108
+ };
109
+ this.canvas.width = this.options.width;
110
+ this.canvas.height = this.options.height;
111
+ }
112
+ draw() {
113
+ const ctx = this.canvas.getContext("2d");
114
+ if (!ctx)
115
+ return;
116
+ const { width, height, background, color, mode, innerRadiusRatio, outerRadiusRatio, lineWidth, waveformAmplitude } = this.options;
117
+ const centerX = width / 2;
118
+ const centerY = height / 2;
119
+ const maxRadius = Math.min(width, height) / 2;
120
+ const innerRadius = maxRadius * innerRadiusRatio;
121
+ const outerRadius = maxRadius * outerRadiusRatio;
122
+ const radialRange = Math.max(2, outerRadius - innerRadius);
123
+ if (background === "transparent") {
124
+ ctx.clearRect(0, 0, width, height);
125
+ }
126
+ else {
127
+ ctx.fillStyle = background;
128
+ ctx.fillRect(0, 0, width, height);
129
+ }
130
+ if (mode === "spectrum") {
131
+ const data = this.player.getSpectrumData();
132
+ const sampleCount = Math.min(160, data.length);
133
+ const step = (Math.PI * 2) / sampleCount;
134
+ ctx.lineWidth = lineWidth;
135
+ ctx.strokeStyle = color;
136
+ ctx.shadowColor = color;
137
+ ctx.shadowBlur = 8;
138
+ for (let i = 0; i < sampleCount; i += 1) {
139
+ const value = (data[i] ?? 0) / 255;
140
+ const amplitude = Math.max(0.04, value);
141
+ const angle = i * step - Math.PI / 2;
142
+ const x0 = centerX + Math.cos(angle) * innerRadius;
143
+ const y0 = centerY + Math.sin(angle) * innerRadius;
144
+ const x1 = centerX + Math.cos(angle) * (innerRadius + amplitude * radialRange);
145
+ const y1 = centerY + Math.sin(angle) * (innerRadius + amplitude * radialRange);
146
+ ctx.globalAlpha = 0.25 + amplitude * 0.75;
147
+ ctx.beginPath();
148
+ ctx.moveTo(x0, y0);
149
+ ctx.lineTo(x1, y1);
150
+ ctx.stroke();
151
+ }
152
+ ctx.shadowBlur = 0;
153
+ ctx.globalAlpha = 1;
154
+ this.frameId = requestAnimationFrame(() => this.draw());
155
+ return;
156
+ }
157
+ const data = this.player.getWaveformData();
158
+ const sampleCount = Math.min(220, data.length);
159
+ const step = (Math.PI * 2) / sampleCount;
160
+ const amplitudeRange = radialRange * waveformAmplitude;
161
+ const baseRadius = innerRadius + radialRange * 0.5;
162
+ ctx.lineWidth = lineWidth;
163
+ ctx.strokeStyle = color;
164
+ ctx.shadowColor = color;
165
+ ctx.shadowBlur = 10;
166
+ ctx.globalAlpha = 0.95;
167
+ ctx.beginPath();
168
+ for (let i = 0; i < sampleCount; i += 1) {
169
+ const normalized = ((data[i] ?? 128) - 128) / 128;
170
+ const r = baseRadius + normalized * amplitudeRange;
171
+ const angle = i * step - Math.PI / 2;
172
+ const x = centerX + Math.cos(angle) * r;
173
+ const y = centerY + Math.sin(angle) * r;
174
+ if (i === 0) {
175
+ ctx.moveTo(x, y);
176
+ }
177
+ else {
178
+ ctx.lineTo(x, y);
179
+ }
180
+ }
181
+ ctx.closePath();
182
+ ctx.stroke();
183
+ ctx.shadowBlur = 0;
184
+ ctx.globalAlpha = 1;
185
+ this.frameId = requestAnimationFrame(() => this.draw());
186
+ }
187
+ }
188
+ exports.CanvasRadialVisualizer = CanvasRadialVisualizer;
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "smooth-player",
3
+ "version": "1.0.0",
4
+ "description": "Typed audio player with spectrum and waveform analyzers",
5
+ "type": "module",
6
+ "workspaces": [
7
+ "packages/*"
8
+ ],
9
+ "main": "./dist-cjs/index.js",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist-cjs/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "dist-cjs",
22
+ "styles",
23
+ "assets"
24
+ ],
25
+ "scripts": {
26
+ "clean": "rm -rf dist dist-cjs",
27
+ "build:esm": "tsc -p tsconfig.build.esm.json",
28
+ "build:cjs": "tsc -p tsconfig.build.cjs.json",
29
+ "build:types": "tsc -p tsconfig.build.json",
30
+ "build:css": "sass --no-source-map styles/index.scss dist/smooth-player.css",
31
+ "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:types && npm run build:css",
32
+ "dev": "tsc -p tsconfig.build.esm.json --watch",
33
+ "typecheck": "tsc --noEmit",
34
+ "demo": "npm run build && node scripts/demo-server.mjs",
35
+ "workspaces:build": "npm run -ws --if-present build",
36
+ "workspaces:typecheck": "npm run -ws --if-present typecheck"
37
+ },
38
+ "keywords": [
39
+ "audio-player",
40
+ "typescript",
41
+ "spectrum-analyzer",
42
+ "waveform",
43
+ "web-audio"
44
+ ],
45
+ "license": "MIT",
46
+ "devDependencies": {
47
+ "sass": "^1.93.2",
48
+ "typescript": "^5.7.3"
49
+ }
50
+ }