@xhub-reels/sdk 0.1.15 → 0.1.18
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 +347 -346
- package/dist/index.d.cts +89 -38
- package/dist/index.d.ts +89 -38
- package/dist/index.js +347 -346
- 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,146 @@ 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
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
};
|
|
1949
|
+
const showPosterOverlay = !isReady && !hasPlayedAhead;
|
|
1950
|
+
const isPreDecoded = hasPlayedAhead;
|
|
1951
|
+
const [showMuteIndicator, setShowMuteIndicator] = react.useState(false);
|
|
1952
|
+
const muteIndicatorTimer = react.useRef(null);
|
|
1953
|
+
const handleToggleMute = react.useCallback(() => {
|
|
1954
|
+
onToggleMute();
|
|
1955
|
+
setShowMuteIndicator(true);
|
|
1956
|
+
if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
|
|
1957
|
+
muteIndicatorTimer.current = setTimeout(() => setShowMuteIndicator(false), 1200);
|
|
1958
|
+
}, [onToggleMute]);
|
|
1959
|
+
const tapStartRef = react.useRef(null);
|
|
1960
|
+
const TAP_SLOP_PX = 10;
|
|
1961
|
+
const handlePointerDown = react.useCallback((e) => {
|
|
1962
|
+
tapStartRef.current = { x: e.clientX, y: e.clientY };
|
|
1981
1963
|
}, []);
|
|
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(() => {
|
|
1964
|
+
const handleClick = react.useCallback((e) => {
|
|
1965
|
+
if (e.button !== 0) return;
|
|
1991
1966
|
if (!isActive) return;
|
|
1992
|
-
const
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
if (
|
|
1997
|
-
|
|
1998
|
-
|
|
1967
|
+
const start = tapStartRef.current;
|
|
1968
|
+
if (start) {
|
|
1969
|
+
const dx = Math.abs(e.clientX - start.x);
|
|
1970
|
+
const dy = Math.abs(e.clientY - start.y);
|
|
1971
|
+
if (dx > TAP_SLOP_PX || dy > TAP_SLOP_PX) {
|
|
1972
|
+
tapStartRef.current = null;
|
|
1973
|
+
return;
|
|
1999
1974
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
1975
|
+
}
|
|
1976
|
+
tapStartRef.current = null;
|
|
1977
|
+
const video = videoRef.current;
|
|
1978
|
+
if (!video) return;
|
|
1979
|
+
if (video.paused || isManuallyPaused) {
|
|
1980
|
+
setIsManuallyPaused(false);
|
|
1981
|
+
video.muted = true;
|
|
1982
|
+
video.play().then(() => {
|
|
1983
|
+
requestAnimationFrame(() => {
|
|
1984
|
+
video.muted = isMuted;
|
|
1985
|
+
});
|
|
1986
|
+
}).catch(() => {
|
|
1987
|
+
});
|
|
2002
1988
|
} 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);
|
|
1989
|
+
setIsManuallyPaused(true);
|
|
1990
|
+
video.pause();
|
|
2016
1991
|
}
|
|
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]);
|
|
1992
|
+
}, [isActive, isManuallyPaused, isMuted]);
|
|
2028
1993
|
const likeDelta = react.useSyncExternalStore(
|
|
2029
1994
|
optimisticManager.store.subscribe,
|
|
2030
1995
|
() => optimisticManager.getLikeDelta(item.id),
|
|
@@ -2042,12 +2007,36 @@ function VideoSlotInner({
|
|
|
2042
2007
|
followState,
|
|
2043
2008
|
share: () => adapters.interaction?.share?.(item.id),
|
|
2044
2009
|
isMuted,
|
|
2045
|
-
toggleMute:
|
|
2046
|
-
isPaused,
|
|
2047
|
-
togglePause: handleTap,
|
|
2010
|
+
toggleMute: handleToggleMute,
|
|
2048
2011
|
isActive,
|
|
2049
2012
|
index
|
|
2050
|
-
}), [item, likeDelta, followState, isMuted,
|
|
2013
|
+
}), [item, likeDelta, followState, isMuted, isActive, index, optimisticManager, adapters, handleToggleMute]);
|
|
2014
|
+
const pauseActions = react.useMemo(() => ({
|
|
2015
|
+
...actions,
|
|
2016
|
+
isPaused: isManuallyPaused,
|
|
2017
|
+
togglePlayPause: () => {
|
|
2018
|
+
const video = videoRef.current;
|
|
2019
|
+
if (!video) return;
|
|
2020
|
+
if (isManuallyPaused) {
|
|
2021
|
+
setIsManuallyPaused(false);
|
|
2022
|
+
video.muted = true;
|
|
2023
|
+
video.play().then(() => {
|
|
2024
|
+
requestAnimationFrame(() => {
|
|
2025
|
+
video.muted = isMuted;
|
|
2026
|
+
});
|
|
2027
|
+
}).catch(() => {
|
|
2028
|
+
});
|
|
2029
|
+
} else {
|
|
2030
|
+
setIsManuallyPaused(true);
|
|
2031
|
+
video.pause();
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
}), [actions, isManuallyPaused, isMuted]);
|
|
2035
|
+
react.useEffect(() => {
|
|
2036
|
+
return () => {
|
|
2037
|
+
if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
|
|
2038
|
+
};
|
|
2039
|
+
}, []);
|
|
2051
2040
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2052
2041
|
"div",
|
|
2053
2042
|
{
|
|
@@ -2058,7 +2047,8 @@ function VideoSlotInner({
|
|
|
2058
2047
|
background: "#111",
|
|
2059
2048
|
overflow: "hidden"
|
|
2060
2049
|
},
|
|
2061
|
-
|
|
2050
|
+
onPointerDown: handlePointerDown,
|
|
2051
|
+
onClick: handleClick,
|
|
2062
2052
|
children: [
|
|
2063
2053
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2064
2054
|
"video",
|
|
@@ -2066,21 +2056,21 @@ function VideoSlotInner({
|
|
|
2066
2056
|
ref: videoRef,
|
|
2067
2057
|
src: mp4Src,
|
|
2068
2058
|
loop: true,
|
|
2069
|
-
muted: true,
|
|
2059
|
+
muted: isActive ? isMuted : true,
|
|
2070
2060
|
playsInline: true,
|
|
2071
|
-
autoPlay: isActive,
|
|
2072
2061
|
preload: shouldLoadSrc ? "auto" : "none",
|
|
2073
2062
|
style: {
|
|
2074
2063
|
width: "100%",
|
|
2075
2064
|
height: "100%",
|
|
2076
2065
|
objectFit: "cover",
|
|
2077
|
-
// Hide video until ready to avoid black frame flash
|
|
2066
|
+
// Hide video until ready to avoid black frame flash.
|
|
2067
|
+
// When pre-decoded, skip transition — first frame is already on canvas.
|
|
2078
2068
|
opacity: showPosterOverlay ? 0 : 1,
|
|
2079
|
-
transition: "opacity 0.15s ease"
|
|
2069
|
+
transition: isPreDecoded ? "none" : "opacity 0.15s ease"
|
|
2080
2070
|
}
|
|
2081
2071
|
}
|
|
2082
2072
|
),
|
|
2083
|
-
item.poster && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2073
|
+
item.poster && !isPreDecoded && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2084
2074
|
"div",
|
|
2085
2075
|
{
|
|
2086
2076
|
style: {
|
|
@@ -2095,19 +2085,29 @@ function VideoSlotInner({
|
|
|
2095
2085
|
}
|
|
2096
2086
|
}
|
|
2097
2087
|
),
|
|
2098
|
-
|
|
2088
|
+
showMuteIndicator && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2099
2089
|
"div",
|
|
2100
2090
|
{
|
|
2101
2091
|
style: {
|
|
2102
2092
|
position: "absolute",
|
|
2103
|
-
|
|
2093
|
+
top: "50%",
|
|
2094
|
+
left: "50%",
|
|
2095
|
+
transform: "translate(-50%, -50%)",
|
|
2096
|
+
background: "rgba(0,0,0,0.6)",
|
|
2097
|
+
borderRadius: "50%",
|
|
2098
|
+
width: 64,
|
|
2099
|
+
height: 64,
|
|
2100
|
+
display: "flex",
|
|
2101
|
+
alignItems: "center",
|
|
2102
|
+
justifyContent: "center",
|
|
2103
|
+
fontSize: 28,
|
|
2104
2104
|
pointerEvents: "none",
|
|
2105
|
-
|
|
2105
|
+
animation: "reels-sdk-fadeInOut 1.2s ease forwards"
|
|
2106
2106
|
},
|
|
2107
|
-
children:
|
|
2107
|
+
children: isMuted ? "\u{1F507}" : "\u{1F50A}"
|
|
2108
2108
|
}
|
|
2109
2109
|
),
|
|
2110
|
-
|
|
2110
|
+
isActive && isManuallyPaused && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2111
2111
|
"div",
|
|
2112
2112
|
{
|
|
2113
2113
|
style: {
|
|
@@ -2116,25 +2116,9 @@ function VideoSlotInner({
|
|
|
2116
2116
|
left: "50%",
|
|
2117
2117
|
transform: "translate(-50%, -50%)",
|
|
2118
2118
|
pointerEvents: "none",
|
|
2119
|
-
|
|
2120
|
-
opacity: 0.3
|
|
2119
|
+
animation: "reels-sdk-fadeIn 0.2s ease forwards"
|
|
2121
2120
|
},
|
|
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
|
-
)
|
|
2121
|
+
children: renderPauseAction ? renderPauseAction(item, pauseActions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultPauseAction, {})
|
|
2138
2122
|
}
|
|
2139
2123
|
),
|
|
2140
2124
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2142,13 +2126,11 @@ function VideoSlotInner({
|
|
|
2142
2126
|
{
|
|
2143
2127
|
style: {
|
|
2144
2128
|
position: "absolute",
|
|
2145
|
-
bottom:
|
|
2129
|
+
bottom: 80,
|
|
2146
2130
|
left: 16,
|
|
2147
2131
|
right: 80,
|
|
2148
|
-
paddingBottom: 16,
|
|
2149
2132
|
pointerEvents: "none",
|
|
2150
|
-
color: "#fff"
|
|
2151
|
-
zIndex: 10
|
|
2133
|
+
color: "#fff"
|
|
2152
2134
|
},
|
|
2153
2135
|
children: renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultOverlay, { item })
|
|
2154
2136
|
}
|
|
@@ -2156,19 +2138,18 @@ function VideoSlotInner({
|
|
|
2156
2138
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2157
2139
|
"div",
|
|
2158
2140
|
{
|
|
2159
|
-
onClick: (e) => e.stopPropagation(),
|
|
2160
2141
|
style: {
|
|
2161
2142
|
position: "absolute",
|
|
2162
|
-
bottom:
|
|
2143
|
+
bottom: 80,
|
|
2163
2144
|
right: 16,
|
|
2164
|
-
paddingBottom: 16,
|
|
2165
2145
|
display: "flex",
|
|
2166
2146
|
flexDirection: "column",
|
|
2167
2147
|
gap: 20,
|
|
2168
|
-
alignItems: "center"
|
|
2169
|
-
|
|
2170
|
-
|
|
2148
|
+
alignItems: "center"
|
|
2149
|
+
// Actions must be clickable; stop propagation so taps don't
|
|
2150
|
+
// also trigger the video play/pause handler on the container.
|
|
2171
2151
|
},
|
|
2152
|
+
onClick: (e) => e.stopPropagation(),
|
|
2172
2153
|
children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultActions, { item, actions })
|
|
2173
2154
|
}
|
|
2174
2155
|
),
|
|
@@ -2218,6 +2199,7 @@ function FpsCounter() {
|
|
|
2218
2199
|
}
|
|
2219
2200
|
);
|
|
2220
2201
|
}
|
|
2202
|
+
var RENDER_WINDOW_RADIUS = 3;
|
|
2221
2203
|
var centerStyle = {
|
|
2222
2204
|
height: "100dvh",
|
|
2223
2205
|
display: "flex",
|
|
@@ -2229,8 +2211,7 @@ var centerStyle = {
|
|
|
2229
2211
|
function ReelsFeed({
|
|
2230
2212
|
renderOverlay,
|
|
2231
2213
|
renderActions,
|
|
2232
|
-
|
|
2233
|
-
renderDoubleTap,
|
|
2214
|
+
renderPauseAction,
|
|
2234
2215
|
renderLoading,
|
|
2235
2216
|
renderEmpty,
|
|
2236
2217
|
renderError: _renderError,
|
|
@@ -2238,21 +2219,24 @@ function ReelsFeed({
|
|
|
2238
2219
|
loadMoreThreshold = 5,
|
|
2239
2220
|
onSlotChange,
|
|
2240
2221
|
gestureConfig,
|
|
2241
|
-
snapConfig
|
|
2222
|
+
snapConfig,
|
|
2223
|
+
initialMuted = true,
|
|
2224
|
+
onAutoplayBlocked
|
|
2242
2225
|
}) {
|
|
2243
2226
|
const { items, loading, loadInitial, loadMore, hasMore } = useFeed();
|
|
2227
|
+
const { adapters } = useSDK();
|
|
2244
2228
|
const {
|
|
2245
|
-
activeIndices,
|
|
2246
|
-
warmIndices,
|
|
2247
2229
|
focusedIndex,
|
|
2248
2230
|
prefetchIndex,
|
|
2231
|
+
preloadQueue,
|
|
2249
2232
|
setFocusedIndexImmediate,
|
|
2250
2233
|
setTotalItems,
|
|
2251
2234
|
shouldRenderVideo,
|
|
2252
2235
|
isWarmAllocated,
|
|
2253
2236
|
setPrefetchIndex
|
|
2254
2237
|
} = useResource();
|
|
2255
|
-
const [isMuted, setIsMuted] = react.useState(
|
|
2238
|
+
const [isMuted, setIsMuted] = react.useState(initialMuted);
|
|
2239
|
+
const [isDragMuted, setIsDragMuted] = react.useState(false);
|
|
2256
2240
|
const containerRef = react.useRef(null);
|
|
2257
2241
|
const slotCacheRef = react.useRef(/* @__PURE__ */ new Map());
|
|
2258
2242
|
const activeIndexRef = react.useRef(0);
|
|
@@ -2268,6 +2252,14 @@ function ReelsFeed({
|
|
|
2268
2252
|
react.useEffect(() => {
|
|
2269
2253
|
setTotalItems(items.length);
|
|
2270
2254
|
}, [items.length, setTotalItems]);
|
|
2255
|
+
react.useEffect(() => {
|
|
2256
|
+
for (const idx of preloadQueue) {
|
|
2257
|
+
const item = items[idx];
|
|
2258
|
+
if (item && isVideoItem(item) && item.source.type === "hls") {
|
|
2259
|
+
adapters.videoLoader?.preloadMetadata?.(item.source.url);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}, [preloadQueue, items, adapters.videoLoader]);
|
|
2271
2263
|
react.useEffect(() => {
|
|
2272
2264
|
if (items.length - focusedIndex <= loadMoreThreshold && hasMore && !loading) {
|
|
2273
2265
|
loadMore();
|
|
@@ -2288,7 +2280,7 @@ function ReelsFeed({
|
|
|
2288
2280
|
const observer = new MutationObserver(rebuild);
|
|
2289
2281
|
observer.observe(container, { childList: true, subtree: true });
|
|
2290
2282
|
return () => observer.disconnect();
|
|
2291
|
-
}, [items.length]);
|
|
2283
|
+
}, [items.length, focusedIndex]);
|
|
2292
2284
|
const containerHeight = react.useRef(
|
|
2293
2285
|
typeof window !== "undefined" ? window.innerHeight : 800
|
|
2294
2286
|
);
|
|
@@ -2379,6 +2371,12 @@ function ReelsFeed({
|
|
|
2379
2371
|
animateBounceBack(targets);
|
|
2380
2372
|
setPrefetchIndex(null);
|
|
2381
2373
|
}, [animateBounceBack, setPrefetchIndex]);
|
|
2374
|
+
const handleDragStart = react.useCallback(() => {
|
|
2375
|
+
setIsDragMuted(true);
|
|
2376
|
+
}, []);
|
|
2377
|
+
const handleDragEnd = react.useCallback(() => {
|
|
2378
|
+
setTimeout(() => setIsDragMuted(false), 50);
|
|
2379
|
+
}, []);
|
|
2382
2380
|
const { bind } = usePointerGesture({
|
|
2383
2381
|
axis: "y",
|
|
2384
2382
|
velocityThreshold: gestureConfig?.velocityThreshold ?? 0.3,
|
|
@@ -2391,7 +2389,9 @@ function ReelsFeed({
|
|
|
2391
2389
|
},
|
|
2392
2390
|
onDragThreshold: handleDragThreshold,
|
|
2393
2391
|
onSnap: handleSnap,
|
|
2394
|
-
onBounceBack: handleBounceBack
|
|
2392
|
+
onBounceBack: handleBounceBack,
|
|
2393
|
+
onDragStart: handleDragStart,
|
|
2394
|
+
onDragEnd: handleDragEnd
|
|
2395
2395
|
});
|
|
2396
2396
|
const getInitialTransformPx = react.useCallback(
|
|
2397
2397
|
(index) => {
|
|
@@ -2433,59 +2433,58 @@ function ReelsFeed({
|
|
|
2433
2433
|
70% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
2434
2434
|
100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
|
|
2435
2435
|
}
|
|
2436
|
+
@keyframes reels-sdk-fadeIn {
|
|
2437
|
+
from { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
|
|
2438
|
+
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
2439
|
+
}
|
|
2436
2440
|
@keyframes reels-sdk-spin {
|
|
2437
2441
|
to { transform: rotate(360deg); }
|
|
2438
2442
|
}
|
|
2439
2443
|
` }),
|
|
2440
|
-
(() => {
|
|
2441
|
-
const
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2444
|
+
items.map((item, index) => {
|
|
2445
|
+
const distFromFocus = Math.abs(index - focusedIndex);
|
|
2446
|
+
const wrapperStyle = {
|
|
2447
|
+
position: "absolute",
|
|
2448
|
+
inset: 0,
|
|
2449
|
+
contain: "layout style paint",
|
|
2450
|
+
willChange: distFromFocus <= 1 ? "transform" : "auto",
|
|
2451
|
+
transform: getInitialTransformPx(index)
|
|
2452
|
+
};
|
|
2453
|
+
if (distFromFocus > RENDER_WINDOW_RADIUS) {
|
|
2454
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot-index": index, style: wrapperStyle }, item.id);
|
|
2447
2455
|
}
|
|
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
|
-
})()
|
|
2456
|
+
const isActive = index === focusedIndex;
|
|
2457
|
+
const isPrefetch = index === prefetchIndex;
|
|
2458
|
+
const isWarm = isWarmAllocated(index);
|
|
2459
|
+
const isVisible = shouldRenderVideo(index) || isPrefetch;
|
|
2460
|
+
const bufferTier = isActive ? "active" : isWarm ? "warm" : "hot";
|
|
2461
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2462
|
+
"div",
|
|
2463
|
+
{
|
|
2464
|
+
"data-slot-index": index,
|
|
2465
|
+
style: wrapperStyle,
|
|
2466
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2467
|
+
VideoSlot,
|
|
2468
|
+
{
|
|
2469
|
+
item,
|
|
2470
|
+
index,
|
|
2471
|
+
isActive,
|
|
2472
|
+
isPrefetch,
|
|
2473
|
+
isPreloaded: !isActive && !isPrefetch && isVisible,
|
|
2474
|
+
bufferTier,
|
|
2475
|
+
isMuted: isMuted || isDragMuted,
|
|
2476
|
+
onToggleMute: handleToggleMute,
|
|
2477
|
+
onAutoplayBlocked,
|
|
2478
|
+
showFps: showFps && isActive,
|
|
2479
|
+
renderOverlay,
|
|
2480
|
+
renderActions,
|
|
2481
|
+
renderPauseAction
|
|
2482
|
+
}
|
|
2483
|
+
)
|
|
2484
|
+
},
|
|
2485
|
+
item.id
|
|
2486
|
+
);
|
|
2487
|
+
})
|
|
2489
2488
|
]
|
|
2490
2489
|
}
|
|
2491
2490
|
);
|
|
@@ -2740,6 +2739,8 @@ var MockVideoLoader = class {
|
|
|
2740
2739
|
this.preloaded.clear();
|
|
2741
2740
|
this.loading.clear();
|
|
2742
2741
|
}
|
|
2742
|
+
preloadMetadata(_url) {
|
|
2743
|
+
}
|
|
2743
2744
|
};
|
|
2744
2745
|
var MockDataSource = class {
|
|
2745
2746
|
constructor(options = {}) {
|
|
@@ -3113,8 +3114,8 @@ exports.DEFAULT_FEED_CONFIG = DEFAULT_FEED_CONFIG;
|
|
|
3113
3114
|
exports.DEFAULT_PLAYER_CONFIG = DEFAULT_PLAYER_CONFIG;
|
|
3114
3115
|
exports.DEFAULT_RESOURCE_CONFIG = DEFAULT_RESOURCE_CONFIG;
|
|
3115
3116
|
exports.DefaultActions = DefaultActions;
|
|
3116
|
-
exports.DefaultDoubleTap = DefaultDoubleTap;
|
|
3117
3117
|
exports.DefaultOverlay = DefaultOverlay;
|
|
3118
|
+
exports.DefaultPauseAction = DefaultPauseAction;
|
|
3118
3119
|
exports.DefaultSkeleton = DefaultSkeleton;
|
|
3119
3120
|
exports.FeedManager = FeedManager;
|
|
3120
3121
|
exports.HttpDataSource = HttpDataSource;
|