nexora / frontend /src /components /MoviePlayer.js
ChandimaPrabath's picture
0.0.2 Alpha player
2a108da
raw
history blame
No virus
9.31 kB
"use client";
import { useEffect, useRef, useState } from "react";
import "./MoviePlayer.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faFastBackward,
faFastForward,
faPause,
faPlay,
faVolumeUp,
faVolumeMute,
faExpand,
faDownload,
faArrowLeft,
faCaretLeft,
faAngleLeft,
} from "@fortawesome/free-solid-svg-icons";
import { Spinner } from "@/components/Spinner";
import SeekableProgressBar from "./SeekableProgressBar";
export default function MoviePlayer({ videoUrl, title }) {
const videoRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const [volume, setVolume] = useState(1);
const [isMuted, setIsMuted] = useState(false);
const [progress, setProgress] = useState(0);
const [buffer, setBuffer] = useState(0); // Buffer state
const [isFullscreen, setIsFullscreen] = useState(false);
const [showControls, setShowControls] = useState(true);
const [isBuffering, setIsBuffering] = useState(false);
const overlayTimeout = useRef(null);
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 });
const playerVersion = "0.0.2 Alpha";
useEffect(() => {
const videoElement = videoRef.current;
const handlePlay = () => setIsPlaying(true);
const handlePause = () => setIsPlaying(false);
const handleTimeUpdate = () => {
setProgress((videoElement.currentTime / videoElement.duration) * 100);
updateBuffer(); // Update buffer on time update
};
const handleWaiting = () => setIsBuffering(true);
const handlePlaying = () => setIsBuffering(false);
const handleProgress = () => updateBuffer(); // Update buffer on progress
videoElement.addEventListener("play", handlePlay);
videoElement.addEventListener("pause", handlePause);
videoElement.addEventListener("timeupdate", handleTimeUpdate);
videoElement.addEventListener("waiting", handleWaiting);
videoElement.addEventListener("playing", handlePlaying);
videoElement.addEventListener("progress", handleProgress); // Listen to progress events
return () => {
videoElement.removeEventListener("play", handlePlay);
videoElement.removeEventListener("pause", handlePause);
videoElement.removeEventListener("timeupdate", handleTimeUpdate);
videoElement.removeEventListener("waiting", handleWaiting);
videoElement.removeEventListener("playing", handlePlaying);
videoElement.removeEventListener("progress", handleProgress); // Clean up progress listener
};
}, []);
useEffect(() => {
if (showControls) {
if (overlayTimeout.current) {
clearTimeout(overlayTimeout.current);
}
overlayTimeout.current = setTimeout(() => setShowControls(false), 3000);
}
return () => clearTimeout(overlayTimeout.current);
}, [showControls]);
const handleContextMenu = (event) => {
event.preventDefault();
setContextMenu({
visible: true,
x: event.pageX,
y: event.pageY,
});
};
const hideContextMenu = () => {
setContextMenu({ visible: false, x: 0, y: 0 });
};
useEffect(() => {
window.addEventListener("click", hideContextMenu);
return () => {
window.removeEventListener("click", hideContextMenu);
};
}, []);
const handleFastForward = () => {
if (videoRef.current) {
videoRef.current.currentTime = Math.min(
videoRef.current.duration,
videoRef.current.currentTime + 10
);
}
};
const handleRewind = () => {
if (videoRef.current) {
videoRef.current.currentTime = Math.max(
0,
videoRef.current.currentTime - 10
);
}
};
const togglePlayPause = () => {
if (isPlaying) {
videoRef.current.pause();
setShowControls(true);
} else {
videoRef.current.play();
setShowControls(false);
}
};
const handleVolumeChange = (event) => {
const volumeValue = event.target.value;
setVolume(volumeValue);
videoRef.current.volume = volumeValue;
setIsMuted(volumeValue === 0);
};
const toggleMute = () => {
if (isMuted) {
videoRef.current.volume = volume;
setIsMuted(false);
} else {
videoRef.current.volume = 0;
setIsMuted(true);
}
};
const toggleFullscreen = () => {
const doc = window.document;
const docEl = doc.documentElement;
const requestFullscreen =
docEl.requestFullscreen ||
docEl.mozRequestFullScreen ||
docEl.webkitRequestFullscreen ||
docEl.msRequestFullscreen;
const exitFullscreen =
doc.exitFullscreen ||
doc.mozCancelFullScreen ||
docEl.webkitExitFullscreen ||
doc.msExitFullscreen;
if (!isFullscreen) {
requestFullscreen.call(docEl);
} else {
exitFullscreen.call(doc);
}
setIsFullscreen(!isFullscreen);
};
const handleSeek = (newProgress) => {
videoRef.current.currentTime = (newProgress / 100) * videoRef.current.duration;
setProgress(newProgress);
};
const handleMouseMove = () => {
setShowControls(true);
};
const formatTime = (seconds) => {
if (isNaN(seconds)) {
return '00:00:00';
}
const wholeSeconds = Math.floor(seconds);
const hours = Math.floor(wholeSeconds / 3600);
const minutes = Math.floor((wholeSeconds % 3600) / 60);
const secs = wholeSeconds % 60;
const formattedHours = String(hours).padStart(2, '0');
const formattedMinutes = String(minutes).padStart(2, '0');
const formattedSeconds = String(secs).padStart(2, '0');
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
};
const updateBuffer = () => {
const videoElement = videoRef.current;
if (videoElement.buffered.length > 0) {
const bufferEnd = videoElement.buffered.end(videoElement.buffered.length - 1);
const bufferValue = (bufferEnd / videoElement.duration) * 100;
setBuffer(bufferValue);
}
};
return (
<div className="video-player-container" onMouseMove={handleMouseMove} onContextMenu={handleContextMenu}>
<div className={`player-overlay ${showControls ? "show" : "hide"}`}>
<h2 className="video-title"><div className="control-btn"><FontAwesomeIcon icon={faAngleLeft} size="lg"/></div>{title}</h2>
<div className="controls">
<div className="player-controls-top">
<label className="current-time">{formatTime(videoRef?.current?.currentTime)}</label>
<SeekableProgressBar progress={progress} buffer={buffer} onSeek={handleSeek} />
<label className="duration">{formatTime(videoRef?.current?.duration)}</label>
</div>
<div className="player-controls-down">
<div className="player-controls-left">
<button
onClick={handleRewind}
className="control-btn"
disabled={!isPlaying}
>
<FontAwesomeIcon icon={faFastBackward} size="xl" />
</button>
<button onClick={togglePlayPause} className="play-pause-btn">
{isPlaying ? (
<FontAwesomeIcon icon={faPause} size="xl" />
) : (
<FontAwesomeIcon icon={faPlay} size="xl" />
)}
</button>
<button
onClick={handleFastForward}
className="control-btn"
disabled={!isPlaying}
>
<FontAwesomeIcon icon={faFastForward} size="xl" />
</button>
</div>
<div className="player-controls-right">
<button onClick={toggleMute} className="control-btn">
<FontAwesomeIcon
icon={isMuted ? faVolumeMute : faVolumeUp}
size="xl"
/>
</button>
<input
type="range"
className="volume-control"
min="0"
max="1"
step="0.01"
value={volume}
onChange={handleVolumeChange}
/>
<button onClick={toggleFullscreen} className="control-btn">
<FontAwesomeIcon icon={faExpand} size="xl" />
</button>
{videoUrl && (
<a href={videoUrl} download className="control-btn">
<FontAwesomeIcon icon={faDownload} size="xl" />
</a>
)}
</div>
</div>
</div>
</div>
<video
ref={videoRef}
className="video-element"
controls={false}
preload="auto"
src={videoUrl}
>
<track
kind="subtitles"
label="English"
srcLang="en"
src="path/to/your/subtitles.vtt" // Replace with actual subtitles URL
/>
Your browser does not support the video tag.
</video>
{isBuffering && <div className="buffering-indicator"><div className="postion-fix"><Spinner/></div></div>}
{contextMenu.visible && (
<div
className="context-menu"
style={{ left: contextMenu.x, top: contextMenu.y }}
>
<ul>
<li>Player Version: {playerVersion}</li>
</ul>
</div>
)}
</div>
);
}