@scarlett-player/playlist 1.8.1 → 1.10.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/dist/index.cjs +50 -27
- package/dist/index.js +52 -27
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
default: () => index_default
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
var import_core2 = require("@scarlett-player/core");
|
|
40
41
|
|
|
41
42
|
// src/controls.ts
|
|
42
43
|
var PLAYLIST_ICONS = {
|
|
@@ -197,6 +198,7 @@ var PlaylistPanel = class {
|
|
|
197
198
|
};
|
|
198
199
|
|
|
199
200
|
// src/styles.ts
|
|
201
|
+
var import_core = require("@scarlett-player/core");
|
|
200
202
|
var STYLE_ID = "sp-playlist-styles";
|
|
201
203
|
var styles = `
|
|
202
204
|
.sp-playlist-skip[disabled] {
|
|
@@ -298,18 +300,11 @@ var styles = `
|
|
|
298
300
|
}
|
|
299
301
|
`;
|
|
300
302
|
function injectStyles() {
|
|
301
|
-
|
|
302
|
-
return null;
|
|
303
|
-
}
|
|
304
|
-
const el = document.createElement("style");
|
|
305
|
-
el.id = STYLE_ID;
|
|
306
|
-
el.textContent = styles;
|
|
307
|
-
document.head.appendChild(el);
|
|
308
|
-
return el;
|
|
303
|
+
return (0, import_core.injectSharedStyles)(STYLE_ID, styles);
|
|
309
304
|
}
|
|
310
305
|
|
|
311
306
|
// src/version.ts
|
|
312
|
-
var PKG_VERSION = true ? "1.
|
|
307
|
+
var PKG_VERSION = true ? "1.10.0" : "0.0.0-dev";
|
|
313
308
|
|
|
314
309
|
// src/index.ts
|
|
315
310
|
var DEFAULT_CONFIG = {
|
|
@@ -336,6 +331,9 @@ function shuffleArray(array) {
|
|
|
336
331
|
function createPlaylistPlugin(config) {
|
|
337
332
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
338
333
|
let api = null;
|
|
334
|
+
let releaseStyles = null;
|
|
335
|
+
let releaseControls = null;
|
|
336
|
+
let lifecycle = 0;
|
|
339
337
|
let tracks = mergedConfig.tracks || [];
|
|
340
338
|
let currentIndex = mergedConfig.initialIndex ?? -1;
|
|
341
339
|
if (currentIndex < -1 || currentIndex >= tracks.length) {
|
|
@@ -430,18 +428,24 @@ function createPlaylistPlugin(config) {
|
|
|
430
428
|
api?.logger.warn("Failed to persist playlist", e);
|
|
431
429
|
}
|
|
432
430
|
};
|
|
431
|
+
const isTrack = (t) => typeof t === "object" && t !== null && typeof t.src === "string";
|
|
433
432
|
const loadPersistedPlaylist = () => {
|
|
434
433
|
if (!mergedConfig.persist) return;
|
|
434
|
+
if (mergedConfig.tracks !== void 0) return;
|
|
435
435
|
try {
|
|
436
436
|
const data = localStorage.getItem(mergedConfig.persistKey);
|
|
437
|
-
if (data)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
437
|
+
if (!data) return;
|
|
438
|
+
const parsed = JSON.parse(data);
|
|
439
|
+
const restored = Array.isArray(parsed.tracks) ? parsed.tracks.filter(isTrack) : [];
|
|
440
|
+
if (restored.length === 0) return;
|
|
441
|
+
tracks = restored;
|
|
442
|
+
const index = Number.isInteger(parsed.currentIndex) ? parsed.currentIndex : -1;
|
|
443
|
+
currentIndex = index >= 0 ? Math.min(index, tracks.length - 1) : -1;
|
|
444
|
+
shuffle = parsed.shuffle === true;
|
|
445
|
+
repeat = parsed.repeat === "one" || parsed.repeat === "all" ? parsed.repeat : "none";
|
|
446
|
+
shuffleOrder = Array.isArray(parsed.shuffleOrder) ? parsed.shuffleOrder.filter(
|
|
447
|
+
(n) => typeof n === "number" && n >= 0 && n < tracks.length
|
|
448
|
+
) : [];
|
|
445
449
|
} catch (e) {
|
|
446
450
|
api?.logger.warn("Failed to load persisted playlist", e);
|
|
447
451
|
}
|
|
@@ -451,20 +455,20 @@ function createPlaylistPlugin(config) {
|
|
|
451
455
|
api?.emit("playlist:change", { track, index: currentIndex });
|
|
452
456
|
persistPlaylist();
|
|
453
457
|
};
|
|
454
|
-
const setCurrentTrack = (index) => {
|
|
458
|
+
const setCurrentTrack = (index, options = {}) => {
|
|
455
459
|
if (index < 0 || index >= tracks.length) {
|
|
456
460
|
api?.logger.warn("Invalid track index", { index });
|
|
457
461
|
return;
|
|
458
462
|
}
|
|
459
463
|
const track = tracks[index];
|
|
460
464
|
currentIndex = index;
|
|
461
|
-
api?.logger.info("Track changed", { index, title: track.title, src: track.src });
|
|
465
|
+
api?.logger.info("Track changed", { index, title: track.title, src: (0, import_core2.sanitizeUrl)(track.src) });
|
|
462
466
|
api?.setState("title", track.title || "");
|
|
463
467
|
api?.setState("poster", track.artwork || "");
|
|
464
468
|
api?.setState("mediaType", track.type || "audio");
|
|
465
469
|
emitChange();
|
|
466
470
|
if (mergedConfig.autoLoad !== false && track.src) {
|
|
467
|
-
api?.emit("media:load-request", { src: track.src, autoplay: true });
|
|
471
|
+
api?.emit("media:load-request", { src: track.src, autoplay: options.autoplay ?? true });
|
|
468
472
|
}
|
|
469
473
|
};
|
|
470
474
|
const plugin = {
|
|
@@ -474,10 +478,11 @@ function createPlaylistPlugin(config) {
|
|
|
474
478
|
type: "feature",
|
|
475
479
|
description: "Playlist management with shuffle, repeat, and gapless playback",
|
|
476
480
|
async init(pluginApi) {
|
|
481
|
+
const generation = ++lifecycle;
|
|
477
482
|
api = pluginApi;
|
|
478
483
|
api.logger.info("Playlist plugin initialized");
|
|
479
484
|
loadPersistedPlaylist();
|
|
480
|
-
if (shuffle && tracks.length > 0) {
|
|
485
|
+
if (shuffle && tracks.length > 0 && shuffleOrder.length !== tracks.length) {
|
|
481
486
|
generateShuffleOrder();
|
|
482
487
|
}
|
|
483
488
|
let advanceTimeout = null;
|
|
@@ -485,11 +490,13 @@ function createPlaylistPlugin(config) {
|
|
|
485
490
|
if (!mergedConfig.autoAdvance) return;
|
|
486
491
|
const nextIdx = getNextIndex();
|
|
487
492
|
if (nextIdx >= 0) {
|
|
493
|
+
const wasPlaying = !api?.getState("paused");
|
|
488
494
|
const advance = () => {
|
|
489
495
|
api?.logger.debug("Auto-advancing to next track", { nextIdx });
|
|
490
|
-
setCurrentTrack(nextIdx);
|
|
496
|
+
setCurrentTrack(nextIdx, { autoplay: wasPlaying });
|
|
491
497
|
};
|
|
492
498
|
if (mergedConfig.advanceDelay) {
|
|
499
|
+
if (advanceTimeout) clearTimeout(advanceTimeout);
|
|
493
500
|
advanceTimeout = setTimeout(advance, mergedConfig.advanceDelay);
|
|
494
501
|
} else {
|
|
495
502
|
advance();
|
|
@@ -499,20 +506,31 @@ function createPlaylistPlugin(config) {
|
|
|
499
506
|
api?.emit("playlist:ended", void 0);
|
|
500
507
|
}
|
|
501
508
|
});
|
|
502
|
-
injectStyles();
|
|
503
|
-
|
|
509
|
+
releaseStyles = injectStyles();
|
|
510
|
+
const owner = api.container;
|
|
511
|
+
void import("@scarlett-player/ui").then(({ registerControl, unregisterControl }) => {
|
|
512
|
+
if (generation !== lifecycle) return;
|
|
504
513
|
const self = plugin;
|
|
505
514
|
registerControl(
|
|
506
515
|
"playlist-previous",
|
|
507
|
-
() => new PlaylistSkipButton(self, "previous")
|
|
516
|
+
() => new PlaylistSkipButton(self, "previous"),
|
|
517
|
+
{ owner }
|
|
508
518
|
);
|
|
509
|
-
registerControl("playlist-next", () => new PlaylistSkipButton(self, "next")
|
|
519
|
+
registerControl("playlist-next", () => new PlaylistSkipButton(self, "next"), {
|
|
520
|
+
owner
|
|
521
|
+
});
|
|
510
522
|
registerControl(
|
|
511
523
|
"playlist",
|
|
512
524
|
() => new PlaylistPanel(self, {
|
|
513
525
|
onSelect: (index) => self.play(index)
|
|
514
|
-
})
|
|
526
|
+
}),
|
|
527
|
+
{ owner }
|
|
515
528
|
);
|
|
529
|
+
releaseControls = () => {
|
|
530
|
+
unregisterControl("playlist-previous", { owner });
|
|
531
|
+
unregisterControl("playlist-next", { owner });
|
|
532
|
+
unregisterControl("playlist", { owner });
|
|
533
|
+
};
|
|
516
534
|
}).catch(() => {
|
|
517
535
|
api?.logger.debug("@scarlett-player/ui not present, playlist controls not registered");
|
|
518
536
|
});
|
|
@@ -543,8 +561,13 @@ function createPlaylistPlugin(config) {
|
|
|
543
561
|
});
|
|
544
562
|
},
|
|
545
563
|
async destroy() {
|
|
564
|
+
lifecycle++;
|
|
546
565
|
api?.logger.info("Playlist plugin destroying");
|
|
547
566
|
persistPlaylist();
|
|
567
|
+
releaseStyles?.();
|
|
568
|
+
releaseStyles = null;
|
|
569
|
+
releaseControls?.();
|
|
570
|
+
releaseControls = null;
|
|
548
571
|
api = null;
|
|
549
572
|
},
|
|
550
573
|
add(trackOrTracks) {
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { sanitizeUrl } from "@scarlett-player/core";
|
|
3
|
+
|
|
1
4
|
// src/controls.ts
|
|
2
5
|
var PLAYLIST_ICONS = {
|
|
3
6
|
previous: '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 6h2v12H6V6zm3.5 6L18 6v12l-8.5-6z"/></svg>',
|
|
@@ -157,6 +160,7 @@ var PlaylistPanel = class {
|
|
|
157
160
|
};
|
|
158
161
|
|
|
159
162
|
// src/styles.ts
|
|
163
|
+
import { injectSharedStyles } from "@scarlett-player/core";
|
|
160
164
|
var STYLE_ID = "sp-playlist-styles";
|
|
161
165
|
var styles = `
|
|
162
166
|
.sp-playlist-skip[disabled] {
|
|
@@ -258,18 +262,11 @@ var styles = `
|
|
|
258
262
|
}
|
|
259
263
|
`;
|
|
260
264
|
function injectStyles() {
|
|
261
|
-
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
const el = document.createElement("style");
|
|
265
|
-
el.id = STYLE_ID;
|
|
266
|
-
el.textContent = styles;
|
|
267
|
-
document.head.appendChild(el);
|
|
268
|
-
return el;
|
|
265
|
+
return injectSharedStyles(STYLE_ID, styles);
|
|
269
266
|
}
|
|
270
267
|
|
|
271
268
|
// src/version.ts
|
|
272
|
-
var PKG_VERSION = true ? "1.
|
|
269
|
+
var PKG_VERSION = true ? "1.10.0" : "0.0.0-dev";
|
|
273
270
|
|
|
274
271
|
// src/index.ts
|
|
275
272
|
var DEFAULT_CONFIG = {
|
|
@@ -296,6 +293,9 @@ function shuffleArray(array) {
|
|
|
296
293
|
function createPlaylistPlugin(config) {
|
|
297
294
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
298
295
|
let api = null;
|
|
296
|
+
let releaseStyles = null;
|
|
297
|
+
let releaseControls = null;
|
|
298
|
+
let lifecycle = 0;
|
|
299
299
|
let tracks = mergedConfig.tracks || [];
|
|
300
300
|
let currentIndex = mergedConfig.initialIndex ?? -1;
|
|
301
301
|
if (currentIndex < -1 || currentIndex >= tracks.length) {
|
|
@@ -390,18 +390,24 @@ function createPlaylistPlugin(config) {
|
|
|
390
390
|
api?.logger.warn("Failed to persist playlist", e);
|
|
391
391
|
}
|
|
392
392
|
};
|
|
393
|
+
const isTrack = (t) => typeof t === "object" && t !== null && typeof t.src === "string";
|
|
393
394
|
const loadPersistedPlaylist = () => {
|
|
394
395
|
if (!mergedConfig.persist) return;
|
|
396
|
+
if (mergedConfig.tracks !== void 0) return;
|
|
395
397
|
try {
|
|
396
398
|
const data = localStorage.getItem(mergedConfig.persistKey);
|
|
397
|
-
if (data)
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
399
|
+
if (!data) return;
|
|
400
|
+
const parsed = JSON.parse(data);
|
|
401
|
+
const restored = Array.isArray(parsed.tracks) ? parsed.tracks.filter(isTrack) : [];
|
|
402
|
+
if (restored.length === 0) return;
|
|
403
|
+
tracks = restored;
|
|
404
|
+
const index = Number.isInteger(parsed.currentIndex) ? parsed.currentIndex : -1;
|
|
405
|
+
currentIndex = index >= 0 ? Math.min(index, tracks.length - 1) : -1;
|
|
406
|
+
shuffle = parsed.shuffle === true;
|
|
407
|
+
repeat = parsed.repeat === "one" || parsed.repeat === "all" ? parsed.repeat : "none";
|
|
408
|
+
shuffleOrder = Array.isArray(parsed.shuffleOrder) ? parsed.shuffleOrder.filter(
|
|
409
|
+
(n) => typeof n === "number" && n >= 0 && n < tracks.length
|
|
410
|
+
) : [];
|
|
405
411
|
} catch (e) {
|
|
406
412
|
api?.logger.warn("Failed to load persisted playlist", e);
|
|
407
413
|
}
|
|
@@ -411,20 +417,20 @@ function createPlaylistPlugin(config) {
|
|
|
411
417
|
api?.emit("playlist:change", { track, index: currentIndex });
|
|
412
418
|
persistPlaylist();
|
|
413
419
|
};
|
|
414
|
-
const setCurrentTrack = (index) => {
|
|
420
|
+
const setCurrentTrack = (index, options = {}) => {
|
|
415
421
|
if (index < 0 || index >= tracks.length) {
|
|
416
422
|
api?.logger.warn("Invalid track index", { index });
|
|
417
423
|
return;
|
|
418
424
|
}
|
|
419
425
|
const track = tracks[index];
|
|
420
426
|
currentIndex = index;
|
|
421
|
-
api?.logger.info("Track changed", { index, title: track.title, src: track.src });
|
|
427
|
+
api?.logger.info("Track changed", { index, title: track.title, src: sanitizeUrl(track.src) });
|
|
422
428
|
api?.setState("title", track.title || "");
|
|
423
429
|
api?.setState("poster", track.artwork || "");
|
|
424
430
|
api?.setState("mediaType", track.type || "audio");
|
|
425
431
|
emitChange();
|
|
426
432
|
if (mergedConfig.autoLoad !== false && track.src) {
|
|
427
|
-
api?.emit("media:load-request", { src: track.src, autoplay: true });
|
|
433
|
+
api?.emit("media:load-request", { src: track.src, autoplay: options.autoplay ?? true });
|
|
428
434
|
}
|
|
429
435
|
};
|
|
430
436
|
const plugin = {
|
|
@@ -434,10 +440,11 @@ function createPlaylistPlugin(config) {
|
|
|
434
440
|
type: "feature",
|
|
435
441
|
description: "Playlist management with shuffle, repeat, and gapless playback",
|
|
436
442
|
async init(pluginApi) {
|
|
443
|
+
const generation = ++lifecycle;
|
|
437
444
|
api = pluginApi;
|
|
438
445
|
api.logger.info("Playlist plugin initialized");
|
|
439
446
|
loadPersistedPlaylist();
|
|
440
|
-
if (shuffle && tracks.length > 0) {
|
|
447
|
+
if (shuffle && tracks.length > 0 && shuffleOrder.length !== tracks.length) {
|
|
441
448
|
generateShuffleOrder();
|
|
442
449
|
}
|
|
443
450
|
let advanceTimeout = null;
|
|
@@ -445,11 +452,13 @@ function createPlaylistPlugin(config) {
|
|
|
445
452
|
if (!mergedConfig.autoAdvance) return;
|
|
446
453
|
const nextIdx = getNextIndex();
|
|
447
454
|
if (nextIdx >= 0) {
|
|
455
|
+
const wasPlaying = !api?.getState("paused");
|
|
448
456
|
const advance = () => {
|
|
449
457
|
api?.logger.debug("Auto-advancing to next track", { nextIdx });
|
|
450
|
-
setCurrentTrack(nextIdx);
|
|
458
|
+
setCurrentTrack(nextIdx, { autoplay: wasPlaying });
|
|
451
459
|
};
|
|
452
460
|
if (mergedConfig.advanceDelay) {
|
|
461
|
+
if (advanceTimeout) clearTimeout(advanceTimeout);
|
|
453
462
|
advanceTimeout = setTimeout(advance, mergedConfig.advanceDelay);
|
|
454
463
|
} else {
|
|
455
464
|
advance();
|
|
@@ -459,20 +468,31 @@ function createPlaylistPlugin(config) {
|
|
|
459
468
|
api?.emit("playlist:ended", void 0);
|
|
460
469
|
}
|
|
461
470
|
});
|
|
462
|
-
injectStyles();
|
|
463
|
-
|
|
471
|
+
releaseStyles = injectStyles();
|
|
472
|
+
const owner = api.container;
|
|
473
|
+
void import("@scarlett-player/ui").then(({ registerControl, unregisterControl }) => {
|
|
474
|
+
if (generation !== lifecycle) return;
|
|
464
475
|
const self = plugin;
|
|
465
476
|
registerControl(
|
|
466
477
|
"playlist-previous",
|
|
467
|
-
() => new PlaylistSkipButton(self, "previous")
|
|
478
|
+
() => new PlaylistSkipButton(self, "previous"),
|
|
479
|
+
{ owner }
|
|
468
480
|
);
|
|
469
|
-
registerControl("playlist-next", () => new PlaylistSkipButton(self, "next")
|
|
481
|
+
registerControl("playlist-next", () => new PlaylistSkipButton(self, "next"), {
|
|
482
|
+
owner
|
|
483
|
+
});
|
|
470
484
|
registerControl(
|
|
471
485
|
"playlist",
|
|
472
486
|
() => new PlaylistPanel(self, {
|
|
473
487
|
onSelect: (index) => self.play(index)
|
|
474
|
-
})
|
|
488
|
+
}),
|
|
489
|
+
{ owner }
|
|
475
490
|
);
|
|
491
|
+
releaseControls = () => {
|
|
492
|
+
unregisterControl("playlist-previous", { owner });
|
|
493
|
+
unregisterControl("playlist-next", { owner });
|
|
494
|
+
unregisterControl("playlist", { owner });
|
|
495
|
+
};
|
|
476
496
|
}).catch(() => {
|
|
477
497
|
api?.logger.debug("@scarlett-player/ui not present, playlist controls not registered");
|
|
478
498
|
});
|
|
@@ -503,8 +523,13 @@ function createPlaylistPlugin(config) {
|
|
|
503
523
|
});
|
|
504
524
|
},
|
|
505
525
|
async destroy() {
|
|
526
|
+
lifecycle++;
|
|
506
527
|
api?.logger.info("Playlist plugin destroying");
|
|
507
528
|
persistPlaylist();
|
|
529
|
+
releaseStyles?.();
|
|
530
|
+
releaseStyles = null;
|
|
531
|
+
releaseControls?.();
|
|
532
|
+
releaseControls = null;
|
|
508
533
|
api = null;
|
|
509
534
|
},
|
|
510
535
|
add(trackOrTracks) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scarlett-player/playlist",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
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.
|
|
26
|
-
"@scarlett-player/ui": "^1.
|
|
25
|
+
"@scarlett-player/core": "^1.9.0",
|
|
26
|
+
"@scarlett-player/ui": "^1.9.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.10.0",
|
|
34
|
+
"@scarlett-player/ui": "1.10.0"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
|
37
37
|
"video",
|
|
@@ -65,6 +65,6 @@
|
|
|
65
65
|
"dev": "tsup --watch",
|
|
66
66
|
"test": "vitest --run",
|
|
67
67
|
"test:watch": "vitest",
|
|
68
|
-
"typecheck": "tsc --noEmit"
|
|
68
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
|
|
69
69
|
}
|
|
70
70
|
}
|