@xhub-reels/sdk 0.2.6 → 0.2.12

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
@@ -444,6 +444,13 @@ var FeedManager = class {
444
444
  // ═══════════════════════════════════════════
445
445
  // PUBLIC API — Prefetch
446
446
  // ═══════════════════════════════════════════
447
+ setInitialItems(items) {
448
+ this.prefetchCache = {
449
+ items,
450
+ nextCursor: null,
451
+ timestamp: Date.now()
452
+ };
453
+ }
447
454
  async prefetch(ttlMs) {
448
455
  if (this.prefetchCache) {
449
456
  const ttl = ttlMs ?? this.config.staleTTL;
@@ -593,16 +600,23 @@ var FeedManager = class {
593
600
  applyItems(incoming, _nextCursor, append) {
594
601
  const { itemsById, displayOrder } = this.store.getState();
595
602
  const nextById = new Map(itemsById);
596
- const existingIds = new Set(displayOrder);
597
- const newIds = [];
598
603
  for (const item of incoming) {
599
- if (!existingIds.has(item.id)) {
600
- newIds.push(item.id);
601
- }
602
604
  nextById.set(item.id, item);
603
605
  this.accessOrder.set(item.id, Date.now());
604
606
  }
605
- const nextOrder = append ? [...displayOrder, ...newIds] : newIds;
607
+ let nextOrder;
608
+ if (append) {
609
+ const existingIds = new Set(displayOrder);
610
+ const newIds = [];
611
+ for (const item of incoming) {
612
+ if (!existingIds.has(item.id)) {
613
+ newIds.push(item.id);
614
+ }
615
+ }
616
+ nextOrder = [...displayOrder, ...newIds];
617
+ } else {
618
+ nextOrder = incoming.map((item) => item.id);
619
+ }
606
620
  if (nextById.size > this.config.maxCacheSize) {
607
621
  this.evictLRU(nextById, nextOrder);
608
622
  }
@@ -1130,7 +1144,12 @@ function useSnapAnimation(config = {}) {
1130
1144
  return { animateSnap, animateBounceBack, cancelAnimation };
1131
1145
  }
1132
1146
  var SDKContext = react.createContext(null);
1133
- function ReelsProvider({ children, adapters, debug = false }) {
1147
+ function ReelsProvider({
1148
+ children,
1149
+ adapters,
1150
+ initialItems,
1151
+ debug = false
1152
+ }) {
1134
1153
  const logger = adapters.logger;
1135
1154
  const sdkRef = react.useRef(null);
1136
1155
  const value = react.useMemo(() => {
@@ -1141,6 +1160,9 @@ function ReelsProvider({ children, adapters, debug = false }) {
1141
1160
  sdkRef.current.optimisticManager.destroy();
1142
1161
  }
1143
1162
  const feedManager = new FeedManager(adapters.dataSource, {}, logger);
1163
+ if (initialItems && initialItems.length > 0) {
1164
+ feedManager.setInitialItems(initialItems);
1165
+ }
1144
1166
  const playerEngine = new PlayerEngine(
1145
1167
  {},
1146
1168
  adapters.analytics,
@@ -1329,10 +1351,11 @@ var ACTIVE_HLS_DEFAULTS = {
1329
1351
  maxMaxBufferLength: 15,
1330
1352
  capLevelToPlayerSize: true,
1331
1353
  startLevel: 0,
1332
- abrEwmaDefaultEstimate: 5e5,
1354
+ abrEwmaDefaultEstimate: 2e6,
1333
1355
  lowLatencyMode: false,
1334
1356
  backBufferLength: 5,
1335
- enableWorker: true
1357
+ enableWorker: true,
1358
+ startFragPrefetch: true
1336
1359
  };
1337
1360
  var HOT_HLS_DEFAULTS = {
1338
1361
  maxBufferLength: 2,
@@ -1826,6 +1849,20 @@ function VideoSlotInner({
1826
1849
  }
1827
1850
  }, [mp4Src, isActive, isPrefetch, isPreloaded, isHlsSource]);
1828
1851
  const isReady = isHlsSource ? hlsReady : mp4Ready;
1852
+ const [isVideoPlaying, setIsVideoPlaying] = react.useState(false);
1853
+ react.useEffect(() => {
1854
+ const video = videoRef.current;
1855
+ if (!video || !isActive) {
1856
+ setIsVideoPlaying(false);
1857
+ return;
1858
+ }
1859
+ const onPlaying = () => setIsVideoPlaying(true);
1860
+ video.addEventListener("playing", onPlaying);
1861
+ return () => {
1862
+ video.removeEventListener("playing", onPlaying);
1863
+ setIsVideoPlaying(false);
1864
+ };
1865
+ }, [isActive]);
1829
1866
  const [hasPlayedAhead, setHasPlayedAhead] = react.useState(false);
1830
1867
  react.useEffect(() => {
1831
1868
  const video = videoRef.current;
@@ -1865,6 +1902,7 @@ function VideoSlotInner({
1865
1902
  }, [isActive, isReady, hasPlayedAhead]);
1866
1903
  react.useEffect(() => {
1867
1904
  setHasPlayedAhead(false);
1905
+ setIsVideoPlaying(false);
1868
1906
  }, [src]);
1869
1907
  const wasActiveRef = react.useRef(false);
1870
1908
  const [isManuallyPaused, setIsManuallyPaused] = react.useState(false);
@@ -1941,7 +1979,7 @@ function VideoSlotInner({
1941
1979
  if (!video) return;
1942
1980
  video.muted = isActive ? isMuted : true;
1943
1981
  }, [isMuted, isActive]);
1944
- const showPosterOverlay = !isReady && !hasPlayedAhead;
1982
+ const showPosterOverlay = isActive ? !isVideoPlaying : !isReady && !hasPlayedAhead;
1945
1983
  const isPreDecoded = hasPlayedAhead;
1946
1984
  const [showMuteIndicator, setShowMuteIndicator] = react.useState(false);
1947
1985
  const muteIndicatorTimer = react.useRef(null);
@@ -2059,9 +2097,9 @@ function VideoSlotInner({
2059
2097
  height: "100%",
2060
2098
  objectFit: "cover",
2061
2099
  // Hide video until ready to avoid black frame flash.
2062
- // When pre-decoded, skip transition — first frame is already on canvas.
2100
+ // When pre-decoded or active, skip transition — first frame is already on canvas or playing.
2063
2101
  opacity: showPosterOverlay ? 0 : 1,
2064
- transition: isPreDecoded ? "none" : "opacity 0.15s ease"
2102
+ transition: isActive ? "none" : isPreDecoded ? "none" : "opacity 0.15s ease"
2065
2103
  }
2066
2104
  }
2067
2105
  ),
@@ -2075,7 +2113,7 @@ function VideoSlotInner({
2075
2113
  backgroundSize: "cover",
2076
2114
  backgroundPosition: "center",
2077
2115
  opacity: showPosterOverlay ? 1 : 0,
2078
- transition: "opacity 0.15s ease",
2116
+ transition: isActive ? "none" : "opacity 0.15s ease",
2079
2117
  pointerEvents: "none"
2080
2118
  }
2081
2119
  }
package/dist/index.d.cts CHANGED
@@ -503,6 +503,7 @@ declare class FeedManager {
503
503
  constructor(dataSource: IDataSource, config?: FeedConfig, logger?: ILogger);
504
504
  getDataSource(): IDataSource;
505
505
  setDataSource(dataSource: IDataSource, reset?: boolean): void;
506
+ setInitialItems(items: ContentItem[]): void;
506
507
  prefetch(ttlMs?: number): Promise<void>;
507
508
  hasPrefetchCache(): boolean;
508
509
  clearPrefetchCache(): void;
@@ -781,10 +782,12 @@ interface SDKContextValue {
781
782
  interface ReelsProviderProps {
782
783
  children: ReactNode;
783
784
  adapters: SDKAdapters;
785
+ /** Seed initial items into feedManager */
786
+ initialItems?: ContentItem[];
784
787
  /** Enable verbose logging (default: false) */
785
788
  debug?: boolean;
786
789
  }
787
- declare function ReelsProvider({ children, adapters, debug }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
790
+ declare function ReelsProvider({ children, adapters, initialItems, debug, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
788
791
  declare function useSDK(): SDKContextValue;
789
792
 
790
793
  declare function ReelsFeed({ renderOverlay, renderActions, renderPauseAction, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, initialMuted, onAutoplayBlocked, }: 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;
package/dist/index.d.ts CHANGED
@@ -503,6 +503,7 @@ declare class FeedManager {
503
503
  constructor(dataSource: IDataSource, config?: FeedConfig, logger?: ILogger);
504
504
  getDataSource(): IDataSource;
505
505
  setDataSource(dataSource: IDataSource, reset?: boolean): void;
506
+ setInitialItems(items: ContentItem[]): void;
506
507
  prefetch(ttlMs?: number): Promise<void>;
507
508
  hasPrefetchCache(): boolean;
508
509
  clearPrefetchCache(): void;
@@ -781,10 +782,12 @@ interface SDKContextValue {
781
782
  interface ReelsProviderProps {
782
783
  children: ReactNode;
783
784
  adapters: SDKAdapters;
785
+ /** Seed initial items into feedManager */
786
+ initialItems?: ContentItem[];
784
787
  /** Enable verbose logging (default: false) */
785
788
  debug?: boolean;
786
789
  }
787
- declare function ReelsProvider({ children, adapters, debug }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
790
+ declare function ReelsProvider({ children, adapters, initialItems, debug, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
788
791
  declare function useSDK(): SDKContextValue;
789
792
 
790
793
  declare function ReelsFeed({ renderOverlay, renderActions, renderPauseAction, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, initialMuted, onAutoplayBlocked, }: 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;
package/dist/index.js CHANGED
@@ -438,6 +438,13 @@ var FeedManager = class {
438
438
  // ═══════════════════════════════════════════
439
439
  // PUBLIC API — Prefetch
440
440
  // ═══════════════════════════════════════════
441
+ setInitialItems(items) {
442
+ this.prefetchCache = {
443
+ items,
444
+ nextCursor: null,
445
+ timestamp: Date.now()
446
+ };
447
+ }
441
448
  async prefetch(ttlMs) {
442
449
  if (this.prefetchCache) {
443
450
  const ttl = ttlMs ?? this.config.staleTTL;
@@ -587,16 +594,23 @@ var FeedManager = class {
587
594
  applyItems(incoming, _nextCursor, append) {
588
595
  const { itemsById, displayOrder } = this.store.getState();
589
596
  const nextById = new Map(itemsById);
590
- const existingIds = new Set(displayOrder);
591
- const newIds = [];
592
597
  for (const item of incoming) {
593
- if (!existingIds.has(item.id)) {
594
- newIds.push(item.id);
595
- }
596
598
  nextById.set(item.id, item);
597
599
  this.accessOrder.set(item.id, Date.now());
598
600
  }
599
- const nextOrder = append ? [...displayOrder, ...newIds] : newIds;
601
+ let nextOrder;
602
+ if (append) {
603
+ const existingIds = new Set(displayOrder);
604
+ const newIds = [];
605
+ for (const item of incoming) {
606
+ if (!existingIds.has(item.id)) {
607
+ newIds.push(item.id);
608
+ }
609
+ }
610
+ nextOrder = [...displayOrder, ...newIds];
611
+ } else {
612
+ nextOrder = incoming.map((item) => item.id);
613
+ }
600
614
  if (nextById.size > this.config.maxCacheSize) {
601
615
  this.evictLRU(nextById, nextOrder);
602
616
  }
@@ -1124,7 +1138,12 @@ function useSnapAnimation(config = {}) {
1124
1138
  return { animateSnap, animateBounceBack, cancelAnimation };
1125
1139
  }
1126
1140
  var SDKContext = createContext(null);
1127
- function ReelsProvider({ children, adapters, debug = false }) {
1141
+ function ReelsProvider({
1142
+ children,
1143
+ adapters,
1144
+ initialItems,
1145
+ debug = false
1146
+ }) {
1128
1147
  const logger = adapters.logger;
1129
1148
  const sdkRef = useRef(null);
1130
1149
  const value = useMemo(() => {
@@ -1135,6 +1154,9 @@ function ReelsProvider({ children, adapters, debug = false }) {
1135
1154
  sdkRef.current.optimisticManager.destroy();
1136
1155
  }
1137
1156
  const feedManager = new FeedManager(adapters.dataSource, {}, logger);
1157
+ if (initialItems && initialItems.length > 0) {
1158
+ feedManager.setInitialItems(initialItems);
1159
+ }
1138
1160
  const playerEngine = new PlayerEngine(
1139
1161
  {},
1140
1162
  adapters.analytics,
@@ -1323,10 +1345,11 @@ var ACTIVE_HLS_DEFAULTS = {
1323
1345
  maxMaxBufferLength: 15,
1324
1346
  capLevelToPlayerSize: true,
1325
1347
  startLevel: 0,
1326
- abrEwmaDefaultEstimate: 5e5,
1348
+ abrEwmaDefaultEstimate: 2e6,
1327
1349
  lowLatencyMode: false,
1328
1350
  backBufferLength: 5,
1329
- enableWorker: true
1351
+ enableWorker: true,
1352
+ startFragPrefetch: true
1330
1353
  };
1331
1354
  var HOT_HLS_DEFAULTS = {
1332
1355
  maxBufferLength: 2,
@@ -1820,6 +1843,20 @@ function VideoSlotInner({
1820
1843
  }
1821
1844
  }, [mp4Src, isActive, isPrefetch, isPreloaded, isHlsSource]);
1822
1845
  const isReady = isHlsSource ? hlsReady : mp4Ready;
1846
+ const [isVideoPlaying, setIsVideoPlaying] = useState(false);
1847
+ useEffect(() => {
1848
+ const video = videoRef.current;
1849
+ if (!video || !isActive) {
1850
+ setIsVideoPlaying(false);
1851
+ return;
1852
+ }
1853
+ const onPlaying = () => setIsVideoPlaying(true);
1854
+ video.addEventListener("playing", onPlaying);
1855
+ return () => {
1856
+ video.removeEventListener("playing", onPlaying);
1857
+ setIsVideoPlaying(false);
1858
+ };
1859
+ }, [isActive]);
1823
1860
  const [hasPlayedAhead, setHasPlayedAhead] = useState(false);
1824
1861
  useEffect(() => {
1825
1862
  const video = videoRef.current;
@@ -1859,6 +1896,7 @@ function VideoSlotInner({
1859
1896
  }, [isActive, isReady, hasPlayedAhead]);
1860
1897
  useEffect(() => {
1861
1898
  setHasPlayedAhead(false);
1899
+ setIsVideoPlaying(false);
1862
1900
  }, [src]);
1863
1901
  const wasActiveRef = useRef(false);
1864
1902
  const [isManuallyPaused, setIsManuallyPaused] = useState(false);
@@ -1935,7 +1973,7 @@ function VideoSlotInner({
1935
1973
  if (!video) return;
1936
1974
  video.muted = isActive ? isMuted : true;
1937
1975
  }, [isMuted, isActive]);
1938
- const showPosterOverlay = !isReady && !hasPlayedAhead;
1976
+ const showPosterOverlay = isActive ? !isVideoPlaying : !isReady && !hasPlayedAhead;
1939
1977
  const isPreDecoded = hasPlayedAhead;
1940
1978
  const [showMuteIndicator, setShowMuteIndicator] = useState(false);
1941
1979
  const muteIndicatorTimer = useRef(null);
@@ -2053,9 +2091,9 @@ function VideoSlotInner({
2053
2091
  height: "100%",
2054
2092
  objectFit: "cover",
2055
2093
  // Hide video until ready to avoid black frame flash.
2056
- // When pre-decoded, skip transition — first frame is already on canvas.
2094
+ // When pre-decoded or active, skip transition — first frame is already on canvas or playing.
2057
2095
  opacity: showPosterOverlay ? 0 : 1,
2058
- transition: isPreDecoded ? "none" : "opacity 0.15s ease"
2096
+ transition: isActive ? "none" : isPreDecoded ? "none" : "opacity 0.15s ease"
2059
2097
  }
2060
2098
  }
2061
2099
  ),
@@ -2069,7 +2107,7 @@ function VideoSlotInner({
2069
2107
  backgroundSize: "cover",
2070
2108
  backgroundPosition: "center",
2071
2109
  opacity: showPosterOverlay ? 1 : 0,
2072
- transition: "opacity 0.15s ease",
2110
+ transition: isActive ? "none" : "opacity 0.15s ease",
2073
2111
  pointerEvents: "none"
2074
2112
  }
2075
2113
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xhub-reels/sdk",
3
- "version": "0.2.6",
3
+ "version": "0.2.12",
4
4
  "description": "High-performance Short Video / Reels SDK for React — optimized for Flutter WebView",
5
5
  "license": "MIT",
6
6
  "type": "module",