@scarlett-player/playlist 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -0
- package/dist/index.cjs +5 -4
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -4
- package/package.json +7 -7
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# @scarlett-player/playlist
|
|
2
|
+
|
|
3
|
+
Playlist plugin for [Scarlett Player](https://scarlettplayer.com). Queue management (add, insert, remove, move, clear), shuffle with a Fisher-Yates order, repeat modes, auto-advance when a track ends, optional localStorage persistence, and control-bar buttons for previous, next and a queue panel when `@scarlett-player/ui` is installed.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @scarlett-player/playlist @scarlett-player/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`@scarlett-player/core` is a required peer dependency. `@scarlett-player/ui` is an optional peer: the control-bar controls register only when it is present, and everything else works without it.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
This matches the audio example in the root README.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createPlayer } from '@scarlett-player/core';
|
|
19
|
+
import { createNativePlugin } from '@scarlett-player/native';
|
|
20
|
+
import { createAudioUIPlugin } from '@scarlett-player/audio-ui';
|
|
21
|
+
import { createPlaylistPlugin, type IPlaylistPlugin } from '@scarlett-player/playlist';
|
|
22
|
+
import { createMediaSessionPlugin } from '@scarlett-player/media-session';
|
|
23
|
+
|
|
24
|
+
const player = await createPlayer({
|
|
25
|
+
container: document.getElementById('audio-player'),
|
|
26
|
+
plugins: [
|
|
27
|
+
createNativePlugin(),
|
|
28
|
+
createAudioUIPlugin({ layout: 'full' }),
|
|
29
|
+
createPlaylistPlugin({
|
|
30
|
+
tracks: [
|
|
31
|
+
{ id: '1', src: '/track1.mp3', title: 'Track 1', artist: 'Artist', artwork: '/art1.jpg' },
|
|
32
|
+
{ id: '2', src: '/track2.mp3', title: 'Track 2', artist: 'Artist', artwork: '/art2.jpg' },
|
|
33
|
+
],
|
|
34
|
+
}),
|
|
35
|
+
createMediaSessionPlugin(),
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Start the first track through the playlist, not through `src` or
|
|
40
|
+
// player.load(). The playlist owns the current index: play() sets it, writes
|
|
41
|
+
// the track's title and artwork into state, and emits `media:load-request`,
|
|
42
|
+
// which the player loads and plays. Loading a source behind the playlist's
|
|
43
|
+
// back leaves its index pointing at nothing, so next/previous and
|
|
44
|
+
// auto-advance start from the wrong place.
|
|
45
|
+
const playlist = player.getPlugin<IPlaylistPlugin>('playlist');
|
|
46
|
+
playlist?.play(0);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If you do pass `src` to `createPlayer()` for the first track, set `initialIndex: 0` so the playlist knows which track is already loaded.
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
| Option | Type | Default | Description |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| `tracks` | `PlaylistTrack[]` | `[]` | Initial queue. Tracks without an `id` get a generated one |
|
|
56
|
+
| `autoAdvance` | `boolean` | `true` | Select the next track on `playback:ended` |
|
|
57
|
+
| `advanceDelay` | `number` | `0` | Milliseconds to wait before auto-advancing |
|
|
58
|
+
| `autoLoad` | `boolean` | `true` | Emit `media:load-request` with `autoplay: true` whenever the current track changes, so the player loads it. Set to `false` to load from `playlist:change` yourself |
|
|
59
|
+
| `shuffle` | `boolean` | `false` | Initial shuffle state |
|
|
60
|
+
| `repeat` | `'none' \| 'one' \| 'all'` | `'none'` | Initial repeat mode |
|
|
61
|
+
| `initialIndex` | `number` | `-1` | Index of the track already loaded by the player. `-1` means no track is active. Out-of-range values fall back to `-1` |
|
|
62
|
+
| `persist` | `boolean` | `false` | Save tracks, index, shuffle and repeat to localStorage and restore them on init |
|
|
63
|
+
| `persistKey` | `string` | `'scarlett-playlist'` | localStorage key used by `persist` |
|
|
64
|
+
| `preloadNext` | `boolean` | `true` | Accepted and stored, but no code path currently reads it |
|
|
65
|
+
|
|
66
|
+
A `PlaylistTrack` is `{ id, src, title?, artist?, album?, artwork?, duration?, type?, mimeType?, metadata? }` plus any extra properties you want to carry. `type` defaults to `'audio'` in player state when omitted.
|
|
67
|
+
|
|
68
|
+
## Plugin API
|
|
69
|
+
|
|
70
|
+
Registered under the id `playlist`; retrieve it with `player.getPlugin<IPlaylistPlugin>('playlist')`.
|
|
71
|
+
|
|
72
|
+
| Method | Description |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `add(track \| track[])` | Append to the queue |
|
|
75
|
+
| `insert(index, track)` | Insert at a position (clamped to the queue length) |
|
|
76
|
+
| `remove(idOrIndex)` | Remove by id or index. Removing the current track selects the track that slides into its slot |
|
|
77
|
+
| `move(fromIndex, toIndex)` | Reorder |
|
|
78
|
+
| `clear()` | Empty the queue and reset the index to `-1` |
|
|
79
|
+
| `play(idOrIndex?)` | Select a track. With no argument, resumes the current track or starts the first |
|
|
80
|
+
| `next()` / `previous()` | Step through the queue honouring shuffle and repeat. `previous()` restarts the current track when more than 3 seconds in |
|
|
81
|
+
| `toggleShuffle()` / `setShuffle(enabled)` | Shuffle control. The current track stays first in the new order |
|
|
82
|
+
| `cycleRepeat()` / `setRepeat(mode)` | Repeat control. `cycleRepeat()` goes none, all, one |
|
|
83
|
+
| `getState()` | `{ tracks, currentIndex, currentTrack, shuffle, repeat, shuffleOrder, hasNext, hasPrevious }` |
|
|
84
|
+
| `getTracks()` / `getCurrentTrack()` / `getTrack(id)` | Read the queue |
|
|
85
|
+
|
|
86
|
+
With the player focused, the `N` and `P` keys call `next()` and `previous()`. Keystrokes inside inputs or with a modifier held are left alone.
|
|
87
|
+
|
|
88
|
+
## Events
|
|
89
|
+
|
|
90
|
+
All payload types live in `PlayerEventMap` in `@scarlett-player/core`.
|
|
91
|
+
|
|
92
|
+
| Event | Payload | When |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `playlist:change` | `{ track, index }` | The current track changed. `track` is `null` after `clear()` |
|
|
95
|
+
| `playlist:add` | `{ track, index }` | `add()` (once per track) or `insert()` |
|
|
96
|
+
| `playlist:remove` | `{ track, index }` | `remove()` |
|
|
97
|
+
| `playlist:reorder` | `{ tracks }` | `move()` |
|
|
98
|
+
| `playlist:clear` | `void` | `clear()` |
|
|
99
|
+
| `playlist:shuffle` | `{ enabled }` | Shuffle changed |
|
|
100
|
+
| `playlist:repeat` | `{ mode }` | Repeat mode changed |
|
|
101
|
+
| `playlist:ended` | `void` | The last track ended with nothing to advance to |
|
|
102
|
+
|
|
103
|
+
On every track change the plugin also writes `title`, `poster` (from `artwork`) and `mediaType` into player state, clearing title and poster when the track has none so a previous track's values never leak.
|
|
104
|
+
|
|
105
|
+
## Control-bar controls
|
|
106
|
+
|
|
107
|
+
When `@scarlett-player/ui` is installed, three controls are registered. Nothing is placed until you list them in the UI layout:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
uiPlugin({ controls: ['playlist-previous', 'play', 'playlist-next', 'progress', 'spacer', 'playlist', 'fullscreen'] })
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
| Control id | Renders |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `playlist-previous` / `playlist-next` | Skip buttons, disabled at the ends of the queue |
|
|
116
|
+
| `playlist` | A button that opens the queue as a list |
|
|
117
|
+
|
|
118
|
+
All three hide themselves when the queue has one track or fewer. The classes `PlaylistSkipButton`, `PlaylistPanel` and the `PLAYLIST_ICONS` map are exported for custom layouts.
|
|
119
|
+
|
|
120
|
+
## Styling
|
|
121
|
+
|
|
122
|
+
A stylesheet is injected once per document (style element id `sp-playlist-styles`). Override these classes to restyle: `sp-playlist-skip` (with `--previous` / `--next` and the `[disabled]` state), `sp-playlist`, `sp-playlist--open`, `sp-playlist__button`, `sp-playlist__panel`, `sp-playlist__item`, `sp-playlist__item--active`, `sp-playlist__position`, `sp-playlist__text`, `sp-playlist__title` and `sp-playlist__artist`. No CSS custom properties are used.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -308,6 +308,9 @@ function injectStyles() {
|
|
|
308
308
|
return el;
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
// src/version.ts
|
|
312
|
+
var PKG_VERSION = true ? "1.8.1" : "0.0.0-dev";
|
|
313
|
+
|
|
311
314
|
// src/index.ts
|
|
312
315
|
var DEFAULT_CONFIG = {
|
|
313
316
|
autoAdvance: true,
|
|
@@ -457,9 +460,7 @@ function createPlaylistPlugin(config) {
|
|
|
457
460
|
currentIndex = index;
|
|
458
461
|
api?.logger.info("Track changed", { index, title: track.title, src: track.src });
|
|
459
462
|
api?.setState("title", track.title || "");
|
|
460
|
-
|
|
461
|
-
api?.setState("poster", track.artwork);
|
|
462
|
-
}
|
|
463
|
+
api?.setState("poster", track.artwork || "");
|
|
463
464
|
api?.setState("mediaType", track.type || "audio");
|
|
464
465
|
emitChange();
|
|
465
466
|
if (mergedConfig.autoLoad !== false && track.src) {
|
|
@@ -469,7 +470,7 @@ function createPlaylistPlugin(config) {
|
|
|
469
470
|
const plugin = {
|
|
470
471
|
id: "playlist",
|
|
471
472
|
name: "Playlist",
|
|
472
|
-
version:
|
|
473
|
+
version: PKG_VERSION,
|
|
473
474
|
type: "feature",
|
|
474
475
|
description: "Playlist management with shuffle, repeat, and gapless playback",
|
|
475
476
|
async init(pluginApi) {
|
package/dist/index.d.cts
CHANGED
|
@@ -240,9 +240,10 @@ declare class PlaylistPanel implements PlaylistControl {
|
|
|
240
240
|
*
|
|
241
241
|
* @example
|
|
242
242
|
* ```ts
|
|
243
|
+
* import { createPlayer } from '@scarlett-player/core';
|
|
243
244
|
* import { createPlaylistPlugin } from '@scarlett-player/playlist';
|
|
244
245
|
*
|
|
245
|
-
* const player =
|
|
246
|
+
* const player = await createPlayer({
|
|
246
247
|
* container: document.getElementById('player'),
|
|
247
248
|
* plugins: [
|
|
248
249
|
* createPlaylistPlugin({
|
package/dist/index.d.ts
CHANGED
|
@@ -240,9 +240,10 @@ declare class PlaylistPanel implements PlaylistControl {
|
|
|
240
240
|
*
|
|
241
241
|
* @example
|
|
242
242
|
* ```ts
|
|
243
|
+
* import { createPlayer } from '@scarlett-player/core';
|
|
243
244
|
* import { createPlaylistPlugin } from '@scarlett-player/playlist';
|
|
244
245
|
*
|
|
245
|
-
* const player =
|
|
246
|
+
* const player = await createPlayer({
|
|
246
247
|
* container: document.getElementById('player'),
|
|
247
248
|
* plugins: [
|
|
248
249
|
* createPlaylistPlugin({
|
package/dist/index.js
CHANGED
|
@@ -268,6 +268,9 @@ function injectStyles() {
|
|
|
268
268
|
return el;
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
// src/version.ts
|
|
272
|
+
var PKG_VERSION = true ? "1.8.1" : "0.0.0-dev";
|
|
273
|
+
|
|
271
274
|
// src/index.ts
|
|
272
275
|
var DEFAULT_CONFIG = {
|
|
273
276
|
autoAdvance: true,
|
|
@@ -417,9 +420,7 @@ function createPlaylistPlugin(config) {
|
|
|
417
420
|
currentIndex = index;
|
|
418
421
|
api?.logger.info("Track changed", { index, title: track.title, src: track.src });
|
|
419
422
|
api?.setState("title", track.title || "");
|
|
420
|
-
|
|
421
|
-
api?.setState("poster", track.artwork);
|
|
422
|
-
}
|
|
423
|
+
api?.setState("poster", track.artwork || "");
|
|
423
424
|
api?.setState("mediaType", track.type || "audio");
|
|
424
425
|
emitChange();
|
|
425
426
|
if (mergedConfig.autoLoad !== false && track.src) {
|
|
@@ -429,7 +430,7 @@ function createPlaylistPlugin(config) {
|
|
|
429
430
|
const plugin = {
|
|
430
431
|
id: "playlist",
|
|
431
432
|
name: "Playlist",
|
|
432
|
-
version:
|
|
433
|
+
version: PKG_VERSION,
|
|
433
434
|
type: "feature",
|
|
434
435
|
description: "Playlist management with shuffle, repeat, and gapless playback",
|
|
435
436
|
async init(pluginApi) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scarlett-player/playlist",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Playlist Plugin for Scarlett Player - Queue management, shuffle, repeat, and gapless playback",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@scarlett-player/core": "^1.0
|
|
26
|
-
"@scarlett-player/ui": "^1.
|
|
25
|
+
"@scarlett-player/core": "^1.8.0",
|
|
26
|
+
"@scarlett-player/ui": "^1.8.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"tsup": "^8.0.0",
|
|
30
30
|
"typescript": "^5.3.0",
|
|
31
31
|
"vitest": "^1.6.0",
|
|
32
32
|
"jsdom": "^24.0.0",
|
|
33
|
-
"@scarlett-player/core": "1.
|
|
34
|
-
"@scarlett-player/ui": "1.
|
|
33
|
+
"@scarlett-player/core": "1.8.1",
|
|
34
|
+
"@scarlett-player/ui": "1.8.1"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
|
37
37
|
"video",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
|
-
"build": "tsup
|
|
65
|
-
"dev": "tsup
|
|
64
|
+
"build": "tsup",
|
|
65
|
+
"dev": "tsup --watch",
|
|
66
66
|
"test": "vitest --run",
|
|
67
67
|
"test:watch": "vitest",
|
|
68
68
|
"typecheck": "tsc --noEmit"
|