@f8team/reel-core 1.0.4 → 1.1.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/CHANGELOG.md +29 -0
- package/dist/index.cjs +220 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -13
- package/dist/index.d.ts +67 -13
- package/dist/index.js +217 -68
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @f8team/reel-core
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- fe0431b: Fix controls that never auto-hide in fullscreen.
|
|
8
|
+
|
|
9
|
+
Entering fullscreen is only possible by hitting the fullscreen control, and the
|
|
10
|
+
pointer focus it leaves behind kept `:focus-within` matching forever, so the
|
|
11
|
+
themes held the controls bar and the mobile transport at full opacity for the
|
|
12
|
+
rest of the session. Reported on Android Chrome and Safari.
|
|
13
|
+
|
|
14
|
+
The idle timer now releases that pointer focus instead of only dropping
|
|
15
|
+
`data-reel-controls-visible`. Keyboard focus and open control menus still keep
|
|
16
|
+
the chrome on screen - the timer re-arms rather than hiding.
|
|
17
|
+
|
|
18
|
+
`@f8team/reel-core` gains `blurFocusInside` and `shouldKeepControlsVisible` so
|
|
19
|
+
the Lit element, the React adapter and app-level players share one auto-hide
|
|
20
|
+
focus contract; `@f8team/reel-react` re-exports both.
|
|
21
|
+
|
|
22
|
+
## 1.0.5
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- 16eaa0b: Harden source loading, authenticated HLS, multi-player input ownership, adapter lifecycle,
|
|
27
|
+
YouTube controls, responsive touch targets, and plugin cleanup. Add framework-specific preset
|
|
28
|
+
entry points, deterministic browser journeys, and focused regression coverage for production
|
|
29
|
+
consumers. Enforce exact tenant API credential boundaries, preserve progressive upload playback,
|
|
30
|
+
and expand release gates across Chromium, WebKit, coverage, bundle size, and dependency security.
|
|
31
|
+
|
|
3
32
|
## 1.0.4
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -214,12 +214,58 @@ function createControlsRegistry() {
|
|
|
214
214
|
}
|
|
215
215
|
return { contribute, snapshot, clear };
|
|
216
216
|
}
|
|
217
|
-
function
|
|
217
|
+
function createSourceTransformRegistry() {
|
|
218
|
+
const transforms = /* @__PURE__ */ new Set();
|
|
219
|
+
function register(transform) {
|
|
220
|
+
transforms.add(transform);
|
|
221
|
+
return () => transforms.delete(transform);
|
|
222
|
+
}
|
|
223
|
+
function apply(source) {
|
|
224
|
+
let current = source;
|
|
225
|
+
for (const transform of transforms) {
|
|
226
|
+
current = transform(current);
|
|
227
|
+
}
|
|
228
|
+
return current;
|
|
229
|
+
}
|
|
230
|
+
return { register, apply };
|
|
231
|
+
}
|
|
232
|
+
function createMediaAttachmentRegistry() {
|
|
233
|
+
let current = null;
|
|
234
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
235
|
+
function get() {
|
|
236
|
+
return current;
|
|
237
|
+
}
|
|
238
|
+
function set(attachment) {
|
|
239
|
+
current = attachment;
|
|
240
|
+
for (const listener of listeners) listener(current);
|
|
241
|
+
}
|
|
242
|
+
function subscribe(listener) {
|
|
243
|
+
listeners.add(listener);
|
|
244
|
+
if (current) listener(current);
|
|
245
|
+
return () => listeners.delete(listener);
|
|
246
|
+
}
|
|
247
|
+
return { get, set, subscribe };
|
|
248
|
+
}
|
|
249
|
+
function createPluginHost({
|
|
250
|
+
commands,
|
|
251
|
+
controls,
|
|
252
|
+
store,
|
|
253
|
+
bus,
|
|
254
|
+
sourceTransforms = createSourceTransformRegistry(),
|
|
255
|
+
mediaAttachments = createMediaAttachmentRegistry()
|
|
256
|
+
}) {
|
|
218
257
|
return {
|
|
219
258
|
controls: {
|
|
220
|
-
contribute:
|
|
259
|
+
contribute: controls.contribute
|
|
221
260
|
},
|
|
222
261
|
commands,
|
|
262
|
+
sources: {
|
|
263
|
+
transform: sourceTransforms.register
|
|
264
|
+
},
|
|
265
|
+
media: {
|
|
266
|
+
get: mediaAttachments.get,
|
|
267
|
+
subscribe: mediaAttachments.subscribe
|
|
268
|
+
},
|
|
223
269
|
store,
|
|
224
270
|
emit: (event, payload) => {
|
|
225
271
|
bus.emit(event, payload);
|
|
@@ -228,6 +274,21 @@ function createPluginHost({ commands, controls, store, bus }) {
|
|
|
228
274
|
}
|
|
229
275
|
|
|
230
276
|
// src/util/url.ts
|
|
277
|
+
function areSourceDescriptorsEquivalent(previous, next) {
|
|
278
|
+
if (previous === next) return true;
|
|
279
|
+
if (!previous || !next) return false;
|
|
280
|
+
const previousThumbnails = previous.thumbnails;
|
|
281
|
+
const nextThumbnails = next.thumbnails;
|
|
282
|
+
return previous.src === next.src && (previous.type || "auto") === (next.type || "auto") && previous.withCredentials === next.withCredentials && previous.tracks === next.tracks && (previousThumbnails === nextThumbnails || previousThumbnails?.src === nextThumbnails?.src && previousThumbnails?.withCredentials === nextThumbnails?.withCredentials);
|
|
283
|
+
}
|
|
284
|
+
function isSafeMediaUrl(input) {
|
|
285
|
+
if (!input?.trim()) return false;
|
|
286
|
+
try {
|
|
287
|
+
return /^(https?|blob|data):$/.test(new URL(input, "https://reel.invalid").protocol);
|
|
288
|
+
} catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
231
292
|
function looksLikeHls(input) {
|
|
232
293
|
if (!input) return false;
|
|
233
294
|
const lower = input.toLowerCase();
|
|
@@ -856,6 +917,7 @@ var NativeLoader = class {
|
|
|
856
917
|
constructor() {
|
|
857
918
|
this.video = null;
|
|
858
919
|
this.cleanups = [];
|
|
920
|
+
this.rejectPending = null;
|
|
859
921
|
}
|
|
860
922
|
async attach(video, source) {
|
|
861
923
|
this.detach();
|
|
@@ -863,12 +925,14 @@ var NativeLoader = class {
|
|
|
863
925
|
return new Promise((resolve, reject) => {
|
|
864
926
|
const onMeta = () => {
|
|
865
927
|
cleanupListeners();
|
|
928
|
+
this.rejectPending = null;
|
|
866
929
|
resolve();
|
|
867
930
|
};
|
|
868
931
|
const onError = () => {
|
|
869
932
|
cleanupListeners();
|
|
870
933
|
const err = video.error;
|
|
871
934
|
const message = err ? `MediaError code=${err.code} message=${err.message}` : "Native loader failed";
|
|
935
|
+
this.rejectPending = null;
|
|
872
936
|
reject(new Error(message));
|
|
873
937
|
};
|
|
874
938
|
const cleanupListeners = () => {
|
|
@@ -878,17 +942,28 @@ var NativeLoader = class {
|
|
|
878
942
|
video.addEventListener("loadedmetadata", onMeta);
|
|
879
943
|
video.addEventListener("error", onError);
|
|
880
944
|
this.cleanups.push(cleanupListeners);
|
|
945
|
+
this.rejectPending = reject;
|
|
881
946
|
video.src = source.src;
|
|
882
947
|
video.load();
|
|
883
948
|
});
|
|
884
949
|
}
|
|
885
|
-
|
|
950
|
+
abort() {
|
|
951
|
+
const reject = this.rejectPending;
|
|
952
|
+
this.rejectPending = null;
|
|
886
953
|
while (this.cleanups.length) {
|
|
887
954
|
try {
|
|
888
955
|
this.cleanups.pop()?.();
|
|
889
956
|
} catch {
|
|
890
957
|
}
|
|
891
958
|
}
|
|
959
|
+
if (reject) {
|
|
960
|
+
const error = new Error("Native source load aborted");
|
|
961
|
+
error.name = "AbortError";
|
|
962
|
+
reject(error);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
detach() {
|
|
966
|
+
this.abort();
|
|
892
967
|
if (this.video) {
|
|
893
968
|
try {
|
|
894
969
|
this.video.removeAttribute("src");
|
|
@@ -1022,7 +1097,9 @@ var YouTubeLoader = class {
|
|
|
1022
1097
|
}
|
|
1023
1098
|
const host = document.createElement("div");
|
|
1024
1099
|
host.setAttribute("data-reel-yt-host", "");
|
|
1025
|
-
host.style.cssText = "position:absolute;inset:0;
|
|
1100
|
+
host.style.cssText = "position:absolute;inset:0;overflow:hidden;background:#000";
|
|
1101
|
+
const mount = document.createElement("div");
|
|
1102
|
+
host.appendChild(mount);
|
|
1026
1103
|
parent.appendChild(host);
|
|
1027
1104
|
this.host = host;
|
|
1028
1105
|
video.setAttribute(YT_HIDDEN_ATTR, "");
|
|
@@ -1035,7 +1112,7 @@ var YouTubeLoader = class {
|
|
|
1035
1112
|
run();
|
|
1036
1113
|
};
|
|
1037
1114
|
try {
|
|
1038
|
-
this.yt = new YT.Player(
|
|
1115
|
+
this.yt = new YT.Player(mount, {
|
|
1039
1116
|
videoId: id,
|
|
1040
1117
|
// 100% so the iframe fills the host div rather than using default px dimensions.
|
|
1041
1118
|
width: "100%",
|
|
@@ -1051,7 +1128,7 @@ var YouTubeLoader = class {
|
|
|
1051
1128
|
onReady: () => {
|
|
1052
1129
|
const iframe = host.querySelector("iframe");
|
|
1053
1130
|
if (iframe) {
|
|
1054
|
-
iframe.style.cssText = "width:100%;height:100%;border:none;display:block
|
|
1131
|
+
iframe.style.cssText = "width:100%;height:100%;border:none;display:block";
|
|
1055
1132
|
}
|
|
1056
1133
|
try {
|
|
1057
1134
|
this.duration = this.yt?.getDuration() ?? 0;
|
|
@@ -1383,21 +1460,6 @@ function createStore(initial, options = {}) {
|
|
|
1383
1460
|
}
|
|
1384
1461
|
|
|
1385
1462
|
// src/createPlayer.ts
|
|
1386
|
-
var NATIVE_EVENTS = [
|
|
1387
|
-
"loadedmetadata",
|
|
1388
|
-
"durationchange",
|
|
1389
|
-
"timeupdate",
|
|
1390
|
-
"play",
|
|
1391
|
-
"pause",
|
|
1392
|
-
"ended",
|
|
1393
|
-
"error",
|
|
1394
|
-
"volumechange",
|
|
1395
|
-
"ratechange",
|
|
1396
|
-
"seeking",
|
|
1397
|
-
"seeked",
|
|
1398
|
-
"waiting",
|
|
1399
|
-
"playing"
|
|
1400
|
-
];
|
|
1401
1463
|
function makeInitialState(options) {
|
|
1402
1464
|
return {
|
|
1403
1465
|
status: "idle",
|
|
@@ -1430,13 +1492,42 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1430
1492
|
const bus = createEventBus();
|
|
1431
1493
|
const commands = createCommandRegistry();
|
|
1432
1494
|
const controls = createControlsRegistry();
|
|
1495
|
+
const sourceTransforms = createSourceTransformRegistry();
|
|
1496
|
+
const mediaAttachments = createMediaAttachmentRegistry();
|
|
1433
1497
|
let status = "idle";
|
|
1434
1498
|
let video = null;
|
|
1435
1499
|
let activeLoader = null;
|
|
1436
1500
|
let pendingAttachAbort = null;
|
|
1501
|
+
let pendingSourceLoad = null;
|
|
1502
|
+
let latestSourceLoadPromise = null;
|
|
1437
1503
|
let disposed = false;
|
|
1438
|
-
let initialOptionsApplied = false;
|
|
1439
1504
|
let pendingPlay = false;
|
|
1505
|
+
function createPendingSourceLoad() {
|
|
1506
|
+
let resolve;
|
|
1507
|
+
let reject;
|
|
1508
|
+
const promise = new Promise((onResolve, onReject) => {
|
|
1509
|
+
resolve = onResolve;
|
|
1510
|
+
reject = onReject;
|
|
1511
|
+
});
|
|
1512
|
+
void promise.catch(() => void 0);
|
|
1513
|
+
latestSourceLoadPromise = promise;
|
|
1514
|
+
return { promise, resolve, reject };
|
|
1515
|
+
}
|
|
1516
|
+
function resolvePendingSourceLoad() {
|
|
1517
|
+
const load = pendingSourceLoad;
|
|
1518
|
+
if (!load) return;
|
|
1519
|
+
pendingSourceLoad = null;
|
|
1520
|
+
load.resolve();
|
|
1521
|
+
}
|
|
1522
|
+
function rejectPendingSourceLoad(reason) {
|
|
1523
|
+
const load = pendingSourceLoad;
|
|
1524
|
+
if (!load) return;
|
|
1525
|
+
pendingSourceLoad = null;
|
|
1526
|
+
load.reject(reason);
|
|
1527
|
+
}
|
|
1528
|
+
function createAbortError() {
|
|
1529
|
+
return Object.assign(new Error("Aborted"), { name: "AbortError" });
|
|
1530
|
+
}
|
|
1440
1531
|
const YT_PLAYER_STATE_PLAYING = 1;
|
|
1441
1532
|
function getYouTubePlayer() {
|
|
1442
1533
|
return activeLoader?._instance?.() ?? null;
|
|
@@ -1459,11 +1550,12 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1459
1550
|
bus.emit("qualitychange", { quality, auto });
|
|
1460
1551
|
}
|
|
1461
1552
|
commands.add("hls:setQuality", (level) => {
|
|
1462
|
-
if (level
|
|
1463
|
-
|
|
1464
|
-
} else if (level !== null && typeof level === "object" && typeof level.height === "number") {
|
|
1465
|
-
bus.emit("qualitypreference", { qualityHeight: level.height });
|
|
1553
|
+
if (level !== null && (typeof level !== "object" || typeof level.height !== "number")) {
|
|
1554
|
+
return;
|
|
1466
1555
|
}
|
|
1556
|
+
bus.emit("qualitypreference", {
|
|
1557
|
+
qualityHeight: level === null ? null : level.height
|
|
1558
|
+
});
|
|
1467
1559
|
const loader = activeLoader;
|
|
1468
1560
|
if (typeof loader?.setQuality === "function") {
|
|
1469
1561
|
loader.setQuality(level === null ? "auto" : level);
|
|
@@ -1524,7 +1616,9 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1524
1616
|
commands,
|
|
1525
1617
|
controls,
|
|
1526
1618
|
store: { getState: store.getState, subscribe: store.subscribe },
|
|
1527
|
-
bus
|
|
1619
|
+
bus,
|
|
1620
|
+
sourceTransforms,
|
|
1621
|
+
mediaAttachments
|
|
1528
1622
|
});
|
|
1529
1623
|
const plugins = createPluginBus({ player: playerRef, host });
|
|
1530
1624
|
function dispatch(event) {
|
|
@@ -1536,6 +1630,10 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1536
1630
|
for (const effect of result.effects) applyEffect(effect);
|
|
1537
1631
|
if (prev !== "ready" && status === "ready") {
|
|
1538
1632
|
bus.emit("ready", { duration: store.getState().duration });
|
|
1633
|
+
resolvePendingSourceLoad();
|
|
1634
|
+
}
|
|
1635
|
+
if (event.type === "loadFailed") {
|
|
1636
|
+
rejectPendingSourceLoad(event.error);
|
|
1539
1637
|
}
|
|
1540
1638
|
}
|
|
1541
1639
|
function applyEffect(effect) {
|
|
@@ -1579,15 +1677,29 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1579
1677
|
activeQuality: null
|
|
1580
1678
|
});
|
|
1581
1679
|
bus.emit("qualitychange", { quality: null, auto: true });
|
|
1680
|
+
if (!isSafeMediaUrl(source.src) || source.tracks?.some((track) => !isSafeMediaUrl(track.src)) || source.thumbnails && !isSafeMediaUrl(source.thumbnails.src)) {
|
|
1681
|
+
if (video) {
|
|
1682
|
+
pendingSourceLoad = createPendingSourceLoad();
|
|
1683
|
+
}
|
|
1684
|
+
const error = createPlayerError({
|
|
1685
|
+
code: "unsupported",
|
|
1686
|
+
message: "Invalid URL"
|
|
1687
|
+
});
|
|
1688
|
+
dispatch({ type: "loadFailed", error });
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1582
1691
|
if (!video) return;
|
|
1692
|
+
const sourceLoad = createPendingSourceLoad();
|
|
1693
|
+
pendingSourceLoad = sourceLoad;
|
|
1583
1694
|
const provider = registry.resolve(source);
|
|
1584
1695
|
if (!provider) {
|
|
1696
|
+
const error = createPlayerError({
|
|
1697
|
+
code: "unsupported",
|
|
1698
|
+
message: `No source provider for ${source.src}`
|
|
1699
|
+
});
|
|
1585
1700
|
dispatch({
|
|
1586
1701
|
type: "loadFailed",
|
|
1587
|
-
error
|
|
1588
|
-
code: "unsupported",
|
|
1589
|
-
message: `No source provider for ${source.src}`
|
|
1590
|
-
})
|
|
1702
|
+
error
|
|
1591
1703
|
});
|
|
1592
1704
|
return;
|
|
1593
1705
|
}
|
|
@@ -1598,8 +1710,13 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1598
1710
|
aborted = true;
|
|
1599
1711
|
try {
|
|
1600
1712
|
loader.abort?.();
|
|
1601
|
-
} catch (
|
|
1602
|
-
console.error(
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
console.error(error);
|
|
1715
|
+
}
|
|
1716
|
+
if (pendingSourceLoad === sourceLoad) {
|
|
1717
|
+
rejectPendingSourceLoad(createAbortError());
|
|
1718
|
+
} else {
|
|
1719
|
+
sourceLoad.reject(createAbortError());
|
|
1603
1720
|
}
|
|
1604
1721
|
};
|
|
1605
1722
|
loader.attach(video, source).then(() => {
|
|
@@ -1701,8 +1818,6 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1701
1818
|
target.addEventListener(name, handler);
|
|
1702
1819
|
nativeListeners.push(() => target.removeEventListener(name, handler));
|
|
1703
1820
|
}
|
|
1704
|
-
for (const _ of NATIVE_EVENTS) {
|
|
1705
|
-
}
|
|
1706
1821
|
on2("loadedmetadata", () => {
|
|
1707
1822
|
store.setState({
|
|
1708
1823
|
duration: Number.isFinite(target.duration) ? target.duration : 0,
|
|
@@ -1773,17 +1888,24 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1773
1888
|
on2("playing", () => {
|
|
1774
1889
|
bus.emit("buffering", { isBuffering: false });
|
|
1775
1890
|
});
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
};
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1891
|
+
on2("enterpictureinpicture", () => {
|
|
1892
|
+
store.setState({ pip: true });
|
|
1893
|
+
bus.emit("pipchange", { pip: true });
|
|
1894
|
+
});
|
|
1895
|
+
on2("leavepictureinpicture", () => {
|
|
1896
|
+
store.setState({ pip: false });
|
|
1897
|
+
bus.emit("pipchange", { pip: false });
|
|
1898
|
+
});
|
|
1899
|
+
const ownerDocument = target.ownerDocument;
|
|
1900
|
+
const onFullscreenChange = () => {
|
|
1901
|
+
const isFullscreen = !!ownerDocument.fullscreenElement?.contains(target);
|
|
1902
|
+
store.setState({ fullscreen: isFullscreen });
|
|
1903
|
+
bus.emit("fullscreenchange", { fullscreen: isFullscreen });
|
|
1904
|
+
};
|
|
1905
|
+
ownerDocument.addEventListener("fullscreenchange", onFullscreenChange);
|
|
1906
|
+
nativeListeners.push(
|
|
1907
|
+
() => ownerDocument.removeEventListener("fullscreenchange", onFullscreenChange)
|
|
1908
|
+
);
|
|
1787
1909
|
}
|
|
1788
1910
|
function unbindNativeEvents() {
|
|
1789
1911
|
while (nativeListeners.length) {
|
|
@@ -1793,24 +1915,18 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1793
1915
|
}
|
|
1794
1916
|
}
|
|
1795
1917
|
}
|
|
1796
|
-
function
|
|
1797
|
-
if (
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1918
|
+
function applyAttachOptions() {
|
|
1919
|
+
if (!video) return;
|
|
1920
|
+
const state = store.getState();
|
|
1921
|
+
video.volume = state.volume;
|
|
1922
|
+
video.muted = state.muted;
|
|
1923
|
+
video.playbackRate = state.playbackRate;
|
|
1801
1924
|
if (options.poster) video.poster = options.poster;
|
|
1802
1925
|
if (options.preload) video.preload = options.preload;
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
if ("crossOrigin" in options) {
|
|
1808
|
-
if (options.crossOrigin) video.crossOrigin = options.crossOrigin;
|
|
1809
|
-
} else {
|
|
1810
|
-
video.crossOrigin = "anonymous";
|
|
1811
|
-
}
|
|
1812
|
-
if (options.loop) video.loop = true;
|
|
1813
|
-
initialOptionsApplied = true;
|
|
1926
|
+
video.playsInline = options.playsInline ?? true;
|
|
1927
|
+
video.toggleAttribute("webkit-playsinline", video.playsInline);
|
|
1928
|
+
video.crossOrigin = "crossOrigin" in options ? options.crossOrigin ?? null : "anonymous";
|
|
1929
|
+
video.loop = !!options.loop;
|
|
1814
1930
|
}
|
|
1815
1931
|
const attachDisposers = [];
|
|
1816
1932
|
function flushAttachDisposers() {
|
|
@@ -1834,12 +1950,21 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1834
1950
|
}
|
|
1835
1951
|
async function attach(el) {
|
|
1836
1952
|
if (disposed) throw new Error("[@f8team/reel-core] cannot attach: player disposed");
|
|
1837
|
-
if (video === el)
|
|
1953
|
+
if (video === el) {
|
|
1954
|
+
await latestSourceLoadPromise;
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1838
1957
|
if (video) detach();
|
|
1839
1958
|
video = el;
|
|
1959
|
+
mediaAttachments.set({
|
|
1960
|
+
video: el,
|
|
1961
|
+
container: el.closest("[data-reel]") ?? el.parentElement ?? el
|
|
1962
|
+
});
|
|
1840
1963
|
bindNativeEvents(el);
|
|
1841
|
-
|
|
1842
|
-
|
|
1964
|
+
applyAttachOptions();
|
|
1965
|
+
const currentSource = store.getState().source;
|
|
1966
|
+
if (currentSource) setSource(currentSource);
|
|
1967
|
+
const sourceLoadPromise = latestSourceLoadPromise;
|
|
1843
1968
|
const wantStartTime = typeof options.startTime === "number" && options.startTime > 0;
|
|
1844
1969
|
const wantAutoplay = !!options.autoplay && options.autoplay !== "off" && !!options.source;
|
|
1845
1970
|
if (status === "ready") {
|
|
@@ -1861,6 +1986,7 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1861
1986
|
attachDisposers.push(off2);
|
|
1862
1987
|
}
|
|
1863
1988
|
}
|
|
1989
|
+
await sourceLoadPromise;
|
|
1864
1990
|
}
|
|
1865
1991
|
function detach() {
|
|
1866
1992
|
pendingAttachAbort?.();
|
|
@@ -1874,6 +2000,7 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1874
2000
|
}
|
|
1875
2001
|
activeLoader = null;
|
|
1876
2002
|
}
|
|
2003
|
+
mediaAttachments.set(null);
|
|
1877
2004
|
video = null;
|
|
1878
2005
|
}
|
|
1879
2006
|
function dispose() {
|
|
@@ -1888,7 +2015,8 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1888
2015
|
}
|
|
1889
2016
|
function setSource(source) {
|
|
1890
2017
|
pendingPlay = false;
|
|
1891
|
-
|
|
2018
|
+
latestSourceLoadPromise = null;
|
|
2019
|
+
dispatch({ type: "setSource", source: source ? sourceTransforms.apply(source) : null });
|
|
1892
2020
|
}
|
|
1893
2021
|
function getSource() {
|
|
1894
2022
|
return store.getState().source;
|
|
@@ -1896,7 +2024,7 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1896
2024
|
function retry() {
|
|
1897
2025
|
const current = store.getState().source;
|
|
1898
2026
|
if (!current) return false;
|
|
1899
|
-
|
|
2027
|
+
setSource(current);
|
|
1900
2028
|
return true;
|
|
1901
2029
|
}
|
|
1902
2030
|
async function play() {
|
|
@@ -2266,6 +2394,27 @@ function createFocusTrap(container) {
|
|
|
2266
2394
|
return { activate, deactivate };
|
|
2267
2395
|
}
|
|
2268
2396
|
|
|
2397
|
+
// src/a11y/controlsFocus.ts
|
|
2398
|
+
var OPEN_MENU_SELECTOR = "[data-reel-menu-open]";
|
|
2399
|
+
function activeElementInside(container) {
|
|
2400
|
+
const active = (container.ownerDocument ?? document).activeElement;
|
|
2401
|
+
if (!(active instanceof HTMLElement)) return null;
|
|
2402
|
+
return container.contains(active) ? active : null;
|
|
2403
|
+
}
|
|
2404
|
+
function blurFocusInside(container) {
|
|
2405
|
+
activeElementInside(container)?.blur();
|
|
2406
|
+
}
|
|
2407
|
+
function shouldKeepControlsVisible(container) {
|
|
2408
|
+
if (container.querySelector(OPEN_MENU_SELECTOR) !== null) return true;
|
|
2409
|
+
const active = activeElementInside(container);
|
|
2410
|
+
if (!active) return false;
|
|
2411
|
+
try {
|
|
2412
|
+
return active.matches(":focus-visible");
|
|
2413
|
+
} catch {
|
|
2414
|
+
return false;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2269
2418
|
// src/util/time.ts
|
|
2270
2419
|
var TIME_RE = /^(?:(\d+):)?(\d{1,2}):(\d{1,2})(?:\.(\d+))?$/;
|
|
2271
2420
|
function parseTime(input) {
|
|
@@ -2304,16 +2453,20 @@ exports.THEME_PRESETS = THEME_PRESETS;
|
|
|
2304
2453
|
exports.TOKEN_TO_CSS_VAR = TOKEN_TO_CSS_VAR;
|
|
2305
2454
|
exports.announce = announce;
|
|
2306
2455
|
exports.applyTheme = applyTheme;
|
|
2456
|
+
exports.areSourceDescriptorsEquivalent = areSourceDescriptorsEquivalent;
|
|
2457
|
+
exports.blurFocusInside = blurFocusInside;
|
|
2307
2458
|
exports.createFocusTrap = createFocusTrap;
|
|
2308
2459
|
exports.createPlayer = createPlayer;
|
|
2309
2460
|
exports.definePlugin = definePlugin;
|
|
2310
2461
|
exports.detectSourceType = detectSourceType;
|
|
2311
2462
|
exports.formatTime = formatTime;
|
|
2463
|
+
exports.isSafeMediaUrl = isSafeMediaUrl;
|
|
2312
2464
|
exports.looksLikeHls = looksLikeHls;
|
|
2313
2465
|
exports.looksLikeProgressive = looksLikeProgressive;
|
|
2314
2466
|
exports.looksLikeYouTube = looksLikeYouTube;
|
|
2315
2467
|
exports.parseTime = parseTime;
|
|
2316
2468
|
exports.resolveTokens = resolveTokens;
|
|
2469
|
+
exports.shouldKeepControlsVisible = shouldKeepControlsVisible;
|
|
2317
2470
|
exports.toSeconds = toSeconds;
|
|
2318
2471
|
//# sourceMappingURL=index.cjs.map
|
|
2319
2472
|
//# sourceMappingURL=index.cjs.map
|