@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.js
CHANGED
|
@@ -827,6 +827,21 @@ var ResourceGovernor = class {
|
|
|
827
827
|
isPreloading(index) {
|
|
828
828
|
return this.store.getState().preloadQueue.includes(index);
|
|
829
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* Returns the buffer tier (1–4) for a given index.
|
|
832
|
+
* - 1: Active (playing, 10s buffer)
|
|
833
|
+
* - 2: Hot (±bufferWindow, 2s buffer, instant swap)
|
|
834
|
+
* - 3: Warm (manifest + 1 segment, ~300ms show)
|
|
835
|
+
* - 4: Cold (preload queue — manifest in HTTP cache, no DOM)
|
|
836
|
+
*/
|
|
837
|
+
getTier(index) {
|
|
838
|
+
const { focusedIndex, activeAllocations, warmAllocations, preloadQueue } = this.store.getState();
|
|
839
|
+
if (index === focusedIndex) return 1;
|
|
840
|
+
if (activeAllocations.has(index)) return 2;
|
|
841
|
+
if (warmAllocations.has(index)) return 3;
|
|
842
|
+
if (preloadQueue.includes(index)) return 4;
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
830
845
|
getActiveAllocations() {
|
|
831
846
|
return [...this.store.getState().activeAllocations];
|
|
832
847
|
}
|
|
@@ -899,7 +914,9 @@ function usePointerGesture(config = {}) {
|
|
|
899
914
|
containerSize,
|
|
900
915
|
dragThresholdRatio = 0.5,
|
|
901
916
|
onSnap,
|
|
902
|
-
onBounceBack
|
|
917
|
+
onBounceBack,
|
|
918
|
+
onDragStart,
|
|
919
|
+
onDragEnd
|
|
903
920
|
} = config;
|
|
904
921
|
const isDraggingRef = useRef(false);
|
|
905
922
|
const dragOffsetRef = useRef(0);
|
|
@@ -916,6 +933,8 @@ function usePointerGesture(config = {}) {
|
|
|
916
933
|
const onDragThresholdRef = useRef(onDragThreshold);
|
|
917
934
|
const onSnapRef = useRef(onSnap);
|
|
918
935
|
const onBounceBackRef = useRef(onBounceBack);
|
|
936
|
+
const onDragStartRef = useRef(onDragStart);
|
|
937
|
+
const onDragEndRef = useRef(onDragEnd);
|
|
919
938
|
const disabledRef = useRef(disabled);
|
|
920
939
|
const containerSizeRef = useRef(containerSize);
|
|
921
940
|
const dragThresholdRatioRef = useRef(dragThresholdRatio);
|
|
@@ -924,6 +943,8 @@ function usePointerGesture(config = {}) {
|
|
|
924
943
|
onDragThresholdRef.current = onDragThreshold;
|
|
925
944
|
onSnapRef.current = onSnap;
|
|
926
945
|
onBounceBackRef.current = onBounceBack;
|
|
946
|
+
onDragStartRef.current = onDragStart;
|
|
947
|
+
onDragEndRef.current = onDragEnd;
|
|
927
948
|
disabledRef.current = disabled;
|
|
928
949
|
containerSizeRef.current = containerSize;
|
|
929
950
|
dragThresholdRatioRef.current = dragThresholdRatio;
|
|
@@ -990,6 +1011,7 @@ function usePointerGesture(config = {}) {
|
|
|
990
1011
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
991
1012
|
window.removeEventListener("pointerup", handlePointerUp);
|
|
992
1013
|
window.removeEventListener("pointercancel", handlePointerUp);
|
|
1014
|
+
onDragEndRef.current?.();
|
|
993
1015
|
const offset = dragOffsetRef.current;
|
|
994
1016
|
const velocity = velocityRef.current;
|
|
995
1017
|
const shouldSnap = Math.abs(velocity) > velocityThreshold || Math.abs(offset) > distanceThreshold;
|
|
@@ -1021,6 +1043,7 @@ function usePointerGesture(config = {}) {
|
|
|
1021
1043
|
lastTimeRef.current = performance.now();
|
|
1022
1044
|
velocityRef.current = 0;
|
|
1023
1045
|
dragOffsetRef.current = 0;
|
|
1046
|
+
onDragStartRef.current?.();
|
|
1024
1047
|
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1025
1048
|
window.addEventListener("pointermove", handlePointerMove, { passive: true });
|
|
1026
1049
|
window.addEventListener("pointerup", handlePointerUp);
|
|
@@ -1238,6 +1261,7 @@ function useResource() {
|
|
|
1238
1261
|
const networkType = useResourceSelector((s) => s.networkType);
|
|
1239
1262
|
const isActive = useResourceSelector((s) => s.isActive);
|
|
1240
1263
|
const prefetchIndex = useResourceSelector((s) => s.prefetchIndex);
|
|
1264
|
+
const preloadQueue = useResourceSelector((s) => s.preloadQueue);
|
|
1241
1265
|
const activeIndices = useMemo(() => [...activeAllocations], [activeAllocations]);
|
|
1242
1266
|
const warmIndices = useMemo(() => [...warmAllocations], [warmAllocations]);
|
|
1243
1267
|
const setFocusedIndex = useCallback(
|
|
@@ -1268,6 +1292,10 @@ function useResource() {
|
|
|
1268
1292
|
(i) => resourceGovernor.setPrefetchIndex(i),
|
|
1269
1293
|
[resourceGovernor]
|
|
1270
1294
|
);
|
|
1295
|
+
const getTier = useCallback(
|
|
1296
|
+
(index) => resourceGovernor.getTier(index),
|
|
1297
|
+
[resourceGovernor]
|
|
1298
|
+
);
|
|
1271
1299
|
return {
|
|
1272
1300
|
activeIndices,
|
|
1273
1301
|
warmIndices,
|
|
@@ -1276,13 +1304,15 @@ function useResource() {
|
|
|
1276
1304
|
networkType,
|
|
1277
1305
|
isActive,
|
|
1278
1306
|
prefetchIndex,
|
|
1307
|
+
preloadQueue,
|
|
1279
1308
|
setFocusedIndex,
|
|
1280
1309
|
setFocusedIndexImmediate,
|
|
1281
1310
|
setTotalItems,
|
|
1282
1311
|
shouldRenderVideo,
|
|
1283
1312
|
isAllocated,
|
|
1284
1313
|
isWarmAllocated,
|
|
1285
|
-
setPrefetchIndex
|
|
1314
|
+
setPrefetchIndex,
|
|
1315
|
+
getTier
|
|
1286
1316
|
};
|
|
1287
1317
|
}
|
|
1288
1318
|
var ACTIVE_HLS_DEFAULTS = {
|
|
@@ -1358,18 +1388,9 @@ function mapHlsError(data) {
|
|
|
1358
1388
|
}
|
|
1359
1389
|
function useHls(options) {
|
|
1360
1390
|
const { src, videoRef, isActive, isPrefetch, bufferTier = "active", hlsConfig, onError } = options;
|
|
1361
|
-
const
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
const hlsSupported = Hls.isSupported();
|
|
1365
|
-
const native = supportsNativeHls();
|
|
1366
|
-
setIsHlsJs(hlsSupported && !native);
|
|
1367
|
-
setIsNativeHls(native);
|
|
1368
|
-
}, []);
|
|
1369
|
-
const isHlsJsRef = useRef(false);
|
|
1370
|
-
const isNativeRef = useRef(false);
|
|
1371
|
-
isHlsJsRef.current = isHlsJs;
|
|
1372
|
-
isNativeRef.current = isNativeHls;
|
|
1391
|
+
const isHlsSupported = typeof window !== "undefined" && Hls.isSupported();
|
|
1392
|
+
const isNative = supportsNativeHls();
|
|
1393
|
+
const isHlsJs = isHlsSupported && !isNative;
|
|
1373
1394
|
const [isReady, setIsReady] = useState(false);
|
|
1374
1395
|
const hlsRef = useRef(null);
|
|
1375
1396
|
const onErrorRef = useRef(onError);
|
|
@@ -1394,18 +1415,7 @@ function useHls(options) {
|
|
|
1394
1415
|
currentSrcRef.current = void 0;
|
|
1395
1416
|
return;
|
|
1396
1417
|
}
|
|
1397
|
-
const isNative = isNativeRef.current;
|
|
1398
|
-
const isHlsSupported = isHlsJsRef.current;
|
|
1399
1418
|
if (!isActive && !isPrefetch) {
|
|
1400
|
-
if (isNative) {
|
|
1401
|
-
if (video.src) {
|
|
1402
|
-
video.removeAttribute("src");
|
|
1403
|
-
video.load();
|
|
1404
|
-
}
|
|
1405
|
-
setIsReady(false);
|
|
1406
|
-
currentSrcRef.current = void 0;
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
1419
|
destroy();
|
|
1410
1420
|
setIsReady(false);
|
|
1411
1421
|
canPlayFiredRef.current = false;
|
|
@@ -1413,45 +1423,40 @@ function useHls(options) {
|
|
|
1413
1423
|
return;
|
|
1414
1424
|
}
|
|
1415
1425
|
if (isNative) {
|
|
1416
|
-
if (
|
|
1417
|
-
|
|
1418
|
-
setIsReady(true);
|
|
1419
|
-
return void 0;
|
|
1420
|
-
}
|
|
1421
|
-
const handleCanPlayReuse = () => setIsReady(true);
|
|
1422
|
-
video.addEventListener("canplay", handleCanPlayReuse, { once: true });
|
|
1423
|
-
video.addEventListener("loadeddata", handleCanPlayReuse, { once: true });
|
|
1424
|
-
video.addEventListener("playing", handleCanPlayReuse, { once: true });
|
|
1425
|
-
if (video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1426
|
-
video.load();
|
|
1427
|
-
}
|
|
1428
|
-
return () => {
|
|
1429
|
-
video.removeEventListener("canplay", handleCanPlayReuse);
|
|
1430
|
-
video.removeEventListener("loadeddata", handleCanPlayReuse);
|
|
1431
|
-
video.removeEventListener("playing", handleCanPlayReuse);
|
|
1432
|
-
};
|
|
1433
|
-
}
|
|
1434
|
-
video.src = src;
|
|
1435
|
-
currentSrcRef.current = src;
|
|
1436
|
-
if (!isActive) {
|
|
1426
|
+
if (video.src !== src) {
|
|
1427
|
+
video.src = src;
|
|
1437
1428
|
video.load();
|
|
1438
1429
|
}
|
|
1439
|
-
if (video.
|
|
1430
|
+
if (!video.hasAttribute("webkit-playsinline")) {
|
|
1431
|
+
video.setAttribute("webkit-playsinline", "");
|
|
1432
|
+
}
|
|
1433
|
+
if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
|
|
1440
1434
|
setIsReady(true);
|
|
1441
|
-
|
|
1435
|
+
currentSrcRef.current = src;
|
|
1436
|
+
return;
|
|
1442
1437
|
}
|
|
1443
1438
|
setIsReady(false);
|
|
1444
|
-
|
|
1439
|
+
canPlayFiredRef.current = false;
|
|
1440
|
+
currentSrcRef.current = src;
|
|
1441
|
+
const handleCanPlay2 = () => {
|
|
1442
|
+
canPlayFiredRef.current = true;
|
|
1443
|
+
setIsReady(true);
|
|
1444
|
+
};
|
|
1445
|
+
const handleLoadedData = () => {
|
|
1446
|
+
if (!canPlayFiredRef.current) {
|
|
1447
|
+
canPlayFiredRef.current = true;
|
|
1448
|
+
setIsReady(true);
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1445
1451
|
video.addEventListener("canplay", handleCanPlay2, { once: true });
|
|
1446
|
-
video.addEventListener("loadeddata",
|
|
1447
|
-
video.addEventListener("playing", handleCanPlay2, { once: true });
|
|
1452
|
+
video.addEventListener("loadeddata", handleLoadedData, { once: true });
|
|
1448
1453
|
return () => {
|
|
1449
1454
|
video.removeEventListener("canplay", handleCanPlay2);
|
|
1450
|
-
video.removeEventListener("loadeddata",
|
|
1451
|
-
video.removeEventListener("playing", handleCanPlay2);
|
|
1455
|
+
video.removeEventListener("loadeddata", handleLoadedData);
|
|
1452
1456
|
};
|
|
1453
1457
|
}
|
|
1454
1458
|
if (!isHlsSupported) {
|
|
1459
|
+
onErrorRef.current?.("UNKNOWN", "HLS playback not supported in this browser");
|
|
1455
1460
|
return;
|
|
1456
1461
|
}
|
|
1457
1462
|
if (hlsRef.current && currentSrcRef.current === src) {
|
|
@@ -1515,7 +1520,7 @@ function useHls(options) {
|
|
|
1515
1520
|
currentSrcRef.current = void 0;
|
|
1516
1521
|
}
|
|
1517
1522
|
};
|
|
1518
|
-
}, [src, isActive, isPrefetch
|
|
1523
|
+
}, [src, isActive, isPrefetch]);
|
|
1519
1524
|
useEffect(() => {
|
|
1520
1525
|
const hls = hlsRef.current;
|
|
1521
1526
|
if (!hls) {
|
|
@@ -1531,10 +1536,15 @@ function useHls(options) {
|
|
|
1531
1536
|
for (const key of configKeys) {
|
|
1532
1537
|
hlsAnyConfig[key] = newConfig[key];
|
|
1533
1538
|
}
|
|
1539
|
+
if (prevTier === "warm" && bufferTier === "active") {
|
|
1540
|
+
try {
|
|
1541
|
+
hls.startLoad();
|
|
1542
|
+
} catch {
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1534
1545
|
}, [bufferTier]);
|
|
1535
1546
|
return {
|
|
1536
1547
|
isHlsJs,
|
|
1537
|
-
isNativeHls,
|
|
1538
1548
|
isReady,
|
|
1539
1549
|
destroy
|
|
1540
1550
|
};
|
|
@@ -1653,43 +1663,60 @@ function skeletonCircle(size) {
|
|
|
1653
1663
|
background: "rgba(255,255,255,0.1)"
|
|
1654
1664
|
};
|
|
1655
1665
|
}
|
|
1656
|
-
function
|
|
1657
|
-
|
|
1658
|
-
return /* @__PURE__ */ jsxs(
|
|
1666
|
+
function DefaultPauseAction() {
|
|
1667
|
+
return /* @__PURE__ */ jsx(
|
|
1659
1668
|
"div",
|
|
1660
1669
|
{
|
|
1661
1670
|
style: {
|
|
1662
|
-
|
|
1663
|
-
|
|
1671
|
+
width: 72,
|
|
1672
|
+
height: 72,
|
|
1673
|
+
borderRadius: "50%",
|
|
1674
|
+
background: "rgba(0, 0, 0, 0.55)",
|
|
1664
1675
|
display: "flex",
|
|
1665
1676
|
alignItems: "center",
|
|
1666
1677
|
justifyContent: "center",
|
|
1667
1678
|
pointerEvents: "none",
|
|
1668
|
-
|
|
1679
|
+
// Inset the triangle visually — triangles look off-center without this
|
|
1680
|
+
paddingLeft: 6
|
|
1669
1681
|
},
|
|
1670
|
-
children:
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
}
|
|
1688
|
-
` })
|
|
1689
|
-
]
|
|
1682
|
+
children: /* @__PURE__ */ jsx(
|
|
1683
|
+
"svg",
|
|
1684
|
+
{
|
|
1685
|
+
width: "32",
|
|
1686
|
+
height: "32",
|
|
1687
|
+
viewBox: "0 0 32 32",
|
|
1688
|
+
fill: "none",
|
|
1689
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1690
|
+
children: /* @__PURE__ */ jsx(
|
|
1691
|
+
"path",
|
|
1692
|
+
{
|
|
1693
|
+
d: "M8 5.5L27 16L8 26.5V5.5Z",
|
|
1694
|
+
fill: "white"
|
|
1695
|
+
}
|
|
1696
|
+
)
|
|
1697
|
+
}
|
|
1698
|
+
)
|
|
1690
1699
|
}
|
|
1691
1700
|
);
|
|
1692
1701
|
}
|
|
1702
|
+
var PLAY_AHEAD_MAX_CONCURRENT = 2;
|
|
1703
|
+
var _playAheadActive = 0;
|
|
1704
|
+
var _playAheadQueue = [];
|
|
1705
|
+
function acquirePlayAhead() {
|
|
1706
|
+
if (_playAheadActive < PLAY_AHEAD_MAX_CONCURRENT) {
|
|
1707
|
+
_playAheadActive++;
|
|
1708
|
+
return Promise.resolve();
|
|
1709
|
+
}
|
|
1710
|
+
return new Promise((resolve) => _playAheadQueue.push(resolve));
|
|
1711
|
+
}
|
|
1712
|
+
function releasePlayAhead() {
|
|
1713
|
+
_playAheadActive = Math.max(0, _playAheadActive - 1);
|
|
1714
|
+
const next = _playAheadQueue.shift();
|
|
1715
|
+
if (next) {
|
|
1716
|
+
_playAheadActive++;
|
|
1717
|
+
next();
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1693
1720
|
function VideoSlot({
|
|
1694
1721
|
item,
|
|
1695
1722
|
index,
|
|
@@ -1699,11 +1726,11 @@ function VideoSlot({
|
|
|
1699
1726
|
bufferTier,
|
|
1700
1727
|
isMuted,
|
|
1701
1728
|
onToggleMute,
|
|
1729
|
+
onAutoplayBlocked,
|
|
1702
1730
|
showFps = false,
|
|
1703
1731
|
renderOverlay,
|
|
1704
1732
|
renderActions,
|
|
1705
|
-
|
|
1706
|
-
renderDoubleTap
|
|
1733
|
+
renderPauseAction
|
|
1707
1734
|
}) {
|
|
1708
1735
|
const { optimisticManager, adapters } = useSDK();
|
|
1709
1736
|
if (!isVideoItem(item)) {
|
|
@@ -1736,11 +1763,11 @@ function VideoSlot({
|
|
|
1736
1763
|
bufferTier,
|
|
1737
1764
|
isMuted,
|
|
1738
1765
|
onToggleMute,
|
|
1766
|
+
onAutoplayBlocked,
|
|
1739
1767
|
showFps,
|
|
1740
1768
|
renderOverlay,
|
|
1741
1769
|
renderActions,
|
|
1742
|
-
|
|
1743
|
-
renderDoubleTap,
|
|
1770
|
+
renderPauseAction,
|
|
1744
1771
|
optimisticManager,
|
|
1745
1772
|
adapters
|
|
1746
1773
|
}
|
|
@@ -1755,11 +1782,11 @@ function VideoSlotInner({
|
|
|
1755
1782
|
bufferTier,
|
|
1756
1783
|
isMuted,
|
|
1757
1784
|
onToggleMute,
|
|
1785
|
+
onAutoplayBlocked,
|
|
1758
1786
|
showFps,
|
|
1759
1787
|
renderOverlay,
|
|
1760
1788
|
renderActions,
|
|
1761
|
-
|
|
1762
|
-
renderDoubleTap,
|
|
1789
|
+
renderPauseAction,
|
|
1763
1790
|
optimisticManager,
|
|
1764
1791
|
adapters
|
|
1765
1792
|
}) {
|
|
@@ -1770,7 +1797,7 @@ function VideoSlotInner({
|
|
|
1770
1797
|
const isHlsSource = sourceType === "hls";
|
|
1771
1798
|
const hlsSrc = isHlsSource && shouldLoadSrc ? src : void 0;
|
|
1772
1799
|
const mp4Src = !isHlsSource && shouldLoadSrc ? src : void 0;
|
|
1773
|
-
const { isReady: hlsReady
|
|
1800
|
+
const { isReady: hlsReady } = useHls({
|
|
1774
1801
|
src: hlsSrc,
|
|
1775
1802
|
videoRef,
|
|
1776
1803
|
isActive,
|
|
@@ -1809,9 +1836,7 @@ function VideoSlotInner({
|
|
|
1809
1836
|
}, [mp4Src, isActive, isPrefetch, isPreloaded, isHlsSource]);
|
|
1810
1837
|
const isReady = isHlsSource ? hlsReady : mp4Ready;
|
|
1811
1838
|
const [hasPlayedAhead, setHasPlayedAhead] = useState(false);
|
|
1812
|
-
const canPlayAhead = isHlsSource && !isNativeHls;
|
|
1813
1839
|
useEffect(() => {
|
|
1814
|
-
if (!canPlayAhead) return;
|
|
1815
1840
|
const video = videoRef.current;
|
|
1816
1841
|
if (!video) return;
|
|
1817
1842
|
if (isActive || !isReady) return;
|
|
@@ -1819,206 +1844,146 @@ function VideoSlotInner({
|
|
|
1819
1844
|
const prevMuted = video.muted;
|
|
1820
1845
|
video.muted = true;
|
|
1821
1846
|
let cancelled = false;
|
|
1822
|
-
let rafId = null;
|
|
1823
|
-
let vfcHandle = null;
|
|
1824
|
-
const pauseAfterDecode = () => {
|
|
1825
|
-
if (cancelled) return;
|
|
1826
|
-
video.pause();
|
|
1827
|
-
video.currentTime = 0;
|
|
1828
|
-
video.muted = prevMuted;
|
|
1829
|
-
setHasPlayedAhead(true);
|
|
1830
|
-
};
|
|
1831
1847
|
const doPlayAhead = async () => {
|
|
1848
|
+
await acquirePlayAhead();
|
|
1832
1849
|
try {
|
|
1833
1850
|
await video.play();
|
|
1834
|
-
if (cancelled)
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
pauseAfterDecode();
|
|
1839
|
-
});
|
|
1840
|
-
} else {
|
|
1841
|
-
rafId = requestAnimationFrame(() => {
|
|
1842
|
-
rafId = requestAnimationFrame(() => {
|
|
1843
|
-
rafId = null;
|
|
1844
|
-
pauseAfterDecode();
|
|
1845
|
-
});
|
|
1846
|
-
});
|
|
1851
|
+
if (cancelled) {
|
|
1852
|
+
video.pause();
|
|
1853
|
+
releasePlayAhead();
|
|
1854
|
+
return;
|
|
1847
1855
|
}
|
|
1856
|
+
const pauseAfterDecode = () => {
|
|
1857
|
+
video.pause();
|
|
1858
|
+
video.currentTime = 0;
|
|
1859
|
+
video.muted = prevMuted;
|
|
1860
|
+
releasePlayAhead();
|
|
1861
|
+
if (!cancelled) {
|
|
1862
|
+
setHasPlayedAhead(true);
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1865
|
+
setTimeout(pauseAfterDecode, 50);
|
|
1848
1866
|
} catch {
|
|
1849
|
-
|
|
1867
|
+
releasePlayAhead();
|
|
1850
1868
|
}
|
|
1851
1869
|
};
|
|
1852
1870
|
doPlayAhead();
|
|
1853
1871
|
return () => {
|
|
1854
1872
|
cancelled = true;
|
|
1855
|
-
if (rafId !== null) {
|
|
1856
|
-
cancelAnimationFrame(rafId);
|
|
1857
|
-
rafId = null;
|
|
1858
|
-
}
|
|
1859
|
-
if (vfcHandle !== null && "cancelVideoFrameCallback" in video) {
|
|
1860
|
-
video.cancelVideoFrameCallback(vfcHandle);
|
|
1861
|
-
vfcHandle = null;
|
|
1862
|
-
}
|
|
1863
1873
|
};
|
|
1864
|
-
}, [
|
|
1874
|
+
}, [isActive, isReady, hasPlayedAhead]);
|
|
1865
1875
|
useEffect(() => {
|
|
1866
1876
|
setHasPlayedAhead(false);
|
|
1867
1877
|
}, [src]);
|
|
1868
1878
|
const wasActiveRef = useRef(false);
|
|
1879
|
+
const [isManuallyPaused, setIsManuallyPaused] = useState(false);
|
|
1869
1880
|
useEffect(() => {
|
|
1870
1881
|
const video = videoRef.current;
|
|
1871
1882
|
if (!video) return;
|
|
1872
1883
|
let onReady = null;
|
|
1873
|
-
let
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
const startPlay = () => {
|
|
1878
|
-
if (onReady) {
|
|
1879
|
-
video.removeEventListener("canplay", onReady);
|
|
1880
|
-
video.removeEventListener("loadeddata", onReady);
|
|
1881
|
-
video.removeEventListener("playing", onReady);
|
|
1882
|
-
onReady = null;
|
|
1883
|
-
}
|
|
1884
|
-
if (fallbackTimerId !== null) {
|
|
1885
|
-
clearTimeout(fallbackTimerId);
|
|
1886
|
-
fallbackTimerId = null;
|
|
1887
|
-
}
|
|
1888
|
-
if (pollId !== null) {
|
|
1889
|
-
clearInterval(pollId);
|
|
1890
|
-
pollId = null;
|
|
1891
|
-
}
|
|
1884
|
+
let cancelled = false;
|
|
1885
|
+
const attemptPlay = () => {
|
|
1886
|
+
if (cancelled) return;
|
|
1887
|
+
if (isMuted) {
|
|
1892
1888
|
video.muted = true;
|
|
1893
|
-
video.play().
|
|
1894
|
-
video.muted = isMuted;
|
|
1895
|
-
}).catch(() => {
|
|
1896
|
-
video.muted = isMuted;
|
|
1889
|
+
video.play().catch(() => {
|
|
1897
1890
|
});
|
|
1898
|
-
};
|
|
1899
|
-
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1900
|
-
startPlay();
|
|
1901
1891
|
} else {
|
|
1902
|
-
|
|
1903
|
-
video.
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
if (
|
|
1913
|
-
|
|
1914
|
-
|
|
1892
|
+
video.muted = false;
|
|
1893
|
+
video.play().then(() => {
|
|
1894
|
+
if (cancelled) {
|
|
1895
|
+
video.pause();
|
|
1896
|
+
}
|
|
1897
|
+
}).catch((err) => {
|
|
1898
|
+
if (cancelled) return;
|
|
1899
|
+
if (err.name === "NotAllowedError") {
|
|
1900
|
+
video.muted = true;
|
|
1901
|
+
video.play().then(() => {
|
|
1902
|
+
if (cancelled) {
|
|
1903
|
+
video.pause();
|
|
1904
|
+
return;
|
|
1915
1905
|
}
|
|
1916
|
-
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
}
|
|
1920
|
-
fallbackTimerId = window.setTimeout(() => {
|
|
1921
|
-
fallbackTimerId = null;
|
|
1922
|
-
if (onReady) {
|
|
1923
|
-
startPlay();
|
|
1906
|
+
onAutoplayBlocked?.();
|
|
1907
|
+
}).catch(() => {
|
|
1908
|
+
});
|
|
1924
1909
|
}
|
|
1925
|
-
}
|
|
1910
|
+
});
|
|
1926
1911
|
}
|
|
1912
|
+
};
|
|
1913
|
+
if (isActive && !isManuallyPaused) {
|
|
1914
|
+
wasActiveRef.current = true;
|
|
1915
|
+
if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
|
|
1916
|
+
attemptPlay();
|
|
1917
|
+
} else {
|
|
1918
|
+
onReady = attemptPlay;
|
|
1919
|
+
video.addEventListener("canplay", onReady, { once: true });
|
|
1920
|
+
}
|
|
1921
|
+
} else if (isActive && isManuallyPaused) {
|
|
1922
|
+
wasActiveRef.current = true;
|
|
1923
|
+
video.pause();
|
|
1927
1924
|
} else if (wasActiveRef.current) {
|
|
1928
1925
|
video.pause();
|
|
1929
1926
|
video.currentTime = 0;
|
|
1930
1927
|
wasActiveRef.current = false;
|
|
1928
|
+
setIsManuallyPaused(false);
|
|
1931
1929
|
setHasPlayedAhead(false);
|
|
1932
1930
|
} else if (!hasPlayedAhead) {
|
|
1933
1931
|
video.pause();
|
|
1934
1932
|
}
|
|
1935
1933
|
return () => {
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
video.removeEventListener("loadeddata", onReady);
|
|
1939
|
-
video.removeEventListener("playing", onReady);
|
|
1940
|
-
}
|
|
1941
|
-
if (fallbackTimerId !== null) {
|
|
1942
|
-
clearTimeout(fallbackTimerId);
|
|
1943
|
-
fallbackTimerId = null;
|
|
1944
|
-
}
|
|
1945
|
-
if (pollId !== null) {
|
|
1946
|
-
clearInterval(pollId);
|
|
1947
|
-
pollId = null;
|
|
1948
|
-
}
|
|
1934
|
+
cancelled = true;
|
|
1935
|
+
if (onReady) video.removeEventListener("canplay", onReady);
|
|
1949
1936
|
};
|
|
1950
|
-
}, [isActive, isMuted, hasPlayedAhead,
|
|
1937
|
+
}, [isActive, isMuted, hasPlayedAhead, isManuallyPaused, onAutoplayBlocked]);
|
|
1951
1938
|
useEffect(() => {
|
|
1952
1939
|
const video = videoRef.current;
|
|
1953
1940
|
if (!video) return;
|
|
1954
|
-
|
|
1955
|
-
video.muted = isMuted;
|
|
1956
|
-
} else {
|
|
1957
|
-
video.muted = true;
|
|
1958
|
-
}
|
|
1941
|
+
video.muted = isActive ? isMuted : true;
|
|
1959
1942
|
}, [isMuted, isActive]);
|
|
1960
|
-
const
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
};
|
|
1943
|
+
const showPosterOverlay = !isReady && !hasPlayedAhead;
|
|
1944
|
+
const isPreDecoded = hasPlayedAhead;
|
|
1945
|
+
const [showMuteIndicator, setShowMuteIndicator] = useState(false);
|
|
1946
|
+
const muteIndicatorTimer = useRef(null);
|
|
1947
|
+
const handleToggleMute = useCallback(() => {
|
|
1948
|
+
onToggleMute();
|
|
1949
|
+
setShowMuteIndicator(true);
|
|
1950
|
+
if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
|
|
1951
|
+
muteIndicatorTimer.current = setTimeout(() => setShowMuteIndicator(false), 1200);
|
|
1952
|
+
}, [onToggleMute]);
|
|
1953
|
+
const tapStartRef = useRef(null);
|
|
1954
|
+
const TAP_SLOP_PX = 10;
|
|
1955
|
+
const handlePointerDown = useCallback((e) => {
|
|
1956
|
+
tapStartRef.current = { x: e.clientX, y: e.clientY };
|
|
1975
1957
|
}, []);
|
|
1976
|
-
|
|
1977
|
-
if (
|
|
1978
|
-
}, [isActive]);
|
|
1979
|
-
const showPosterOverlay = isActive ? !isReady && !isActuallyPlaying : canPlayAhead ? !hasPlayedAhead : !isReady;
|
|
1980
|
-
const [isPaused, setIsPaused] = useState(false);
|
|
1981
|
-
const [isDoubleTap, setIsDoubleTap] = useState(false);
|
|
1982
|
-
const lastTapTimeRef = useRef(0);
|
|
1983
|
-
const doubleTapTimerRef = useRef(null);
|
|
1984
|
-
const handleTap = useCallback(() => {
|
|
1958
|
+
const handleClick = useCallback((e) => {
|
|
1959
|
+
if (e.button !== 0) return;
|
|
1985
1960
|
if (!isActive) return;
|
|
1986
|
-
const
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
if (
|
|
1991
|
-
|
|
1992
|
-
|
|
1961
|
+
const start = tapStartRef.current;
|
|
1962
|
+
if (start) {
|
|
1963
|
+
const dx = Math.abs(e.clientX - start.x);
|
|
1964
|
+
const dy = Math.abs(e.clientY - start.y);
|
|
1965
|
+
if (dx > TAP_SLOP_PX || dy > TAP_SLOP_PX) {
|
|
1966
|
+
tapStartRef.current = null;
|
|
1967
|
+
return;
|
|
1993
1968
|
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1969
|
+
}
|
|
1970
|
+
tapStartRef.current = null;
|
|
1971
|
+
const video = videoRef.current;
|
|
1972
|
+
if (!video) return;
|
|
1973
|
+
if (video.paused || isManuallyPaused) {
|
|
1974
|
+
setIsManuallyPaused(false);
|
|
1975
|
+
video.muted = true;
|
|
1976
|
+
video.play().then(() => {
|
|
1977
|
+
requestAnimationFrame(() => {
|
|
1978
|
+
video.muted = isMuted;
|
|
1979
|
+
});
|
|
1980
|
+
}).catch(() => {
|
|
1981
|
+
});
|
|
1996
1982
|
} else {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
const video = videoRef.current;
|
|
2000
|
-
if (!video) return;
|
|
2001
|
-
if (video.paused) {
|
|
2002
|
-
video.play().catch(() => {
|
|
2003
|
-
});
|
|
2004
|
-
setIsPaused(false);
|
|
2005
|
-
} else {
|
|
2006
|
-
video.pause();
|
|
2007
|
-
setIsPaused(true);
|
|
2008
|
-
}
|
|
2009
|
-
}, 300);
|
|
1983
|
+
setIsManuallyPaused(true);
|
|
1984
|
+
video.pause();
|
|
2010
1985
|
}
|
|
2011
|
-
}, [isActive]);
|
|
2012
|
-
useEffect(() => {
|
|
2013
|
-
return () => {
|
|
2014
|
-
if (doubleTapTimerRef.current !== null) {
|
|
2015
|
-
clearTimeout(doubleTapTimerRef.current);
|
|
2016
|
-
}
|
|
2017
|
-
};
|
|
2018
|
-
}, []);
|
|
2019
|
-
useEffect(() => {
|
|
2020
|
-
if (isActive) setIsPaused(false);
|
|
2021
|
-
}, [isActive]);
|
|
1986
|
+
}, [isActive, isManuallyPaused, isMuted]);
|
|
2022
1987
|
const likeDelta = useSyncExternalStore(
|
|
2023
1988
|
optimisticManager.store.subscribe,
|
|
2024
1989
|
() => optimisticManager.getLikeDelta(item.id),
|
|
@@ -2036,12 +2001,36 @@ function VideoSlotInner({
|
|
|
2036
2001
|
followState,
|
|
2037
2002
|
share: () => adapters.interaction?.share?.(item.id),
|
|
2038
2003
|
isMuted,
|
|
2039
|
-
toggleMute:
|
|
2040
|
-
isPaused,
|
|
2041
|
-
togglePause: handleTap,
|
|
2004
|
+
toggleMute: handleToggleMute,
|
|
2042
2005
|
isActive,
|
|
2043
2006
|
index
|
|
2044
|
-
}), [item, likeDelta, followState, isMuted,
|
|
2007
|
+
}), [item, likeDelta, followState, isMuted, isActive, index, optimisticManager, adapters, handleToggleMute]);
|
|
2008
|
+
const pauseActions = useMemo(() => ({
|
|
2009
|
+
...actions,
|
|
2010
|
+
isPaused: isManuallyPaused,
|
|
2011
|
+
togglePlayPause: () => {
|
|
2012
|
+
const video = videoRef.current;
|
|
2013
|
+
if (!video) return;
|
|
2014
|
+
if (isManuallyPaused) {
|
|
2015
|
+
setIsManuallyPaused(false);
|
|
2016
|
+
video.muted = true;
|
|
2017
|
+
video.play().then(() => {
|
|
2018
|
+
requestAnimationFrame(() => {
|
|
2019
|
+
video.muted = isMuted;
|
|
2020
|
+
});
|
|
2021
|
+
}).catch(() => {
|
|
2022
|
+
});
|
|
2023
|
+
} else {
|
|
2024
|
+
setIsManuallyPaused(true);
|
|
2025
|
+
video.pause();
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}), [actions, isManuallyPaused, isMuted]);
|
|
2029
|
+
useEffect(() => {
|
|
2030
|
+
return () => {
|
|
2031
|
+
if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
|
|
2032
|
+
};
|
|
2033
|
+
}, []);
|
|
2045
2034
|
return /* @__PURE__ */ jsxs(
|
|
2046
2035
|
"div",
|
|
2047
2036
|
{
|
|
@@ -2052,7 +2041,8 @@ function VideoSlotInner({
|
|
|
2052
2041
|
background: "#111",
|
|
2053
2042
|
overflow: "hidden"
|
|
2054
2043
|
},
|
|
2055
|
-
|
|
2044
|
+
onPointerDown: handlePointerDown,
|
|
2045
|
+
onClick: handleClick,
|
|
2056
2046
|
children: [
|
|
2057
2047
|
/* @__PURE__ */ jsx(
|
|
2058
2048
|
"video",
|
|
@@ -2060,21 +2050,21 @@ function VideoSlotInner({
|
|
|
2060
2050
|
ref: videoRef,
|
|
2061
2051
|
src: mp4Src,
|
|
2062
2052
|
loop: true,
|
|
2063
|
-
muted: true,
|
|
2053
|
+
muted: isActive ? isMuted : true,
|
|
2064
2054
|
playsInline: true,
|
|
2065
|
-
autoPlay: isActive,
|
|
2066
2055
|
preload: shouldLoadSrc ? "auto" : "none",
|
|
2067
2056
|
style: {
|
|
2068
2057
|
width: "100%",
|
|
2069
2058
|
height: "100%",
|
|
2070
2059
|
objectFit: "cover",
|
|
2071
|
-
// Hide video until ready to avoid black frame flash
|
|
2060
|
+
// Hide video until ready to avoid black frame flash.
|
|
2061
|
+
// When pre-decoded, skip transition — first frame is already on canvas.
|
|
2072
2062
|
opacity: showPosterOverlay ? 0 : 1,
|
|
2073
|
-
transition: "opacity 0.15s ease"
|
|
2063
|
+
transition: isPreDecoded ? "none" : "opacity 0.15s ease"
|
|
2074
2064
|
}
|
|
2075
2065
|
}
|
|
2076
2066
|
),
|
|
2077
|
-
item.poster && /* @__PURE__ */ jsx(
|
|
2067
|
+
item.poster && !isPreDecoded && /* @__PURE__ */ jsx(
|
|
2078
2068
|
"div",
|
|
2079
2069
|
{
|
|
2080
2070
|
style: {
|
|
@@ -2089,19 +2079,29 @@ function VideoSlotInner({
|
|
|
2089
2079
|
}
|
|
2090
2080
|
}
|
|
2091
2081
|
),
|
|
2092
|
-
|
|
2082
|
+
showMuteIndicator && /* @__PURE__ */ jsx(
|
|
2093
2083
|
"div",
|
|
2094
2084
|
{
|
|
2095
2085
|
style: {
|
|
2096
2086
|
position: "absolute",
|
|
2097
|
-
|
|
2087
|
+
top: "50%",
|
|
2088
|
+
left: "50%",
|
|
2089
|
+
transform: "translate(-50%, -50%)",
|
|
2090
|
+
background: "rgba(0,0,0,0.6)",
|
|
2091
|
+
borderRadius: "50%",
|
|
2092
|
+
width: 64,
|
|
2093
|
+
height: 64,
|
|
2094
|
+
display: "flex",
|
|
2095
|
+
alignItems: "center",
|
|
2096
|
+
justifyContent: "center",
|
|
2097
|
+
fontSize: 28,
|
|
2098
2098
|
pointerEvents: "none",
|
|
2099
|
-
|
|
2099
|
+
animation: "reels-sdk-fadeInOut 1.2s ease forwards"
|
|
2100
2100
|
},
|
|
2101
|
-
children:
|
|
2101
|
+
children: isMuted ? "\u{1F507}" : "\u{1F50A}"
|
|
2102
2102
|
}
|
|
2103
2103
|
),
|
|
2104
|
-
|
|
2104
|
+
isActive && isManuallyPaused && /* @__PURE__ */ jsx(
|
|
2105
2105
|
"div",
|
|
2106
2106
|
{
|
|
2107
2107
|
style: {
|
|
@@ -2110,25 +2110,9 @@ function VideoSlotInner({
|
|
|
2110
2110
|
left: "50%",
|
|
2111
2111
|
transform: "translate(-50%, -50%)",
|
|
2112
2112
|
pointerEvents: "none",
|
|
2113
|
-
|
|
2114
|
-
opacity: 0.3
|
|
2113
|
+
animation: "reels-sdk-fadeIn 0.2s ease forwards"
|
|
2115
2114
|
},
|
|
2116
|
-
children:
|
|
2117
|
-
"div",
|
|
2118
|
-
{
|
|
2119
|
-
style: {
|
|
2120
|
-
background: "rgba(0,0,0,0.6)",
|
|
2121
|
-
borderRadius: "50%",
|
|
2122
|
-
width: 64,
|
|
2123
|
-
height: 64,
|
|
2124
|
-
display: "flex",
|
|
2125
|
-
alignItems: "center",
|
|
2126
|
-
justifyContent: "center",
|
|
2127
|
-
fontSize: 28
|
|
2128
|
-
},
|
|
2129
|
-
children: "\u25B6\uFE0F"
|
|
2130
|
-
}
|
|
2131
|
-
)
|
|
2115
|
+
children: renderPauseAction ? renderPauseAction(item, pauseActions) : /* @__PURE__ */ jsx(DefaultPauseAction, {})
|
|
2132
2116
|
}
|
|
2133
2117
|
),
|
|
2134
2118
|
/* @__PURE__ */ jsx(
|
|
@@ -2136,13 +2120,11 @@ function VideoSlotInner({
|
|
|
2136
2120
|
{
|
|
2137
2121
|
style: {
|
|
2138
2122
|
position: "absolute",
|
|
2139
|
-
bottom:
|
|
2123
|
+
bottom: 80,
|
|
2140
2124
|
left: 16,
|
|
2141
2125
|
right: 80,
|
|
2142
|
-
paddingBottom: 16,
|
|
2143
2126
|
pointerEvents: "none",
|
|
2144
|
-
color: "#fff"
|
|
2145
|
-
zIndex: 10
|
|
2127
|
+
color: "#fff"
|
|
2146
2128
|
},
|
|
2147
2129
|
children: renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsx(DefaultOverlay, { item })
|
|
2148
2130
|
}
|
|
@@ -2150,19 +2132,18 @@ function VideoSlotInner({
|
|
|
2150
2132
|
/* @__PURE__ */ jsx(
|
|
2151
2133
|
"div",
|
|
2152
2134
|
{
|
|
2153
|
-
onClick: (e) => e.stopPropagation(),
|
|
2154
2135
|
style: {
|
|
2155
2136
|
position: "absolute",
|
|
2156
|
-
bottom:
|
|
2137
|
+
bottom: 80,
|
|
2157
2138
|
right: 16,
|
|
2158
|
-
paddingBottom: 16,
|
|
2159
2139
|
display: "flex",
|
|
2160
2140
|
flexDirection: "column",
|
|
2161
2141
|
gap: 20,
|
|
2162
|
-
alignItems: "center"
|
|
2163
|
-
|
|
2164
|
-
|
|
2142
|
+
alignItems: "center"
|
|
2143
|
+
// Actions must be clickable; stop propagation so taps don't
|
|
2144
|
+
// also trigger the video play/pause handler on the container.
|
|
2165
2145
|
},
|
|
2146
|
+
onClick: (e) => e.stopPropagation(),
|
|
2166
2147
|
children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsx(DefaultActions, { item, actions })
|
|
2167
2148
|
}
|
|
2168
2149
|
),
|
|
@@ -2212,6 +2193,7 @@ function FpsCounter() {
|
|
|
2212
2193
|
}
|
|
2213
2194
|
);
|
|
2214
2195
|
}
|
|
2196
|
+
var RENDER_WINDOW_RADIUS = 3;
|
|
2215
2197
|
var centerStyle = {
|
|
2216
2198
|
height: "100dvh",
|
|
2217
2199
|
display: "flex",
|
|
@@ -2223,8 +2205,7 @@ var centerStyle = {
|
|
|
2223
2205
|
function ReelsFeed({
|
|
2224
2206
|
renderOverlay,
|
|
2225
2207
|
renderActions,
|
|
2226
|
-
|
|
2227
|
-
renderDoubleTap,
|
|
2208
|
+
renderPauseAction,
|
|
2228
2209
|
renderLoading,
|
|
2229
2210
|
renderEmpty,
|
|
2230
2211
|
renderError: _renderError,
|
|
@@ -2232,21 +2213,24 @@ function ReelsFeed({
|
|
|
2232
2213
|
loadMoreThreshold = 5,
|
|
2233
2214
|
onSlotChange,
|
|
2234
2215
|
gestureConfig,
|
|
2235
|
-
snapConfig
|
|
2216
|
+
snapConfig,
|
|
2217
|
+
initialMuted = true,
|
|
2218
|
+
onAutoplayBlocked
|
|
2236
2219
|
}) {
|
|
2237
2220
|
const { items, loading, loadInitial, loadMore, hasMore } = useFeed();
|
|
2221
|
+
const { adapters } = useSDK();
|
|
2238
2222
|
const {
|
|
2239
|
-
activeIndices,
|
|
2240
|
-
warmIndices,
|
|
2241
2223
|
focusedIndex,
|
|
2242
2224
|
prefetchIndex,
|
|
2225
|
+
preloadQueue,
|
|
2243
2226
|
setFocusedIndexImmediate,
|
|
2244
2227
|
setTotalItems,
|
|
2245
2228
|
shouldRenderVideo,
|
|
2246
2229
|
isWarmAllocated,
|
|
2247
2230
|
setPrefetchIndex
|
|
2248
2231
|
} = useResource();
|
|
2249
|
-
const [isMuted, setIsMuted] = useState(
|
|
2232
|
+
const [isMuted, setIsMuted] = useState(initialMuted);
|
|
2233
|
+
const [isDragMuted, setIsDragMuted] = useState(false);
|
|
2250
2234
|
const containerRef = useRef(null);
|
|
2251
2235
|
const slotCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
2252
2236
|
const activeIndexRef = useRef(0);
|
|
@@ -2262,6 +2246,14 @@ function ReelsFeed({
|
|
|
2262
2246
|
useEffect(() => {
|
|
2263
2247
|
setTotalItems(items.length);
|
|
2264
2248
|
}, [items.length, setTotalItems]);
|
|
2249
|
+
useEffect(() => {
|
|
2250
|
+
for (const idx of preloadQueue) {
|
|
2251
|
+
const item = items[idx];
|
|
2252
|
+
if (item && isVideoItem(item) && item.source.type === "hls") {
|
|
2253
|
+
adapters.videoLoader?.preloadMetadata?.(item.source.url);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
}, [preloadQueue, items, adapters.videoLoader]);
|
|
2265
2257
|
useEffect(() => {
|
|
2266
2258
|
if (items.length - focusedIndex <= loadMoreThreshold && hasMore && !loading) {
|
|
2267
2259
|
loadMore();
|
|
@@ -2282,7 +2274,7 @@ function ReelsFeed({
|
|
|
2282
2274
|
const observer = new MutationObserver(rebuild);
|
|
2283
2275
|
observer.observe(container, { childList: true, subtree: true });
|
|
2284
2276
|
return () => observer.disconnect();
|
|
2285
|
-
}, [items.length]);
|
|
2277
|
+
}, [items.length, focusedIndex]);
|
|
2286
2278
|
const containerHeight = useRef(
|
|
2287
2279
|
typeof window !== "undefined" ? window.innerHeight : 800
|
|
2288
2280
|
);
|
|
@@ -2373,6 +2365,12 @@ function ReelsFeed({
|
|
|
2373
2365
|
animateBounceBack(targets);
|
|
2374
2366
|
setPrefetchIndex(null);
|
|
2375
2367
|
}, [animateBounceBack, setPrefetchIndex]);
|
|
2368
|
+
const handleDragStart = useCallback(() => {
|
|
2369
|
+
setIsDragMuted(true);
|
|
2370
|
+
}, []);
|
|
2371
|
+
const handleDragEnd = useCallback(() => {
|
|
2372
|
+
setTimeout(() => setIsDragMuted(false), 50);
|
|
2373
|
+
}, []);
|
|
2376
2374
|
const { bind } = usePointerGesture({
|
|
2377
2375
|
axis: "y",
|
|
2378
2376
|
velocityThreshold: gestureConfig?.velocityThreshold ?? 0.3,
|
|
@@ -2385,7 +2383,9 @@ function ReelsFeed({
|
|
|
2385
2383
|
},
|
|
2386
2384
|
onDragThreshold: handleDragThreshold,
|
|
2387
2385
|
onSnap: handleSnap,
|
|
2388
|
-
onBounceBack: handleBounceBack
|
|
2386
|
+
onBounceBack: handleBounceBack,
|
|
2387
|
+
onDragStart: handleDragStart,
|
|
2388
|
+
onDragEnd: handleDragEnd
|
|
2389
2389
|
});
|
|
2390
2390
|
const getInitialTransformPx = useCallback(
|
|
2391
2391
|
(index) => {
|
|
@@ -2427,59 +2427,58 @@ function ReelsFeed({
|
|
|
2427
2427
|
70% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
2428
2428
|
100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
|
|
2429
2429
|
}
|
|
2430
|
+
@keyframes reels-sdk-fadeIn {
|
|
2431
|
+
from { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
|
|
2432
|
+
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
2433
|
+
}
|
|
2430
2434
|
@keyframes reels-sdk-spin {
|
|
2431
2435
|
to { transform: rotate(360deg); }
|
|
2432
2436
|
}
|
|
2433
2437
|
` }),
|
|
2434
|
-
(() => {
|
|
2435
|
-
const
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2438
|
+
items.map((item, index) => {
|
|
2439
|
+
const distFromFocus = Math.abs(index - focusedIndex);
|
|
2440
|
+
const wrapperStyle = {
|
|
2441
|
+
position: "absolute",
|
|
2442
|
+
inset: 0,
|
|
2443
|
+
contain: "layout style paint",
|
|
2444
|
+
willChange: distFromFocus <= 1 ? "transform" : "auto",
|
|
2445
|
+
transform: getInitialTransformPx(index)
|
|
2446
|
+
};
|
|
2447
|
+
if (distFromFocus > RENDER_WINDOW_RADIUS) {
|
|
2448
|
+
return /* @__PURE__ */ jsx("div", { "data-slot-index": index, style: wrapperStyle }, item.id);
|
|
2441
2449
|
}
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
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
|
-
renderPauseIndicator,
|
|
2475
|
-
renderDoubleTap
|
|
2476
|
-
}
|
|
2477
|
-
) : null
|
|
2478
|
-
},
|
|
2479
|
-
item.id
|
|
2480
|
-
);
|
|
2481
|
-
});
|
|
2482
|
-
})()
|
|
2450
|
+
const isActive = index === focusedIndex;
|
|
2451
|
+
const isPrefetch = index === prefetchIndex;
|
|
2452
|
+
const isWarm = isWarmAllocated(index);
|
|
2453
|
+
const isVisible = shouldRenderVideo(index) || isPrefetch;
|
|
2454
|
+
const bufferTier = isActive ? "active" : isWarm ? "warm" : "hot";
|
|
2455
|
+
return /* @__PURE__ */ jsx(
|
|
2456
|
+
"div",
|
|
2457
|
+
{
|
|
2458
|
+
"data-slot-index": index,
|
|
2459
|
+
style: wrapperStyle,
|
|
2460
|
+
children: /* @__PURE__ */ jsx(
|
|
2461
|
+
VideoSlot,
|
|
2462
|
+
{
|
|
2463
|
+
item,
|
|
2464
|
+
index,
|
|
2465
|
+
isActive,
|
|
2466
|
+
isPrefetch,
|
|
2467
|
+
isPreloaded: !isActive && !isPrefetch && isVisible,
|
|
2468
|
+
bufferTier,
|
|
2469
|
+
isMuted: isMuted || isDragMuted,
|
|
2470
|
+
onToggleMute: handleToggleMute,
|
|
2471
|
+
onAutoplayBlocked,
|
|
2472
|
+
showFps: showFps && isActive,
|
|
2473
|
+
renderOverlay,
|
|
2474
|
+
renderActions,
|
|
2475
|
+
renderPauseAction
|
|
2476
|
+
}
|
|
2477
|
+
)
|
|
2478
|
+
},
|
|
2479
|
+
item.id
|
|
2480
|
+
);
|
|
2481
|
+
})
|
|
2483
2482
|
]
|
|
2484
2483
|
}
|
|
2485
2484
|
);
|
|
@@ -2734,6 +2733,8 @@ var MockVideoLoader = class {
|
|
|
2734
2733
|
this.preloaded.clear();
|
|
2735
2734
|
this.loading.clear();
|
|
2736
2735
|
}
|
|
2736
|
+
preloadMetadata(_url) {
|
|
2737
|
+
}
|
|
2737
2738
|
};
|
|
2738
2739
|
var MockDataSource = class {
|
|
2739
2740
|
constructor(options = {}) {
|
|
@@ -3103,4 +3104,4 @@ var HttpError = class extends Error {
|
|
|
3103
3104
|
}
|
|
3104
3105
|
};
|
|
3105
3106
|
|
|
3106
|
-
export { DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions,
|
|
3107
|
+
export { DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, FeedManager, HttpDataSource, HttpError, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, OptimisticManager, PlayerEngine, PlayerStatus, ReelsFeed, ReelsProvider, ResourceGovernor, VALID_TRANSITIONS, VideoSlot, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|