UNPKG

react-advanced-video-player

Version:

React Advanced Video Player is a customizable and feature-rich video player for React applications. It supports multiple video formats, subtitles, quality selection, Picture-in-Picture mode, playback speed adjustment, and more, providing a seamless media

413 lines (409 loc) 19.3 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { VideoPlayer: () => VideoPlayer_default, default: () => index_default }); module.exports = __toCommonJS(index_exports); // src/components/VideoPlayer/index.tsx var import_react = require("react"); var import_lucide_react = require("lucide-react"); var import_jsx_runtime = require("react/jsx-runtime"); var formatTime = (time) => { const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); return `${minutes}:${seconds.toString().padStart(2, "0")}`; }; var VideoPlayer = ({ sources = [], subtitles = [], poster = "", onNext, onPrevious, autoPlay = false, className = "", theme = "default", isDeveloperModeDisabled = true }) => { const videoRef = (0, import_react.useRef)(null); const containerRef = (0, import_react.useRef)(null); const playPromiseRef = (0, import_react.useRef)(null); const [isPlaying, setIsPlaying] = (0, import_react.useState)(false); const [currentTime, setCurrentTime] = (0, import_react.useState)(0); const [duration, setDuration] = (0, import_react.useState)(0); const [volume, setVolume] = (0, import_react.useState)(1); const [isMuted, setIsMuted] = (0, import_react.useState)(false); const [isLocked, setIsLocked] = (0, import_react.useState)(false); const [isFullscreen, setIsFullscreen] = (0, import_react.useState)(false); const [showControls, setShowControls] = (0, import_react.useState)(true); const [showSettings, setShowSettings] = (0, import_react.useState)(false); const [currentQuality, setCurrentQuality] = (0, import_react.useState)(sources[0]); const [currentSubtitle, setCurrentSubtitle] = (0, import_react.useState)(null); const [playbackSpeed, setPlaybackSpeed] = (0, import_react.useState)(1); const [dropdowns, setDropdowns] = (0, import_react.useState)({ speed: false, quality: false, subtitles: false, audio: false }); (0, import_react.useEffect)(() => { const video = videoRef.current; if (!video) return; const handleTimeUpdate = () => { setCurrentTime(video.currentTime); }; const handleLoadedMetadata = () => { setDuration(video.duration); }; const handlePlay = () => { setIsPlaying(true); }; const handlePause = () => { setIsPlaying(false); }; video.addEventListener("timeupdate", handleTimeUpdate); video.addEventListener("loadedmetadata", handleLoadedMetadata); video.addEventListener("play", handlePlay); video.addEventListener("pause", handlePause); return () => { video.removeEventListener("timeupdate", handleTimeUpdate); video.removeEventListener("loadedmetadata", handleLoadedMetadata); video.removeEventListener("play", handlePlay); video.removeEventListener("pause", handlePause); }; }, []); (0, import_react.useEffect)(() => { const handleClickOutside = (event) => { if (containerRef.current && !containerRef.current.contains(event.target)) { setShowSettings(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); (0, import_react.useEffect)(() => { if (isDeveloperModeDisabled) { const disableRightClick = (event) => event.preventDefault(); document.addEventListener("contextmenu", disableRightClick); const disableShortcuts = (event) => { if (event.key === "F12" || event.ctrlKey && event.shiftKey && event.key === "I" || event.ctrlKey && event.shiftKey && event.key === "J" || event.ctrlKey && event.key === "U") { event.preventDefault(); } }; document.addEventListener("keydown", disableShortcuts); const checkDevTools = setInterval(() => { const widthThreshold = window.outerWidth - window.innerWidth > 200; const heightThreshold = window.outerHeight - window.innerHeight > 200; if (widthThreshold || heightThreshold) { alert("Developer tools detected! Access denied, Please closed developer tools"); window.location.reload(); return; } }, 1e3); return () => { document.removeEventListener("contextmenu", disableRightClick); document.removeEventListener("keydown", disableShortcuts); clearInterval(checkDevTools); }; } }, [isDeveloperModeDisabled]); const toggleDropdown = (key) => { setDropdowns((prev) => ({ ...prev, [key]: !prev[key] })); }; const togglePlay = async () => { if (isLocked || !videoRef?.current) return; try { if (isPlaying) { if (playPromiseRef?.current) { await playPromiseRef.current; } videoRef.current.pause(); } else { playPromiseRef.current = videoRef.current.play(); await playPromiseRef.current; } setShowSettings(false); } catch (error) { console.error("Error toggling play state:", error); } finally { playPromiseRef.current = null; } }; const handleProgress = (e) => { if (videoRef.current) { const time = parseFloat(e.target.value); videoRef.current.currentTime = time; setCurrentTime(time); } }; const handleVolumeChange = (e) => { if (videoRef.current) { const newVolume = parseFloat(e.target.value); videoRef.current.volume = newVolume; setVolume(newVolume); setIsMuted(newVolume === 0); } }; const toggleMute = () => { if (videoRef.current) { const newMutedState = !isMuted; videoRef.current.muted = newMutedState; setIsMuted(newMutedState); if (newMutedState) { setVolume(0); videoRef.current.volume = 0; } else { setVolume(1); videoRef.current.volume = 1; } } }; const toggleFullscreen = () => { if (!document.fullscreenElement) { containerRef.current?.requestFullscreen(); } else { document.exitFullscreen(); } setIsFullscreen(!isFullscreen); }; const changeQuality = async (source) => { if (videoRef.current) { const currentTime2 = videoRef.current.currentTime; const wasPlaying = !videoRef.current.paused; setCurrentQuality(source); videoRef.current.src = source?.src; videoRef.current.currentTime = currentTime2; if (wasPlaying) { try { playPromiseRef.current = videoRef.current.play(); await playPromiseRef.current; } catch (error) { console.error("Error resuming playback after quality change:", error); } finally { playPromiseRef.current = null; } } } setShowSettings(false); }; const changeSubtitle = (subtitle) => { if (videoRef.current) { while (videoRef.current.textTracks.length > 0) { videoRef.current.removeChild(videoRef.current.getElementsByTagName("track")[0]); } if (subtitle) { const track = document.createElement("track"); track.kind = "subtitles"; track.src = subtitle.src; track.srclang = subtitle?.srclang; track.label = subtitle?.label; track.default = true; videoRef.current.appendChild(track); } } setCurrentSubtitle(subtitle); setShowSettings(false); }; const changePlaybackSpeed = (speed) => { if (videoRef.current) { videoRef.current.playbackRate = speed; setPlaybackSpeed(speed); } setShowSettings(false); }; const fastForward = () => { if (videoRef.current) { videoRef.current.currentTime += 10; } }; const rewind = () => { if (videoRef.current) { videoRef.current.currentTime -= 10; } }; return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "div", { ref: containerRef, className: `video-container ${theme ? `theme-${theme}` : ""} ${className}`, onMouseEnter: () => !isLocked && setShowControls(true), onMouseLeave: () => !isLocked && setShowControls(false), children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "video", { ref: videoRef, className: "video-player", poster, autoPlay, onClick: togglePlay, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("source", { src: currentQuality?.src, type: "video/mp4" }), currentSubtitle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "track", { src: currentSubtitle.src, kind: "subtitles", srcLang: currentSubtitle.srclang, label: currentSubtitle.label, default: true } ) ] } ), !isLocked && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "play-pause-overlay", onClick: togglePlay, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "play-pause-button", children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Pause, { size: 30 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Play, { size: 30 }) }) }), isLocked && /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "button", { onClick: () => setIsLocked(false), className: "control-button unlock", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Unlock, { size: 22 }) } ), showControls && !isLocked && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "setting-controls-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setShowSettings(!showSettings), className: "control-button", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Settings, { size: 22 }) }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "controls-wrapper", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "progress-container", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "time-display", children: formatTime(currentTime) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "input", { type: "range", min: 0, max: duration || 0, value: currentTime, onChange: handleProgress, className: "progress-bar" } ), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "time-display end-time", children: formatTime(duration) }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "controls-bar", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "controls-left", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setIsLocked(true), className: "control-button", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Lock, { size: 22 }) }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "controls-center", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: rewind, className: "control-button", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Rewind, { size: 22 }) }), onPrevious && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: onPrevious, className: "control-button", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.SkipBack, { size: 22 }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: togglePlay, className: "control-button", children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Pause, { size: 27 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Play, { size: 27 }) }), onNext && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: onNext, className: "control-button", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.SkipForward, { size: 22 }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: fastForward, className: "control-button", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.FastForward, { size: 22 }) }) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "controls-right", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "volume-control", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: toggleMute, className: "control-button", children: isMuted ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.VolumeX, { size: 22 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Volume2, { size: 22 }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "input", { type: "range", min: 0, max: 1, step: 0.1, value: volume, onChange: handleVolumeChange, className: "volume-slider" } ) ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: toggleFullscreen, className: "control-button", children: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Minimize, { size: 22 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Maximize, { size: 22 }) }) ] }) ] }) ] }) ] }), showSettings && !isLocked && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "settings-panel", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "settings-menu", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { onClick: () => toggleDropdown("speed"), className: "settings-button", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Gauge, { size: 18 }), " Playback Speed" ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ChevronDown, { size: 18 }) ] }), dropdowns.speed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "settings-dropdown", children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "button", { onClick: () => changePlaybackSpeed(speed), className: `dropdown-item ${playbackSpeed === speed ? "active" : ""}`, children: [ speed, "x" ] }, speed )) }), sources && sources?.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { onClick: () => toggleDropdown("quality"), className: "settings-button", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Video, { size: 18 }), " Quality" ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ChevronDown, { size: 18 }) ] }), dropdowns?.quality && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "settings-dropdown", children: sources?.map((source) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "button", { onClick: () => changeQuality(source), className: `dropdown-item ${currentQuality?.quality === source?.quality ? "active" : ""}`, children: source?.label }, source?.quality )) }) ] }), subtitles && subtitles?.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { onClick: () => toggleDropdown("subtitles"), className: "settings-button", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Subtitles, { size: 18 }), " Subtitles" ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ChevronDown, { size: 18 }) ] }), dropdowns?.subtitles && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "settings-dropdown", children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "button", { onClick: () => changeSubtitle(null), className: `dropdown-item ${currentSubtitle === null ? "active" : ""}`, children: "Off" } ), subtitles?.map((subtitle) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "button", { onClick: () => changeSubtitle(subtitle), className: `dropdown-item ${currentSubtitle?.srclang === subtitle?.srclang ? "active" : ""}`, children: subtitle?.label }, subtitle?.srclang )) ] }) ] }) ] }) }) ] } ); }; var VideoPlayer_default = VideoPlayer; // src/index.ts var index_default = VideoPlayer_default; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { VideoPlayer });