@xhub-reels/sdk 0.1.15 → 0.1.17
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/README.md +11 -11
- package/dist/index.cjs +342 -343
- package/dist/index.d.cts +89 -38
- package/dist/index.d.ts +89 -38
- package/dist/index.js +342 -343
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -833,6 +833,21 @@ var ResourceGovernor = class {
|
|
|
833
833
|
isPreloading(index) {
|
|
834
834
|
return this.store.getState().preloadQueue.includes(index);
|
|
835
835
|
}
|
|
836
|
+
/**
|
|
837
|
+
* Returns the buffer tier (1–4) for a given index.
|
|
838
|
+
* - 1: Active (playing, 10s buffer)
|
|
839
|
+
* - 2: Hot (±bufferWindow, 2s buffer, instant swap)
|
|
840
|
+
* - 3: Warm (manifest + 1 segment, ~300ms show)
|
|
841
|
+
* - 4: Cold (preload queue — manifest in HTTP cache, no DOM)
|
|
842
|
+
*/
|
|
843
|
+
getTier(index) {
|
|
844
|
+
const { focusedIndex, activeAllocations, warmAllocations, preloadQueue } = this.store.getState();
|
|
845
|
+
if (index === focusedIndex) return 1;
|
|
846
|
+
if (activeAllocations.has(index)) return 2;
|
|
847
|
+
if (warmAllocations.has(index)) return 3;
|
|
848
|
+
if (preloadQueue.includes(index)) return 4;
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
836
851
|
getActiveAllocations() {
|
|
837
852
|
return [...this.store.getState().activeAllocations];
|
|
838
853
|
}
|
|
@@ -905,7 +920,9 @@ function usePointerGesture(config = {}) {
|
|
|
905
920
|
containerSize,
|
|
906
921
|
dragThresholdRatio = 0.5,
|
|
907
922
|
onSnap,
|
|
908
|
-
onBounceBack
|
|
923
|
+
onBounceBack,
|
|
924
|
+
onDragStart,
|
|
925
|
+
onDragEnd
|
|
909
926
|
} = config;
|
|
910
927
|
const isDraggingRef = react.useRef(false);
|
|
911
928
|
const dragOffsetRef = react.useRef(0);
|
|
@@ -922,6 +939,8 @@ function usePointerGesture(config = {}) {
|
|
|
922
939
|
const onDragThresholdRef = react.useRef(onDragThreshold);
|
|
923
940
|
const onSnapRef = react.useRef(onSnap);
|
|
924
941
|
const onBounceBackRef = react.useRef(onBounceBack);
|
|
942
|
+
const onDragStartRef = react.useRef(onDragStart);
|
|
943
|
+
const onDragEndRef = react.useRef(onDragEnd);
|
|
925
944
|
const disabledRef = react.useRef(disabled);
|
|
926
945
|
const containerSizeRef = react.useRef(containerSize);
|
|
927
946
|
const dragThresholdRatioRef = react.useRef(dragThresholdRatio);
|
|
@@ -930,6 +949,8 @@ function usePointerGesture(config = {}) {
|
|
|
930
949
|
onDragThresholdRef.current = onDragThreshold;
|
|
931
950
|
onSnapRef.current = onSnap;
|
|
932
951
|
onBounceBackRef.current = onBounceBack;
|
|
952
|
+
onDragStartRef.current = onDragStart;
|
|
953
|
+
onDragEndRef.current = onDragEnd;
|
|
933
954
|
disabledRef.current = disabled;
|
|
934
955
|
containerSizeRef.current = containerSize;
|
|
935
956
|
dragThresholdRatioRef.current = dragThresholdRatio;
|
|
@@ -996,6 +1017,7 @@ function usePointerGesture(config = {}) {
|
|
|
996
1017
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
997
1018
|
window.removeEventListener("pointerup", handlePointerUp);
|
|
998
1019
|
window.removeEventListener("pointercancel", handlePointerUp);
|
|
1020
|
+
onDragEndRef.current?.();
|
|
999
1021
|
const offset = dragOffsetRef.current;
|
|
1000
1022
|
const velocity = velocityRef.current;
|
|
1001
1023
|
const shouldSnap = Math.abs(velocity) > velocityThreshold || Math.abs(offset) > distanceThreshold;
|
|
@@ -1027,6 +1049,7 @@ function usePointerGesture(config = {}) {
|
|
|
1027
1049
|
lastTimeRef.current = performance.now();
|
|
1028
1050
|
velocityRef.current = 0;
|
|
1029
1051
|
dragOffsetRef.current = 0;
|
|
1052
|
+
onDragStartRef.current?.();
|
|
1030
1053
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1031
1054
|
window.addEventListener("pointermove", handlePointerMove, { passive: true });
|
|
1032
1055
|
window.addEventListener("pointerup", handlePointerUp);
|
|
@@ -1244,6 +1267,7 @@ function useResource() {
|
|
|
1244
1267
|
const networkType = useResourceSelector((s) => s.networkType);
|
|
1245
1268
|
const isActive = useResourceSelector((s) => s.isActive);
|
|
1246
1269
|
const prefetchIndex = useResourceSelector((s) => s.prefetchIndex);
|
|
1270
|
+
const preloadQueue = useResourceSelector((s) => s.preloadQueue);
|
|
1247
1271
|
const activeIndices = react.useMemo(() => [...activeAllocations], [activeAllocations]);
|
|
1248
1272
|
const warmIndices = react.useMemo(() => [...warmAllocations], [warmAllocations]);
|
|
1249
1273
|
const setFocusedIndex = react.useCallback(
|
|
@@ -1274,6 +1298,10 @@ function useResource() {
|
|
|
1274
1298
|
(i) => resourceGovernor.setPrefetchIndex(i),
|
|
1275
1299
|
[resourceGovernor]
|
|
1276
1300
|
);
|
|
1301
|
+
const getTier = react.useCallback(
|
|
1302
|
+
(index) => resourceGovernor.getTier(index),
|
|
1303
|
+
[resourceGovernor]
|
|
1304
|
+
);
|
|
1277
1305
|
return {
|
|
1278
1306
|
activeIndices,
|
|
1279
1307
|
warmIndices,
|
|
@@ -1282,13 +1310,15 @@ function useResource() {
|
|
|
1282
1310
|
networkType,
|
|
1283
1311
|
isActive,
|
|
1284
1312
|
prefetchIndex,
|
|
1313
|
+
preloadQueue,
|
|
1285
1314
|
setFocusedIndex,
|
|
1286
1315
|
setFocusedIndexImmediate,
|
|
1287
1316
|
setTotalItems,
|
|
1288
1317
|
shouldRenderVideo,
|
|
1289
1318
|
isAllocated,
|
|
1290
1319
|
isWarmAllocated,
|
|
1291
|
-
setPrefetchIndex
|
|
1320
|
+
setPrefetchIndex,
|
|
1321
|
+
getTier
|
|
1292
1322
|
};
|
|
1293
1323
|
}
|
|
1294
1324
|
var ACTIVE_HLS_DEFAULTS = {
|
|
@@ -1364,18 +1394,9 @@ function mapHlsError(data) {
|
|
|
1364
1394
|
}
|
|
1365
1395
|
function useHls(options) {
|
|
1366
1396
|
const { src, videoRef, isActive, isPrefetch, bufferTier = "active", hlsConfig, onError } = options;
|
|
1367
|
-
const
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
const hlsSupported = Hls__default.default.isSupported();
|
|
1371
|
-
const native = supportsNativeHls();
|
|
1372
|
-
setIsHlsJs(hlsSupported && !native);
|
|
1373
|
-
setIsNativeHls(native);
|
|
1374
|
-
}, []);
|
|
1375
|
-
const isHlsJsRef = react.useRef(false);
|
|
1376
|
-
const isNativeRef = react.useRef(false);
|
|
1377
|
-
isHlsJsRef.current = isHlsJs;
|
|
1378
|
-
isNativeRef.current = isNativeHls;
|
|
1397
|
+
const isHlsSupported = typeof window !== "undefined" && Hls__default.default.isSupported();
|
|
1398
|
+
const isNative = supportsNativeHls();
|
|
1399
|
+
const isHlsJs = isHlsSupported && !isNative;
|
|
1379
1400
|
const [isReady, setIsReady] = react.useState(false);
|
|
1380
1401
|
const hlsRef = react.useRef(null);
|
|
1381
1402
|
const onErrorRef = react.useRef(onError);
|
|
@@ -1400,18 +1421,7 @@ function useHls(options) {
|
|
|
1400
1421
|
currentSrcRef.current = void 0;
|
|
1401
1422
|
return;
|
|
1402
1423
|
}
|
|
1403
|
-
const isNative = isNativeRef.current;
|
|
1404
|
-
const isHlsSupported = isHlsJsRef.current;
|
|
1405
1424
|
if (!isActive && !isPrefetch) {
|
|
1406
|
-
if (isNative) {
|
|
1407
|
-
if (video.src) {
|
|
1408
|
-
video.removeAttribute("src");
|
|
1409
|
-
video.load();
|
|
1410
|
-
}
|
|
1411
|
-
setIsReady(false);
|
|
1412
|
-
currentSrcRef.current = void 0;
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
1425
|
destroy();
|
|
1416
1426
|
setIsReady(false);
|
|
1417
1427
|
canPlayFiredRef.current = false;
|
|
@@ -1419,45 +1429,40 @@ function useHls(options) {
|
|
|
1419
1429
|
return;
|
|
1420
1430
|
}
|
|
1421
1431
|
if (isNative) {
|
|
1422
|
-
if (
|
|
1423
|
-
|
|
1424
|
-
setIsReady(true);
|
|
1425
|
-
return void 0;
|
|
1426
|
-
}
|
|
1427
|
-
const handleCanPlayReuse = () => setIsReady(true);
|
|
1428
|
-
video.addEventListener("canplay", handleCanPlayReuse, { once: true });
|
|
1429
|
-
video.addEventListener("loadeddata", handleCanPlayReuse, { once: true });
|
|
1430
|
-
video.addEventListener("playing", handleCanPlayReuse, { once: true });
|
|
1431
|
-
if (video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1432
|
-
video.load();
|
|
1433
|
-
}
|
|
1434
|
-
return () => {
|
|
1435
|
-
video.removeEventListener("canplay", handleCanPlayReuse);
|
|
1436
|
-
video.removeEventListener("loadeddata", handleCanPlayReuse);
|
|
1437
|
-
video.removeEventListener("playing", handleCanPlayReuse);
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
video.src = src;
|
|
1441
|
-
currentSrcRef.current = src;
|
|
1442
|
-
if (!isActive) {
|
|
1432
|
+
if (video.src !== src) {
|
|
1433
|
+
video.src = src;
|
|
1443
1434
|
video.load();
|
|
1444
1435
|
}
|
|
1445
|
-
if (video.
|
|
1436
|
+
if (!video.hasAttribute("webkit-playsinline")) {
|
|
1437
|
+
video.setAttribute("webkit-playsinline", "");
|
|
1438
|
+
}
|
|
1439
|
+
if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
|
|
1446
1440
|
setIsReady(true);
|
|
1447
|
-
|
|
1441
|
+
currentSrcRef.current = src;
|
|
1442
|
+
return;
|
|
1448
1443
|
}
|
|
1449
1444
|
setIsReady(false);
|
|
1450
|
-
|
|
1445
|
+
canPlayFiredRef.current = false;
|
|
1446
|
+
currentSrcRef.current = src;
|
|
1447
|
+
const handleCanPlay2 = () => {
|
|
1448
|
+
canPlayFiredRef.current = true;
|
|
1449
|
+
setIsReady(true);
|
|
1450
|
+
};
|
|
1451
|
+
const handleLoadedData = () => {
|
|
1452
|
+
if (!canPlayFiredRef.current) {
|
|
1453
|
+
canPlayFiredRef.current = true;
|
|
1454
|
+
setIsReady(true);
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1451
1457
|
video.addEventListener("canplay", handleCanPlay2, { once: true });
|
|
1452
|
-
video.addEventListener("loadeddata",
|
|
1453
|
-
video.addEventListener("playing", handleCanPlay2, { once: true });
|
|
1458
|
+
video.addEventListener("loadeddata", handleLoadedData, { once: true });
|
|
1454
1459
|
return () => {
|
|
1455
1460
|
video.removeEventListener("canplay", handleCanPlay2);
|
|
1456
|
-
video.removeEventListener("loadeddata",
|
|
1457
|
-
video.removeEventListener("playing", handleCanPlay2);
|
|
1461
|
+
video.removeEventListener("loadeddata", handleLoadedData);
|
|
1458
1462
|
};
|
|
1459
1463
|
}
|
|
1460
1464
|
if (!isHlsSupported) {
|
|
1465
|
+
onErrorRef.current?.("UNKNOWN", "HLS playback not supported in this browser");
|
|
1461
1466
|
return;
|
|
1462
1467
|
}
|
|
1463
1468
|
if (hlsRef.current && currentSrcRef.current === src) {
|
|
@@ -1521,7 +1526,7 @@ function useHls(options) {
|
|
|
1521
1526
|
currentSrcRef.current = void 0;
|
|
1522
1527
|
}
|
|
1523
1528
|
};
|
|
1524
|
-
}, [src, isActive, isPrefetch
|
|
1529
|
+
}, [src, isActive, isPrefetch]);
|
|
1525
1530
|
react.useEffect(() => {
|
|
1526
1531
|
const hls = hlsRef.current;
|
|
1527
1532
|
if (!hls) {
|
|
@@ -1537,10 +1542,15 @@ function useHls(options) {
|
|
|
1537
1542
|
for (const key of configKeys) {
|
|
1538
1543
|
hlsAnyConfig[key] = newConfig[key];
|
|
1539
1544
|
}
|
|
1545
|
+
if (prevTier === "warm" && bufferTier === "active") {
|
|
1546
|
+
try {
|
|
1547
|
+
hls.startLoad();
|
|
1548
|
+
} catch {
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1540
1551
|
}, [bufferTier]);
|
|
1541
1552
|
return {
|
|
1542
1553
|
isHlsJs,
|
|
1543
|
-
isNativeHls,
|
|
1544
1554
|
isReady,
|
|
1545
1555
|
destroy
|
|
1546
1556
|
};
|
|
@@ -1659,43 +1669,60 @@ function skeletonCircle(size) {
|
|
|
1659
1669
|
background: "rgba(255,255,255,0.1)"
|
|
1660
1670
|
};
|
|
1661
1671
|
}
|
|
1662
|
-
function
|
|
1663
|
-
|
|
1664
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1672
|
+
function DefaultPauseAction() {
|
|
1673
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1665
1674
|
"div",
|
|
1666
1675
|
{
|
|
1667
1676
|
style: {
|
|
1668
|
-
|
|
1669
|
-
|
|
1677
|
+
width: 72,
|
|
1678
|
+
height: 72,
|
|
1679
|
+
borderRadius: "50%",
|
|
1680
|
+
background: "rgba(0, 0, 0, 0.55)",
|
|
1670
1681
|
display: "flex",
|
|
1671
1682
|
alignItems: "center",
|
|
1672
1683
|
justifyContent: "center",
|
|
1673
1684
|
pointerEvents: "none",
|
|
1674
|
-
|
|
1685
|
+
// Inset the triangle visually — triangles look off-center without this
|
|
1686
|
+
paddingLeft: 6
|
|
1675
1687
|
},
|
|
1676
|
-
children:
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
}
|
|
1694
|
-
` })
|
|
1695
|
-
]
|
|
1688
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1689
|
+
"svg",
|
|
1690
|
+
{
|
|
1691
|
+
width: "32",
|
|
1692
|
+
height: "32",
|
|
1693
|
+
viewBox: "0 0 32 32",
|
|
1694
|
+
fill: "none",
|
|
1695
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1696
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1697
|
+
"path",
|
|
1698
|
+
{
|
|
1699
|
+
d: "M8 5.5L27 16L8 26.5V5.5Z",
|
|
1700
|
+
fill: "white"
|
|
1701
|
+
}
|
|
1702
|
+
)
|
|
1703
|
+
}
|
|
1704
|
+
)
|
|
1696
1705
|
}
|
|
1697
1706
|
);
|
|
1698
1707
|
}
|
|
1708
|
+
var PLAY_AHEAD_MAX_CONCURRENT = 2;
|
|
1709
|
+
var _playAheadActive = 0;
|
|
1710
|
+
var _playAheadQueue = [];
|
|
1711
|
+
function acquirePlayAhead() {
|
|
1712
|
+
if (_playAheadActive < PLAY_AHEAD_MAX_CONCURRENT) {
|
|
1713
|
+
_playAheadActive++;
|
|
1714
|
+
return Promise.resolve();
|
|
1715
|
+
}
|
|
1716
|
+
return new Promise((resolve) => _playAheadQueue.push(resolve));
|
|
1717
|
+
}
|
|
1718
|
+
function releasePlayAhead() {
|
|
1719
|
+
_playAheadActive = Math.max(0, _playAheadActive - 1);
|
|
1720
|
+
const next = _playAheadQueue.shift();
|
|
1721
|
+
if (next) {
|
|
1722
|
+
_playAheadActive++;
|
|
1723
|
+
next();
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1699
1726
|
function VideoSlot({
|
|
1700
1727
|
item,
|
|
1701
1728
|
index,
|
|
@@ -1705,11 +1732,11 @@ function VideoSlot({
|
|
|
1705
1732
|
bufferTier,
|
|
1706
1733
|
isMuted,
|
|
1707
1734
|
onToggleMute,
|
|
1735
|
+
onAutoplayBlocked,
|
|
1708
1736
|
showFps = false,
|
|
1709
1737
|
renderOverlay,
|
|
1710
1738
|
renderActions,
|
|
1711
|
-
|
|
1712
|
-
renderDoubleTap
|
|
1739
|
+
renderPauseAction
|
|
1713
1740
|
}) {
|
|
1714
1741
|
const { optimisticManager, adapters } = useSDK();
|
|
1715
1742
|
if (!isVideoItem(item)) {
|
|
@@ -1742,11 +1769,11 @@ function VideoSlot({
|
|
|
1742
1769
|
bufferTier,
|
|
1743
1770
|
isMuted,
|
|
1744
1771
|
onToggleMute,
|
|
1772
|
+
onAutoplayBlocked,
|
|
1745
1773
|
showFps,
|
|
1746
1774
|
renderOverlay,
|
|
1747
1775
|
renderActions,
|
|
1748
|
-
|
|
1749
|
-
renderDoubleTap,
|
|
1776
|
+
renderPauseAction,
|
|
1750
1777
|
optimisticManager,
|
|
1751
1778
|
adapters
|
|
1752
1779
|
}
|
|
@@ -1761,11 +1788,11 @@ function VideoSlotInner({
|
|
|
1761
1788
|
bufferTier,
|
|
1762
1789
|
isMuted,
|
|
1763
1790
|
onToggleMute,
|
|
1791
|
+
onAutoplayBlocked,
|
|
1764
1792
|
showFps,
|
|
1765
1793
|
renderOverlay,
|
|
1766
1794
|
renderActions,
|
|
1767
|
-
|
|
1768
|
-
renderDoubleTap,
|
|
1795
|
+
renderPauseAction,
|
|
1769
1796
|
optimisticManager,
|
|
1770
1797
|
adapters
|
|
1771
1798
|
}) {
|
|
@@ -1776,7 +1803,7 @@ function VideoSlotInner({
|
|
|
1776
1803
|
const isHlsSource = sourceType === "hls";
|
|
1777
1804
|
const hlsSrc = isHlsSource && shouldLoadSrc ? src : void 0;
|
|
1778
1805
|
const mp4Src = !isHlsSource && shouldLoadSrc ? src : void 0;
|
|
1779
|
-
const { isReady: hlsReady
|
|
1806
|
+
const { isReady: hlsReady } = useHls({
|
|
1780
1807
|
src: hlsSrc,
|
|
1781
1808
|
videoRef,
|
|
1782
1809
|
isActive,
|
|
@@ -1815,9 +1842,7 @@ function VideoSlotInner({
|
|
|
1815
1842
|
}, [mp4Src, isActive, isPrefetch, isPreloaded, isHlsSource]);
|
|
1816
1843
|
const isReady = isHlsSource ? hlsReady : mp4Ready;
|
|
1817
1844
|
const [hasPlayedAhead, setHasPlayedAhead] = react.useState(false);
|
|
1818
|
-
const canPlayAhead = isHlsSource && !isNativeHls;
|
|
1819
1845
|
react.useEffect(() => {
|
|
1820
|
-
if (!canPlayAhead) return;
|
|
1821
1846
|
const video = videoRef.current;
|
|
1822
1847
|
if (!video) return;
|
|
1823
1848
|
if (isActive || !isReady) return;
|
|
@@ -1825,206 +1850,145 @@ function VideoSlotInner({
|
|
|
1825
1850
|
const prevMuted = video.muted;
|
|
1826
1851
|
video.muted = true;
|
|
1827
1852
|
let cancelled = false;
|
|
1828
|
-
let rafId = null;
|
|
1829
|
-
let vfcHandle = null;
|
|
1830
|
-
const pauseAfterDecode = () => {
|
|
1831
|
-
if (cancelled) return;
|
|
1832
|
-
video.pause();
|
|
1833
|
-
video.currentTime = 0;
|
|
1834
|
-
video.muted = prevMuted;
|
|
1835
|
-
setHasPlayedAhead(true);
|
|
1836
|
-
};
|
|
1837
1853
|
const doPlayAhead = async () => {
|
|
1854
|
+
await acquirePlayAhead();
|
|
1838
1855
|
try {
|
|
1839
1856
|
await video.play();
|
|
1840
|
-
if (cancelled)
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
pauseAfterDecode();
|
|
1845
|
-
});
|
|
1846
|
-
} else {
|
|
1847
|
-
rafId = requestAnimationFrame(() => {
|
|
1848
|
-
rafId = requestAnimationFrame(() => {
|
|
1849
|
-
rafId = null;
|
|
1850
|
-
pauseAfterDecode();
|
|
1851
|
-
});
|
|
1852
|
-
});
|
|
1857
|
+
if (cancelled) {
|
|
1858
|
+
video.pause();
|
|
1859
|
+
releasePlayAhead();
|
|
1860
|
+
return;
|
|
1853
1861
|
}
|
|
1862
|
+
const pauseAfterDecode = () => {
|
|
1863
|
+
video.pause();
|
|
1864
|
+
video.currentTime = 0;
|
|
1865
|
+
video.muted = prevMuted;
|
|
1866
|
+
releasePlayAhead();
|
|
1867
|
+
if (!cancelled) {
|
|
1868
|
+
setHasPlayedAhead(true);
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
setTimeout(pauseAfterDecode, 50);
|
|
1854
1872
|
} catch {
|
|
1855
|
-
|
|
1873
|
+
releasePlayAhead();
|
|
1856
1874
|
}
|
|
1857
1875
|
};
|
|
1858
1876
|
doPlayAhead();
|
|
1859
1877
|
return () => {
|
|
1860
1878
|
cancelled = true;
|
|
1861
|
-
if (rafId !== null) {
|
|
1862
|
-
cancelAnimationFrame(rafId);
|
|
1863
|
-
rafId = null;
|
|
1864
|
-
}
|
|
1865
|
-
if (vfcHandle !== null && "cancelVideoFrameCallback" in video) {
|
|
1866
|
-
video.cancelVideoFrameCallback(vfcHandle);
|
|
1867
|
-
vfcHandle = null;
|
|
1868
|
-
}
|
|
1869
1879
|
};
|
|
1870
|
-
}, [
|
|
1880
|
+
}, [isActive, isReady, hasPlayedAhead]);
|
|
1871
1881
|
react.useEffect(() => {
|
|
1872
1882
|
setHasPlayedAhead(false);
|
|
1873
1883
|
}, [src]);
|
|
1874
1884
|
const wasActiveRef = react.useRef(false);
|
|
1885
|
+
const [isManuallyPaused, setIsManuallyPaused] = react.useState(false);
|
|
1875
1886
|
react.useEffect(() => {
|
|
1876
1887
|
const video = videoRef.current;
|
|
1877
1888
|
if (!video) return;
|
|
1878
1889
|
let onReady = null;
|
|
1879
|
-
let
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
const startPlay = () => {
|
|
1884
|
-
if (onReady) {
|
|
1885
|
-
video.removeEventListener("canplay", onReady);
|
|
1886
|
-
video.removeEventListener("loadeddata", onReady);
|
|
1887
|
-
video.removeEventListener("playing", onReady);
|
|
1888
|
-
onReady = null;
|
|
1889
|
-
}
|
|
1890
|
-
if (fallbackTimerId !== null) {
|
|
1891
|
-
clearTimeout(fallbackTimerId);
|
|
1892
|
-
fallbackTimerId = null;
|
|
1893
|
-
}
|
|
1894
|
-
if (pollId !== null) {
|
|
1895
|
-
clearInterval(pollId);
|
|
1896
|
-
pollId = null;
|
|
1897
|
-
}
|
|
1890
|
+
let cancelled = false;
|
|
1891
|
+
const attemptPlay = () => {
|
|
1892
|
+
if (cancelled) return;
|
|
1893
|
+
if (isMuted) {
|
|
1898
1894
|
video.muted = true;
|
|
1899
|
-
video.play().
|
|
1900
|
-
video.muted = isMuted;
|
|
1901
|
-
}).catch(() => {
|
|
1902
|
-
video.muted = isMuted;
|
|
1895
|
+
video.play().catch(() => {
|
|
1903
1896
|
});
|
|
1904
|
-
};
|
|
1905
|
-
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1906
|
-
startPlay();
|
|
1907
1897
|
} else {
|
|
1908
|
-
|
|
1909
|
-
video.
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
if (
|
|
1919
|
-
|
|
1920
|
-
|
|
1898
|
+
video.muted = false;
|
|
1899
|
+
video.play().then(() => {
|
|
1900
|
+
if (cancelled) {
|
|
1901
|
+
video.pause();
|
|
1902
|
+
}
|
|
1903
|
+
}).catch((err) => {
|
|
1904
|
+
if (cancelled) return;
|
|
1905
|
+
if (err.name === "NotAllowedError") {
|
|
1906
|
+
video.muted = true;
|
|
1907
|
+
video.play().then(() => {
|
|
1908
|
+
if (cancelled) {
|
|
1909
|
+
video.pause();
|
|
1910
|
+
return;
|
|
1921
1911
|
}
|
|
1922
|
-
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
}
|
|
1926
|
-
fallbackTimerId = window.setTimeout(() => {
|
|
1927
|
-
fallbackTimerId = null;
|
|
1928
|
-
if (onReady) {
|
|
1929
|
-
startPlay();
|
|
1912
|
+
onAutoplayBlocked?.();
|
|
1913
|
+
}).catch(() => {
|
|
1914
|
+
});
|
|
1930
1915
|
}
|
|
1931
|
-
}
|
|
1916
|
+
});
|
|
1932
1917
|
}
|
|
1918
|
+
};
|
|
1919
|
+
if (isActive && !isManuallyPaused) {
|
|
1920
|
+
wasActiveRef.current = true;
|
|
1921
|
+
if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
|
|
1922
|
+
attemptPlay();
|
|
1923
|
+
} else {
|
|
1924
|
+
onReady = attemptPlay;
|
|
1925
|
+
video.addEventListener("canplay", onReady, { once: true });
|
|
1926
|
+
}
|
|
1927
|
+
} else if (isActive && isManuallyPaused) {
|
|
1928
|
+
wasActiveRef.current = true;
|
|
1929
|
+
video.pause();
|
|
1933
1930
|
} else if (wasActiveRef.current) {
|
|
1934
1931
|
video.pause();
|
|
1935
1932
|
video.currentTime = 0;
|
|
1936
1933
|
wasActiveRef.current = false;
|
|
1934
|
+
setIsManuallyPaused(false);
|
|
1937
1935
|
setHasPlayedAhead(false);
|
|
1938
1936
|
} else if (!hasPlayedAhead) {
|
|
1939
1937
|
video.pause();
|
|
1940
1938
|
}
|
|
1941
1939
|
return () => {
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
video.removeEventListener("loadeddata", onReady);
|
|
1945
|
-
video.removeEventListener("playing", onReady);
|
|
1946
|
-
}
|
|
1947
|
-
if (fallbackTimerId !== null) {
|
|
1948
|
-
clearTimeout(fallbackTimerId);
|
|
1949
|
-
fallbackTimerId = null;
|
|
1950
|
-
}
|
|
1951
|
-
if (pollId !== null) {
|
|
1952
|
-
clearInterval(pollId);
|
|
1953
|
-
pollId = null;
|
|
1954
|
-
}
|
|
1940
|
+
cancelled = true;
|
|
1941
|
+
if (onReady) video.removeEventListener("canplay", onReady);
|
|
1955
1942
|
};
|
|
1956
|
-
}, [isActive, isMuted, hasPlayedAhead,
|
|
1943
|
+
}, [isActive, isMuted, hasPlayedAhead, isManuallyPaused, onAutoplayBlocked]);
|
|
1957
1944
|
react.useEffect(() => {
|
|
1958
1945
|
const video = videoRef.current;
|
|
1959
1946
|
if (!video) return;
|
|
1960
|
-
|
|
1961
|
-
video.muted = isMuted;
|
|
1962
|
-
} else {
|
|
1963
|
-
video.muted = true;
|
|
1964
|
-
}
|
|
1947
|
+
video.muted = isActive ? isMuted : true;
|
|
1965
1948
|
}, [isMuted, isActive]);
|
|
1966
|
-
const
|
|
1967
|
-
react.
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
video.removeEventListener("ended", onEnded);
|
|
1980
|
-
};
|
|
1949
|
+
const showPosterOverlay = !isReady && !hasPlayedAhead;
|
|
1950
|
+
const [showMuteIndicator, setShowMuteIndicator] = react.useState(false);
|
|
1951
|
+
const muteIndicatorTimer = react.useRef(null);
|
|
1952
|
+
const handleToggleMute = react.useCallback(() => {
|
|
1953
|
+
onToggleMute();
|
|
1954
|
+
setShowMuteIndicator(true);
|
|
1955
|
+
if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
|
|
1956
|
+
muteIndicatorTimer.current = setTimeout(() => setShowMuteIndicator(false), 1200);
|
|
1957
|
+
}, [onToggleMute]);
|
|
1958
|
+
const tapStartRef = react.useRef(null);
|
|
1959
|
+
const TAP_SLOP_PX = 10;
|
|
1960
|
+
const handlePointerDown = react.useCallback((e) => {
|
|
1961
|
+
tapStartRef.current = { x: e.clientX, y: e.clientY };
|
|
1981
1962
|
}, []);
|
|
1982
|
-
react.
|
|
1983
|
-
if (
|
|
1984
|
-
}, [isActive]);
|
|
1985
|
-
const showPosterOverlay = isActive ? !isReady && !isActuallyPlaying : canPlayAhead ? !hasPlayedAhead : !isReady;
|
|
1986
|
-
const [isPaused, setIsPaused] = react.useState(false);
|
|
1987
|
-
const [isDoubleTap, setIsDoubleTap] = react.useState(false);
|
|
1988
|
-
const lastTapTimeRef = react.useRef(0);
|
|
1989
|
-
const doubleTapTimerRef = react.useRef(null);
|
|
1990
|
-
const handleTap = react.useCallback(() => {
|
|
1963
|
+
const handleClick = react.useCallback((e) => {
|
|
1964
|
+
if (e.button !== 0) return;
|
|
1991
1965
|
if (!isActive) return;
|
|
1992
|
-
const
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
if (
|
|
1997
|
-
|
|
1998
|
-
|
|
1966
|
+
const start = tapStartRef.current;
|
|
1967
|
+
if (start) {
|
|
1968
|
+
const dx = Math.abs(e.clientX - start.x);
|
|
1969
|
+
const dy = Math.abs(e.clientY - start.y);
|
|
1970
|
+
if (dx > TAP_SLOP_PX || dy > TAP_SLOP_PX) {
|
|
1971
|
+
tapStartRef.current = null;
|
|
1972
|
+
return;
|
|
1999
1973
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
1974
|
+
}
|
|
1975
|
+
tapStartRef.current = null;
|
|
1976
|
+
const video = videoRef.current;
|
|
1977
|
+
if (!video) return;
|
|
1978
|
+
if (video.paused || isManuallyPaused) {
|
|
1979
|
+
setIsManuallyPaused(false);
|
|
1980
|
+
video.muted = true;
|
|
1981
|
+
video.play().then(() => {
|
|
1982
|
+
requestAnimationFrame(() => {
|
|
1983
|
+
video.muted = isMuted;
|
|
1984
|
+
});
|
|
1985
|
+
}).catch(() => {
|
|
1986
|
+
});
|
|
2002
1987
|
} else {
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
const video = videoRef.current;
|
|
2006
|
-
if (!video) return;
|
|
2007
|
-
if (video.paused) {
|
|
2008
|
-
video.play().catch(() => {
|
|
2009
|
-
});
|
|
2010
|
-
setIsPaused(false);
|
|
2011
|
-
} else {
|
|
2012
|
-
video.pause();
|
|
2013
|
-
setIsPaused(true);
|
|
2014
|
-
}
|
|
2015
|
-
}, 300);
|
|
1988
|
+
setIsManuallyPaused(true);
|
|
1989
|
+
video.pause();
|
|
2016
1990
|
}
|
|
2017
|
-
}, [isActive]);
|
|
2018
|
-
react.useEffect(() => {
|
|
2019
|
-
return () => {
|
|
2020
|
-
if (doubleTapTimerRef.current !== null) {
|
|
2021
|
-
clearTimeout(doubleTapTimerRef.current);
|
|
2022
|
-
}
|
|
2023
|
-
};
|
|
2024
|
-
}, []);
|
|
2025
|
-
react.useEffect(() => {
|
|
2026
|
-
if (isActive) setIsPaused(false);
|
|
2027
|
-
}, [isActive]);
|
|
1991
|
+
}, [isActive, isManuallyPaused, isMuted]);
|
|
2028
1992
|
const likeDelta = react.useSyncExternalStore(
|
|
2029
1993
|
optimisticManager.store.subscribe,
|
|
2030
1994
|
() => optimisticManager.getLikeDelta(item.id),
|
|
@@ -2042,12 +2006,36 @@ function VideoSlotInner({
|
|
|
2042
2006
|
followState,
|
|
2043
2007
|
share: () => adapters.interaction?.share?.(item.id),
|
|
2044
2008
|
isMuted,
|
|
2045
|
-
toggleMute:
|
|
2046
|
-
isPaused,
|
|
2047
|
-
togglePause: handleTap,
|
|
2009
|
+
toggleMute: handleToggleMute,
|
|
2048
2010
|
isActive,
|
|
2049
2011
|
index
|
|
2050
|
-
}), [item, likeDelta, followState, isMuted,
|
|
2012
|
+
}), [item, likeDelta, followState, isMuted, isActive, index, optimisticManager, adapters, handleToggleMute]);
|
|
2013
|
+
const pauseActions = react.useMemo(() => ({
|
|
2014
|
+
...actions,
|
|
2015
|
+
isPaused: isManuallyPaused,
|
|
2016
|
+
togglePlayPause: () => {
|
|
2017
|
+
const video = videoRef.current;
|
|
2018
|
+
if (!video) return;
|
|
2019
|
+
if (isManuallyPaused) {
|
|
2020
|
+
setIsManuallyPaused(false);
|
|
2021
|
+
video.muted = true;
|
|
2022
|
+
video.play().then(() => {
|
|
2023
|
+
requestAnimationFrame(() => {
|
|
2024
|
+
video.muted = isMuted;
|
|
2025
|
+
});
|
|
2026
|
+
}).catch(() => {
|
|
2027
|
+
});
|
|
2028
|
+
} else {
|
|
2029
|
+
setIsManuallyPaused(true);
|
|
2030
|
+
video.pause();
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
}), [actions, isManuallyPaused, isMuted]);
|
|
2034
|
+
react.useEffect(() => {
|
|
2035
|
+
return () => {
|
|
2036
|
+
if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
|
|
2037
|
+
};
|
|
2038
|
+
}, []);
|
|
2051
2039
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2052
2040
|
"div",
|
|
2053
2041
|
{
|
|
@@ -2058,7 +2046,8 @@ function VideoSlotInner({
|
|
|
2058
2046
|
background: "#111",
|
|
2059
2047
|
overflow: "hidden"
|
|
2060
2048
|
},
|
|
2061
|
-
|
|
2049
|
+
onPointerDown: handlePointerDown,
|
|
2050
|
+
onClick: handleClick,
|
|
2062
2051
|
children: [
|
|
2063
2052
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2064
2053
|
"video",
|
|
@@ -2066,9 +2055,8 @@ function VideoSlotInner({
|
|
|
2066
2055
|
ref: videoRef,
|
|
2067
2056
|
src: mp4Src,
|
|
2068
2057
|
loop: true,
|
|
2069
|
-
muted: true,
|
|
2058
|
+
muted: isActive ? isMuted : true,
|
|
2070
2059
|
playsInline: true,
|
|
2071
|
-
autoPlay: isActive,
|
|
2072
2060
|
preload: shouldLoadSrc ? "auto" : "none",
|
|
2073
2061
|
style: {
|
|
2074
2062
|
width: "100%",
|
|
@@ -2095,19 +2083,29 @@ function VideoSlotInner({
|
|
|
2095
2083
|
}
|
|
2096
2084
|
}
|
|
2097
2085
|
),
|
|
2098
|
-
|
|
2086
|
+
showMuteIndicator && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2099
2087
|
"div",
|
|
2100
2088
|
{
|
|
2101
2089
|
style: {
|
|
2102
2090
|
position: "absolute",
|
|
2103
|
-
|
|
2091
|
+
top: "50%",
|
|
2092
|
+
left: "50%",
|
|
2093
|
+
transform: "translate(-50%, -50%)",
|
|
2094
|
+
background: "rgba(0,0,0,0.6)",
|
|
2095
|
+
borderRadius: "50%",
|
|
2096
|
+
width: 64,
|
|
2097
|
+
height: 64,
|
|
2098
|
+
display: "flex",
|
|
2099
|
+
alignItems: "center",
|
|
2100
|
+
justifyContent: "center",
|
|
2101
|
+
fontSize: 28,
|
|
2104
2102
|
pointerEvents: "none",
|
|
2105
|
-
|
|
2103
|
+
animation: "reels-sdk-fadeInOut 1.2s ease forwards"
|
|
2106
2104
|
},
|
|
2107
|
-
children:
|
|
2105
|
+
children: isMuted ? "\u{1F507}" : "\u{1F50A}"
|
|
2108
2106
|
}
|
|
2109
2107
|
),
|
|
2110
|
-
|
|
2108
|
+
isActive && isManuallyPaused && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2111
2109
|
"div",
|
|
2112
2110
|
{
|
|
2113
2111
|
style: {
|
|
@@ -2116,25 +2114,9 @@ function VideoSlotInner({
|
|
|
2116
2114
|
left: "50%",
|
|
2117
2115
|
transform: "translate(-50%, -50%)",
|
|
2118
2116
|
pointerEvents: "none",
|
|
2119
|
-
|
|
2120
|
-
opacity: 0.3
|
|
2117
|
+
animation: "reels-sdk-fadeIn 0.2s ease forwards"
|
|
2121
2118
|
},
|
|
2122
|
-
children:
|
|
2123
|
-
"div",
|
|
2124
|
-
{
|
|
2125
|
-
style: {
|
|
2126
|
-
background: "rgba(0,0,0,0.6)",
|
|
2127
|
-
borderRadius: "50%",
|
|
2128
|
-
width: 64,
|
|
2129
|
-
height: 64,
|
|
2130
|
-
display: "flex",
|
|
2131
|
-
alignItems: "center",
|
|
2132
|
-
justifyContent: "center",
|
|
2133
|
-
fontSize: 28
|
|
2134
|
-
},
|
|
2135
|
-
children: "\u25B6\uFE0F"
|
|
2136
|
-
}
|
|
2137
|
-
)
|
|
2119
|
+
children: renderPauseAction ? renderPauseAction(item, pauseActions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultPauseAction, {})
|
|
2138
2120
|
}
|
|
2139
2121
|
),
|
|
2140
2122
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2142,13 +2124,11 @@ function VideoSlotInner({
|
|
|
2142
2124
|
{
|
|
2143
2125
|
style: {
|
|
2144
2126
|
position: "absolute",
|
|
2145
|
-
bottom:
|
|
2127
|
+
bottom: 80,
|
|
2146
2128
|
left: 16,
|
|
2147
2129
|
right: 80,
|
|
2148
|
-
paddingBottom: 16,
|
|
2149
2130
|
pointerEvents: "none",
|
|
2150
|
-
color: "#fff"
|
|
2151
|
-
zIndex: 10
|
|
2131
|
+
color: "#fff"
|
|
2152
2132
|
},
|
|
2153
2133
|
children: renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultOverlay, { item })
|
|
2154
2134
|
}
|
|
@@ -2156,19 +2136,18 @@ function VideoSlotInner({
|
|
|
2156
2136
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2157
2137
|
"div",
|
|
2158
2138
|
{
|
|
2159
|
-
onClick: (e) => e.stopPropagation(),
|
|
2160
2139
|
style: {
|
|
2161
2140
|
position: "absolute",
|
|
2162
|
-
bottom:
|
|
2141
|
+
bottom: 80,
|
|
2163
2142
|
right: 16,
|
|
2164
|
-
paddingBottom: 16,
|
|
2165
2143
|
display: "flex",
|
|
2166
2144
|
flexDirection: "column",
|
|
2167
2145
|
gap: 20,
|
|
2168
|
-
alignItems: "center"
|
|
2169
|
-
|
|
2170
|
-
|
|
2146
|
+
alignItems: "center"
|
|
2147
|
+
// Actions must be clickable; stop propagation so taps don't
|
|
2148
|
+
// also trigger the video play/pause handler on the container.
|
|
2171
2149
|
},
|
|
2150
|
+
onClick: (e) => e.stopPropagation(),
|
|
2172
2151
|
children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultActions, { item, actions })
|
|
2173
2152
|
}
|
|
2174
2153
|
),
|
|
@@ -2218,6 +2197,7 @@ function FpsCounter() {
|
|
|
2218
2197
|
}
|
|
2219
2198
|
);
|
|
2220
2199
|
}
|
|
2200
|
+
var RENDER_WINDOW_RADIUS = 3;
|
|
2221
2201
|
var centerStyle = {
|
|
2222
2202
|
height: "100dvh",
|
|
2223
2203
|
display: "flex",
|
|
@@ -2229,8 +2209,7 @@ var centerStyle = {
|
|
|
2229
2209
|
function ReelsFeed({
|
|
2230
2210
|
renderOverlay,
|
|
2231
2211
|
renderActions,
|
|
2232
|
-
|
|
2233
|
-
renderDoubleTap,
|
|
2212
|
+
renderPauseAction,
|
|
2234
2213
|
renderLoading,
|
|
2235
2214
|
renderEmpty,
|
|
2236
2215
|
renderError: _renderError,
|
|
@@ -2238,21 +2217,24 @@ function ReelsFeed({
|
|
|
2238
2217
|
loadMoreThreshold = 5,
|
|
2239
2218
|
onSlotChange,
|
|
2240
2219
|
gestureConfig,
|
|
2241
|
-
snapConfig
|
|
2220
|
+
snapConfig,
|
|
2221
|
+
initialMuted = true,
|
|
2222
|
+
onAutoplayBlocked
|
|
2242
2223
|
}) {
|
|
2243
2224
|
const { items, loading, loadInitial, loadMore, hasMore } = useFeed();
|
|
2225
|
+
const { adapters } = useSDK();
|
|
2244
2226
|
const {
|
|
2245
|
-
activeIndices,
|
|
2246
|
-
warmIndices,
|
|
2247
2227
|
focusedIndex,
|
|
2248
2228
|
prefetchIndex,
|
|
2229
|
+
preloadQueue,
|
|
2249
2230
|
setFocusedIndexImmediate,
|
|
2250
2231
|
setTotalItems,
|
|
2251
2232
|
shouldRenderVideo,
|
|
2252
2233
|
isWarmAllocated,
|
|
2253
2234
|
setPrefetchIndex
|
|
2254
2235
|
} = useResource();
|
|
2255
|
-
const [isMuted, setIsMuted] = react.useState(
|
|
2236
|
+
const [isMuted, setIsMuted] = react.useState(initialMuted);
|
|
2237
|
+
const [isDragMuted, setIsDragMuted] = react.useState(false);
|
|
2256
2238
|
const containerRef = react.useRef(null);
|
|
2257
2239
|
const slotCacheRef = react.useRef(/* @__PURE__ */ new Map());
|
|
2258
2240
|
const activeIndexRef = react.useRef(0);
|
|
@@ -2268,6 +2250,14 @@ function ReelsFeed({
|
|
|
2268
2250
|
react.useEffect(() => {
|
|
2269
2251
|
setTotalItems(items.length);
|
|
2270
2252
|
}, [items.length, setTotalItems]);
|
|
2253
|
+
react.useEffect(() => {
|
|
2254
|
+
for (const idx of preloadQueue) {
|
|
2255
|
+
const item = items[idx];
|
|
2256
|
+
if (item && isVideoItem(item) && item.source.type === "hls") {
|
|
2257
|
+
adapters.videoLoader?.preloadMetadata?.(item.source.url);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}, [preloadQueue, items, adapters.videoLoader]);
|
|
2271
2261
|
react.useEffect(() => {
|
|
2272
2262
|
if (items.length - focusedIndex <= loadMoreThreshold && hasMore && !loading) {
|
|
2273
2263
|
loadMore();
|
|
@@ -2288,7 +2278,7 @@ function ReelsFeed({
|
|
|
2288
2278
|
const observer = new MutationObserver(rebuild);
|
|
2289
2279
|
observer.observe(container, { childList: true, subtree: true });
|
|
2290
2280
|
return () => observer.disconnect();
|
|
2291
|
-
}, [items.length]);
|
|
2281
|
+
}, [items.length, focusedIndex]);
|
|
2292
2282
|
const containerHeight = react.useRef(
|
|
2293
2283
|
typeof window !== "undefined" ? window.innerHeight : 800
|
|
2294
2284
|
);
|
|
@@ -2379,6 +2369,12 @@ function ReelsFeed({
|
|
|
2379
2369
|
animateBounceBack(targets);
|
|
2380
2370
|
setPrefetchIndex(null);
|
|
2381
2371
|
}, [animateBounceBack, setPrefetchIndex]);
|
|
2372
|
+
const handleDragStart = react.useCallback(() => {
|
|
2373
|
+
setIsDragMuted(true);
|
|
2374
|
+
}, []);
|
|
2375
|
+
const handleDragEnd = react.useCallback(() => {
|
|
2376
|
+
setTimeout(() => setIsDragMuted(false), 50);
|
|
2377
|
+
}, []);
|
|
2382
2378
|
const { bind } = usePointerGesture({
|
|
2383
2379
|
axis: "y",
|
|
2384
2380
|
velocityThreshold: gestureConfig?.velocityThreshold ?? 0.3,
|
|
@@ -2391,7 +2387,9 @@ function ReelsFeed({
|
|
|
2391
2387
|
},
|
|
2392
2388
|
onDragThreshold: handleDragThreshold,
|
|
2393
2389
|
onSnap: handleSnap,
|
|
2394
|
-
onBounceBack: handleBounceBack
|
|
2390
|
+
onBounceBack: handleBounceBack,
|
|
2391
|
+
onDragStart: handleDragStart,
|
|
2392
|
+
onDragEnd: handleDragEnd
|
|
2395
2393
|
});
|
|
2396
2394
|
const getInitialTransformPx = react.useCallback(
|
|
2397
2395
|
(index) => {
|
|
@@ -2433,59 +2431,58 @@ function ReelsFeed({
|
|
|
2433
2431
|
70% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
2434
2432
|
100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
|
|
2435
2433
|
}
|
|
2434
|
+
@keyframes reels-sdk-fadeIn {
|
|
2435
|
+
from { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
|
|
2436
|
+
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
2437
|
+
}
|
|
2436
2438
|
@keyframes reels-sdk-spin {
|
|
2437
2439
|
to { transform: rotate(360deg); }
|
|
2438
2440
|
}
|
|
2439
2441
|
` }),
|
|
2440
|
-
(() => {
|
|
2441
|
-
const
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2442
|
+
items.map((item, index) => {
|
|
2443
|
+
const distFromFocus = Math.abs(index - focusedIndex);
|
|
2444
|
+
const wrapperStyle = {
|
|
2445
|
+
position: "absolute",
|
|
2446
|
+
inset: 0,
|
|
2447
|
+
contain: "layout style paint",
|
|
2448
|
+
willChange: distFromFocus <= 1 ? "transform" : "auto",
|
|
2449
|
+
transform: getInitialTransformPx(index)
|
|
2450
|
+
};
|
|
2451
|
+
if (distFromFocus > RENDER_WINDOW_RADIUS) {
|
|
2452
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot-index": index, style: wrapperStyle }, item.id);
|
|
2447
2453
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
renderPauseIndicator,
|
|
2481
|
-
renderDoubleTap
|
|
2482
|
-
}
|
|
2483
|
-
) : null
|
|
2484
|
-
},
|
|
2485
|
-
item.id
|
|
2486
|
-
);
|
|
2487
|
-
});
|
|
2488
|
-
})()
|
|
2454
|
+
const isActive = index === focusedIndex;
|
|
2455
|
+
const isPrefetch = index === prefetchIndex;
|
|
2456
|
+
const isWarm = isWarmAllocated(index);
|
|
2457
|
+
const isVisible = shouldRenderVideo(index) || isPrefetch;
|
|
2458
|
+
const bufferTier = isActive ? "active" : isWarm ? "warm" : "hot";
|
|
2459
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2460
|
+
"div",
|
|
2461
|
+
{
|
|
2462
|
+
"data-slot-index": index,
|
|
2463
|
+
style: wrapperStyle,
|
|
2464
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2465
|
+
VideoSlot,
|
|
2466
|
+
{
|
|
2467
|
+
item,
|
|
2468
|
+
index,
|
|
2469
|
+
isActive,
|
|
2470
|
+
isPrefetch,
|
|
2471
|
+
isPreloaded: !isActive && !isPrefetch && isVisible,
|
|
2472
|
+
bufferTier,
|
|
2473
|
+
isMuted: isMuted || isDragMuted,
|
|
2474
|
+
onToggleMute: handleToggleMute,
|
|
2475
|
+
onAutoplayBlocked,
|
|
2476
|
+
showFps: showFps && isActive,
|
|
2477
|
+
renderOverlay,
|
|
2478
|
+
renderActions,
|
|
2479
|
+
renderPauseAction
|
|
2480
|
+
}
|
|
2481
|
+
)
|
|
2482
|
+
},
|
|
2483
|
+
item.id
|
|
2484
|
+
);
|
|
2485
|
+
})
|
|
2489
2486
|
]
|
|
2490
2487
|
}
|
|
2491
2488
|
);
|
|
@@ -2740,6 +2737,8 @@ var MockVideoLoader = class {
|
|
|
2740
2737
|
this.preloaded.clear();
|
|
2741
2738
|
this.loading.clear();
|
|
2742
2739
|
}
|
|
2740
|
+
preloadMetadata(_url) {
|
|
2741
|
+
}
|
|
2743
2742
|
};
|
|
2744
2743
|
var MockDataSource = class {
|
|
2745
2744
|
constructor(options = {}) {
|
|
@@ -3113,8 +3112,8 @@ exports.DEFAULT_FEED_CONFIG = DEFAULT_FEED_CONFIG;
|
|
|
3113
3112
|
exports.DEFAULT_PLAYER_CONFIG = DEFAULT_PLAYER_CONFIG;
|
|
3114
3113
|
exports.DEFAULT_RESOURCE_CONFIG = DEFAULT_RESOURCE_CONFIG;
|
|
3115
3114
|
exports.DefaultActions = DefaultActions;
|
|
3116
|
-
exports.DefaultDoubleTap = DefaultDoubleTap;
|
|
3117
3115
|
exports.DefaultOverlay = DefaultOverlay;
|
|
3116
|
+
exports.DefaultPauseAction = DefaultPauseAction;
|
|
3118
3117
|
exports.DefaultSkeleton = DefaultSkeleton;
|
|
3119
3118
|
exports.FeedManager = FeedManager;
|
|
3120
3119
|
exports.HttpDataSource = HttpDataSource;
|