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.
- package/README.md +211 -0
- package/assets/icons/menu.svg +5 -0
- package/assets/icons/next.svg +4 -0
- package/assets/icons/note.svg +3 -0
- package/assets/icons/pause.svg +4 -0
- package/assets/icons/play.svg +3 -0
- package/assets/icons/playlist.svg +7 -0
- package/assets/icons/prev.svg +4 -0
- package/assets/icons/shuffle.svg +7 -0
- package/dist/SmoothPlayer.d.ts +91 -0
- package/dist/SmoothPlayer.js +930 -0
- package/dist/events.d.ts +7 -0
- package/dist/events.js +20 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/smooth-player.css +675 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.js +1 -0
- package/dist/ui.d.ts +3 -0
- package/dist/ui.js +119 -0
- package/dist/visualizers.d.ts +46 -0
- package/dist/visualizers.js +182 -0
- package/dist-cjs/SmoothPlayer.js +934 -0
- package/dist-cjs/events.js +24 -0
- package/dist-cjs/index.js +11 -0
- package/dist-cjs/types.js +2 -0
- package/dist-cjs/ui.js +122 -0
- package/dist-cjs/visualizers.js +188 -0
- package/package.json +50 -0
- package/styles/common/_base.scss +487 -0
- package/styles/index.scss +2 -0
- package/styles/themes/_aurora.scss +70 -0
- package/styles/themes/_nocturne.scss +259 -0
- package/styles/themes/_ocean.scss +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Smooth Player
|
|
2
|
+
|
|
3
|
+
Smooth Player is a TypeScript audio player for the web with built-in playlist handling, visualizers, accent-based styling, and reusable UI mount helpers.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Single track and playlist playback
|
|
8
|
+
- Nested playlists (`AudioPlaylist` inside `PlaylistEntry`)
|
|
9
|
+
- Automatic playlist behavior:
|
|
10
|
+
- If there is only one track, playlist controls stay hidden
|
|
11
|
+
- If there are multiple tracks, next/previous + playlist panel are available
|
|
12
|
+
- Visualizer modes: `spectrum`, `waveform`, `none`
|
|
13
|
+
- Circular draggable progress ring support
|
|
14
|
+
- Configurable `accentColor` through player config
|
|
15
|
+
- Built-in debug panel support
|
|
16
|
+
- Typed API (`.d.ts`) with ESM + CJS builds
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install smooth-player
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { SmoothPlayer, mountStandardPlayerUI } from "smooth-player";
|
|
30
|
+
import "smooth-player/dist/smooth-player.css";
|
|
31
|
+
|
|
32
|
+
const tracks = [
|
|
33
|
+
{
|
|
34
|
+
id: "song-1",
|
|
35
|
+
src: "/audio/song-1.mp3",
|
|
36
|
+
metadata: { title: "Song 1", artist: "Artist 1" },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "song-2",
|
|
40
|
+
src: "/audio/song-2.mp3",
|
|
41
|
+
metadata: { title: "Song 2", artist: "Artist 2" },
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const player = new SmoothPlayer({
|
|
46
|
+
playlist: tracks,
|
|
47
|
+
initialVolume: 0.8,
|
|
48
|
+
visualizer: "spectrum",
|
|
49
|
+
accentColor: "#0ed2a4",
|
|
50
|
+
debug: false,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const root = document.querySelector("#player-root");
|
|
54
|
+
if (!(root instanceof HTMLElement)) throw new Error("Missing #player-root");
|
|
55
|
+
|
|
56
|
+
mountStandardPlayerUI(player, root);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Core Config (`SmoothPlayerOptions`)
|
|
60
|
+
|
|
61
|
+
- `playlist?: PlaylistEntry[]`
|
|
62
|
+
- `visualizer?: "spectrum" | "waveform" | "none"` (default: `"spectrum"`)
|
|
63
|
+
- `accentColor?: string` (default: `#0ed2a4`)
|
|
64
|
+
- `debug?: boolean` (default: `false`)
|
|
65
|
+
- `initialVolume?: number`
|
|
66
|
+
- `initialTrackIndex?: number`
|
|
67
|
+
- `initialShuffle?: boolean`
|
|
68
|
+
- `autoplay?: boolean`
|
|
69
|
+
- `loop?: boolean`
|
|
70
|
+
- `durationFallback?: boolean` (default: `true`, fallback decode for unknown metadata duration)
|
|
71
|
+
- `analyzer?: { fftSize, smoothingTimeConstant, minDecibels, maxDecibels }`
|
|
72
|
+
|
|
73
|
+
## Visualizer
|
|
74
|
+
|
|
75
|
+
At runtime:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
player.setVisualizer("waveform");
|
|
79
|
+
const current = player.getVisualizer(); // "waveform"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Raw data API:
|
|
83
|
+
|
|
84
|
+
- `getSpectrumData()`
|
|
85
|
+
- `getWaveformData()`
|
|
86
|
+
|
|
87
|
+
Exported visualizer classes:
|
|
88
|
+
|
|
89
|
+
- `CanvasRadialVisualizer`
|
|
90
|
+
- `CanvasSpectrumVisualizer`
|
|
91
|
+
- `CanvasWaveformVisualizer`
|
|
92
|
+
|
|
93
|
+
## Debug
|
|
94
|
+
|
|
95
|
+
Enable debug directly in config:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const player = new SmoothPlayer({
|
|
99
|
+
playlist: tracks,
|
|
100
|
+
debug: true,
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use `mountDebugPanel(...)` to bind debug metrics to your elements, or call `mountStandardPlayerUI(player, root, { debugEnabled: true })` when your DOM includes the debug panel nodes.
|
|
105
|
+
|
|
106
|
+
Runtime methods:
|
|
107
|
+
|
|
108
|
+
- `setDebug(enabled: boolean)`
|
|
109
|
+
- `getDebug()`
|
|
110
|
+
|
|
111
|
+
## Playlists (including nested)
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const playlist = [
|
|
115
|
+
{
|
|
116
|
+
id: "focus",
|
|
117
|
+
title: "Focus",
|
|
118
|
+
tracks: [
|
|
119
|
+
{ id: "f-1", src: "/audio/focus-1.mp3", metadata: { title: "Focus 1" } },
|
|
120
|
+
{
|
|
121
|
+
id: "focus-deep",
|
|
122
|
+
title: "Focus Deep",
|
|
123
|
+
tracks: [{ id: "fd-1", src: "/audio/focus-deep-1.mp3", metadata: { title: "Deep 1" } }],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "chill",
|
|
129
|
+
title: "Chill",
|
|
130
|
+
tracks: [{ id: "c-1", src: "/audio/chill-1.mp3", metadata: { title: "Chill 1" } }],
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const player = new SmoothPlayer({ playlist });
|
|
135
|
+
player.selectPlaylist("chill");
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Playlist API highlights:
|
|
139
|
+
|
|
140
|
+
- `setPlaylist(entries, startIndex?)`
|
|
141
|
+
- `getPlaylists()`
|
|
142
|
+
- `getCurrentPlaylist()`
|
|
143
|
+
- `selectPlaylist(playlistId, startIndex?)`
|
|
144
|
+
|
|
145
|
+
## Events
|
|
146
|
+
|
|
147
|
+
Subscribe with `player.on(eventName, handler)`:
|
|
148
|
+
|
|
149
|
+
- `ready`
|
|
150
|
+
- `play`
|
|
151
|
+
- `pause`
|
|
152
|
+
- `ended`
|
|
153
|
+
- `playlistchange`
|
|
154
|
+
- `trackchange`
|
|
155
|
+
- `durationchange`
|
|
156
|
+
- `timeupdate`
|
|
157
|
+
- `volumechange`
|
|
158
|
+
- `error`
|
|
159
|
+
|
|
160
|
+
## UI Mount Helpers
|
|
161
|
+
|
|
162
|
+
- `mountStandardPlayerUI(player, root, options?)`
|
|
163
|
+
- `mountTrackInfo(titleElement, artistElement, options?)`
|
|
164
|
+
- `mountPlayButton(buttonElement, options?)`
|
|
165
|
+
- `mountProgress(options)`
|
|
166
|
+
- `mountTransportControls(options)`
|
|
167
|
+
- `mountShuffleToggle(options)`
|
|
168
|
+
- `mountPlaylistPanel(options)`
|
|
169
|
+
- `mountPlaylistSwitcher(container, options?)`
|
|
170
|
+
- `mountPlaylist(container, options?)`
|
|
171
|
+
- `mountPlaylistTitle(element, options?)`
|
|
172
|
+
- `mountDebugPanel(options)`
|
|
173
|
+
|
|
174
|
+
## Utility Methods
|
|
175
|
+
|
|
176
|
+
- `setAccentColor(color)`
|
|
177
|
+
- `getAccentColor()`
|
|
178
|
+
- `applyAccentColor(targetElement)`
|
|
179
|
+
- `formatTime(seconds)`
|
|
180
|
+
- `getState()`
|
|
181
|
+
- `getAudioElement()`
|
|
182
|
+
|
|
183
|
+
## Scripts
|
|
184
|
+
|
|
185
|
+
- `npm run dev`
|
|
186
|
+
- `npm run build`
|
|
187
|
+
- `npm run build:css`
|
|
188
|
+
- `npm run typecheck`
|
|
189
|
+
- `npm run demo`
|
|
190
|
+
|
|
191
|
+
## Local Demo
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npm install
|
|
195
|
+
npm run demo
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Open:
|
|
199
|
+
|
|
200
|
+
- `http://127.0.0.1:4173/examples/demo.html`
|
|
201
|
+
|
|
202
|
+
## Media Attribution and CORS
|
|
203
|
+
|
|
204
|
+
Audio tracks included in this repository are provided for demonstration purposes only.
|
|
205
|
+
|
|
206
|
+
- Part of the demo media is sourced from [Pixabay](https://pixabay.com/).
|
|
207
|
+
- Additional demo files are SoundHelix songs available in `examples/audio`.
|
|
208
|
+
|
|
209
|
+
If you load audio from external hosts, those sources must be CORS-enabled for browser playback and analysis features.
|
|
210
|
+
The media server should return a valid `Access-Control-Allow-Origin` header for your application origin (or `*` when appropriate).
|
|
211
|
+
Without proper CORS headers, browsers may block playback and prevent analyzer/visualizer processing.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
|
2
|
+
<path d="M5 7.5H19" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/>
|
|
3
|
+
<path d="M5 12H19" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/>
|
|
4
|
+
<path d="M5 16.5H19" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
|
2
|
+
<path d="M17.5 6V18" stroke="currentColor" stroke-width="2.1" stroke-linecap="round"/>
|
|
3
|
+
<path d="M7.3 6.7C6.74 6.34 6 6.74 6 7.41V16.59C6 17.26 6.74 17.66 7.3 17.3L13.85 13.09C14.35 12.77 14.35 11.23 13.85 10.91L7.3 6.7Z" fill="currentColor"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
|
2
|
+
<path d="M14 4.8V14.2C13.3 13.8 12.4 13.6 11.4 13.6C9.1 13.6 7.2 14.8 7.2 16.5C7.2 18.2 9.1 19.4 11.4 19.4C13.7 19.4 15.6 18.2 15.6 16.5V8.6L20 7.4V13.2C19.3 12.8 18.4 12.6 17.4 12.6C15.1 12.6 13.2 13.8 13.2 15.5C13.2 17.2 15.1 18.4 17.4 18.4C19.7 18.4 21.6 17.2 21.6 15.5V4L14 4.8Z" fill="currentColor"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
|
2
|
+
<path d="M5 8H14" stroke="currentColor" stroke-width="2.1" stroke-linecap="round"/>
|
|
3
|
+
<path d="M5 12H14" stroke="currentColor" stroke-width="2.1" stroke-linecap="round"/>
|
|
4
|
+
<path d="M5 16H10.5" stroke="currentColor" stroke-width="2.1" stroke-linecap="round"/>
|
|
5
|
+
<circle cx="17.8" cy="15.8" r="2.2" stroke="currentColor" stroke-width="1.9"/>
|
|
6
|
+
<path d="M20 15.8V8.2L16.2 9" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
|
2
|
+
<path d="M6.5 6V18" stroke="currentColor" stroke-width="2.1" stroke-linecap="round"/>
|
|
3
|
+
<path d="M16.7 6.7C17.26 6.34 18 6.74 18 7.41V16.59C18 17.26 17.26 17.66 16.7 17.3L10.15 13.09C9.65 12.77 9.65 11.23 10.15 10.91L16.7 6.7Z" fill="currentColor"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true">
|
|
2
|
+
<path d="M16 5H20V9" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3
|
+
<path d="M4 7H9L15 17H20" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
<path d="M20 15V19H16" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
|
+
<path d="M4 17H9L10.5 14.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
6
|
+
<path d="M13.5 9.5L15 7H20" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type AnalyzerOptions, type AudioTrack, type DebugPanelMountOptions, type PlaylistEntry, type PlaylistMountOptions, type PlaylistPanelController, type PlaylistPanelMountOptions, type PlaylistSwitcherMountOptions, type PlaylistTitleMountOptions, type PlayButtonMountOptions, type ProgressMountOptions, type PlaybackState, type PlayerEvents, type SmoothPlayerOptions, type ShuffleToggleMountOptions, type TrackInfoMountOptions, type TransportControlsMountOptions, type VisualizerMode } from "./types.js";
|
|
2
|
+
export declare class SmoothPlayer {
|
|
3
|
+
private readonly audio;
|
|
4
|
+
private readonly context;
|
|
5
|
+
private readonly sourceNode;
|
|
6
|
+
private readonly analyser;
|
|
7
|
+
private readonly events;
|
|
8
|
+
private playlists;
|
|
9
|
+
private activePlaylistId;
|
|
10
|
+
private currentTrackIndex;
|
|
11
|
+
private visualizerMode;
|
|
12
|
+
private accentColor;
|
|
13
|
+
private shuffleEnabled;
|
|
14
|
+
private debugEnabled;
|
|
15
|
+
private durationFallbackEnabled;
|
|
16
|
+
private resolvedDuration;
|
|
17
|
+
private readonly durationFallbackCache;
|
|
18
|
+
private resolvingDurationSrc;
|
|
19
|
+
constructor(options?: SmoothPlayerOptions);
|
|
20
|
+
on: <TKey extends keyof PlayerEvents>(event: TKey, listener: (payload: PlayerEvents[TKey]) => void) => () => void;
|
|
21
|
+
off: <TKey extends keyof PlayerEvents>(event: TKey, listener: (payload: PlayerEvents[TKey]) => void) => void;
|
|
22
|
+
destroy(): void;
|
|
23
|
+
setAccentColor(color: string): void;
|
|
24
|
+
getAccentColor(): string;
|
|
25
|
+
applyAccentColor(target: HTMLElement): void;
|
|
26
|
+
setShuffle(enabled: boolean): void;
|
|
27
|
+
getShuffle(): boolean;
|
|
28
|
+
setDebug(enabled: boolean): void;
|
|
29
|
+
getDebug(): boolean;
|
|
30
|
+
setPlaylist(entries: PlaylistEntry[], startIndex?: number): void;
|
|
31
|
+
selectPlaylist(playlistId: string, startIndex?: number): void;
|
|
32
|
+
getPlaylists(): Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
count: number;
|
|
36
|
+
}>;
|
|
37
|
+
getCurrentPlaylist(): {
|
|
38
|
+
id: string;
|
|
39
|
+
title: string;
|
|
40
|
+
tracks: AudioTrack[];
|
|
41
|
+
} | null;
|
|
42
|
+
getPlaylist(): AudioTrack[];
|
|
43
|
+
getCurrentTrack(): AudioTrack | null;
|
|
44
|
+
getCurrentTrackIndex(): number;
|
|
45
|
+
formatTime(value: number): string;
|
|
46
|
+
mountPlaylist(container: HTMLElement, options?: PlaylistMountOptions): () => void;
|
|
47
|
+
mountPlaylistSwitcher(container: HTMLElement, options?: PlaylistSwitcherMountOptions): () => void;
|
|
48
|
+
mountPlaylistTitle(element: HTMLElement, options?: PlaylistTitleMountOptions): () => void;
|
|
49
|
+
mountTrackInfo(titleElement: HTMLElement, artistElement: HTMLElement, options?: TrackInfoMountOptions): () => void;
|
|
50
|
+
mountPlayButton(button: HTMLButtonElement, options?: PlayButtonMountOptions): () => void;
|
|
51
|
+
mountTransportControls(options: TransportControlsMountOptions): () => void;
|
|
52
|
+
mountShuffleToggle(options: ShuffleToggleMountOptions): () => void;
|
|
53
|
+
mountPlaylistPanel(options: PlaylistPanelMountOptions): PlaylistPanelController;
|
|
54
|
+
mountDebugPanel(options: DebugPanelMountOptions): () => void;
|
|
55
|
+
mountProgress(options: ProgressMountOptions): () => void;
|
|
56
|
+
play(index?: number): Promise<void>;
|
|
57
|
+
pause(): void;
|
|
58
|
+
toggle(): Promise<void>;
|
|
59
|
+
next(): void;
|
|
60
|
+
previous(): void;
|
|
61
|
+
setLoop(loop: boolean): void;
|
|
62
|
+
setVolume(volume: number): void;
|
|
63
|
+
seek(seconds: number): void;
|
|
64
|
+
loadTrack(track: AudioTrack): void;
|
|
65
|
+
getState(): PlaybackState;
|
|
66
|
+
getAudioElement(): HTMLAudioElement;
|
|
67
|
+
getCurrentTime(): number;
|
|
68
|
+
getDuration(): number;
|
|
69
|
+
getSpectrumData(): Uint8Array;
|
|
70
|
+
getWaveformData(): Uint8Array;
|
|
71
|
+
setVisualizer(mode: VisualizerMode): void;
|
|
72
|
+
getVisualizer(): VisualizerMode;
|
|
73
|
+
configureAnalyzer(options?: AnalyzerOptions): void;
|
|
74
|
+
private getActivePlaylist;
|
|
75
|
+
private getActiveTracks;
|
|
76
|
+
private loadTrackByIndex;
|
|
77
|
+
private emitPlaylistChange;
|
|
78
|
+
private bindAudioEvents;
|
|
79
|
+
private clamp;
|
|
80
|
+
private pickRandomTrackIndex;
|
|
81
|
+
private emitTimeUpdate;
|
|
82
|
+
private emitDurationChange;
|
|
83
|
+
private syncDurationFromAudio;
|
|
84
|
+
private resolveDurationFallback;
|
|
85
|
+
private resolvePlaylists;
|
|
86
|
+
private collectDirectTracks;
|
|
87
|
+
private flattenTracks;
|
|
88
|
+
private collectNestedPlaylists;
|
|
89
|
+
private dedupePlaylistIds;
|
|
90
|
+
private isAudioPlaylist;
|
|
91
|
+
}
|