@xhub-reels/sdk 0.1.10 → 0.1.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
@@ -1364,9 +1364,18 @@ function mapHlsError(data) {
1364
1364
  }
1365
1365
  function useHls(options) {
1366
1366
  const { src, videoRef, isActive, isPrefetch, bufferTier = "active", hlsConfig, onError } = options;
1367
- const isHlsSupported = typeof window !== "undefined" && Hls__default.default.isSupported();
1368
- const isNative = supportsNativeHls();
1369
- const isHlsJs = isHlsSupported && !isNative;
1367
+ const [isHlsJs, setIsHlsJs] = react.useState(false);
1368
+ const [isNativeHls, setIsNativeHls] = react.useState(false);
1369
+ react.useEffect(() => {
1370
+ const hlsSupported = Hls__default.default.isSupported();
1371
+ const native = supportsNativeHls();
1372
+ setIsHlsJs(hlsSupported && !native);
1373
+ setIsNativeHls(native);
1374
+ }, []);
1375
+ const isHlsJsRef = react.useRef(false);
1376
+ const isNativeRef = react.useRef(false);
1377
+ isHlsJsRef.current = isHlsJs;
1378
+ isNativeRef.current = isNativeHls;
1370
1379
  const [isReady, setIsReady] = react.useState(false);
1371
1380
  const hlsRef = react.useRef(null);
1372
1381
  const onErrorRef = react.useRef(onError);
@@ -1391,6 +1400,8 @@ function useHls(options) {
1391
1400
  currentSrcRef.current = void 0;
1392
1401
  return;
1393
1402
  }
1403
+ const isNative = isNativeRef.current;
1404
+ const isHlsSupported = isHlsJsRef.current;
1394
1405
  if (!isActive && !isPrefetch) {
1395
1406
  if (isNative) {
1396
1407
  if (video.src) {
@@ -1441,7 +1452,6 @@ function useHls(options) {
1441
1452
  };
1442
1453
  }
1443
1454
  if (!isHlsSupported) {
1444
- onErrorRef.current?.("UNKNOWN", "HLS playback not supported in this browser");
1445
1455
  return;
1446
1456
  }
1447
1457
  if (hlsRef.current && currentSrcRef.current === src) {
@@ -1505,7 +1515,7 @@ function useHls(options) {
1505
1515
  currentSrcRef.current = void 0;
1506
1516
  }
1507
1517
  };
1508
- }, [src, isActive, isPrefetch]);
1518
+ }, [src, isActive, isPrefetch, isHlsJs, isNativeHls]);
1509
1519
  react.useEffect(() => {
1510
1520
  const hls = hlsRef.current;
1511
1521
  if (!hls) {
@@ -1524,7 +1534,7 @@ function useHls(options) {
1524
1534
  }, [bufferTier]);
1525
1535
  return {
1526
1536
  isHlsJs,
1527
- isNativeHls: isNative,
1537
+ isNativeHls,
1528
1538
  isReady,
1529
1539
  destroy
1530
1540
  };
@@ -1769,24 +1779,47 @@ function VideoSlotInner({
1769
1779
  const prevMuted = video.muted;
1770
1780
  video.muted = true;
1771
1781
  let cancelled = false;
1782
+ let rafId = null;
1783
+ let vfcHandle = null;
1784
+ const pauseAfterDecode = () => {
1785
+ if (cancelled) return;
1786
+ video.pause();
1787
+ video.currentTime = 0;
1788
+ video.muted = prevMuted;
1789
+ setHasPlayedAhead(true);
1790
+ };
1772
1791
  const doPlayAhead = async () => {
1773
1792
  try {
1774
1793
  await video.play();
1775
1794
  if (cancelled) return;
1776
- const pauseAfterDecode = () => {
1777
- if (cancelled) return;
1778
- video.pause();
1779
- video.currentTime = 0;
1780
- video.muted = prevMuted;
1781
- setHasPlayedAhead(true);
1782
- };
1783
- setTimeout(pauseAfterDecode, 50);
1795
+ if ("requestVideoFrameCallback" in video) {
1796
+ vfcHandle = video.requestVideoFrameCallback(() => {
1797
+ vfcHandle = null;
1798
+ pauseAfterDecode();
1799
+ });
1800
+ } else {
1801
+ rafId = requestAnimationFrame(() => {
1802
+ rafId = requestAnimationFrame(() => {
1803
+ rafId = null;
1804
+ pauseAfterDecode();
1805
+ });
1806
+ });
1807
+ }
1784
1808
  } catch {
1809
+ video.muted = prevMuted;
1785
1810
  }
1786
1811
  };
1787
1812
  doPlayAhead();
1788
1813
  return () => {
1789
1814
  cancelled = true;
1815
+ if (rafId !== null) {
1816
+ cancelAnimationFrame(rafId);
1817
+ rafId = null;
1818
+ }
1819
+ if (vfcHandle !== null && "cancelVideoFrameCallback" in video) {
1820
+ video.cancelVideoFrameCallback(vfcHandle);
1821
+ vfcHandle = null;
1822
+ }
1790
1823
  };
1791
1824
  }, [canPlayAhead, isActive, isReady, hasPlayedAhead]);
1792
1825
  react.useEffect(() => {
@@ -2084,6 +2117,8 @@ function ReelsFeed({
2084
2117
  }) {
2085
2118
  const { items, loading, loadInitial, loadMore, hasMore } = useFeed();
2086
2119
  const {
2120
+ activeIndices,
2121
+ warmIndices,
2087
2122
  focusedIndex,
2088
2123
  prefetchIndex,
2089
2124
  setFocusedIndexImmediate,
@@ -2277,43 +2312,54 @@ function ReelsFeed({
2277
2312
  to { transform: rotate(360deg); }
2278
2313
  }
2279
2314
  ` }),
2280
- items.map((item, index) => {
2281
- const isActive = index === focusedIndex;
2282
- const isPrefetch = index === prefetchIndex;
2283
- const isWarm = isWarmAllocated(index);
2284
- const isVisible = shouldRenderVideo(index) || isPrefetch;
2285
- const bufferTier = isActive ? "active" : isWarm ? "warm" : "hot";
2286
- return /* @__PURE__ */ jsxRuntime.jsx(
2287
- "div",
2288
- {
2289
- "data-slot-index": index,
2290
- style: {
2291
- position: "absolute",
2292
- inset: 0,
2293
- willChange: "transform",
2294
- transform: getInitialTransformPx(index)
2315
+ (() => {
2316
+ const windowIndices = /* @__PURE__ */ new Set([
2317
+ ...activeIndices,
2318
+ ...warmIndices
2319
+ ]);
2320
+ if (prefetchIndex !== null && prefetchIndex !== void 0) {
2321
+ windowIndices.add(prefetchIndex);
2322
+ }
2323
+ return [...windowIndices].map((index) => {
2324
+ const item = items[index];
2325
+ if (!item) return null;
2326
+ const isActive = index === focusedIndex;
2327
+ const isPrefetchSlot = index === prefetchIndex;
2328
+ const isWarm = isWarmAllocated(index);
2329
+ const isVisible = shouldRenderVideo(index) || isPrefetchSlot;
2330
+ const bufferTier = isActive ? "active" : isWarm ? "warm" : "hot";
2331
+ return /* @__PURE__ */ jsxRuntime.jsx(
2332
+ "div",
2333
+ {
2334
+ "data-slot-index": index,
2335
+ style: {
2336
+ position: "absolute",
2337
+ inset: 0,
2338
+ willChange: "transform",
2339
+ transform: getInitialTransformPx(index)
2340
+ },
2341
+ children: isVisible ? /* @__PURE__ */ jsxRuntime.jsx(
2342
+ VideoSlot,
2343
+ {
2344
+ item,
2345
+ index,
2346
+ isActive,
2347
+ isPrefetch: isPrefetchSlot,
2348
+ isPreloaded: !isActive && !isPrefetchSlot && isVisible,
2349
+ bufferTier,
2350
+ isMuted,
2351
+ onToggleMute: handleToggleMute,
2352
+ showFps: showFps && isActive,
2353
+ renderOverlay,
2354
+ renderActions,
2355
+ renderPauseIndicator
2356
+ }
2357
+ ) : null
2295
2358
  },
2296
- children: /* @__PURE__ */ jsxRuntime.jsx(
2297
- VideoSlot,
2298
- {
2299
- item,
2300
- index,
2301
- isActive,
2302
- isPrefetch,
2303
- isPreloaded: !isActive && !isPrefetch && isVisible,
2304
- bufferTier,
2305
- isMuted,
2306
- onToggleMute: handleToggleMute,
2307
- showFps: showFps && isActive,
2308
- renderOverlay,
2309
- renderActions,
2310
- renderPauseIndicator
2311
- }
2312
- )
2313
- },
2314
- item.id
2315
- );
2316
- })
2359
+ item.id
2360
+ );
2361
+ });
2362
+ })()
2317
2363
  ]
2318
2364
  }
2319
2365
  );
package/dist/index.js CHANGED
@@ -1358,9 +1358,18 @@ function mapHlsError(data) {
1358
1358
  }
1359
1359
  function useHls(options) {
1360
1360
  const { src, videoRef, isActive, isPrefetch, bufferTier = "active", hlsConfig, onError } = options;
1361
- const isHlsSupported = typeof window !== "undefined" && Hls.isSupported();
1362
- const isNative = supportsNativeHls();
1363
- const isHlsJs = isHlsSupported && !isNative;
1361
+ const [isHlsJs, setIsHlsJs] = useState(false);
1362
+ const [isNativeHls, setIsNativeHls] = useState(false);
1363
+ useEffect(() => {
1364
+ const hlsSupported = Hls.isSupported();
1365
+ const native = supportsNativeHls();
1366
+ setIsHlsJs(hlsSupported && !native);
1367
+ setIsNativeHls(native);
1368
+ }, []);
1369
+ const isHlsJsRef = useRef(false);
1370
+ const isNativeRef = useRef(false);
1371
+ isHlsJsRef.current = isHlsJs;
1372
+ isNativeRef.current = isNativeHls;
1364
1373
  const [isReady, setIsReady] = useState(false);
1365
1374
  const hlsRef = useRef(null);
1366
1375
  const onErrorRef = useRef(onError);
@@ -1385,6 +1394,8 @@ function useHls(options) {
1385
1394
  currentSrcRef.current = void 0;
1386
1395
  return;
1387
1396
  }
1397
+ const isNative = isNativeRef.current;
1398
+ const isHlsSupported = isHlsJsRef.current;
1388
1399
  if (!isActive && !isPrefetch) {
1389
1400
  if (isNative) {
1390
1401
  if (video.src) {
@@ -1435,7 +1446,6 @@ function useHls(options) {
1435
1446
  };
1436
1447
  }
1437
1448
  if (!isHlsSupported) {
1438
- onErrorRef.current?.("UNKNOWN", "HLS playback not supported in this browser");
1439
1449
  return;
1440
1450
  }
1441
1451
  if (hlsRef.current && currentSrcRef.current === src) {
@@ -1499,7 +1509,7 @@ function useHls(options) {
1499
1509
  currentSrcRef.current = void 0;
1500
1510
  }
1501
1511
  };
1502
- }, [src, isActive, isPrefetch]);
1512
+ }, [src, isActive, isPrefetch, isHlsJs, isNativeHls]);
1503
1513
  useEffect(() => {
1504
1514
  const hls = hlsRef.current;
1505
1515
  if (!hls) {
@@ -1518,7 +1528,7 @@ function useHls(options) {
1518
1528
  }, [bufferTier]);
1519
1529
  return {
1520
1530
  isHlsJs,
1521
- isNativeHls: isNative,
1531
+ isNativeHls,
1522
1532
  isReady,
1523
1533
  destroy
1524
1534
  };
@@ -1763,24 +1773,47 @@ function VideoSlotInner({
1763
1773
  const prevMuted = video.muted;
1764
1774
  video.muted = true;
1765
1775
  let cancelled = false;
1776
+ let rafId = null;
1777
+ let vfcHandle = null;
1778
+ const pauseAfterDecode = () => {
1779
+ if (cancelled) return;
1780
+ video.pause();
1781
+ video.currentTime = 0;
1782
+ video.muted = prevMuted;
1783
+ setHasPlayedAhead(true);
1784
+ };
1766
1785
  const doPlayAhead = async () => {
1767
1786
  try {
1768
1787
  await video.play();
1769
1788
  if (cancelled) return;
1770
- const pauseAfterDecode = () => {
1771
- if (cancelled) return;
1772
- video.pause();
1773
- video.currentTime = 0;
1774
- video.muted = prevMuted;
1775
- setHasPlayedAhead(true);
1776
- };
1777
- setTimeout(pauseAfterDecode, 50);
1789
+ if ("requestVideoFrameCallback" in video) {
1790
+ vfcHandle = video.requestVideoFrameCallback(() => {
1791
+ vfcHandle = null;
1792
+ pauseAfterDecode();
1793
+ });
1794
+ } else {
1795
+ rafId = requestAnimationFrame(() => {
1796
+ rafId = requestAnimationFrame(() => {
1797
+ rafId = null;
1798
+ pauseAfterDecode();
1799
+ });
1800
+ });
1801
+ }
1778
1802
  } catch {
1803
+ video.muted = prevMuted;
1779
1804
  }
1780
1805
  };
1781
1806
  doPlayAhead();
1782
1807
  return () => {
1783
1808
  cancelled = true;
1809
+ if (rafId !== null) {
1810
+ cancelAnimationFrame(rafId);
1811
+ rafId = null;
1812
+ }
1813
+ if (vfcHandle !== null && "cancelVideoFrameCallback" in video) {
1814
+ video.cancelVideoFrameCallback(vfcHandle);
1815
+ vfcHandle = null;
1816
+ }
1784
1817
  };
1785
1818
  }, [canPlayAhead, isActive, isReady, hasPlayedAhead]);
1786
1819
  useEffect(() => {
@@ -2078,6 +2111,8 @@ function ReelsFeed({
2078
2111
  }) {
2079
2112
  const { items, loading, loadInitial, loadMore, hasMore } = useFeed();
2080
2113
  const {
2114
+ activeIndices,
2115
+ warmIndices,
2081
2116
  focusedIndex,
2082
2117
  prefetchIndex,
2083
2118
  setFocusedIndexImmediate,
@@ -2271,43 +2306,54 @@ function ReelsFeed({
2271
2306
  to { transform: rotate(360deg); }
2272
2307
  }
2273
2308
  ` }),
2274
- items.map((item, index) => {
2275
- const isActive = index === focusedIndex;
2276
- const isPrefetch = index === prefetchIndex;
2277
- const isWarm = isWarmAllocated(index);
2278
- const isVisible = shouldRenderVideo(index) || isPrefetch;
2279
- const bufferTier = isActive ? "active" : isWarm ? "warm" : "hot";
2280
- return /* @__PURE__ */ jsx(
2281
- "div",
2282
- {
2283
- "data-slot-index": index,
2284
- style: {
2285
- position: "absolute",
2286
- inset: 0,
2287
- willChange: "transform",
2288
- transform: getInitialTransformPx(index)
2309
+ (() => {
2310
+ const windowIndices = /* @__PURE__ */ new Set([
2311
+ ...activeIndices,
2312
+ ...warmIndices
2313
+ ]);
2314
+ if (prefetchIndex !== null && prefetchIndex !== void 0) {
2315
+ windowIndices.add(prefetchIndex);
2316
+ }
2317
+ return [...windowIndices].map((index) => {
2318
+ const item = items[index];
2319
+ if (!item) return null;
2320
+ const isActive = index === focusedIndex;
2321
+ const isPrefetchSlot = index === prefetchIndex;
2322
+ const isWarm = isWarmAllocated(index);
2323
+ const isVisible = shouldRenderVideo(index) || isPrefetchSlot;
2324
+ const bufferTier = isActive ? "active" : isWarm ? "warm" : "hot";
2325
+ return /* @__PURE__ */ jsx(
2326
+ "div",
2327
+ {
2328
+ "data-slot-index": index,
2329
+ style: {
2330
+ position: "absolute",
2331
+ inset: 0,
2332
+ willChange: "transform",
2333
+ transform: getInitialTransformPx(index)
2334
+ },
2335
+ children: isVisible ? /* @__PURE__ */ jsx(
2336
+ VideoSlot,
2337
+ {
2338
+ item,
2339
+ index,
2340
+ isActive,
2341
+ isPrefetch: isPrefetchSlot,
2342
+ isPreloaded: !isActive && !isPrefetchSlot && isVisible,
2343
+ bufferTier,
2344
+ isMuted,
2345
+ onToggleMute: handleToggleMute,
2346
+ showFps: showFps && isActive,
2347
+ renderOverlay,
2348
+ renderActions,
2349
+ renderPauseIndicator
2350
+ }
2351
+ ) : null
2289
2352
  },
2290
- children: /* @__PURE__ */ jsx(
2291
- VideoSlot,
2292
- {
2293
- item,
2294
- index,
2295
- isActive,
2296
- isPrefetch,
2297
- isPreloaded: !isActive && !isPrefetch && isVisible,
2298
- bufferTier,
2299
- isMuted,
2300
- onToggleMute: handleToggleMute,
2301
- showFps: showFps && isActive,
2302
- renderOverlay,
2303
- renderActions,
2304
- renderPauseIndicator
2305
- }
2306
- )
2307
- },
2308
- item.id
2309
- );
2310
- })
2353
+ item.id
2354
+ );
2355
+ });
2356
+ })()
2311
2357
  ]
2312
2358
  }
2313
2359
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xhub-reels/sdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "High-performance Short Video / Reels SDK for React — optimized for Flutter WebView",
5
5
  "license": "MIT",
6
6
  "type": "module",