@xhub-reel/feed 0.2.2 → 0.2.3

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.js CHANGED
@@ -4,8 +4,10 @@ var react = require('react');
4
4
  var core = require('@xhub-reel/core');
5
5
  var gestures = require('@xhub-reel/gestures');
6
6
  var player = require('@xhub-reel/player');
7
+ var playerEngine = require('@xhub-reel/player-engine');
7
8
  var jsxRuntime = require('react/jsx-runtime');
8
9
  var api = require('@xhub-reel/core/api');
10
+ var ui = require('@xhub-reel/ui');
9
11
  var reactQuery = require('@tanstack/react-query');
10
12
 
11
13
  // src/components/VideoFeed.tsx
@@ -26,8 +28,9 @@ var videoFeedItemStyles = {
26
28
  placeholder: {
27
29
  position: "absolute",
28
30
  inset: 0,
29
- backgroundSize: "cover",
30
- backgroundPosition: "center"
31
+ backgroundSize: "contain",
32
+ backgroundPosition: "center",
33
+ backgroundRepeat: "no-repeat"
31
34
  },
32
35
  // Tap area for play/pause
33
36
  tapArea: {
@@ -612,6 +615,7 @@ function useVideoFeedItemState({
612
615
  return {
613
616
  video,
614
617
  isActive,
618
+ priority,
615
619
  shouldRenderVideo,
616
620
  preload,
617
621
  isPreloaded,
@@ -638,9 +642,245 @@ function useVideoFeedItemState({
638
642
  handleSeekEnd
639
643
  };
640
644
  }
645
+ var VideoEnginePoolContext = react.createContext(null);
646
+ function VideoEnginePoolProvider({
647
+ children,
648
+ options,
649
+ config,
650
+ forceHLSJS,
651
+ onSlotStateChange,
652
+ onError,
653
+ onFirstFrameReady
654
+ }) {
655
+ const poolRef = react.useRef(null);
656
+ const [isReady, setIsReady] = react.useState(false);
657
+ const isClient = typeof window !== "undefined";
658
+ react.useEffect(() => {
659
+ if (!poolRef.current && isClient) {
660
+ poolRef.current = playerEngine.createVideoEnginePool({
661
+ ...options,
662
+ config,
663
+ forceHLSJS,
664
+ onSlotStateChange,
665
+ onError,
666
+ onFirstFrameReady
667
+ });
668
+ setIsReady(true);
669
+ console.log("[VideoEnginePoolProvider] Pool created and ready");
670
+ }
671
+ return () => {
672
+ poolRef.current?.destroy();
673
+ poolRef.current = null;
674
+ setIsReady(false);
675
+ };
676
+ }, []);
677
+ const getPool = react.useCallback(() => {
678
+ return poolRef.current;
679
+ }, []);
680
+ const contextValue = react.useMemo(
681
+ () => ({
682
+ pool: poolRef.current,
683
+ isReady,
684
+ getPool
685
+ }),
686
+ [isReady, getPool]
687
+ );
688
+ return /* @__PURE__ */ jsxRuntime.jsx(VideoEnginePoolContext.Provider, { value: contextValue, children });
689
+ }
690
+ function useVideoEnginePool() {
691
+ const context = react.useContext(VideoEnginePoolContext);
692
+ if (!context) {
693
+ throw new Error(
694
+ "[useVideoEnginePool] Must be used within a VideoEnginePoolProvider. Wrap your feed component with <VideoEnginePoolProvider>."
695
+ );
696
+ }
697
+ return context.pool;
698
+ }
699
+ function useVideoEnginePoolOptional() {
700
+ const context = react.useContext(VideoEnginePoolContext);
701
+ return context?.pool ?? null;
702
+ }
703
+ function useVideoEnginePoolReady() {
704
+ const context = react.useContext(VideoEnginePoolContext);
705
+ return context?.isReady ?? false;
706
+ }
707
+ function usePoolOrchestration(currentIndex, videos, enabled = true) {
708
+ const pool = useVideoEnginePoolOptional();
709
+ react.useEffect(() => {
710
+ if (!pool || !enabled || videos.length === 0) return;
711
+ const videoList = videos.map((v) => ({
712
+ id: v.id,
713
+ url: v.url
714
+ }));
715
+ pool.orchestrate(currentIndex, videoList);
716
+ }, [pool, currentIndex, videos, enabled]);
717
+ }
718
+ function usePooledVideo(video, isActive) {
719
+ const pool = useVideoEnginePoolOptional();
720
+ const containerRef = react.useRef(null);
721
+ const attachedRef = react.useRef(false);
722
+ const state = pool?.getState(video.id) ?? null;
723
+ const isReady = pool?.isReady(video.id) ?? false;
724
+ const element = pool?.getElement(video.id) ?? null;
725
+ const setContainerRef = react.useCallback((node) => {
726
+ containerRef.current = node;
727
+ if (!pool || !element) return;
728
+ if (node && !attachedRef.current) {
729
+ node.appendChild(element);
730
+ attachedRef.current = true;
731
+ } else if (!node && attachedRef.current) {
732
+ if (element.parentNode) {
733
+ element.parentNode.removeChild(element);
734
+ }
735
+ attachedRef.current = false;
736
+ }
737
+ }, [pool, element]);
738
+ react.useEffect(() => {
739
+ if (!pool) return;
740
+ if (isActive) {
741
+ pool.activate(video.id).catch((err) => {
742
+ console.warn("[usePooledVideo] Activation failed", video.id, err);
743
+ });
744
+ } else {
745
+ pool.deactivate(video.id);
746
+ }
747
+ }, [pool, video.id, isActive]);
748
+ react.useEffect(() => {
749
+ return () => {
750
+ if (attachedRef.current && element?.parentNode && element.parentNode === containerRef.current) {
751
+ element.parentNode.removeChild(element);
752
+ attachedRef.current = false;
753
+ }
754
+ };
755
+ }, [element]);
756
+ return {
757
+ element,
758
+ isReady,
759
+ state,
760
+ containerRef: setContainerRef
761
+ };
762
+ }
763
+ function usePoolStats() {
764
+ const pool = useVideoEnginePoolOptional();
765
+ return pool?.getStats() ?? null;
766
+ }
767
+ function usePoolMemoryControl() {
768
+ const pool = useVideoEnginePoolOptional();
769
+ const reduceMemory = react.useCallback(() => {
770
+ pool?.reduceMemory();
771
+ }, [pool]);
772
+ const pauseAll = react.useCallback(() => {
773
+ pool?.pauseAll();
774
+ }, [pool]);
775
+ return { reduceMemory, pauseAll };
776
+ }
641
777
  var VideoFeedItemPlayer = react.forwardRef(
642
- ({ placeholder, ...props }, ref) => {
643
- const { video, videoRef, shouldRenderVideo, preload, isPreloaded, initialMuted } = useVideoFeedItemContext();
778
+ ({ placeholder, forceNative = false, ...props }, ref) => {
779
+ const {
780
+ video,
781
+ videoRef,
782
+ shouldRenderVideo,
783
+ preload,
784
+ isPreloaded,
785
+ initialMuted,
786
+ isActive,
787
+ priority
788
+ } = useVideoFeedItemContext();
789
+ const pool = useVideoEnginePoolOptional();
790
+ const poolReady = useVideoEnginePoolReady();
791
+ const containerRef = react.useRef(null);
792
+ const attachedElementRef = react.useRef(null);
793
+ const [isLoading, setIsLoading] = react.useState(false);
794
+ const prepareStartedRef = react.useRef(false);
795
+ const usePool = pool && poolReady && !forceNative;
796
+ const attachElement = react.useCallback((element, isReady) => {
797
+ if (!containerRef.current) return false;
798
+ if (attachedElementRef.current && attachedElementRef.current.parentNode) {
799
+ attachedElementRef.current.parentNode.removeChild(attachedElementRef.current);
800
+ }
801
+ Object.assign(element.style, {
802
+ position: "absolute",
803
+ top: "0",
804
+ left: "0",
805
+ width: "100%",
806
+ height: "100%",
807
+ objectFit: "contain",
808
+ zIndex: "1",
809
+ opacity: isReady ? "1" : "0",
810
+ transition: "opacity 0.15s ease-out"
811
+ });
812
+ const handleCanPlay = () => {
813
+ element.style.opacity = "1";
814
+ setIsLoading(false);
815
+ };
816
+ if (element.readyState >= 3) {
817
+ handleCanPlay();
818
+ } else {
819
+ element.addEventListener("canplay", handleCanPlay, { once: true });
820
+ }
821
+ containerRef.current.appendChild(element);
822
+ attachedElementRef.current = element;
823
+ if (typeof ref === "function") {
824
+ ref(element);
825
+ } else if (ref) {
826
+ ref.current = element;
827
+ }
828
+ videoRef.current = element;
829
+ return true;
830
+ }, [ref, videoRef]);
831
+ react.useEffect(() => {
832
+ if (!usePool || !pool || !shouldRenderVideo) {
833
+ setIsLoading(false);
834
+ prepareStartedRef.current = false;
835
+ return;
836
+ }
837
+ const videoId = video.id;
838
+ const videoUrl = video.url;
839
+ const existingElement = pool.getElement(videoId);
840
+ if (existingElement) {
841
+ const isReady = pool.isReady(videoId);
842
+ const attached = attachElement(existingElement, isReady);
843
+ if (attached) {
844
+ if (!isReady) {
845
+ setIsLoading(true);
846
+ pool.prepare(videoId, videoUrl, { priority: "high" }).catch(() => {
847
+ });
848
+ }
849
+ console.log("[VideoFeedItemPlayer] Attached existing element:", videoId, { isReady });
850
+ return;
851
+ }
852
+ }
853
+ if (prepareStartedRef.current) return;
854
+ prepareStartedRef.current = true;
855
+ setIsLoading(true);
856
+ const poolPriority = isActive ? "high" : priority === "high" ? "high" : "medium";
857
+ pool.prepare(videoId, videoUrl, { priority: poolPriority }).then(() => {
858
+ const element = pool.getElement(videoId);
859
+ if (element) {
860
+ attachElement(element, true);
861
+ console.log("[VideoFeedItemPlayer] Prepared and attached:", videoId);
862
+ }
863
+ }).catch((err) => {
864
+ console.warn("[VideoFeedItemPlayer] Prepare failed:", videoId, err);
865
+ setIsLoading(false);
866
+ });
867
+ return () => {
868
+ if (attachedElementRef.current && attachedElementRef.current.parentNode === containerRef.current) {
869
+ containerRef.current?.removeChild(attachedElementRef.current);
870
+ }
871
+ attachedElementRef.current = null;
872
+ prepareStartedRef.current = false;
873
+ };
874
+ }, [usePool, pool, video.id, video.url, shouldRenderVideo, isActive, priority, attachElement]);
875
+ react.useEffect(() => {
876
+ if (!usePool || !pool) return;
877
+ const videoId = video.id;
878
+ if (isActive) {
879
+ pool.activate(videoId);
880
+ } else {
881
+ pool.deactivate(videoId);
882
+ }
883
+ }, [usePool, pool, video.id, isActive]);
644
884
  if (!shouldRenderVideo) {
645
885
  return placeholder ?? /* @__PURE__ */ jsxRuntime.jsx(
646
886
  "div",
@@ -654,6 +894,67 @@ var VideoFeedItemPlayer = react.forwardRef(
654
894
  }
655
895
  );
656
896
  }
897
+ if (usePool) {
898
+ return /* @__PURE__ */ jsxRuntime.jsxs(
899
+ "div",
900
+ {
901
+ ref: containerRef,
902
+ style: {
903
+ ...videoFeedItemStyles.video,
904
+ position: "relative"
905
+ },
906
+ ...props,
907
+ children: [
908
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
909
+ @keyframes xhub-reel-spin {
910
+ to { transform: rotate(360deg); }
911
+ }
912
+ ` }),
913
+ /* @__PURE__ */ jsxRuntime.jsx(
914
+ "div",
915
+ {
916
+ style: {
917
+ ...videoFeedItemStyles.placeholder,
918
+ backgroundImage: `url(${video.thumbnail})`,
919
+ position: "absolute",
920
+ inset: 0,
921
+ zIndex: 0
922
+ }
923
+ }
924
+ ),
925
+ isLoading && /* @__PURE__ */ jsxRuntime.jsx(
926
+ "div",
927
+ {
928
+ style: {
929
+ position: "absolute",
930
+ inset: 0,
931
+ display: "flex",
932
+ alignItems: "center",
933
+ justifyContent: "center",
934
+ zIndex: 2,
935
+ pointerEvents: "none"
936
+ },
937
+ children: /* @__PURE__ */ jsxRuntime.jsx(
938
+ "div",
939
+ {
940
+ style: {
941
+ width: 40,
942
+ height: 40,
943
+ borderWidth: 3,
944
+ borderStyle: "solid",
945
+ borderColor: "rgba(255, 255, 255, 0.2)",
946
+ borderTopColor: "#8B5CF6",
947
+ borderRadius: "50%",
948
+ animation: "xhub-reel-spin 0.8s linear infinite"
949
+ }
950
+ }
951
+ )
952
+ }
953
+ )
954
+ ]
955
+ }
956
+ );
957
+ }
657
958
  const showPoster = !isPreloaded;
658
959
  return /* @__PURE__ */ jsxRuntime.jsx(
659
960
  "video",
@@ -703,8 +1004,16 @@ var VideoFeedItemActions = react.forwardRef(
703
1004
  );
704
1005
  VideoFeedItemActions.displayName = "VideoFeedItemActions";
705
1006
  var VideoFeedItemTimeline = react.forwardRef(
706
- ({ expanded: expandedProp, ...props }, ref) => {
1007
+ ({
1008
+ expanded: expandedProp,
1009
+ showPreview = false,
1010
+ getThumbnailUrl,
1011
+ previewWidth,
1012
+ previewHeight,
1013
+ ...props
1014
+ }, ref) => {
707
1015
  const {
1016
+ video,
708
1017
  videoRef,
709
1018
  shouldRenderVideo,
710
1019
  timelineExpanded,
@@ -712,6 +1021,12 @@ var VideoFeedItemTimeline = react.forwardRef(
712
1021
  handleSeekStart,
713
1022
  handleSeekEnd
714
1023
  } = useVideoFeedItemContext();
1024
+ const defaultGetThumbnailUrl = react.useCallback(
1025
+ (_time) => {
1026
+ return video.thumbnail || void 0;
1027
+ },
1028
+ [video.thumbnail]
1029
+ );
715
1030
  if (!shouldRenderVideo) {
716
1031
  return null;
717
1032
  }
@@ -722,7 +1037,11 @@ var VideoFeedItemTimeline = react.forwardRef(
722
1037
  expanded: expandedProp ?? timelineExpanded,
723
1038
  onSeekStart: handleSeekStart,
724
1039
  onSeekEnd: handleSeekEnd,
725
- onExpandedChange: setTimelineExpanded
1040
+ onExpandedChange: setTimelineExpanded,
1041
+ showPreview,
1042
+ getThumbnailUrl: getThumbnailUrl ?? (showPreview ? defaultGetThumbnailUrl : void 0),
1043
+ previewWidth,
1044
+ previewHeight
726
1045
  }
727
1046
  ) });
728
1047
  }
@@ -1555,68 +1874,42 @@ async function prefetchVideoFeed(queryClient, config, params = {}) {
1555
1874
  initialPageParam: void 0
1556
1875
  });
1557
1876
  }
1558
- var stateStyles = {
1559
- container: {
1560
- position: "fixed",
1561
- inset: 0,
1562
- display: "flex",
1563
- flexDirection: "column",
1564
- alignItems: "center",
1565
- justifyContent: "center",
1566
- backgroundColor: core.colors.background,
1567
- color: core.colors.text,
1568
- gap: core.spacing[4]
1569
- },
1570
- spinner: {
1571
- width: 40,
1572
- height: 40,
1573
- borderWidth: 3,
1574
- borderStyle: "solid",
1575
- borderColor: "rgba(255, 255, 255, 0.2)",
1576
- borderTopColor: core.colors.accent,
1577
- borderRadius: core.radii.full,
1578
- animation: "xhub-reel-spin 1s linear infinite"
1579
- },
1580
- text: {
1581
- fontSize: core.fontSizes.md,
1582
- color: core.colors.textSecondary,
1583
- textAlign: "center",
1584
- maxWidth: 280,
1585
- lineHeight: 1.5
1586
- },
1587
- button: {
1588
- padding: `${core.spacing[3]}px ${core.spacing[6]}px`,
1589
- backgroundColor: core.colors.accent,
1590
- color: core.colors.text,
1591
- border: "none",
1592
- borderRadius: core.radii.md,
1593
- fontSize: core.fontSizes.sm,
1594
- fontWeight: core.fontWeights.semibold,
1595
- cursor: "pointer"
1596
- },
1597
- errorIcon: {
1598
- fontSize: 48,
1599
- marginBottom: core.spacing[2]
1600
- }
1601
- };
1602
- var DefaultLoading = () => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: stateStyles.container, children: [
1603
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1604
- @keyframes xhub-reel-spin {
1605
- to { transform: rotate(360deg); }
1877
+ var ConnectedVideoFeedInner = react.forwardRef(
1878
+ ({
1879
+ videos,
1880
+ isLoading,
1881
+ hasMore,
1882
+ onLoadMore,
1883
+ onVideoChange,
1884
+ initialIndex = 0,
1885
+ usePooling,
1886
+ ...feedProps
1887
+ }, ref) => {
1888
+ const [currentIndex, setCurrentIndex] = react.useState(initialIndex);
1889
+ usePoolOrchestration(usePooling ? currentIndex : -1, usePooling ? videos : []);
1890
+ const handleVideoChange = react.useCallback(
1891
+ (video, index) => {
1892
+ setCurrentIndex(index);
1893
+ onVideoChange?.(video, index);
1894
+ },
1895
+ [onVideoChange]
1896
+ );
1897
+ return /* @__PURE__ */ jsxRuntime.jsx(
1898
+ VideoFeed,
1899
+ {
1900
+ ref,
1901
+ videos,
1902
+ isLoading,
1903
+ hasMore,
1904
+ onLoadMore,
1905
+ onVideoChange: handleVideoChange,
1906
+ initialIndex,
1907
+ ...feedProps
1606
1908
  }
1607
- ` }),
1608
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: stateStyles.spinner }),
1609
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: stateStyles.text, children: "\u0110ang t\u1EA3i video..." })
1610
- ] });
1611
- var DefaultError = ({ error, retry }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: stateStyles.container, children: [
1612
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: stateStyles.errorIcon, children: "\u{1F615}" }),
1613
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: stateStyles.text, children: error.message || "C\xF3 l\u1ED7i x\u1EA3y ra khi t\u1EA3i video" }),
1614
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: stateStyles.button, onClick: retry, children: "Th\u1EED l\u1EA1i" })
1615
- ] });
1616
- var DefaultEmpty = () => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: stateStyles.container, children: [
1617
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: stateStyles.errorIcon, children: "\u{1F4ED}" }),
1618
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: stateStyles.text, children: "Kh\xF4ng c\xF3 video n\xE0o \u0111\u1EC3 hi\u1EC3n th\u1ECB" })
1619
- ] });
1909
+ );
1910
+ }
1911
+ );
1912
+ ConnectedVideoFeedInner.displayName = "ConnectedVideoFeedInner";
1620
1913
  var ConnectedVideoFeed = react.forwardRef(
1621
1914
  ({
1622
1915
  config: configProp,
@@ -1626,11 +1919,14 @@ var ConnectedVideoFeed = react.forwardRef(
1626
1919
  pageSize = 10,
1627
1920
  initialVideos,
1628
1921
  initialMuted = true,
1922
+ pooling = true,
1923
+ poolConfig,
1924
+ forceHLSJS,
1629
1925
  onFetchSuccess,
1630
1926
  onFetchError,
1631
- renderLoading = () => /* @__PURE__ */ jsxRuntime.jsx(DefaultLoading, {}),
1632
- renderError = (error, retry) => /* @__PURE__ */ jsxRuntime.jsx(DefaultError, { error, retry }),
1633
- renderEmpty = () => /* @__PURE__ */ jsxRuntime.jsx(DefaultEmpty, {}),
1927
+ renderLoading,
1928
+ renderError,
1929
+ renderEmpty,
1634
1930
  // Pass through VideoFeed props
1635
1931
  onVideoChange,
1636
1932
  onLike,
@@ -1666,22 +1962,19 @@ var ConnectedVideoFeed = react.forwardRef(
1666
1962
  refetch();
1667
1963
  }, [refetch]);
1668
1964
  if (!config) {
1669
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: stateStyles.container, children: [
1670
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: stateStyles.errorIcon, children: "\u26A0\uFE0F" }),
1671
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: stateStyles.text, children: "Ch\u01B0a c\u1EA5u h\xECnh API. Vui l\xF2ng wrap component trong XHubReelProvider v\u1EDBi config ho\u1EB7c truy\u1EC1n config prop." })
1672
- ] });
1965
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.FeedNoConfigState, {});
1673
1966
  }
1674
1967
  if (isLoading && videos.length === 0) {
1675
- return renderLoading();
1968
+ return renderLoading ? renderLoading() : /* @__PURE__ */ jsxRuntime.jsx(ui.FeedLoadingState, {});
1676
1969
  }
1677
1970
  if (error && videos.length === 0) {
1678
- return renderError(error, handleRetry);
1971
+ return renderError ? renderError(error, handleRetry) : /* @__PURE__ */ jsxRuntime.jsx(ui.FeedErrorState, { error, onRetry: handleRetry });
1679
1972
  }
1680
1973
  if (!isLoading && videos.length === 0) {
1681
- return renderEmpty();
1974
+ return renderEmpty ? renderEmpty() : /* @__PURE__ */ jsxRuntime.jsx(ui.FeedEmptyState, {});
1682
1975
  }
1683
- return /* @__PURE__ */ jsxRuntime.jsx(
1684
- VideoFeed,
1976
+ const feedContent = /* @__PURE__ */ jsxRuntime.jsx(
1977
+ ConnectedVideoFeedInner,
1685
1978
  {
1686
1979
  ref,
1687
1980
  videos,
@@ -1694,96 +1987,103 @@ var ConnectedVideoFeed = react.forwardRef(
1694
1987
  onShare,
1695
1988
  onAuthorClick,
1696
1989
  initialMuted,
1990
+ usePooling: pooling,
1697
1991
  ...videoFeedProps
1698
1992
  }
1699
1993
  );
1994
+ if (pooling) {
1995
+ return /* @__PURE__ */ jsxRuntime.jsx(
1996
+ VideoEnginePoolProvider,
1997
+ {
1998
+ config: poolConfig,
1999
+ forceHLSJS,
2000
+ onFirstFrameReady: (slot) => {
2001
+ console.log("[ConnectedVideoFeed] First frame ready:", slot.videoId);
2002
+ },
2003
+ onError: (slot, err) => {
2004
+ console.warn("[ConnectedVideoFeed] Slot error:", slot.videoId, err);
2005
+ },
2006
+ children: feedContent
2007
+ }
2008
+ );
2009
+ }
2010
+ return feedContent;
1700
2011
  }
1701
2012
  );
1702
2013
  ConnectedVideoFeed.displayName = "ConnectedVideoFeed";
1703
- function useFeedScroll({
1704
- scrollRef,
1705
- itemCount,
1706
- itemHeight,
1707
- onScrollChange,
1708
- onIndexChange
1709
- }) {
1710
- const [currentIndex, setCurrentIndex] = react.useState(0);
1711
- const [scrollVelocity, setScrollVelocity] = react.useState(0);
1712
- const [isScrolling, setIsScrolling] = react.useState(false);
1713
- const lastScrollTopRef = react.useRef(0);
1714
- const lastScrollTimeRef = react.useRef(Date.now());
1715
- const scrollTimeoutRef = react.useRef(null);
1716
- const getItemHeight = react.useCallback(() => {
1717
- if (itemHeight) return itemHeight;
1718
- if (typeof window !== "undefined") return window.innerHeight;
1719
- return 800;
1720
- }, [itemHeight]);
1721
- const scrollToIndex = react.useCallback(
1722
- (index, smooth = true) => {
1723
- const element = scrollRef.current;
1724
- if (!element) return;
1725
- const clampedIndex = Math.max(0, Math.min(index, itemCount - 1));
1726
- const top = clampedIndex * getItemHeight();
1727
- element.scrollTo({
1728
- top,
1729
- behavior: smooth ? "smooth" : "auto"
1730
- });
1731
- },
1732
- [scrollRef, itemCount, getItemHeight]
1733
- );
1734
- const scrollToNext = react.useCallback(() => {
1735
- scrollToIndex(currentIndex + 1);
1736
- }, [currentIndex, scrollToIndex]);
1737
- const scrollToPrev = react.useCallback(() => {
1738
- scrollToIndex(currentIndex - 1);
1739
- }, [currentIndex, scrollToIndex]);
1740
- react.useEffect(() => {
1741
- const element = scrollRef.current;
1742
- if (!element) return;
1743
- const handleScroll = () => {
1744
- const now = Date.now();
1745
- const scrollTop = element.scrollTop;
1746
- const timeDelta = now - lastScrollTimeRef.current;
1747
- const scrollDelta = Math.abs(scrollTop - lastScrollTopRef.current);
1748
- if (timeDelta > 0) {
1749
- const velocity = scrollDelta / timeDelta * 1e3;
1750
- setScrollVelocity(velocity);
1751
- onScrollChange?.(scrollTop, velocity);
1752
- }
1753
- const height = getItemHeight();
1754
- const newIndex = Math.round(scrollTop / height);
1755
- if (newIndex !== currentIndex && newIndex >= 0 && newIndex < itemCount) {
1756
- setCurrentIndex(newIndex);
1757
- onIndexChange?.(newIndex);
1758
- }
1759
- setIsScrolling(true);
1760
- if (scrollTimeoutRef.current) {
1761
- clearTimeout(scrollTimeoutRef.current);
2014
+ var PooledVideoFeedInner = react.forwardRef(
2015
+ ({
2016
+ videos,
2017
+ showTimeline,
2018
+ renderVideoItem,
2019
+ onVideoChangeInternal,
2020
+ onVideoChange,
2021
+ ...feedProps
2022
+ }, ref) => {
2023
+ const [currentIndex, setCurrentIndex] = react.useState(feedProps.initialIndex ?? 0);
2024
+ usePoolOrchestration(currentIndex, videos);
2025
+ const handleVideoChange = react.useCallback(
2026
+ (video, index) => {
2027
+ setCurrentIndex(index);
2028
+ onVideoChangeInternal(video, index);
2029
+ onVideoChange?.(video, index);
2030
+ },
2031
+ [onVideoChangeInternal, onVideoChange]
2032
+ );
2033
+ return /* @__PURE__ */ jsxRuntime.jsx(
2034
+ VideoFeed,
2035
+ {
2036
+ ref,
2037
+ videos,
2038
+ ...feedProps,
2039
+ onVideoChange: handleVideoChange
1762
2040
  }
1763
- scrollTimeoutRef.current = setTimeout(() => {
1764
- setIsScrolling(false);
1765
- setScrollVelocity(0);
1766
- }, 150);
1767
- lastScrollTopRef.current = scrollTop;
1768
- lastScrollTimeRef.current = now;
1769
- };
1770
- element.addEventListener("scroll", handleScroll, { passive: true });
1771
- return () => {
1772
- element.removeEventListener("scroll", handleScroll);
1773
- if (scrollTimeoutRef.current) {
1774
- clearTimeout(scrollTimeoutRef.current);
2041
+ );
2042
+ }
2043
+ );
2044
+ PooledVideoFeedInner.displayName = "PooledVideoFeedInner";
2045
+ var PooledVideoFeed = react.forwardRef(
2046
+ ({
2047
+ poolConfig,
2048
+ forceHLSJS,
2049
+ showTimeline = true,
2050
+ renderVideoItem,
2051
+ onVideoChange,
2052
+ ...feedProps
2053
+ }, ref) => {
2054
+ const handleVideoChangeInternal = react.useCallback(
2055
+ (video, index) => {
2056
+ console.log("[PooledVideoFeed] Video changed", { videoId: video.id, index });
2057
+ },
2058
+ []
2059
+ );
2060
+ return /* @__PURE__ */ jsxRuntime.jsx(
2061
+ VideoEnginePoolProvider,
2062
+ {
2063
+ config: poolConfig,
2064
+ forceHLSJS,
2065
+ onFirstFrameReady: (slot) => {
2066
+ console.log("[PooledVideoFeed] First frame ready", slot.videoId);
2067
+ },
2068
+ onError: (slot, error) => {
2069
+ console.warn("[PooledVideoFeed] Slot error", slot.videoId, error);
2070
+ },
2071
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2072
+ PooledVideoFeedInner,
2073
+ {
2074
+ ref,
2075
+ showTimeline,
2076
+ renderVideoItem,
2077
+ onVideoChange,
2078
+ onVideoChangeInternal: handleVideoChangeInternal,
2079
+ ...feedProps
2080
+ }
2081
+ )
1775
2082
  }
1776
- };
1777
- }, [scrollRef, itemCount, currentIndex, getItemHeight, onScrollChange, onIndexChange]);
1778
- return {
1779
- currentIndex,
1780
- scrollVelocity,
1781
- isScrolling,
1782
- scrollToIndex,
1783
- scrollToNext,
1784
- scrollToPrev
1785
- };
1786
- }
2083
+ );
2084
+ }
2085
+ );
2086
+ PooledVideoFeed.displayName = "PooledVideoFeed";
1787
2087
  function useInfiniteScroll({
1788
2088
  onLoadMore,
1789
2089
  isLoading = false,
@@ -1850,6 +2150,8 @@ Object.defineProperty(exports, "usePreload", {
1850
2150
  get: function () { return player.usePreload; }
1851
2151
  });
1852
2152
  exports.ConnectedVideoFeed = ConnectedVideoFeed;
2153
+ exports.PooledVideoFeed = PooledVideoFeed;
2154
+ exports.VideoEnginePoolProvider = VideoEnginePoolProvider;
1853
2155
  exports.VideoFeed = VideoFeed;
1854
2156
  exports.VideoFeedItem = VideoFeedItem;
1855
2157
  exports.VideoFeedItemActions = VideoFeedItemActions;
@@ -1862,12 +2164,17 @@ exports.getPreloadPriorityForFeed = getPreloadPriorityForFeed;
1862
2164
  exports.mapPriorityToNumeric = mapPriorityToNumeric;
1863
2165
  exports.memoryManager = memoryManager;
1864
2166
  exports.prefetchVideoFeed = prefetchVideoFeed;
1865
- exports.useFeedScroll = useFeedScroll;
1866
2167
  exports.useGlobalMemoryState = useGlobalMemoryState;
1867
2168
  exports.useInfiniteScroll = useInfiniteScroll;
1868
2169
  exports.useMemoryManager = useMemoryManager;
2170
+ exports.usePoolMemoryControl = usePoolMemoryControl;
2171
+ exports.usePoolOrchestration = usePoolOrchestration;
2172
+ exports.usePoolStats = usePoolStats;
2173
+ exports.usePooledVideo = usePooledVideo;
1869
2174
  exports.useSwipeAnimation = useSwipeAnimation;
1870
2175
  exports.useVideoActivation = useVideoActivation;
2176
+ exports.useVideoEnginePool = useVideoEnginePool;
2177
+ exports.useVideoEnginePoolOptional = useVideoEnginePoolOptional;
1871
2178
  exports.useVideoFeed = useVideoFeed;
1872
2179
  exports.useVideoFeedItemContext = useVideoFeedItemContext;
1873
2180
  exports.useVideoVisibility = useVideoVisibility;