@xhub-reels/sdk 0.1.8 → 0.1.10
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/dist/index.cjs +130 -43
- package/dist/index.d.cts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +130 -43
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1392,6 +1392,15 @@ function useHls(options) {
|
|
|
1392
1392
|
return;
|
|
1393
1393
|
}
|
|
1394
1394
|
if (!isActive && !isPrefetch) {
|
|
1395
|
+
if (isNative) {
|
|
1396
|
+
if (video.src) {
|
|
1397
|
+
video.removeAttribute("src");
|
|
1398
|
+
video.load();
|
|
1399
|
+
}
|
|
1400
|
+
setIsReady(false);
|
|
1401
|
+
currentSrcRef.current = void 0;
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1395
1404
|
destroy();
|
|
1396
1405
|
setIsReady(false);
|
|
1397
1406
|
canPlayFiredRef.current = false;
|
|
@@ -1399,20 +1408,36 @@ function useHls(options) {
|
|
|
1399
1408
|
return;
|
|
1400
1409
|
}
|
|
1401
1410
|
if (isNative) {
|
|
1402
|
-
if (
|
|
1403
|
-
video.
|
|
1411
|
+
if (currentSrcRef.current === src) {
|
|
1412
|
+
if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
|
|
1413
|
+
setIsReady(true);
|
|
1414
|
+
return void 0;
|
|
1415
|
+
}
|
|
1416
|
+
const handleCanPlayReuse = () => setIsReady(true);
|
|
1417
|
+
video.addEventListener("canplay", handleCanPlayReuse, { once: true });
|
|
1418
|
+
video.addEventListener("loadeddata", handleCanPlayReuse, { once: true });
|
|
1419
|
+
video.addEventListener("playing", handleCanPlayReuse, { once: true });
|
|
1420
|
+
return () => {
|
|
1421
|
+
video.removeEventListener("canplay", handleCanPlayReuse);
|
|
1422
|
+
video.removeEventListener("loadeddata", handleCanPlayReuse);
|
|
1423
|
+
video.removeEventListener("playing", handleCanPlayReuse);
|
|
1424
|
+
};
|
|
1404
1425
|
}
|
|
1426
|
+
video.src = src;
|
|
1427
|
+
currentSrcRef.current = src;
|
|
1405
1428
|
if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
|
|
1406
1429
|
setIsReady(true);
|
|
1407
|
-
|
|
1408
|
-
return;
|
|
1430
|
+
return void 0;
|
|
1409
1431
|
}
|
|
1410
1432
|
setIsReady(false);
|
|
1411
|
-
currentSrcRef.current = src;
|
|
1412
1433
|
const handleCanPlay2 = () => setIsReady(true);
|
|
1413
1434
|
video.addEventListener("canplay", handleCanPlay2, { once: true });
|
|
1435
|
+
video.addEventListener("loadeddata", handleCanPlay2, { once: true });
|
|
1436
|
+
video.addEventListener("playing", handleCanPlay2, { once: true });
|
|
1414
1437
|
return () => {
|
|
1415
1438
|
video.removeEventListener("canplay", handleCanPlay2);
|
|
1439
|
+
video.removeEventListener("loadeddata", handleCanPlay2);
|
|
1440
|
+
video.removeEventListener("playing", handleCanPlay2);
|
|
1416
1441
|
};
|
|
1417
1442
|
}
|
|
1418
1443
|
if (!isHlsSupported) {
|
|
@@ -1499,6 +1524,7 @@ function useHls(options) {
|
|
|
1499
1524
|
}, [bufferTier]);
|
|
1500
1525
|
return {
|
|
1501
1526
|
isHlsJs,
|
|
1527
|
+
isNativeHls: isNative,
|
|
1502
1528
|
isReady,
|
|
1503
1529
|
destroy
|
|
1504
1530
|
};
|
|
@@ -1628,7 +1654,8 @@ function VideoSlot({
|
|
|
1628
1654
|
onToggleMute,
|
|
1629
1655
|
showFps = false,
|
|
1630
1656
|
renderOverlay,
|
|
1631
|
-
renderActions
|
|
1657
|
+
renderActions,
|
|
1658
|
+
renderPauseIndicator
|
|
1632
1659
|
}) {
|
|
1633
1660
|
const { optimisticManager, adapters } = useSDK();
|
|
1634
1661
|
if (!isVideoItem(item)) {
|
|
@@ -1664,6 +1691,7 @@ function VideoSlot({
|
|
|
1664
1691
|
showFps,
|
|
1665
1692
|
renderOverlay,
|
|
1666
1693
|
renderActions,
|
|
1694
|
+
renderPauseIndicator,
|
|
1667
1695
|
optimisticManager,
|
|
1668
1696
|
adapters
|
|
1669
1697
|
}
|
|
@@ -1681,6 +1709,7 @@ function VideoSlotInner({
|
|
|
1681
1709
|
showFps,
|
|
1682
1710
|
renderOverlay,
|
|
1683
1711
|
renderActions,
|
|
1712
|
+
renderPauseIndicator,
|
|
1684
1713
|
optimisticManager,
|
|
1685
1714
|
adapters
|
|
1686
1715
|
}) {
|
|
@@ -1691,7 +1720,7 @@ function VideoSlotInner({
|
|
|
1691
1720
|
const isHlsSource = sourceType === "hls";
|
|
1692
1721
|
const hlsSrc = isHlsSource && shouldLoadSrc ? src : void 0;
|
|
1693
1722
|
const mp4Src = !isHlsSource && shouldLoadSrc ? src : void 0;
|
|
1694
|
-
const { isReady: hlsReady } = useHls({
|
|
1723
|
+
const { isReady: hlsReady, isNativeHls } = useHls({
|
|
1695
1724
|
src: hlsSrc,
|
|
1696
1725
|
videoRef,
|
|
1697
1726
|
isActive,
|
|
@@ -1730,7 +1759,9 @@ function VideoSlotInner({
|
|
|
1730
1759
|
}, [mp4Src, isActive, isPrefetch, isPreloaded, isHlsSource]);
|
|
1731
1760
|
const isReady = isHlsSource ? hlsReady : mp4Ready;
|
|
1732
1761
|
const [hasPlayedAhead, setHasPlayedAhead] = react.useState(false);
|
|
1762
|
+
const canPlayAhead = isHlsSource && !isNativeHls;
|
|
1733
1763
|
react.useEffect(() => {
|
|
1764
|
+
if (!canPlayAhead) return;
|
|
1734
1765
|
const video = videoRef.current;
|
|
1735
1766
|
if (!video) return;
|
|
1736
1767
|
if (isActive || !isReady) return;
|
|
@@ -1757,7 +1788,7 @@ function VideoSlotInner({
|
|
|
1757
1788
|
return () => {
|
|
1758
1789
|
cancelled = true;
|
|
1759
1790
|
};
|
|
1760
|
-
}, [isActive, isReady, hasPlayedAhead]);
|
|
1791
|
+
}, [canPlayAhead, isActive, isReady, hasPlayedAhead]);
|
|
1761
1792
|
react.useEffect(() => {
|
|
1762
1793
|
setHasPlayedAhead(false);
|
|
1763
1794
|
}, [src]);
|
|
@@ -1768,16 +1799,30 @@ function VideoSlotInner({
|
|
|
1768
1799
|
let onReady = null;
|
|
1769
1800
|
if (isActive) {
|
|
1770
1801
|
wasActiveRef.current = true;
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1802
|
+
const startPlay = () => {
|
|
1803
|
+
if (onReady) {
|
|
1804
|
+
video.removeEventListener("canplay", onReady);
|
|
1805
|
+
video.removeEventListener("loadeddata", onReady);
|
|
1806
|
+
video.removeEventListener("playing", onReady);
|
|
1807
|
+
onReady = null;
|
|
1808
|
+
}
|
|
1809
|
+
video.muted = true;
|
|
1810
|
+
video.play().then(() => {
|
|
1811
|
+
video.muted = isMuted;
|
|
1812
|
+
}).catch(() => {
|
|
1813
|
+
video.muted = isMuted;
|
|
1774
1814
|
});
|
|
1815
|
+
};
|
|
1816
|
+
if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
|
|
1817
|
+
startPlay();
|
|
1775
1818
|
} else {
|
|
1776
|
-
onReady =
|
|
1777
|
-
video.play().catch(() => {
|
|
1778
|
-
});
|
|
1779
|
-
};
|
|
1819
|
+
onReady = startPlay;
|
|
1780
1820
|
video.addEventListener("canplay", onReady, { once: true });
|
|
1821
|
+
video.addEventListener("loadeddata", onReady, { once: true });
|
|
1822
|
+
video.addEventListener("playing", onReady, { once: true });
|
|
1823
|
+
if (video.readyState === HTMLMediaElement.HAVE_NOTHING && isNativeHls && video.src) {
|
|
1824
|
+
video.load();
|
|
1825
|
+
}
|
|
1781
1826
|
}
|
|
1782
1827
|
} else if (wasActiveRef.current) {
|
|
1783
1828
|
video.pause();
|
|
@@ -1788,28 +1833,54 @@ function VideoSlotInner({
|
|
|
1788
1833
|
video.pause();
|
|
1789
1834
|
}
|
|
1790
1835
|
return () => {
|
|
1791
|
-
if (onReady)
|
|
1836
|
+
if (onReady) {
|
|
1837
|
+
video.removeEventListener("canplay", onReady);
|
|
1838
|
+
video.removeEventListener("loadeddata", onReady);
|
|
1839
|
+
video.removeEventListener("playing", onReady);
|
|
1840
|
+
}
|
|
1792
1841
|
};
|
|
1793
|
-
}, [isActive, isMuted, hasPlayedAhead]);
|
|
1842
|
+
}, [isActive, isMuted, hasPlayedAhead, isNativeHls]);
|
|
1794
1843
|
react.useEffect(() => {
|
|
1795
1844
|
const video = videoRef.current;
|
|
1796
1845
|
if (!video) return;
|
|
1797
1846
|
video.muted = isMuted;
|
|
1798
1847
|
}, [isMuted]);
|
|
1799
|
-
const
|
|
1800
|
-
const [showMuteIndicator, setShowMuteIndicator] = react.useState(false);
|
|
1801
|
-
const muteIndicatorTimer = react.useRef(null);
|
|
1802
|
-
const handleTap = react.useCallback(() => {
|
|
1803
|
-
onToggleMute();
|
|
1804
|
-
setShowMuteIndicator(true);
|
|
1805
|
-
if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
|
|
1806
|
-
muteIndicatorTimer.current = setTimeout(() => setShowMuteIndicator(false), 1200);
|
|
1807
|
-
}, [onToggleMute]);
|
|
1848
|
+
const [isActuallyPlaying, setIsActuallyPlaying] = react.useState(false);
|
|
1808
1849
|
react.useEffect(() => {
|
|
1850
|
+
const video = videoRef.current;
|
|
1851
|
+
if (!video) return;
|
|
1852
|
+
const onPlaying = () => setIsActuallyPlaying(true);
|
|
1853
|
+
const onPause = () => setIsActuallyPlaying(false);
|
|
1854
|
+
const onEnded = () => setIsActuallyPlaying(false);
|
|
1855
|
+
video.addEventListener("playing", onPlaying);
|
|
1856
|
+
video.addEventListener("pause", onPause);
|
|
1857
|
+
video.addEventListener("ended", onEnded);
|
|
1809
1858
|
return () => {
|
|
1810
|
-
|
|
1859
|
+
video.removeEventListener("playing", onPlaying);
|
|
1860
|
+
video.removeEventListener("pause", onPause);
|
|
1861
|
+
video.removeEventListener("ended", onEnded);
|
|
1811
1862
|
};
|
|
1812
1863
|
}, []);
|
|
1864
|
+
react.useEffect(() => {
|
|
1865
|
+
if (!isActive) setIsActuallyPlaying(false);
|
|
1866
|
+
}, [isActive]);
|
|
1867
|
+
const showPosterOverlay = isActive ? !isReady && !isActuallyPlaying : canPlayAhead ? !hasPlayedAhead : !isReady;
|
|
1868
|
+
const [isPaused, setIsPaused] = react.useState(false);
|
|
1869
|
+
const handleTap = react.useCallback(() => {
|
|
1870
|
+
const video = videoRef.current;
|
|
1871
|
+
if (!video || !isActive) return;
|
|
1872
|
+
if (video.paused) {
|
|
1873
|
+
video.play().catch(() => {
|
|
1874
|
+
});
|
|
1875
|
+
setIsPaused(false);
|
|
1876
|
+
} else {
|
|
1877
|
+
video.pause();
|
|
1878
|
+
setIsPaused(true);
|
|
1879
|
+
}
|
|
1880
|
+
}, [isActive]);
|
|
1881
|
+
react.useEffect(() => {
|
|
1882
|
+
if (isActive) setIsPaused(false);
|
|
1883
|
+
}, [isActive]);
|
|
1813
1884
|
const likeDelta = react.useSyncExternalStore(
|
|
1814
1885
|
optimisticManager.store.subscribe,
|
|
1815
1886
|
() => optimisticManager.getLikeDelta(item.id),
|
|
@@ -1828,9 +1899,11 @@ function VideoSlotInner({
|
|
|
1828
1899
|
share: () => adapters.interaction?.share?.(item.id),
|
|
1829
1900
|
isMuted,
|
|
1830
1901
|
toggleMute: onToggleMute,
|
|
1902
|
+
isPaused,
|
|
1903
|
+
togglePause: handleTap,
|
|
1831
1904
|
isActive,
|
|
1832
1905
|
index
|
|
1833
|
-
}), [item, likeDelta, followState, isMuted, isActive, index, optimisticManager, adapters, onToggleMute]);
|
|
1906
|
+
}), [item, likeDelta, followState, isMuted, isPaused, isActive, index, optimisticManager, adapters, onToggleMute, handleTap]);
|
|
1834
1907
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1835
1908
|
"div",
|
|
1836
1909
|
{
|
|
@@ -1877,7 +1950,7 @@ function VideoSlotInner({
|
|
|
1877
1950
|
}
|
|
1878
1951
|
}
|
|
1879
1952
|
),
|
|
1880
|
-
|
|
1953
|
+
isPaused && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1881
1954
|
"div",
|
|
1882
1955
|
{
|
|
1883
1956
|
style: {
|
|
@@ -1885,18 +1958,26 @@ function VideoSlotInner({
|
|
|
1885
1958
|
top: "50%",
|
|
1886
1959
|
left: "50%",
|
|
1887
1960
|
transform: "translate(-50%, -50%)",
|
|
1888
|
-
background: "rgba(0,0,0,0.6)",
|
|
1889
|
-
borderRadius: "50%",
|
|
1890
|
-
width: 64,
|
|
1891
|
-
height: 64,
|
|
1892
|
-
display: "flex",
|
|
1893
|
-
alignItems: "center",
|
|
1894
|
-
justifyContent: "center",
|
|
1895
|
-
fontSize: 28,
|
|
1896
1961
|
pointerEvents: "none",
|
|
1897
|
-
|
|
1962
|
+
zIndex: 5,
|
|
1963
|
+
opacity: 0.3
|
|
1898
1964
|
},
|
|
1899
|
-
children:
|
|
1965
|
+
children: renderPauseIndicator ? renderPauseIndicator(isPaused) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1966
|
+
"div",
|
|
1967
|
+
{
|
|
1968
|
+
style: {
|
|
1969
|
+
background: "rgba(0,0,0,0.6)",
|
|
1970
|
+
borderRadius: "50%",
|
|
1971
|
+
width: 64,
|
|
1972
|
+
height: 64,
|
|
1973
|
+
display: "flex",
|
|
1974
|
+
alignItems: "center",
|
|
1975
|
+
justifyContent: "center",
|
|
1976
|
+
fontSize: 28
|
|
1977
|
+
},
|
|
1978
|
+
children: "\u25B6\uFE0F"
|
|
1979
|
+
}
|
|
1980
|
+
)
|
|
1900
1981
|
}
|
|
1901
1982
|
),
|
|
1902
1983
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1909,7 +1990,8 @@ function VideoSlotInner({
|
|
|
1909
1990
|
right: 80,
|
|
1910
1991
|
paddingBottom: 16,
|
|
1911
1992
|
pointerEvents: "none",
|
|
1912
|
-
color: "#fff"
|
|
1993
|
+
color: "#fff",
|
|
1994
|
+
zIndex: 10
|
|
1913
1995
|
},
|
|
1914
1996
|
children: renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultOverlay, { item })
|
|
1915
1997
|
}
|
|
@@ -1917,6 +1999,7 @@ function VideoSlotInner({
|
|
|
1917
1999
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1918
2000
|
"div",
|
|
1919
2001
|
{
|
|
2002
|
+
onClick: (e) => e.stopPropagation(),
|
|
1920
2003
|
style: {
|
|
1921
2004
|
position: "absolute",
|
|
1922
2005
|
bottom: 0,
|
|
@@ -1925,7 +2008,9 @@ function VideoSlotInner({
|
|
|
1925
2008
|
display: "flex",
|
|
1926
2009
|
flexDirection: "column",
|
|
1927
2010
|
gap: 20,
|
|
1928
|
-
alignItems: "center"
|
|
2011
|
+
alignItems: "center",
|
|
2012
|
+
pointerEvents: "auto",
|
|
2013
|
+
zIndex: 10
|
|
1929
2014
|
},
|
|
1930
2015
|
children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultActions, { item, actions })
|
|
1931
2016
|
}
|
|
@@ -1987,6 +2072,7 @@ var centerStyle = {
|
|
|
1987
2072
|
function ReelsFeed({
|
|
1988
2073
|
renderOverlay,
|
|
1989
2074
|
renderActions,
|
|
2075
|
+
renderPauseIndicator,
|
|
1990
2076
|
renderLoading,
|
|
1991
2077
|
renderEmpty,
|
|
1992
2078
|
renderError: _renderError,
|
|
@@ -2006,7 +2092,7 @@ function ReelsFeed({
|
|
|
2006
2092
|
isWarmAllocated,
|
|
2007
2093
|
setPrefetchIndex
|
|
2008
2094
|
} = useResource();
|
|
2009
|
-
const [isMuted, setIsMuted] = react.useState(
|
|
2095
|
+
const [isMuted, setIsMuted] = react.useState(false);
|
|
2010
2096
|
const containerRef = react.useRef(null);
|
|
2011
2097
|
const slotCacheRef = react.useRef(/* @__PURE__ */ new Map());
|
|
2012
2098
|
const activeIndexRef = react.useRef(0);
|
|
@@ -2220,7 +2306,8 @@ function ReelsFeed({
|
|
|
2220
2306
|
onToggleMute: handleToggleMute,
|
|
2221
2307
|
showFps: showFps && isActive,
|
|
2222
2308
|
renderOverlay,
|
|
2223
|
-
renderActions
|
|
2309
|
+
renderActions,
|
|
2310
|
+
renderPauseIndicator
|
|
2224
2311
|
}
|
|
2225
2312
|
)
|
|
2226
2313
|
},
|
package/dist/index.d.cts
CHANGED
|
@@ -302,6 +302,10 @@ interface SlotActions {
|
|
|
302
302
|
isMuted: boolean;
|
|
303
303
|
/** Toggle global mute */
|
|
304
304
|
toggleMute: () => void;
|
|
305
|
+
/** Whether the active video is currently paused */
|
|
306
|
+
isPaused: boolean;
|
|
307
|
+
/** Toggle pause/play on the active video */
|
|
308
|
+
togglePause: () => void;
|
|
305
309
|
/** Whether this slot is the active playing slot */
|
|
306
310
|
isActive: boolean;
|
|
307
311
|
/** Slot index in feed */
|
|
@@ -323,6 +327,13 @@ interface ReelsFeedProps {
|
|
|
323
327
|
* If not provided, SDK uses DefaultActions showing like/comment/share emojis.
|
|
324
328
|
*/
|
|
325
329
|
renderActions?: (item: ContentItem, actions: SlotActions) => ReactNode;
|
|
330
|
+
/**
|
|
331
|
+
* Custom pause/play indicator rendered when user taps to pause/play.
|
|
332
|
+
* Receives isPaused state. Return ReactNode.
|
|
333
|
+
* Positioned absolute center by SDK.
|
|
334
|
+
* If not provided, SDK uses a default ▶️/⏸️ emoji indicator.
|
|
335
|
+
*/
|
|
336
|
+
renderPauseIndicator?: (isPaused: boolean) => ReactNode;
|
|
326
337
|
/**
|
|
327
338
|
* Custom loading skeleton shown during initial feed load.
|
|
328
339
|
* If not provided, SDK uses DefaultSkeleton with shimmer animation.
|
|
@@ -713,7 +724,7 @@ interface ReelsProviderProps {
|
|
|
713
724
|
declare function ReelsProvider({ children, adapters, debug }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
714
725
|
declare function useSDK(): SDKContextValue;
|
|
715
726
|
|
|
716
|
-
declare function ReelsFeed({ renderOverlay, renderActions, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, }: ReelsFeedProps): string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
|
|
727
|
+
declare function ReelsFeed({ renderOverlay, renderActions, renderPauseIndicator, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, }: ReelsFeedProps): string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
|
|
717
728
|
|
|
718
729
|
/**
|
|
719
730
|
* useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
|
|
@@ -764,6 +775,12 @@ interface UseHlsOptions {
|
|
|
764
775
|
interface UseHlsReturn {
|
|
765
776
|
/** Whether hls.js is being used (false = native HLS on Safari) */
|
|
766
777
|
isHlsJs: boolean;
|
|
778
|
+
/**
|
|
779
|
+
* Whether the device uses native HLS (Safari / iOS WebView).
|
|
780
|
+
* When true, play-ahead (video.play() on non-active slots) must be skipped —
|
|
781
|
+
* iOS only allows one concurrently-playing video element at a time.
|
|
782
|
+
*/
|
|
783
|
+
isNativeHls: boolean;
|
|
767
784
|
/** Whether the video has buffered enough data to play without black flash */
|
|
768
785
|
isReady: boolean;
|
|
769
786
|
/** Destroy the HLS instance manually (also called automatically on unmount) */
|
|
@@ -783,8 +800,9 @@ interface VideoSlotProps {
|
|
|
783
800
|
showFps?: boolean;
|
|
784
801
|
renderOverlay?: (item: ContentItem, actions: SlotActions) => ReactNode;
|
|
785
802
|
renderActions?: (item: ContentItem, actions: SlotActions) => ReactNode;
|
|
803
|
+
renderPauseIndicator?: (isPaused: boolean) => ReactNode;
|
|
786
804
|
}
|
|
787
|
-
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
805
|
+
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, renderPauseIndicator, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
788
806
|
|
|
789
807
|
declare function DefaultOverlay({ item }: {
|
|
790
808
|
item: ContentItem;
|
package/dist/index.d.ts
CHANGED
|
@@ -302,6 +302,10 @@ interface SlotActions {
|
|
|
302
302
|
isMuted: boolean;
|
|
303
303
|
/** Toggle global mute */
|
|
304
304
|
toggleMute: () => void;
|
|
305
|
+
/** Whether the active video is currently paused */
|
|
306
|
+
isPaused: boolean;
|
|
307
|
+
/** Toggle pause/play on the active video */
|
|
308
|
+
togglePause: () => void;
|
|
305
309
|
/** Whether this slot is the active playing slot */
|
|
306
310
|
isActive: boolean;
|
|
307
311
|
/** Slot index in feed */
|
|
@@ -323,6 +327,13 @@ interface ReelsFeedProps {
|
|
|
323
327
|
* If not provided, SDK uses DefaultActions showing like/comment/share emojis.
|
|
324
328
|
*/
|
|
325
329
|
renderActions?: (item: ContentItem, actions: SlotActions) => ReactNode;
|
|
330
|
+
/**
|
|
331
|
+
* Custom pause/play indicator rendered when user taps to pause/play.
|
|
332
|
+
* Receives isPaused state. Return ReactNode.
|
|
333
|
+
* Positioned absolute center by SDK.
|
|
334
|
+
* If not provided, SDK uses a default ▶️/⏸️ emoji indicator.
|
|
335
|
+
*/
|
|
336
|
+
renderPauseIndicator?: (isPaused: boolean) => ReactNode;
|
|
326
337
|
/**
|
|
327
338
|
* Custom loading skeleton shown during initial feed load.
|
|
328
339
|
* If not provided, SDK uses DefaultSkeleton with shimmer animation.
|
|
@@ -713,7 +724,7 @@ interface ReelsProviderProps {
|
|
|
713
724
|
declare function ReelsProvider({ children, adapters, debug }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
714
725
|
declare function useSDK(): SDKContextValue;
|
|
715
726
|
|
|
716
|
-
declare function ReelsFeed({ renderOverlay, renderActions, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, }: ReelsFeedProps): string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
|
|
727
|
+
declare function ReelsFeed({ renderOverlay, renderActions, renderPauseIndicator, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, }: ReelsFeedProps): string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
|
|
717
728
|
|
|
718
729
|
/**
|
|
719
730
|
* useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
|
|
@@ -764,6 +775,12 @@ interface UseHlsOptions {
|
|
|
764
775
|
interface UseHlsReturn {
|
|
765
776
|
/** Whether hls.js is being used (false = native HLS on Safari) */
|
|
766
777
|
isHlsJs: boolean;
|
|
778
|
+
/**
|
|
779
|
+
* Whether the device uses native HLS (Safari / iOS WebView).
|
|
780
|
+
* When true, play-ahead (video.play() on non-active slots) must be skipped —
|
|
781
|
+
* iOS only allows one concurrently-playing video element at a time.
|
|
782
|
+
*/
|
|
783
|
+
isNativeHls: boolean;
|
|
767
784
|
/** Whether the video has buffered enough data to play without black flash */
|
|
768
785
|
isReady: boolean;
|
|
769
786
|
/** Destroy the HLS instance manually (also called automatically on unmount) */
|
|
@@ -783,8 +800,9 @@ interface VideoSlotProps {
|
|
|
783
800
|
showFps?: boolean;
|
|
784
801
|
renderOverlay?: (item: ContentItem, actions: SlotActions) => ReactNode;
|
|
785
802
|
renderActions?: (item: ContentItem, actions: SlotActions) => ReactNode;
|
|
803
|
+
renderPauseIndicator?: (isPaused: boolean) => ReactNode;
|
|
786
804
|
}
|
|
787
|
-
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
805
|
+
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, renderPauseIndicator, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
788
806
|
|
|
789
807
|
declare function DefaultOverlay({ item }: {
|
|
790
808
|
item: ContentItem;
|
package/dist/index.js
CHANGED
|
@@ -1386,6 +1386,15 @@ function useHls(options) {
|
|
|
1386
1386
|
return;
|
|
1387
1387
|
}
|
|
1388
1388
|
if (!isActive && !isPrefetch) {
|
|
1389
|
+
if (isNative) {
|
|
1390
|
+
if (video.src) {
|
|
1391
|
+
video.removeAttribute("src");
|
|
1392
|
+
video.load();
|
|
1393
|
+
}
|
|
1394
|
+
setIsReady(false);
|
|
1395
|
+
currentSrcRef.current = void 0;
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1389
1398
|
destroy();
|
|
1390
1399
|
setIsReady(false);
|
|
1391
1400
|
canPlayFiredRef.current = false;
|
|
@@ -1393,20 +1402,36 @@ function useHls(options) {
|
|
|
1393
1402
|
return;
|
|
1394
1403
|
}
|
|
1395
1404
|
if (isNative) {
|
|
1396
|
-
if (
|
|
1397
|
-
video.
|
|
1405
|
+
if (currentSrcRef.current === src) {
|
|
1406
|
+
if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
|
|
1407
|
+
setIsReady(true);
|
|
1408
|
+
return void 0;
|
|
1409
|
+
}
|
|
1410
|
+
const handleCanPlayReuse = () => setIsReady(true);
|
|
1411
|
+
video.addEventListener("canplay", handleCanPlayReuse, { once: true });
|
|
1412
|
+
video.addEventListener("loadeddata", handleCanPlayReuse, { once: true });
|
|
1413
|
+
video.addEventListener("playing", handleCanPlayReuse, { once: true });
|
|
1414
|
+
return () => {
|
|
1415
|
+
video.removeEventListener("canplay", handleCanPlayReuse);
|
|
1416
|
+
video.removeEventListener("loadeddata", handleCanPlayReuse);
|
|
1417
|
+
video.removeEventListener("playing", handleCanPlayReuse);
|
|
1418
|
+
};
|
|
1398
1419
|
}
|
|
1420
|
+
video.src = src;
|
|
1421
|
+
currentSrcRef.current = src;
|
|
1399
1422
|
if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
|
|
1400
1423
|
setIsReady(true);
|
|
1401
|
-
|
|
1402
|
-
return;
|
|
1424
|
+
return void 0;
|
|
1403
1425
|
}
|
|
1404
1426
|
setIsReady(false);
|
|
1405
|
-
currentSrcRef.current = src;
|
|
1406
1427
|
const handleCanPlay2 = () => setIsReady(true);
|
|
1407
1428
|
video.addEventListener("canplay", handleCanPlay2, { once: true });
|
|
1429
|
+
video.addEventListener("loadeddata", handleCanPlay2, { once: true });
|
|
1430
|
+
video.addEventListener("playing", handleCanPlay2, { once: true });
|
|
1408
1431
|
return () => {
|
|
1409
1432
|
video.removeEventListener("canplay", handleCanPlay2);
|
|
1433
|
+
video.removeEventListener("loadeddata", handleCanPlay2);
|
|
1434
|
+
video.removeEventListener("playing", handleCanPlay2);
|
|
1410
1435
|
};
|
|
1411
1436
|
}
|
|
1412
1437
|
if (!isHlsSupported) {
|
|
@@ -1493,6 +1518,7 @@ function useHls(options) {
|
|
|
1493
1518
|
}, [bufferTier]);
|
|
1494
1519
|
return {
|
|
1495
1520
|
isHlsJs,
|
|
1521
|
+
isNativeHls: isNative,
|
|
1496
1522
|
isReady,
|
|
1497
1523
|
destroy
|
|
1498
1524
|
};
|
|
@@ -1622,7 +1648,8 @@ function VideoSlot({
|
|
|
1622
1648
|
onToggleMute,
|
|
1623
1649
|
showFps = false,
|
|
1624
1650
|
renderOverlay,
|
|
1625
|
-
renderActions
|
|
1651
|
+
renderActions,
|
|
1652
|
+
renderPauseIndicator
|
|
1626
1653
|
}) {
|
|
1627
1654
|
const { optimisticManager, adapters } = useSDK();
|
|
1628
1655
|
if (!isVideoItem(item)) {
|
|
@@ -1658,6 +1685,7 @@ function VideoSlot({
|
|
|
1658
1685
|
showFps,
|
|
1659
1686
|
renderOverlay,
|
|
1660
1687
|
renderActions,
|
|
1688
|
+
renderPauseIndicator,
|
|
1661
1689
|
optimisticManager,
|
|
1662
1690
|
adapters
|
|
1663
1691
|
}
|
|
@@ -1675,6 +1703,7 @@ function VideoSlotInner({
|
|
|
1675
1703
|
showFps,
|
|
1676
1704
|
renderOverlay,
|
|
1677
1705
|
renderActions,
|
|
1706
|
+
renderPauseIndicator,
|
|
1678
1707
|
optimisticManager,
|
|
1679
1708
|
adapters
|
|
1680
1709
|
}) {
|
|
@@ -1685,7 +1714,7 @@ function VideoSlotInner({
|
|
|
1685
1714
|
const isHlsSource = sourceType === "hls";
|
|
1686
1715
|
const hlsSrc = isHlsSource && shouldLoadSrc ? src : void 0;
|
|
1687
1716
|
const mp4Src = !isHlsSource && shouldLoadSrc ? src : void 0;
|
|
1688
|
-
const { isReady: hlsReady } = useHls({
|
|
1717
|
+
const { isReady: hlsReady, isNativeHls } = useHls({
|
|
1689
1718
|
src: hlsSrc,
|
|
1690
1719
|
videoRef,
|
|
1691
1720
|
isActive,
|
|
@@ -1724,7 +1753,9 @@ function VideoSlotInner({
|
|
|
1724
1753
|
}, [mp4Src, isActive, isPrefetch, isPreloaded, isHlsSource]);
|
|
1725
1754
|
const isReady = isHlsSource ? hlsReady : mp4Ready;
|
|
1726
1755
|
const [hasPlayedAhead, setHasPlayedAhead] = useState(false);
|
|
1756
|
+
const canPlayAhead = isHlsSource && !isNativeHls;
|
|
1727
1757
|
useEffect(() => {
|
|
1758
|
+
if (!canPlayAhead) return;
|
|
1728
1759
|
const video = videoRef.current;
|
|
1729
1760
|
if (!video) return;
|
|
1730
1761
|
if (isActive || !isReady) return;
|
|
@@ -1751,7 +1782,7 @@ function VideoSlotInner({
|
|
|
1751
1782
|
return () => {
|
|
1752
1783
|
cancelled = true;
|
|
1753
1784
|
};
|
|
1754
|
-
}, [isActive, isReady, hasPlayedAhead]);
|
|
1785
|
+
}, [canPlayAhead, isActive, isReady, hasPlayedAhead]);
|
|
1755
1786
|
useEffect(() => {
|
|
1756
1787
|
setHasPlayedAhead(false);
|
|
1757
1788
|
}, [src]);
|
|
@@ -1762,16 +1793,30 @@ function VideoSlotInner({
|
|
|
1762
1793
|
let onReady = null;
|
|
1763
1794
|
if (isActive) {
|
|
1764
1795
|
wasActiveRef.current = true;
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1796
|
+
const startPlay = () => {
|
|
1797
|
+
if (onReady) {
|
|
1798
|
+
video.removeEventListener("canplay", onReady);
|
|
1799
|
+
video.removeEventListener("loadeddata", onReady);
|
|
1800
|
+
video.removeEventListener("playing", onReady);
|
|
1801
|
+
onReady = null;
|
|
1802
|
+
}
|
|
1803
|
+
video.muted = true;
|
|
1804
|
+
video.play().then(() => {
|
|
1805
|
+
video.muted = isMuted;
|
|
1806
|
+
}).catch(() => {
|
|
1807
|
+
video.muted = isMuted;
|
|
1768
1808
|
});
|
|
1809
|
+
};
|
|
1810
|
+
if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {
|
|
1811
|
+
startPlay();
|
|
1769
1812
|
} else {
|
|
1770
|
-
onReady =
|
|
1771
|
-
video.play().catch(() => {
|
|
1772
|
-
});
|
|
1773
|
-
};
|
|
1813
|
+
onReady = startPlay;
|
|
1774
1814
|
video.addEventListener("canplay", onReady, { once: true });
|
|
1815
|
+
video.addEventListener("loadeddata", onReady, { once: true });
|
|
1816
|
+
video.addEventListener("playing", onReady, { once: true });
|
|
1817
|
+
if (video.readyState === HTMLMediaElement.HAVE_NOTHING && isNativeHls && video.src) {
|
|
1818
|
+
video.load();
|
|
1819
|
+
}
|
|
1775
1820
|
}
|
|
1776
1821
|
} else if (wasActiveRef.current) {
|
|
1777
1822
|
video.pause();
|
|
@@ -1782,28 +1827,54 @@ function VideoSlotInner({
|
|
|
1782
1827
|
video.pause();
|
|
1783
1828
|
}
|
|
1784
1829
|
return () => {
|
|
1785
|
-
if (onReady)
|
|
1830
|
+
if (onReady) {
|
|
1831
|
+
video.removeEventListener("canplay", onReady);
|
|
1832
|
+
video.removeEventListener("loadeddata", onReady);
|
|
1833
|
+
video.removeEventListener("playing", onReady);
|
|
1834
|
+
}
|
|
1786
1835
|
};
|
|
1787
|
-
}, [isActive, isMuted, hasPlayedAhead]);
|
|
1836
|
+
}, [isActive, isMuted, hasPlayedAhead, isNativeHls]);
|
|
1788
1837
|
useEffect(() => {
|
|
1789
1838
|
const video = videoRef.current;
|
|
1790
1839
|
if (!video) return;
|
|
1791
1840
|
video.muted = isMuted;
|
|
1792
1841
|
}, [isMuted]);
|
|
1793
|
-
const
|
|
1794
|
-
const [showMuteIndicator, setShowMuteIndicator] = useState(false);
|
|
1795
|
-
const muteIndicatorTimer = useRef(null);
|
|
1796
|
-
const handleTap = useCallback(() => {
|
|
1797
|
-
onToggleMute();
|
|
1798
|
-
setShowMuteIndicator(true);
|
|
1799
|
-
if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
|
|
1800
|
-
muteIndicatorTimer.current = setTimeout(() => setShowMuteIndicator(false), 1200);
|
|
1801
|
-
}, [onToggleMute]);
|
|
1842
|
+
const [isActuallyPlaying, setIsActuallyPlaying] = useState(false);
|
|
1802
1843
|
useEffect(() => {
|
|
1844
|
+
const video = videoRef.current;
|
|
1845
|
+
if (!video) return;
|
|
1846
|
+
const onPlaying = () => setIsActuallyPlaying(true);
|
|
1847
|
+
const onPause = () => setIsActuallyPlaying(false);
|
|
1848
|
+
const onEnded = () => setIsActuallyPlaying(false);
|
|
1849
|
+
video.addEventListener("playing", onPlaying);
|
|
1850
|
+
video.addEventListener("pause", onPause);
|
|
1851
|
+
video.addEventListener("ended", onEnded);
|
|
1803
1852
|
return () => {
|
|
1804
|
-
|
|
1853
|
+
video.removeEventListener("playing", onPlaying);
|
|
1854
|
+
video.removeEventListener("pause", onPause);
|
|
1855
|
+
video.removeEventListener("ended", onEnded);
|
|
1805
1856
|
};
|
|
1806
1857
|
}, []);
|
|
1858
|
+
useEffect(() => {
|
|
1859
|
+
if (!isActive) setIsActuallyPlaying(false);
|
|
1860
|
+
}, [isActive]);
|
|
1861
|
+
const showPosterOverlay = isActive ? !isReady && !isActuallyPlaying : canPlayAhead ? !hasPlayedAhead : !isReady;
|
|
1862
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
1863
|
+
const handleTap = useCallback(() => {
|
|
1864
|
+
const video = videoRef.current;
|
|
1865
|
+
if (!video || !isActive) return;
|
|
1866
|
+
if (video.paused) {
|
|
1867
|
+
video.play().catch(() => {
|
|
1868
|
+
});
|
|
1869
|
+
setIsPaused(false);
|
|
1870
|
+
} else {
|
|
1871
|
+
video.pause();
|
|
1872
|
+
setIsPaused(true);
|
|
1873
|
+
}
|
|
1874
|
+
}, [isActive]);
|
|
1875
|
+
useEffect(() => {
|
|
1876
|
+
if (isActive) setIsPaused(false);
|
|
1877
|
+
}, [isActive]);
|
|
1807
1878
|
const likeDelta = useSyncExternalStore(
|
|
1808
1879
|
optimisticManager.store.subscribe,
|
|
1809
1880
|
() => optimisticManager.getLikeDelta(item.id),
|
|
@@ -1822,9 +1893,11 @@ function VideoSlotInner({
|
|
|
1822
1893
|
share: () => adapters.interaction?.share?.(item.id),
|
|
1823
1894
|
isMuted,
|
|
1824
1895
|
toggleMute: onToggleMute,
|
|
1896
|
+
isPaused,
|
|
1897
|
+
togglePause: handleTap,
|
|
1825
1898
|
isActive,
|
|
1826
1899
|
index
|
|
1827
|
-
}), [item, likeDelta, followState, isMuted, isActive, index, optimisticManager, adapters, onToggleMute]);
|
|
1900
|
+
}), [item, likeDelta, followState, isMuted, isPaused, isActive, index, optimisticManager, adapters, onToggleMute, handleTap]);
|
|
1828
1901
|
return /* @__PURE__ */ jsxs(
|
|
1829
1902
|
"div",
|
|
1830
1903
|
{
|
|
@@ -1871,7 +1944,7 @@ function VideoSlotInner({
|
|
|
1871
1944
|
}
|
|
1872
1945
|
}
|
|
1873
1946
|
),
|
|
1874
|
-
|
|
1947
|
+
isPaused && /* @__PURE__ */ jsx(
|
|
1875
1948
|
"div",
|
|
1876
1949
|
{
|
|
1877
1950
|
style: {
|
|
@@ -1879,18 +1952,26 @@ function VideoSlotInner({
|
|
|
1879
1952
|
top: "50%",
|
|
1880
1953
|
left: "50%",
|
|
1881
1954
|
transform: "translate(-50%, -50%)",
|
|
1882
|
-
background: "rgba(0,0,0,0.6)",
|
|
1883
|
-
borderRadius: "50%",
|
|
1884
|
-
width: 64,
|
|
1885
|
-
height: 64,
|
|
1886
|
-
display: "flex",
|
|
1887
|
-
alignItems: "center",
|
|
1888
|
-
justifyContent: "center",
|
|
1889
|
-
fontSize: 28,
|
|
1890
1955
|
pointerEvents: "none",
|
|
1891
|
-
|
|
1956
|
+
zIndex: 5,
|
|
1957
|
+
opacity: 0.3
|
|
1892
1958
|
},
|
|
1893
|
-
children:
|
|
1959
|
+
children: renderPauseIndicator ? renderPauseIndicator(isPaused) : /* @__PURE__ */ jsx(
|
|
1960
|
+
"div",
|
|
1961
|
+
{
|
|
1962
|
+
style: {
|
|
1963
|
+
background: "rgba(0,0,0,0.6)",
|
|
1964
|
+
borderRadius: "50%",
|
|
1965
|
+
width: 64,
|
|
1966
|
+
height: 64,
|
|
1967
|
+
display: "flex",
|
|
1968
|
+
alignItems: "center",
|
|
1969
|
+
justifyContent: "center",
|
|
1970
|
+
fontSize: 28
|
|
1971
|
+
},
|
|
1972
|
+
children: "\u25B6\uFE0F"
|
|
1973
|
+
}
|
|
1974
|
+
)
|
|
1894
1975
|
}
|
|
1895
1976
|
),
|
|
1896
1977
|
/* @__PURE__ */ jsx(
|
|
@@ -1903,7 +1984,8 @@ function VideoSlotInner({
|
|
|
1903
1984
|
right: 80,
|
|
1904
1985
|
paddingBottom: 16,
|
|
1905
1986
|
pointerEvents: "none",
|
|
1906
|
-
color: "#fff"
|
|
1987
|
+
color: "#fff",
|
|
1988
|
+
zIndex: 10
|
|
1907
1989
|
},
|
|
1908
1990
|
children: renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsx(DefaultOverlay, { item })
|
|
1909
1991
|
}
|
|
@@ -1911,6 +1993,7 @@ function VideoSlotInner({
|
|
|
1911
1993
|
/* @__PURE__ */ jsx(
|
|
1912
1994
|
"div",
|
|
1913
1995
|
{
|
|
1996
|
+
onClick: (e) => e.stopPropagation(),
|
|
1914
1997
|
style: {
|
|
1915
1998
|
position: "absolute",
|
|
1916
1999
|
bottom: 0,
|
|
@@ -1919,7 +2002,9 @@ function VideoSlotInner({
|
|
|
1919
2002
|
display: "flex",
|
|
1920
2003
|
flexDirection: "column",
|
|
1921
2004
|
gap: 20,
|
|
1922
|
-
alignItems: "center"
|
|
2005
|
+
alignItems: "center",
|
|
2006
|
+
pointerEvents: "auto",
|
|
2007
|
+
zIndex: 10
|
|
1923
2008
|
},
|
|
1924
2009
|
children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsx(DefaultActions, { item, actions })
|
|
1925
2010
|
}
|
|
@@ -1981,6 +2066,7 @@ var centerStyle = {
|
|
|
1981
2066
|
function ReelsFeed({
|
|
1982
2067
|
renderOverlay,
|
|
1983
2068
|
renderActions,
|
|
2069
|
+
renderPauseIndicator,
|
|
1984
2070
|
renderLoading,
|
|
1985
2071
|
renderEmpty,
|
|
1986
2072
|
renderError: _renderError,
|
|
@@ -2000,7 +2086,7 @@ function ReelsFeed({
|
|
|
2000
2086
|
isWarmAllocated,
|
|
2001
2087
|
setPrefetchIndex
|
|
2002
2088
|
} = useResource();
|
|
2003
|
-
const [isMuted, setIsMuted] = useState(
|
|
2089
|
+
const [isMuted, setIsMuted] = useState(false);
|
|
2004
2090
|
const containerRef = useRef(null);
|
|
2005
2091
|
const slotCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
2006
2092
|
const activeIndexRef = useRef(0);
|
|
@@ -2214,7 +2300,8 @@ function ReelsFeed({
|
|
|
2214
2300
|
onToggleMute: handleToggleMute,
|
|
2215
2301
|
showFps: showFps && isActive,
|
|
2216
2302
|
renderOverlay,
|
|
2217
|
-
renderActions
|
|
2303
|
+
renderActions,
|
|
2304
|
+
renderPauseIndicator
|
|
2218
2305
|
}
|
|
2219
2306
|
)
|
|
2220
2307
|
},
|