@xhub-reels/sdk 0.1.11 → 0.1.13

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) {
@@ -1417,6 +1428,9 @@ function useHls(options) {
1417
1428
  video.addEventListener("canplay", handleCanPlayReuse, { once: true });
1418
1429
  video.addEventListener("loadeddata", handleCanPlayReuse, { once: true });
1419
1430
  video.addEventListener("playing", handleCanPlayReuse, { once: true });
1431
+ if (isActive && video.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) {
1432
+ video.load();
1433
+ }
1420
1434
  return () => {
1421
1435
  video.removeEventListener("canplay", handleCanPlayReuse);
1422
1436
  video.removeEventListener("loadeddata", handleCanPlayReuse);
@@ -1441,7 +1455,6 @@ function useHls(options) {
1441
1455
  };
1442
1456
  }
1443
1457
  if (!isHlsSupported) {
1444
- onErrorRef.current?.("UNKNOWN", "HLS playback not supported in this browser");
1445
1458
  return;
1446
1459
  }
1447
1460
  if (hlsRef.current && currentSrcRef.current === src) {
@@ -1505,7 +1518,7 @@ function useHls(options) {
1505
1518
  currentSrcRef.current = void 0;
1506
1519
  }
1507
1520
  };
1508
- }, [src, isActive, isPrefetch]);
1521
+ }, [src, isActive, isPrefetch, isHlsJs, isNativeHls]);
1509
1522
  react.useEffect(() => {
1510
1523
  const hls = hlsRef.current;
1511
1524
  if (!hls) {
@@ -1524,7 +1537,7 @@ function useHls(options) {
1524
1537
  }, [bufferTier]);
1525
1538
  return {
1526
1539
  isHlsJs,
1527
- isNativeHls: isNative,
1540
+ isNativeHls,
1528
1541
  isReady,
1529
1542
  destroy
1530
1543
  };
@@ -1769,24 +1782,47 @@ function VideoSlotInner({
1769
1782
  const prevMuted = video.muted;
1770
1783
  video.muted = true;
1771
1784
  let cancelled = false;
1785
+ let rafId = null;
1786
+ let vfcHandle = null;
1787
+ const pauseAfterDecode = () => {
1788
+ if (cancelled) return;
1789
+ video.pause();
1790
+ video.currentTime = 0;
1791
+ video.muted = prevMuted;
1792
+ setHasPlayedAhead(true);
1793
+ };
1772
1794
  const doPlayAhead = async () => {
1773
1795
  try {
1774
1796
  await video.play();
1775
1797
  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);
1798
+ if ("requestVideoFrameCallback" in video) {
1799
+ vfcHandle = video.requestVideoFrameCallback(() => {
1800
+ vfcHandle = null;
1801
+ pauseAfterDecode();
1802
+ });
1803
+ } else {
1804
+ rafId = requestAnimationFrame(() => {
1805
+ rafId = requestAnimationFrame(() => {
1806
+ rafId = null;
1807
+ pauseAfterDecode();
1808
+ });
1809
+ });
1810
+ }
1784
1811
  } catch {
1812
+ video.muted = prevMuted;
1785
1813
  }
1786
1814
  };
1787
1815
  doPlayAhead();
1788
1816
  return () => {
1789
1817
  cancelled = true;
1818
+ if (rafId !== null) {
1819
+ cancelAnimationFrame(rafId);
1820
+ rafId = null;
1821
+ }
1822
+ if (vfcHandle !== null && "cancelVideoFrameCallback" in video) {
1823
+ video.cancelVideoFrameCallback(vfcHandle);
1824
+ vfcHandle = null;
1825
+ }
1790
1826
  };
1791
1827
  }, [canPlayAhead, isActive, isReady, hasPlayedAhead]);
1792
1828
  react.useEffect(() => {
@@ -1797,6 +1833,7 @@ function VideoSlotInner({
1797
1833
  const video = videoRef.current;
1798
1834
  if (!video) return;
1799
1835
  let onReady = null;
1836
+ let fallbackTimerId = null;
1800
1837
  if (isActive) {
1801
1838
  wasActiveRef.current = true;
1802
1839
  const startPlay = () => {
@@ -1806,6 +1843,10 @@ function VideoSlotInner({
1806
1843
  video.removeEventListener("playing", onReady);
1807
1844
  onReady = null;
1808
1845
  }
1846
+ if (fallbackTimerId !== null) {
1847
+ clearTimeout(fallbackTimerId);
1848
+ fallbackTimerId = null;
1849
+ }
1809
1850
  video.muted = true;
1810
1851
  video.play().then(() => {
1811
1852
  video.muted = isMuted;
@@ -1820,9 +1861,15 @@ function VideoSlotInner({
1820
1861
  video.addEventListener("canplay", onReady, { once: true });
1821
1862
  video.addEventListener("loadeddata", onReady, { once: true });
1822
1863
  video.addEventListener("playing", onReady, { once: true });
1823
- if (video.readyState === HTMLMediaElement.HAVE_NOTHING && isNativeHls && video.src) {
1864
+ if (isNativeHls && video.src && video.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) {
1824
1865
  video.load();
1825
1866
  }
1867
+ fallbackTimerId = window.setTimeout(() => {
1868
+ fallbackTimerId = null;
1869
+ if (onReady) {
1870
+ startPlay();
1871
+ }
1872
+ }, 3e3);
1826
1873
  }
1827
1874
  } else if (wasActiveRef.current) {
1828
1875
  video.pause();
@@ -1838,13 +1885,21 @@ function VideoSlotInner({
1838
1885
  video.removeEventListener("loadeddata", onReady);
1839
1886
  video.removeEventListener("playing", onReady);
1840
1887
  }
1888
+ if (fallbackTimerId !== null) {
1889
+ clearTimeout(fallbackTimerId);
1890
+ fallbackTimerId = null;
1891
+ }
1841
1892
  };
1842
1893
  }, [isActive, isMuted, hasPlayedAhead, isNativeHls]);
1843
1894
  react.useEffect(() => {
1844
1895
  const video = videoRef.current;
1845
1896
  if (!video) return;
1846
- video.muted = isMuted;
1847
- }, [isMuted]);
1897
+ if (isActive) {
1898
+ video.muted = isMuted;
1899
+ } else {
1900
+ video.muted = true;
1901
+ }
1902
+ }, [isMuted, isActive]);
1848
1903
  const [isActuallyPlaying, setIsActuallyPlaying] = react.useState(false);
1849
1904
  react.useEffect(() => {
1850
1905
  const video = videoRef.current;
@@ -1922,8 +1977,9 @@ function VideoSlotInner({
1922
1977
  ref: videoRef,
1923
1978
  src: mp4Src,
1924
1979
  loop: true,
1925
- muted: isMuted,
1980
+ muted: true,
1926
1981
  playsInline: true,
1982
+ autoPlay: isActive,
1927
1983
  preload: shouldLoadSrc ? "auto" : "none",
1928
1984
  style: {
1929
1985
  width: "100%",
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) {
@@ -1411,6 +1422,9 @@ function useHls(options) {
1411
1422
  video.addEventListener("canplay", handleCanPlayReuse, { once: true });
1412
1423
  video.addEventListener("loadeddata", handleCanPlayReuse, { once: true });
1413
1424
  video.addEventListener("playing", handleCanPlayReuse, { once: true });
1425
+ if (isActive && video.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) {
1426
+ video.load();
1427
+ }
1414
1428
  return () => {
1415
1429
  video.removeEventListener("canplay", handleCanPlayReuse);
1416
1430
  video.removeEventListener("loadeddata", handleCanPlayReuse);
@@ -1435,7 +1449,6 @@ function useHls(options) {
1435
1449
  };
1436
1450
  }
1437
1451
  if (!isHlsSupported) {
1438
- onErrorRef.current?.("UNKNOWN", "HLS playback not supported in this browser");
1439
1452
  return;
1440
1453
  }
1441
1454
  if (hlsRef.current && currentSrcRef.current === src) {
@@ -1499,7 +1512,7 @@ function useHls(options) {
1499
1512
  currentSrcRef.current = void 0;
1500
1513
  }
1501
1514
  };
1502
- }, [src, isActive, isPrefetch]);
1515
+ }, [src, isActive, isPrefetch, isHlsJs, isNativeHls]);
1503
1516
  useEffect(() => {
1504
1517
  const hls = hlsRef.current;
1505
1518
  if (!hls) {
@@ -1518,7 +1531,7 @@ function useHls(options) {
1518
1531
  }, [bufferTier]);
1519
1532
  return {
1520
1533
  isHlsJs,
1521
- isNativeHls: isNative,
1534
+ isNativeHls,
1522
1535
  isReady,
1523
1536
  destroy
1524
1537
  };
@@ -1763,24 +1776,47 @@ function VideoSlotInner({
1763
1776
  const prevMuted = video.muted;
1764
1777
  video.muted = true;
1765
1778
  let cancelled = false;
1779
+ let rafId = null;
1780
+ let vfcHandle = null;
1781
+ const pauseAfterDecode = () => {
1782
+ if (cancelled) return;
1783
+ video.pause();
1784
+ video.currentTime = 0;
1785
+ video.muted = prevMuted;
1786
+ setHasPlayedAhead(true);
1787
+ };
1766
1788
  const doPlayAhead = async () => {
1767
1789
  try {
1768
1790
  await video.play();
1769
1791
  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);
1792
+ if ("requestVideoFrameCallback" in video) {
1793
+ vfcHandle = video.requestVideoFrameCallback(() => {
1794
+ vfcHandle = null;
1795
+ pauseAfterDecode();
1796
+ });
1797
+ } else {
1798
+ rafId = requestAnimationFrame(() => {
1799
+ rafId = requestAnimationFrame(() => {
1800
+ rafId = null;
1801
+ pauseAfterDecode();
1802
+ });
1803
+ });
1804
+ }
1778
1805
  } catch {
1806
+ video.muted = prevMuted;
1779
1807
  }
1780
1808
  };
1781
1809
  doPlayAhead();
1782
1810
  return () => {
1783
1811
  cancelled = true;
1812
+ if (rafId !== null) {
1813
+ cancelAnimationFrame(rafId);
1814
+ rafId = null;
1815
+ }
1816
+ if (vfcHandle !== null && "cancelVideoFrameCallback" in video) {
1817
+ video.cancelVideoFrameCallback(vfcHandle);
1818
+ vfcHandle = null;
1819
+ }
1784
1820
  };
1785
1821
  }, [canPlayAhead, isActive, isReady, hasPlayedAhead]);
1786
1822
  useEffect(() => {
@@ -1791,6 +1827,7 @@ function VideoSlotInner({
1791
1827
  const video = videoRef.current;
1792
1828
  if (!video) return;
1793
1829
  let onReady = null;
1830
+ let fallbackTimerId = null;
1794
1831
  if (isActive) {
1795
1832
  wasActiveRef.current = true;
1796
1833
  const startPlay = () => {
@@ -1800,6 +1837,10 @@ function VideoSlotInner({
1800
1837
  video.removeEventListener("playing", onReady);
1801
1838
  onReady = null;
1802
1839
  }
1840
+ if (fallbackTimerId !== null) {
1841
+ clearTimeout(fallbackTimerId);
1842
+ fallbackTimerId = null;
1843
+ }
1803
1844
  video.muted = true;
1804
1845
  video.play().then(() => {
1805
1846
  video.muted = isMuted;
@@ -1814,9 +1855,15 @@ function VideoSlotInner({
1814
1855
  video.addEventListener("canplay", onReady, { once: true });
1815
1856
  video.addEventListener("loadeddata", onReady, { once: true });
1816
1857
  video.addEventListener("playing", onReady, { once: true });
1817
- if (video.readyState === HTMLMediaElement.HAVE_NOTHING && isNativeHls && video.src) {
1858
+ if (isNativeHls && video.src && video.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) {
1818
1859
  video.load();
1819
1860
  }
1861
+ fallbackTimerId = window.setTimeout(() => {
1862
+ fallbackTimerId = null;
1863
+ if (onReady) {
1864
+ startPlay();
1865
+ }
1866
+ }, 3e3);
1820
1867
  }
1821
1868
  } else if (wasActiveRef.current) {
1822
1869
  video.pause();
@@ -1832,13 +1879,21 @@ function VideoSlotInner({
1832
1879
  video.removeEventListener("loadeddata", onReady);
1833
1880
  video.removeEventListener("playing", onReady);
1834
1881
  }
1882
+ if (fallbackTimerId !== null) {
1883
+ clearTimeout(fallbackTimerId);
1884
+ fallbackTimerId = null;
1885
+ }
1835
1886
  };
1836
1887
  }, [isActive, isMuted, hasPlayedAhead, isNativeHls]);
1837
1888
  useEffect(() => {
1838
1889
  const video = videoRef.current;
1839
1890
  if (!video) return;
1840
- video.muted = isMuted;
1841
- }, [isMuted]);
1891
+ if (isActive) {
1892
+ video.muted = isMuted;
1893
+ } else {
1894
+ video.muted = true;
1895
+ }
1896
+ }, [isMuted, isActive]);
1842
1897
  const [isActuallyPlaying, setIsActuallyPlaying] = useState(false);
1843
1898
  useEffect(() => {
1844
1899
  const video = videoRef.current;
@@ -1916,8 +1971,9 @@ function VideoSlotInner({
1916
1971
  ref: videoRef,
1917
1972
  src: mp4Src,
1918
1973
  loop: true,
1919
- muted: isMuted,
1974
+ muted: true,
1920
1975
  playsInline: true,
1976
+ autoPlay: isActive,
1921
1977
  preload: shouldLoadSrc ? "auto" : "none",
1922
1978
  style: {
1923
1979
  width: "100%",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xhub-reels/sdk",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "High-performance Short Video / Reels SDK for React — optimized for Flutter WebView",
5
5
  "license": "MIT",
6
6
  "type": "module",