stormcloud-video-player 0.2.0 → 0.2.1

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/lib/index.cjs CHANGED
@@ -36,11 +36,12 @@ __export(index_exports, {
36
36
  IS_SAFARI: () => IS_SAFARI,
37
37
  SUPPORTS_DASH: () => SUPPORTS_DASH,
38
38
  SUPPORTS_HLS: () => SUPPORTS_HLS,
39
+ StormcloudPlayer: () => StormcloudPlayer_default,
39
40
  StormcloudVideoPlayer: () => StormcloudVideoPlayer,
40
41
  StormcloudVideoPlayerComponent: () => StormcloudVideoPlayerComponent,
41
42
  canPlay: () => canPlay,
42
43
  createStormcloudPlayer: () => createStormcloudPlayer,
43
- default: () => StormcloudPlayer_default,
44
+ default: () => StormcloudVideoPlayerComponent,
44
45
  getBrowserID: () => getBrowserID,
45
46
  getClientInfo: () => getClientInfo,
46
47
  isMediaStream: () => isMediaStream,
@@ -56,8 +57,8 @@ __export(index_exports, {
56
57
  });
57
58
  module.exports = __toCommonJS(index_exports);
58
59
 
59
- // src/StormcloudPlayer.tsx
60
- var import_react5 = __toESM(require("react"), 1);
60
+ // src/ui/StormcloudVideoPlayer.tsx
61
+ var import_react = __toESM(require("react"), 1);
61
62
 
62
63
  // src/player/StormcloudVideoPlayer.ts
63
64
  var import_hls = __toESM(require("hls.js"), 1);
@@ -1690,1185 +1691,301 @@ var StormcloudVideoPlayer = class {
1690
1691
  }
1691
1692
  };
1692
1693
 
1693
- // src/props.ts
1694
- var noop = () => {
1695
- };
1696
- var defaultProps = {
1697
- playing: false,
1698
- loop: false,
1699
- controls: true,
1700
- volume: 1,
1701
- muted: false,
1702
- playbackRate: 1,
1703
- width: "100%",
1704
- height: "auto",
1705
- style: {},
1706
- progressInterval: 1e3,
1707
- playsInline: false,
1708
- autoplay: false,
1709
- preload: "metadata",
1710
- poster: "",
1711
- className: "",
1712
- wrapperClassName: "",
1713
- wrapperStyle: {},
1714
- allowNativeHls: false,
1715
- lowLatencyMode: false,
1716
- driftToleranceMs: 1e3,
1717
- immediateManifestAds: true,
1718
- debugAdTiming: false,
1719
- showCustomControls: false,
1720
- licenseKey: "",
1721
- adFailsafeTimeoutMs: 1e4,
1722
- onStart: noop,
1723
- onPlay: noop,
1724
- onPause: noop,
1725
- onBuffer: noop,
1726
- onBufferEnd: noop,
1727
- onEnded: noop,
1728
- onError: noop,
1729
- onDuration: noop,
1730
- onSeek: noop,
1731
- onProgress: noop,
1732
- onVolumeToggle: noop,
1733
- onFullscreenToggle: noop,
1734
- onControlClick: noop
1735
- };
1736
-
1737
- // src/utils.ts
1738
- var import_react = require("react");
1739
- var lazy = import_react.lazy;
1740
- var omit = (object, keys) => {
1741
- const result = { ...object };
1742
- keys.forEach((key) => {
1743
- delete result[key];
1744
- });
1745
- return result;
1746
- };
1747
- var isMediaStream = (url) => {
1748
- return typeof window !== "undefined" && window.MediaStream && url instanceof window.MediaStream;
1749
- };
1750
- var supportsWebKitPresentationMode = () => {
1751
- if (typeof window === "undefined") return false;
1752
- const video = document.createElement("video");
1753
- return "webkitSupportsPresentationMode" in video;
1754
- };
1755
- var randomString = () => {
1756
- return Math.random().toString(36).substr(2, 9);
1757
- };
1758
- var parseQuery = (url) => {
1759
- const query = {};
1760
- const params = new URLSearchParams(url.split("?")[1] || "");
1761
- params.forEach((value, key) => {
1762
- query[key] = value;
1763
- });
1764
- return query;
1765
- };
1766
- var merge = (target, ...sources) => {
1767
- if (!sources.length) return target;
1768
- const source = sources.shift();
1769
- if (isObject(target) && isObject(source)) {
1770
- for (const key in source) {
1771
- if (isObject(source[key])) {
1772
- if (!target[key]) Object.assign(target, { [key]: {} });
1773
- merge(target[key], source[key]);
1774
- } else {
1775
- Object.assign(target, { [key]: source[key] });
1776
- }
1777
- }
1778
- }
1779
- return merge(target, ...sources);
1780
- };
1781
- var isObject = (item) => {
1782
- return item && typeof item === "object" && !Array.isArray(item);
1783
- };
1784
- var IS_BROWSER = typeof window !== "undefined" && window.document;
1785
- var IS_GLOBAL = typeof globalThis !== "undefined" && globalThis.window && globalThis.window.document;
1786
- var IS_IOS = IS_BROWSER && /iPad|iPhone|iPod/.test(navigator.userAgent);
1787
- var IS_SAFARI = IS_BROWSER && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
1788
- var SUPPORTS_HLS = () => {
1789
- if (!IS_BROWSER) return false;
1790
- const video = document.createElement("video");
1791
- return Boolean(video.canPlayType("application/vnd.apple.mpegurl"));
1792
- };
1793
- var SUPPORTS_DASH = () => {
1794
- if (!IS_BROWSER) return false;
1795
- const video = document.createElement("video");
1796
- return Boolean(video.canPlayType("application/dash+xml"));
1797
- };
1798
-
1799
- // src/patterns.ts
1800
- var HLS_EXTENSIONS = /\.(m3u8)($|\?)/i;
1801
- var HLS_PATHS = /\/hls\//i;
1802
- var DASH_EXTENSIONS = /\.(mpd)($|\?)/i;
1803
- var VIDEO_EXTENSIONS = /\.(mp4|webm|ogg|avi|mov|wmv|flv|mkv)($|\?)/i;
1804
- var AUDIO_EXTENSIONS = /\.(mp3|wav|ogg|aac|wma|flac|m4a)($|\?)/i;
1805
- var canPlay = {
1806
- hls: (url) => {
1807
- if (!url || typeof url !== "string") return false;
1808
- return HLS_EXTENSIONS.test(url) || HLS_PATHS.test(url);
1809
- },
1810
- dash: (url) => {
1811
- if (!url || typeof url !== "string") return false;
1812
- return DASH_EXTENSIONS.test(url);
1813
- },
1814
- video: (url) => {
1815
- if (!url || typeof url !== "string") return false;
1816
- return VIDEO_EXTENSIONS.test(url);
1817
- },
1818
- audio: (url) => {
1819
- if (!url || typeof url !== "string") return false;
1820
- return AUDIO_EXTENSIONS.test(url);
1821
- },
1822
- file: (url) => {
1823
- if (!url || typeof url !== "string") return false;
1824
- return VIDEO_EXTENSIONS.test(url) || AUDIO_EXTENSIONS.test(url);
1825
- }
1826
- };
1827
-
1828
- // src/players/HlsPlayer.tsx
1829
- var import_react2 = require("react");
1830
- var HlsPlayer = class extends import_react2.Component {
1831
- constructor() {
1832
- super(...arguments);
1833
- this.player = null;
1834
- this.mounted = false;
1835
- this.load = async () => {
1836
- if (!this.props.videoElement || !this.props.src) return;
1837
- try {
1838
- if (this.player) {
1839
- this.player.destroy();
1840
- this.player = null;
1841
- }
1842
- const config = {
1843
- src: this.props.src,
1844
- videoElement: this.props.videoElement
1845
- };
1846
- if (this.props.autoplay !== void 0)
1847
- config.autoplay = this.props.autoplay;
1848
- if (this.props.muted !== void 0) config.muted = this.props.muted;
1849
- if (this.props.lowLatencyMode !== void 0)
1850
- config.lowLatencyMode = this.props.lowLatencyMode;
1851
- if (this.props.allowNativeHls !== void 0)
1852
- config.allowNativeHls = this.props.allowNativeHls;
1853
- if (this.props.driftToleranceMs !== void 0)
1854
- config.driftToleranceMs = this.props.driftToleranceMs;
1855
- if (this.props.immediateManifestAds !== void 0)
1856
- config.immediateManifestAds = this.props.immediateManifestAds;
1857
- if (this.props.debugAdTiming !== void 0)
1858
- config.debugAdTiming = this.props.debugAdTiming;
1859
- if (this.props.showCustomControls !== void 0)
1860
- config.showCustomControls = this.props.showCustomControls;
1861
- if (this.props.onVolumeToggle !== void 0)
1862
- config.onVolumeToggle = this.props.onVolumeToggle;
1863
- if (this.props.onFullscreenToggle !== void 0)
1864
- config.onFullscreenToggle = this.props.onFullscreenToggle;
1865
- if (this.props.onControlClick !== void 0)
1866
- config.onControlClick = this.props.onControlClick;
1867
- if (this.props.licenseKey !== void 0)
1868
- config.licenseKey = this.props.licenseKey;
1869
- if (this.props.adFailsafeTimeoutMs !== void 0)
1870
- config.adFailsafeTimeoutMs = this.props.adFailsafeTimeoutMs;
1871
- this.player = new StormcloudVideoPlayer(config);
1872
- this.props.onMount?.(this);
1873
- await this.player.load();
1874
- if (this.mounted) {
1875
- this.props.onReady?.();
1876
- }
1877
- } catch (error) {
1878
- if (this.mounted) {
1879
- this.props.onError?.(error);
1880
- }
1881
- }
1694
+ // src/ui/StormcloudVideoPlayer.tsx
1695
+ var import_fa = require("react-icons/fa");
1696
+ var import_jsx_runtime = require("react/jsx-runtime");
1697
+ var CRITICAL_PROPS = [
1698
+ "src",
1699
+ "allowNativeHls",
1700
+ "licenseKey",
1701
+ "lowLatencyMode",
1702
+ "driftToleranceMs"
1703
+ ];
1704
+ var StormcloudVideoPlayerComponent = import_react.default.memo(
1705
+ (props) => {
1706
+ const {
1707
+ src,
1708
+ autoplay,
1709
+ muted,
1710
+ lowLatencyMode,
1711
+ allowNativeHls,
1712
+ driftToleranceMs,
1713
+ immediateManifestAds,
1714
+ debugAdTiming,
1715
+ showCustomControls,
1716
+ onVolumeToggle,
1717
+ onFullscreenToggle,
1718
+ onControlClick,
1719
+ onReady,
1720
+ wrapperClassName,
1721
+ wrapperStyle,
1722
+ className,
1723
+ style,
1724
+ controls,
1725
+ playsInline,
1726
+ preload,
1727
+ poster,
1728
+ children,
1729
+ licenseKey,
1730
+ ...restVideoAttrs
1731
+ } = props;
1732
+ const videoRef = (0, import_react.useRef)(null);
1733
+ const playerRef = (0, import_react.useRef)(null);
1734
+ const [adStatus, setAdStatus] = import_react.default.useState({ showAds: false, currentIndex: 0, totalAds: 0 });
1735
+ const [shouldShowNativeControls, setShouldShowNativeControls] = import_react.default.useState(true);
1736
+ const [isMuted, setIsMuted] = import_react.default.useState(false);
1737
+ const [isFullscreen, setIsFullscreen] = import_react.default.useState(false);
1738
+ const [isPlaying, setIsPlaying] = import_react.default.useState(false);
1739
+ const [currentTime, setCurrentTime] = import_react.default.useState(0);
1740
+ const [duration, setDuration] = import_react.default.useState(0);
1741
+ const [volume, setVolume] = import_react.default.useState(1);
1742
+ const [playbackRate, setPlaybackRate] = import_react.default.useState(1);
1743
+ const [showVolumeSlider, setShowVolumeSlider] = import_react.default.useState(false);
1744
+ const [showSpeedMenu, setShowSpeedMenu] = import_react.default.useState(false);
1745
+ const [isLoading, setIsLoading] = import_react.default.useState(true);
1746
+ const [isBuffering, setIsBuffering] = import_react.default.useState(false);
1747
+ const [showCenterPlay, setShowCenterPlay] = import_react.default.useState(false);
1748
+ const formatTime = (seconds) => {
1749
+ if (!isFinite(seconds)) return "0:00:00";
1750
+ const hours = Math.floor(seconds / 3600);
1751
+ const minutes = Math.floor(seconds % 3600 / 60);
1752
+ const remainingSeconds = Math.floor(seconds % 60);
1753
+ return `${hours}:${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
1882
1754
  };
1883
- this.play = () => {
1884
- if (this.props.videoElement) {
1885
- this.props.videoElement.play();
1886
- this.props.onPlay?.();
1755
+ const handlePlayPause = () => {
1756
+ if (videoRef.current) {
1757
+ if (videoRef.current.paused) {
1758
+ videoRef.current.play();
1759
+ setShowCenterPlay(false);
1760
+ } else {
1761
+ videoRef.current.pause();
1762
+ setShowCenterPlay(true);
1763
+ }
1887
1764
  }
1888
1765
  };
1889
- this.pause = () => {
1890
- if (this.props.videoElement) {
1891
- this.props.videoElement.pause();
1892
- this.props.onPause?.();
1766
+ const handleCenterPlayClick = () => {
1767
+ if (videoRef.current && videoRef.current.paused) {
1768
+ videoRef.current.play();
1769
+ setShowCenterPlay(false);
1893
1770
  }
1894
1771
  };
1895
- this.stop = () => {
1896
- this.pause();
1897
- if (this.props.videoElement) {
1898
- this.props.videoElement.currentTime = 0;
1899
- }
1900
- };
1901
- this.seekTo = (seconds, keepPlaying) => {
1902
- if (this.props.videoElement) {
1903
- this.props.videoElement.currentTime = seconds;
1904
- if (!keepPlaying) {
1905
- this.pause();
1772
+ const handleTimelineSeek = (e) => {
1773
+ if (videoRef.current && duration > 0 && isFinite(duration)) {
1774
+ const rect = e.currentTarget.getBoundingClientRect();
1775
+ const clickX = e.clientX - rect.left;
1776
+ const progress = Math.max(0, Math.min(1, clickX / rect.width));
1777
+ const newTime = progress * duration;
1778
+ if (isFinite(newTime) && newTime >= 0 && newTime <= duration) {
1779
+ videoRef.current.currentTime = newTime;
1906
1780
  }
1907
1781
  }
1908
1782
  };
1909
- this.setVolume = (volume) => {
1910
- if (this.props.videoElement) {
1911
- this.props.videoElement.volume = Math.max(0, Math.min(1, volume));
1912
- }
1913
- };
1914
- this.mute = () => {
1915
- if (this.props.videoElement) {
1916
- this.props.videoElement.muted = true;
1917
- }
1918
- };
1919
- this.unmute = () => {
1920
- if (this.props.videoElement) {
1921
- this.props.videoElement.muted = false;
1922
- }
1923
- };
1924
- this.setPlaybackRate = (rate) => {
1925
- if (this.props.videoElement && rate > 0) {
1926
- this.props.videoElement.playbackRate = rate;
1783
+ const handleVolumeChange = (newVolume) => {
1784
+ if (videoRef.current && isFinite(newVolume)) {
1785
+ const clampedVolume = Math.max(0, Math.min(1, newVolume));
1786
+ videoRef.current.volume = clampedVolume;
1787
+ videoRef.current.muted = clampedVolume === 0;
1927
1788
  }
1928
1789
  };
1929
- this.getDuration = () => {
1930
- if (this.props.videoElement && isFinite(this.props.videoElement.duration)) {
1931
- return this.props.videoElement.duration;
1790
+ const handlePlaybackRateChange = (rate) => {
1791
+ if (videoRef.current && isFinite(rate) && rate > 0) {
1792
+ videoRef.current.playbackRate = rate;
1932
1793
  }
1933
- return null;
1794
+ setShowSpeedMenu(false);
1934
1795
  };
1935
- this.getCurrentTime = () => {
1936
- if (this.props.videoElement && isFinite(this.props.videoElement.currentTime)) {
1937
- return this.props.videoElement.currentTime;
1796
+ const isHlsStream = src?.toLowerCase().includes(".m3u8") || src?.toLowerCase().includes("/hls/");
1797
+ const shouldShowEnhancedControls = showCustomControls && (isHlsStream ? allowNativeHls : true);
1798
+ const criticalPropsKey = (0, import_react.useMemo)(() => {
1799
+ return CRITICAL_PROPS.map((prop) => `${prop}:${props[prop]}`).join("|");
1800
+ }, [src, allowNativeHls, licenseKey, lowLatencyMode, driftToleranceMs]);
1801
+ (0, import_react.useEffect)(() => {
1802
+ if (typeof window === "undefined") return;
1803
+ const el = videoRef.current;
1804
+ if (!el || !src) return;
1805
+ if (playerRef.current) {
1806
+ try {
1807
+ playerRef.current.destroy();
1808
+ } catch {
1809
+ }
1810
+ playerRef.current = null;
1938
1811
  }
1939
- return null;
1940
- };
1941
- this.getSecondsLoaded = () => {
1942
- if (this.props.videoElement && this.props.videoElement.buffered.length > 0) {
1943
- return this.props.videoElement.buffered.end(
1944
- this.props.videoElement.buffered.length - 1
1812
+ const cfg = {
1813
+ src,
1814
+ videoElement: el
1815
+ };
1816
+ if (autoplay !== void 0) cfg.autoplay = autoplay;
1817
+ if (muted !== void 0) cfg.muted = muted;
1818
+ if (lowLatencyMode !== void 0) cfg.lowLatencyMode = lowLatencyMode;
1819
+ if (allowNativeHls !== void 0) cfg.allowNativeHls = allowNativeHls;
1820
+ if (driftToleranceMs !== void 0)
1821
+ cfg.driftToleranceMs = driftToleranceMs;
1822
+ if (immediateManifestAds !== void 0)
1823
+ cfg.immediateManifestAds = immediateManifestAds;
1824
+ if (debugAdTiming !== void 0) cfg.debugAdTiming = debugAdTiming;
1825
+ if (showCustomControls !== void 0)
1826
+ cfg.showCustomControls = showCustomControls;
1827
+ if (onVolumeToggle !== void 0) cfg.onVolumeToggle = onVolumeToggle;
1828
+ if (onFullscreenToggle !== void 0)
1829
+ cfg.onFullscreenToggle = onFullscreenToggle;
1830
+ if (onControlClick !== void 0) cfg.onControlClick = onControlClick;
1831
+ if (licenseKey !== void 0) cfg.licenseKey = licenseKey;
1832
+ const player = new StormcloudVideoPlayer(cfg);
1833
+ playerRef.current = player;
1834
+ player.load().then(() => {
1835
+ const showNative = player.shouldShowNativeControls();
1836
+ setShouldShowNativeControls(showNative);
1837
+ onReady?.(player);
1838
+ }).catch((error) => {
1839
+ console.error(
1840
+ "StormcloudVideoPlayer: Failed to load player:",
1841
+ error
1945
1842
  );
1946
- }
1947
- return null;
1948
- };
1949
- this.getInternalPlayer = (key = "player") => {
1950
- if (key === "player") return this.player;
1951
- if (key === "video") return this.props.videoElement;
1952
- if (key === "hls" && this.player) return this.player.hls;
1953
- return null;
1954
- };
1955
- }
1956
- componentDidMount() {
1957
- this.mounted = true;
1958
- this.load();
1959
- }
1960
- componentWillUnmount() {
1961
- this.mounted = false;
1962
- if (this.player) {
1963
- this.player.destroy();
1964
- this.player = null;
1965
- }
1966
- }
1967
- componentDidUpdate(prevProps) {
1968
- if (prevProps.src !== this.props.src) {
1969
- this.load();
1970
- }
1971
- }
1972
- render() {
1973
- return null;
1974
- }
1975
- };
1976
- HlsPlayer.displayName = "HlsPlayer";
1977
- HlsPlayer.canPlay = canPlay.hls;
1978
-
1979
- // src/players/FilePlayer.tsx
1980
- var import_react3 = require("react");
1981
- var FilePlayer = class extends import_react3.Component {
1982
- constructor() {
1983
- super(...arguments);
1984
- this.mounted = false;
1985
- this.ready = false;
1986
- this.load = () => {
1987
- if (!this.props.videoElement || !this.props.src) return;
1988
- const video = this.props.videoElement;
1989
- const handleLoadedMetadata = () => {
1990
- if (this.mounted && !this.ready) {
1991
- this.ready = true;
1992
- this.props.onReady?.();
1843
+ onReady?.(player);
1844
+ });
1845
+ return () => {
1846
+ try {
1847
+ player.destroy();
1848
+ } catch {
1993
1849
  }
1850
+ playerRef.current = null;
1994
1851
  };
1995
- const handlePlay = () => {
1996
- if (this.mounted) {
1997
- this.props.onPlay?.();
1852
+ }, [criticalPropsKey]);
1853
+ (0, import_react.useEffect)(() => {
1854
+ if (!playerRef.current) return;
1855
+ try {
1856
+ if (autoplay !== void 0 && playerRef.current.videoElement) {
1857
+ playerRef.current.videoElement.autoplay = autoplay;
1998
1858
  }
1999
- };
2000
- const handlePause = () => {
2001
- if (this.mounted) {
2002
- this.props.onPause?.();
1859
+ if (muted !== void 0 && playerRef.current.videoElement) {
1860
+ playerRef.current.videoElement.muted = muted;
2003
1861
  }
2004
- };
2005
- const handleEnded = () => {
2006
- if (this.mounted) {
2007
- this.props.onEnded?.();
1862
+ } catch (error) {
1863
+ console.warn("Failed to update player properties:", error);
1864
+ }
1865
+ }, [autoplay, muted]);
1866
+ (0, import_react.useEffect)(() => {
1867
+ if (!playerRef.current) return;
1868
+ const checkAdStatus = () => {
1869
+ if (playerRef.current) {
1870
+ const showAds = playerRef.current.isShowingAds();
1871
+ const currentIndex = playerRef.current.getCurrentAdIndex();
1872
+ const totalAds = playerRef.current.getTotalAdsInBreak();
1873
+ setAdStatus((prev) => {
1874
+ if (prev.showAds !== showAds || prev.currentIndex !== currentIndex || prev.totalAds !== totalAds) {
1875
+ return { showAds, currentIndex, totalAds };
1876
+ }
1877
+ return prev;
1878
+ });
2008
1879
  }
2009
1880
  };
2010
- const handleError = (error) => {
2011
- if (this.mounted) {
2012
- this.props.onError?.(error);
1881
+ const interval = setInterval(checkAdStatus, 100);
1882
+ return () => clearInterval(interval);
1883
+ }, []);
1884
+ (0, import_react.useEffect)(() => {
1885
+ if (typeof window === "undefined" || !playerRef.current) return;
1886
+ const handleResize = () => {
1887
+ if (playerRef.current && videoRef.current) {
1888
+ if (typeof playerRef.current.resize === "function") {
1889
+ playerRef.current.resize();
1890
+ }
2013
1891
  }
2014
1892
  };
2015
- const handleLoadedData = () => {
2016
- if (this.mounted) {
2017
- this.props.onLoaded?.();
1893
+ window.addEventListener("resize", handleResize);
1894
+ return () => window.removeEventListener("resize", handleResize);
1895
+ }, []);
1896
+ (0, import_react.useEffect)(() => {
1897
+ if (!playerRef.current || !videoRef.current) return;
1898
+ const updateStates = () => {
1899
+ if (playerRef.current && videoRef.current) {
1900
+ setIsMuted(playerRef.current.isMuted());
1901
+ setIsPlaying(!videoRef.current.paused);
1902
+ const currentTimeValue = videoRef.current.currentTime;
1903
+ setCurrentTime(isFinite(currentTimeValue) ? currentTimeValue : 0);
1904
+ const durationValue = videoRef.current.duration;
1905
+ setDuration(isFinite(durationValue) ? durationValue : 0);
1906
+ const volumeValue = videoRef.current.volume;
1907
+ setVolume(
1908
+ isFinite(volumeValue) ? Math.max(0, Math.min(1, volumeValue)) : 1
1909
+ );
1910
+ const rateValue = videoRef.current.playbackRate;
1911
+ setPlaybackRate(
1912
+ isFinite(rateValue) && rateValue > 0 ? rateValue : 1
1913
+ );
2018
1914
  }
1915
+ setIsFullscreen(
1916
+ document.fullscreenElement === videoRef.current?.parentElement
1917
+ );
2019
1918
  };
2020
- video.addEventListener("loadedmetadata", handleLoadedMetadata);
2021
- video.addEventListener("play", handlePlay);
2022
- video.addEventListener("pause", handlePause);
2023
- video.addEventListener("ended", handleEnded);
2024
- video.addEventListener("error", handleError);
2025
- video.addEventListener("loadeddata", handleLoadedData);
2026
- video.src = this.props.src;
2027
- if (this.props.autoplay !== void 0) video.autoplay = this.props.autoplay;
2028
- if (this.props.muted !== void 0) video.muted = this.props.muted;
2029
- if (this.props.loop !== void 0) video.loop = this.props.loop;
2030
- if (this.props.controls !== void 0) video.controls = this.props.controls;
2031
- if (this.props.playsInline !== void 0)
2032
- video.playsInline = this.props.playsInline;
2033
- if (this.props.preload !== void 0)
2034
- video.preload = this.props.preload;
2035
- if (this.props.poster !== void 0) video.poster = this.props.poster;
2036
- this.props.onMount?.(this);
1919
+ const interval = setInterval(updateStates, 200);
1920
+ const handleFullscreenChange = () => {
1921
+ setIsFullscreen(
1922
+ document.fullscreenElement === videoRef.current?.parentElement
1923
+ );
1924
+ };
1925
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
2037
1926
  return () => {
2038
- video.removeEventListener("loadedmetadata", handleLoadedMetadata);
2039
- video.removeEventListener("play", handlePlay);
2040
- video.removeEventListener("pause", handlePause);
2041
- video.removeEventListener("ended", handleEnded);
2042
- video.removeEventListener("error", handleError);
2043
- video.removeEventListener("loadeddata", handleLoadedData);
1927
+ clearInterval(interval);
1928
+ document.removeEventListener(
1929
+ "fullscreenchange",
1930
+ handleFullscreenChange
1931
+ );
2044
1932
  };
2045
- };
2046
- this.play = () => {
2047
- if (this.props.videoElement) {
2048
- this.props.videoElement.play();
2049
- }
2050
- };
2051
- this.pause = () => {
2052
- if (this.props.videoElement) {
2053
- this.props.videoElement.pause();
2054
- }
2055
- };
2056
- this.stop = () => {
2057
- this.pause();
2058
- if (this.props.videoElement) {
2059
- this.props.videoElement.currentTime = 0;
2060
- }
2061
- };
2062
- this.seekTo = (seconds, keepPlaying) => {
2063
- if (this.props.videoElement) {
2064
- this.props.videoElement.currentTime = seconds;
2065
- if (!keepPlaying) {
2066
- this.pause();
2067
- }
2068
- }
2069
- };
2070
- this.setVolume = (volume) => {
2071
- if (this.props.videoElement) {
2072
- this.props.videoElement.volume = Math.max(0, Math.min(1, volume));
2073
- }
2074
- };
2075
- this.mute = () => {
2076
- if (this.props.videoElement) {
2077
- this.props.videoElement.muted = true;
2078
- }
2079
- };
2080
- this.unmute = () => {
2081
- if (this.props.videoElement) {
2082
- this.props.videoElement.muted = false;
2083
- }
2084
- };
2085
- this.setPlaybackRate = (rate) => {
2086
- if (this.props.videoElement && rate > 0) {
2087
- this.props.videoElement.playbackRate = rate;
2088
- }
2089
- };
2090
- this.setLoop = (loop) => {
2091
- if (this.props.videoElement) {
2092
- this.props.videoElement.loop = loop;
2093
- }
2094
- };
2095
- this.getDuration = () => {
2096
- if (this.props.videoElement && isFinite(this.props.videoElement.duration)) {
2097
- return this.props.videoElement.duration;
2098
- }
2099
- return null;
2100
- };
2101
- this.getCurrentTime = () => {
2102
- if (this.props.videoElement && isFinite(this.props.videoElement.currentTime)) {
2103
- return this.props.videoElement.currentTime;
2104
- }
2105
- return null;
2106
- };
2107
- this.getSecondsLoaded = () => {
2108
- if (this.props.videoElement && this.props.videoElement.buffered.length > 0) {
2109
- return this.props.videoElement.buffered.end(
2110
- this.props.videoElement.buffered.length - 1
2111
- );
2112
- }
2113
- return null;
2114
- };
2115
- this.getInternalPlayer = (key = "player") => {
2116
- if (key === "video") return this.props.videoElement;
2117
- return null;
2118
- };
2119
- this.enablePIP = async () => {
2120
- if (this.props.videoElement && "requestPictureInPicture" in this.props.videoElement) {
2121
- try {
2122
- await this.props.videoElement.requestPictureInPicture();
2123
- } catch (error) {
2124
- console.warn("Picture-in-Picture failed:", error);
2125
- }
2126
- }
2127
- };
2128
- this.disablePIP = async () => {
2129
- if (document.pictureInPictureElement) {
2130
- try {
2131
- await document.exitPictureInPicture();
2132
- } catch (error) {
2133
- console.warn("Exit Picture-in-Picture failed:", error);
2134
- }
2135
- }
2136
- };
2137
- }
2138
- componentDidMount() {
2139
- this.mounted = true;
2140
- this.load();
2141
- }
2142
- componentWillUnmount() {
2143
- this.mounted = false;
2144
- }
2145
- componentDidUpdate(prevProps) {
2146
- if (prevProps.src !== this.props.src) {
2147
- this.load();
2148
- }
2149
- }
2150
- render() {
2151
- return null;
2152
- }
2153
- };
2154
- FilePlayer.displayName = "FilePlayer";
2155
- FilePlayer.canPlay = canPlay.file;
2156
-
2157
- // src/players/index.ts
2158
- var players = [
2159
- {
2160
- key: "hls",
2161
- name: "HLS Player",
2162
- canPlay: canPlay.hls,
2163
- lazyPlayer: lazy(() => Promise.resolve({ default: HlsPlayer }))
2164
- },
2165
- {
2166
- key: "file",
2167
- name: "File Player",
2168
- canPlay: canPlay.file,
2169
- canEnablePIP: (url) => {
2170
- return canPlay.file(url) && (document.pictureInPictureEnabled || typeof document.webkitSupportsPresentationMode === "function");
2171
- },
2172
- lazyPlayer: lazy(() => Promise.resolve({ default: FilePlayer }))
2173
- }
2174
- ];
2175
- var players_default = players;
2176
-
2177
- // src/Player.tsx
2178
- var import_react4 = __toESM(require("react"), 1);
2179
- var SEEK_ON_PLAY_EXPIRY = 5e3;
2180
- var Player = class extends import_react4.Component {
2181
- constructor() {
2182
- super(...arguments);
2183
- this.mounted = false;
2184
- this.isReady = false;
2185
- this.isPlaying = false;
2186
- this.isLoading = true;
2187
- this.loadOnReady = null;
2188
- this.startOnPlay = true;
2189
- this.seekOnPlay = null;
2190
- this.onDurationCalled = false;
2191
- this.handlePlayerMount = (player) => {
2192
- if (this.player) {
2193
- this.progress();
2194
- return;
2195
- }
2196
- this.player = player;
2197
- this.player.load(this.props.src);
2198
- this.progress();
2199
- };
2200
- this.getInternalPlayer = (key) => {
2201
- if (!this.player) return null;
2202
- return this.player.getInternalPlayer(key);
2203
- };
2204
- this.progress = () => {
2205
- if (this.props.src && this.player && this.isReady) {
2206
- const playedSeconds = this.getCurrentTime() || 0;
2207
- const loadedSeconds = this.getSecondsLoaded();
2208
- const duration = this.getDuration();
2209
- if (duration) {
2210
- const progress = {
2211
- playedSeconds,
2212
- played: playedSeconds / duration,
2213
- loaded: 0,
2214
- loadedSeconds: 0
2215
- };
2216
- if (loadedSeconds !== null) {
2217
- progress.loadedSeconds = loadedSeconds;
2218
- progress.loaded = loadedSeconds / duration;
2219
- }
2220
- if (progress.playedSeconds !== this.prevPlayed || progress.loadedSeconds !== this.prevLoaded) {
2221
- this.props.onProgress?.(progress);
2222
- }
2223
- this.prevPlayed = progress.playedSeconds;
2224
- this.prevLoaded = progress.loadedSeconds;
1933
+ }, []);
1934
+ (0, import_react.useEffect)(() => {
1935
+ if (!videoRef.current) return;
1936
+ const handleLoadedMetadata = () => {
1937
+ if (videoRef.current) {
1938
+ const video2 = videoRef.current;
1939
+ void video2.offsetHeight;
2225
1940
  }
2226
- }
2227
- this.progressTimeout = window.setTimeout(
2228
- this.progress,
2229
- this.props.progressInterval || 1e3
2230
- );
2231
- };
2232
- this.handleReady = () => {
2233
- if (!this.mounted) return;
2234
- this.isReady = true;
2235
- this.isLoading = false;
2236
- const { onReady, playing, volume, muted } = this.props;
2237
- onReady();
2238
- if (!muted && volume !== null) {
2239
- this.player.setVolume(volume);
2240
- }
2241
- if (this.loadOnReady) {
2242
- this.player.load(this.loadOnReady, true);
2243
- this.loadOnReady = null;
2244
- } else if (playing) {
2245
- this.player.play();
2246
- }
2247
- this.handleDurationCheck();
2248
- };
2249
- this.handlePlay = () => {
2250
- this.isPlaying = true;
2251
- this.isLoading = false;
2252
- const { onStart, onPlay, playbackRate } = this.props;
2253
- if (this.startOnPlay) {
2254
- if (this.player.setPlaybackRate && playbackRate !== 1) {
2255
- this.player.setPlaybackRate(playbackRate);
1941
+ };
1942
+ const handleLoadStart = () => {
1943
+ setIsLoading(true);
1944
+ setIsBuffering(false);
1945
+ };
1946
+ const handleCanPlay = () => {
1947
+ setIsLoading(false);
1948
+ setIsBuffering(false);
1949
+ };
1950
+ const handleWaiting = () => {
1951
+ setIsBuffering(true);
1952
+ };
1953
+ const handlePlaying = () => {
1954
+ setIsLoading(false);
1955
+ setIsBuffering(false);
1956
+ setShowCenterPlay(false);
1957
+ };
1958
+ const handlePause = () => {
1959
+ if (playerRef.current && !playerRef.current.isShowingAds()) {
1960
+ setShowCenterPlay(true);
1961
+ } else {
1962
+ setShowCenterPlay(false);
2256
1963
  }
2257
- onStart?.();
2258
- this.startOnPlay = false;
1964
+ };
1965
+ const handleEnded = () => {
1966
+ setShowCenterPlay(true);
1967
+ };
1968
+ const video = videoRef.current;
1969
+ video.addEventListener("loadstart", handleLoadStart);
1970
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
1971
+ video.addEventListener("loadeddata", handleLoadedMetadata);
1972
+ video.addEventListener("canplay", handleCanPlay);
1973
+ video.addEventListener("waiting", handleWaiting);
1974
+ video.addEventListener("playing", handlePlaying);
1975
+ video.addEventListener("pause", handlePause);
1976
+ video.addEventListener("ended", handleEnded);
1977
+ if (video.paused) {
1978
+ setShowCenterPlay(true);
2259
1979
  }
2260
- onPlay?.();
2261
- if (this.seekOnPlay) {
2262
- this.seekTo(this.seekOnPlay);
2263
- this.seekOnPlay = null;
2264
- }
2265
- this.handleDurationCheck();
2266
- };
2267
- this.handlePause = (e) => {
2268
- this.isPlaying = false;
2269
- if (!this.isLoading) {
2270
- this.props.onPause?.(e);
2271
- }
2272
- };
2273
- this.handleEnded = () => {
2274
- const { activePlayer, loop, onEnded } = this.props;
2275
- if (activePlayer.loopOnEnded && loop) {
2276
- this.seekTo(0);
2277
- }
2278
- if (!loop) {
2279
- this.isPlaying = false;
2280
- onEnded?.();
2281
- }
2282
- };
2283
- this.handleError = (...args) => {
2284
- this.isLoading = false;
2285
- this.props.onError?.(args[0], args[1], args[2], args[3]);
2286
- };
2287
- this.handleDurationCheck = () => {
2288
- clearTimeout(this.durationCheckTimeout);
2289
- const duration = this.getDuration();
2290
- if (duration) {
2291
- if (!this.onDurationCalled) {
2292
- this.props.onDuration?.(duration);
2293
- this.onDurationCalled = true;
2294
- }
2295
- } else {
2296
- this.durationCheckTimeout = window.setTimeout(
2297
- this.handleDurationCheck,
2298
- 100
2299
- );
2300
- }
2301
- };
2302
- this.handleLoaded = () => {
2303
- this.isLoading = false;
2304
- };
2305
- }
2306
- componentDidMount() {
2307
- this.mounted = true;
2308
- }
2309
- componentWillUnmount() {
2310
- clearTimeout(this.progressTimeout);
2311
- clearTimeout(this.durationCheckTimeout);
2312
- this.mounted = false;
2313
- }
2314
- componentDidUpdate(prevProps) {
2315
- if (!this.player) return;
2316
- const { src, playing, volume, muted, playbackRate, loop, activePlayer } = this.props;
2317
- if (prevProps.src !== src) {
2318
- if (this.isLoading && !activePlayer.forceLoad && !isMediaStream(src)) {
2319
- console.warn(
2320
- `StormcloudPlayer: the attempt to load ${src} is being deferred until the player has loaded`
2321
- );
2322
- this.loadOnReady = src || null;
2323
- return;
2324
- }
2325
- this.isLoading = true;
2326
- this.startOnPlay = true;
2327
- this.onDurationCalled = false;
2328
- this.player.load(src, this.isReady);
2329
- }
2330
- if (!prevProps.playing && playing && !this.isPlaying) {
2331
- this.player.play();
2332
- }
2333
- if (prevProps.playing && !playing && this.isPlaying) {
2334
- this.player.pause();
2335
- }
2336
- if (prevProps.volume !== volume && volume !== null) {
2337
- this.player.setVolume(volume);
2338
- }
2339
- if (prevProps.muted !== muted) {
2340
- if (muted) {
2341
- this.player.mute();
2342
- } else {
2343
- this.player.unmute();
2344
- if (volume !== null) {
2345
- setTimeout(() => this.player.setVolume(volume));
2346
- }
2347
- }
2348
- }
2349
- if (prevProps.playbackRate !== playbackRate && this.player.setPlaybackRate) {
2350
- this.player.setPlaybackRate(playbackRate);
2351
- }
2352
- if (prevProps.loop !== loop && this.player.setLoop) {
2353
- this.player.setLoop(loop);
2354
- }
2355
- }
2356
- getDuration() {
2357
- if (!this.isReady) return null;
2358
- return this.player.getDuration();
2359
- }
2360
- getCurrentTime() {
2361
- if (!this.isReady) return null;
2362
- return this.player.getCurrentTime();
2363
- }
2364
- getSecondsLoaded() {
2365
- if (!this.isReady) return null;
2366
- return this.player.getSecondsLoaded();
2367
- }
2368
- seekTo(amount, type, keepPlaying) {
2369
- if (!this.isReady) {
2370
- if (amount !== 0) {
2371
- this.seekOnPlay = amount;
2372
- setTimeout(() => {
2373
- this.seekOnPlay = null;
2374
- }, SEEK_ON_PLAY_EXPIRY);
2375
- }
2376
- return;
2377
- }
2378
- const isFraction = !type ? amount > 0 && amount < 1 : type === "fraction";
2379
- if (isFraction) {
2380
- const duration = this.player.getDuration();
2381
- if (!duration) {
2382
- console.warn(
2383
- "StormcloudPlayer: could not seek using fraction \u2013 duration not yet available"
2384
- );
2385
- return;
2386
- }
2387
- this.player.seekTo(duration * amount, keepPlaying);
2388
- return;
2389
- }
2390
- this.player.seekTo(amount, keepPlaying);
2391
- }
2392
- render() {
2393
- const Player2 = this.props.activePlayer;
2394
- if (!Player2) {
2395
- return null;
2396
- }
2397
- return import_react4.default.createElement(Player2, {
2398
- ...this.props,
2399
- onMount: this.handlePlayerMount,
2400
- onReady: this.handleReady,
2401
- onPlay: this.handlePlay,
2402
- onPause: this.handlePause,
2403
- onEnded: this.handleEnded,
2404
- onLoaded: this.handleLoaded,
2405
- onError: this.handleError
2406
- });
2407
- }
2408
- };
2409
- Player.displayName = "Player";
2410
- Player.defaultProps = defaultProps;
2411
-
2412
- // src/StormcloudPlayer.tsx
2413
- var IS_BROWSER2 = typeof window !== "undefined" && window.document;
2414
- var IS_GLOBAL2 = typeof globalThis !== "undefined" && globalThis.window && globalThis.window.document;
2415
- var UniversalSuspense = IS_BROWSER2 || IS_GLOBAL2 ? import_react5.Suspense : () => null;
2416
- var SUPPORTED_PROPS = [
2417
- "src",
2418
- "playing",
2419
- "loop",
2420
- "controls",
2421
- "volume",
2422
- "muted",
2423
- "playbackRate",
2424
- "width",
2425
- "height",
2426
- "style",
2427
- "progressInterval",
2428
- "playsInline",
2429
- "autoplay",
2430
- "preload",
2431
- "poster",
2432
- "className",
2433
- "wrapperClassName",
2434
- "wrapperStyle",
2435
- "allowNativeHls",
2436
- "lowLatencyMode",
2437
- "driftToleranceMs",
2438
- "immediateManifestAds",
2439
- "debugAdTiming",
2440
- "showCustomControls",
2441
- "licenseKey",
2442
- "adFailsafeTimeoutMs",
2443
- "onReady",
2444
- "onStart",
2445
- "onPlay",
2446
- "onPause",
2447
- "onBuffer",
2448
- "onBufferEnd",
2449
- "onEnded",
2450
- "onError",
2451
- "onDuration",
2452
- "onSeek",
2453
- "onProgress",
2454
- "onVolumeToggle",
2455
- "onFullscreenToggle",
2456
- "onControlClick"
2457
- ];
2458
- var customPlayers = [];
2459
- var createStormcloudPlayer = (playerList, fallback) => {
2460
- var _a;
2461
- return _a = class extends import_react5.Component {
2462
- constructor() {
2463
- super(...arguments);
2464
- this.state = {
2465
- showPreview: false
2466
- };
2467
- this.references = {
2468
- wrapper: (wrapper) => {
2469
- this.wrapper = wrapper;
2470
- },
2471
- player: (player) => {
2472
- this.player = player;
2473
- }
2474
- };
2475
- this.getActivePlayer = (src) => {
2476
- if (!src) return null;
2477
- for (const player of [...customPlayers, ...playerList]) {
2478
- if (player.canPlay(src)) {
2479
- return player;
2480
- }
2481
- }
2482
- if (fallback) {
2483
- return fallback;
2484
- }
2485
- return null;
2486
- };
2487
- this.getAttributes = (src) => {
2488
- return omit(this.props, SUPPORTED_PROPS);
2489
- };
2490
- this.handleReady = () => {
2491
- this.props.onReady?.(this);
2492
- };
2493
- this.seekTo = (fraction, type, keepPlaying) => {
2494
- if (!this.player) return null;
2495
- this.player.seekTo(fraction, type, keepPlaying);
2496
- };
2497
- this.getCurrentTime = () => {
2498
- if (!this.player) return null;
2499
- return this.player.getCurrentTime();
2500
- };
2501
- this.getSecondsLoaded = () => {
2502
- if (!this.player) return null;
2503
- return this.player.getSecondsLoaded();
2504
- };
2505
- this.getDuration = () => {
2506
- if (!this.player) return null;
2507
- return this.player.getDuration();
2508
- };
2509
- this.getInternalPlayer = (key = "player") => {
2510
- if (!this.player) return null;
2511
- return this.player.getInternalPlayer(key);
2512
- };
2513
- this.renderActivePlayer = (src) => {
2514
- if (!src) return null;
2515
- const activePlayer = this.getActivePlayer(src);
2516
- if (!activePlayer) return null;
2517
- return import_react5.default.createElement(Player, {
2518
- ...this.props,
2519
- key: activePlayer.key,
2520
- ref: this.references.player,
2521
- activePlayer: activePlayer.lazyPlayer || activePlayer,
2522
- onReady: this.handleReady
2523
- });
2524
- };
2525
- }
2526
- render() {
2527
- const {
2528
- src,
2529
- style,
2530
- width,
2531
- height,
2532
- fallback: fallbackElement,
2533
- wrapper: Wrapper
2534
- } = this.props;
2535
- const attributes = this.getAttributes(src);
2536
- const wrapperRef = typeof Wrapper === "string" ? this.references.wrapper : void 0;
2537
- return import_react5.default.createElement(
2538
- Wrapper,
2539
- {
2540
- ref: wrapperRef,
2541
- style: { ...style, width, height },
2542
- ...attributes
2543
- },
2544
- import_react5.default.createElement(
2545
- UniversalSuspense,
2546
- { fallback: fallbackElement },
2547
- this.renderActivePlayer(src)
2548
- )
2549
- );
2550
- }
2551
- }, _a.displayName = "StormcloudPlayer", _a.defaultProps = {
2552
- ...defaultProps,
2553
- fallback: null,
2554
- wrapper: "div"
2555
- }, _a.addCustomPlayer = (player) => {
2556
- customPlayers.push(player);
2557
- }, _a.removeCustomPlayers = () => {
2558
- customPlayers.length = 0;
2559
- }, _a.canPlay = (src) => {
2560
- for (const Player2 of [...customPlayers, ...playerList]) {
2561
- if (Player2.canPlay(src)) {
2562
- return true;
2563
- }
2564
- }
2565
- return false;
2566
- }, _a.canEnablePIP = (src) => {
2567
- for (const Player2 of [...customPlayers, ...playerList]) {
2568
- if (Player2.canEnablePIP && Player2.canEnablePIP(src)) {
2569
- return true;
2570
- }
2571
- }
2572
- return false;
2573
- }, _a;
2574
- };
2575
- var StormcloudPlayer = createStormcloudPlayer(
2576
- players_default,
2577
- players_default[players_default.length - 1]
2578
- );
2579
- var StormcloudPlayer_default = StormcloudPlayer;
2580
-
2581
- // src/ui/StormcloudVideoPlayer.tsx
2582
- var import_react6 = __toESM(require("react"), 1);
2583
- var import_fa = require("react-icons/fa");
2584
- var import_jsx_runtime = require("react/jsx-runtime");
2585
- var CRITICAL_PROPS = [
2586
- "src",
2587
- "allowNativeHls",
2588
- "licenseKey",
2589
- "lowLatencyMode",
2590
- "driftToleranceMs"
2591
- ];
2592
- var StormcloudVideoPlayerComponent = import_react6.default.memo(
2593
- (props) => {
2594
- const {
2595
- src,
2596
- autoplay,
2597
- muted,
2598
- lowLatencyMode,
2599
- allowNativeHls,
2600
- driftToleranceMs,
2601
- immediateManifestAds,
2602
- debugAdTiming,
2603
- showCustomControls,
2604
- onVolumeToggle,
2605
- onFullscreenToggle,
2606
- onControlClick,
2607
- onReady,
2608
- wrapperClassName,
2609
- wrapperStyle,
2610
- className,
2611
- style,
2612
- controls,
2613
- playsInline,
2614
- preload,
2615
- poster,
2616
- children,
2617
- licenseKey,
2618
- ...restVideoAttrs
2619
- } = props;
2620
- const videoRef = (0, import_react6.useRef)(null);
2621
- const playerRef = (0, import_react6.useRef)(null);
2622
- const [adStatus, setAdStatus] = import_react6.default.useState({ showAds: false, currentIndex: 0, totalAds: 0 });
2623
- const [shouldShowNativeControls, setShouldShowNativeControls] = import_react6.default.useState(true);
2624
- const [isMuted, setIsMuted] = import_react6.default.useState(false);
2625
- const [isFullscreen, setIsFullscreen] = import_react6.default.useState(false);
2626
- const [isPlaying, setIsPlaying] = import_react6.default.useState(false);
2627
- const [currentTime, setCurrentTime] = import_react6.default.useState(0);
2628
- const [duration, setDuration] = import_react6.default.useState(0);
2629
- const [volume, setVolume] = import_react6.default.useState(1);
2630
- const [playbackRate, setPlaybackRate] = import_react6.default.useState(1);
2631
- const [showVolumeSlider, setShowVolumeSlider] = import_react6.default.useState(false);
2632
- const [showSpeedMenu, setShowSpeedMenu] = import_react6.default.useState(false);
2633
- const [isLoading, setIsLoading] = import_react6.default.useState(true);
2634
- const [isBuffering, setIsBuffering] = import_react6.default.useState(false);
2635
- const [showCenterPlay, setShowCenterPlay] = import_react6.default.useState(false);
2636
- const formatTime = (seconds) => {
2637
- if (!isFinite(seconds)) return "0:00:00";
2638
- const hours = Math.floor(seconds / 3600);
2639
- const minutes = Math.floor(seconds % 3600 / 60);
2640
- const remainingSeconds = Math.floor(seconds % 60);
2641
- return `${hours}:${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
2642
- };
2643
- const handlePlayPause = () => {
2644
- if (videoRef.current) {
2645
- if (videoRef.current.paused) {
2646
- videoRef.current.play();
2647
- setShowCenterPlay(false);
2648
- } else {
2649
- videoRef.current.pause();
2650
- setShowCenterPlay(true);
2651
- }
2652
- }
2653
- };
2654
- const handleCenterPlayClick = () => {
2655
- if (videoRef.current && videoRef.current.paused) {
2656
- videoRef.current.play();
2657
- setShowCenterPlay(false);
2658
- }
2659
- };
2660
- const handleTimelineSeek = (e) => {
2661
- if (videoRef.current && duration > 0 && isFinite(duration)) {
2662
- const rect = e.currentTarget.getBoundingClientRect();
2663
- const clickX = e.clientX - rect.left;
2664
- const progress = Math.max(0, Math.min(1, clickX / rect.width));
2665
- const newTime = progress * duration;
2666
- if (isFinite(newTime) && newTime >= 0 && newTime <= duration) {
2667
- videoRef.current.currentTime = newTime;
2668
- }
2669
- }
2670
- };
2671
- const handleVolumeChange = (newVolume) => {
2672
- if (videoRef.current && isFinite(newVolume)) {
2673
- const clampedVolume = Math.max(0, Math.min(1, newVolume));
2674
- videoRef.current.volume = clampedVolume;
2675
- videoRef.current.muted = clampedVolume === 0;
2676
- }
2677
- };
2678
- const handlePlaybackRateChange = (rate) => {
2679
- if (videoRef.current && isFinite(rate) && rate > 0) {
2680
- videoRef.current.playbackRate = rate;
2681
- }
2682
- setShowSpeedMenu(false);
2683
- };
2684
- const isHlsStream = src?.toLowerCase().includes(".m3u8") || src?.toLowerCase().includes("/hls/");
2685
- const shouldShowEnhancedControls = showCustomControls && (isHlsStream ? allowNativeHls : true);
2686
- const criticalPropsKey = (0, import_react6.useMemo)(() => {
2687
- return CRITICAL_PROPS.map((prop) => `${prop}:${props[prop]}`).join("|");
2688
- }, [src, allowNativeHls, licenseKey, lowLatencyMode, driftToleranceMs]);
2689
- (0, import_react6.useEffect)(() => {
2690
- if (typeof window === "undefined") return;
2691
- const el = videoRef.current;
2692
- if (!el || !src) return;
2693
- if (playerRef.current) {
2694
- try {
2695
- playerRef.current.destroy();
2696
- } catch {
2697
- }
2698
- playerRef.current = null;
2699
- }
2700
- const cfg = {
2701
- src,
2702
- videoElement: el
2703
- };
2704
- if (autoplay !== void 0) cfg.autoplay = autoplay;
2705
- if (muted !== void 0) cfg.muted = muted;
2706
- if (lowLatencyMode !== void 0) cfg.lowLatencyMode = lowLatencyMode;
2707
- if (allowNativeHls !== void 0) cfg.allowNativeHls = allowNativeHls;
2708
- if (driftToleranceMs !== void 0)
2709
- cfg.driftToleranceMs = driftToleranceMs;
2710
- if (immediateManifestAds !== void 0)
2711
- cfg.immediateManifestAds = immediateManifestAds;
2712
- if (debugAdTiming !== void 0) cfg.debugAdTiming = debugAdTiming;
2713
- if (showCustomControls !== void 0)
2714
- cfg.showCustomControls = showCustomControls;
2715
- if (onVolumeToggle !== void 0) cfg.onVolumeToggle = onVolumeToggle;
2716
- if (onFullscreenToggle !== void 0)
2717
- cfg.onFullscreenToggle = onFullscreenToggle;
2718
- if (onControlClick !== void 0) cfg.onControlClick = onControlClick;
2719
- if (licenseKey !== void 0) cfg.licenseKey = licenseKey;
2720
- const player = new StormcloudVideoPlayer(cfg);
2721
- playerRef.current = player;
2722
- player.load().then(() => {
2723
- const showNative = player.shouldShowNativeControls();
2724
- setShouldShowNativeControls(showNative);
2725
- onReady?.(player);
2726
- }).catch(() => {
2727
- });
2728
- return () => {
2729
- try {
2730
- player.destroy();
2731
- } catch {
2732
- }
2733
- playerRef.current = null;
2734
- };
2735
- }, [criticalPropsKey]);
2736
- (0, import_react6.useEffect)(() => {
2737
- if (!playerRef.current) return;
2738
- try {
2739
- if (autoplay !== void 0 && playerRef.current.videoElement) {
2740
- playerRef.current.videoElement.autoplay = autoplay;
2741
- }
2742
- if (muted !== void 0 && playerRef.current.videoElement) {
2743
- playerRef.current.videoElement.muted = muted;
2744
- }
2745
- } catch (error) {
2746
- console.warn("Failed to update player properties:", error);
2747
- }
2748
- }, [autoplay, muted]);
2749
- (0, import_react6.useEffect)(() => {
2750
- if (!playerRef.current) return;
2751
- const checkAdStatus = () => {
2752
- if (playerRef.current) {
2753
- const showAds = playerRef.current.isShowingAds();
2754
- const currentIndex = playerRef.current.getCurrentAdIndex();
2755
- const totalAds = playerRef.current.getTotalAdsInBreak();
2756
- setAdStatus((prev) => {
2757
- if (prev.showAds !== showAds || prev.currentIndex !== currentIndex || prev.totalAds !== totalAds) {
2758
- return { showAds, currentIndex, totalAds };
2759
- }
2760
- return prev;
2761
- });
2762
- }
2763
- };
2764
- const interval = setInterval(checkAdStatus, 100);
2765
- return () => clearInterval(interval);
2766
- }, []);
2767
- (0, import_react6.useEffect)(() => {
2768
- if (typeof window === "undefined" || !playerRef.current) return;
2769
- const handleResize = () => {
2770
- if (playerRef.current && videoRef.current) {
2771
- if (typeof playerRef.current.resize === "function") {
2772
- playerRef.current.resize();
2773
- }
2774
- }
2775
- };
2776
- window.addEventListener("resize", handleResize);
2777
- return () => window.removeEventListener("resize", handleResize);
2778
- }, []);
2779
- (0, import_react6.useEffect)(() => {
2780
- if (!playerRef.current || !videoRef.current) return;
2781
- const updateStates = () => {
2782
- if (playerRef.current && videoRef.current) {
2783
- setIsMuted(playerRef.current.isMuted());
2784
- setIsPlaying(!videoRef.current.paused);
2785
- const currentTimeValue = videoRef.current.currentTime;
2786
- setCurrentTime(isFinite(currentTimeValue) ? currentTimeValue : 0);
2787
- const durationValue = videoRef.current.duration;
2788
- setDuration(isFinite(durationValue) ? durationValue : 0);
2789
- const volumeValue = videoRef.current.volume;
2790
- setVolume(
2791
- isFinite(volumeValue) ? Math.max(0, Math.min(1, volumeValue)) : 1
2792
- );
2793
- const rateValue = videoRef.current.playbackRate;
2794
- setPlaybackRate(
2795
- isFinite(rateValue) && rateValue > 0 ? rateValue : 1
2796
- );
2797
- }
2798
- setIsFullscreen(
2799
- document.fullscreenElement === videoRef.current?.parentElement
2800
- );
2801
- };
2802
- const interval = setInterval(updateStates, 200);
2803
- const handleFullscreenChange = () => {
2804
- setIsFullscreen(
2805
- document.fullscreenElement === videoRef.current?.parentElement
2806
- );
2807
- };
2808
- document.addEventListener("fullscreenchange", handleFullscreenChange);
2809
- return () => {
2810
- clearInterval(interval);
2811
- document.removeEventListener(
2812
- "fullscreenchange",
2813
- handleFullscreenChange
2814
- );
2815
- };
2816
- }, []);
2817
- (0, import_react6.useEffect)(() => {
2818
- if (!videoRef.current) return;
2819
- const handleLoadedMetadata = () => {
2820
- if (videoRef.current) {
2821
- const video2 = videoRef.current;
2822
- void video2.offsetHeight;
2823
- }
2824
- };
2825
- const handleLoadStart = () => {
2826
- setIsLoading(true);
2827
- setIsBuffering(false);
2828
- };
2829
- const handleCanPlay = () => {
2830
- setIsLoading(false);
2831
- setIsBuffering(false);
2832
- };
2833
- const handleWaiting = () => {
2834
- setIsBuffering(true);
2835
- };
2836
- const handlePlaying = () => {
2837
- setIsLoading(false);
2838
- setIsBuffering(false);
2839
- setShowCenterPlay(false);
2840
- };
2841
- const handlePause = () => {
2842
- if (playerRef.current && !playerRef.current.isShowingAds()) {
2843
- setShowCenterPlay(true);
2844
- } else {
2845
- setShowCenterPlay(false);
2846
- }
2847
- };
2848
- const handleEnded = () => {
2849
- setShowCenterPlay(true);
2850
- };
2851
- const video = videoRef.current;
2852
- video.addEventListener("loadstart", handleLoadStart);
2853
- video.addEventListener("loadedmetadata", handleLoadedMetadata);
2854
- video.addEventListener("loadeddata", handleLoadedMetadata);
2855
- video.addEventListener("canplay", handleCanPlay);
2856
- video.addEventListener("waiting", handleWaiting);
2857
- video.addEventListener("playing", handlePlaying);
2858
- video.addEventListener("pause", handlePause);
2859
- video.addEventListener("ended", handleEnded);
2860
- if (video.paused) {
2861
- setShowCenterPlay(true);
2862
- }
2863
- return () => {
2864
- video.removeEventListener("loadstart", handleLoadStart);
2865
- video.removeEventListener("loadedmetadata", handleLoadedMetadata);
2866
- video.removeEventListener("loadeddata", handleLoadedMetadata);
2867
- video.removeEventListener("canplay", handleCanPlay);
2868
- video.removeEventListener("waiting", handleWaiting);
2869
- video.removeEventListener("playing", handlePlaying);
2870
- video.removeEventListener("pause", handlePause);
2871
- video.removeEventListener("ended", handleEnded);
1980
+ return () => {
1981
+ video.removeEventListener("loadstart", handleLoadStart);
1982
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
1983
+ video.removeEventListener("loadeddata", handleLoadedMetadata);
1984
+ video.removeEventListener("canplay", handleCanPlay);
1985
+ video.removeEventListener("waiting", handleWaiting);
1986
+ video.removeEventListener("playing", handlePlaying);
1987
+ video.removeEventListener("pause", handlePause);
1988
+ video.removeEventListener("ended", handleEnded);
2872
1989
  };
2873
1990
  }, []);
2874
1991
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -3769,48 +2886,939 @@ var StormcloudVideoPlayerComponent = import_react6.default.memo(
3769
2886
  )
3770
2887
  ]
3771
2888
  }
3772
- )
3773
- ] });
3774
- },
3775
- (prevProps, nextProps) => {
3776
- for (const prop of CRITICAL_PROPS) {
3777
- if (prevProps[prop] !== nextProps[prop]) {
3778
- return false;
2889
+ )
2890
+ ] });
2891
+ },
2892
+ (prevProps, nextProps) => {
2893
+ for (const prop of CRITICAL_PROPS) {
2894
+ if (prevProps[prop] !== nextProps[prop]) {
2895
+ return false;
2896
+ }
2897
+ }
2898
+ const uiProps = [
2899
+ "autoplay",
2900
+ "muted",
2901
+ "controls",
2902
+ "showCustomControls",
2903
+ "className",
2904
+ "style",
2905
+ "wrapperClassName",
2906
+ "wrapperStyle",
2907
+ "playsInline",
2908
+ "preload",
2909
+ "poster",
2910
+ "children"
2911
+ ];
2912
+ for (const prop of uiProps) {
2913
+ if (prevProps[prop] !== nextProps[prop]) {
2914
+ return false;
2915
+ }
2916
+ }
2917
+ const callbackProps = [
2918
+ "onReady",
2919
+ "onVolumeToggle",
2920
+ "onFullscreenToggle",
2921
+ "onControlClick"
2922
+ ];
2923
+ for (const prop of callbackProps) {
2924
+ if (prevProps[prop] !== nextProps[prop]) {
2925
+ return false;
2926
+ }
2927
+ }
2928
+ return true;
2929
+ }
2930
+ );
2931
+
2932
+ // src/StormcloudPlayer.tsx
2933
+ var import_react6 = __toESM(require("react"), 1);
2934
+
2935
+ // src/props.ts
2936
+ var noop = () => {
2937
+ };
2938
+ var defaultProps = {
2939
+ playing: false,
2940
+ loop: false,
2941
+ controls: true,
2942
+ volume: 1,
2943
+ muted: false,
2944
+ playbackRate: 1,
2945
+ width: "100%",
2946
+ height: "auto",
2947
+ style: {},
2948
+ progressInterval: 1e3,
2949
+ playsInline: false,
2950
+ autoplay: false,
2951
+ preload: "metadata",
2952
+ poster: "",
2953
+ className: "",
2954
+ wrapperClassName: "",
2955
+ wrapperStyle: {},
2956
+ allowNativeHls: false,
2957
+ lowLatencyMode: false,
2958
+ driftToleranceMs: 1e3,
2959
+ immediateManifestAds: true,
2960
+ debugAdTiming: false,
2961
+ showCustomControls: false,
2962
+ licenseKey: "",
2963
+ adFailsafeTimeoutMs: 1e4,
2964
+ onStart: noop,
2965
+ onPlay: noop,
2966
+ onPause: noop,
2967
+ onBuffer: noop,
2968
+ onBufferEnd: noop,
2969
+ onEnded: noop,
2970
+ onError: noop,
2971
+ onDuration: noop,
2972
+ onSeek: noop,
2973
+ onProgress: noop,
2974
+ onVolumeToggle: noop,
2975
+ onFullscreenToggle: noop,
2976
+ onControlClick: noop
2977
+ };
2978
+
2979
+ // src/utils.ts
2980
+ var import_react2 = require("react");
2981
+ var lazy = import_react2.lazy;
2982
+ var omit = (object, keys) => {
2983
+ const result = { ...object };
2984
+ keys.forEach((key) => {
2985
+ delete result[key];
2986
+ });
2987
+ return result;
2988
+ };
2989
+ var isMediaStream = (url) => {
2990
+ return typeof window !== "undefined" && window.MediaStream && url instanceof window.MediaStream;
2991
+ };
2992
+ var supportsWebKitPresentationMode = () => {
2993
+ if (typeof window === "undefined") return false;
2994
+ const video = document.createElement("video");
2995
+ return "webkitSupportsPresentationMode" in video;
2996
+ };
2997
+ var randomString = () => {
2998
+ return Math.random().toString(36).substr(2, 9);
2999
+ };
3000
+ var parseQuery = (url) => {
3001
+ const query = {};
3002
+ const params = new URLSearchParams(url.split("?")[1] || "");
3003
+ params.forEach((value, key) => {
3004
+ query[key] = value;
3005
+ });
3006
+ return query;
3007
+ };
3008
+ var merge = (target, ...sources) => {
3009
+ if (!sources.length) return target;
3010
+ const source = sources.shift();
3011
+ if (isObject(target) && isObject(source)) {
3012
+ for (const key in source) {
3013
+ if (isObject(source[key])) {
3014
+ if (!target[key]) Object.assign(target, { [key]: {} });
3015
+ merge(target[key], source[key]);
3016
+ } else {
3017
+ Object.assign(target, { [key]: source[key] });
3018
+ }
3019
+ }
3020
+ }
3021
+ return merge(target, ...sources);
3022
+ };
3023
+ var isObject = (item) => {
3024
+ return item && typeof item === "object" && !Array.isArray(item);
3025
+ };
3026
+ var IS_BROWSER = typeof window !== "undefined" && window.document;
3027
+ var IS_GLOBAL = typeof globalThis !== "undefined" && globalThis.window && globalThis.window.document;
3028
+ var IS_IOS = IS_BROWSER && /iPad|iPhone|iPod/.test(navigator.userAgent);
3029
+ var IS_SAFARI = IS_BROWSER && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
3030
+ var SUPPORTS_HLS = () => {
3031
+ if (!IS_BROWSER) return false;
3032
+ const video = document.createElement("video");
3033
+ return Boolean(video.canPlayType("application/vnd.apple.mpegurl"));
3034
+ };
3035
+ var SUPPORTS_DASH = () => {
3036
+ if (!IS_BROWSER) return false;
3037
+ const video = document.createElement("video");
3038
+ return Boolean(video.canPlayType("application/dash+xml"));
3039
+ };
3040
+
3041
+ // src/patterns.ts
3042
+ var HLS_EXTENSIONS = /\.(m3u8)($|\?)/i;
3043
+ var HLS_PATHS = /\/hls\//i;
3044
+ var DASH_EXTENSIONS = /\.(mpd)($|\?)/i;
3045
+ var VIDEO_EXTENSIONS = /\.(mp4|webm|ogg|avi|mov|wmv|flv|mkv)($|\?)/i;
3046
+ var AUDIO_EXTENSIONS = /\.(mp3|wav|ogg|aac|wma|flac|m4a)($|\?)/i;
3047
+ var canPlay = {
3048
+ hls: (url) => {
3049
+ if (!url || typeof url !== "string") return false;
3050
+ return HLS_EXTENSIONS.test(url) || HLS_PATHS.test(url);
3051
+ },
3052
+ dash: (url) => {
3053
+ if (!url || typeof url !== "string") return false;
3054
+ return DASH_EXTENSIONS.test(url);
3055
+ },
3056
+ video: (url) => {
3057
+ if (!url || typeof url !== "string") return false;
3058
+ return VIDEO_EXTENSIONS.test(url);
3059
+ },
3060
+ audio: (url) => {
3061
+ if (!url || typeof url !== "string") return false;
3062
+ return AUDIO_EXTENSIONS.test(url);
3063
+ },
3064
+ file: (url) => {
3065
+ if (!url || typeof url !== "string") return false;
3066
+ return VIDEO_EXTENSIONS.test(url) || AUDIO_EXTENSIONS.test(url);
3067
+ }
3068
+ };
3069
+
3070
+ // src/players/HlsPlayer.tsx
3071
+ var import_react3 = require("react");
3072
+ var HlsPlayer = class extends import_react3.Component {
3073
+ constructor() {
3074
+ super(...arguments);
3075
+ this.player = null;
3076
+ this.mounted = false;
3077
+ this.load = async () => {
3078
+ if (!this.props.videoElement || !this.props.src) return;
3079
+ try {
3080
+ if (this.player) {
3081
+ this.player.destroy();
3082
+ this.player = null;
3083
+ }
3084
+ const config = {
3085
+ src: this.props.src,
3086
+ videoElement: this.props.videoElement
3087
+ };
3088
+ if (this.props.autoplay !== void 0)
3089
+ config.autoplay = this.props.autoplay;
3090
+ if (this.props.muted !== void 0) config.muted = this.props.muted;
3091
+ if (this.props.lowLatencyMode !== void 0)
3092
+ config.lowLatencyMode = this.props.lowLatencyMode;
3093
+ if (this.props.allowNativeHls !== void 0)
3094
+ config.allowNativeHls = this.props.allowNativeHls;
3095
+ if (this.props.driftToleranceMs !== void 0)
3096
+ config.driftToleranceMs = this.props.driftToleranceMs;
3097
+ if (this.props.immediateManifestAds !== void 0)
3098
+ config.immediateManifestAds = this.props.immediateManifestAds;
3099
+ if (this.props.debugAdTiming !== void 0)
3100
+ config.debugAdTiming = this.props.debugAdTiming;
3101
+ if (this.props.showCustomControls !== void 0)
3102
+ config.showCustomControls = this.props.showCustomControls;
3103
+ if (this.props.onVolumeToggle !== void 0)
3104
+ config.onVolumeToggle = this.props.onVolumeToggle;
3105
+ if (this.props.onFullscreenToggle !== void 0)
3106
+ config.onFullscreenToggle = this.props.onFullscreenToggle;
3107
+ if (this.props.onControlClick !== void 0)
3108
+ config.onControlClick = this.props.onControlClick;
3109
+ if (this.props.licenseKey !== void 0)
3110
+ config.licenseKey = this.props.licenseKey;
3111
+ if (this.props.adFailsafeTimeoutMs !== void 0)
3112
+ config.adFailsafeTimeoutMs = this.props.adFailsafeTimeoutMs;
3113
+ this.player = new StormcloudVideoPlayer(config);
3114
+ this.props.onMount?.(this);
3115
+ await this.player.load();
3116
+ if (this.mounted) {
3117
+ this.props.onReady?.();
3118
+ }
3119
+ } catch (error) {
3120
+ if (this.mounted) {
3121
+ this.props.onError?.(error);
3122
+ }
3123
+ }
3124
+ };
3125
+ this.play = () => {
3126
+ if (this.props.videoElement) {
3127
+ this.props.videoElement.play();
3128
+ this.props.onPlay?.();
3129
+ }
3130
+ };
3131
+ this.pause = () => {
3132
+ if (this.props.videoElement) {
3133
+ this.props.videoElement.pause();
3134
+ this.props.onPause?.();
3135
+ }
3136
+ };
3137
+ this.stop = () => {
3138
+ this.pause();
3139
+ if (this.props.videoElement) {
3140
+ this.props.videoElement.currentTime = 0;
3141
+ }
3142
+ };
3143
+ this.seekTo = (seconds, keepPlaying) => {
3144
+ if (this.props.videoElement) {
3145
+ this.props.videoElement.currentTime = seconds;
3146
+ if (!keepPlaying) {
3147
+ this.pause();
3148
+ }
3149
+ }
3150
+ };
3151
+ this.setVolume = (volume) => {
3152
+ if (this.props.videoElement) {
3153
+ this.props.videoElement.volume = Math.max(0, Math.min(1, volume));
3154
+ }
3155
+ };
3156
+ this.mute = () => {
3157
+ if (this.props.videoElement) {
3158
+ this.props.videoElement.muted = true;
3159
+ }
3160
+ };
3161
+ this.unmute = () => {
3162
+ if (this.props.videoElement) {
3163
+ this.props.videoElement.muted = false;
3164
+ }
3165
+ };
3166
+ this.setPlaybackRate = (rate) => {
3167
+ if (this.props.videoElement && rate > 0) {
3168
+ this.props.videoElement.playbackRate = rate;
3169
+ }
3170
+ };
3171
+ this.getDuration = () => {
3172
+ if (this.props.videoElement && isFinite(this.props.videoElement.duration)) {
3173
+ return this.props.videoElement.duration;
3174
+ }
3175
+ return null;
3176
+ };
3177
+ this.getCurrentTime = () => {
3178
+ if (this.props.videoElement && isFinite(this.props.videoElement.currentTime)) {
3179
+ return this.props.videoElement.currentTime;
3180
+ }
3181
+ return null;
3182
+ };
3183
+ this.getSecondsLoaded = () => {
3184
+ if (this.props.videoElement && this.props.videoElement.buffered.length > 0) {
3185
+ return this.props.videoElement.buffered.end(
3186
+ this.props.videoElement.buffered.length - 1
3187
+ );
3188
+ }
3189
+ return null;
3190
+ };
3191
+ this.getInternalPlayer = (key = "player") => {
3192
+ if (key === "player") return this.player;
3193
+ if (key === "video") return this.props.videoElement;
3194
+ if (key === "hls" && this.player) return this.player.hls;
3195
+ return null;
3196
+ };
3197
+ }
3198
+ componentDidMount() {
3199
+ this.mounted = true;
3200
+ this.load();
3201
+ }
3202
+ componentWillUnmount() {
3203
+ this.mounted = false;
3204
+ if (this.player) {
3205
+ this.player.destroy();
3206
+ this.player = null;
3207
+ }
3208
+ }
3209
+ componentDidUpdate(prevProps) {
3210
+ if (prevProps.src !== this.props.src) {
3211
+ this.load();
3212
+ }
3213
+ }
3214
+ render() {
3215
+ return null;
3216
+ }
3217
+ };
3218
+ HlsPlayer.displayName = "HlsPlayer";
3219
+ HlsPlayer.canPlay = canPlay.hls;
3220
+
3221
+ // src/players/FilePlayer.tsx
3222
+ var import_react4 = require("react");
3223
+ var FilePlayer = class extends import_react4.Component {
3224
+ constructor() {
3225
+ super(...arguments);
3226
+ this.mounted = false;
3227
+ this.ready = false;
3228
+ this.load = () => {
3229
+ if (!this.props.videoElement || !this.props.src) return;
3230
+ const video = this.props.videoElement;
3231
+ const handleLoadedMetadata = () => {
3232
+ if (this.mounted && !this.ready) {
3233
+ this.ready = true;
3234
+ this.props.onReady?.();
3235
+ }
3236
+ };
3237
+ const handlePlay = () => {
3238
+ if (this.mounted) {
3239
+ this.props.onPlay?.();
3240
+ }
3241
+ };
3242
+ const handlePause = () => {
3243
+ if (this.mounted) {
3244
+ this.props.onPause?.();
3245
+ }
3246
+ };
3247
+ const handleEnded = () => {
3248
+ if (this.mounted) {
3249
+ this.props.onEnded?.();
3250
+ }
3251
+ };
3252
+ const handleError = (error) => {
3253
+ if (this.mounted) {
3254
+ this.props.onError?.(error);
3255
+ }
3256
+ };
3257
+ const handleLoadedData = () => {
3258
+ if (this.mounted) {
3259
+ this.props.onLoaded?.();
3260
+ }
3261
+ };
3262
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
3263
+ video.addEventListener("play", handlePlay);
3264
+ video.addEventListener("pause", handlePause);
3265
+ video.addEventListener("ended", handleEnded);
3266
+ video.addEventListener("error", handleError);
3267
+ video.addEventListener("loadeddata", handleLoadedData);
3268
+ video.src = this.props.src;
3269
+ if (this.props.autoplay !== void 0) video.autoplay = this.props.autoplay;
3270
+ if (this.props.muted !== void 0) video.muted = this.props.muted;
3271
+ if (this.props.loop !== void 0) video.loop = this.props.loop;
3272
+ if (this.props.controls !== void 0) video.controls = this.props.controls;
3273
+ if (this.props.playsInline !== void 0)
3274
+ video.playsInline = this.props.playsInline;
3275
+ if (this.props.preload !== void 0)
3276
+ video.preload = this.props.preload;
3277
+ if (this.props.poster !== void 0) video.poster = this.props.poster;
3278
+ this.props.onMount?.(this);
3279
+ return () => {
3280
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
3281
+ video.removeEventListener("play", handlePlay);
3282
+ video.removeEventListener("pause", handlePause);
3283
+ video.removeEventListener("ended", handleEnded);
3284
+ video.removeEventListener("error", handleError);
3285
+ video.removeEventListener("loadeddata", handleLoadedData);
3286
+ };
3287
+ };
3288
+ this.play = () => {
3289
+ if (this.props.videoElement) {
3290
+ this.props.videoElement.play();
3291
+ }
3292
+ };
3293
+ this.pause = () => {
3294
+ if (this.props.videoElement) {
3295
+ this.props.videoElement.pause();
3296
+ }
3297
+ };
3298
+ this.stop = () => {
3299
+ this.pause();
3300
+ if (this.props.videoElement) {
3301
+ this.props.videoElement.currentTime = 0;
3302
+ }
3303
+ };
3304
+ this.seekTo = (seconds, keepPlaying) => {
3305
+ if (this.props.videoElement) {
3306
+ this.props.videoElement.currentTime = seconds;
3307
+ if (!keepPlaying) {
3308
+ this.pause();
3309
+ }
3310
+ }
3311
+ };
3312
+ this.setVolume = (volume) => {
3313
+ if (this.props.videoElement) {
3314
+ this.props.videoElement.volume = Math.max(0, Math.min(1, volume));
3315
+ }
3316
+ };
3317
+ this.mute = () => {
3318
+ if (this.props.videoElement) {
3319
+ this.props.videoElement.muted = true;
3320
+ }
3321
+ };
3322
+ this.unmute = () => {
3323
+ if (this.props.videoElement) {
3324
+ this.props.videoElement.muted = false;
3325
+ }
3326
+ };
3327
+ this.setPlaybackRate = (rate) => {
3328
+ if (this.props.videoElement && rate > 0) {
3329
+ this.props.videoElement.playbackRate = rate;
3330
+ }
3331
+ };
3332
+ this.setLoop = (loop) => {
3333
+ if (this.props.videoElement) {
3334
+ this.props.videoElement.loop = loop;
3335
+ }
3336
+ };
3337
+ this.getDuration = () => {
3338
+ if (this.props.videoElement && isFinite(this.props.videoElement.duration)) {
3339
+ return this.props.videoElement.duration;
3340
+ }
3341
+ return null;
3342
+ };
3343
+ this.getCurrentTime = () => {
3344
+ if (this.props.videoElement && isFinite(this.props.videoElement.currentTime)) {
3345
+ return this.props.videoElement.currentTime;
3346
+ }
3347
+ return null;
3348
+ };
3349
+ this.getSecondsLoaded = () => {
3350
+ if (this.props.videoElement && this.props.videoElement.buffered.length > 0) {
3351
+ return this.props.videoElement.buffered.end(
3352
+ this.props.videoElement.buffered.length - 1
3353
+ );
3354
+ }
3355
+ return null;
3356
+ };
3357
+ this.getInternalPlayer = (key = "player") => {
3358
+ if (key === "video") return this.props.videoElement;
3359
+ return null;
3360
+ };
3361
+ this.enablePIP = async () => {
3362
+ if (this.props.videoElement && "requestPictureInPicture" in this.props.videoElement) {
3363
+ try {
3364
+ await this.props.videoElement.requestPictureInPicture();
3365
+ } catch (error) {
3366
+ console.warn("Picture-in-Picture failed:", error);
3367
+ }
3368
+ }
3369
+ };
3370
+ this.disablePIP = async () => {
3371
+ if (document.pictureInPictureElement) {
3372
+ try {
3373
+ await document.exitPictureInPicture();
3374
+ } catch (error) {
3375
+ console.warn("Exit Picture-in-Picture failed:", error);
3376
+ }
3377
+ }
3378
+ };
3379
+ }
3380
+ componentDidMount() {
3381
+ this.mounted = true;
3382
+ this.load();
3383
+ }
3384
+ componentWillUnmount() {
3385
+ this.mounted = false;
3386
+ }
3387
+ componentDidUpdate(prevProps) {
3388
+ if (prevProps.src !== this.props.src) {
3389
+ this.load();
3390
+ }
3391
+ }
3392
+ render() {
3393
+ return null;
3394
+ }
3395
+ };
3396
+ FilePlayer.displayName = "FilePlayer";
3397
+ FilePlayer.canPlay = canPlay.file;
3398
+
3399
+ // src/players/index.ts
3400
+ var players = [
3401
+ {
3402
+ key: "hls",
3403
+ name: "HLS Player",
3404
+ canPlay: canPlay.hls,
3405
+ lazyPlayer: lazy(() => Promise.resolve({ default: HlsPlayer }))
3406
+ },
3407
+ {
3408
+ key: "file",
3409
+ name: "File Player",
3410
+ canPlay: canPlay.file,
3411
+ canEnablePIP: (url) => {
3412
+ return canPlay.file(url) && (document.pictureInPictureEnabled || typeof document.webkitSupportsPresentationMode === "function");
3413
+ },
3414
+ lazyPlayer: lazy(() => Promise.resolve({ default: FilePlayer }))
3415
+ }
3416
+ ];
3417
+ var players_default = players;
3418
+
3419
+ // src/Player.tsx
3420
+ var import_react5 = __toESM(require("react"), 1);
3421
+ var SEEK_ON_PLAY_EXPIRY = 5e3;
3422
+ var Player = class extends import_react5.Component {
3423
+ constructor() {
3424
+ super(...arguments);
3425
+ this.mounted = false;
3426
+ this.isReady = false;
3427
+ this.isPlaying = false;
3428
+ this.isLoading = true;
3429
+ this.loadOnReady = null;
3430
+ this.startOnPlay = true;
3431
+ this.seekOnPlay = null;
3432
+ this.onDurationCalled = false;
3433
+ this.handlePlayerMount = (player) => {
3434
+ if (this.player) {
3435
+ this.progress();
3436
+ return;
3437
+ }
3438
+ this.player = player;
3439
+ this.player.load(this.props.src);
3440
+ this.progress();
3441
+ };
3442
+ this.getInternalPlayer = (key) => {
3443
+ if (!this.player) return null;
3444
+ return this.player.getInternalPlayer(key);
3445
+ };
3446
+ this.progress = () => {
3447
+ if (this.props.src && this.player && this.isReady) {
3448
+ const playedSeconds = this.getCurrentTime() || 0;
3449
+ const loadedSeconds = this.getSecondsLoaded();
3450
+ const duration = this.getDuration();
3451
+ if (duration) {
3452
+ const progress = {
3453
+ playedSeconds,
3454
+ played: playedSeconds / duration,
3455
+ loaded: 0,
3456
+ loadedSeconds: 0
3457
+ };
3458
+ if (loadedSeconds !== null) {
3459
+ progress.loadedSeconds = loadedSeconds;
3460
+ progress.loaded = loadedSeconds / duration;
3461
+ }
3462
+ if (progress.playedSeconds !== this.prevPlayed || progress.loadedSeconds !== this.prevLoaded) {
3463
+ this.props.onProgress?.(progress);
3464
+ }
3465
+ this.prevPlayed = progress.playedSeconds;
3466
+ this.prevLoaded = progress.loadedSeconds;
3467
+ }
3468
+ }
3469
+ this.progressTimeout = window.setTimeout(
3470
+ this.progress,
3471
+ this.props.progressInterval || 1e3
3472
+ );
3473
+ };
3474
+ this.handleReady = () => {
3475
+ if (!this.mounted) return;
3476
+ this.isReady = true;
3477
+ this.isLoading = false;
3478
+ const { onReady, playing, volume, muted } = this.props;
3479
+ onReady();
3480
+ if (!muted && volume !== null) {
3481
+ this.player.setVolume(volume);
3482
+ }
3483
+ if (this.loadOnReady) {
3484
+ this.player.load(this.loadOnReady, true);
3485
+ this.loadOnReady = null;
3486
+ } else if (playing) {
3487
+ this.player.play();
3488
+ }
3489
+ this.handleDurationCheck();
3490
+ };
3491
+ this.handlePlay = () => {
3492
+ this.isPlaying = true;
3493
+ this.isLoading = false;
3494
+ const { onStart, onPlay, playbackRate } = this.props;
3495
+ if (this.startOnPlay) {
3496
+ if (this.player.setPlaybackRate && playbackRate !== 1) {
3497
+ this.player.setPlaybackRate(playbackRate);
3498
+ }
3499
+ onStart?.();
3500
+ this.startOnPlay = false;
3501
+ }
3502
+ onPlay?.();
3503
+ if (this.seekOnPlay) {
3504
+ this.seekTo(this.seekOnPlay);
3505
+ this.seekOnPlay = null;
3506
+ }
3507
+ this.handleDurationCheck();
3508
+ };
3509
+ this.handlePause = (e) => {
3510
+ this.isPlaying = false;
3511
+ if (!this.isLoading) {
3512
+ this.props.onPause?.(e);
3513
+ }
3514
+ };
3515
+ this.handleEnded = () => {
3516
+ const { activePlayer, loop, onEnded } = this.props;
3517
+ if (activePlayer.loopOnEnded && loop) {
3518
+ this.seekTo(0);
3519
+ }
3520
+ if (!loop) {
3521
+ this.isPlaying = false;
3522
+ onEnded?.();
3523
+ }
3524
+ };
3525
+ this.handleError = (...args) => {
3526
+ this.isLoading = false;
3527
+ this.props.onError?.(args[0], args[1], args[2], args[3]);
3528
+ };
3529
+ this.handleDurationCheck = () => {
3530
+ clearTimeout(this.durationCheckTimeout);
3531
+ const duration = this.getDuration();
3532
+ if (duration) {
3533
+ if (!this.onDurationCalled) {
3534
+ this.props.onDuration?.(duration);
3535
+ this.onDurationCalled = true;
3536
+ }
3537
+ } else {
3538
+ this.durationCheckTimeout = window.setTimeout(
3539
+ this.handleDurationCheck,
3540
+ 100
3541
+ );
3542
+ }
3543
+ };
3544
+ this.handleLoaded = () => {
3545
+ this.isLoading = false;
3546
+ };
3547
+ }
3548
+ componentDidMount() {
3549
+ this.mounted = true;
3550
+ }
3551
+ componentWillUnmount() {
3552
+ clearTimeout(this.progressTimeout);
3553
+ clearTimeout(this.durationCheckTimeout);
3554
+ this.mounted = false;
3555
+ }
3556
+ componentDidUpdate(prevProps) {
3557
+ if (!this.player) return;
3558
+ const { src, playing, volume, muted, playbackRate, loop, activePlayer } = this.props;
3559
+ if (prevProps.src !== src) {
3560
+ if (this.isLoading && !activePlayer.forceLoad && !isMediaStream(src)) {
3561
+ console.warn(
3562
+ `StormcloudPlayer: the attempt to load ${src} is being deferred until the player has loaded`
3563
+ );
3564
+ this.loadOnReady = src || null;
3565
+ return;
3566
+ }
3567
+ this.isLoading = true;
3568
+ this.startOnPlay = true;
3569
+ this.onDurationCalled = false;
3570
+ this.player.load(src, this.isReady);
3571
+ }
3572
+ if (!prevProps.playing && playing && !this.isPlaying) {
3573
+ this.player.play();
3574
+ }
3575
+ if (prevProps.playing && !playing && this.isPlaying) {
3576
+ this.player.pause();
3577
+ }
3578
+ if (prevProps.volume !== volume && volume !== null) {
3579
+ this.player.setVolume(volume);
3580
+ }
3581
+ if (prevProps.muted !== muted) {
3582
+ if (muted) {
3583
+ this.player.mute();
3584
+ } else {
3585
+ this.player.unmute();
3586
+ if (volume !== null) {
3587
+ setTimeout(() => this.player.setVolume(volume));
3588
+ }
3779
3589
  }
3780
3590
  }
3781
- const uiProps = [
3782
- "autoplay",
3783
- "muted",
3784
- "controls",
3785
- "showCustomControls",
3786
- "className",
3787
- "style",
3788
- "wrapperClassName",
3789
- "wrapperStyle",
3790
- "playsInline",
3791
- "preload",
3792
- "poster",
3793
- "children"
3794
- ];
3795
- for (const prop of uiProps) {
3796
- if (prevProps[prop] !== nextProps[prop]) {
3797
- return false;
3591
+ if (prevProps.playbackRate !== playbackRate && this.player.setPlaybackRate) {
3592
+ this.player.setPlaybackRate(playbackRate);
3593
+ }
3594
+ if (prevProps.loop !== loop && this.player.setLoop) {
3595
+ this.player.setLoop(loop);
3596
+ }
3597
+ }
3598
+ getDuration() {
3599
+ if (!this.isReady) return null;
3600
+ return this.player.getDuration();
3601
+ }
3602
+ getCurrentTime() {
3603
+ if (!this.isReady) return null;
3604
+ return this.player.getCurrentTime();
3605
+ }
3606
+ getSecondsLoaded() {
3607
+ if (!this.isReady) return null;
3608
+ return this.player.getSecondsLoaded();
3609
+ }
3610
+ seekTo(amount, type, keepPlaying) {
3611
+ if (!this.isReady) {
3612
+ if (amount !== 0) {
3613
+ this.seekOnPlay = amount;
3614
+ setTimeout(() => {
3615
+ this.seekOnPlay = null;
3616
+ }, SEEK_ON_PLAY_EXPIRY);
3798
3617
  }
3618
+ return;
3799
3619
  }
3800
- const callbackProps = [
3801
- "onReady",
3802
- "onVolumeToggle",
3803
- "onFullscreenToggle",
3804
- "onControlClick"
3805
- ];
3806
- for (const prop of callbackProps) {
3807
- if (prevProps[prop] !== nextProps[prop]) {
3808
- return false;
3620
+ const isFraction = !type ? amount > 0 && amount < 1 : type === "fraction";
3621
+ if (isFraction) {
3622
+ const duration = this.player.getDuration();
3623
+ if (!duration) {
3624
+ console.warn(
3625
+ "StormcloudPlayer: could not seek using fraction \u2013 duration not yet available"
3626
+ );
3627
+ return;
3809
3628
  }
3629
+ this.player.seekTo(duration * amount, keepPlaying);
3630
+ return;
3810
3631
  }
3811
- return true;
3632
+ this.player.seekTo(amount, keepPlaying);
3633
+ }
3634
+ render() {
3635
+ const Player2 = this.props.activePlayer;
3636
+ if (!Player2) {
3637
+ return null;
3638
+ }
3639
+ return import_react5.default.createElement(Player2, {
3640
+ ...this.props,
3641
+ onMount: this.handlePlayerMount,
3642
+ onReady: this.handleReady,
3643
+ onPlay: this.handlePlay,
3644
+ onPause: this.handlePause,
3645
+ onEnded: this.handleEnded,
3646
+ onLoaded: this.handleLoaded,
3647
+ onError: this.handleError
3648
+ });
3812
3649
  }
3650
+ };
3651
+ Player.displayName = "Player";
3652
+ Player.defaultProps = defaultProps;
3653
+
3654
+ // src/StormcloudPlayer.tsx
3655
+ var IS_BROWSER2 = typeof window !== "undefined" && window.document;
3656
+ var IS_GLOBAL2 = typeof globalThis !== "undefined" && globalThis.window && globalThis.window.document;
3657
+ var UniversalSuspense = IS_BROWSER2 || IS_GLOBAL2 ? import_react6.Suspense : () => null;
3658
+ var SUPPORTED_PROPS = [
3659
+ "src",
3660
+ "playing",
3661
+ "loop",
3662
+ "controls",
3663
+ "volume",
3664
+ "muted",
3665
+ "playbackRate",
3666
+ "width",
3667
+ "height",
3668
+ "style",
3669
+ "progressInterval",
3670
+ "playsInline",
3671
+ "autoplay",
3672
+ "preload",
3673
+ "poster",
3674
+ "className",
3675
+ "wrapperClassName",
3676
+ "wrapperStyle",
3677
+ "allowNativeHls",
3678
+ "lowLatencyMode",
3679
+ "driftToleranceMs",
3680
+ "immediateManifestAds",
3681
+ "debugAdTiming",
3682
+ "showCustomControls",
3683
+ "licenseKey",
3684
+ "adFailsafeTimeoutMs",
3685
+ "onReady",
3686
+ "onStart",
3687
+ "onPlay",
3688
+ "onPause",
3689
+ "onBuffer",
3690
+ "onBufferEnd",
3691
+ "onEnded",
3692
+ "onError",
3693
+ "onDuration",
3694
+ "onSeek",
3695
+ "onProgress",
3696
+ "onVolumeToggle",
3697
+ "onFullscreenToggle",
3698
+ "onControlClick"
3699
+ ];
3700
+ var customPlayers = [];
3701
+ var createStormcloudPlayer = (playerList, fallback) => {
3702
+ var _a;
3703
+ return _a = class extends import_react6.Component {
3704
+ constructor() {
3705
+ super(...arguments);
3706
+ this.state = {
3707
+ showPreview: false
3708
+ };
3709
+ this.references = {
3710
+ wrapper: (wrapper) => {
3711
+ this.wrapper = wrapper;
3712
+ },
3713
+ player: (player) => {
3714
+ this.player = player;
3715
+ }
3716
+ };
3717
+ this.getActivePlayer = (src) => {
3718
+ if (!src) return null;
3719
+ for (const player of [...customPlayers, ...playerList]) {
3720
+ if (player.canPlay(src)) {
3721
+ return player;
3722
+ }
3723
+ }
3724
+ if (fallback) {
3725
+ return fallback;
3726
+ }
3727
+ return null;
3728
+ };
3729
+ this.getAttributes = (src) => {
3730
+ return omit(this.props, SUPPORTED_PROPS);
3731
+ };
3732
+ this.handleReady = () => {
3733
+ this.props.onReady?.(this);
3734
+ };
3735
+ this.seekTo = (fraction, type, keepPlaying) => {
3736
+ if (!this.player) return null;
3737
+ this.player.seekTo(fraction, type, keepPlaying);
3738
+ };
3739
+ this.getCurrentTime = () => {
3740
+ if (!this.player) return null;
3741
+ return this.player.getCurrentTime();
3742
+ };
3743
+ this.getSecondsLoaded = () => {
3744
+ if (!this.player) return null;
3745
+ return this.player.getSecondsLoaded();
3746
+ };
3747
+ this.getDuration = () => {
3748
+ if (!this.player) return null;
3749
+ return this.player.getDuration();
3750
+ };
3751
+ this.getInternalPlayer = (key = "player") => {
3752
+ if (!this.player) return null;
3753
+ return this.player.getInternalPlayer(key);
3754
+ };
3755
+ this.renderActivePlayer = (src) => {
3756
+ if (!src) return null;
3757
+ const activePlayer = this.getActivePlayer(src);
3758
+ if (!activePlayer) return null;
3759
+ return import_react6.default.createElement(Player, {
3760
+ ...this.props,
3761
+ key: activePlayer.key,
3762
+ ref: this.references.player,
3763
+ activePlayer: activePlayer.lazyPlayer || activePlayer,
3764
+ onReady: this.handleReady
3765
+ });
3766
+ };
3767
+ }
3768
+ render() {
3769
+ const {
3770
+ src,
3771
+ style,
3772
+ width,
3773
+ height,
3774
+ fallback: fallbackElement,
3775
+ wrapper: Wrapper
3776
+ } = this.props;
3777
+ const attributes = this.getAttributes(src);
3778
+ const wrapperRef = typeof Wrapper === "string" ? this.references.wrapper : void 0;
3779
+ return import_react6.default.createElement(
3780
+ Wrapper,
3781
+ {
3782
+ ref: wrapperRef,
3783
+ style: { ...style, width, height },
3784
+ ...attributes
3785
+ },
3786
+ import_react6.default.createElement(
3787
+ UniversalSuspense,
3788
+ { fallback: fallbackElement },
3789
+ this.renderActivePlayer(src)
3790
+ )
3791
+ );
3792
+ }
3793
+ }, _a.displayName = "StormcloudPlayer", _a.defaultProps = {
3794
+ ...defaultProps,
3795
+ fallback: null,
3796
+ wrapper: "div"
3797
+ }, _a.addCustomPlayer = (player) => {
3798
+ customPlayers.push(player);
3799
+ }, _a.removeCustomPlayers = () => {
3800
+ customPlayers.length = 0;
3801
+ }, _a.canPlay = (src) => {
3802
+ for (const Player2 of [...customPlayers, ...playerList]) {
3803
+ if (Player2.canPlay(src)) {
3804
+ return true;
3805
+ }
3806
+ }
3807
+ return false;
3808
+ }, _a.canEnablePIP = (src) => {
3809
+ for (const Player2 of [...customPlayers, ...playerList]) {
3810
+ if (Player2.canEnablePIP && Player2.canEnablePIP(src)) {
3811
+ return true;
3812
+ }
3813
+ }
3814
+ return false;
3815
+ }, _a;
3816
+ };
3817
+ var StormcloudPlayer = createStormcloudPlayer(
3818
+ players_default,
3819
+ players_default[players_default.length - 1]
3813
3820
  );
3821
+ var StormcloudPlayer_default = StormcloudPlayer;
3814
3822
  // Annotate the CommonJS export names for ESM import in node:
3815
3823
  0 && (module.exports = {
3816
3824
  IS_BROWSER,
@@ -3819,6 +3827,7 @@ var StormcloudVideoPlayerComponent = import_react6.default.memo(
3819
3827
  IS_SAFARI,
3820
3828
  SUPPORTS_DASH,
3821
3829
  SUPPORTS_HLS,
3830
+ StormcloudPlayer,
3822
3831
  StormcloudVideoPlayer,
3823
3832
  StormcloudVideoPlayerComponent,
3824
3833
  canPlay,