@xhub-reels/sdk 0.1.8 → 0.1.9

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 CHANGED
@@ -1628,7 +1628,8 @@ function VideoSlot({
1628
1628
  onToggleMute,
1629
1629
  showFps = false,
1630
1630
  renderOverlay,
1631
- renderActions
1631
+ renderActions,
1632
+ renderPauseIndicator
1632
1633
  }) {
1633
1634
  const { optimisticManager, adapters } = useSDK();
1634
1635
  if (!isVideoItem(item)) {
@@ -1664,6 +1665,7 @@ function VideoSlot({
1664
1665
  showFps,
1665
1666
  renderOverlay,
1666
1667
  renderActions,
1668
+ renderPauseIndicator,
1667
1669
  optimisticManager,
1668
1670
  adapters
1669
1671
  }
@@ -1681,6 +1683,7 @@ function VideoSlotInner({
1681
1683
  showFps,
1682
1684
  renderOverlay,
1683
1685
  renderActions,
1686
+ renderPauseIndicator,
1684
1687
  optimisticManager,
1685
1688
  adapters
1686
1689
  }) {
@@ -1797,19 +1800,22 @@ function VideoSlotInner({
1797
1800
  video.muted = isMuted;
1798
1801
  }, [isMuted]);
1799
1802
  const showPosterOverlay = !isReady && !hasPlayedAhead;
1800
- const [showMuteIndicator, setShowMuteIndicator] = react.useState(false);
1801
- const muteIndicatorTimer = react.useRef(null);
1803
+ const [isPaused, setIsPaused] = react.useState(false);
1802
1804
  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]);
1805
+ const video = videoRef.current;
1806
+ if (!video || !isActive) return;
1807
+ if (video.paused) {
1808
+ video.play().catch(() => {
1809
+ });
1810
+ setIsPaused(false);
1811
+ } else {
1812
+ video.pause();
1813
+ setIsPaused(true);
1814
+ }
1815
+ }, [isActive]);
1808
1816
  react.useEffect(() => {
1809
- return () => {
1810
- if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
1811
- };
1812
- }, []);
1817
+ if (isActive) setIsPaused(false);
1818
+ }, [isActive]);
1813
1819
  const likeDelta = react.useSyncExternalStore(
1814
1820
  optimisticManager.store.subscribe,
1815
1821
  () => optimisticManager.getLikeDelta(item.id),
@@ -1828,9 +1834,11 @@ function VideoSlotInner({
1828
1834
  share: () => adapters.interaction?.share?.(item.id),
1829
1835
  isMuted,
1830
1836
  toggleMute: onToggleMute,
1837
+ isPaused,
1838
+ togglePause: handleTap,
1831
1839
  isActive,
1832
1840
  index
1833
- }), [item, likeDelta, followState, isMuted, isActive, index, optimisticManager, adapters, onToggleMute]);
1841
+ }), [item, likeDelta, followState, isMuted, isPaused, isActive, index, optimisticManager, adapters, onToggleMute, handleTap]);
1834
1842
  return /* @__PURE__ */ jsxRuntime.jsxs(
1835
1843
  "div",
1836
1844
  {
@@ -1877,7 +1885,7 @@ function VideoSlotInner({
1877
1885
  }
1878
1886
  }
1879
1887
  ),
1880
- showMuteIndicator && /* @__PURE__ */ jsxRuntime.jsx(
1888
+ isPaused && /* @__PURE__ */ jsxRuntime.jsx(
1881
1889
  "div",
1882
1890
  {
1883
1891
  style: {
@@ -1885,18 +1893,26 @@ function VideoSlotInner({
1885
1893
  top: "50%",
1886
1894
  left: "50%",
1887
1895
  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
1896
  pointerEvents: "none",
1897
- animation: "fadeInOut 1.2s ease forwards"
1897
+ zIndex: 5,
1898
+ opacity: 0.3
1898
1899
  },
1899
- children: isMuted ? "\u{1F507}" : "\u{1F50A}"
1900
+ children: renderPauseIndicator ? renderPauseIndicator(isPaused) : /* @__PURE__ */ jsxRuntime.jsx(
1901
+ "div",
1902
+ {
1903
+ style: {
1904
+ background: "rgba(0,0,0,0.6)",
1905
+ borderRadius: "50%",
1906
+ width: 64,
1907
+ height: 64,
1908
+ display: "flex",
1909
+ alignItems: "center",
1910
+ justifyContent: "center",
1911
+ fontSize: 28
1912
+ },
1913
+ children: "\u25B6\uFE0F"
1914
+ }
1915
+ )
1900
1916
  }
1901
1917
  ),
1902
1918
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1909,7 +1925,8 @@ function VideoSlotInner({
1909
1925
  right: 80,
1910
1926
  paddingBottom: 16,
1911
1927
  pointerEvents: "none",
1912
- color: "#fff"
1928
+ color: "#fff",
1929
+ zIndex: 10
1913
1930
  },
1914
1931
  children: renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultOverlay, { item })
1915
1932
  }
@@ -1917,6 +1934,7 @@ function VideoSlotInner({
1917
1934
  /* @__PURE__ */ jsxRuntime.jsx(
1918
1935
  "div",
1919
1936
  {
1937
+ onClick: (e) => e.stopPropagation(),
1920
1938
  style: {
1921
1939
  position: "absolute",
1922
1940
  bottom: 0,
@@ -1925,7 +1943,9 @@ function VideoSlotInner({
1925
1943
  display: "flex",
1926
1944
  flexDirection: "column",
1927
1945
  gap: 20,
1928
- alignItems: "center"
1946
+ alignItems: "center",
1947
+ pointerEvents: "auto",
1948
+ zIndex: 10
1929
1949
  },
1930
1950
  children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultActions, { item, actions })
1931
1951
  }
@@ -1987,6 +2007,7 @@ var centerStyle = {
1987
2007
  function ReelsFeed({
1988
2008
  renderOverlay,
1989
2009
  renderActions,
2010
+ renderPauseIndicator,
1990
2011
  renderLoading,
1991
2012
  renderEmpty,
1992
2013
  renderError: _renderError,
@@ -2006,7 +2027,7 @@ function ReelsFeed({
2006
2027
  isWarmAllocated,
2007
2028
  setPrefetchIndex
2008
2029
  } = useResource();
2009
- const [isMuted, setIsMuted] = react.useState(true);
2030
+ const [isMuted, setIsMuted] = react.useState(false);
2010
2031
  const containerRef = react.useRef(null);
2011
2032
  const slotCacheRef = react.useRef(/* @__PURE__ */ new Map());
2012
2033
  const activeIndexRef = react.useRef(0);
@@ -2220,7 +2241,8 @@ function ReelsFeed({
2220
2241
  onToggleMute: handleToggleMute,
2221
2242
  showFps: showFps && isActive,
2222
2243
  renderOverlay,
2223
- renderActions
2244
+ renderActions,
2245
+ renderPauseIndicator
2224
2246
  }
2225
2247
  )
2226
2248
  },
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)
@@ -783,8 +794,9 @@ interface VideoSlotProps {
783
794
  showFps?: boolean;
784
795
  renderOverlay?: (item: ContentItem, actions: SlotActions) => ReactNode;
785
796
  renderActions?: (item: ContentItem, actions: SlotActions) => ReactNode;
797
+ renderPauseIndicator?: (isPaused: boolean) => ReactNode;
786
798
  }
787
- declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
799
+ declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, renderPauseIndicator, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
788
800
 
789
801
  declare function DefaultOverlay({ item }: {
790
802
  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)
@@ -783,8 +794,9 @@ interface VideoSlotProps {
783
794
  showFps?: boolean;
784
795
  renderOverlay?: (item: ContentItem, actions: SlotActions) => ReactNode;
785
796
  renderActions?: (item: ContentItem, actions: SlotActions) => ReactNode;
797
+ renderPauseIndicator?: (isPaused: boolean) => ReactNode;
786
798
  }
787
- declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
799
+ declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, renderPauseIndicator, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
788
800
 
789
801
  declare function DefaultOverlay({ item }: {
790
802
  item: ContentItem;
package/dist/index.js CHANGED
@@ -1622,7 +1622,8 @@ function VideoSlot({
1622
1622
  onToggleMute,
1623
1623
  showFps = false,
1624
1624
  renderOverlay,
1625
- renderActions
1625
+ renderActions,
1626
+ renderPauseIndicator
1626
1627
  }) {
1627
1628
  const { optimisticManager, adapters } = useSDK();
1628
1629
  if (!isVideoItem(item)) {
@@ -1658,6 +1659,7 @@ function VideoSlot({
1658
1659
  showFps,
1659
1660
  renderOverlay,
1660
1661
  renderActions,
1662
+ renderPauseIndicator,
1661
1663
  optimisticManager,
1662
1664
  adapters
1663
1665
  }
@@ -1675,6 +1677,7 @@ function VideoSlotInner({
1675
1677
  showFps,
1676
1678
  renderOverlay,
1677
1679
  renderActions,
1680
+ renderPauseIndicator,
1678
1681
  optimisticManager,
1679
1682
  adapters
1680
1683
  }) {
@@ -1791,19 +1794,22 @@ function VideoSlotInner({
1791
1794
  video.muted = isMuted;
1792
1795
  }, [isMuted]);
1793
1796
  const showPosterOverlay = !isReady && !hasPlayedAhead;
1794
- const [showMuteIndicator, setShowMuteIndicator] = useState(false);
1795
- const muteIndicatorTimer = useRef(null);
1797
+ const [isPaused, setIsPaused] = useState(false);
1796
1798
  const handleTap = useCallback(() => {
1797
- onToggleMute();
1798
- setShowMuteIndicator(true);
1799
- if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
1800
- muteIndicatorTimer.current = setTimeout(() => setShowMuteIndicator(false), 1200);
1801
- }, [onToggleMute]);
1799
+ const video = videoRef.current;
1800
+ if (!video || !isActive) return;
1801
+ if (video.paused) {
1802
+ video.play().catch(() => {
1803
+ });
1804
+ setIsPaused(false);
1805
+ } else {
1806
+ video.pause();
1807
+ setIsPaused(true);
1808
+ }
1809
+ }, [isActive]);
1802
1810
  useEffect(() => {
1803
- return () => {
1804
- if (muteIndicatorTimer.current) clearTimeout(muteIndicatorTimer.current);
1805
- };
1806
- }, []);
1811
+ if (isActive) setIsPaused(false);
1812
+ }, [isActive]);
1807
1813
  const likeDelta = useSyncExternalStore(
1808
1814
  optimisticManager.store.subscribe,
1809
1815
  () => optimisticManager.getLikeDelta(item.id),
@@ -1822,9 +1828,11 @@ function VideoSlotInner({
1822
1828
  share: () => adapters.interaction?.share?.(item.id),
1823
1829
  isMuted,
1824
1830
  toggleMute: onToggleMute,
1831
+ isPaused,
1832
+ togglePause: handleTap,
1825
1833
  isActive,
1826
1834
  index
1827
- }), [item, likeDelta, followState, isMuted, isActive, index, optimisticManager, adapters, onToggleMute]);
1835
+ }), [item, likeDelta, followState, isMuted, isPaused, isActive, index, optimisticManager, adapters, onToggleMute, handleTap]);
1828
1836
  return /* @__PURE__ */ jsxs(
1829
1837
  "div",
1830
1838
  {
@@ -1871,7 +1879,7 @@ function VideoSlotInner({
1871
1879
  }
1872
1880
  }
1873
1881
  ),
1874
- showMuteIndicator && /* @__PURE__ */ jsx(
1882
+ isPaused && /* @__PURE__ */ jsx(
1875
1883
  "div",
1876
1884
  {
1877
1885
  style: {
@@ -1879,18 +1887,26 @@ function VideoSlotInner({
1879
1887
  top: "50%",
1880
1888
  left: "50%",
1881
1889
  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
1890
  pointerEvents: "none",
1891
- animation: "fadeInOut 1.2s ease forwards"
1891
+ zIndex: 5,
1892
+ opacity: 0.3
1892
1893
  },
1893
- children: isMuted ? "\u{1F507}" : "\u{1F50A}"
1894
+ children: renderPauseIndicator ? renderPauseIndicator(isPaused) : /* @__PURE__ */ jsx(
1895
+ "div",
1896
+ {
1897
+ style: {
1898
+ background: "rgba(0,0,0,0.6)",
1899
+ borderRadius: "50%",
1900
+ width: 64,
1901
+ height: 64,
1902
+ display: "flex",
1903
+ alignItems: "center",
1904
+ justifyContent: "center",
1905
+ fontSize: 28
1906
+ },
1907
+ children: "\u25B6\uFE0F"
1908
+ }
1909
+ )
1894
1910
  }
1895
1911
  ),
1896
1912
  /* @__PURE__ */ jsx(
@@ -1903,7 +1919,8 @@ function VideoSlotInner({
1903
1919
  right: 80,
1904
1920
  paddingBottom: 16,
1905
1921
  pointerEvents: "none",
1906
- color: "#fff"
1922
+ color: "#fff",
1923
+ zIndex: 10
1907
1924
  },
1908
1925
  children: renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsx(DefaultOverlay, { item })
1909
1926
  }
@@ -1911,6 +1928,7 @@ function VideoSlotInner({
1911
1928
  /* @__PURE__ */ jsx(
1912
1929
  "div",
1913
1930
  {
1931
+ onClick: (e) => e.stopPropagation(),
1914
1932
  style: {
1915
1933
  position: "absolute",
1916
1934
  bottom: 0,
@@ -1919,7 +1937,9 @@ function VideoSlotInner({
1919
1937
  display: "flex",
1920
1938
  flexDirection: "column",
1921
1939
  gap: 20,
1922
- alignItems: "center"
1940
+ alignItems: "center",
1941
+ pointerEvents: "auto",
1942
+ zIndex: 10
1923
1943
  },
1924
1944
  children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsx(DefaultActions, { item, actions })
1925
1945
  }
@@ -1981,6 +2001,7 @@ var centerStyle = {
1981
2001
  function ReelsFeed({
1982
2002
  renderOverlay,
1983
2003
  renderActions,
2004
+ renderPauseIndicator,
1984
2005
  renderLoading,
1985
2006
  renderEmpty,
1986
2007
  renderError: _renderError,
@@ -2000,7 +2021,7 @@ function ReelsFeed({
2000
2021
  isWarmAllocated,
2001
2022
  setPrefetchIndex
2002
2023
  } = useResource();
2003
- const [isMuted, setIsMuted] = useState(true);
2024
+ const [isMuted, setIsMuted] = useState(false);
2004
2025
  const containerRef = useRef(null);
2005
2026
  const slotCacheRef = useRef(/* @__PURE__ */ new Map());
2006
2027
  const activeIndexRef = useRef(0);
@@ -2214,7 +2235,8 @@ function ReelsFeed({
2214
2235
  onToggleMute: handleToggleMute,
2215
2236
  showFps: showFps && isActive,
2216
2237
  renderOverlay,
2217
- renderActions
2238
+ renderActions,
2239
+ renderPauseIndicator
2218
2240
  }
2219
2241
  )
2220
2242
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xhub-reels/sdk",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "High-performance Short Video / Reels SDK for React — optimized for Flutter WebView",
5
5
  "license": "MIT",
6
6
  "type": "module",