@scarlett-player/ui 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 +272 -86
- package/dist/index.d.cts +56 -24
- package/dist/index.d.ts +56 -24
- package/dist/index.js +260 -70
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -23,8 +23,8 @@ __export(index_exports, {
|
|
|
23
23
|
DEFAULT_PRIORITY: () => DEFAULT_PRIORITY,
|
|
24
24
|
assertFitLayout: () => assertFitLayout,
|
|
25
25
|
default: () => index_default,
|
|
26
|
-
formatLiveTime: () => formatLiveTime,
|
|
27
|
-
formatTime: () => formatTime,
|
|
26
|
+
formatLiveTime: () => import_core2.formatLiveTime,
|
|
27
|
+
formatTime: () => import_core2.formatTime,
|
|
28
28
|
getControlFactory: () => getControlFactory,
|
|
29
29
|
icons: () => icons,
|
|
30
30
|
planFit: () => planFit,
|
|
@@ -33,10 +33,11 @@ __export(index_exports, {
|
|
|
33
33
|
resolveFitItems: () => resolveFitItems,
|
|
34
34
|
styles: () => styles,
|
|
35
35
|
uiPlugin: () => uiPlugin,
|
|
36
|
-
unregisterControl: () => unregisterControl
|
|
36
|
+
unregisterControl: () => unregisterControl,
|
|
37
|
+
unregisterControlsFor: () => unregisterControlsFor
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(index_exports);
|
|
39
|
-
var
|
|
40
|
+
var import_core4 = require("@scarlett-player/core");
|
|
40
41
|
|
|
41
42
|
// src/fit.ts
|
|
42
43
|
var UNKNOWN_RANK = 3;
|
|
@@ -123,6 +124,10 @@ var styles = `
|
|
|
123
124
|
============================================ */
|
|
124
125
|
.sp-container {
|
|
125
126
|
position: relative;
|
|
127
|
+
/* Own stacking context: the control bar, menus and overlays all carry
|
|
128
|
+
z-index, and without this they compete with the host page's own layers
|
|
129
|
+
instead of staying inside the player. */
|
|
130
|
+
isolation: isolate;
|
|
126
131
|
width: 100%;
|
|
127
132
|
height: 100%;
|
|
128
133
|
background: #000;
|
|
@@ -244,11 +249,13 @@ var styles = `
|
|
|
244
249
|
cursor: pointer;
|
|
245
250
|
z-index: 10;
|
|
246
251
|
opacity: 0;
|
|
252
|
+
pointer-events: none;
|
|
247
253
|
transition: opacity 0.25s ease;
|
|
248
254
|
}
|
|
249
255
|
|
|
250
256
|
.sp-progress-wrapper--visible {
|
|
251
257
|
opacity: 1;
|
|
258
|
+
pointer-events: auto;
|
|
252
259
|
}
|
|
253
260
|
|
|
254
261
|
/* Touch: a 20px wrapper is not a 20px target. The control bar is a later
|
|
@@ -1197,24 +1204,30 @@ var styles = `
|
|
|
1197
1204
|
|
|
1198
1205
|
/* ============================================
|
|
1199
1206
|
CSS Custom Properties (Theming)
|
|
1207
|
+
============================================
|
|
1208
|
+
|
|
1209
|
+
Token defaults live at the use sites as var() fallbacks, not in a :root
|
|
1210
|
+
block. A :root declaration wrote --sp-accent, --sp-color, --sp-bg,
|
|
1211
|
+
--sp-control-height and --sp-icon-size into the HOST document, so mounting a
|
|
1212
|
+
player changed variables the page may own. A .sp-container declaration would
|
|
1213
|
+
be no better: the container rule wins for every descendant, so it would
|
|
1214
|
+
override a host that themes the player from its own :root. A var() fallback
|
|
1215
|
+
does neither - it applies only where nothing else defines the token.
|
|
1216
|
+
|
|
1217
|
+
setTheme() writes the same names onto the player's container, which still
|
|
1218
|
+
wins over the fallbacks.
|
|
1200
1219
|
============================================ */
|
|
1201
|
-
:root {
|
|
1202
|
-
--sp-accent: #e50914;
|
|
1203
|
-
--sp-color: #fff;
|
|
1204
|
-
--sp-bg: rgba(0, 0, 0, 0.8);
|
|
1205
|
-
--sp-control-height: 48px;
|
|
1206
|
-
--sp-icon-size: 24px;
|
|
1207
|
-
}
|
|
1208
1220
|
`;
|
|
1209
1221
|
|
|
1210
1222
|
// src/icons.ts
|
|
1223
|
+
var import_core = require("@scarlett-player/core");
|
|
1211
1224
|
var icons = {
|
|
1212
|
-
play: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="
|
|
1225
|
+
play: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="${import_core.SHARED_ICON_PATHS.play}"/></svg>`,
|
|
1213
1226
|
pause: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/></svg>`,
|
|
1214
1227
|
replay: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/></svg>`,
|
|
1215
|
-
volumeHigh: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="
|
|
1228
|
+
volumeHigh: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="${import_core.SHARED_ICON_PATHS.volumeHigh}"/></svg>`,
|
|
1216
1229
|
volumeLow: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/></svg>`,
|
|
1217
|
-
volumeMute: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="
|
|
1230
|
+
volumeMute: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="${import_core.SHARED_ICON_PATHS.volumeMuted}"/></svg>`,
|
|
1218
1231
|
fullscreen: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>`,
|
|
1219
1232
|
exitFullscreen: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/></svg>`,
|
|
1220
1233
|
pip: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z"/></svg>`,
|
|
@@ -1287,29 +1300,7 @@ function setAttr(el, name, value) {
|
|
|
1287
1300
|
}
|
|
1288
1301
|
|
|
1289
1302
|
// src/utils/format.ts
|
|
1290
|
-
|
|
1291
|
-
if (!isFinite(seconds) || isNaN(seconds)) {
|
|
1292
|
-
return "0:00";
|
|
1293
|
-
}
|
|
1294
|
-
const absSeconds = Math.abs(seconds);
|
|
1295
|
-
const h = Math.floor(absSeconds / 3600);
|
|
1296
|
-
const m = Math.floor(absSeconds % 3600 / 60);
|
|
1297
|
-
const s = Math.floor(absSeconds % 60);
|
|
1298
|
-
const sign = seconds < 0 ? "-" : "";
|
|
1299
|
-
if (h > 0) {
|
|
1300
|
-
return `${sign}${h}:${pad(m)}:${pad(s)}`;
|
|
1301
|
-
}
|
|
1302
|
-
return `${sign}${m}:${pad(s)}`;
|
|
1303
|
-
}
|
|
1304
|
-
function pad(n) {
|
|
1305
|
-
return n < 10 ? `0${n}` : `${n}`;
|
|
1306
|
-
}
|
|
1307
|
-
function formatLiveTime(behindLive) {
|
|
1308
|
-
if (behindLive <= 0) {
|
|
1309
|
-
return "LIVE";
|
|
1310
|
-
}
|
|
1311
|
-
return `-${formatTime(behindLive)}`;
|
|
1312
|
-
}
|
|
1303
|
+
var import_core2 = require("@scarlett-player/core");
|
|
1313
1304
|
|
|
1314
1305
|
// src/controls/PlayButton.ts
|
|
1315
1306
|
var PlayButton = class {
|
|
@@ -1371,7 +1362,7 @@ var BigPlayButton = class {
|
|
|
1371
1362
|
* must not sit on top of it, and `error` state alone does not say (a
|
|
1372
1363
|
* dismissed overlay leaves the error behind)
|
|
1373
1364
|
*/
|
|
1374
|
-
constructor(api, isOverlayVisible
|
|
1365
|
+
constructor(api, isOverlayVisible) {
|
|
1375
1366
|
/**
|
|
1376
1367
|
* Latched on the first `playing`.
|
|
1377
1368
|
*
|
|
@@ -1385,7 +1376,8 @@ var BigPlayButton = class {
|
|
|
1385
1376
|
this.start();
|
|
1386
1377
|
};
|
|
1387
1378
|
this.api = api;
|
|
1388
|
-
this.
|
|
1379
|
+
this.hasOverlayProbe = typeof isOverlayVisible === "function";
|
|
1380
|
+
this.isOverlayVisible = isOverlayVisible ?? (() => false);
|
|
1389
1381
|
const btn = document.createElement("button");
|
|
1390
1382
|
btn.className = "sp-big-play";
|
|
1391
1383
|
btn.setAttribute("type", "button");
|
|
@@ -1414,7 +1406,7 @@ var BigPlayButton = class {
|
|
|
1414
1406
|
this.hasStarted = true;
|
|
1415
1407
|
}
|
|
1416
1408
|
let visible;
|
|
1417
|
-
if (
|
|
1409
|
+
if (this.hasOverlayProbe ? this.isOverlayVisible() : Boolean(error)) {
|
|
1418
1410
|
visible = false;
|
|
1419
1411
|
} else if (playbackState === "loading") {
|
|
1420
1412
|
visible = false;
|
|
@@ -1613,7 +1605,7 @@ var ProgressBar = class {
|
|
|
1613
1605
|
video.addEventListener("seeked", resumePlayback);
|
|
1614
1606
|
}
|
|
1615
1607
|
}
|
|
1616
|
-
this.tooltip.style.opacity = "
|
|
1608
|
+
this.tooltip.style.opacity = "";
|
|
1617
1609
|
this.thumbnailPreview.hide();
|
|
1618
1610
|
}
|
|
1619
1611
|
};
|
|
@@ -1622,7 +1614,7 @@ var ProgressBar = class {
|
|
|
1622
1614
|
};
|
|
1623
1615
|
this.onMouseLeave = () => {
|
|
1624
1616
|
if (!this.isDragging) {
|
|
1625
|
-
this.tooltip.style.opacity = "
|
|
1617
|
+
this.tooltip.style.opacity = "";
|
|
1626
1618
|
this.thumbnailPreview.hide();
|
|
1627
1619
|
}
|
|
1628
1620
|
};
|
|
@@ -1766,7 +1758,7 @@ var ProgressBar = class {
|
|
|
1766
1758
|
}
|
|
1767
1759
|
this.el.setAttribute("aria-valuemax", String(Math.floor(duration)));
|
|
1768
1760
|
this.el.setAttribute("aria-valuenow", String(Math.floor(currentTime)));
|
|
1769
|
-
this.el.setAttribute("aria-valuetext", formatTime(currentTime));
|
|
1761
|
+
this.el.setAttribute("aria-valuetext", (0, import_core2.formatTime)(currentTime));
|
|
1770
1762
|
}
|
|
1771
1763
|
}
|
|
1772
1764
|
/**
|
|
@@ -1840,22 +1832,24 @@ var ProgressBar = class {
|
|
|
1840
1832
|
const seekableRange = this.api.getState("seekableRange");
|
|
1841
1833
|
if (live && seekableRange) {
|
|
1842
1834
|
const rangeLength = seekableRange.end - seekableRange.start;
|
|
1843
|
-
|
|
1835
|
+
const time2 = seekableRange.start + percent * rangeLength;
|
|
1836
|
+
return Number.isFinite(time2) ? time2 : null;
|
|
1844
1837
|
}
|
|
1845
1838
|
const duration = this.api.getState("duration") || 0;
|
|
1846
|
-
|
|
1839
|
+
const time = percent * duration;
|
|
1840
|
+
return Number.isFinite(time) ? time : null;
|
|
1847
1841
|
}
|
|
1848
1842
|
updateTooltip(clientX) {
|
|
1849
1843
|
const rect = this.el.getBoundingClientRect();
|
|
1850
1844
|
const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
1851
|
-
const time = this.getTimeFromPosition(clientX);
|
|
1845
|
+
const time = this.getTimeFromPosition(clientX) ?? 0;
|
|
1852
1846
|
const live = this.api.getState("live");
|
|
1853
1847
|
const seekableRange = this.api.getState("seekableRange");
|
|
1854
1848
|
if (live && seekableRange) {
|
|
1855
1849
|
const behindLive = seekableRange.end - time;
|
|
1856
|
-
this.tooltip.textContent = formatLiveTime(behindLive);
|
|
1850
|
+
this.tooltip.textContent = (0, import_core2.formatLiveTime)(behindLive);
|
|
1857
1851
|
} else {
|
|
1858
|
-
this.tooltip.textContent = formatTime(time);
|
|
1852
|
+
this.tooltip.textContent = (0, import_core2.formatTime)(time);
|
|
1859
1853
|
}
|
|
1860
1854
|
const chapterLabel = this.chapterLabelAt(time);
|
|
1861
1855
|
if (chapterLabel) {
|
|
@@ -1883,13 +1877,16 @@ var ProgressBar = class {
|
|
|
1883
1877
|
}
|
|
1884
1878
|
this.lastSeekTime = now;
|
|
1885
1879
|
const time = this.getTimeFromPosition(clientX);
|
|
1886
|
-
|
|
1880
|
+
if (time !== null && Number.isFinite(time)) {
|
|
1881
|
+
video.currentTime = time;
|
|
1882
|
+
}
|
|
1887
1883
|
}
|
|
1888
1884
|
destroy() {
|
|
1889
1885
|
this.wrapper.removeEventListener("mousedown", this.onMouseDown);
|
|
1890
1886
|
this.wrapper.removeEventListener("mousemove", this.onMouseMove);
|
|
1891
1887
|
this.wrapper.removeEventListener("mouseleave", this.onMouseLeave);
|
|
1892
1888
|
this.wrapper.removeEventListener("touchstart", this.onTouchStart);
|
|
1889
|
+
this.el.removeEventListener("keydown", this.onKeyDown);
|
|
1893
1890
|
document.removeEventListener("mousemove", this.onDocMouseMove);
|
|
1894
1891
|
document.removeEventListener("mouseup", this.onMouseUp);
|
|
1895
1892
|
document.removeEventListener("touchmove", this.onDocTouchMove);
|
|
@@ -1918,12 +1915,12 @@ var TimeDisplay = class {
|
|
|
1918
1915
|
const seekableRange = this.api.getState("seekableRange");
|
|
1919
1916
|
if (seekableRange) {
|
|
1920
1917
|
const behindLive = seekableRange.end - currentTime;
|
|
1921
|
-
this.el.textContent = formatLiveTime(behindLive);
|
|
1918
|
+
this.el.textContent = (0, import_core2.formatLiveTime)(behindLive);
|
|
1922
1919
|
} else {
|
|
1923
|
-
this.el.textContent = formatLiveTime(0);
|
|
1920
|
+
this.el.textContent = (0, import_core2.formatLiveTime)(0);
|
|
1924
1921
|
}
|
|
1925
1922
|
} else {
|
|
1926
|
-
this.el.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
|
|
1923
|
+
this.el.textContent = `${(0, import_core2.formatTime)(currentTime)} / ${(0, import_core2.formatTime)(duration)}`;
|
|
1927
1924
|
}
|
|
1928
1925
|
}
|
|
1929
1926
|
destroy() {
|
|
@@ -2114,11 +2111,9 @@ var LiveIndicator = class {
|
|
|
2114
2111
|
}
|
|
2115
2112
|
}
|
|
2116
2113
|
seekToLive() {
|
|
2117
|
-
const video = getVideo(this.api.container);
|
|
2118
|
-
if (!video) return;
|
|
2119
2114
|
const seekableRange = this.api.getState("seekableRange");
|
|
2120
2115
|
if (seekableRange) {
|
|
2121
|
-
|
|
2116
|
+
this.api.emit("playback:seeking", { time: seekableRange.end });
|
|
2122
2117
|
}
|
|
2123
2118
|
}
|
|
2124
2119
|
destroy() {
|
|
@@ -2164,6 +2159,9 @@ var QualityMenu = class {
|
|
|
2164
2159
|
const qualities = this.api.getState("qualities") || [];
|
|
2165
2160
|
const currentQuality = this.api.getState("currentQuality");
|
|
2166
2161
|
this.el.style.display = qualities.length > 0 ? "" : "none";
|
|
2162
|
+
if (qualities.length === 0 && this.isOpen) {
|
|
2163
|
+
this.close();
|
|
2164
|
+
}
|
|
2167
2165
|
this.btnLabel.textContent = currentQuality?.label || "Auto";
|
|
2168
2166
|
const qualitiesJson = JSON.stringify(qualities.map((q) => q.id));
|
|
2169
2167
|
const currentId = currentQuality?.id || "auto";
|
|
@@ -2228,6 +2226,16 @@ var QualityMenu = class {
|
|
|
2228
2226
|
this.menu.classList.remove("sp-quality-menu--open");
|
|
2229
2227
|
this.btn.setAttribute("aria-expanded", "false");
|
|
2230
2228
|
}
|
|
2229
|
+
/**
|
|
2230
|
+
* Whether the dropdown is open.
|
|
2231
|
+
*
|
|
2232
|
+
* Read by the UI plugin so the control bar's auto-hide waits for it.
|
|
2233
|
+
*
|
|
2234
|
+
* @returns True while the menu is showing
|
|
2235
|
+
*/
|
|
2236
|
+
isMenuOpen() {
|
|
2237
|
+
return this.isOpen;
|
|
2238
|
+
}
|
|
2231
2239
|
destroy() {
|
|
2232
2240
|
document.removeEventListener("click", this.closeHandler);
|
|
2233
2241
|
this.el.remove();
|
|
@@ -2397,7 +2405,7 @@ var PipButton = class {
|
|
|
2397
2405
|
};
|
|
2398
2406
|
|
|
2399
2407
|
// src/controls/FullscreenButton.ts
|
|
2400
|
-
var
|
|
2408
|
+
var import_core3 = require("@scarlett-player/core");
|
|
2401
2409
|
var FullscreenButton = class {
|
|
2402
2410
|
constructor(api) {
|
|
2403
2411
|
this.clickHandler = () => {
|
|
@@ -2432,10 +2440,10 @@ var FullscreenButton = class {
|
|
|
2432
2440
|
async toggle() {
|
|
2433
2441
|
const container = this.api.container;
|
|
2434
2442
|
try {
|
|
2435
|
-
if ((0,
|
|
2436
|
-
await (0,
|
|
2443
|
+
if ((0, import_core3.isFullscreen)(container)) {
|
|
2444
|
+
await (0, import_core3.exitFullscreen)(container);
|
|
2437
2445
|
} else {
|
|
2438
|
-
await (0,
|
|
2446
|
+
await (0, import_core3.enterFullscreen)(container);
|
|
2439
2447
|
}
|
|
2440
2448
|
} catch {
|
|
2441
2449
|
}
|
|
@@ -2652,7 +2660,7 @@ var SettingsMenu = class {
|
|
|
2652
2660
|
this.el.appendChild(this.panel);
|
|
2653
2661
|
this.closeHandler = (e) => {
|
|
2654
2662
|
if (!this.el.contains(e.target)) {
|
|
2655
|
-
this.close();
|
|
2663
|
+
this.close(false);
|
|
2656
2664
|
}
|
|
2657
2665
|
};
|
|
2658
2666
|
document.addEventListener("click", this.closeHandler);
|
|
@@ -2665,7 +2673,6 @@ var SettingsMenu = class {
|
|
|
2665
2673
|
this.showPanel("main");
|
|
2666
2674
|
} else {
|
|
2667
2675
|
this.close();
|
|
2668
|
-
this.btn.focus();
|
|
2669
2676
|
}
|
|
2670
2677
|
return;
|
|
2671
2678
|
}
|
|
@@ -2702,6 +2709,8 @@ var SettingsMenu = class {
|
|
|
2702
2709
|
this.updateSpeedActiveStates();
|
|
2703
2710
|
} else if (this.currentPanel === "captions") {
|
|
2704
2711
|
this.updateCaptionsActiveStates();
|
|
2712
|
+
} else if (this.currentPanel === "audio") {
|
|
2713
|
+
this.updateAudioActiveStates();
|
|
2705
2714
|
}
|
|
2706
2715
|
}
|
|
2707
2716
|
}
|
|
@@ -2716,11 +2725,25 @@ var SettingsMenu = class {
|
|
|
2716
2725
|
this.btn.setAttribute("aria-expanded", "true");
|
|
2717
2726
|
this.focusFirstItem();
|
|
2718
2727
|
}
|
|
2719
|
-
|
|
2728
|
+
/**
|
|
2729
|
+
* Close the panel.
|
|
2730
|
+
*
|
|
2731
|
+
* Choosing an item tears down the row that had focus, which drops focus to
|
|
2732
|
+
* `<body>` and strands keyboard viewers outside the player entirely. Hand it
|
|
2733
|
+
* back to the button that opened the menu instead.
|
|
2734
|
+
*
|
|
2735
|
+
* @param restoreFocus - Return focus to the gear button. The outside-click
|
|
2736
|
+
* handler passes false, so a click elsewhere is not pulled back into the bar.
|
|
2737
|
+
*/
|
|
2738
|
+
close(restoreFocus = true) {
|
|
2739
|
+
const wasOpen = this.isOpen;
|
|
2720
2740
|
this.isOpen = false;
|
|
2721
2741
|
this.currentPanel = "main";
|
|
2722
2742
|
this.panel.classList.remove("sp-settings-panel--open");
|
|
2723
2743
|
this.btn.setAttribute("aria-expanded", "false");
|
|
2744
|
+
if (wasOpen && restoreFocus) {
|
|
2745
|
+
this.btn.focus();
|
|
2746
|
+
}
|
|
2724
2747
|
}
|
|
2725
2748
|
showPanel(panel) {
|
|
2726
2749
|
this.currentPanel = panel;
|
|
@@ -2737,6 +2760,9 @@ var SettingsMenu = class {
|
|
|
2737
2760
|
case "captions":
|
|
2738
2761
|
this.renderCaptionsPanel();
|
|
2739
2762
|
break;
|
|
2763
|
+
case "audio":
|
|
2764
|
+
this.renderAudioPanel();
|
|
2765
|
+
break;
|
|
2740
2766
|
}
|
|
2741
2767
|
this.focusFirstItem();
|
|
2742
2768
|
}
|
|
@@ -2765,6 +2791,16 @@ var SettingsMenu = class {
|
|
|
2765
2791
|
);
|
|
2766
2792
|
this.panel.appendChild(captionsRow);
|
|
2767
2793
|
}
|
|
2794
|
+
const audioTracks = this.api.getState("audioTracks") || [];
|
|
2795
|
+
if (audioTracks.length > 1) {
|
|
2796
|
+
const currentAudioTrack = this.api.getState("currentAudioTrack");
|
|
2797
|
+
const audioRow = this.createMainRow(
|
|
2798
|
+
"Audio",
|
|
2799
|
+
currentAudioTrack?.label || "Default",
|
|
2800
|
+
() => this.showPanel("audio")
|
|
2801
|
+
);
|
|
2802
|
+
this.panel.appendChild(audioRow);
|
|
2803
|
+
}
|
|
2768
2804
|
const speedLabel = playbackRate === 1 ? "Normal" : `${playbackRate}x`;
|
|
2769
2805
|
const speedRow = this.createMainRow(
|
|
2770
2806
|
"Speed",
|
|
@@ -2865,6 +2901,36 @@ var SettingsMenu = class {
|
|
|
2865
2901
|
this.panel.appendChild(item);
|
|
2866
2902
|
}
|
|
2867
2903
|
}
|
|
2904
|
+
renderAudioPanel() {
|
|
2905
|
+
this.panel.innerHTML = "";
|
|
2906
|
+
this.panel.className = "sp-settings-panel sp-settings-panel--open sp-settings-panel--sub";
|
|
2907
|
+
const header = this.createSubHeader("Audio");
|
|
2908
|
+
this.panel.appendChild(header);
|
|
2909
|
+
const audioTracks = this.api.getState("audioTracks") || [];
|
|
2910
|
+
const currentAudioTrack = this.api.getState("currentAudioTrack");
|
|
2911
|
+
const activeId = currentAudioTrack?.id ?? null;
|
|
2912
|
+
for (const track of audioTracks) {
|
|
2913
|
+
const item = this.createMenuItem(track.label, track.id, track.id === activeId);
|
|
2914
|
+
item.addEventListener("click", (e) => {
|
|
2915
|
+
e.preventDefault();
|
|
2916
|
+
this.selectAudioTrack(track.id);
|
|
2917
|
+
});
|
|
2918
|
+
this.panel.appendChild(item);
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
selectAudioTrack(trackId) {
|
|
2922
|
+
this.api.emit("track:audio", { trackId });
|
|
2923
|
+
this.close();
|
|
2924
|
+
}
|
|
2925
|
+
updateAudioActiveStates() {
|
|
2926
|
+
const currentAudioTrack = this.api.getState("currentAudioTrack");
|
|
2927
|
+
const activeId = currentAudioTrack?.id ?? null;
|
|
2928
|
+
const items = this.panel.querySelectorAll(".sp-settings-panel__item");
|
|
2929
|
+
items.forEach((item) => {
|
|
2930
|
+
const id = item.getAttribute("data-id");
|
|
2931
|
+
item.classList.toggle("sp-settings-panel__item--active", id === activeId);
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2868
2934
|
selectCaption(trackId) {
|
|
2869
2935
|
this.api.emit("track:text", { trackId });
|
|
2870
2936
|
this.close();
|
|
@@ -3254,6 +3320,17 @@ var OverflowTray = class {
|
|
|
3254
3320
|
this.panel.classList.remove("sp-overflow-tray--open");
|
|
3255
3321
|
this.btn.setAttribute("aria-expanded", "false");
|
|
3256
3322
|
}
|
|
3323
|
+
/**
|
|
3324
|
+
* Whether the strip is open.
|
|
3325
|
+
*
|
|
3326
|
+
* Read by the UI plugin so the control bar's auto-hide waits for it: hiding
|
|
3327
|
+
* the bar would take the open tray with it, mid-use.
|
|
3328
|
+
*
|
|
3329
|
+
* @returns True while the strip is showing
|
|
3330
|
+
*/
|
|
3331
|
+
isMenuOpen() {
|
|
3332
|
+
return this.isOpen;
|
|
3333
|
+
}
|
|
3257
3334
|
/**
|
|
3258
3335
|
* Show the button only while the tray holds something the viewer can see.
|
|
3259
3336
|
*
|
|
@@ -3293,19 +3370,53 @@ var OverflowTray = class {
|
|
|
3293
3370
|
};
|
|
3294
3371
|
|
|
3295
3372
|
// src/control-registry.ts
|
|
3296
|
-
var
|
|
3373
|
+
var globalRegistry = /* @__PURE__ */ new Map();
|
|
3374
|
+
var scopedRegistries = /* @__PURE__ */ new Map();
|
|
3297
3375
|
var listeners = /* @__PURE__ */ new Set();
|
|
3298
|
-
function registerControl(id, factory) {
|
|
3299
|
-
|
|
3376
|
+
function registerControl(id, factory, options = {}) {
|
|
3377
|
+
const { owner } = options;
|
|
3378
|
+
if (owner) {
|
|
3379
|
+
let scoped = scopedRegistries.get(owner);
|
|
3380
|
+
if (!scoped) {
|
|
3381
|
+
scoped = /* @__PURE__ */ new Map();
|
|
3382
|
+
scopedRegistries.set(owner, scoped);
|
|
3383
|
+
}
|
|
3384
|
+
scoped.set(id, factory);
|
|
3385
|
+
} else {
|
|
3386
|
+
globalRegistry.set(id, factory);
|
|
3387
|
+
}
|
|
3300
3388
|
for (const listener of listeners) {
|
|
3301
|
-
listener(id);
|
|
3389
|
+
listener(id, owner);
|
|
3302
3390
|
}
|
|
3303
3391
|
}
|
|
3304
|
-
function unregisterControl(id) {
|
|
3305
|
-
|
|
3392
|
+
function unregisterControl(id, options = {}) {
|
|
3393
|
+
const { owner } = options;
|
|
3394
|
+
if (!owner) {
|
|
3395
|
+
return globalRegistry.delete(id);
|
|
3396
|
+
}
|
|
3397
|
+
const scoped = scopedRegistries.get(owner);
|
|
3398
|
+
if (!scoped) {
|
|
3399
|
+
return false;
|
|
3400
|
+
}
|
|
3401
|
+
const removed = scoped.delete(id);
|
|
3402
|
+
if (scoped.size === 0) {
|
|
3403
|
+
scopedRegistries.delete(owner);
|
|
3404
|
+
}
|
|
3405
|
+
return removed;
|
|
3306
3406
|
}
|
|
3307
|
-
function
|
|
3308
|
-
|
|
3407
|
+
function unregisterControlsFor(owner) {
|
|
3408
|
+
const removed = scopedRegistries.get(owner)?.size ?? 0;
|
|
3409
|
+
scopedRegistries.delete(owner);
|
|
3410
|
+
return removed;
|
|
3411
|
+
}
|
|
3412
|
+
function getControlFactory(id, owner) {
|
|
3413
|
+
if (owner) {
|
|
3414
|
+
const scoped = scopedRegistries.get(owner)?.get(id);
|
|
3415
|
+
if (scoped) {
|
|
3416
|
+
return scoped;
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
return globalRegistry.get(id) ?? null;
|
|
3309
3420
|
}
|
|
3310
3421
|
function onControlRegistered(listener) {
|
|
3311
3422
|
listeners.add(listener);
|
|
@@ -3314,12 +3425,13 @@ function onControlRegistered(listener) {
|
|
|
3314
3425
|
};
|
|
3315
3426
|
}
|
|
3316
3427
|
function resetControlRegistry() {
|
|
3317
|
-
|
|
3428
|
+
globalRegistry.clear();
|
|
3429
|
+
scopedRegistries.clear();
|
|
3318
3430
|
listeners.clear();
|
|
3319
3431
|
}
|
|
3320
3432
|
|
|
3321
3433
|
// src/version.ts
|
|
3322
|
-
var PKG_VERSION = true ? "1.
|
|
3434
|
+
var PKG_VERSION = true ? "1.10.0" : "0.0.0-dev";
|
|
3323
3435
|
|
|
3324
3436
|
// src/index.ts
|
|
3325
3437
|
var DEFAULT_LAYOUT = [
|
|
@@ -3338,12 +3450,25 @@ var DEFAULT_LAYOUT = [
|
|
|
3338
3450
|
"pip",
|
|
3339
3451
|
"fullscreen"
|
|
3340
3452
|
];
|
|
3453
|
+
var STYLE_ID = "sp-ui-styles";
|
|
3341
3454
|
var DEFAULT_HIDE_DELAY = 3e3;
|
|
3455
|
+
var ACTIVATION_KEYS = /* @__PURE__ */ new Set([" ", "Enter"]);
|
|
3456
|
+
var SLIDER_KEYS = /* @__PURE__ */ new Set([
|
|
3457
|
+
"ArrowLeft",
|
|
3458
|
+
"ArrowRight",
|
|
3459
|
+
"ArrowUp",
|
|
3460
|
+
"ArrowDown",
|
|
3461
|
+
"Home",
|
|
3462
|
+
"End",
|
|
3463
|
+
"PageUp",
|
|
3464
|
+
"PageDown"
|
|
3465
|
+
]);
|
|
3342
3466
|
var UNMEASURED_CONTROL_WIDTH = 48;
|
|
3343
3467
|
var FALLBACK_VOLUME_SLIDER_WIDTH = 64;
|
|
3344
3468
|
var OVERFLOW_BUTTON_WIDTH = 44;
|
|
3345
3469
|
var FALLBACK_BAR_PADDING_X = 24;
|
|
3346
3470
|
var FALLBACK_BAR_GAP = 4;
|
|
3471
|
+
var RESIZE_FALLBACK_DEBOUNCE_MS = 100;
|
|
3347
3472
|
var MENU_HEIGHT_RESERVE = 16;
|
|
3348
3473
|
var FALLBACK_BAR_HEIGHT = 56;
|
|
3349
3474
|
var MIN_MENU_HEIGHT = 120;
|
|
@@ -3355,7 +3480,7 @@ function uiPlugin(config = {}) {
|
|
|
3355
3480
|
let bufferingIndicator = null;
|
|
3356
3481
|
let errorOverlay = null;
|
|
3357
3482
|
let bigPlayButton = null;
|
|
3358
|
-
let
|
|
3483
|
+
let releaseStyles = null;
|
|
3359
3484
|
let controls = [];
|
|
3360
3485
|
let hideTimeout = null;
|
|
3361
3486
|
let stateUnsubscribe = null;
|
|
@@ -3363,17 +3488,21 @@ function uiPlugin(config = {}) {
|
|
|
3363
3488
|
let errorUnsubscribe = null;
|
|
3364
3489
|
let reconnectingUnsubscribe = null;
|
|
3365
3490
|
let recoveredUnsubscribe = null;
|
|
3491
|
+
let loadedUnsubscribe = null;
|
|
3366
3492
|
let controlsVisible = true;
|
|
3367
3493
|
let rafHandle = null;
|
|
3368
3494
|
let tray = null;
|
|
3369
3495
|
let entries = [];
|
|
3370
3496
|
let timeEntry = null;
|
|
3371
3497
|
let resizeObserver = null;
|
|
3498
|
+
let windowResizeTimer = null;
|
|
3499
|
+
let onWindowResize = null;
|
|
3372
3500
|
let barPaddingX = FALLBACK_BAR_PADDING_X;
|
|
3373
3501
|
let barGap = FALLBACK_BAR_GAP;
|
|
3374
3502
|
let volumeSliderWidth = FALLBACK_VOLUME_SLIDER_WIDTH;
|
|
3375
3503
|
let lastFitSignature = null;
|
|
3376
3504
|
let fitPending = true;
|
|
3505
|
+
let addedContainerClass = false;
|
|
3377
3506
|
const layout = config.controls || DEFAULT_LAYOUT;
|
|
3378
3507
|
const hideDelay = config.hideDelay ?? DEFAULT_HIDE_DELAY;
|
|
3379
3508
|
const showBigPlayButton = config.bigPlayButton !== false;
|
|
@@ -3416,7 +3545,7 @@ function uiPlugin(config = {}) {
|
|
|
3416
3545
|
case "spacer":
|
|
3417
3546
|
return new Spacer();
|
|
3418
3547
|
default: {
|
|
3419
|
-
const factory = getControlFactory(slot);
|
|
3548
|
+
const factory = getControlFactory(slot, api.container);
|
|
3420
3549
|
if (factory) {
|
|
3421
3550
|
try {
|
|
3422
3551
|
return factory(api);
|
|
@@ -3643,6 +3772,10 @@ function uiPlugin(config = {}) {
|
|
|
3643
3772
|
updateControls();
|
|
3644
3773
|
});
|
|
3645
3774
|
};
|
|
3775
|
+
const hasOpenMenu = () => controls.some((control) => {
|
|
3776
|
+
const menu = control;
|
|
3777
|
+
return typeof menu.isMenuOpen === "function" && menu.isMenuOpen();
|
|
3778
|
+
});
|
|
3646
3779
|
const showControls = () => {
|
|
3647
3780
|
if (controlsVisible) {
|
|
3648
3781
|
resetHideTimer();
|
|
@@ -3659,6 +3792,10 @@ function uiPlugin(config = {}) {
|
|
|
3659
3792
|
const hideControls = () => {
|
|
3660
3793
|
const paused = api?.getState("paused");
|
|
3661
3794
|
if (paused) return;
|
|
3795
|
+
if (hasOpenMenu()) {
|
|
3796
|
+
resetHideTimer();
|
|
3797
|
+
return;
|
|
3798
|
+
}
|
|
3662
3799
|
controlsVisible = false;
|
|
3663
3800
|
controlBar?.classList.remove("sp-controls--visible");
|
|
3664
3801
|
controlBar?.classList.add("sp-controls--hidden");
|
|
@@ -3676,22 +3813,33 @@ function uiPlugin(config = {}) {
|
|
|
3676
3813
|
const handlePointerActivity = (event) => {
|
|
3677
3814
|
last_pointer_type = event.pointerType;
|
|
3678
3815
|
};
|
|
3679
|
-
const handleInteraction = () => {
|
|
3680
|
-
if (last_pointer_type === "touch") {
|
|
3816
|
+
const handleInteraction = (event) => {
|
|
3817
|
+
if (last_pointer_type === "touch" && !isOnControls(event)) {
|
|
3681
3818
|
const gestures = api?.getPlugin("gestures");
|
|
3682
3819
|
if (gestures?.ownsTapInteraction()) return;
|
|
3683
3820
|
}
|
|
3684
3821
|
showControls();
|
|
3685
3822
|
};
|
|
3823
|
+
const isOnControls = (event) => {
|
|
3824
|
+
const target = event?.target;
|
|
3825
|
+
if (!(target instanceof Node)) return false;
|
|
3826
|
+
return Boolean(controlBar?.contains(target)) || Boolean(progressBar?.render().contains(target));
|
|
3827
|
+
};
|
|
3686
3828
|
const handleMouseLeave = () => {
|
|
3687
3829
|
hideControls();
|
|
3688
3830
|
};
|
|
3689
3831
|
const handleKeyDown = (e) => {
|
|
3690
3832
|
if (!api.container.contains(document.activeElement)) return;
|
|
3833
|
+
if (e.defaultPrevented) return;
|
|
3834
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
3691
3835
|
const activeEl = document.activeElement;
|
|
3692
3836
|
if (activeEl instanceof HTMLInputElement || activeEl instanceof HTMLTextAreaElement || activeEl instanceof HTMLSelectElement || activeEl?.isContentEditable) {
|
|
3693
3837
|
return;
|
|
3694
3838
|
}
|
|
3839
|
+
const role = activeEl?.getAttribute("role");
|
|
3840
|
+
const isActivatable = activeEl instanceof HTMLButtonElement || role === "button" || role === "menuitem";
|
|
3841
|
+
if (isActivatable && ACTIVATION_KEYS.has(e.key)) return;
|
|
3842
|
+
if (role === "slider" && SLIDER_KEYS.has(e.key)) return;
|
|
3695
3843
|
const video = api.container.querySelector("video");
|
|
3696
3844
|
if (!video) return;
|
|
3697
3845
|
const live = api.getState("live");
|
|
@@ -3713,11 +3861,11 @@ function uiPlugin(config = {}) {
|
|
|
3713
3861
|
break;
|
|
3714
3862
|
case "f":
|
|
3715
3863
|
e.preventDefault();
|
|
3716
|
-
if ((0,
|
|
3717
|
-
(0,
|
|
3864
|
+
if ((0, import_core4.isFullscreen)(api.container)) {
|
|
3865
|
+
(0, import_core4.exitFullscreen)(api.container).catch(() => {
|
|
3718
3866
|
});
|
|
3719
3867
|
} else {
|
|
3720
|
-
(0,
|
|
3868
|
+
(0, import_core4.enterFullscreen)(api.container).catch(() => {
|
|
3721
3869
|
});
|
|
3722
3870
|
}
|
|
3723
3871
|
break;
|
|
@@ -3758,9 +3906,7 @@ function uiPlugin(config = {}) {
|
|
|
3758
3906
|
version: PKG_VERSION,
|
|
3759
3907
|
async init(pluginApi) {
|
|
3760
3908
|
api = pluginApi;
|
|
3761
|
-
|
|
3762
|
-
styleEl.textContent = styles;
|
|
3763
|
-
document.head.appendChild(styleEl);
|
|
3909
|
+
releaseStyles = (0, import_core4.injectSharedStyles)(STYLE_ID, styles);
|
|
3764
3910
|
if (config.theme) {
|
|
3765
3911
|
this.setTheme(config.theme);
|
|
3766
3912
|
}
|
|
@@ -3773,6 +3919,10 @@ function uiPlugin(config = {}) {
|
|
|
3773
3919
|
if (containerStyle.position === "static") {
|
|
3774
3920
|
container.style.position = "relative";
|
|
3775
3921
|
}
|
|
3922
|
+
if (!container.classList.contains("sp-container")) {
|
|
3923
|
+
container.classList.add("sp-container");
|
|
3924
|
+
addedContainerClass = true;
|
|
3925
|
+
}
|
|
3776
3926
|
const isPlaying = api.getState("playing");
|
|
3777
3927
|
gradient = document.createElement("div");
|
|
3778
3928
|
gradient.className = isPlaying ? "sp-gradient" : "sp-gradient sp-gradient--visible";
|
|
@@ -3788,13 +3938,20 @@ function uiPlugin(config = {}) {
|
|
|
3788
3938
|
if (payload?.fatal) {
|
|
3789
3939
|
const error = api.getState("error") || payload;
|
|
3790
3940
|
errorOverlay?.show(error);
|
|
3941
|
+
scheduleUpdate();
|
|
3791
3942
|
}
|
|
3792
3943
|
});
|
|
3793
3944
|
reconnectingUnsubscribe = api.on("error:reconnecting", () => {
|
|
3794
3945
|
errorOverlay?.showReconnecting();
|
|
3946
|
+
scheduleUpdate();
|
|
3795
3947
|
});
|
|
3796
3948
|
recoveredUnsubscribe = api.on("error:recovered", () => {
|
|
3797
3949
|
errorOverlay?.hide();
|
|
3950
|
+
scheduleUpdate();
|
|
3951
|
+
});
|
|
3952
|
+
loadedUnsubscribe = api.on("media:loaded", () => {
|
|
3953
|
+
errorOverlay?.hide();
|
|
3954
|
+
scheduleUpdate();
|
|
3798
3955
|
});
|
|
3799
3956
|
if (showBigPlayButton) {
|
|
3800
3957
|
bigPlayButton = new BigPlayButton(api, () => errorOverlay?.isVisible() ?? false);
|
|
@@ -3818,11 +3975,25 @@ function uiPlugin(config = {}) {
|
|
|
3818
3975
|
scheduleUpdate();
|
|
3819
3976
|
});
|
|
3820
3977
|
resizeObserver.observe(container);
|
|
3978
|
+
} else if (responsive && typeof window !== "undefined") {
|
|
3979
|
+
onWindowResize = () => {
|
|
3980
|
+
if (windowResizeTimer) clearTimeout(windowResizeTimer);
|
|
3981
|
+
windowResizeTimer = setTimeout(() => {
|
|
3982
|
+
windowResizeTimer = null;
|
|
3983
|
+
applyMenuBounds(container.clientHeight);
|
|
3984
|
+
fitPending = true;
|
|
3985
|
+
scheduleUpdate();
|
|
3986
|
+
}, RESIZE_FALLBACK_DEBOUNCE_MS);
|
|
3987
|
+
};
|
|
3988
|
+
window.addEventListener("resize", onWindowResize);
|
|
3821
3989
|
}
|
|
3822
|
-
controlRegistryUnsubscribe = onControlRegistered((id) => {
|
|
3990
|
+
controlRegistryUnsubscribe = onControlRegistered((id, owner) => {
|
|
3823
3991
|
if (!layout.includes(id)) {
|
|
3824
3992
|
return;
|
|
3825
3993
|
}
|
|
3994
|
+
if (owner && owner !== api.container) {
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3826
3997
|
api.logger.debug(`Control "${id}" registered after init, rebuilding control bar`);
|
|
3827
3998
|
rebuildControlBar();
|
|
3828
3999
|
});
|
|
@@ -3857,6 +4028,14 @@ function uiPlugin(config = {}) {
|
|
|
3857
4028
|
}
|
|
3858
4029
|
resizeObserver?.disconnect();
|
|
3859
4030
|
resizeObserver = null;
|
|
4031
|
+
if (onWindowResize) {
|
|
4032
|
+
window.removeEventListener("resize", onWindowResize);
|
|
4033
|
+
onWindowResize = null;
|
|
4034
|
+
}
|
|
4035
|
+
if (windowResizeTimer) {
|
|
4036
|
+
clearTimeout(windowResizeTimer);
|
|
4037
|
+
windowResizeTimer = null;
|
|
4038
|
+
}
|
|
3860
4039
|
api?.container?.style.removeProperty("--sp-menu-max-height");
|
|
3861
4040
|
stateUnsubscribe?.();
|
|
3862
4041
|
stateUnsubscribe = null;
|
|
@@ -3866,6 +4045,8 @@ function uiPlugin(config = {}) {
|
|
|
3866
4045
|
reconnectingUnsubscribe = null;
|
|
3867
4046
|
recoveredUnsubscribe?.();
|
|
3868
4047
|
recoveredUnsubscribe = null;
|
|
4048
|
+
loadedUnsubscribe?.();
|
|
4049
|
+
loadedUnsubscribe = null;
|
|
3869
4050
|
if (api?.container) {
|
|
3870
4051
|
api.container.removeEventListener("pointerdown", handlePointerActivity);
|
|
3871
4052
|
api.container.removeEventListener("pointermove", handlePointerActivity);
|
|
@@ -3895,8 +4076,12 @@ function uiPlugin(config = {}) {
|
|
|
3895
4076
|
gradient = null;
|
|
3896
4077
|
bufferingIndicator?.remove();
|
|
3897
4078
|
bufferingIndicator = null;
|
|
3898
|
-
|
|
3899
|
-
|
|
4079
|
+
releaseStyles?.();
|
|
4080
|
+
releaseStyles = null;
|
|
4081
|
+
if (addedContainerClass) {
|
|
4082
|
+
api?.container?.classList.remove("sp-container");
|
|
4083
|
+
addedContainerClass = false;
|
|
4084
|
+
}
|
|
3900
4085
|
api?.logger.debug("UI controls plugin destroyed");
|
|
3901
4086
|
},
|
|
3902
4087
|
// Public API
|
|
@@ -3949,5 +4134,6 @@ var index_default = uiPlugin;
|
|
|
3949
4134
|
resolveFitItems,
|
|
3950
4135
|
styles,
|
|
3951
4136
|
uiPlugin,
|
|
3952
|
-
unregisterControl
|
|
4137
|
+
unregisterControl,
|
|
4138
|
+
unregisterControlsFor
|
|
3953
4139
|
});
|