@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.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
enterFullscreen as enterFullscreen2,
|
|
4
|
+
exitFullscreen as exitFullscreen2,
|
|
5
|
+
injectSharedStyles,
|
|
6
|
+
isFullscreen as isFullscreen2
|
|
7
|
+
} from "@scarlett-player/core";
|
|
3
8
|
|
|
4
9
|
// src/fit.ts
|
|
5
10
|
var UNKNOWN_RANK = 3;
|
|
@@ -86,6 +91,10 @@ var styles = `
|
|
|
86
91
|
============================================ */
|
|
87
92
|
.sp-container {
|
|
88
93
|
position: relative;
|
|
94
|
+
/* Own stacking context: the control bar, menus and overlays all carry
|
|
95
|
+
z-index, and without this they compete with the host page's own layers
|
|
96
|
+
instead of staying inside the player. */
|
|
97
|
+
isolation: isolate;
|
|
89
98
|
width: 100%;
|
|
90
99
|
height: 100%;
|
|
91
100
|
background: #000;
|
|
@@ -207,11 +216,13 @@ var styles = `
|
|
|
207
216
|
cursor: pointer;
|
|
208
217
|
z-index: 10;
|
|
209
218
|
opacity: 0;
|
|
219
|
+
pointer-events: none;
|
|
210
220
|
transition: opacity 0.25s ease;
|
|
211
221
|
}
|
|
212
222
|
|
|
213
223
|
.sp-progress-wrapper--visible {
|
|
214
224
|
opacity: 1;
|
|
225
|
+
pointer-events: auto;
|
|
215
226
|
}
|
|
216
227
|
|
|
217
228
|
/* Touch: a 20px wrapper is not a 20px target. The control bar is a later
|
|
@@ -1160,24 +1171,30 @@ var styles = `
|
|
|
1160
1171
|
|
|
1161
1172
|
/* ============================================
|
|
1162
1173
|
CSS Custom Properties (Theming)
|
|
1174
|
+
============================================
|
|
1175
|
+
|
|
1176
|
+
Token defaults live at the use sites as var() fallbacks, not in a :root
|
|
1177
|
+
block. A :root declaration wrote --sp-accent, --sp-color, --sp-bg,
|
|
1178
|
+
--sp-control-height and --sp-icon-size into the HOST document, so mounting a
|
|
1179
|
+
player changed variables the page may own. A .sp-container declaration would
|
|
1180
|
+
be no better: the container rule wins for every descendant, so it would
|
|
1181
|
+
override a host that themes the player from its own :root. A var() fallback
|
|
1182
|
+
does neither - it applies only where nothing else defines the token.
|
|
1183
|
+
|
|
1184
|
+
setTheme() writes the same names onto the player's container, which still
|
|
1185
|
+
wins over the fallbacks.
|
|
1163
1186
|
============================================ */
|
|
1164
|
-
:root {
|
|
1165
|
-
--sp-accent: #e50914;
|
|
1166
|
-
--sp-color: #fff;
|
|
1167
|
-
--sp-bg: rgba(0, 0, 0, 0.8);
|
|
1168
|
-
--sp-control-height: 48px;
|
|
1169
|
-
--sp-icon-size: 24px;
|
|
1170
|
-
}
|
|
1171
1187
|
`;
|
|
1172
1188
|
|
|
1173
1189
|
// src/icons.ts
|
|
1190
|
+
import { SHARED_ICON_PATHS } from "@scarlett-player/core";
|
|
1174
1191
|
var icons = {
|
|
1175
|
-
play: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="
|
|
1192
|
+
play: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="${SHARED_ICON_PATHS.play}"/></svg>`,
|
|
1176
1193
|
pause: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/></svg>`,
|
|
1177
1194
|
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>`,
|
|
1178
|
-
volumeHigh: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="
|
|
1195
|
+
volumeHigh: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="${SHARED_ICON_PATHS.volumeHigh}"/></svg>`,
|
|
1179
1196
|
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>`,
|
|
1180
|
-
volumeMute: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="
|
|
1197
|
+
volumeMute: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="${SHARED_ICON_PATHS.volumeMuted}"/></svg>`,
|
|
1181
1198
|
fullscreen: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>`,
|
|
1182
1199
|
exitFullscreen: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/></svg>`,
|
|
1183
1200
|
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>`,
|
|
@@ -1250,29 +1267,7 @@ function setAttr(el, name, value) {
|
|
|
1250
1267
|
}
|
|
1251
1268
|
|
|
1252
1269
|
// src/utils/format.ts
|
|
1253
|
-
|
|
1254
|
-
if (!isFinite(seconds) || isNaN(seconds)) {
|
|
1255
|
-
return "0:00";
|
|
1256
|
-
}
|
|
1257
|
-
const absSeconds = Math.abs(seconds);
|
|
1258
|
-
const h = Math.floor(absSeconds / 3600);
|
|
1259
|
-
const m = Math.floor(absSeconds % 3600 / 60);
|
|
1260
|
-
const s = Math.floor(absSeconds % 60);
|
|
1261
|
-
const sign = seconds < 0 ? "-" : "";
|
|
1262
|
-
if (h > 0) {
|
|
1263
|
-
return `${sign}${h}:${pad(m)}:${pad(s)}`;
|
|
1264
|
-
}
|
|
1265
|
-
return `${sign}${m}:${pad(s)}`;
|
|
1266
|
-
}
|
|
1267
|
-
function pad(n) {
|
|
1268
|
-
return n < 10 ? `0${n}` : `${n}`;
|
|
1269
|
-
}
|
|
1270
|
-
function formatLiveTime(behindLive) {
|
|
1271
|
-
if (behindLive <= 0) {
|
|
1272
|
-
return "LIVE";
|
|
1273
|
-
}
|
|
1274
|
-
return `-${formatTime(behindLive)}`;
|
|
1275
|
-
}
|
|
1270
|
+
import { formatTime, formatLiveTime } from "@scarlett-player/core";
|
|
1276
1271
|
|
|
1277
1272
|
// src/controls/PlayButton.ts
|
|
1278
1273
|
var PlayButton = class {
|
|
@@ -1334,7 +1329,7 @@ var BigPlayButton = class {
|
|
|
1334
1329
|
* must not sit on top of it, and `error` state alone does not say (a
|
|
1335
1330
|
* dismissed overlay leaves the error behind)
|
|
1336
1331
|
*/
|
|
1337
|
-
constructor(api, isOverlayVisible
|
|
1332
|
+
constructor(api, isOverlayVisible) {
|
|
1338
1333
|
/**
|
|
1339
1334
|
* Latched on the first `playing`.
|
|
1340
1335
|
*
|
|
@@ -1348,7 +1343,8 @@ var BigPlayButton = class {
|
|
|
1348
1343
|
this.start();
|
|
1349
1344
|
};
|
|
1350
1345
|
this.api = api;
|
|
1351
|
-
this.
|
|
1346
|
+
this.hasOverlayProbe = typeof isOverlayVisible === "function";
|
|
1347
|
+
this.isOverlayVisible = isOverlayVisible ?? (() => false);
|
|
1352
1348
|
const btn = document.createElement("button");
|
|
1353
1349
|
btn.className = "sp-big-play";
|
|
1354
1350
|
btn.setAttribute("type", "button");
|
|
@@ -1377,7 +1373,7 @@ var BigPlayButton = class {
|
|
|
1377
1373
|
this.hasStarted = true;
|
|
1378
1374
|
}
|
|
1379
1375
|
let visible;
|
|
1380
|
-
if (
|
|
1376
|
+
if (this.hasOverlayProbe ? this.isOverlayVisible() : Boolean(error)) {
|
|
1381
1377
|
visible = false;
|
|
1382
1378
|
} else if (playbackState === "loading") {
|
|
1383
1379
|
visible = false;
|
|
@@ -1576,7 +1572,7 @@ var ProgressBar = class {
|
|
|
1576
1572
|
video.addEventListener("seeked", resumePlayback);
|
|
1577
1573
|
}
|
|
1578
1574
|
}
|
|
1579
|
-
this.tooltip.style.opacity = "
|
|
1575
|
+
this.tooltip.style.opacity = "";
|
|
1580
1576
|
this.thumbnailPreview.hide();
|
|
1581
1577
|
}
|
|
1582
1578
|
};
|
|
@@ -1585,7 +1581,7 @@ var ProgressBar = class {
|
|
|
1585
1581
|
};
|
|
1586
1582
|
this.onMouseLeave = () => {
|
|
1587
1583
|
if (!this.isDragging) {
|
|
1588
|
-
this.tooltip.style.opacity = "
|
|
1584
|
+
this.tooltip.style.opacity = "";
|
|
1589
1585
|
this.thumbnailPreview.hide();
|
|
1590
1586
|
}
|
|
1591
1587
|
};
|
|
@@ -1803,15 +1799,17 @@ var ProgressBar = class {
|
|
|
1803
1799
|
const seekableRange = this.api.getState("seekableRange");
|
|
1804
1800
|
if (live && seekableRange) {
|
|
1805
1801
|
const rangeLength = seekableRange.end - seekableRange.start;
|
|
1806
|
-
|
|
1802
|
+
const time2 = seekableRange.start + percent * rangeLength;
|
|
1803
|
+
return Number.isFinite(time2) ? time2 : null;
|
|
1807
1804
|
}
|
|
1808
1805
|
const duration = this.api.getState("duration") || 0;
|
|
1809
|
-
|
|
1806
|
+
const time = percent * duration;
|
|
1807
|
+
return Number.isFinite(time) ? time : null;
|
|
1810
1808
|
}
|
|
1811
1809
|
updateTooltip(clientX) {
|
|
1812
1810
|
const rect = this.el.getBoundingClientRect();
|
|
1813
1811
|
const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
1814
|
-
const time = this.getTimeFromPosition(clientX);
|
|
1812
|
+
const time = this.getTimeFromPosition(clientX) ?? 0;
|
|
1815
1813
|
const live = this.api.getState("live");
|
|
1816
1814
|
const seekableRange = this.api.getState("seekableRange");
|
|
1817
1815
|
if (live && seekableRange) {
|
|
@@ -1846,13 +1844,16 @@ var ProgressBar = class {
|
|
|
1846
1844
|
}
|
|
1847
1845
|
this.lastSeekTime = now;
|
|
1848
1846
|
const time = this.getTimeFromPosition(clientX);
|
|
1849
|
-
|
|
1847
|
+
if (time !== null && Number.isFinite(time)) {
|
|
1848
|
+
video.currentTime = time;
|
|
1849
|
+
}
|
|
1850
1850
|
}
|
|
1851
1851
|
destroy() {
|
|
1852
1852
|
this.wrapper.removeEventListener("mousedown", this.onMouseDown);
|
|
1853
1853
|
this.wrapper.removeEventListener("mousemove", this.onMouseMove);
|
|
1854
1854
|
this.wrapper.removeEventListener("mouseleave", this.onMouseLeave);
|
|
1855
1855
|
this.wrapper.removeEventListener("touchstart", this.onTouchStart);
|
|
1856
|
+
this.el.removeEventListener("keydown", this.onKeyDown);
|
|
1856
1857
|
document.removeEventListener("mousemove", this.onDocMouseMove);
|
|
1857
1858
|
document.removeEventListener("mouseup", this.onMouseUp);
|
|
1858
1859
|
document.removeEventListener("touchmove", this.onDocTouchMove);
|
|
@@ -2077,11 +2078,9 @@ var LiveIndicator = class {
|
|
|
2077
2078
|
}
|
|
2078
2079
|
}
|
|
2079
2080
|
seekToLive() {
|
|
2080
|
-
const video = getVideo(this.api.container);
|
|
2081
|
-
if (!video) return;
|
|
2082
2081
|
const seekableRange = this.api.getState("seekableRange");
|
|
2083
2082
|
if (seekableRange) {
|
|
2084
|
-
|
|
2083
|
+
this.api.emit("playback:seeking", { time: seekableRange.end });
|
|
2085
2084
|
}
|
|
2086
2085
|
}
|
|
2087
2086
|
destroy() {
|
|
@@ -2127,6 +2126,9 @@ var QualityMenu = class {
|
|
|
2127
2126
|
const qualities = this.api.getState("qualities") || [];
|
|
2128
2127
|
const currentQuality = this.api.getState("currentQuality");
|
|
2129
2128
|
this.el.style.display = qualities.length > 0 ? "" : "none";
|
|
2129
|
+
if (qualities.length === 0 && this.isOpen) {
|
|
2130
|
+
this.close();
|
|
2131
|
+
}
|
|
2130
2132
|
this.btnLabel.textContent = currentQuality?.label || "Auto";
|
|
2131
2133
|
const qualitiesJson = JSON.stringify(qualities.map((q) => q.id));
|
|
2132
2134
|
const currentId = currentQuality?.id || "auto";
|
|
@@ -2191,6 +2193,16 @@ var QualityMenu = class {
|
|
|
2191
2193
|
this.menu.classList.remove("sp-quality-menu--open");
|
|
2192
2194
|
this.btn.setAttribute("aria-expanded", "false");
|
|
2193
2195
|
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Whether the dropdown is open.
|
|
2198
|
+
*
|
|
2199
|
+
* Read by the UI plugin so the control bar's auto-hide waits for it.
|
|
2200
|
+
*
|
|
2201
|
+
* @returns True while the menu is showing
|
|
2202
|
+
*/
|
|
2203
|
+
isMenuOpen() {
|
|
2204
|
+
return this.isOpen;
|
|
2205
|
+
}
|
|
2194
2206
|
destroy() {
|
|
2195
2207
|
document.removeEventListener("click", this.closeHandler);
|
|
2196
2208
|
this.el.remove();
|
|
@@ -2615,7 +2627,7 @@ var SettingsMenu = class {
|
|
|
2615
2627
|
this.el.appendChild(this.panel);
|
|
2616
2628
|
this.closeHandler = (e) => {
|
|
2617
2629
|
if (!this.el.contains(e.target)) {
|
|
2618
|
-
this.close();
|
|
2630
|
+
this.close(false);
|
|
2619
2631
|
}
|
|
2620
2632
|
};
|
|
2621
2633
|
document.addEventListener("click", this.closeHandler);
|
|
@@ -2628,7 +2640,6 @@ var SettingsMenu = class {
|
|
|
2628
2640
|
this.showPanel("main");
|
|
2629
2641
|
} else {
|
|
2630
2642
|
this.close();
|
|
2631
|
-
this.btn.focus();
|
|
2632
2643
|
}
|
|
2633
2644
|
return;
|
|
2634
2645
|
}
|
|
@@ -2665,6 +2676,8 @@ var SettingsMenu = class {
|
|
|
2665
2676
|
this.updateSpeedActiveStates();
|
|
2666
2677
|
} else if (this.currentPanel === "captions") {
|
|
2667
2678
|
this.updateCaptionsActiveStates();
|
|
2679
|
+
} else if (this.currentPanel === "audio") {
|
|
2680
|
+
this.updateAudioActiveStates();
|
|
2668
2681
|
}
|
|
2669
2682
|
}
|
|
2670
2683
|
}
|
|
@@ -2679,11 +2692,25 @@ var SettingsMenu = class {
|
|
|
2679
2692
|
this.btn.setAttribute("aria-expanded", "true");
|
|
2680
2693
|
this.focusFirstItem();
|
|
2681
2694
|
}
|
|
2682
|
-
|
|
2695
|
+
/**
|
|
2696
|
+
* Close the panel.
|
|
2697
|
+
*
|
|
2698
|
+
* Choosing an item tears down the row that had focus, which drops focus to
|
|
2699
|
+
* `<body>` and strands keyboard viewers outside the player entirely. Hand it
|
|
2700
|
+
* back to the button that opened the menu instead.
|
|
2701
|
+
*
|
|
2702
|
+
* @param restoreFocus - Return focus to the gear button. The outside-click
|
|
2703
|
+
* handler passes false, so a click elsewhere is not pulled back into the bar.
|
|
2704
|
+
*/
|
|
2705
|
+
close(restoreFocus = true) {
|
|
2706
|
+
const wasOpen = this.isOpen;
|
|
2683
2707
|
this.isOpen = false;
|
|
2684
2708
|
this.currentPanel = "main";
|
|
2685
2709
|
this.panel.classList.remove("sp-settings-panel--open");
|
|
2686
2710
|
this.btn.setAttribute("aria-expanded", "false");
|
|
2711
|
+
if (wasOpen && restoreFocus) {
|
|
2712
|
+
this.btn.focus();
|
|
2713
|
+
}
|
|
2687
2714
|
}
|
|
2688
2715
|
showPanel(panel) {
|
|
2689
2716
|
this.currentPanel = panel;
|
|
@@ -2700,6 +2727,9 @@ var SettingsMenu = class {
|
|
|
2700
2727
|
case "captions":
|
|
2701
2728
|
this.renderCaptionsPanel();
|
|
2702
2729
|
break;
|
|
2730
|
+
case "audio":
|
|
2731
|
+
this.renderAudioPanel();
|
|
2732
|
+
break;
|
|
2703
2733
|
}
|
|
2704
2734
|
this.focusFirstItem();
|
|
2705
2735
|
}
|
|
@@ -2728,6 +2758,16 @@ var SettingsMenu = class {
|
|
|
2728
2758
|
);
|
|
2729
2759
|
this.panel.appendChild(captionsRow);
|
|
2730
2760
|
}
|
|
2761
|
+
const audioTracks = this.api.getState("audioTracks") || [];
|
|
2762
|
+
if (audioTracks.length > 1) {
|
|
2763
|
+
const currentAudioTrack = this.api.getState("currentAudioTrack");
|
|
2764
|
+
const audioRow = this.createMainRow(
|
|
2765
|
+
"Audio",
|
|
2766
|
+
currentAudioTrack?.label || "Default",
|
|
2767
|
+
() => this.showPanel("audio")
|
|
2768
|
+
);
|
|
2769
|
+
this.panel.appendChild(audioRow);
|
|
2770
|
+
}
|
|
2731
2771
|
const speedLabel = playbackRate === 1 ? "Normal" : `${playbackRate}x`;
|
|
2732
2772
|
const speedRow = this.createMainRow(
|
|
2733
2773
|
"Speed",
|
|
@@ -2828,6 +2868,36 @@ var SettingsMenu = class {
|
|
|
2828
2868
|
this.panel.appendChild(item);
|
|
2829
2869
|
}
|
|
2830
2870
|
}
|
|
2871
|
+
renderAudioPanel() {
|
|
2872
|
+
this.panel.innerHTML = "";
|
|
2873
|
+
this.panel.className = "sp-settings-panel sp-settings-panel--open sp-settings-panel--sub";
|
|
2874
|
+
const header = this.createSubHeader("Audio");
|
|
2875
|
+
this.panel.appendChild(header);
|
|
2876
|
+
const audioTracks = this.api.getState("audioTracks") || [];
|
|
2877
|
+
const currentAudioTrack = this.api.getState("currentAudioTrack");
|
|
2878
|
+
const activeId = currentAudioTrack?.id ?? null;
|
|
2879
|
+
for (const track of audioTracks) {
|
|
2880
|
+
const item = this.createMenuItem(track.label, track.id, track.id === activeId);
|
|
2881
|
+
item.addEventListener("click", (e) => {
|
|
2882
|
+
e.preventDefault();
|
|
2883
|
+
this.selectAudioTrack(track.id);
|
|
2884
|
+
});
|
|
2885
|
+
this.panel.appendChild(item);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
selectAudioTrack(trackId) {
|
|
2889
|
+
this.api.emit("track:audio", { trackId });
|
|
2890
|
+
this.close();
|
|
2891
|
+
}
|
|
2892
|
+
updateAudioActiveStates() {
|
|
2893
|
+
const currentAudioTrack = this.api.getState("currentAudioTrack");
|
|
2894
|
+
const activeId = currentAudioTrack?.id ?? null;
|
|
2895
|
+
const items = this.panel.querySelectorAll(".sp-settings-panel__item");
|
|
2896
|
+
items.forEach((item) => {
|
|
2897
|
+
const id = item.getAttribute("data-id");
|
|
2898
|
+
item.classList.toggle("sp-settings-panel__item--active", id === activeId);
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2831
2901
|
selectCaption(trackId) {
|
|
2832
2902
|
this.api.emit("track:text", { trackId });
|
|
2833
2903
|
this.close();
|
|
@@ -3217,6 +3287,17 @@ var OverflowTray = class {
|
|
|
3217
3287
|
this.panel.classList.remove("sp-overflow-tray--open");
|
|
3218
3288
|
this.btn.setAttribute("aria-expanded", "false");
|
|
3219
3289
|
}
|
|
3290
|
+
/**
|
|
3291
|
+
* Whether the strip is open.
|
|
3292
|
+
*
|
|
3293
|
+
* Read by the UI plugin so the control bar's auto-hide waits for it: hiding
|
|
3294
|
+
* the bar would take the open tray with it, mid-use.
|
|
3295
|
+
*
|
|
3296
|
+
* @returns True while the strip is showing
|
|
3297
|
+
*/
|
|
3298
|
+
isMenuOpen() {
|
|
3299
|
+
return this.isOpen;
|
|
3300
|
+
}
|
|
3220
3301
|
/**
|
|
3221
3302
|
* Show the button only while the tray holds something the viewer can see.
|
|
3222
3303
|
*
|
|
@@ -3256,19 +3337,53 @@ var OverflowTray = class {
|
|
|
3256
3337
|
};
|
|
3257
3338
|
|
|
3258
3339
|
// src/control-registry.ts
|
|
3259
|
-
var
|
|
3340
|
+
var globalRegistry = /* @__PURE__ */ new Map();
|
|
3341
|
+
var scopedRegistries = /* @__PURE__ */ new Map();
|
|
3260
3342
|
var listeners = /* @__PURE__ */ new Set();
|
|
3261
|
-
function registerControl(id, factory) {
|
|
3262
|
-
|
|
3343
|
+
function registerControl(id, factory, options = {}) {
|
|
3344
|
+
const { owner } = options;
|
|
3345
|
+
if (owner) {
|
|
3346
|
+
let scoped = scopedRegistries.get(owner);
|
|
3347
|
+
if (!scoped) {
|
|
3348
|
+
scoped = /* @__PURE__ */ new Map();
|
|
3349
|
+
scopedRegistries.set(owner, scoped);
|
|
3350
|
+
}
|
|
3351
|
+
scoped.set(id, factory);
|
|
3352
|
+
} else {
|
|
3353
|
+
globalRegistry.set(id, factory);
|
|
3354
|
+
}
|
|
3263
3355
|
for (const listener of listeners) {
|
|
3264
|
-
listener(id);
|
|
3356
|
+
listener(id, owner);
|
|
3265
3357
|
}
|
|
3266
3358
|
}
|
|
3267
|
-
function unregisterControl(id) {
|
|
3268
|
-
|
|
3359
|
+
function unregisterControl(id, options = {}) {
|
|
3360
|
+
const { owner } = options;
|
|
3361
|
+
if (!owner) {
|
|
3362
|
+
return globalRegistry.delete(id);
|
|
3363
|
+
}
|
|
3364
|
+
const scoped = scopedRegistries.get(owner);
|
|
3365
|
+
if (!scoped) {
|
|
3366
|
+
return false;
|
|
3367
|
+
}
|
|
3368
|
+
const removed = scoped.delete(id);
|
|
3369
|
+
if (scoped.size === 0) {
|
|
3370
|
+
scopedRegistries.delete(owner);
|
|
3371
|
+
}
|
|
3372
|
+
return removed;
|
|
3373
|
+
}
|
|
3374
|
+
function unregisterControlsFor(owner) {
|
|
3375
|
+
const removed = scopedRegistries.get(owner)?.size ?? 0;
|
|
3376
|
+
scopedRegistries.delete(owner);
|
|
3377
|
+
return removed;
|
|
3269
3378
|
}
|
|
3270
|
-
function getControlFactory(id) {
|
|
3271
|
-
|
|
3379
|
+
function getControlFactory(id, owner) {
|
|
3380
|
+
if (owner) {
|
|
3381
|
+
const scoped = scopedRegistries.get(owner)?.get(id);
|
|
3382
|
+
if (scoped) {
|
|
3383
|
+
return scoped;
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
return globalRegistry.get(id) ?? null;
|
|
3272
3387
|
}
|
|
3273
3388
|
function onControlRegistered(listener) {
|
|
3274
3389
|
listeners.add(listener);
|
|
@@ -3277,12 +3392,13 @@ function onControlRegistered(listener) {
|
|
|
3277
3392
|
};
|
|
3278
3393
|
}
|
|
3279
3394
|
function resetControlRegistry() {
|
|
3280
|
-
|
|
3395
|
+
globalRegistry.clear();
|
|
3396
|
+
scopedRegistries.clear();
|
|
3281
3397
|
listeners.clear();
|
|
3282
3398
|
}
|
|
3283
3399
|
|
|
3284
3400
|
// src/version.ts
|
|
3285
|
-
var PKG_VERSION = true ? "1.
|
|
3401
|
+
var PKG_VERSION = true ? "1.10.0" : "0.0.0-dev";
|
|
3286
3402
|
|
|
3287
3403
|
// src/index.ts
|
|
3288
3404
|
var DEFAULT_LAYOUT = [
|
|
@@ -3301,12 +3417,25 @@ var DEFAULT_LAYOUT = [
|
|
|
3301
3417
|
"pip",
|
|
3302
3418
|
"fullscreen"
|
|
3303
3419
|
];
|
|
3420
|
+
var STYLE_ID = "sp-ui-styles";
|
|
3304
3421
|
var DEFAULT_HIDE_DELAY = 3e3;
|
|
3422
|
+
var ACTIVATION_KEYS = /* @__PURE__ */ new Set([" ", "Enter"]);
|
|
3423
|
+
var SLIDER_KEYS = /* @__PURE__ */ new Set([
|
|
3424
|
+
"ArrowLeft",
|
|
3425
|
+
"ArrowRight",
|
|
3426
|
+
"ArrowUp",
|
|
3427
|
+
"ArrowDown",
|
|
3428
|
+
"Home",
|
|
3429
|
+
"End",
|
|
3430
|
+
"PageUp",
|
|
3431
|
+
"PageDown"
|
|
3432
|
+
]);
|
|
3305
3433
|
var UNMEASURED_CONTROL_WIDTH = 48;
|
|
3306
3434
|
var FALLBACK_VOLUME_SLIDER_WIDTH = 64;
|
|
3307
3435
|
var OVERFLOW_BUTTON_WIDTH = 44;
|
|
3308
3436
|
var FALLBACK_BAR_PADDING_X = 24;
|
|
3309
3437
|
var FALLBACK_BAR_GAP = 4;
|
|
3438
|
+
var RESIZE_FALLBACK_DEBOUNCE_MS = 100;
|
|
3310
3439
|
var MENU_HEIGHT_RESERVE = 16;
|
|
3311
3440
|
var FALLBACK_BAR_HEIGHT = 56;
|
|
3312
3441
|
var MIN_MENU_HEIGHT = 120;
|
|
@@ -3318,7 +3447,7 @@ function uiPlugin(config = {}) {
|
|
|
3318
3447
|
let bufferingIndicator = null;
|
|
3319
3448
|
let errorOverlay = null;
|
|
3320
3449
|
let bigPlayButton = null;
|
|
3321
|
-
let
|
|
3450
|
+
let releaseStyles = null;
|
|
3322
3451
|
let controls = [];
|
|
3323
3452
|
let hideTimeout = null;
|
|
3324
3453
|
let stateUnsubscribe = null;
|
|
@@ -3326,17 +3455,21 @@ function uiPlugin(config = {}) {
|
|
|
3326
3455
|
let errorUnsubscribe = null;
|
|
3327
3456
|
let reconnectingUnsubscribe = null;
|
|
3328
3457
|
let recoveredUnsubscribe = null;
|
|
3458
|
+
let loadedUnsubscribe = null;
|
|
3329
3459
|
let controlsVisible = true;
|
|
3330
3460
|
let rafHandle = null;
|
|
3331
3461
|
let tray = null;
|
|
3332
3462
|
let entries = [];
|
|
3333
3463
|
let timeEntry = null;
|
|
3334
3464
|
let resizeObserver = null;
|
|
3465
|
+
let windowResizeTimer = null;
|
|
3466
|
+
let onWindowResize = null;
|
|
3335
3467
|
let barPaddingX = FALLBACK_BAR_PADDING_X;
|
|
3336
3468
|
let barGap = FALLBACK_BAR_GAP;
|
|
3337
3469
|
let volumeSliderWidth = FALLBACK_VOLUME_SLIDER_WIDTH;
|
|
3338
3470
|
let lastFitSignature = null;
|
|
3339
3471
|
let fitPending = true;
|
|
3472
|
+
let addedContainerClass = false;
|
|
3340
3473
|
const layout = config.controls || DEFAULT_LAYOUT;
|
|
3341
3474
|
const hideDelay = config.hideDelay ?? DEFAULT_HIDE_DELAY;
|
|
3342
3475
|
const showBigPlayButton = config.bigPlayButton !== false;
|
|
@@ -3379,7 +3512,7 @@ function uiPlugin(config = {}) {
|
|
|
3379
3512
|
case "spacer":
|
|
3380
3513
|
return new Spacer();
|
|
3381
3514
|
default: {
|
|
3382
|
-
const factory = getControlFactory(slot);
|
|
3515
|
+
const factory = getControlFactory(slot, api.container);
|
|
3383
3516
|
if (factory) {
|
|
3384
3517
|
try {
|
|
3385
3518
|
return factory(api);
|
|
@@ -3606,6 +3739,10 @@ function uiPlugin(config = {}) {
|
|
|
3606
3739
|
updateControls();
|
|
3607
3740
|
});
|
|
3608
3741
|
};
|
|
3742
|
+
const hasOpenMenu = () => controls.some((control) => {
|
|
3743
|
+
const menu = control;
|
|
3744
|
+
return typeof menu.isMenuOpen === "function" && menu.isMenuOpen();
|
|
3745
|
+
});
|
|
3609
3746
|
const showControls = () => {
|
|
3610
3747
|
if (controlsVisible) {
|
|
3611
3748
|
resetHideTimer();
|
|
@@ -3622,6 +3759,10 @@ function uiPlugin(config = {}) {
|
|
|
3622
3759
|
const hideControls = () => {
|
|
3623
3760
|
const paused = api?.getState("paused");
|
|
3624
3761
|
if (paused) return;
|
|
3762
|
+
if (hasOpenMenu()) {
|
|
3763
|
+
resetHideTimer();
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3625
3766
|
controlsVisible = false;
|
|
3626
3767
|
controlBar?.classList.remove("sp-controls--visible");
|
|
3627
3768
|
controlBar?.classList.add("sp-controls--hidden");
|
|
@@ -3639,22 +3780,33 @@ function uiPlugin(config = {}) {
|
|
|
3639
3780
|
const handlePointerActivity = (event) => {
|
|
3640
3781
|
last_pointer_type = event.pointerType;
|
|
3641
3782
|
};
|
|
3642
|
-
const handleInteraction = () => {
|
|
3643
|
-
if (last_pointer_type === "touch") {
|
|
3783
|
+
const handleInteraction = (event) => {
|
|
3784
|
+
if (last_pointer_type === "touch" && !isOnControls(event)) {
|
|
3644
3785
|
const gestures = api?.getPlugin("gestures");
|
|
3645
3786
|
if (gestures?.ownsTapInteraction()) return;
|
|
3646
3787
|
}
|
|
3647
3788
|
showControls();
|
|
3648
3789
|
};
|
|
3790
|
+
const isOnControls = (event) => {
|
|
3791
|
+
const target = event?.target;
|
|
3792
|
+
if (!(target instanceof Node)) return false;
|
|
3793
|
+
return Boolean(controlBar?.contains(target)) || Boolean(progressBar?.render().contains(target));
|
|
3794
|
+
};
|
|
3649
3795
|
const handleMouseLeave = () => {
|
|
3650
3796
|
hideControls();
|
|
3651
3797
|
};
|
|
3652
3798
|
const handleKeyDown = (e) => {
|
|
3653
3799
|
if (!api.container.contains(document.activeElement)) return;
|
|
3800
|
+
if (e.defaultPrevented) return;
|
|
3801
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
3654
3802
|
const activeEl = document.activeElement;
|
|
3655
3803
|
if (activeEl instanceof HTMLInputElement || activeEl instanceof HTMLTextAreaElement || activeEl instanceof HTMLSelectElement || activeEl?.isContentEditable) {
|
|
3656
3804
|
return;
|
|
3657
3805
|
}
|
|
3806
|
+
const role = activeEl?.getAttribute("role");
|
|
3807
|
+
const isActivatable = activeEl instanceof HTMLButtonElement || role === "button" || role === "menuitem";
|
|
3808
|
+
if (isActivatable && ACTIVATION_KEYS.has(e.key)) return;
|
|
3809
|
+
if (role === "slider" && SLIDER_KEYS.has(e.key)) return;
|
|
3658
3810
|
const video = api.container.querySelector("video");
|
|
3659
3811
|
if (!video) return;
|
|
3660
3812
|
const live = api.getState("live");
|
|
@@ -3721,9 +3873,7 @@ function uiPlugin(config = {}) {
|
|
|
3721
3873
|
version: PKG_VERSION,
|
|
3722
3874
|
async init(pluginApi) {
|
|
3723
3875
|
api = pluginApi;
|
|
3724
|
-
|
|
3725
|
-
styleEl.textContent = styles;
|
|
3726
|
-
document.head.appendChild(styleEl);
|
|
3876
|
+
releaseStyles = injectSharedStyles(STYLE_ID, styles);
|
|
3727
3877
|
if (config.theme) {
|
|
3728
3878
|
this.setTheme(config.theme);
|
|
3729
3879
|
}
|
|
@@ -3736,6 +3886,10 @@ function uiPlugin(config = {}) {
|
|
|
3736
3886
|
if (containerStyle.position === "static") {
|
|
3737
3887
|
container.style.position = "relative";
|
|
3738
3888
|
}
|
|
3889
|
+
if (!container.classList.contains("sp-container")) {
|
|
3890
|
+
container.classList.add("sp-container");
|
|
3891
|
+
addedContainerClass = true;
|
|
3892
|
+
}
|
|
3739
3893
|
const isPlaying = api.getState("playing");
|
|
3740
3894
|
gradient = document.createElement("div");
|
|
3741
3895
|
gradient.className = isPlaying ? "sp-gradient" : "sp-gradient sp-gradient--visible";
|
|
@@ -3751,13 +3905,20 @@ function uiPlugin(config = {}) {
|
|
|
3751
3905
|
if (payload?.fatal) {
|
|
3752
3906
|
const error = api.getState("error") || payload;
|
|
3753
3907
|
errorOverlay?.show(error);
|
|
3908
|
+
scheduleUpdate();
|
|
3754
3909
|
}
|
|
3755
3910
|
});
|
|
3756
3911
|
reconnectingUnsubscribe = api.on("error:reconnecting", () => {
|
|
3757
3912
|
errorOverlay?.showReconnecting();
|
|
3913
|
+
scheduleUpdate();
|
|
3758
3914
|
});
|
|
3759
3915
|
recoveredUnsubscribe = api.on("error:recovered", () => {
|
|
3760
3916
|
errorOverlay?.hide();
|
|
3917
|
+
scheduleUpdate();
|
|
3918
|
+
});
|
|
3919
|
+
loadedUnsubscribe = api.on("media:loaded", () => {
|
|
3920
|
+
errorOverlay?.hide();
|
|
3921
|
+
scheduleUpdate();
|
|
3761
3922
|
});
|
|
3762
3923
|
if (showBigPlayButton) {
|
|
3763
3924
|
bigPlayButton = new BigPlayButton(api, () => errorOverlay?.isVisible() ?? false);
|
|
@@ -3781,11 +3942,25 @@ function uiPlugin(config = {}) {
|
|
|
3781
3942
|
scheduleUpdate();
|
|
3782
3943
|
});
|
|
3783
3944
|
resizeObserver.observe(container);
|
|
3945
|
+
} else if (responsive && typeof window !== "undefined") {
|
|
3946
|
+
onWindowResize = () => {
|
|
3947
|
+
if (windowResizeTimer) clearTimeout(windowResizeTimer);
|
|
3948
|
+
windowResizeTimer = setTimeout(() => {
|
|
3949
|
+
windowResizeTimer = null;
|
|
3950
|
+
applyMenuBounds(container.clientHeight);
|
|
3951
|
+
fitPending = true;
|
|
3952
|
+
scheduleUpdate();
|
|
3953
|
+
}, RESIZE_FALLBACK_DEBOUNCE_MS);
|
|
3954
|
+
};
|
|
3955
|
+
window.addEventListener("resize", onWindowResize);
|
|
3784
3956
|
}
|
|
3785
|
-
controlRegistryUnsubscribe = onControlRegistered((id) => {
|
|
3957
|
+
controlRegistryUnsubscribe = onControlRegistered((id, owner) => {
|
|
3786
3958
|
if (!layout.includes(id)) {
|
|
3787
3959
|
return;
|
|
3788
3960
|
}
|
|
3961
|
+
if (owner && owner !== api.container) {
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3789
3964
|
api.logger.debug(`Control "${id}" registered after init, rebuilding control bar`);
|
|
3790
3965
|
rebuildControlBar();
|
|
3791
3966
|
});
|
|
@@ -3820,6 +3995,14 @@ function uiPlugin(config = {}) {
|
|
|
3820
3995
|
}
|
|
3821
3996
|
resizeObserver?.disconnect();
|
|
3822
3997
|
resizeObserver = null;
|
|
3998
|
+
if (onWindowResize) {
|
|
3999
|
+
window.removeEventListener("resize", onWindowResize);
|
|
4000
|
+
onWindowResize = null;
|
|
4001
|
+
}
|
|
4002
|
+
if (windowResizeTimer) {
|
|
4003
|
+
clearTimeout(windowResizeTimer);
|
|
4004
|
+
windowResizeTimer = null;
|
|
4005
|
+
}
|
|
3823
4006
|
api?.container?.style.removeProperty("--sp-menu-max-height");
|
|
3824
4007
|
stateUnsubscribe?.();
|
|
3825
4008
|
stateUnsubscribe = null;
|
|
@@ -3829,6 +4012,8 @@ function uiPlugin(config = {}) {
|
|
|
3829
4012
|
reconnectingUnsubscribe = null;
|
|
3830
4013
|
recoveredUnsubscribe?.();
|
|
3831
4014
|
recoveredUnsubscribe = null;
|
|
4015
|
+
loadedUnsubscribe?.();
|
|
4016
|
+
loadedUnsubscribe = null;
|
|
3832
4017
|
if (api?.container) {
|
|
3833
4018
|
api.container.removeEventListener("pointerdown", handlePointerActivity);
|
|
3834
4019
|
api.container.removeEventListener("pointermove", handlePointerActivity);
|
|
@@ -3858,8 +4043,12 @@ function uiPlugin(config = {}) {
|
|
|
3858
4043
|
gradient = null;
|
|
3859
4044
|
bufferingIndicator?.remove();
|
|
3860
4045
|
bufferingIndicator = null;
|
|
3861
|
-
|
|
3862
|
-
|
|
4046
|
+
releaseStyles?.();
|
|
4047
|
+
releaseStyles = null;
|
|
4048
|
+
if (addedContainerClass) {
|
|
4049
|
+
api?.container?.classList.remove("sp-container");
|
|
4050
|
+
addedContainerClass = false;
|
|
4051
|
+
}
|
|
3863
4052
|
api?.logger.debug("UI controls plugin destroyed");
|
|
3864
4053
|
},
|
|
3865
4054
|
// Public API
|
|
@@ -3912,5 +4101,6 @@ export {
|
|
|
3912
4101
|
resolveFitItems,
|
|
3913
4102
|
styles,
|
|
3914
4103
|
uiPlugin,
|
|
3915
|
-
unregisterControl
|
|
4104
|
+
unregisterControl,
|
|
4105
|
+
unregisterControlsFor
|
|
3916
4106
|
};
|