@xhub-reels/sdk 0.1.13 → 0.1.15
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 +120 -16
- package/dist/index.d.cts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +120 -17
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1420,7 +1420,7 @@ function useHls(options) {
|
|
|
1420
1420
|
}
|
|
1421
1421
|
if (isNative) {
|
|
1422
1422
|
if (currentSrcRef.current === src) {
|
|
1423
|
-
if (video.readyState >= HTMLMediaElement.
|
|
1423
|
+
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1424
1424
|
setIsReady(true);
|
|
1425
1425
|
return void 0;
|
|
1426
1426
|
}
|
|
@@ -1428,7 +1428,7 @@ function useHls(options) {
|
|
|
1428
1428
|
video.addEventListener("canplay", handleCanPlayReuse, { once: true });
|
|
1429
1429
|
video.addEventListener("loadeddata", handleCanPlayReuse, { once: true });
|
|
1430
1430
|
video.addEventListener("playing", handleCanPlayReuse, { once: true });
|
|
1431
|
-
if (
|
|
1431
|
+
if (video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1432
1432
|
video.load();
|
|
1433
1433
|
}
|
|
1434
1434
|
return () => {
|
|
@@ -1439,7 +1439,10 @@ function useHls(options) {
|
|
|
1439
1439
|
}
|
|
1440
1440
|
video.src = src;
|
|
1441
1441
|
currentSrcRef.current = src;
|
|
1442
|
-
if (
|
|
1442
|
+
if (!isActive) {
|
|
1443
|
+
video.load();
|
|
1444
|
+
}
|
|
1445
|
+
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1443
1446
|
setIsReady(true);
|
|
1444
1447
|
return void 0;
|
|
1445
1448
|
}
|
|
@@ -1656,6 +1659,43 @@ function skeletonCircle(size) {
|
|
|
1656
1659
|
background: "rgba(255,255,255,0.1)"
|
|
1657
1660
|
};
|
|
1658
1661
|
}
|
|
1662
|
+
function DefaultDoubleTap({ isDoubleTap }) {
|
|
1663
|
+
if (!isDoubleTap) return null;
|
|
1664
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1665
|
+
"div",
|
|
1666
|
+
{
|
|
1667
|
+
style: {
|
|
1668
|
+
position: "absolute",
|
|
1669
|
+
inset: 0,
|
|
1670
|
+
display: "flex",
|
|
1671
|
+
alignItems: "center",
|
|
1672
|
+
justifyContent: "center",
|
|
1673
|
+
pointerEvents: "none",
|
|
1674
|
+
zIndex: 20
|
|
1675
|
+
},
|
|
1676
|
+
children: [
|
|
1677
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1678
|
+
"div",
|
|
1679
|
+
{
|
|
1680
|
+
style: {
|
|
1681
|
+
fontSize: 80,
|
|
1682
|
+
animation: "reels-sdk-doubleTapHeart 0.6s ease forwards"
|
|
1683
|
+
},
|
|
1684
|
+
children: "\u2764\uFE0F"
|
|
1685
|
+
}
|
|
1686
|
+
),
|
|
1687
|
+
/* @__PURE__ */ jsxRuntime.jsx("style", { children: `
|
|
1688
|
+
@keyframes reels-sdk-doubleTapHeart {
|
|
1689
|
+
0% { opacity: 0; transform: scale(0.5); }
|
|
1690
|
+
30% { opacity: 1; transform: scale(1.2); }
|
|
1691
|
+
60% { opacity: 1; transform: scale(1.0); }
|
|
1692
|
+
100% { opacity: 0; transform: scale(1.1); }
|
|
1693
|
+
}
|
|
1694
|
+
` })
|
|
1695
|
+
]
|
|
1696
|
+
}
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1659
1699
|
function VideoSlot({
|
|
1660
1700
|
item,
|
|
1661
1701
|
index,
|
|
@@ -1668,7 +1708,8 @@ function VideoSlot({
|
|
|
1668
1708
|
showFps = false,
|
|
1669
1709
|
renderOverlay,
|
|
1670
1710
|
renderActions,
|
|
1671
|
-
renderPauseIndicator
|
|
1711
|
+
renderPauseIndicator,
|
|
1712
|
+
renderDoubleTap
|
|
1672
1713
|
}) {
|
|
1673
1714
|
const { optimisticManager, adapters } = useSDK();
|
|
1674
1715
|
if (!isVideoItem(item)) {
|
|
@@ -1705,6 +1746,7 @@ function VideoSlot({
|
|
|
1705
1746
|
renderOverlay,
|
|
1706
1747
|
renderActions,
|
|
1707
1748
|
renderPauseIndicator,
|
|
1749
|
+
renderDoubleTap,
|
|
1708
1750
|
optimisticManager,
|
|
1709
1751
|
adapters
|
|
1710
1752
|
}
|
|
@@ -1723,6 +1765,7 @@ function VideoSlotInner({
|
|
|
1723
1765
|
renderOverlay,
|
|
1724
1766
|
renderActions,
|
|
1725
1767
|
renderPauseIndicator,
|
|
1768
|
+
renderDoubleTap,
|
|
1726
1769
|
optimisticManager,
|
|
1727
1770
|
adapters
|
|
1728
1771
|
}) {
|
|
@@ -1834,6 +1877,7 @@ function VideoSlotInner({
|
|
|
1834
1877
|
if (!video) return;
|
|
1835
1878
|
let onReady = null;
|
|
1836
1879
|
let fallbackTimerId = null;
|
|
1880
|
+
let pollId = null;
|
|
1837
1881
|
if (isActive) {
|
|
1838
1882
|
wasActiveRef.current = true;
|
|
1839
1883
|
const startPlay = () => {
|
|
@@ -1847,6 +1891,10 @@ function VideoSlotInner({
|
|
|
1847
1891
|
clearTimeout(fallbackTimerId);
|
|
1848
1892
|
fallbackTimerId = null;
|
|
1849
1893
|
}
|
|
1894
|
+
if (pollId !== null) {
|
|
1895
|
+
clearInterval(pollId);
|
|
1896
|
+
pollId = null;
|
|
1897
|
+
}
|
|
1850
1898
|
video.muted = true;
|
|
1851
1899
|
video.play().then(() => {
|
|
1852
1900
|
video.muted = isMuted;
|
|
@@ -1854,22 +1902,33 @@ function VideoSlotInner({
|
|
|
1854
1902
|
video.muted = isMuted;
|
|
1855
1903
|
});
|
|
1856
1904
|
};
|
|
1857
|
-
if (video.readyState >= HTMLMediaElement.
|
|
1905
|
+
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1858
1906
|
startPlay();
|
|
1859
1907
|
} else {
|
|
1860
1908
|
onReady = startPlay;
|
|
1861
1909
|
video.addEventListener("canplay", onReady, { once: true });
|
|
1862
1910
|
video.addEventListener("loadeddata", onReady, { once: true });
|
|
1863
1911
|
video.addEventListener("playing", onReady, { once: true });
|
|
1864
|
-
if (isNativeHls && video.src && video.readyState < HTMLMediaElement.
|
|
1912
|
+
if (isNativeHls && video.src && video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1865
1913
|
video.load();
|
|
1866
1914
|
}
|
|
1915
|
+
if (isNativeHls) {
|
|
1916
|
+
pollId = setInterval(() => {
|
|
1917
|
+
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && onReady) {
|
|
1918
|
+
if (pollId !== null) {
|
|
1919
|
+
clearInterval(pollId);
|
|
1920
|
+
pollId = null;
|
|
1921
|
+
}
|
|
1922
|
+
startPlay();
|
|
1923
|
+
}
|
|
1924
|
+
}, 100);
|
|
1925
|
+
}
|
|
1867
1926
|
fallbackTimerId = window.setTimeout(() => {
|
|
1868
1927
|
fallbackTimerId = null;
|
|
1869
1928
|
if (onReady) {
|
|
1870
1929
|
startPlay();
|
|
1871
1930
|
}
|
|
1872
|
-
}, 3e3);
|
|
1931
|
+
}, isNativeHls ? 800 : 3e3);
|
|
1873
1932
|
}
|
|
1874
1933
|
} else if (wasActiveRef.current) {
|
|
1875
1934
|
video.pause();
|
|
@@ -1889,6 +1948,10 @@ function VideoSlotInner({
|
|
|
1889
1948
|
clearTimeout(fallbackTimerId);
|
|
1890
1949
|
fallbackTimerId = null;
|
|
1891
1950
|
}
|
|
1951
|
+
if (pollId !== null) {
|
|
1952
|
+
clearInterval(pollId);
|
|
1953
|
+
pollId = null;
|
|
1954
|
+
}
|
|
1892
1955
|
};
|
|
1893
1956
|
}, [isActive, isMuted, hasPlayedAhead, isNativeHls]);
|
|
1894
1957
|
react.useEffect(() => {
|
|
@@ -1921,18 +1984,44 @@ function VideoSlotInner({
|
|
|
1921
1984
|
}, [isActive]);
|
|
1922
1985
|
const showPosterOverlay = isActive ? !isReady && !isActuallyPlaying : canPlayAhead ? !hasPlayedAhead : !isReady;
|
|
1923
1986
|
const [isPaused, setIsPaused] = react.useState(false);
|
|
1987
|
+
const [isDoubleTap, setIsDoubleTap] = react.useState(false);
|
|
1988
|
+
const lastTapTimeRef = react.useRef(0);
|
|
1989
|
+
const doubleTapTimerRef = react.useRef(null);
|
|
1924
1990
|
const handleTap = react.useCallback(() => {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1991
|
+
if (!isActive) return;
|
|
1992
|
+
const now = Date.now();
|
|
1993
|
+
const delta = now - lastTapTimeRef.current;
|
|
1994
|
+
lastTapTimeRef.current = now;
|
|
1995
|
+
if (delta < 300) {
|
|
1996
|
+
if (doubleTapTimerRef.current !== null) {
|
|
1997
|
+
clearTimeout(doubleTapTimerRef.current);
|
|
1998
|
+
doubleTapTimerRef.current = null;
|
|
1999
|
+
}
|
|
2000
|
+
setIsDoubleTap(true);
|
|
2001
|
+
setTimeout(() => setIsDoubleTap(false), 600);
|
|
1931
2002
|
} else {
|
|
1932
|
-
|
|
1933
|
-
|
|
2003
|
+
doubleTapTimerRef.current = setTimeout(() => {
|
|
2004
|
+
doubleTapTimerRef.current = null;
|
|
2005
|
+
const video = videoRef.current;
|
|
2006
|
+
if (!video) return;
|
|
2007
|
+
if (video.paused) {
|
|
2008
|
+
video.play().catch(() => {
|
|
2009
|
+
});
|
|
2010
|
+
setIsPaused(false);
|
|
2011
|
+
} else {
|
|
2012
|
+
video.pause();
|
|
2013
|
+
setIsPaused(true);
|
|
2014
|
+
}
|
|
2015
|
+
}, 300);
|
|
1934
2016
|
}
|
|
1935
2017
|
}, [isActive]);
|
|
2018
|
+
react.useEffect(() => {
|
|
2019
|
+
return () => {
|
|
2020
|
+
if (doubleTapTimerRef.current !== null) {
|
|
2021
|
+
clearTimeout(doubleTapTimerRef.current);
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
2024
|
+
}, []);
|
|
1936
2025
|
react.useEffect(() => {
|
|
1937
2026
|
if (isActive) setIsPaused(false);
|
|
1938
2027
|
}, [isActive]);
|
|
@@ -2006,6 +2095,18 @@ function VideoSlotInner({
|
|
|
2006
2095
|
}
|
|
2007
2096
|
}
|
|
2008
2097
|
),
|
|
2098
|
+
(isDoubleTap || renderDoubleTap) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2099
|
+
"div",
|
|
2100
|
+
{
|
|
2101
|
+
style: {
|
|
2102
|
+
position: "absolute",
|
|
2103
|
+
inset: 0,
|
|
2104
|
+
pointerEvents: "none",
|
|
2105
|
+
zIndex: 20
|
|
2106
|
+
},
|
|
2107
|
+
children: renderDoubleTap ? renderDoubleTap(isDoubleTap) : /* @__PURE__ */ jsxRuntime.jsx(DefaultDoubleTap, { isDoubleTap })
|
|
2108
|
+
}
|
|
2109
|
+
),
|
|
2009
2110
|
isPaused && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2010
2111
|
"div",
|
|
2011
2112
|
{
|
|
@@ -2129,6 +2230,7 @@ function ReelsFeed({
|
|
|
2129
2230
|
renderOverlay,
|
|
2130
2231
|
renderActions,
|
|
2131
2232
|
renderPauseIndicator,
|
|
2233
|
+
renderDoubleTap,
|
|
2132
2234
|
renderLoading,
|
|
2133
2235
|
renderEmpty,
|
|
2134
2236
|
renderError: _renderError,
|
|
@@ -2375,7 +2477,8 @@ function ReelsFeed({
|
|
|
2375
2477
|
showFps: showFps && isActive,
|
|
2376
2478
|
renderOverlay,
|
|
2377
2479
|
renderActions,
|
|
2378
|
-
renderPauseIndicator
|
|
2480
|
+
renderPauseIndicator,
|
|
2481
|
+
renderDoubleTap
|
|
2379
2482
|
}
|
|
2380
2483
|
) : null
|
|
2381
2484
|
},
|
|
@@ -3010,6 +3113,7 @@ exports.DEFAULT_FEED_CONFIG = DEFAULT_FEED_CONFIG;
|
|
|
3010
3113
|
exports.DEFAULT_PLAYER_CONFIG = DEFAULT_PLAYER_CONFIG;
|
|
3011
3114
|
exports.DEFAULT_RESOURCE_CONFIG = DEFAULT_RESOURCE_CONFIG;
|
|
3012
3115
|
exports.DefaultActions = DefaultActions;
|
|
3116
|
+
exports.DefaultDoubleTap = DefaultDoubleTap;
|
|
3013
3117
|
exports.DefaultOverlay = DefaultOverlay;
|
|
3014
3118
|
exports.DefaultSkeleton = DefaultSkeleton;
|
|
3015
3119
|
exports.FeedManager = FeedManager;
|
package/dist/index.d.cts
CHANGED
|
@@ -334,6 +334,13 @@ interface ReelsFeedProps {
|
|
|
334
334
|
* If not provided, SDK uses a default ▶️/⏸️ emoji indicator.
|
|
335
335
|
*/
|
|
336
336
|
renderPauseIndicator?: (isPaused: boolean) => ReactNode;
|
|
337
|
+
/**
|
|
338
|
+
* Custom double-tap overlay rendered when user double-taps a video slot.
|
|
339
|
+
* Receives isDoubleTap boolean (true for the duration of the animation window).
|
|
340
|
+
* Return ReactNode — positioned absolute center (full inset) by SDK.
|
|
341
|
+
* If not provided, SDK uses DefaultDoubleTap (animated ❤️ heart).
|
|
342
|
+
*/
|
|
343
|
+
renderDoubleTap?: (isDoubleTap: boolean) => ReactNode;
|
|
337
344
|
/**
|
|
338
345
|
* Custom loading skeleton shown during initial feed load.
|
|
339
346
|
* If not provided, SDK uses DefaultSkeleton with shimmer animation.
|
|
@@ -724,7 +731,7 @@ interface ReelsProviderProps {
|
|
|
724
731
|
declare function ReelsProvider({ children, adapters, debug }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
725
732
|
declare function useSDK(): SDKContextValue;
|
|
726
733
|
|
|
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;
|
|
734
|
+
declare function ReelsFeed({ renderOverlay, renderActions, renderPauseIndicator, renderDoubleTap, 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;
|
|
728
735
|
|
|
729
736
|
/**
|
|
730
737
|
* useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
|
|
@@ -801,8 +808,9 @@ interface VideoSlotProps {
|
|
|
801
808
|
renderOverlay?: (item: ContentItem, actions: SlotActions) => ReactNode;
|
|
802
809
|
renderActions?: (item: ContentItem, actions: SlotActions) => ReactNode;
|
|
803
810
|
renderPauseIndicator?: (isPaused: boolean) => ReactNode;
|
|
811
|
+
renderDoubleTap?: (isDoubleTap: boolean) => ReactNode;
|
|
804
812
|
}
|
|
805
|
-
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, renderPauseIndicator, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
813
|
+
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, renderPauseIndicator, renderDoubleTap, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
806
814
|
|
|
807
815
|
declare function DefaultOverlay({ item }: {
|
|
808
816
|
item: ContentItem;
|
|
@@ -820,6 +828,16 @@ declare function DefaultActions({ item, actions }: {
|
|
|
820
828
|
*/
|
|
821
829
|
declare function DefaultSkeleton(): react_jsx_runtime.JSX.Element;
|
|
822
830
|
|
|
831
|
+
/**
|
|
832
|
+
* DefaultDoubleTap — Default double-tap heart animation overlay
|
|
833
|
+
*
|
|
834
|
+
* Shown when user double-taps a video slot and no renderDoubleTap is provided.
|
|
835
|
+
* Renders an animated ❤️ that fades in/scales up then fades out.
|
|
836
|
+
*/
|
|
837
|
+
declare function DefaultDoubleTap({ isDoubleTap }: {
|
|
838
|
+
isDoubleTap: boolean;
|
|
839
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
840
|
+
|
|
823
841
|
declare function useFeedSelector<T>(selector: (state: FeedState) => T): T;
|
|
824
842
|
declare function useFeed(): {
|
|
825
843
|
items: NonNullable<ContentItem | undefined>[];
|
|
@@ -1090,4 +1108,4 @@ declare class HttpError extends Error {
|
|
|
1090
1108
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1091
1109
|
}
|
|
1092
1110
|
|
|
1093
|
-
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NetworkType, OptimisticManager, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
|
1111
|
+
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultDoubleTap, DefaultOverlay, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NetworkType, OptimisticManager, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
package/dist/index.d.ts
CHANGED
|
@@ -334,6 +334,13 @@ interface ReelsFeedProps {
|
|
|
334
334
|
* If not provided, SDK uses a default ▶️/⏸️ emoji indicator.
|
|
335
335
|
*/
|
|
336
336
|
renderPauseIndicator?: (isPaused: boolean) => ReactNode;
|
|
337
|
+
/**
|
|
338
|
+
* Custom double-tap overlay rendered when user double-taps a video slot.
|
|
339
|
+
* Receives isDoubleTap boolean (true for the duration of the animation window).
|
|
340
|
+
* Return ReactNode — positioned absolute center (full inset) by SDK.
|
|
341
|
+
* If not provided, SDK uses DefaultDoubleTap (animated ❤️ heart).
|
|
342
|
+
*/
|
|
343
|
+
renderDoubleTap?: (isDoubleTap: boolean) => ReactNode;
|
|
337
344
|
/**
|
|
338
345
|
* Custom loading skeleton shown during initial feed load.
|
|
339
346
|
* If not provided, SDK uses DefaultSkeleton with shimmer animation.
|
|
@@ -724,7 +731,7 @@ interface ReelsProviderProps {
|
|
|
724
731
|
declare function ReelsProvider({ children, adapters, debug }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
725
732
|
declare function useSDK(): SDKContextValue;
|
|
726
733
|
|
|
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;
|
|
734
|
+
declare function ReelsFeed({ renderOverlay, renderActions, renderPauseIndicator, renderDoubleTap, 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;
|
|
728
735
|
|
|
729
736
|
/**
|
|
730
737
|
* useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
|
|
@@ -801,8 +808,9 @@ interface VideoSlotProps {
|
|
|
801
808
|
renderOverlay?: (item: ContentItem, actions: SlotActions) => ReactNode;
|
|
802
809
|
renderActions?: (item: ContentItem, actions: SlotActions) => ReactNode;
|
|
803
810
|
renderPauseIndicator?: (isPaused: boolean) => ReactNode;
|
|
811
|
+
renderDoubleTap?: (isDoubleTap: boolean) => ReactNode;
|
|
804
812
|
}
|
|
805
|
-
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, renderPauseIndicator, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
813
|
+
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, showFps, renderOverlay, renderActions, renderPauseIndicator, renderDoubleTap, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
806
814
|
|
|
807
815
|
declare function DefaultOverlay({ item }: {
|
|
808
816
|
item: ContentItem;
|
|
@@ -820,6 +828,16 @@ declare function DefaultActions({ item, actions }: {
|
|
|
820
828
|
*/
|
|
821
829
|
declare function DefaultSkeleton(): react_jsx_runtime.JSX.Element;
|
|
822
830
|
|
|
831
|
+
/**
|
|
832
|
+
* DefaultDoubleTap — Default double-tap heart animation overlay
|
|
833
|
+
*
|
|
834
|
+
* Shown when user double-taps a video slot and no renderDoubleTap is provided.
|
|
835
|
+
* Renders an animated ❤️ that fades in/scales up then fades out.
|
|
836
|
+
*/
|
|
837
|
+
declare function DefaultDoubleTap({ isDoubleTap }: {
|
|
838
|
+
isDoubleTap: boolean;
|
|
839
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
840
|
+
|
|
823
841
|
declare function useFeedSelector<T>(selector: (state: FeedState) => T): T;
|
|
824
842
|
declare function useFeed(): {
|
|
825
843
|
items: NonNullable<ContentItem | undefined>[];
|
|
@@ -1090,4 +1108,4 @@ declare class HttpError extends Error {
|
|
|
1090
1108
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1091
1109
|
}
|
|
1092
1110
|
|
|
1093
|
-
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NetworkType, OptimisticManager, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
|
1111
|
+
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultDoubleTap, DefaultOverlay, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NetworkType, OptimisticManager, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
package/dist/index.js
CHANGED
|
@@ -1414,7 +1414,7 @@ function useHls(options) {
|
|
|
1414
1414
|
}
|
|
1415
1415
|
if (isNative) {
|
|
1416
1416
|
if (currentSrcRef.current === src) {
|
|
1417
|
-
if (video.readyState >= HTMLMediaElement.
|
|
1417
|
+
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1418
1418
|
setIsReady(true);
|
|
1419
1419
|
return void 0;
|
|
1420
1420
|
}
|
|
@@ -1422,7 +1422,7 @@ function useHls(options) {
|
|
|
1422
1422
|
video.addEventListener("canplay", handleCanPlayReuse, { once: true });
|
|
1423
1423
|
video.addEventListener("loadeddata", handleCanPlayReuse, { once: true });
|
|
1424
1424
|
video.addEventListener("playing", handleCanPlayReuse, { once: true });
|
|
1425
|
-
if (
|
|
1425
|
+
if (video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1426
1426
|
video.load();
|
|
1427
1427
|
}
|
|
1428
1428
|
return () => {
|
|
@@ -1433,7 +1433,10 @@ function useHls(options) {
|
|
|
1433
1433
|
}
|
|
1434
1434
|
video.src = src;
|
|
1435
1435
|
currentSrcRef.current = src;
|
|
1436
|
-
if (
|
|
1436
|
+
if (!isActive) {
|
|
1437
|
+
video.load();
|
|
1438
|
+
}
|
|
1439
|
+
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1437
1440
|
setIsReady(true);
|
|
1438
1441
|
return void 0;
|
|
1439
1442
|
}
|
|
@@ -1650,6 +1653,43 @@ function skeletonCircle(size) {
|
|
|
1650
1653
|
background: "rgba(255,255,255,0.1)"
|
|
1651
1654
|
};
|
|
1652
1655
|
}
|
|
1656
|
+
function DefaultDoubleTap({ isDoubleTap }) {
|
|
1657
|
+
if (!isDoubleTap) return null;
|
|
1658
|
+
return /* @__PURE__ */ jsxs(
|
|
1659
|
+
"div",
|
|
1660
|
+
{
|
|
1661
|
+
style: {
|
|
1662
|
+
position: "absolute",
|
|
1663
|
+
inset: 0,
|
|
1664
|
+
display: "flex",
|
|
1665
|
+
alignItems: "center",
|
|
1666
|
+
justifyContent: "center",
|
|
1667
|
+
pointerEvents: "none",
|
|
1668
|
+
zIndex: 20
|
|
1669
|
+
},
|
|
1670
|
+
children: [
|
|
1671
|
+
/* @__PURE__ */ jsx(
|
|
1672
|
+
"div",
|
|
1673
|
+
{
|
|
1674
|
+
style: {
|
|
1675
|
+
fontSize: 80,
|
|
1676
|
+
animation: "reels-sdk-doubleTapHeart 0.6s ease forwards"
|
|
1677
|
+
},
|
|
1678
|
+
children: "\u2764\uFE0F"
|
|
1679
|
+
}
|
|
1680
|
+
),
|
|
1681
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
1682
|
+
@keyframes reels-sdk-doubleTapHeart {
|
|
1683
|
+
0% { opacity: 0; transform: scale(0.5); }
|
|
1684
|
+
30% { opacity: 1; transform: scale(1.2); }
|
|
1685
|
+
60% { opacity: 1; transform: scale(1.0); }
|
|
1686
|
+
100% { opacity: 0; transform: scale(1.1); }
|
|
1687
|
+
}
|
|
1688
|
+
` })
|
|
1689
|
+
]
|
|
1690
|
+
}
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1653
1693
|
function VideoSlot({
|
|
1654
1694
|
item,
|
|
1655
1695
|
index,
|
|
@@ -1662,7 +1702,8 @@ function VideoSlot({
|
|
|
1662
1702
|
showFps = false,
|
|
1663
1703
|
renderOverlay,
|
|
1664
1704
|
renderActions,
|
|
1665
|
-
renderPauseIndicator
|
|
1705
|
+
renderPauseIndicator,
|
|
1706
|
+
renderDoubleTap
|
|
1666
1707
|
}) {
|
|
1667
1708
|
const { optimisticManager, adapters } = useSDK();
|
|
1668
1709
|
if (!isVideoItem(item)) {
|
|
@@ -1699,6 +1740,7 @@ function VideoSlot({
|
|
|
1699
1740
|
renderOverlay,
|
|
1700
1741
|
renderActions,
|
|
1701
1742
|
renderPauseIndicator,
|
|
1743
|
+
renderDoubleTap,
|
|
1702
1744
|
optimisticManager,
|
|
1703
1745
|
adapters
|
|
1704
1746
|
}
|
|
@@ -1717,6 +1759,7 @@ function VideoSlotInner({
|
|
|
1717
1759
|
renderOverlay,
|
|
1718
1760
|
renderActions,
|
|
1719
1761
|
renderPauseIndicator,
|
|
1762
|
+
renderDoubleTap,
|
|
1720
1763
|
optimisticManager,
|
|
1721
1764
|
adapters
|
|
1722
1765
|
}) {
|
|
@@ -1828,6 +1871,7 @@ function VideoSlotInner({
|
|
|
1828
1871
|
if (!video) return;
|
|
1829
1872
|
let onReady = null;
|
|
1830
1873
|
let fallbackTimerId = null;
|
|
1874
|
+
let pollId = null;
|
|
1831
1875
|
if (isActive) {
|
|
1832
1876
|
wasActiveRef.current = true;
|
|
1833
1877
|
const startPlay = () => {
|
|
@@ -1841,6 +1885,10 @@ function VideoSlotInner({
|
|
|
1841
1885
|
clearTimeout(fallbackTimerId);
|
|
1842
1886
|
fallbackTimerId = null;
|
|
1843
1887
|
}
|
|
1888
|
+
if (pollId !== null) {
|
|
1889
|
+
clearInterval(pollId);
|
|
1890
|
+
pollId = null;
|
|
1891
|
+
}
|
|
1844
1892
|
video.muted = true;
|
|
1845
1893
|
video.play().then(() => {
|
|
1846
1894
|
video.muted = isMuted;
|
|
@@ -1848,22 +1896,33 @@ function VideoSlotInner({
|
|
|
1848
1896
|
video.muted = isMuted;
|
|
1849
1897
|
});
|
|
1850
1898
|
};
|
|
1851
|
-
if (video.readyState >= HTMLMediaElement.
|
|
1899
|
+
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1852
1900
|
startPlay();
|
|
1853
1901
|
} else {
|
|
1854
1902
|
onReady = startPlay;
|
|
1855
1903
|
video.addEventListener("canplay", onReady, { once: true });
|
|
1856
1904
|
video.addEventListener("loadeddata", onReady, { once: true });
|
|
1857
1905
|
video.addEventListener("playing", onReady, { once: true });
|
|
1858
|
-
if (isNativeHls && video.src && video.readyState < HTMLMediaElement.
|
|
1906
|
+
if (isNativeHls && video.src && video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
1859
1907
|
video.load();
|
|
1860
1908
|
}
|
|
1909
|
+
if (isNativeHls) {
|
|
1910
|
+
pollId = setInterval(() => {
|
|
1911
|
+
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && onReady) {
|
|
1912
|
+
if (pollId !== null) {
|
|
1913
|
+
clearInterval(pollId);
|
|
1914
|
+
pollId = null;
|
|
1915
|
+
}
|
|
1916
|
+
startPlay();
|
|
1917
|
+
}
|
|
1918
|
+
}, 100);
|
|
1919
|
+
}
|
|
1861
1920
|
fallbackTimerId = window.setTimeout(() => {
|
|
1862
1921
|
fallbackTimerId = null;
|
|
1863
1922
|
if (onReady) {
|
|
1864
1923
|
startPlay();
|
|
1865
1924
|
}
|
|
1866
|
-
}, 3e3);
|
|
1925
|
+
}, isNativeHls ? 800 : 3e3);
|
|
1867
1926
|
}
|
|
1868
1927
|
} else if (wasActiveRef.current) {
|
|
1869
1928
|
video.pause();
|
|
@@ -1883,6 +1942,10 @@ function VideoSlotInner({
|
|
|
1883
1942
|
clearTimeout(fallbackTimerId);
|
|
1884
1943
|
fallbackTimerId = null;
|
|
1885
1944
|
}
|
|
1945
|
+
if (pollId !== null) {
|
|
1946
|
+
clearInterval(pollId);
|
|
1947
|
+
pollId = null;
|
|
1948
|
+
}
|
|
1886
1949
|
};
|
|
1887
1950
|
}, [isActive, isMuted, hasPlayedAhead, isNativeHls]);
|
|
1888
1951
|
useEffect(() => {
|
|
@@ -1915,18 +1978,44 @@ function VideoSlotInner({
|
|
|
1915
1978
|
}, [isActive]);
|
|
1916
1979
|
const showPosterOverlay = isActive ? !isReady && !isActuallyPlaying : canPlayAhead ? !hasPlayedAhead : !isReady;
|
|
1917
1980
|
const [isPaused, setIsPaused] = useState(false);
|
|
1981
|
+
const [isDoubleTap, setIsDoubleTap] = useState(false);
|
|
1982
|
+
const lastTapTimeRef = useRef(0);
|
|
1983
|
+
const doubleTapTimerRef = useRef(null);
|
|
1918
1984
|
const handleTap = useCallback(() => {
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1985
|
+
if (!isActive) return;
|
|
1986
|
+
const now = Date.now();
|
|
1987
|
+
const delta = now - lastTapTimeRef.current;
|
|
1988
|
+
lastTapTimeRef.current = now;
|
|
1989
|
+
if (delta < 300) {
|
|
1990
|
+
if (doubleTapTimerRef.current !== null) {
|
|
1991
|
+
clearTimeout(doubleTapTimerRef.current);
|
|
1992
|
+
doubleTapTimerRef.current = null;
|
|
1993
|
+
}
|
|
1994
|
+
setIsDoubleTap(true);
|
|
1995
|
+
setTimeout(() => setIsDoubleTap(false), 600);
|
|
1925
1996
|
} else {
|
|
1926
|
-
|
|
1927
|
-
|
|
1997
|
+
doubleTapTimerRef.current = setTimeout(() => {
|
|
1998
|
+
doubleTapTimerRef.current = null;
|
|
1999
|
+
const video = videoRef.current;
|
|
2000
|
+
if (!video) return;
|
|
2001
|
+
if (video.paused) {
|
|
2002
|
+
video.play().catch(() => {
|
|
2003
|
+
});
|
|
2004
|
+
setIsPaused(false);
|
|
2005
|
+
} else {
|
|
2006
|
+
video.pause();
|
|
2007
|
+
setIsPaused(true);
|
|
2008
|
+
}
|
|
2009
|
+
}, 300);
|
|
1928
2010
|
}
|
|
1929
2011
|
}, [isActive]);
|
|
2012
|
+
useEffect(() => {
|
|
2013
|
+
return () => {
|
|
2014
|
+
if (doubleTapTimerRef.current !== null) {
|
|
2015
|
+
clearTimeout(doubleTapTimerRef.current);
|
|
2016
|
+
}
|
|
2017
|
+
};
|
|
2018
|
+
}, []);
|
|
1930
2019
|
useEffect(() => {
|
|
1931
2020
|
if (isActive) setIsPaused(false);
|
|
1932
2021
|
}, [isActive]);
|
|
@@ -2000,6 +2089,18 @@ function VideoSlotInner({
|
|
|
2000
2089
|
}
|
|
2001
2090
|
}
|
|
2002
2091
|
),
|
|
2092
|
+
(isDoubleTap || renderDoubleTap) && /* @__PURE__ */ jsx(
|
|
2093
|
+
"div",
|
|
2094
|
+
{
|
|
2095
|
+
style: {
|
|
2096
|
+
position: "absolute",
|
|
2097
|
+
inset: 0,
|
|
2098
|
+
pointerEvents: "none",
|
|
2099
|
+
zIndex: 20
|
|
2100
|
+
},
|
|
2101
|
+
children: renderDoubleTap ? renderDoubleTap(isDoubleTap) : /* @__PURE__ */ jsx(DefaultDoubleTap, { isDoubleTap })
|
|
2102
|
+
}
|
|
2103
|
+
),
|
|
2003
2104
|
isPaused && /* @__PURE__ */ jsx(
|
|
2004
2105
|
"div",
|
|
2005
2106
|
{
|
|
@@ -2123,6 +2224,7 @@ function ReelsFeed({
|
|
|
2123
2224
|
renderOverlay,
|
|
2124
2225
|
renderActions,
|
|
2125
2226
|
renderPauseIndicator,
|
|
2227
|
+
renderDoubleTap,
|
|
2126
2228
|
renderLoading,
|
|
2127
2229
|
renderEmpty,
|
|
2128
2230
|
renderError: _renderError,
|
|
@@ -2369,7 +2471,8 @@ function ReelsFeed({
|
|
|
2369
2471
|
showFps: showFps && isActive,
|
|
2370
2472
|
renderOverlay,
|
|
2371
2473
|
renderActions,
|
|
2372
|
-
renderPauseIndicator
|
|
2474
|
+
renderPauseIndicator,
|
|
2475
|
+
renderDoubleTap
|
|
2373
2476
|
}
|
|
2374
2477
|
) : null
|
|
2375
2478
|
},
|
|
@@ -3000,4 +3103,4 @@ var HttpError = class extends Error {
|
|
|
3000
3103
|
}
|
|
3001
3104
|
};
|
|
3002
3105
|
|
|
3003
|
-
export { DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultSkeleton, FeedManager, HttpDataSource, HttpError, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, OptimisticManager, PlayerEngine, PlayerStatus, ReelsFeed, ReelsProvider, ResourceGovernor, VALID_TRANSITIONS, VideoSlot, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
|
3106
|
+
export { DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultDoubleTap, DefaultOverlay, DefaultSkeleton, FeedManager, HttpDataSource, HttpError, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, OptimisticManager, PlayerEngine, PlayerStatus, ReelsFeed, ReelsProvider, ResourceGovernor, VALID_TRANSITIONS, VideoSlot, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|