@scarlett-player/embed 1.5.0 → 1.6.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 +1 -1
- package/dist/embed.audio.js +304 -4
- package/dist/embed.audio.js.map +1 -1
- package/dist/embed.audio.umd.cjs +1 -1
- package/dist/embed.audio.umd.cjs.map +1 -1
- package/dist/embed.js +488 -56
- package/dist/embed.js.map +1 -1
- package/dist/embed.umd.cjs +1 -1
- package/dist/embed.umd.cjs.map +1 -1
- package/dist/embed.video.js +119 -3
- package/dist/embed.video.js.map +1 -1
- package/dist/embed.video.umd.cjs +1 -1
- package/dist/embed.video.umd.cjs.map +1 -1
- package/dist/hls2.js +12 -0
- package/dist/hls2.js.map +1 -0
- package/iframe.html +1 -1
- package/package.json +10 -10
package/dist/embed.js
CHANGED
|
@@ -180,8 +180,8 @@ class StateManager {
|
|
|
180
180
|
* Register a state key at runtime, for state a plugin owns.
|
|
181
181
|
*
|
|
182
182
|
* Core cannot know every plugin's keys, and {@link get} deliberately throws
|
|
183
|
-
* for unregistered ones
|
|
184
|
-
* keeping
|
|
183
|
+
* for unregistered ones - that throw is a useful typo-catcher and is worth
|
|
184
|
+
* keeping - so a plugin declares its keys before first use.
|
|
185
185
|
*
|
|
186
186
|
* Idempotent by design: re-defining an existing key leaves the current value
|
|
187
187
|
* untouched. Plugins commonly re-run setup after a source change, and that
|
|
@@ -920,9 +920,9 @@ class Logger {
|
|
|
920
920
|
* ```
|
|
921
921
|
*/
|
|
922
922
|
removeHandler(handler) {
|
|
923
|
-
const
|
|
924
|
-
if (
|
|
925
|
-
this.handlers.splice(
|
|
923
|
+
const index2 = this.handlers.indexOf(handler);
|
|
924
|
+
if (index2 !== -1) {
|
|
925
|
+
this.handlers.splice(index2, 1);
|
|
926
926
|
}
|
|
927
927
|
}
|
|
928
928
|
/**
|
|
@@ -1259,7 +1259,7 @@ class PluginAPI {
|
|
|
1259
1259
|
/**
|
|
1260
1260
|
* Register a state key this plugin owns, before first use.
|
|
1261
1261
|
*
|
|
1262
|
-
* Idempotent
|
|
1262
|
+
* Idempotent - re-defining an existing key keeps its current value.
|
|
1263
1263
|
* See {@link IPluginAPI.defineState}.
|
|
1264
1264
|
*
|
|
1265
1265
|
* @param key - State property key
|
|
@@ -1983,7 +1983,7 @@ class ScarlettPlayer {
|
|
|
1983
1983
|
* Set quality level (-1 for auto).
|
|
1984
1984
|
* @param index - Quality level index
|
|
1985
1985
|
*/
|
|
1986
|
-
setQuality(
|
|
1986
|
+
setQuality(index2) {
|
|
1987
1987
|
this.checkDestroyed();
|
|
1988
1988
|
if (!this._currentProvider) {
|
|
1989
1989
|
this.logger.warn("No provider available for quality change");
|
|
@@ -1991,17 +1991,17 @@ class ScarlettPlayer {
|
|
|
1991
1991
|
}
|
|
1992
1992
|
const provider = this._currentProvider;
|
|
1993
1993
|
if (typeof provider.setLevel === "function") {
|
|
1994
|
-
if (
|
|
1994
|
+
if (index2 !== -1) {
|
|
1995
1995
|
const levels = this.getQualities();
|
|
1996
|
-
if (levels.length > 0 && (
|
|
1997
|
-
this.logger.warn(`Invalid quality index: ${
|
|
1996
|
+
if (levels.length > 0 && (index2 < 0 || index2 >= levels.length)) {
|
|
1997
|
+
this.logger.warn(`Invalid quality index: ${index2} (available: ${levels.length})`);
|
|
1998
1998
|
return;
|
|
1999
1999
|
}
|
|
2000
2000
|
}
|
|
2001
|
-
provider.setLevel(
|
|
2001
|
+
provider.setLevel(index2);
|
|
2002
2002
|
this.eventBus.emit("quality:change", {
|
|
2003
|
-
quality:
|
|
2004
|
-
auto:
|
|
2003
|
+
quality: index2 === -1 ? "auto" : `level-${index2}`,
|
|
2004
|
+
auto: index2 === -1
|
|
2005
2005
|
});
|
|
2006
2006
|
}
|
|
2007
2007
|
}
|
|
@@ -2323,8 +2323,8 @@ function formatBitrate(bitrate) {
|
|
|
2323
2323
|
return `${bitrate} bps`;
|
|
2324
2324
|
}
|
|
2325
2325
|
function mapLevels(levels, _currentLevel) {
|
|
2326
|
-
return levels.map((level,
|
|
2327
|
-
index,
|
|
2326
|
+
return levels.map((level, index2) => ({
|
|
2327
|
+
index: index2,
|
|
2328
2328
|
width: level.width || 0,
|
|
2329
2329
|
height: level.height || 0,
|
|
2330
2330
|
bitrate: level.bitrate || 0,
|
|
@@ -2379,13 +2379,13 @@ function setupHlsEventHandlers(hls, api, callbacks) {
|
|
|
2379
2379
|
};
|
|
2380
2380
|
addHandler("hlsManifestParsed", (_event, data) => {
|
|
2381
2381
|
api.logger.debug("HLS manifest parsed", { levels: data.levels.length });
|
|
2382
|
-
const levels = data.levels.map((level,
|
|
2383
|
-
id: `level-${
|
|
2382
|
+
const levels = data.levels.map((level, index2) => ({
|
|
2383
|
+
id: `level-${index2}`,
|
|
2384
2384
|
label: formatLevel(level),
|
|
2385
2385
|
width: level.width,
|
|
2386
2386
|
height: level.height,
|
|
2387
2387
|
bitrate: level.bitrate,
|
|
2388
|
-
active:
|
|
2388
|
+
active: index2 === hls.currentLevel
|
|
2389
2389
|
}));
|
|
2390
2390
|
api.setState("qualities", levels);
|
|
2391
2391
|
api.emit("quality:levels", {
|
|
@@ -3298,12 +3298,12 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
3298
3298
|
if (isNative || !hls) return -1;
|
|
3299
3299
|
return hls.currentLevel;
|
|
3300
3300
|
},
|
|
3301
|
-
setLevel(
|
|
3301
|
+
setLevel(index2) {
|
|
3302
3302
|
if (isNative || !hls) {
|
|
3303
3303
|
api?.logger.warn("Quality selection not available in native HLS mode");
|
|
3304
3304
|
return;
|
|
3305
3305
|
}
|
|
3306
|
-
hls.currentLevel =
|
|
3306
|
+
hls.currentLevel = index2;
|
|
3307
3307
|
},
|
|
3308
3308
|
getLevels() {
|
|
3309
3309
|
if (isNative || !hls) return [];
|
|
@@ -3488,7 +3488,7 @@ function createHLSPlugin(config) {
|
|
|
3488
3488
|
config
|
|
3489
3489
|
);
|
|
3490
3490
|
}
|
|
3491
|
-
var styles = `
|
|
3491
|
+
var styles$1 = `
|
|
3492
3492
|
/* ============================================
|
|
3493
3493
|
Container & Base
|
|
3494
3494
|
============================================ */
|
|
@@ -3635,6 +3635,25 @@ var styles = `
|
|
|
3635
3635
|
border-radius: inherit;
|
|
3636
3636
|
}
|
|
3637
3637
|
|
|
3638
|
+
/* Chapter markers */
|
|
3639
|
+
.sp-progress__markers {
|
|
3640
|
+
position: absolute;
|
|
3641
|
+
top: 0;
|
|
3642
|
+
left: 0;
|
|
3643
|
+
width: 100%;
|
|
3644
|
+
height: 100%;
|
|
3645
|
+
pointer-events: none;
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
.sp-progress__marker {
|
|
3649
|
+
position: absolute;
|
|
3650
|
+
top: 0;
|
|
3651
|
+
width: 2px;
|
|
3652
|
+
height: 100%;
|
|
3653
|
+
margin-left: -1px;
|
|
3654
|
+
background: rgba(0, 0, 0, 0.65);
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3638
3657
|
.sp-progress__handle {
|
|
3639
3658
|
position: absolute;
|
|
3640
3659
|
top: 50%;
|
|
@@ -3694,6 +3713,16 @@ var styles = `
|
|
|
3694
3713
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
3695
3714
|
}
|
|
3696
3715
|
|
|
3716
|
+
.sp-progress__tooltip-chapter {
|
|
3717
|
+
display: block;
|
|
3718
|
+
max-width: 220px;
|
|
3719
|
+
overflow: hidden;
|
|
3720
|
+
color: rgba(255, 255, 255, 0.75);
|
|
3721
|
+
font-weight: 400;
|
|
3722
|
+
font-variant-numeric: normal;
|
|
3723
|
+
text-overflow: ellipsis;
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3697
3726
|
@media (hover: hover) {
|
|
3698
3727
|
.sp-progress-wrapper:hover .sp-progress__tooltip {
|
|
3699
3728
|
opacity: 1;
|
|
@@ -4504,9 +4533,9 @@ var ThumbnailPreview = class {
|
|
|
4504
4533
|
return;
|
|
4505
4534
|
}
|
|
4506
4535
|
const { src, width, height, columns, interval } = this.config;
|
|
4507
|
-
const
|
|
4508
|
-
const col =
|
|
4509
|
-
const row = Math.floor(
|
|
4536
|
+
const index2 = Math.floor(time / interval);
|
|
4537
|
+
const col = index2 % columns;
|
|
4538
|
+
const row = Math.floor(index2 / columns);
|
|
4510
4539
|
this.img.style.backgroundImage = `url(${src})`;
|
|
4511
4540
|
this.img.style.backgroundPosition = `-${col * width}px -${row * height}px`;
|
|
4512
4541
|
this.img.style.backgroundSize = `${columns * width}px auto`;
|
|
@@ -4531,6 +4560,8 @@ var ProgressBar = class {
|
|
|
4531
4560
|
this.lastSeekTime = 0;
|
|
4532
4561
|
this.seekThrottleMs = 100;
|
|
4533
4562
|
this.wasPlayingBeforeDrag = false;
|
|
4563
|
+
this.renderedChapters = null;
|
|
4564
|
+
this.renderedDuration = 0;
|
|
4534
4565
|
this.onMouseDown = (e) => {
|
|
4535
4566
|
e.preventDefault();
|
|
4536
4567
|
const video = getVideo(this.api.container);
|
|
@@ -4665,12 +4696,14 @@ var ProgressBar = class {
|
|
|
4665
4696
|
const track = createElement("div", { className: "sp-progress__track" });
|
|
4666
4697
|
this.buffered = createElement("div", { className: "sp-progress__buffered" });
|
|
4667
4698
|
this.filled = createElement("div", { className: "sp-progress__filled" });
|
|
4699
|
+
this.markers = createElement("div", { className: "sp-progress__markers" });
|
|
4668
4700
|
this.handle = createElement("div", { className: "sp-progress__handle" });
|
|
4669
4701
|
this.tooltip = createElement("div", { className: "sp-progress__tooltip" });
|
|
4670
4702
|
this.tooltip.textContent = "0:00";
|
|
4671
4703
|
this.thumbnailPreview = new ThumbnailPreview();
|
|
4672
4704
|
track.appendChild(this.buffered);
|
|
4673
4705
|
track.appendChild(this.filled);
|
|
4706
|
+
track.appendChild(this.markers);
|
|
4674
4707
|
track.appendChild(this.handle);
|
|
4675
4708
|
this.el.appendChild(track);
|
|
4676
4709
|
this.el.appendChild(this.thumbnailPreview.getElement());
|
|
@@ -4720,6 +4753,7 @@ var ProgressBar = class {
|
|
|
4720
4753
|
this.thumbnailPreview.setConfig(thumbnails);
|
|
4721
4754
|
}
|
|
4722
4755
|
this.el.classList.toggle("sp-progress--live", !!live);
|
|
4756
|
+
this.updateMarkers(duration, live, seekableRange);
|
|
4723
4757
|
if (live && seekableRange) {
|
|
4724
4758
|
const rangeLength = seekableRange.end - seekableRange.start;
|
|
4725
4759
|
if (rangeLength > 0) {
|
|
@@ -4752,6 +4786,70 @@ var ProgressBar = class {
|
|
|
4752
4786
|
this.el.setAttribute("aria-valuetext", formatTime$1(currentTime));
|
|
4753
4787
|
}
|
|
4754
4788
|
}
|
|
4789
|
+
/**
|
|
4790
|
+
* Label of the chapter containing a point on the timeline.
|
|
4791
|
+
*
|
|
4792
|
+
* Mirrors the chapters plugin's own lookup: a start time belongs to its
|
|
4793
|
+
* chapter, an end time belongs to the next one, and a point in a gap between
|
|
4794
|
+
* sparse chapters belongs to neither.
|
|
4795
|
+
*
|
|
4796
|
+
* @param time - Position in seconds
|
|
4797
|
+
* @returns The chapter label, or null when the point is outside every chapter
|
|
4798
|
+
*/
|
|
4799
|
+
chapterLabelAt(time) {
|
|
4800
|
+
const chapters = this.api.getState("chapters") ?? [];
|
|
4801
|
+
for (let i = chapters.length - 1; i >= 0; i--) {
|
|
4802
|
+
const chapter = chapters[i];
|
|
4803
|
+
if (time < chapter.time) continue;
|
|
4804
|
+
const next = chapters[i + 1];
|
|
4805
|
+
const end = chapter.endTime ?? (next ? next.time : Infinity);
|
|
4806
|
+
return time < end ? chapter.label : null;
|
|
4807
|
+
}
|
|
4808
|
+
return null;
|
|
4809
|
+
}
|
|
4810
|
+
/**
|
|
4811
|
+
* Paint chapter dividers along the track.
|
|
4812
|
+
*
|
|
4813
|
+
* Reads the `chapters` state that core owns, so this works whether the list
|
|
4814
|
+
* came from the chapters plugin or the host set it directly, and renders
|
|
4815
|
+
* nothing at all when there are none.
|
|
4816
|
+
*
|
|
4817
|
+
* Rebuilds only when the list or the duration actually changed. `update()`
|
|
4818
|
+
* runs on every time update, and rebuilding a dozen nodes 4 times a second
|
|
4819
|
+
* would churn the DOM for no reason.
|
|
4820
|
+
*
|
|
4821
|
+
* @param duration - Media duration in seconds
|
|
4822
|
+
* @param live - Whether the media is live
|
|
4823
|
+
* @param seekableRange - DVR window, when the media is live
|
|
4824
|
+
*/
|
|
4825
|
+
updateMarkers(duration, live, seekableRange) {
|
|
4826
|
+
const chapters = this.api.getState("chapters") ?? [];
|
|
4827
|
+
const range = live ? seekableRange ? seekableRange.end - seekableRange.start : 0 : duration;
|
|
4828
|
+
if (chapters.length === 0 || range <= 0) {
|
|
4829
|
+
if (this.renderedChapters !== null) {
|
|
4830
|
+
this.markers.textContent = "";
|
|
4831
|
+
this.renderedChapters = null;
|
|
4832
|
+
this.renderedDuration = 0;
|
|
4833
|
+
}
|
|
4834
|
+
return;
|
|
4835
|
+
}
|
|
4836
|
+
if (chapters === this.renderedChapters && range === this.renderedDuration) {
|
|
4837
|
+
return;
|
|
4838
|
+
}
|
|
4839
|
+
const origin = live && seekableRange ? seekableRange.start : 0;
|
|
4840
|
+
this.markers.textContent = "";
|
|
4841
|
+
for (const chapter of chapters) {
|
|
4842
|
+
if (chapter.time <= origin) continue;
|
|
4843
|
+
const percent = (chapter.time - origin) / range * 100;
|
|
4844
|
+
if (percent <= 0 || percent >= 100) continue;
|
|
4845
|
+
const marker = createElement("div", { className: "sp-progress__marker" });
|
|
4846
|
+
marker.style.left = `${percent}%`;
|
|
4847
|
+
marker.title = chapter.label;
|
|
4848
|
+
this.markers.appendChild(marker);
|
|
4849
|
+
}
|
|
4850
|
+
this.renderedChapters = chapters;
|
|
4851
|
+
this.renderedDuration = range;
|
|
4852
|
+
}
|
|
4755
4853
|
getTimeFromPosition(clientX) {
|
|
4756
4854
|
const rect = this.el.getBoundingClientRect();
|
|
4757
4855
|
const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
@@ -4776,6 +4874,12 @@ var ProgressBar = class {
|
|
|
4776
4874
|
} else {
|
|
4777
4875
|
this.tooltip.textContent = formatTime$1(time);
|
|
4778
4876
|
}
|
|
4877
|
+
const chapterLabel = this.chapterLabelAt(time);
|
|
4878
|
+
if (chapterLabel) {
|
|
4879
|
+
const label = createElement("span", { className: "sp-progress__tooltip-chapter" });
|
|
4880
|
+
label.textContent = chapterLabel;
|
|
4881
|
+
this.tooltip.appendChild(label);
|
|
4882
|
+
}
|
|
4779
4883
|
this.tooltip.style.left = `${percent * 100}%`;
|
|
4780
4884
|
if (this.thumbnailPreview.isConfigured()) {
|
|
4781
4885
|
this.thumbnailPreview.show(time, percent);
|
|
@@ -6024,6 +6128,12 @@ var BandwidthIndicator = class {
|
|
|
6024
6128
|
};
|
|
6025
6129
|
var registry = /* @__PURE__ */ new Map();
|
|
6026
6130
|
var listeners = /* @__PURE__ */ new Set();
|
|
6131
|
+
function registerControl(id, factory) {
|
|
6132
|
+
registry.set(id, factory);
|
|
6133
|
+
for (const listener of listeners) {
|
|
6134
|
+
listener(id);
|
|
6135
|
+
}
|
|
6136
|
+
}
|
|
6027
6137
|
function getControlFactory(id) {
|
|
6028
6138
|
return registry.get(id) ?? null;
|
|
6029
6139
|
}
|
|
@@ -6187,7 +6297,15 @@ function uiPlugin(config = {}) {
|
|
|
6187
6297
|
}
|
|
6188
6298
|
hideTimeout = setTimeout(hideControls, hideDelay);
|
|
6189
6299
|
};
|
|
6300
|
+
let last_pointer_type = null;
|
|
6301
|
+
const handlePointerActivity = (event) => {
|
|
6302
|
+
last_pointer_type = event.pointerType;
|
|
6303
|
+
};
|
|
6190
6304
|
const handleInteraction = () => {
|
|
6305
|
+
if (last_pointer_type === "touch") {
|
|
6306
|
+
const gestures = api?.getPlugin("gestures");
|
|
6307
|
+
if (gestures?.ownsTapInteraction()) return;
|
|
6308
|
+
}
|
|
6191
6309
|
showControls();
|
|
6192
6310
|
};
|
|
6193
6311
|
const handleMouseLeave = () => {
|
|
@@ -6266,7 +6384,7 @@ function uiPlugin(config = {}) {
|
|
|
6266
6384
|
async init(pluginApi) {
|
|
6267
6385
|
api = pluginApi;
|
|
6268
6386
|
styleEl = document.createElement("style");
|
|
6269
|
-
styleEl.textContent = styles;
|
|
6387
|
+
styleEl.textContent = styles$1;
|
|
6270
6388
|
document.head.appendChild(styleEl);
|
|
6271
6389
|
if (config.theme) {
|
|
6272
6390
|
this.setTheme(config.theme);
|
|
@@ -6321,6 +6439,8 @@ function uiPlugin(config = {}) {
|
|
|
6321
6439
|
api.logger.debug(`Control "${id}" registered after init, rebuilding control bar`);
|
|
6322
6440
|
rebuildControlBar();
|
|
6323
6441
|
});
|
|
6442
|
+
container.addEventListener("pointerdown", handlePointerActivity, { passive: true });
|
|
6443
|
+
container.addEventListener("pointermove", handlePointerActivity, { passive: true });
|
|
6324
6444
|
container.addEventListener("mousemove", handleInteraction);
|
|
6325
6445
|
container.addEventListener("mouseenter", handleInteraction);
|
|
6326
6446
|
container.addEventListener("mouseleave", handleMouseLeave);
|
|
@@ -6358,6 +6478,8 @@ function uiPlugin(config = {}) {
|
|
|
6358
6478
|
recoveredUnsubscribe?.();
|
|
6359
6479
|
recoveredUnsubscribe = null;
|
|
6360
6480
|
if (api?.container) {
|
|
6481
|
+
api.container.removeEventListener("pointerdown", handlePointerActivity);
|
|
6482
|
+
api.container.removeEventListener("pointermove", handlePointerActivity);
|
|
6361
6483
|
api.container.removeEventListener("mousemove", handleInteraction);
|
|
6362
6484
|
api.container.removeEventListener("mouseenter", handleInteraction);
|
|
6363
6485
|
api.container.removeEventListener("mouseleave", handleMouseLeave);
|
|
@@ -6419,6 +6541,16 @@ function uiPlugin(config = {}) {
|
|
|
6419
6541
|
}
|
|
6420
6542
|
};
|
|
6421
6543
|
}
|
|
6544
|
+
const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
6545
|
+
__proto__: null,
|
|
6546
|
+
formatLiveTime,
|
|
6547
|
+
formatTime: formatTime$1,
|
|
6548
|
+
getControlFactory,
|
|
6549
|
+
icons,
|
|
6550
|
+
registerControl,
|
|
6551
|
+
styles: styles$1,
|
|
6552
|
+
uiPlugin
|
|
6553
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
6422
6554
|
var DEFAULT_THEME = {
|
|
6423
6555
|
primary: "#6366f1",
|
|
6424
6556
|
background: "#18181b",
|
|
@@ -6993,7 +7125,7 @@ function createAudioUIPlugin(config) {
|
|
|
6993
7125
|
if (layout === "mini") {
|
|
6994
7126
|
const wrapper = titleEl.parentElement;
|
|
6995
7127
|
if (wrapper && titleEl.scrollWidth > wrapper.clientWidth) {
|
|
6996
|
-
titleEl.textContent = `${title}
|
|
7128
|
+
titleEl.textContent = `${title} - ${title} - `;
|
|
6997
7129
|
titleEl.classList.add("scrolling");
|
|
6998
7130
|
} else {
|
|
6999
7131
|
titleEl.classList.remove("scrolling");
|
|
@@ -7732,6 +7864,272 @@ function createAnalyticsPlugin(config) {
|
|
|
7732
7864
|
}
|
|
7733
7865
|
};
|
|
7734
7866
|
}
|
|
7867
|
+
var PLAYLIST_ICONS = {
|
|
7868
|
+
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>',
|
|
7869
|
+
next: '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false"><path d="M16 6h2v12h-2V6zM6 6l8.5 6L6 18V6z"/></svg>',
|
|
7870
|
+
list: '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false"><path d="M3 6h11v2H3V6zm0 5h11v2H3v-2zm0 5h7v2H3v-2zm13-1.5V8l6 3.5-6 3.5z"/></svg>'
|
|
7871
|
+
};
|
|
7872
|
+
var PlaylistSkipButton = class {
|
|
7873
|
+
constructor(plugin, direction) {
|
|
7874
|
+
this.plugin = plugin;
|
|
7875
|
+
this.direction = direction;
|
|
7876
|
+
this.clickHandler = () => {
|
|
7877
|
+
if (this.direction === "next") {
|
|
7878
|
+
this.plugin.next();
|
|
7879
|
+
} else {
|
|
7880
|
+
this.plugin.previous();
|
|
7881
|
+
}
|
|
7882
|
+
};
|
|
7883
|
+
const label = direction === "next" ? "Next item" : "Previous item";
|
|
7884
|
+
this.el = document.createElement("button");
|
|
7885
|
+
this.el.type = "button";
|
|
7886
|
+
this.el.className = `sp-control sp-playlist-skip sp-playlist-skip--${direction}`;
|
|
7887
|
+
this.el.setAttribute("aria-label", label);
|
|
7888
|
+
this.el.setAttribute("title", label);
|
|
7889
|
+
this.el.innerHTML = PLAYLIST_ICONS[direction];
|
|
7890
|
+
this.el.addEventListener("click", this.clickHandler);
|
|
7891
|
+
}
|
|
7892
|
+
render() {
|
|
7893
|
+
return this.el;
|
|
7894
|
+
}
|
|
7895
|
+
update() {
|
|
7896
|
+
const state = this.plugin.getState();
|
|
7897
|
+
if (state.tracks.length <= 1) {
|
|
7898
|
+
this.el.style.display = "none";
|
|
7899
|
+
return;
|
|
7900
|
+
}
|
|
7901
|
+
this.el.style.display = "";
|
|
7902
|
+
const enabled = this.direction === "next" ? state.hasNext : state.hasPrevious;
|
|
7903
|
+
this.el.disabled = !enabled;
|
|
7904
|
+
}
|
|
7905
|
+
destroy() {
|
|
7906
|
+
this.el.removeEventListener("click", this.clickHandler);
|
|
7907
|
+
this.el.remove();
|
|
7908
|
+
}
|
|
7909
|
+
};
|
|
7910
|
+
var PlaylistPanel = class {
|
|
7911
|
+
constructor(plugin, options) {
|
|
7912
|
+
this.plugin = plugin;
|
|
7913
|
+
this.options = options;
|
|
7914
|
+
this.open = false;
|
|
7915
|
+
this.renderedIds = "";
|
|
7916
|
+
this.toggleHandler = (event) => {
|
|
7917
|
+
event.stopPropagation();
|
|
7918
|
+
this.setOpen(!this.open);
|
|
7919
|
+
};
|
|
7920
|
+
this.documentClickHandler = () => {
|
|
7921
|
+
if (this.open) this.setOpen(false);
|
|
7922
|
+
};
|
|
7923
|
+
this.keydownHandler = (event) => {
|
|
7924
|
+
if (event.key === "Escape" && this.open) {
|
|
7925
|
+
this.setOpen(false);
|
|
7926
|
+
this.button.focus();
|
|
7927
|
+
}
|
|
7928
|
+
};
|
|
7929
|
+
this.el = document.createElement("div");
|
|
7930
|
+
this.el.className = "sp-playlist";
|
|
7931
|
+
this.button = document.createElement("button");
|
|
7932
|
+
this.button.type = "button";
|
|
7933
|
+
this.button.className = "sp-control sp-playlist__button";
|
|
7934
|
+
this.button.setAttribute("aria-label", "Playlist");
|
|
7935
|
+
this.button.setAttribute("title", "Playlist");
|
|
7936
|
+
this.button.setAttribute("aria-haspopup", "true");
|
|
7937
|
+
this.button.setAttribute("aria-expanded", "false");
|
|
7938
|
+
this.button.innerHTML = PLAYLIST_ICONS.list;
|
|
7939
|
+
this.panel = document.createElement("div");
|
|
7940
|
+
this.panel.className = "sp-playlist__panel";
|
|
7941
|
+
this.panel.setAttribute("role", "menu");
|
|
7942
|
+
this.panel.hidden = true;
|
|
7943
|
+
this.el.appendChild(this.button);
|
|
7944
|
+
this.el.appendChild(this.panel);
|
|
7945
|
+
this.button.addEventListener("click", this.toggleHandler);
|
|
7946
|
+
document.addEventListener("click", this.documentClickHandler);
|
|
7947
|
+
document.addEventListener("keydown", this.keydownHandler);
|
|
7948
|
+
}
|
|
7949
|
+
render() {
|
|
7950
|
+
return this.el;
|
|
7951
|
+
}
|
|
7952
|
+
update() {
|
|
7953
|
+
const state = this.plugin.getState();
|
|
7954
|
+
if (state.tracks.length <= 1) {
|
|
7955
|
+
this.el.style.display = "none";
|
|
7956
|
+
return;
|
|
7957
|
+
}
|
|
7958
|
+
this.el.style.display = "";
|
|
7959
|
+
const signature = state.tracks.map((track) => track.id).join("|");
|
|
7960
|
+
if (signature !== this.renderedIds) {
|
|
7961
|
+
this.renderedIds = signature;
|
|
7962
|
+
this.renderPanel(state.tracks, state.currentIndex);
|
|
7963
|
+
return;
|
|
7964
|
+
}
|
|
7965
|
+
this.markActive(state.currentIndex);
|
|
7966
|
+
}
|
|
7967
|
+
destroy() {
|
|
7968
|
+
this.button.removeEventListener("click", this.toggleHandler);
|
|
7969
|
+
document.removeEventListener("click", this.documentClickHandler);
|
|
7970
|
+
document.removeEventListener("keydown", this.keydownHandler);
|
|
7971
|
+
this.el.remove();
|
|
7972
|
+
}
|
|
7973
|
+
setOpen(open) {
|
|
7974
|
+
this.open = open;
|
|
7975
|
+
this.panel.hidden = !open;
|
|
7976
|
+
this.button.setAttribute("aria-expanded", String(open));
|
|
7977
|
+
this.el.classList.toggle("sp-playlist--open", open);
|
|
7978
|
+
}
|
|
7979
|
+
markActive(currentIndex) {
|
|
7980
|
+
const items = this.panel.querySelectorAll(".sp-playlist__item");
|
|
7981
|
+
items.forEach((item, index2) => {
|
|
7982
|
+
item.classList.toggle("sp-playlist__item--active", index2 === currentIndex);
|
|
7983
|
+
item.setAttribute("aria-current", index2 === currentIndex ? "true" : "false");
|
|
7984
|
+
});
|
|
7985
|
+
}
|
|
7986
|
+
renderPanel(tracks, currentIndex) {
|
|
7987
|
+
this.panel.textContent = "";
|
|
7988
|
+
tracks.forEach((track, index2) => {
|
|
7989
|
+
const item = document.createElement("button");
|
|
7990
|
+
item.type = "button";
|
|
7991
|
+
item.className = "sp-playlist__item";
|
|
7992
|
+
item.setAttribute("role", "menuitem");
|
|
7993
|
+
item.setAttribute("aria-current", index2 === currentIndex ? "true" : "false");
|
|
7994
|
+
if (index2 === currentIndex) {
|
|
7995
|
+
item.classList.add("sp-playlist__item--active");
|
|
7996
|
+
}
|
|
7997
|
+
const position = document.createElement("span");
|
|
7998
|
+
position.className = "sp-playlist__position";
|
|
7999
|
+
position.textContent = String(index2 + 1);
|
|
8000
|
+
const text = document.createElement("span");
|
|
8001
|
+
text.className = "sp-playlist__text";
|
|
8002
|
+
const title = document.createElement("span");
|
|
8003
|
+
title.className = "sp-playlist__title";
|
|
8004
|
+
title.textContent = track.title ?? `Item ${index2 + 1}`;
|
|
8005
|
+
text.appendChild(title);
|
|
8006
|
+
if (track.artist) {
|
|
8007
|
+
const artist = document.createElement("span");
|
|
8008
|
+
artist.className = "sp-playlist__artist";
|
|
8009
|
+
artist.textContent = track.artist;
|
|
8010
|
+
text.appendChild(artist);
|
|
8011
|
+
}
|
|
8012
|
+
item.appendChild(position);
|
|
8013
|
+
item.appendChild(text);
|
|
8014
|
+
item.addEventListener("click", (event) => {
|
|
8015
|
+
event.stopPropagation();
|
|
8016
|
+
this.options.onSelect(index2);
|
|
8017
|
+
this.setOpen(false);
|
|
8018
|
+
});
|
|
8019
|
+
this.panel.appendChild(item);
|
|
8020
|
+
});
|
|
8021
|
+
}
|
|
8022
|
+
};
|
|
8023
|
+
var STYLE_ID = "sp-playlist-styles";
|
|
8024
|
+
var styles = `
|
|
8025
|
+
.sp-playlist-skip[disabled] {
|
|
8026
|
+
opacity: 0.4;
|
|
8027
|
+
cursor: default;
|
|
8028
|
+
}
|
|
8029
|
+
|
|
8030
|
+
.sp-playlist {
|
|
8031
|
+
position: relative;
|
|
8032
|
+
display: inline-flex;
|
|
8033
|
+
}
|
|
8034
|
+
|
|
8035
|
+
.sp-playlist__button svg,
|
|
8036
|
+
.sp-playlist-skip svg {
|
|
8037
|
+
width: 20px;
|
|
8038
|
+
height: 20px;
|
|
8039
|
+
}
|
|
8040
|
+
|
|
8041
|
+
.sp-playlist__panel {
|
|
8042
|
+
position: absolute;
|
|
8043
|
+
bottom: calc(100% + 8px);
|
|
8044
|
+
right: 0;
|
|
8045
|
+
z-index: 20;
|
|
8046
|
+
display: flex;
|
|
8047
|
+
flex-direction: column;
|
|
8048
|
+
min-width: 240px;
|
|
8049
|
+
max-width: 320px;
|
|
8050
|
+
max-height: 300px;
|
|
8051
|
+
overflow-y: auto;
|
|
8052
|
+
padding: 4px;
|
|
8053
|
+
border-radius: 8px;
|
|
8054
|
+
background: rgba(20, 20, 20, 0.96);
|
|
8055
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
|
8056
|
+
}
|
|
8057
|
+
|
|
8058
|
+
.sp-playlist__panel[hidden] {
|
|
8059
|
+
display: none;
|
|
8060
|
+
}
|
|
8061
|
+
|
|
8062
|
+
.sp-playlist__item {
|
|
8063
|
+
display: flex;
|
|
8064
|
+
gap: 10px;
|
|
8065
|
+
align-items: flex-start;
|
|
8066
|
+
width: 100%;
|
|
8067
|
+
padding: 8px 10px;
|
|
8068
|
+
border: 0;
|
|
8069
|
+
border-radius: 6px;
|
|
8070
|
+
background: transparent;
|
|
8071
|
+
color: #fff;
|
|
8072
|
+
font: inherit;
|
|
8073
|
+
text-align: left;
|
|
8074
|
+
cursor: pointer;
|
|
8075
|
+
}
|
|
8076
|
+
|
|
8077
|
+
.sp-playlist__item:hover,
|
|
8078
|
+
.sp-playlist__item:focus-visible {
|
|
8079
|
+
background: rgba(255, 255, 255, 0.12);
|
|
8080
|
+
}
|
|
8081
|
+
|
|
8082
|
+
.sp-playlist__item--active {
|
|
8083
|
+
background: rgba(255, 255, 255, 0.08);
|
|
8084
|
+
}
|
|
8085
|
+
|
|
8086
|
+
.sp-playlist__item--active .sp-playlist__title {
|
|
8087
|
+
font-weight: 600;
|
|
8088
|
+
}
|
|
8089
|
+
|
|
8090
|
+
.sp-playlist__position {
|
|
8091
|
+
flex: 0 0 auto;
|
|
8092
|
+
min-width: 18px;
|
|
8093
|
+
color: rgba(255, 255, 255, 0.7);
|
|
8094
|
+
font-variant-numeric: tabular-nums;
|
|
8095
|
+
font-size: 12px;
|
|
8096
|
+
line-height: 18px;
|
|
8097
|
+
}
|
|
8098
|
+
|
|
8099
|
+
.sp-playlist__text {
|
|
8100
|
+
display: flex;
|
|
8101
|
+
flex-direction: column;
|
|
8102
|
+
min-width: 0;
|
|
8103
|
+
}
|
|
8104
|
+
|
|
8105
|
+
.sp-playlist__title {
|
|
8106
|
+
font-size: 13px;
|
|
8107
|
+
line-height: 18px;
|
|
8108
|
+
}
|
|
8109
|
+
|
|
8110
|
+
.sp-playlist__artist {
|
|
8111
|
+
color: rgba(255, 255, 255, 0.6);
|
|
8112
|
+
font-size: 11px;
|
|
8113
|
+
line-height: 16px;
|
|
8114
|
+
}
|
|
8115
|
+
|
|
8116
|
+
@media (max-width: 480px) {
|
|
8117
|
+
.sp-playlist__panel {
|
|
8118
|
+
min-width: 200px;
|
|
8119
|
+
max-width: 76vw;
|
|
8120
|
+
}
|
|
8121
|
+
}
|
|
8122
|
+
`;
|
|
8123
|
+
function injectStyles() {
|
|
8124
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) {
|
|
8125
|
+
return null;
|
|
8126
|
+
}
|
|
8127
|
+
const el = document.createElement("style");
|
|
8128
|
+
el.id = STYLE_ID;
|
|
8129
|
+
el.textContent = styles;
|
|
8130
|
+
document.head.appendChild(el);
|
|
8131
|
+
return el;
|
|
8132
|
+
}
|
|
7735
8133
|
var DEFAULT_CONFIG$1 = {
|
|
7736
8134
|
autoAdvance: true,
|
|
7737
8135
|
preloadNext: true,
|
|
@@ -7871,14 +8269,14 @@ function createPlaylistPlugin(config) {
|
|
|
7871
8269
|
api?.emit("playlist:change", { track, index: currentIndex });
|
|
7872
8270
|
persistPlaylist();
|
|
7873
8271
|
};
|
|
7874
|
-
const setCurrentTrack = (
|
|
7875
|
-
if (
|
|
7876
|
-
api?.logger.warn("Invalid track index", { index });
|
|
8272
|
+
const setCurrentTrack = (index2) => {
|
|
8273
|
+
if (index2 < 0 || index2 >= tracks.length) {
|
|
8274
|
+
api?.logger.warn("Invalid track index", { index: index2 });
|
|
7877
8275
|
return;
|
|
7878
8276
|
}
|
|
7879
|
-
const track = tracks[
|
|
7880
|
-
currentIndex =
|
|
7881
|
-
api?.logger.info("Track changed", { index, title: track.title, src: track.src });
|
|
8277
|
+
const track = tracks[index2];
|
|
8278
|
+
currentIndex = index2;
|
|
8279
|
+
api?.logger.info("Track changed", { index: index2, title: track.title, src: track.src });
|
|
7882
8280
|
api?.setState("title", track.title || "");
|
|
7883
8281
|
if (track.artwork) {
|
|
7884
8282
|
api?.setState("poster", track.artwork);
|
|
@@ -7921,8 +8319,42 @@ function createPlaylistPlugin(config) {
|
|
|
7921
8319
|
api?.emit("playlist:ended", void 0);
|
|
7922
8320
|
}
|
|
7923
8321
|
});
|
|
8322
|
+
injectStyles();
|
|
8323
|
+
void Promise.resolve().then(() => index).then(({ registerControl: registerControl2 }) => {
|
|
8324
|
+
const self = plugin;
|
|
8325
|
+
registerControl2(
|
|
8326
|
+
"playlist-previous",
|
|
8327
|
+
() => new PlaylistSkipButton(self, "previous")
|
|
8328
|
+
);
|
|
8329
|
+
registerControl2("playlist-next", () => new PlaylistSkipButton(self, "next"));
|
|
8330
|
+
registerControl2(
|
|
8331
|
+
"playlist",
|
|
8332
|
+
() => new PlaylistPanel(self, {
|
|
8333
|
+
onSelect: (index2) => self.play(index2)
|
|
8334
|
+
})
|
|
8335
|
+
);
|
|
8336
|
+
}).catch(() => {
|
|
8337
|
+
api?.logger.debug("@scarlett-player/ui not present, playlist controls not registered");
|
|
8338
|
+
});
|
|
8339
|
+
const onKeyDown = (event) => {
|
|
8340
|
+
if (!api || !api.container.contains(document.activeElement)) return;
|
|
8341
|
+
const activeEl = document.activeElement;
|
|
8342
|
+
if (activeEl instanceof HTMLInputElement || activeEl instanceof HTMLTextAreaElement || activeEl instanceof HTMLSelectElement || activeEl?.isContentEditable) {
|
|
8343
|
+
return;
|
|
8344
|
+
}
|
|
8345
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
8346
|
+
if (event.key === "n" || event.key === "N") {
|
|
8347
|
+
event.preventDefault();
|
|
8348
|
+
plugin.next();
|
|
8349
|
+
} else if (event.key === "p" || event.key === "P") {
|
|
8350
|
+
event.preventDefault();
|
|
8351
|
+
plugin.previous();
|
|
8352
|
+
}
|
|
8353
|
+
};
|
|
8354
|
+
document.addEventListener("keydown", onKeyDown);
|
|
7924
8355
|
api.onDestroy(() => {
|
|
7925
8356
|
unsubEnded();
|
|
8357
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
7926
8358
|
if (advanceTimeout) {
|
|
7927
8359
|
clearTimeout(advanceTimeout);
|
|
7928
8360
|
advanceTimeout = null;
|
|
@@ -7939,10 +8371,10 @@ function createPlaylistPlugin(config) {
|
|
|
7939
8371
|
const newTracks = Array.isArray(trackOrTracks) ? trackOrTracks : [trackOrTracks];
|
|
7940
8372
|
newTracks.forEach((track) => {
|
|
7941
8373
|
const normalizedTrack = { ...track, id: track.id || generateId() };
|
|
7942
|
-
const
|
|
8374
|
+
const index2 = tracks.length;
|
|
7943
8375
|
tracks.push(normalizedTrack);
|
|
7944
|
-
api?.emit("playlist:add", { track: normalizedTrack, index });
|
|
7945
|
-
api?.logger.debug("Track added", { title: normalizedTrack.title, index });
|
|
8376
|
+
api?.emit("playlist:add", { track: normalizedTrack, index: index2 });
|
|
8377
|
+
api?.logger.debug("Track added", { title: normalizedTrack.title, index: index2 });
|
|
7946
8378
|
});
|
|
7947
8379
|
if (shuffle) {
|
|
7948
8380
|
const startIndex = tracks.length - newTracks.length;
|
|
@@ -7953,9 +8385,9 @@ function createPlaylistPlugin(config) {
|
|
|
7953
8385
|
}
|
|
7954
8386
|
persistPlaylist();
|
|
7955
8387
|
},
|
|
7956
|
-
insert(
|
|
8388
|
+
insert(index2, track) {
|
|
7957
8389
|
const normalizedTrack = { ...track, id: track.id || generateId() };
|
|
7958
|
-
const clampedIndex = Math.max(0, Math.min(
|
|
8390
|
+
const clampedIndex = Math.max(0, Math.min(index2, tracks.length));
|
|
7959
8391
|
tracks.splice(clampedIndex, 0, normalizedTrack);
|
|
7960
8392
|
if (currentIndex >= clampedIndex) {
|
|
7961
8393
|
currentIndex++;
|
|
@@ -7969,33 +8401,33 @@ function createPlaylistPlugin(config) {
|
|
|
7969
8401
|
persistPlaylist();
|
|
7970
8402
|
},
|
|
7971
8403
|
remove(idOrIndex) {
|
|
7972
|
-
let
|
|
8404
|
+
let index2;
|
|
7973
8405
|
if (typeof idOrIndex === "string") {
|
|
7974
|
-
|
|
7975
|
-
if (
|
|
8406
|
+
index2 = tracks.findIndex((t) => t.id === idOrIndex);
|
|
8407
|
+
if (index2 === -1) {
|
|
7976
8408
|
api?.logger.warn("Track not found", { id: idOrIndex });
|
|
7977
8409
|
return;
|
|
7978
8410
|
}
|
|
7979
8411
|
} else {
|
|
7980
|
-
|
|
8412
|
+
index2 = idOrIndex;
|
|
7981
8413
|
}
|
|
7982
|
-
if (
|
|
7983
|
-
api?.logger.warn("Invalid track index", { index });
|
|
8414
|
+
if (index2 < 0 || index2 >= tracks.length) {
|
|
8415
|
+
api?.logger.warn("Invalid track index", { index: index2 });
|
|
7984
8416
|
return;
|
|
7985
8417
|
}
|
|
7986
|
-
const [removedTrack] = tracks.splice(
|
|
7987
|
-
if (
|
|
8418
|
+
const [removedTrack] = tracks.splice(index2, 1);
|
|
8419
|
+
if (index2 < currentIndex) {
|
|
7988
8420
|
currentIndex--;
|
|
7989
|
-
} else if (
|
|
8421
|
+
} else if (index2 === currentIndex) {
|
|
7990
8422
|
if (currentIndex >= tracks.length) {
|
|
7991
8423
|
currentIndex = tracks.length - 1;
|
|
7992
8424
|
}
|
|
7993
8425
|
emitChange();
|
|
7994
8426
|
}
|
|
7995
8427
|
if (shuffle) {
|
|
7996
|
-
shuffleOrder = shuffleOrder.filter((i) => i !==
|
|
8428
|
+
shuffleOrder = shuffleOrder.filter((i) => i !== index2).map((i) => i > index2 ? i - 1 : i);
|
|
7997
8429
|
}
|
|
7998
|
-
api?.emit("playlist:remove", { track: removedTrack, index });
|
|
8430
|
+
api?.emit("playlist:remove", { track: removedTrack, index: index2 });
|
|
7999
8431
|
persistPlaylist();
|
|
8000
8432
|
},
|
|
8001
8433
|
clear() {
|
|
@@ -8006,23 +8438,23 @@ function createPlaylistPlugin(config) {
|
|
|
8006
8438
|
emitChange();
|
|
8007
8439
|
},
|
|
8008
8440
|
play(idOrIndex) {
|
|
8009
|
-
let
|
|
8441
|
+
let index2;
|
|
8010
8442
|
if (idOrIndex === void 0) {
|
|
8011
|
-
|
|
8443
|
+
index2 = currentIndex >= 0 ? currentIndex : shuffle ? getActualIndex(0) : 0;
|
|
8012
8444
|
} else if (typeof idOrIndex === "string") {
|
|
8013
|
-
|
|
8014
|
-
if (
|
|
8445
|
+
index2 = tracks.findIndex((t) => t.id === idOrIndex);
|
|
8446
|
+
if (index2 === -1) {
|
|
8015
8447
|
api?.logger.warn("Track not found", { id: idOrIndex });
|
|
8016
8448
|
return;
|
|
8017
8449
|
}
|
|
8018
8450
|
} else {
|
|
8019
|
-
|
|
8451
|
+
index2 = idOrIndex;
|
|
8020
8452
|
}
|
|
8021
8453
|
if (tracks.length === 0) {
|
|
8022
8454
|
api?.logger.warn("Playlist is empty");
|
|
8023
8455
|
return;
|
|
8024
8456
|
}
|
|
8025
|
-
setCurrentTrack(
|
|
8457
|
+
setCurrentTrack(index2);
|
|
8026
8458
|
},
|
|
8027
8459
|
next() {
|
|
8028
8460
|
const nextIdx = getNextIndex();
|
|
@@ -8965,8 +9397,8 @@ async function createEmbedPlayer(container, config, pluginCreators2, availableTy
|
|
|
8965
9397
|
const plugins = [pluginCreators2.hls()];
|
|
8966
9398
|
if (pluginCreators2.playlist && config.playlist?.length) {
|
|
8967
9399
|
plugins.push(pluginCreators2.playlist({
|
|
8968
|
-
items: config.playlist.map((item,
|
|
8969
|
-
id: `item-${
|
|
9400
|
+
items: config.playlist.map((item, index2) => ({
|
|
9401
|
+
id: `item-${index2}`,
|
|
8970
9402
|
src: item.src,
|
|
8971
9403
|
title: item.title,
|
|
8972
9404
|
artist: item.artist,
|