@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 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
- if (track.artwork) {
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: "1.0.0",
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 = new ScarlettPlayer({
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 = new ScarlettPlayer({
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
- if (track.artwork) {
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: "1.0.0",
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.7.0",
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.3",
26
- "@scarlett-player/ui": "^1.5.0"
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.7.0",
34
- "@scarlett-player/ui": "1.7.0"
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 src/index.ts --format esm,cjs --dts",
65
- "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
64
+ "build": "tsup",
65
+ "dev": "tsup --watch",
66
66
  "test": "vitest --run",
67
67
  "test:watch": "vitest",
68
68
  "typecheck": "tsc --noEmit"