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
405 lines (403 loc) • 16 kB
JavaScript
// src/components/VideoPlayer/index.tsx
import { useRef, useState, useEffect } from "react";
import {
Play,
Pause,
SkipForward,
SkipBack,
Volume2,
VolumeX,
Settings,
Lock,
Unlock,
Maximize,
Minimize,
FastForward,
Rewind,
ChevronDown,
Gauge,
Video,
Subtitles
} from "lucide-react";
import { Fragment, jsx, jsxs } from "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 = useRef(null);
const containerRef = useRef(null);
const playPromiseRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [volume, setVolume] = useState(1);
const [isMuted, setIsMuted] = useState(false);
const [isLocked, setIsLocked] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
const [showControls, setShowControls] = useState(true);
const [showSettings, setShowSettings] = useState(false);
const [currentQuality, setCurrentQuality] = useState(sources[0]);
const [currentSubtitle, setCurrentSubtitle] = useState(null);
const [playbackSpeed, setPlaybackSpeed] = useState(1);
const [dropdowns, setDropdowns] = useState({
speed: false,
quality: false,
subtitles: false,
audio: false
});
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);
};
}, []);
useEffect(() => {
const handleClickOutside = (event) => {
if (containerRef.current && !containerRef.current.contains(event.target)) {
setShowSettings(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
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__ */ jsxs(
"div",
{
ref: containerRef,
className: `video-container ${theme ? `theme-${theme}` : ""} ${className}`,
onMouseEnter: () => !isLocked && setShowControls(true),
onMouseLeave: () => !isLocked && setShowControls(false),
children: [
/* @__PURE__ */ jsxs(
"video",
{
ref: videoRef,
className: "video-player",
poster,
autoPlay,
onClick: togglePlay,
children: [
/* @__PURE__ */ jsx("source", { src: currentQuality?.src, type: "video/mp4" }),
currentSubtitle && /* @__PURE__ */ jsx(
"track",
{
src: currentSubtitle.src,
kind: "subtitles",
srcLang: currentSubtitle.srclang,
label: currentSubtitle.label,
default: true
}
)
]
}
),
!isLocked && /* @__PURE__ */ jsx("div", { className: "play-pause-overlay", onClick: togglePlay, children: /* @__PURE__ */ jsx("button", { className: "play-pause-button", children: isPlaying ? /* @__PURE__ */ jsx(Pause, { size: 30 }) : /* @__PURE__ */ jsx(Play, { size: 30 }) }) }),
isLocked && /* @__PURE__ */ jsx(
"button",
{
onClick: () => setIsLocked(false),
className: "control-button unlock",
children: /* @__PURE__ */ jsx(Unlock, { size: 22 })
}
),
showControls && !isLocked && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", { className: "setting-controls-wrapper", children: /* @__PURE__ */ jsx("button", { onClick: () => setShowSettings(!showSettings), className: "control-button", children: /* @__PURE__ */ jsx(Settings, { size: 22 }) }) }),
/* @__PURE__ */ jsxs("div", { className: "controls-wrapper", children: [
/* @__PURE__ */ jsxs("div", { className: "progress-container", children: [
/* @__PURE__ */ jsx("span", { className: "time-display", children: formatTime(currentTime) }),
/* @__PURE__ */ jsx(
"input",
{
type: "range",
min: 0,
max: duration || 0,
value: currentTime,
onChange: handleProgress,
className: "progress-bar"
}
),
/* @__PURE__ */ jsx("span", { className: "time-display end-time", children: formatTime(duration) })
] }),
/* @__PURE__ */ jsxs("div", { className: "controls-bar", children: [
/* @__PURE__ */ jsx("div", { className: "controls-left", children: /* @__PURE__ */ jsx("button", { onClick: () => setIsLocked(true), className: "control-button", children: /* @__PURE__ */ jsx(Lock, { size: 22 }) }) }),
/* @__PURE__ */ jsxs("div", { className: "controls-center", children: [
/* @__PURE__ */ jsx("button", { onClick: rewind, className: "control-button", children: /* @__PURE__ */ jsx(Rewind, { size: 22 }) }),
onPrevious && /* @__PURE__ */ jsx("button", { onClick: onPrevious, className: "control-button", children: /* @__PURE__ */ jsx(SkipBack, { size: 22 }) }),
/* @__PURE__ */ jsx("button", { onClick: togglePlay, className: "control-button", children: isPlaying ? /* @__PURE__ */ jsx(Pause, { size: 27 }) : /* @__PURE__ */ jsx(Play, { size: 27 }) }),
onNext && /* @__PURE__ */ jsx("button", { onClick: onNext, className: "control-button", children: /* @__PURE__ */ jsx(SkipForward, { size: 22 }) }),
/* @__PURE__ */ jsx("button", { onClick: fastForward, className: "control-button", children: /* @__PURE__ */ jsx(FastForward, { size: 22 }) })
] }),
/* @__PURE__ */ jsxs("div", { className: "controls-right", children: [
/* @__PURE__ */ jsxs("div", { className: "volume-control", children: [
/* @__PURE__ */ jsx("button", { onClick: toggleMute, className: "control-button", children: isMuted ? /* @__PURE__ */ jsx(VolumeX, { size: 22 }) : /* @__PURE__ */ jsx(Volume2, { size: 22 }) }),
/* @__PURE__ */ jsx(
"input",
{
type: "range",
min: 0,
max: 1,
step: 0.1,
value: volume,
onChange: handleVolumeChange,
className: "volume-slider"
}
)
] }),
/* @__PURE__ */ jsx("button", { onClick: toggleFullscreen, className: "control-button", children: isFullscreen ? /* @__PURE__ */ jsx(Minimize, { size: 22 }) : /* @__PURE__ */ jsx(Maximize, { size: 22 }) })
] })
] })
] })
] }),
showSettings && !isLocked && /* @__PURE__ */ jsx("div", { className: "settings-panel", children: /* @__PURE__ */ jsxs("div", { className: "settings-menu", children: [
/* @__PURE__ */ jsxs("button", { onClick: () => toggleDropdown("speed"), className: "settings-button", children: [
/* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
/* @__PURE__ */ jsx(Gauge, { size: 18 }),
" Playback Speed"
] }),
/* @__PURE__ */ jsx(ChevronDown, { size: 18 })
] }),
dropdowns.speed && /* @__PURE__ */ jsx("div", { className: "settings-dropdown", children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ jsxs(
"button",
{
onClick: () => changePlaybackSpeed(speed),
className: `dropdown-item ${playbackSpeed === speed ? "active" : ""}`,
children: [
speed,
"x"
]
},
speed
)) }),
sources && sources?.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs("button", { onClick: () => toggleDropdown("quality"), className: "settings-button", children: [
/* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
/* @__PURE__ */ jsx(Video, { size: 18 }),
" Quality"
] }),
/* @__PURE__ */ jsx(ChevronDown, { size: 18 })
] }),
dropdowns?.quality && /* @__PURE__ */ jsx("div", { className: "settings-dropdown", children: sources?.map((source) => /* @__PURE__ */ jsx(
"button",
{
onClick: () => changeQuality(source),
className: `dropdown-item ${currentQuality?.quality === source?.quality ? "active" : ""}`,
children: source?.label
},
source?.quality
)) })
] }),
subtitles && subtitles?.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs("button", { onClick: () => toggleDropdown("subtitles"), className: "settings-button", children: [
/* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
/* @__PURE__ */ jsx(Subtitles, { size: 18 }),
" Subtitles"
] }),
/* @__PURE__ */ jsx(ChevronDown, { size: 18 })
] }),
dropdowns?.subtitles && /* @__PURE__ */ jsxs("div", { className: "settings-dropdown", children: [
/* @__PURE__ */ jsx(
"button",
{
onClick: () => changeSubtitle(null),
className: `dropdown-item ${currentSubtitle === null ? "active" : ""}`,
children: "Off"
}
),
subtitles?.map((subtitle) => /* @__PURE__ */ 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;
export {
VideoPlayer_default as VideoPlayer,
index_default as default
};