@xhub-reels/sdk 0.1.18 → 0.1.19
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 +59 -9
- package/dist/index.d.cts +17 -2
- package/dist/index.d.ts +17 -2
- package/dist/index.js +59 -9
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -741,9 +741,12 @@ var OptimisticManager = class {
|
|
|
741
741
|
}
|
|
742
742
|
};
|
|
743
743
|
var DEFAULT_RESOURCE_CONFIG = {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
744
|
+
// bufferWindow=2 → ±2 hot slots + 1 active = 5 HLS instances max.
|
|
745
|
+
// Reduced from 3 to eliminate media pipeline thread contention on iOS/Android WebView
|
|
746
|
+
// that caused jank after scrolling 4-5 videos (7 concurrent HLS instances was too many).
|
|
747
|
+
maxAllocations: 8,
|
|
748
|
+
bufferWindow: 2,
|
|
749
|
+
warmWindow: 3,
|
|
747
750
|
// 0ms debounce — setFocusedIndexImmediate is already used post-snap.
|
|
748
751
|
focusDebounceMs: 0,
|
|
749
752
|
preloadLookAhead: 3
|
|
@@ -1393,7 +1396,7 @@ function mapHlsError(data) {
|
|
|
1393
1396
|
}
|
|
1394
1397
|
}
|
|
1395
1398
|
function useHls(options) {
|
|
1396
|
-
const { src, videoRef, isActive, isPrefetch, bufferTier = "active", hlsConfig, onError } = options;
|
|
1399
|
+
const { src, videoRef, isActive, isPrefetch, bufferTier = "active", hlsConfig, onError, isDragging = false } = options;
|
|
1397
1400
|
const isHlsSupported = typeof window !== "undefined" && Hls__default.default.isSupported();
|
|
1398
1401
|
const isNative = supportsNativeHls();
|
|
1399
1402
|
const isHlsJs = isHlsSupported && !isNative;
|
|
@@ -1549,6 +1552,21 @@ function useHls(options) {
|
|
|
1549
1552
|
}
|
|
1550
1553
|
}
|
|
1551
1554
|
}, [bufferTier]);
|
|
1555
|
+
react.useEffect(() => {
|
|
1556
|
+
const hls = hlsRef.current;
|
|
1557
|
+
if (!hls || isActive) return;
|
|
1558
|
+
if (isDragging) {
|
|
1559
|
+
try {
|
|
1560
|
+
hls.stopLoad();
|
|
1561
|
+
} catch {
|
|
1562
|
+
}
|
|
1563
|
+
} else {
|
|
1564
|
+
try {
|
|
1565
|
+
hls.startLoad(-1);
|
|
1566
|
+
} catch {
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}, [isDragging, isActive]);
|
|
1552
1570
|
return {
|
|
1553
1571
|
isHlsJs,
|
|
1554
1572
|
isReady,
|
|
@@ -1705,7 +1723,8 @@ function DefaultPauseAction() {
|
|
|
1705
1723
|
}
|
|
1706
1724
|
);
|
|
1707
1725
|
}
|
|
1708
|
-
var PLAY_AHEAD_MAX_CONCURRENT =
|
|
1726
|
+
var PLAY_AHEAD_MAX_CONCURRENT = 3;
|
|
1727
|
+
var PLAY_AHEAD_STAGGER_MS = 50;
|
|
1709
1728
|
var _playAheadActive = 0;
|
|
1710
1729
|
var _playAheadQueue = [];
|
|
1711
1730
|
function acquirePlayAhead() {
|
|
@@ -1713,7 +1732,12 @@ function acquirePlayAhead() {
|
|
|
1713
1732
|
_playAheadActive++;
|
|
1714
1733
|
return Promise.resolve();
|
|
1715
1734
|
}
|
|
1716
|
-
return new Promise((resolve) =>
|
|
1735
|
+
return new Promise((resolve) => {
|
|
1736
|
+
const queuePosition = _playAheadQueue.length;
|
|
1737
|
+
_playAheadQueue.push(() => {
|
|
1738
|
+
setTimeout(resolve, PLAY_AHEAD_STAGGER_MS * queuePosition);
|
|
1739
|
+
});
|
|
1740
|
+
});
|
|
1717
1741
|
}
|
|
1718
1742
|
function releasePlayAhead() {
|
|
1719
1743
|
_playAheadActive = Math.max(0, _playAheadActive - 1);
|
|
@@ -1734,6 +1758,7 @@ function VideoSlot({
|
|
|
1734
1758
|
onToggleMute,
|
|
1735
1759
|
onAutoplayBlocked,
|
|
1736
1760
|
showFps = false,
|
|
1761
|
+
isDragging = false,
|
|
1737
1762
|
renderOverlay,
|
|
1738
1763
|
renderActions,
|
|
1739
1764
|
renderPauseAction
|
|
@@ -1771,6 +1796,7 @@ function VideoSlot({
|
|
|
1771
1796
|
onToggleMute,
|
|
1772
1797
|
onAutoplayBlocked,
|
|
1773
1798
|
showFps,
|
|
1799
|
+
isDragging,
|
|
1774
1800
|
renderOverlay,
|
|
1775
1801
|
renderActions,
|
|
1776
1802
|
renderPauseAction,
|
|
@@ -1790,6 +1816,7 @@ function VideoSlotInner({
|
|
|
1790
1816
|
onToggleMute,
|
|
1791
1817
|
onAutoplayBlocked,
|
|
1792
1818
|
showFps,
|
|
1819
|
+
isDragging,
|
|
1793
1820
|
renderOverlay,
|
|
1794
1821
|
renderActions,
|
|
1795
1822
|
renderPauseAction,
|
|
@@ -1811,6 +1838,7 @@ function VideoSlotInner({
|
|
|
1811
1838
|
// so useHls creates the HLS instance and starts buffering
|
|
1812
1839
|
isPrefetch: isPrefetch || isPreloaded,
|
|
1813
1840
|
bufferTier,
|
|
1841
|
+
isDragging,
|
|
1814
1842
|
onError: (code, message) => {
|
|
1815
1843
|
console.error(`[VideoSlot] HLS error: ${code} \u2014 ${message}`);
|
|
1816
1844
|
}
|
|
@@ -2200,6 +2228,7 @@ function FpsCounter() {
|
|
|
2200
2228
|
);
|
|
2201
2229
|
}
|
|
2202
2230
|
var RENDER_WINDOW_RADIUS = 3;
|
|
2231
|
+
var PLACEHOLDER_EXTRA = 2;
|
|
2203
2232
|
var centerStyle = {
|
|
2204
2233
|
height: "100dvh",
|
|
2205
2234
|
display: "flex",
|
|
@@ -2277,9 +2306,20 @@ function ReelsFeed({
|
|
|
2277
2306
|
}
|
|
2278
2307
|
};
|
|
2279
2308
|
rebuild();
|
|
2280
|
-
|
|
2309
|
+
let rebuildTimer = null;
|
|
2310
|
+
const debouncedRebuild = () => {
|
|
2311
|
+
if (rebuildTimer !== null) return;
|
|
2312
|
+
rebuildTimer = setTimeout(() => {
|
|
2313
|
+
rebuildTimer = null;
|
|
2314
|
+
rebuild();
|
|
2315
|
+
}, 16);
|
|
2316
|
+
};
|
|
2317
|
+
const observer = new MutationObserver(debouncedRebuild);
|
|
2281
2318
|
observer.observe(container, { childList: true, subtree: true });
|
|
2282
|
-
return () =>
|
|
2319
|
+
return () => {
|
|
2320
|
+
observer.disconnect();
|
|
2321
|
+
if (rebuildTimer !== null) clearTimeout(rebuildTimer);
|
|
2322
|
+
};
|
|
2283
2323
|
}, [items.length, focusedIndex]);
|
|
2284
2324
|
const containerHeight = react.useRef(
|
|
2285
2325
|
typeof window !== "undefined" ? window.innerHeight : 800
|
|
@@ -2405,6 +2445,15 @@ function ReelsFeed({
|
|
|
2405
2445
|
const handleToggleMute = react.useCallback(() => {
|
|
2406
2446
|
setIsMuted((prev) => !prev);
|
|
2407
2447
|
}, []);
|
|
2448
|
+
const windowedItems = react.useMemo(() => {
|
|
2449
|
+
const start = Math.max(0, focusedIndex - RENDER_WINDOW_RADIUS - PLACEHOLDER_EXTRA);
|
|
2450
|
+
const end = Math.min(items.length - 1, focusedIndex + RENDER_WINDOW_RADIUS + PLACEHOLDER_EXTRA);
|
|
2451
|
+
const result = [];
|
|
2452
|
+
for (let i = start; i <= end; i++) {
|
|
2453
|
+
result.push({ item: items[i], index: i });
|
|
2454
|
+
}
|
|
2455
|
+
return result;
|
|
2456
|
+
}, [items, focusedIndex]);
|
|
2408
2457
|
if (loading && items.length === 0) {
|
|
2409
2458
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...centerStyle, flexDirection: "column", gap: 0 }, children: renderLoading ? renderLoading() : /* @__PURE__ */ jsxRuntime.jsx(DefaultSkeleton, {}) });
|
|
2410
2459
|
}
|
|
@@ -2441,7 +2490,7 @@ function ReelsFeed({
|
|
|
2441
2490
|
to { transform: rotate(360deg); }
|
|
2442
2491
|
}
|
|
2443
2492
|
` }),
|
|
2444
|
-
|
|
2493
|
+
windowedItems.map(({ item, index }) => {
|
|
2445
2494
|
const distFromFocus = Math.abs(index - focusedIndex);
|
|
2446
2495
|
const wrapperStyle = {
|
|
2447
2496
|
position: "absolute",
|
|
@@ -2476,6 +2525,7 @@ function ReelsFeed({
|
|
|
2476
2525
|
onToggleMute: handleToggleMute,
|
|
2477
2526
|
onAutoplayBlocked,
|
|
2478
2527
|
showFps: showFps && isActive,
|
|
2528
|
+
isDragging: isDragMuted,
|
|
2479
2529
|
renderOverlay,
|
|
2480
2530
|
renderActions,
|
|
2481
2531
|
renderPauseAction
|
package/dist/index.d.cts
CHANGED
|
@@ -577,7 +577,10 @@ declare class OptimisticManager {
|
|
|
577
577
|
* Tier 4 (Cold): preloadLookAhead beyond warm — metadata/manifest prefetch only (no DOM)
|
|
578
578
|
*
|
|
579
579
|
* Total DOM nodes = 1 + 2×bufferWindow + warmWindow (forward) + 1 (backward)
|
|
580
|
-
* Default: 1 +
|
|
580
|
+
* Default: 1 + 4 + 3 = 8 DOM nodes, ~80 MB memory (fits comfortably in 1 GB budget)
|
|
581
|
+
*
|
|
582
|
+
* bufferWindow reduced from 3→2 to cut concurrent HLS instances from 7→5,
|
|
583
|
+
* eliminating media pipeline thread contention on iOS/Android WebView.
|
|
581
584
|
*/
|
|
582
585
|
|
|
583
586
|
interface ResourceState {
|
|
@@ -831,6 +834,13 @@ interface UseHlsOptions {
|
|
|
831
834
|
hlsConfig?: Partial<HlsConfig>;
|
|
832
835
|
/** Called when hls.js encounters a fatal error. Maps to PlayerEngine error codes. */
|
|
833
836
|
onError?: (code: 'NETWORK_ERROR' | 'MEDIA_ERROR' | 'DECODE_ERROR' | 'UNKNOWN', message: string) => void;
|
|
837
|
+
/**
|
|
838
|
+
* Whether the user is currently dragging/swiping.
|
|
839
|
+
* When true, non-active slots pause HLS network fetching (hls.stopLoad) to
|
|
840
|
+
* free bandwidth for the active slot and reduce main-thread contention.
|
|
841
|
+
* Active slot is never paused regardless of this flag.
|
|
842
|
+
*/
|
|
843
|
+
isDragging?: boolean;
|
|
834
844
|
}
|
|
835
845
|
interface UseHlsReturn {
|
|
836
846
|
/** Whether hls.js is being used (false = native HLS on Safari) */
|
|
@@ -854,11 +864,16 @@ interface VideoSlotProps {
|
|
|
854
864
|
/** Called when unmuted autoplay fails and SDK falls back to muted playback */
|
|
855
865
|
onAutoplayBlocked?: () => void;
|
|
856
866
|
showFps?: boolean;
|
|
867
|
+
/**
|
|
868
|
+
* Whether the user is currently dragging. Passed to useHls to pause
|
|
869
|
+
* non-active HLS fetching during gesture, freeing bandwidth + main thread.
|
|
870
|
+
*/
|
|
871
|
+
isDragging?: boolean;
|
|
857
872
|
renderOverlay?: (item: ContentItem, actions: SlotActions) => react.ReactNode;
|
|
858
873
|
renderActions?: (item: ContentItem, actions: SlotActions) => react.ReactNode;
|
|
859
874
|
renderPauseAction?: (item: ContentItem, actions: PauseSlotActions) => react.ReactNode;
|
|
860
875
|
}
|
|
861
|
-
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, onAutoplayBlocked, showFps, renderOverlay, renderActions, renderPauseAction, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
876
|
+
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, onAutoplayBlocked, showFps, isDragging, renderOverlay, renderActions, renderPauseAction, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
862
877
|
|
|
863
878
|
declare function DefaultOverlay({ item }: {
|
|
864
879
|
item: ContentItem;
|
package/dist/index.d.ts
CHANGED
|
@@ -577,7 +577,10 @@ declare class OptimisticManager {
|
|
|
577
577
|
* Tier 4 (Cold): preloadLookAhead beyond warm — metadata/manifest prefetch only (no DOM)
|
|
578
578
|
*
|
|
579
579
|
* Total DOM nodes = 1 + 2×bufferWindow + warmWindow (forward) + 1 (backward)
|
|
580
|
-
* Default: 1 +
|
|
580
|
+
* Default: 1 + 4 + 3 = 8 DOM nodes, ~80 MB memory (fits comfortably in 1 GB budget)
|
|
581
|
+
*
|
|
582
|
+
* bufferWindow reduced from 3→2 to cut concurrent HLS instances from 7→5,
|
|
583
|
+
* eliminating media pipeline thread contention on iOS/Android WebView.
|
|
581
584
|
*/
|
|
582
585
|
|
|
583
586
|
interface ResourceState {
|
|
@@ -831,6 +834,13 @@ interface UseHlsOptions {
|
|
|
831
834
|
hlsConfig?: Partial<HlsConfig>;
|
|
832
835
|
/** Called when hls.js encounters a fatal error. Maps to PlayerEngine error codes. */
|
|
833
836
|
onError?: (code: 'NETWORK_ERROR' | 'MEDIA_ERROR' | 'DECODE_ERROR' | 'UNKNOWN', message: string) => void;
|
|
837
|
+
/**
|
|
838
|
+
* Whether the user is currently dragging/swiping.
|
|
839
|
+
* When true, non-active slots pause HLS network fetching (hls.stopLoad) to
|
|
840
|
+
* free bandwidth for the active slot and reduce main-thread contention.
|
|
841
|
+
* Active slot is never paused regardless of this flag.
|
|
842
|
+
*/
|
|
843
|
+
isDragging?: boolean;
|
|
834
844
|
}
|
|
835
845
|
interface UseHlsReturn {
|
|
836
846
|
/** Whether hls.js is being used (false = native HLS on Safari) */
|
|
@@ -854,11 +864,16 @@ interface VideoSlotProps {
|
|
|
854
864
|
/** Called when unmuted autoplay fails and SDK falls back to muted playback */
|
|
855
865
|
onAutoplayBlocked?: () => void;
|
|
856
866
|
showFps?: boolean;
|
|
867
|
+
/**
|
|
868
|
+
* Whether the user is currently dragging. Passed to useHls to pause
|
|
869
|
+
* non-active HLS fetching during gesture, freeing bandwidth + main thread.
|
|
870
|
+
*/
|
|
871
|
+
isDragging?: boolean;
|
|
857
872
|
renderOverlay?: (item: ContentItem, actions: SlotActions) => react.ReactNode;
|
|
858
873
|
renderActions?: (item: ContentItem, actions: SlotActions) => react.ReactNode;
|
|
859
874
|
renderPauseAction?: (item: ContentItem, actions: PauseSlotActions) => react.ReactNode;
|
|
860
875
|
}
|
|
861
|
-
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, onAutoplayBlocked, showFps, renderOverlay, renderActions, renderPauseAction, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
876
|
+
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, onAutoplayBlocked, showFps, isDragging, renderOverlay, renderActions, renderPauseAction, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
862
877
|
|
|
863
878
|
declare function DefaultOverlay({ item }: {
|
|
864
879
|
item: ContentItem;
|
package/dist/index.js
CHANGED
|
@@ -735,9 +735,12 @@ var OptimisticManager = class {
|
|
|
735
735
|
}
|
|
736
736
|
};
|
|
737
737
|
var DEFAULT_RESOURCE_CONFIG = {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
738
|
+
// bufferWindow=2 → ±2 hot slots + 1 active = 5 HLS instances max.
|
|
739
|
+
// Reduced from 3 to eliminate media pipeline thread contention on iOS/Android WebView
|
|
740
|
+
// that caused jank after scrolling 4-5 videos (7 concurrent HLS instances was too many).
|
|
741
|
+
maxAllocations: 8,
|
|
742
|
+
bufferWindow: 2,
|
|
743
|
+
warmWindow: 3,
|
|
741
744
|
// 0ms debounce — setFocusedIndexImmediate is already used post-snap.
|
|
742
745
|
focusDebounceMs: 0,
|
|
743
746
|
preloadLookAhead: 3
|
|
@@ -1387,7 +1390,7 @@ function mapHlsError(data) {
|
|
|
1387
1390
|
}
|
|
1388
1391
|
}
|
|
1389
1392
|
function useHls(options) {
|
|
1390
|
-
const { src, videoRef, isActive, isPrefetch, bufferTier = "active", hlsConfig, onError } = options;
|
|
1393
|
+
const { src, videoRef, isActive, isPrefetch, bufferTier = "active", hlsConfig, onError, isDragging = false } = options;
|
|
1391
1394
|
const isHlsSupported = typeof window !== "undefined" && Hls.isSupported();
|
|
1392
1395
|
const isNative = supportsNativeHls();
|
|
1393
1396
|
const isHlsJs = isHlsSupported && !isNative;
|
|
@@ -1543,6 +1546,21 @@ function useHls(options) {
|
|
|
1543
1546
|
}
|
|
1544
1547
|
}
|
|
1545
1548
|
}, [bufferTier]);
|
|
1549
|
+
useEffect(() => {
|
|
1550
|
+
const hls = hlsRef.current;
|
|
1551
|
+
if (!hls || isActive) return;
|
|
1552
|
+
if (isDragging) {
|
|
1553
|
+
try {
|
|
1554
|
+
hls.stopLoad();
|
|
1555
|
+
} catch {
|
|
1556
|
+
}
|
|
1557
|
+
} else {
|
|
1558
|
+
try {
|
|
1559
|
+
hls.startLoad(-1);
|
|
1560
|
+
} catch {
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}, [isDragging, isActive]);
|
|
1546
1564
|
return {
|
|
1547
1565
|
isHlsJs,
|
|
1548
1566
|
isReady,
|
|
@@ -1699,7 +1717,8 @@ function DefaultPauseAction() {
|
|
|
1699
1717
|
}
|
|
1700
1718
|
);
|
|
1701
1719
|
}
|
|
1702
|
-
var PLAY_AHEAD_MAX_CONCURRENT =
|
|
1720
|
+
var PLAY_AHEAD_MAX_CONCURRENT = 3;
|
|
1721
|
+
var PLAY_AHEAD_STAGGER_MS = 50;
|
|
1703
1722
|
var _playAheadActive = 0;
|
|
1704
1723
|
var _playAheadQueue = [];
|
|
1705
1724
|
function acquirePlayAhead() {
|
|
@@ -1707,7 +1726,12 @@ function acquirePlayAhead() {
|
|
|
1707
1726
|
_playAheadActive++;
|
|
1708
1727
|
return Promise.resolve();
|
|
1709
1728
|
}
|
|
1710
|
-
return new Promise((resolve) =>
|
|
1729
|
+
return new Promise((resolve) => {
|
|
1730
|
+
const queuePosition = _playAheadQueue.length;
|
|
1731
|
+
_playAheadQueue.push(() => {
|
|
1732
|
+
setTimeout(resolve, PLAY_AHEAD_STAGGER_MS * queuePosition);
|
|
1733
|
+
});
|
|
1734
|
+
});
|
|
1711
1735
|
}
|
|
1712
1736
|
function releasePlayAhead() {
|
|
1713
1737
|
_playAheadActive = Math.max(0, _playAheadActive - 1);
|
|
@@ -1728,6 +1752,7 @@ function VideoSlot({
|
|
|
1728
1752
|
onToggleMute,
|
|
1729
1753
|
onAutoplayBlocked,
|
|
1730
1754
|
showFps = false,
|
|
1755
|
+
isDragging = false,
|
|
1731
1756
|
renderOverlay,
|
|
1732
1757
|
renderActions,
|
|
1733
1758
|
renderPauseAction
|
|
@@ -1765,6 +1790,7 @@ function VideoSlot({
|
|
|
1765
1790
|
onToggleMute,
|
|
1766
1791
|
onAutoplayBlocked,
|
|
1767
1792
|
showFps,
|
|
1793
|
+
isDragging,
|
|
1768
1794
|
renderOverlay,
|
|
1769
1795
|
renderActions,
|
|
1770
1796
|
renderPauseAction,
|
|
@@ -1784,6 +1810,7 @@ function VideoSlotInner({
|
|
|
1784
1810
|
onToggleMute,
|
|
1785
1811
|
onAutoplayBlocked,
|
|
1786
1812
|
showFps,
|
|
1813
|
+
isDragging,
|
|
1787
1814
|
renderOverlay,
|
|
1788
1815
|
renderActions,
|
|
1789
1816
|
renderPauseAction,
|
|
@@ -1805,6 +1832,7 @@ function VideoSlotInner({
|
|
|
1805
1832
|
// so useHls creates the HLS instance and starts buffering
|
|
1806
1833
|
isPrefetch: isPrefetch || isPreloaded,
|
|
1807
1834
|
bufferTier,
|
|
1835
|
+
isDragging,
|
|
1808
1836
|
onError: (code, message) => {
|
|
1809
1837
|
console.error(`[VideoSlot] HLS error: ${code} \u2014 ${message}`);
|
|
1810
1838
|
}
|
|
@@ -2194,6 +2222,7 @@ function FpsCounter() {
|
|
|
2194
2222
|
);
|
|
2195
2223
|
}
|
|
2196
2224
|
var RENDER_WINDOW_RADIUS = 3;
|
|
2225
|
+
var PLACEHOLDER_EXTRA = 2;
|
|
2197
2226
|
var centerStyle = {
|
|
2198
2227
|
height: "100dvh",
|
|
2199
2228
|
display: "flex",
|
|
@@ -2271,9 +2300,20 @@ function ReelsFeed({
|
|
|
2271
2300
|
}
|
|
2272
2301
|
};
|
|
2273
2302
|
rebuild();
|
|
2274
|
-
|
|
2303
|
+
let rebuildTimer = null;
|
|
2304
|
+
const debouncedRebuild = () => {
|
|
2305
|
+
if (rebuildTimer !== null) return;
|
|
2306
|
+
rebuildTimer = setTimeout(() => {
|
|
2307
|
+
rebuildTimer = null;
|
|
2308
|
+
rebuild();
|
|
2309
|
+
}, 16);
|
|
2310
|
+
};
|
|
2311
|
+
const observer = new MutationObserver(debouncedRebuild);
|
|
2275
2312
|
observer.observe(container, { childList: true, subtree: true });
|
|
2276
|
-
return () =>
|
|
2313
|
+
return () => {
|
|
2314
|
+
observer.disconnect();
|
|
2315
|
+
if (rebuildTimer !== null) clearTimeout(rebuildTimer);
|
|
2316
|
+
};
|
|
2277
2317
|
}, [items.length, focusedIndex]);
|
|
2278
2318
|
const containerHeight = useRef(
|
|
2279
2319
|
typeof window !== "undefined" ? window.innerHeight : 800
|
|
@@ -2399,6 +2439,15 @@ function ReelsFeed({
|
|
|
2399
2439
|
const handleToggleMute = useCallback(() => {
|
|
2400
2440
|
setIsMuted((prev) => !prev);
|
|
2401
2441
|
}, []);
|
|
2442
|
+
const windowedItems = useMemo(() => {
|
|
2443
|
+
const start = Math.max(0, focusedIndex - RENDER_WINDOW_RADIUS - PLACEHOLDER_EXTRA);
|
|
2444
|
+
const end = Math.min(items.length - 1, focusedIndex + RENDER_WINDOW_RADIUS + PLACEHOLDER_EXTRA);
|
|
2445
|
+
const result = [];
|
|
2446
|
+
for (let i = start; i <= end; i++) {
|
|
2447
|
+
result.push({ item: items[i], index: i });
|
|
2448
|
+
}
|
|
2449
|
+
return result;
|
|
2450
|
+
}, [items, focusedIndex]);
|
|
2402
2451
|
if (loading && items.length === 0) {
|
|
2403
2452
|
return /* @__PURE__ */ jsx("div", { style: { ...centerStyle, flexDirection: "column", gap: 0 }, children: renderLoading ? renderLoading() : /* @__PURE__ */ jsx(DefaultSkeleton, {}) });
|
|
2404
2453
|
}
|
|
@@ -2435,7 +2484,7 @@ function ReelsFeed({
|
|
|
2435
2484
|
to { transform: rotate(360deg); }
|
|
2436
2485
|
}
|
|
2437
2486
|
` }),
|
|
2438
|
-
|
|
2487
|
+
windowedItems.map(({ item, index }) => {
|
|
2439
2488
|
const distFromFocus = Math.abs(index - focusedIndex);
|
|
2440
2489
|
const wrapperStyle = {
|
|
2441
2490
|
position: "absolute",
|
|
@@ -2470,6 +2519,7 @@ function ReelsFeed({
|
|
|
2470
2519
|
onToggleMute: handleToggleMute,
|
|
2471
2520
|
onAutoplayBlocked,
|
|
2472
2521
|
showFps: showFps && isActive,
|
|
2522
|
+
isDragging: isDragMuted,
|
|
2473
2523
|
renderOverlay,
|
|
2474
2524
|
renderActions,
|
|
2475
2525
|
renderPauseAction
|