@scarlett-player/ui 1.8.1 → 1.9.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 +214 -35
- package/dist/index.d.cts +51 -12
- package/dist/index.d.ts +51 -12
- package/dist/index.js +218 -35
- package/package.json +3 -3
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
|
|
@@ -1334,7 +1345,7 @@ var BigPlayButton = class {
|
|
|
1334
1345
|
* must not sit on top of it, and `error` state alone does not say (a
|
|
1335
1346
|
* dismissed overlay leaves the error behind)
|
|
1336
1347
|
*/
|
|
1337
|
-
constructor(api, isOverlayVisible
|
|
1348
|
+
constructor(api, isOverlayVisible) {
|
|
1338
1349
|
/**
|
|
1339
1350
|
* Latched on the first `playing`.
|
|
1340
1351
|
*
|
|
@@ -1348,7 +1359,8 @@ var BigPlayButton = class {
|
|
|
1348
1359
|
this.start();
|
|
1349
1360
|
};
|
|
1350
1361
|
this.api = api;
|
|
1351
|
-
this.
|
|
1362
|
+
this.hasOverlayProbe = typeof isOverlayVisible === "function";
|
|
1363
|
+
this.isOverlayVisible = isOverlayVisible ?? (() => false);
|
|
1352
1364
|
const btn = document.createElement("button");
|
|
1353
1365
|
btn.className = "sp-big-play";
|
|
1354
1366
|
btn.setAttribute("type", "button");
|
|
@@ -1377,7 +1389,7 @@ var BigPlayButton = class {
|
|
|
1377
1389
|
this.hasStarted = true;
|
|
1378
1390
|
}
|
|
1379
1391
|
let visible;
|
|
1380
|
-
if (
|
|
1392
|
+
if (this.hasOverlayProbe ? this.isOverlayVisible() : Boolean(error)) {
|
|
1381
1393
|
visible = false;
|
|
1382
1394
|
} else if (playbackState === "loading") {
|
|
1383
1395
|
visible = false;
|
|
@@ -1803,15 +1815,17 @@ var ProgressBar = class {
|
|
|
1803
1815
|
const seekableRange = this.api.getState("seekableRange");
|
|
1804
1816
|
if (live && seekableRange) {
|
|
1805
1817
|
const rangeLength = seekableRange.end - seekableRange.start;
|
|
1806
|
-
|
|
1818
|
+
const time2 = seekableRange.start + percent * rangeLength;
|
|
1819
|
+
return Number.isFinite(time2) ? time2 : null;
|
|
1807
1820
|
}
|
|
1808
1821
|
const duration = this.api.getState("duration") || 0;
|
|
1809
|
-
|
|
1822
|
+
const time = percent * duration;
|
|
1823
|
+
return Number.isFinite(time) ? time : null;
|
|
1810
1824
|
}
|
|
1811
1825
|
updateTooltip(clientX) {
|
|
1812
1826
|
const rect = this.el.getBoundingClientRect();
|
|
1813
1827
|
const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
1814
|
-
const time = this.getTimeFromPosition(clientX);
|
|
1828
|
+
const time = this.getTimeFromPosition(clientX) ?? 0;
|
|
1815
1829
|
const live = this.api.getState("live");
|
|
1816
1830
|
const seekableRange = this.api.getState("seekableRange");
|
|
1817
1831
|
if (live && seekableRange) {
|
|
@@ -1846,7 +1860,9 @@ var ProgressBar = class {
|
|
|
1846
1860
|
}
|
|
1847
1861
|
this.lastSeekTime = now;
|
|
1848
1862
|
const time = this.getTimeFromPosition(clientX);
|
|
1849
|
-
|
|
1863
|
+
if (time !== null && Number.isFinite(time)) {
|
|
1864
|
+
video.currentTime = time;
|
|
1865
|
+
}
|
|
1850
1866
|
}
|
|
1851
1867
|
destroy() {
|
|
1852
1868
|
this.wrapper.removeEventListener("mousedown", this.onMouseDown);
|
|
@@ -2077,11 +2093,9 @@ var LiveIndicator = class {
|
|
|
2077
2093
|
}
|
|
2078
2094
|
}
|
|
2079
2095
|
seekToLive() {
|
|
2080
|
-
const video = getVideo(this.api.container);
|
|
2081
|
-
if (!video) return;
|
|
2082
2096
|
const seekableRange = this.api.getState("seekableRange");
|
|
2083
2097
|
if (seekableRange) {
|
|
2084
|
-
|
|
2098
|
+
this.api.emit("playback:seeking", { time: seekableRange.end });
|
|
2085
2099
|
}
|
|
2086
2100
|
}
|
|
2087
2101
|
destroy() {
|
|
@@ -2127,6 +2141,9 @@ var QualityMenu = class {
|
|
|
2127
2141
|
const qualities = this.api.getState("qualities") || [];
|
|
2128
2142
|
const currentQuality = this.api.getState("currentQuality");
|
|
2129
2143
|
this.el.style.display = qualities.length > 0 ? "" : "none";
|
|
2144
|
+
if (qualities.length === 0 && this.isOpen) {
|
|
2145
|
+
this.close();
|
|
2146
|
+
}
|
|
2130
2147
|
this.btnLabel.textContent = currentQuality?.label || "Auto";
|
|
2131
2148
|
const qualitiesJson = JSON.stringify(qualities.map((q) => q.id));
|
|
2132
2149
|
const currentId = currentQuality?.id || "auto";
|
|
@@ -2191,6 +2208,16 @@ var QualityMenu = class {
|
|
|
2191
2208
|
this.menu.classList.remove("sp-quality-menu--open");
|
|
2192
2209
|
this.btn.setAttribute("aria-expanded", "false");
|
|
2193
2210
|
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Whether the dropdown is open.
|
|
2213
|
+
*
|
|
2214
|
+
* Read by the UI plugin so the control bar's auto-hide waits for it.
|
|
2215
|
+
*
|
|
2216
|
+
* @returns True while the menu is showing
|
|
2217
|
+
*/
|
|
2218
|
+
isMenuOpen() {
|
|
2219
|
+
return this.isOpen;
|
|
2220
|
+
}
|
|
2194
2221
|
destroy() {
|
|
2195
2222
|
document.removeEventListener("click", this.closeHandler);
|
|
2196
2223
|
this.el.remove();
|
|
@@ -2615,7 +2642,7 @@ var SettingsMenu = class {
|
|
|
2615
2642
|
this.el.appendChild(this.panel);
|
|
2616
2643
|
this.closeHandler = (e) => {
|
|
2617
2644
|
if (!this.el.contains(e.target)) {
|
|
2618
|
-
this.close();
|
|
2645
|
+
this.close(false);
|
|
2619
2646
|
}
|
|
2620
2647
|
};
|
|
2621
2648
|
document.addEventListener("click", this.closeHandler);
|
|
@@ -2628,7 +2655,6 @@ var SettingsMenu = class {
|
|
|
2628
2655
|
this.showPanel("main");
|
|
2629
2656
|
} else {
|
|
2630
2657
|
this.close();
|
|
2631
|
-
this.btn.focus();
|
|
2632
2658
|
}
|
|
2633
2659
|
return;
|
|
2634
2660
|
}
|
|
@@ -2665,6 +2691,8 @@ var SettingsMenu = class {
|
|
|
2665
2691
|
this.updateSpeedActiveStates();
|
|
2666
2692
|
} else if (this.currentPanel === "captions") {
|
|
2667
2693
|
this.updateCaptionsActiveStates();
|
|
2694
|
+
} else if (this.currentPanel === "audio") {
|
|
2695
|
+
this.updateAudioActiveStates();
|
|
2668
2696
|
}
|
|
2669
2697
|
}
|
|
2670
2698
|
}
|
|
@@ -2679,11 +2707,25 @@ var SettingsMenu = class {
|
|
|
2679
2707
|
this.btn.setAttribute("aria-expanded", "true");
|
|
2680
2708
|
this.focusFirstItem();
|
|
2681
2709
|
}
|
|
2682
|
-
|
|
2710
|
+
/**
|
|
2711
|
+
* Close the panel.
|
|
2712
|
+
*
|
|
2713
|
+
* Choosing an item tears down the row that had focus, which drops focus to
|
|
2714
|
+
* `<body>` and strands keyboard viewers outside the player entirely. Hand it
|
|
2715
|
+
* back to the button that opened the menu instead.
|
|
2716
|
+
*
|
|
2717
|
+
* @param restoreFocus - Return focus to the gear button. The outside-click
|
|
2718
|
+
* handler passes false, so a click elsewhere is not pulled back into the bar.
|
|
2719
|
+
*/
|
|
2720
|
+
close(restoreFocus = true) {
|
|
2721
|
+
const wasOpen = this.isOpen;
|
|
2683
2722
|
this.isOpen = false;
|
|
2684
2723
|
this.currentPanel = "main";
|
|
2685
2724
|
this.panel.classList.remove("sp-settings-panel--open");
|
|
2686
2725
|
this.btn.setAttribute("aria-expanded", "false");
|
|
2726
|
+
if (wasOpen && restoreFocus) {
|
|
2727
|
+
this.btn.focus();
|
|
2728
|
+
}
|
|
2687
2729
|
}
|
|
2688
2730
|
showPanel(panel) {
|
|
2689
2731
|
this.currentPanel = panel;
|
|
@@ -2700,6 +2742,9 @@ var SettingsMenu = class {
|
|
|
2700
2742
|
case "captions":
|
|
2701
2743
|
this.renderCaptionsPanel();
|
|
2702
2744
|
break;
|
|
2745
|
+
case "audio":
|
|
2746
|
+
this.renderAudioPanel();
|
|
2747
|
+
break;
|
|
2703
2748
|
}
|
|
2704
2749
|
this.focusFirstItem();
|
|
2705
2750
|
}
|
|
@@ -2728,6 +2773,16 @@ var SettingsMenu = class {
|
|
|
2728
2773
|
);
|
|
2729
2774
|
this.panel.appendChild(captionsRow);
|
|
2730
2775
|
}
|
|
2776
|
+
const audioTracks = this.api.getState("audioTracks") || [];
|
|
2777
|
+
if (audioTracks.length > 1) {
|
|
2778
|
+
const currentAudioTrack = this.api.getState("currentAudioTrack");
|
|
2779
|
+
const audioRow = this.createMainRow(
|
|
2780
|
+
"Audio",
|
|
2781
|
+
currentAudioTrack?.label || "Default",
|
|
2782
|
+
() => this.showPanel("audio")
|
|
2783
|
+
);
|
|
2784
|
+
this.panel.appendChild(audioRow);
|
|
2785
|
+
}
|
|
2731
2786
|
const speedLabel = playbackRate === 1 ? "Normal" : `${playbackRate}x`;
|
|
2732
2787
|
const speedRow = this.createMainRow(
|
|
2733
2788
|
"Speed",
|
|
@@ -2828,6 +2883,36 @@ var SettingsMenu = class {
|
|
|
2828
2883
|
this.panel.appendChild(item);
|
|
2829
2884
|
}
|
|
2830
2885
|
}
|
|
2886
|
+
renderAudioPanel() {
|
|
2887
|
+
this.panel.innerHTML = "";
|
|
2888
|
+
this.panel.className = "sp-settings-panel sp-settings-panel--open sp-settings-panel--sub";
|
|
2889
|
+
const header = this.createSubHeader("Audio");
|
|
2890
|
+
this.panel.appendChild(header);
|
|
2891
|
+
const audioTracks = this.api.getState("audioTracks") || [];
|
|
2892
|
+
const currentAudioTrack = this.api.getState("currentAudioTrack");
|
|
2893
|
+
const activeId = currentAudioTrack?.id ?? null;
|
|
2894
|
+
for (const track of audioTracks) {
|
|
2895
|
+
const item = this.createMenuItem(track.label, track.id, track.id === activeId);
|
|
2896
|
+
item.addEventListener("click", (e) => {
|
|
2897
|
+
e.preventDefault();
|
|
2898
|
+
this.selectAudioTrack(track.id);
|
|
2899
|
+
});
|
|
2900
|
+
this.panel.appendChild(item);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
selectAudioTrack(trackId) {
|
|
2904
|
+
this.api.emit("track:audio", { trackId });
|
|
2905
|
+
this.close();
|
|
2906
|
+
}
|
|
2907
|
+
updateAudioActiveStates() {
|
|
2908
|
+
const currentAudioTrack = this.api.getState("currentAudioTrack");
|
|
2909
|
+
const activeId = currentAudioTrack?.id ?? null;
|
|
2910
|
+
const items = this.panel.querySelectorAll(".sp-settings-panel__item");
|
|
2911
|
+
items.forEach((item) => {
|
|
2912
|
+
const id = item.getAttribute("data-id");
|
|
2913
|
+
item.classList.toggle("sp-settings-panel__item--active", id === activeId);
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2831
2916
|
selectCaption(trackId) {
|
|
2832
2917
|
this.api.emit("track:text", { trackId });
|
|
2833
2918
|
this.close();
|
|
@@ -3217,6 +3302,17 @@ var OverflowTray = class {
|
|
|
3217
3302
|
this.panel.classList.remove("sp-overflow-tray--open");
|
|
3218
3303
|
this.btn.setAttribute("aria-expanded", "false");
|
|
3219
3304
|
}
|
|
3305
|
+
/**
|
|
3306
|
+
* Whether the strip is open.
|
|
3307
|
+
*
|
|
3308
|
+
* Read by the UI plugin so the control bar's auto-hide waits for it: hiding
|
|
3309
|
+
* the bar would take the open tray with it, mid-use.
|
|
3310
|
+
*
|
|
3311
|
+
* @returns True while the strip is showing
|
|
3312
|
+
*/
|
|
3313
|
+
isMenuOpen() {
|
|
3314
|
+
return this.isOpen;
|
|
3315
|
+
}
|
|
3220
3316
|
/**
|
|
3221
3317
|
* Show the button only while the tray holds something the viewer can see.
|
|
3222
3318
|
*
|
|
@@ -3256,19 +3352,53 @@ var OverflowTray = class {
|
|
|
3256
3352
|
};
|
|
3257
3353
|
|
|
3258
3354
|
// src/control-registry.ts
|
|
3259
|
-
var
|
|
3355
|
+
var globalRegistry = /* @__PURE__ */ new Map();
|
|
3356
|
+
var scopedRegistries = /* @__PURE__ */ new Map();
|
|
3260
3357
|
var listeners = /* @__PURE__ */ new Set();
|
|
3261
|
-
function registerControl(id, factory) {
|
|
3262
|
-
|
|
3358
|
+
function registerControl(id, factory, options = {}) {
|
|
3359
|
+
const { owner } = options;
|
|
3360
|
+
if (owner) {
|
|
3361
|
+
let scoped = scopedRegistries.get(owner);
|
|
3362
|
+
if (!scoped) {
|
|
3363
|
+
scoped = /* @__PURE__ */ new Map();
|
|
3364
|
+
scopedRegistries.set(owner, scoped);
|
|
3365
|
+
}
|
|
3366
|
+
scoped.set(id, factory);
|
|
3367
|
+
} else {
|
|
3368
|
+
globalRegistry.set(id, factory);
|
|
3369
|
+
}
|
|
3263
3370
|
for (const listener of listeners) {
|
|
3264
|
-
listener(id);
|
|
3371
|
+
listener(id, owner);
|
|
3265
3372
|
}
|
|
3266
3373
|
}
|
|
3267
|
-
function unregisterControl(id) {
|
|
3268
|
-
|
|
3374
|
+
function unregisterControl(id, options = {}) {
|
|
3375
|
+
const { owner } = options;
|
|
3376
|
+
if (!owner) {
|
|
3377
|
+
return globalRegistry.delete(id);
|
|
3378
|
+
}
|
|
3379
|
+
const scoped = scopedRegistries.get(owner);
|
|
3380
|
+
if (!scoped) {
|
|
3381
|
+
return false;
|
|
3382
|
+
}
|
|
3383
|
+
const removed = scoped.delete(id);
|
|
3384
|
+
if (scoped.size === 0) {
|
|
3385
|
+
scopedRegistries.delete(owner);
|
|
3386
|
+
}
|
|
3387
|
+
return removed;
|
|
3388
|
+
}
|
|
3389
|
+
function unregisterControlsFor(owner) {
|
|
3390
|
+
const removed = scopedRegistries.get(owner)?.size ?? 0;
|
|
3391
|
+
scopedRegistries.delete(owner);
|
|
3392
|
+
return removed;
|
|
3269
3393
|
}
|
|
3270
|
-
function getControlFactory(id) {
|
|
3271
|
-
|
|
3394
|
+
function getControlFactory(id, owner) {
|
|
3395
|
+
if (owner) {
|
|
3396
|
+
const scoped = scopedRegistries.get(owner)?.get(id);
|
|
3397
|
+
if (scoped) {
|
|
3398
|
+
return scoped;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
return globalRegistry.get(id) ?? null;
|
|
3272
3402
|
}
|
|
3273
3403
|
function onControlRegistered(listener) {
|
|
3274
3404
|
listeners.add(listener);
|
|
@@ -3277,12 +3407,13 @@ function onControlRegistered(listener) {
|
|
|
3277
3407
|
};
|
|
3278
3408
|
}
|
|
3279
3409
|
function resetControlRegistry() {
|
|
3280
|
-
|
|
3410
|
+
globalRegistry.clear();
|
|
3411
|
+
scopedRegistries.clear();
|
|
3281
3412
|
listeners.clear();
|
|
3282
3413
|
}
|
|
3283
3414
|
|
|
3284
3415
|
// src/version.ts
|
|
3285
|
-
var PKG_VERSION = true ? "1.
|
|
3416
|
+
var PKG_VERSION = true ? "1.9.0" : "0.0.0-dev";
|
|
3286
3417
|
|
|
3287
3418
|
// src/index.ts
|
|
3288
3419
|
var DEFAULT_LAYOUT = [
|
|
@@ -3301,7 +3432,19 @@ var DEFAULT_LAYOUT = [
|
|
|
3301
3432
|
"pip",
|
|
3302
3433
|
"fullscreen"
|
|
3303
3434
|
];
|
|
3435
|
+
var STYLE_ID = "sp-ui-styles";
|
|
3304
3436
|
var DEFAULT_HIDE_DELAY = 3e3;
|
|
3437
|
+
var ACTIVATION_KEYS = /* @__PURE__ */ new Set([" ", "Enter"]);
|
|
3438
|
+
var SLIDER_KEYS = /* @__PURE__ */ new Set([
|
|
3439
|
+
"ArrowLeft",
|
|
3440
|
+
"ArrowRight",
|
|
3441
|
+
"ArrowUp",
|
|
3442
|
+
"ArrowDown",
|
|
3443
|
+
"Home",
|
|
3444
|
+
"End",
|
|
3445
|
+
"PageUp",
|
|
3446
|
+
"PageDown"
|
|
3447
|
+
]);
|
|
3305
3448
|
var UNMEASURED_CONTROL_WIDTH = 48;
|
|
3306
3449
|
var FALLBACK_VOLUME_SLIDER_WIDTH = 64;
|
|
3307
3450
|
var OVERFLOW_BUTTON_WIDTH = 44;
|
|
@@ -3318,7 +3461,7 @@ function uiPlugin(config = {}) {
|
|
|
3318
3461
|
let bufferingIndicator = null;
|
|
3319
3462
|
let errorOverlay = null;
|
|
3320
3463
|
let bigPlayButton = null;
|
|
3321
|
-
let
|
|
3464
|
+
let releaseStyles = null;
|
|
3322
3465
|
let controls = [];
|
|
3323
3466
|
let hideTimeout = null;
|
|
3324
3467
|
let stateUnsubscribe = null;
|
|
@@ -3326,6 +3469,7 @@ function uiPlugin(config = {}) {
|
|
|
3326
3469
|
let errorUnsubscribe = null;
|
|
3327
3470
|
let reconnectingUnsubscribe = null;
|
|
3328
3471
|
let recoveredUnsubscribe = null;
|
|
3472
|
+
let loadedUnsubscribe = null;
|
|
3329
3473
|
let controlsVisible = true;
|
|
3330
3474
|
let rafHandle = null;
|
|
3331
3475
|
let tray = null;
|
|
@@ -3337,6 +3481,7 @@ function uiPlugin(config = {}) {
|
|
|
3337
3481
|
let volumeSliderWidth = FALLBACK_VOLUME_SLIDER_WIDTH;
|
|
3338
3482
|
let lastFitSignature = null;
|
|
3339
3483
|
let fitPending = true;
|
|
3484
|
+
let addedContainerClass = false;
|
|
3340
3485
|
const layout = config.controls || DEFAULT_LAYOUT;
|
|
3341
3486
|
const hideDelay = config.hideDelay ?? DEFAULT_HIDE_DELAY;
|
|
3342
3487
|
const showBigPlayButton = config.bigPlayButton !== false;
|
|
@@ -3379,7 +3524,7 @@ function uiPlugin(config = {}) {
|
|
|
3379
3524
|
case "spacer":
|
|
3380
3525
|
return new Spacer();
|
|
3381
3526
|
default: {
|
|
3382
|
-
const factory = getControlFactory(slot);
|
|
3527
|
+
const factory = getControlFactory(slot, api.container);
|
|
3383
3528
|
if (factory) {
|
|
3384
3529
|
try {
|
|
3385
3530
|
return factory(api);
|
|
@@ -3606,6 +3751,10 @@ function uiPlugin(config = {}) {
|
|
|
3606
3751
|
updateControls();
|
|
3607
3752
|
});
|
|
3608
3753
|
};
|
|
3754
|
+
const hasOpenMenu = () => controls.some((control) => {
|
|
3755
|
+
const menu = control;
|
|
3756
|
+
return typeof menu.isMenuOpen === "function" && menu.isMenuOpen();
|
|
3757
|
+
});
|
|
3609
3758
|
const showControls = () => {
|
|
3610
3759
|
if (controlsVisible) {
|
|
3611
3760
|
resetHideTimer();
|
|
@@ -3622,6 +3771,10 @@ function uiPlugin(config = {}) {
|
|
|
3622
3771
|
const hideControls = () => {
|
|
3623
3772
|
const paused = api?.getState("paused");
|
|
3624
3773
|
if (paused) return;
|
|
3774
|
+
if (hasOpenMenu()) {
|
|
3775
|
+
resetHideTimer();
|
|
3776
|
+
return;
|
|
3777
|
+
}
|
|
3625
3778
|
controlsVisible = false;
|
|
3626
3779
|
controlBar?.classList.remove("sp-controls--visible");
|
|
3627
3780
|
controlBar?.classList.add("sp-controls--hidden");
|
|
@@ -3639,22 +3792,33 @@ function uiPlugin(config = {}) {
|
|
|
3639
3792
|
const handlePointerActivity = (event) => {
|
|
3640
3793
|
last_pointer_type = event.pointerType;
|
|
3641
3794
|
};
|
|
3642
|
-
const handleInteraction = () => {
|
|
3643
|
-
if (last_pointer_type === "touch") {
|
|
3795
|
+
const handleInteraction = (event) => {
|
|
3796
|
+
if (last_pointer_type === "touch" && !isOnControls(event)) {
|
|
3644
3797
|
const gestures = api?.getPlugin("gestures");
|
|
3645
3798
|
if (gestures?.ownsTapInteraction()) return;
|
|
3646
3799
|
}
|
|
3647
3800
|
showControls();
|
|
3648
3801
|
};
|
|
3802
|
+
const isOnControls = (event) => {
|
|
3803
|
+
const target = event?.target;
|
|
3804
|
+
if (!(target instanceof Node)) return false;
|
|
3805
|
+
return Boolean(controlBar?.contains(target)) || Boolean(progressBar?.render().contains(target));
|
|
3806
|
+
};
|
|
3649
3807
|
const handleMouseLeave = () => {
|
|
3650
3808
|
hideControls();
|
|
3651
3809
|
};
|
|
3652
3810
|
const handleKeyDown = (e) => {
|
|
3653
3811
|
if (!api.container.contains(document.activeElement)) return;
|
|
3812
|
+
if (e.defaultPrevented) return;
|
|
3813
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
3654
3814
|
const activeEl = document.activeElement;
|
|
3655
3815
|
if (activeEl instanceof HTMLInputElement || activeEl instanceof HTMLTextAreaElement || activeEl instanceof HTMLSelectElement || activeEl?.isContentEditable) {
|
|
3656
3816
|
return;
|
|
3657
3817
|
}
|
|
3818
|
+
const role = activeEl?.getAttribute("role");
|
|
3819
|
+
const isActivatable = activeEl instanceof HTMLButtonElement || role === "button" || role === "menuitem";
|
|
3820
|
+
if (isActivatable && ACTIVATION_KEYS.has(e.key)) return;
|
|
3821
|
+
if (role === "slider" && SLIDER_KEYS.has(e.key)) return;
|
|
3658
3822
|
const video = api.container.querySelector("video");
|
|
3659
3823
|
if (!video) return;
|
|
3660
3824
|
const live = api.getState("live");
|
|
@@ -3721,9 +3885,7 @@ function uiPlugin(config = {}) {
|
|
|
3721
3885
|
version: PKG_VERSION,
|
|
3722
3886
|
async init(pluginApi) {
|
|
3723
3887
|
api = pluginApi;
|
|
3724
|
-
|
|
3725
|
-
styleEl.textContent = styles;
|
|
3726
|
-
document.head.appendChild(styleEl);
|
|
3888
|
+
releaseStyles = injectSharedStyles(STYLE_ID, styles);
|
|
3727
3889
|
if (config.theme) {
|
|
3728
3890
|
this.setTheme(config.theme);
|
|
3729
3891
|
}
|
|
@@ -3736,6 +3898,10 @@ function uiPlugin(config = {}) {
|
|
|
3736
3898
|
if (containerStyle.position === "static") {
|
|
3737
3899
|
container.style.position = "relative";
|
|
3738
3900
|
}
|
|
3901
|
+
if (!container.classList.contains("sp-container")) {
|
|
3902
|
+
container.classList.add("sp-container");
|
|
3903
|
+
addedContainerClass = true;
|
|
3904
|
+
}
|
|
3739
3905
|
const isPlaying = api.getState("playing");
|
|
3740
3906
|
gradient = document.createElement("div");
|
|
3741
3907
|
gradient.className = isPlaying ? "sp-gradient" : "sp-gradient sp-gradient--visible";
|
|
@@ -3751,13 +3917,20 @@ function uiPlugin(config = {}) {
|
|
|
3751
3917
|
if (payload?.fatal) {
|
|
3752
3918
|
const error = api.getState("error") || payload;
|
|
3753
3919
|
errorOverlay?.show(error);
|
|
3920
|
+
scheduleUpdate();
|
|
3754
3921
|
}
|
|
3755
3922
|
});
|
|
3756
3923
|
reconnectingUnsubscribe = api.on("error:reconnecting", () => {
|
|
3757
3924
|
errorOverlay?.showReconnecting();
|
|
3925
|
+
scheduleUpdate();
|
|
3758
3926
|
});
|
|
3759
3927
|
recoveredUnsubscribe = api.on("error:recovered", () => {
|
|
3760
3928
|
errorOverlay?.hide();
|
|
3929
|
+
scheduleUpdate();
|
|
3930
|
+
});
|
|
3931
|
+
loadedUnsubscribe = api.on("media:loaded", () => {
|
|
3932
|
+
errorOverlay?.hide();
|
|
3933
|
+
scheduleUpdate();
|
|
3761
3934
|
});
|
|
3762
3935
|
if (showBigPlayButton) {
|
|
3763
3936
|
bigPlayButton = new BigPlayButton(api, () => errorOverlay?.isVisible() ?? false);
|
|
@@ -3782,10 +3955,13 @@ function uiPlugin(config = {}) {
|
|
|
3782
3955
|
});
|
|
3783
3956
|
resizeObserver.observe(container);
|
|
3784
3957
|
}
|
|
3785
|
-
controlRegistryUnsubscribe = onControlRegistered((id) => {
|
|
3958
|
+
controlRegistryUnsubscribe = onControlRegistered((id, owner) => {
|
|
3786
3959
|
if (!layout.includes(id)) {
|
|
3787
3960
|
return;
|
|
3788
3961
|
}
|
|
3962
|
+
if (owner && owner !== api.container) {
|
|
3963
|
+
return;
|
|
3964
|
+
}
|
|
3789
3965
|
api.logger.debug(`Control "${id}" registered after init, rebuilding control bar`);
|
|
3790
3966
|
rebuildControlBar();
|
|
3791
3967
|
});
|
|
@@ -3829,6 +4005,8 @@ function uiPlugin(config = {}) {
|
|
|
3829
4005
|
reconnectingUnsubscribe = null;
|
|
3830
4006
|
recoveredUnsubscribe?.();
|
|
3831
4007
|
recoveredUnsubscribe = null;
|
|
4008
|
+
loadedUnsubscribe?.();
|
|
4009
|
+
loadedUnsubscribe = null;
|
|
3832
4010
|
if (api?.container) {
|
|
3833
4011
|
api.container.removeEventListener("pointerdown", handlePointerActivity);
|
|
3834
4012
|
api.container.removeEventListener("pointermove", handlePointerActivity);
|
|
@@ -3858,8 +4036,12 @@ function uiPlugin(config = {}) {
|
|
|
3858
4036
|
gradient = null;
|
|
3859
4037
|
bufferingIndicator?.remove();
|
|
3860
4038
|
bufferingIndicator = null;
|
|
3861
|
-
|
|
3862
|
-
|
|
4039
|
+
releaseStyles?.();
|
|
4040
|
+
releaseStyles = null;
|
|
4041
|
+
if (addedContainerClass) {
|
|
4042
|
+
api?.container?.classList.remove("sp-container");
|
|
4043
|
+
addedContainerClass = false;
|
|
4044
|
+
}
|
|
3863
4045
|
api?.logger.debug("UI controls plugin destroyed");
|
|
3864
4046
|
},
|
|
3865
4047
|
// Public API
|
|
@@ -3912,5 +4094,6 @@ export {
|
|
|
3912
4094
|
resolveFitItems,
|
|
3913
4095
|
styles,
|
|
3914
4096
|
uiPlugin,
|
|
3915
|
-
unregisterControl
|
|
4097
|
+
unregisterControl,
|
|
4098
|
+
unregisterControlsFor
|
|
3916
4099
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scarlett-player/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "UI Controls Plugin for Scarlett Player",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"typescript": "^5.3.0",
|
|
30
30
|
"vitest": "^1.6.0",
|
|
31
31
|
"jsdom": "^24.0.0",
|
|
32
|
-
"@scarlett-player/core": "1.
|
|
32
|
+
"@scarlett-player/core": "1.9.0"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"video",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"dev": "tsup --watch",
|
|
55
55
|
"test": "vitest --run",
|
|
56
56
|
"test:watch": "vitest",
|
|
57
|
-
"typecheck": "tsc --noEmit"
|
|
57
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
|
|
58
58
|
}
|
|
59
59
|
}
|