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
JavaScript
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
});