@f8team/reel-core 1.0.1 → 1.0.3
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 +18 -12
- package/dist/index.cjs +74 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +74 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @f8team/reel-core
|
|
2
2
|
|
|
3
|
+
## 1.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Stabilize YouTube playback state by preventing hidden native media timeupdate events from overriding the YouTube clock.
|
|
8
|
+
|
|
3
9
|
## 1.0.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -18,46 +24,46 @@
|
|
|
18
24
|
publishable as OSS.
|
|
19
25
|
|
|
20
26
|
## Public API additions (minor)
|
|
21
|
-
- **`@f8team/reel-core`**
|
|
27
|
+
- **`@f8team/reel-core`** - `Player.retry(): boolean` re-attempts the last
|
|
22
28
|
source. Returns `false` when there is nothing to retry. Useful for
|
|
23
29
|
"Try again" buttons on error overlays.
|
|
24
|
-
- **`@f8team/reel-core`**
|
|
30
|
+
- **`@f8team/reel-core`** - `SourceLoader.abort?()` (optional) lets the
|
|
25
31
|
orchestrator cancel in-flight network work on rapid `setSource` swaps
|
|
26
32
|
without tearing down the underlying media element. Both built-in HLS and
|
|
27
33
|
YouTube loaders implement it.
|
|
28
|
-
- **`@f8team/reel-react`**
|
|
34
|
+
- **`@f8team/reel-react`** - `<Player.Root labels>` prop + `defaultLabels` /
|
|
29
35
|
`vietnameseLabels` presets + `useLabels()` hook for ARIA / time-formatter
|
|
30
36
|
i18n.
|
|
31
|
-
- **`@f8team/reel-plugin-resume-position`**
|
|
37
|
+
- **`@f8team/reel-plugin-resume-position`** - `keyFn(source) => string | null`
|
|
32
38
|
(signed-URL-friendly), `saveIntervalMs` (default 10s, periodic save while
|
|
33
39
|
playing).
|
|
34
|
-
- **`@f8team/reel-plugin-auth-aware`**
|
|
40
|
+
- **`@f8team/reel-plugin-auth-aware`** - `allowlist` now accepts
|
|
35
41
|
`string | RegExp | (url: string) => boolean` per entry; new
|
|
36
42
|
`AuthAwareAllowlistEntry` exported type.
|
|
37
|
-
- **`@f8team/reel-themes`**
|
|
43
|
+
- **`@f8team/reel-themes`** - `f8-brand.css` opt-in layer for F8 orange
|
|
38
44
|
branding on top of any base theme.
|
|
39
45
|
|
|
40
46
|
## Production robustness (patch + minor)
|
|
41
|
-
- **HLS**
|
|
47
|
+
- **HLS** - listener-leak fix on per-segment XHRs; manifest-load timeout
|
|
42
48
|
(default 30s) so a stalled manifest doesn't pin the player in `loading`;
|
|
43
49
|
non-fatal HTTP 4xx/5xx now surface as load errors; native fallback path
|
|
44
50
|
cleans up listeners on early `detach()`.
|
|
45
|
-
- **YouTube**
|
|
51
|
+
- **YouTube** - bridges polled `getCurrentTime` / `getDuration` into the
|
|
46
52
|
core store and event bus (seek bars and time-aware plugins now tick on
|
|
47
53
|
YouTube sources); marks the underlying `<video>` via the
|
|
48
54
|
`data-reel-yt-hidden` attribute instead of mutating inline styles
|
|
49
55
|
(host CSS no longer fights the loader); warns once when the player's
|
|
50
56
|
parent is `position: static`.
|
|
51
|
-
- **createPlayer**
|
|
57
|
+
- **createPlayer** - autoplay / startTime no longer leak listeners across
|
|
52
58
|
re-attach cycles, and re-attach to an already-`ready` player now applies
|
|
53
59
|
them synchronously instead of waiting for a transition that already fired.
|
|
54
|
-
- **plugin-keyboard**
|
|
60
|
+
- **plugin-keyboard** - fixes container-scope fallback; skips `contenteditable`
|
|
55
61
|
regions, `role="textbox"`, and IME composition (no more accidental Space
|
|
56
62
|
toggles while typing in a rich-text editor).
|
|
57
|
-
- **plugin-resume-position**
|
|
63
|
+
- **plugin-resume-position** - SSR-safe (no-op when `window` is undefined);
|
|
58
64
|
also saves on `pagehide` / `visibilitychange→hidden` for iOS Safari, plus
|
|
59
65
|
periodic save while playing.
|
|
60
|
-
- **plugin-auth-aware**
|
|
66
|
+
- **plugin-auth-aware** - emits a one-time console warning when a string
|
|
61
67
|
allowlist entry ends with `.`, to surface a class of consumer mistakes
|
|
62
68
|
(`"https://api-gateway."` literal vs `/^https:\/\/api-gateway\./` regex).
|
|
63
69
|
|
package/dist/index.cjs
CHANGED
|
@@ -113,16 +113,16 @@ function createPluginBus({ player, host }) {
|
|
|
113
113
|
if (findIndex(plugin.name) >= 0) {
|
|
114
114
|
throw new Error(`[@f8team/reel-core] plugin "${plugin.name}" is already registered`);
|
|
115
115
|
}
|
|
116
|
-
let
|
|
116
|
+
let setupResult;
|
|
117
117
|
try {
|
|
118
|
-
|
|
119
|
-
teardown = typeof result === "function" ? result : null;
|
|
118
|
+
setupResult = plugin.setup(player, host);
|
|
120
119
|
} catch (err) {
|
|
121
120
|
throw new Error(
|
|
122
121
|
`[@f8team/reel-core] plugin "${plugin.name}" setup threw: ${err.message}`,
|
|
123
122
|
{ cause: err }
|
|
124
123
|
);
|
|
125
124
|
}
|
|
125
|
+
const teardown = typeof setupResult === "function" ? setupResult : null;
|
|
126
126
|
entries.push({ plugin, teardown });
|
|
127
127
|
}
|
|
128
128
|
function unregister(name) {
|
|
@@ -1210,6 +1210,10 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1210
1210
|
let disposed = false;
|
|
1211
1211
|
let initialOptionsApplied = false;
|
|
1212
1212
|
let pendingPlay = false;
|
|
1213
|
+
const YT_PLAYER_STATE_PLAYING = 1;
|
|
1214
|
+
function getYouTubePlayer() {
|
|
1215
|
+
return activeLoader?._instance?.() ?? null;
|
|
1216
|
+
}
|
|
1213
1217
|
const registry = createSourceRegistry();
|
|
1214
1218
|
function emitUnauthorized(payload) {
|
|
1215
1219
|
bus.emit("unauthorized", payload);
|
|
@@ -1263,6 +1267,20 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1263
1267
|
onDuration: (duration) => {
|
|
1264
1268
|
store.setState({ duration });
|
|
1265
1269
|
bus.emit("durationchange", { duration });
|
|
1270
|
+
},
|
|
1271
|
+
onStateChange: (ytState) => {
|
|
1272
|
+
if (ytState === "playing") {
|
|
1273
|
+
bus.emit("play");
|
|
1274
|
+
if (status !== "playing" && status !== "loading") dispatch({ type: "play" });
|
|
1275
|
+
} else if (ytState === "paused") {
|
|
1276
|
+
bus.emit("pause");
|
|
1277
|
+
if (status === "playing") dispatch({ type: "pause" });
|
|
1278
|
+
} else if (ytState === "ended") {
|
|
1279
|
+
bus.emit("ended");
|
|
1280
|
+
if (status !== "ended") dispatch({ type: "ended" });
|
|
1281
|
+
} else if (ytState === "buffering") {
|
|
1282
|
+
bus.emit("buffering", { isBuffering: true });
|
|
1283
|
+
}
|
|
1266
1284
|
}
|
|
1267
1285
|
})
|
|
1268
1286
|
);
|
|
@@ -1388,6 +1406,14 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1388
1406
|
}
|
|
1389
1407
|
function runMediaPlay() {
|
|
1390
1408
|
if (!video) return;
|
|
1409
|
+
const yt = getYouTubePlayer();
|
|
1410
|
+
if (yt) {
|
|
1411
|
+
try {
|
|
1412
|
+
yt.playVideo();
|
|
1413
|
+
} catch {
|
|
1414
|
+
}
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1391
1417
|
const result = video.play();
|
|
1392
1418
|
if (result && typeof result.then === "function") {
|
|
1393
1419
|
result.catch((err) => {
|
|
@@ -1400,6 +1426,14 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1400
1426
|
}
|
|
1401
1427
|
function runMediaPause() {
|
|
1402
1428
|
if (!video) return;
|
|
1429
|
+
const yt = getYouTubePlayer();
|
|
1430
|
+
if (yt) {
|
|
1431
|
+
try {
|
|
1432
|
+
yt.pauseVideo();
|
|
1433
|
+
} catch {
|
|
1434
|
+
}
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1403
1437
|
try {
|
|
1404
1438
|
video.pause();
|
|
1405
1439
|
} catch {
|
|
@@ -1407,6 +1441,20 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1407
1441
|
}
|
|
1408
1442
|
function runMediaSeek(seconds) {
|
|
1409
1443
|
if (!video) return;
|
|
1444
|
+
const yt = getYouTubePlayer();
|
|
1445
|
+
if (yt) {
|
|
1446
|
+
try {
|
|
1447
|
+
yt.seekTo(seconds, true);
|
|
1448
|
+
store.setState({ currentTime: seconds });
|
|
1449
|
+
bus.emit("timeupdate", {
|
|
1450
|
+
currentTime: seconds,
|
|
1451
|
+
playedSeconds: seconds,
|
|
1452
|
+
duration: store.getState().duration
|
|
1453
|
+
});
|
|
1454
|
+
} catch {
|
|
1455
|
+
}
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1410
1458
|
try {
|
|
1411
1459
|
video.currentTime = seconds;
|
|
1412
1460
|
} catch {
|
|
@@ -1436,6 +1484,7 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1436
1484
|
bus.emit("durationchange", { duration });
|
|
1437
1485
|
});
|
|
1438
1486
|
on2("timeupdate", () => {
|
|
1487
|
+
if (getYouTubePlayer()) return;
|
|
1439
1488
|
const currentTime = target.currentTime;
|
|
1440
1489
|
store.setState({ currentTime, buffered: bufferedToRanges(target.buffered) });
|
|
1441
1490
|
bus.emit("timeupdate", {
|
|
@@ -1652,6 +1701,14 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1652
1701
|
}
|
|
1653
1702
|
function paused() {
|
|
1654
1703
|
if (!video) return true;
|
|
1704
|
+
const yt = getYouTubePlayer();
|
|
1705
|
+
if (yt) {
|
|
1706
|
+
try {
|
|
1707
|
+
return yt.getPlayerState() !== YT_PLAYER_STATE_PLAYING;
|
|
1708
|
+
} catch {
|
|
1709
|
+
return true;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1655
1712
|
return video.paused;
|
|
1656
1713
|
}
|
|
1657
1714
|
function seekTo(seconds) {
|
|
@@ -1659,6 +1716,20 @@ function createPlayer(options = {}, internal = {}) {
|
|
|
1659
1716
|
if (!video) return;
|
|
1660
1717
|
const duration = Number.isFinite(video.duration) ? video.duration : Number.POSITIVE_INFINITY;
|
|
1661
1718
|
const clamped = Math.max(0, Math.min(seconds, duration));
|
|
1719
|
+
const yt = getYouTubePlayer();
|
|
1720
|
+
if (yt) {
|
|
1721
|
+
try {
|
|
1722
|
+
yt.seekTo(clamped, true);
|
|
1723
|
+
store.setState({ currentTime: clamped });
|
|
1724
|
+
bus.emit("timeupdate", {
|
|
1725
|
+
currentTime: clamped,
|
|
1726
|
+
playedSeconds: clamped,
|
|
1727
|
+
duration: store.getState().duration
|
|
1728
|
+
});
|
|
1729
|
+
} catch {
|
|
1730
|
+
}
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1662
1733
|
video.currentTime = clamped;
|
|
1663
1734
|
}
|
|
1664
1735
|
function syncVolumeMutedStoreFromVideo() {
|