Commit ·
0d81ded
1
Parent(s): 689380a
player 0.0.3 Alpha
Browse files
frontend/src/app/globals.css
CHANGED
|
@@ -8,6 +8,7 @@
|
|
| 8 |
--bg-primary: #11121f;
|
| 9 |
--bg-secondary: #0c0c16;
|
| 10 |
--primary-special-color:#4343ff;
|
|
|
|
| 11 |
--gray-text-color:#d8d8d8;
|
| 12 |
--card-color: #1b1b3b;
|
| 13 |
}
|
|
|
|
| 8 |
--bg-primary: #11121f;
|
| 9 |
--bg-secondary: #0c0c16;
|
| 10 |
--primary-special-color:#4343ff;
|
| 11 |
+
--player-primary: rgb(79, 79, 216);
|
| 12 |
--gray-text-color:#d8d8d8;
|
| 13 |
--card-color: #1b1b3b;
|
| 14 |
}
|
frontend/src/components/Player/Player.css
CHANGED
|
@@ -45,10 +45,12 @@
|
|
| 45 |
.player-overlay.hide {
|
| 46 |
opacity: 0;
|
| 47 |
pointer-events: none;
|
|
|
|
| 48 |
}
|
| 49 |
|
| 50 |
.player-overlay.show {
|
| 51 |
opacity: 1;
|
|
|
|
| 52 |
}
|
| 53 |
|
| 54 |
.player-controls-top {
|
|
@@ -59,6 +61,39 @@
|
|
| 59 |
align-items: center;
|
| 60 |
}
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
.player-controls-down {
|
| 63 |
display: flex;
|
| 64 |
flex-direction: row;
|
|
@@ -98,8 +133,9 @@
|
|
| 98 |
|
| 99 |
.play-pause-btn,
|
| 100 |
.control-btn {
|
| 101 |
-
color:
|
| 102 |
border-radius: 10px;
|
|
|
|
| 103 |
padding: 10px;
|
| 104 |
cursor: pointer;
|
| 105 |
font-size: 1em;
|
|
@@ -155,7 +191,7 @@
|
|
| 155 |
position: absolute;
|
| 156 |
color: white;
|
| 157 |
background: rgb(29, 31, 57);
|
| 158 |
-
border: 1px solid
|
| 159 |
border-radius: 10px;
|
| 160 |
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
| 161 |
z-index: 1000;
|
|
@@ -174,10 +210,6 @@
|
|
| 174 |
border-radius: 5px;
|
| 175 |
}
|
| 176 |
|
| 177 |
-
.context-menu li:hover {
|
| 178 |
-
background-color: #5755a2;
|
| 179 |
-
}
|
| 180 |
-
|
| 181 |
/********** Range Input Styles **********/
|
| 182 |
/*Range Reset*/
|
| 183 |
input[type="range"] {
|
|
@@ -206,7 +238,7 @@ input[type="range"]::-webkit-slider-thumb {
|
|
| 206 |
appearance: none;
|
| 207 |
margin-top: -6px; /* Centers thumb on the track */
|
| 208 |
/*custom styles*/
|
| 209 |
-
background-color:
|
| 210 |
height: 1.2rem;
|
| 211 |
width: 0.8rem;
|
| 212 |
border-radius: 10px;
|
|
@@ -224,7 +256,7 @@ input[type="range"]::-moz-range-track {
|
|
| 224 |
input[type="range"]::-moz-range-thumb {
|
| 225 |
border: none; /*Removes extra border that FF applies*/
|
| 226 |
/*custom styles*/
|
| 227 |
-
background-color:
|
| 228 |
height: 1.2rem;
|
| 229 |
width: 0.8rem;
|
| 230 |
border-radius: 10px;
|
|
|
|
| 45 |
.player-overlay.hide {
|
| 46 |
opacity: 0;
|
| 47 |
pointer-events: none;
|
| 48 |
+
background-color: transparent;
|
| 49 |
}
|
| 50 |
|
| 51 |
.player-overlay.show {
|
| 52 |
opacity: 1;
|
| 53 |
+
background-color: #00000062;
|
| 54 |
}
|
| 55 |
|
| 56 |
.player-controls-top {
|
|
|
|
| 61 |
align-items: center;
|
| 62 |
}
|
| 63 |
|
| 64 |
+
.player-controls-center {
|
| 65 |
+
gap: 5dvw;
|
| 66 |
+
display: flex;
|
| 67 |
+
justify-content: center;
|
| 68 |
+
align-items: center;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
.player-controls-center .control-btn,
|
| 72 |
+
.player-controls-center .play-pause-btn {
|
| 73 |
+
cursor: pointer;
|
| 74 |
+
font-size: 3.5em;
|
| 75 |
+
transition: background-color 0.3s;
|
| 76 |
+
color: var(--player-primary);
|
| 77 |
+
border: none !important;
|
| 78 |
+
min-width: 10%;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.rewind-label-left{
|
| 82 |
+
font-size: 2rem;
|
| 83 |
+
font-weight: 600;
|
| 84 |
+
position: absolute;
|
| 85 |
+
transform: translate(2rem, 1.5rem);
|
| 86 |
+
color: white;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.rewind-label-right{
|
| 90 |
+
font-size: 2rem;
|
| 91 |
+
font-weight: 600;
|
| 92 |
+
position: absolute;
|
| 93 |
+
transform: translate(-4rem, 1.5rem);
|
| 94 |
+
color: white;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
.player-controls-down {
|
| 98 |
display: flex;
|
| 99 |
flex-direction: row;
|
|
|
|
| 133 |
|
| 134 |
.play-pause-btn,
|
| 135 |
.control-btn {
|
| 136 |
+
color: var(--player-primary);
|
| 137 |
border-radius: 10px;
|
| 138 |
+
border: none;
|
| 139 |
padding: 10px;
|
| 140 |
cursor: pointer;
|
| 141 |
font-size: 1em;
|
|
|
|
| 191 |
position: absolute;
|
| 192 |
color: white;
|
| 193 |
background: rgb(29, 31, 57);
|
| 194 |
+
border: 1px solid var(--player-primary);
|
| 195 |
border-radius: 10px;
|
| 196 |
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
| 197 |
z-index: 1000;
|
|
|
|
| 210 |
border-radius: 5px;
|
| 211 |
}
|
| 212 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
/********** Range Input Styles **********/
|
| 214 |
/*Range Reset*/
|
| 215 |
input[type="range"] {
|
|
|
|
| 238 |
appearance: none;
|
| 239 |
margin-top: -6px; /* Centers thumb on the track */
|
| 240 |
/*custom styles*/
|
| 241 |
+
background-color: var(--player-primary);
|
| 242 |
height: 1.2rem;
|
| 243 |
width: 0.8rem;
|
| 244 |
border-radius: 10px;
|
|
|
|
| 256 |
input[type="range"]::-moz-range-thumb {
|
| 257 |
border: none; /*Removes extra border that FF applies*/
|
| 258 |
/*custom styles*/
|
| 259 |
+
background-color: var(--player-primary);
|
| 260 |
height: 1.2rem;
|
| 261 |
width: 0.8rem;
|
| 262 |
border-radius: 10px;
|
frontend/src/components/Player/Player.js
CHANGED
|
@@ -9,16 +9,26 @@ import {
|
|
| 9 |
faVolumeUp,
|
| 10 |
faVolumeMute,
|
| 11 |
faExpand,
|
| 12 |
-
faDownload,
|
| 13 |
faArrowLeft,
|
| 14 |
-
faAngleDoubleRight,
|
| 15 |
-
faAngleDoubleLeft,
|
| 16 |
faCompress,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
} from "@fortawesome/free-solid-svg-icons";
|
|
|
|
|
|
|
| 18 |
import { Spinner } from "@/components/shared/Spinner/Spinner";
|
| 19 |
import SeekableProgressBar from "@/components/shared/ProgressBar/SeekableProgressBar";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
export default function
|
| 22 |
const router = useRouter();
|
| 23 |
const videoRef = useRef(null);
|
| 24 |
const [isPlaying, setIsPlaying] = useState(false);
|
|
@@ -28,31 +38,32 @@ export default function MoviePlayer({ videoUrl, title, type, episode=null }) {
|
|
| 28 |
const [buffer, setBuffer] = useState(0);
|
| 29 |
const [isFullscreen, setIsFullscreen] = useState(false);
|
| 30 |
const [showControls, setShowControls] = useState(true);
|
| 31 |
-
const [isBuffering, setIsBuffering] = useState(true);
|
| 32 |
const overlayTimeout = useRef(null);
|
| 33 |
const [contextMenu, setContextMenu] = useState({
|
| 34 |
visible: false,
|
| 35 |
x: 0,
|
| 36 |
y: 0,
|
| 37 |
});
|
| 38 |
-
const playerVersion = "0.0.
|
| 39 |
-
|
| 40 |
-
const getFileNameWithoutExtension = (episodeFile) => {
|
| 41 |
-
if (!episodeFile) return null;
|
| 42 |
-
|
| 43 |
-
const lastDotIndex = episodeFile.lastIndexOf('.');
|
| 44 |
-
if (lastDotIndex === -1) return episodeFile; // No dot found, return the original string
|
| 45 |
-
|
| 46 |
-
return episodeFile.substring(0, lastDotIndex);
|
| 47 |
-
};
|
| 48 |
|
| 49 |
useEffect(() => {
|
| 50 |
const videoElement = videoRef.current;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
const handlePlay = () => setIsPlaying(true);
|
| 53 |
const handlePause = () => setIsPlaying(false);
|
| 54 |
const handleTimeUpdate = () => {
|
| 55 |
setProgress((videoElement.currentTime / videoElement.duration) * 100);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
updateBuffer(); // Update buffer on time update
|
| 57 |
};
|
| 58 |
const handleWaiting = () => setIsBuffering(true);
|
|
@@ -192,21 +203,6 @@ export default function MoviePlayer({ videoUrl, title, type, episode=null }) {
|
|
| 192 |
setShowControls(true);
|
| 193 |
};
|
| 194 |
|
| 195 |
-
const formatTime = (seconds) => {
|
| 196 |
-
if (isNaN(seconds)) {
|
| 197 |
-
return "00:00:00";
|
| 198 |
-
}
|
| 199 |
-
const wholeSeconds = Math.floor(seconds);
|
| 200 |
-
const hours = Math.floor(wholeSeconds / 3600);
|
| 201 |
-
const minutes = Math.floor((wholeSeconds % 3600) / 60);
|
| 202 |
-
const secs = wholeSeconds % 60;
|
| 203 |
-
|
| 204 |
-
const formattedHours = String(hours).padStart(2, "0");
|
| 205 |
-
const formattedMinutes = String(minutes).padStart(2, "0");
|
| 206 |
-
const formattedSeconds = String(secs).padStart(2, "0");
|
| 207 |
-
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
|
| 208 |
-
};
|
| 209 |
-
|
| 210 |
const updateBuffer = () => {
|
| 211 |
const videoElement = videoRef.current;
|
| 212 |
if (videoElement.buffered.length > 0) {
|
|
@@ -228,13 +224,31 @@ export default function MoviePlayer({ videoUrl, title, type, episode=null }) {
|
|
| 228 |
onMouseMove={handleMouseMove}
|
| 229 |
onContextMenu={handleContextMenu}
|
| 230 |
>
|
|
|
|
| 231 |
<div className={`player-overlay ${showControls ? "show" : "hide"}`}>
|
| 232 |
<h2 className="video-title">
|
| 233 |
<div className="control-btn player-exit" onClick={handleExitClick}>
|
| 234 |
<FontAwesomeIcon icon={faArrowLeft} size="lg" />
|
| 235 |
</div>
|
| 236 |
-
{episode ? getFileNameWithoutExtension(episode):title}
|
| 237 |
</h2>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
<div className="controls">
|
| 239 |
<div className="player-controls-top">
|
| 240 |
<label className="current-time">
|
|
@@ -251,21 +265,6 @@ export default function MoviePlayer({ videoUrl, title, type, episode=null }) {
|
|
| 251 |
</div>
|
| 252 |
<div className="player-controls-down">
|
| 253 |
<div className="player-controls-left">
|
| 254 |
-
<button onClick={handleRewind} className="control-btn">
|
| 255 |
-
<FontAwesomeIcon icon={faAngleDoubleLeft} size="xl" />
|
| 256 |
-
</button>
|
| 257 |
-
<button onClick={togglePlayPause} className="play-pause-btn">
|
| 258 |
-
{isPlaying ? (
|
| 259 |
-
<FontAwesomeIcon icon={faPause} size="xl" />
|
| 260 |
-
) : (
|
| 261 |
-
<FontAwesomeIcon icon={faPlay} size="xl" />
|
| 262 |
-
)}
|
| 263 |
-
</button>
|
| 264 |
-
<button onClick={handleFastForward} className="control-btn">
|
| 265 |
-
<FontAwesomeIcon icon={faAngleDoubleRight} size="xl" />
|
| 266 |
-
</button>
|
| 267 |
-
</div>
|
| 268 |
-
<div className="player-controls-right">
|
| 269 |
<button onClick={toggleMute} className="control-btn">
|
| 270 |
<FontAwesomeIcon
|
| 271 |
icon={isMuted ? faVolumeMute : faVolumeUp}
|
|
@@ -281,17 +280,44 @@ export default function MoviePlayer({ videoUrl, title, type, episode=null }) {
|
|
| 281 |
value={volume}
|
| 282 |
onChange={handleVolumeChange}
|
| 283 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
<button onClick={toggleFullscreen} className="control-btn">
|
| 285 |
<FontAwesomeIcon
|
| 286 |
icon={isFullscreen ? faCompress : faExpand}
|
| 287 |
size="xl"
|
| 288 |
/>
|
| 289 |
</button>
|
| 290 |
-
{videoUrl && (
|
| 291 |
<a href={videoUrl} download className="control-btn">
|
| 292 |
<FontAwesomeIcon icon={faDownload} size="xl" />
|
| 293 |
</a>
|
| 294 |
-
)}
|
| 295 |
</div>
|
| 296 |
</div>
|
| 297 |
</div>
|
|
@@ -303,13 +329,13 @@ export default function MoviePlayer({ videoUrl, title, type, episode=null }) {
|
|
| 303 |
src={videoUrl}
|
| 304 |
autoPlay={true}
|
| 305 |
>
|
| 306 |
-
<track
|
| 307 |
kind="subtitles"
|
| 308 |
label="English"
|
| 309 |
srcLang="en"
|
| 310 |
src="/My.Spy.The.Eternal.City.(2024).WEB.vtt"
|
| 311 |
default
|
| 312 |
-
/>
|
| 313 |
Your browser does not support the video tag.
|
| 314 |
</video>
|
| 315 |
{isBuffering && (
|
|
@@ -331,4 +357,4 @@ export default function MoviePlayer({ videoUrl, title, type, episode=null }) {
|
|
| 331 |
)}
|
| 332 |
</div>
|
| 333 |
);
|
| 334 |
-
}
|
|
|
|
| 9 |
faVolumeUp,
|
| 10 |
faVolumeMute,
|
| 11 |
faExpand,
|
|
|
|
| 12 |
faArrowLeft,
|
|
|
|
|
|
|
| 13 |
faCompress,
|
| 14 |
+
faArrowRotateLeft,
|
| 15 |
+
faArrowRotateRight,
|
| 16 |
+
faClosedCaptioning as ccOn,
|
| 17 |
+
faBackwardStep,
|
| 18 |
+
faForwardStep,
|
| 19 |
+
faRectangleList as playlistOn,
|
| 20 |
} from "@fortawesome/free-solid-svg-icons";
|
| 21 |
+
import { faClosedCaptioning as ccOff, faRectangleList as playlistOff } from "@fortawesome/free-regular-svg-icons";
|
| 22 |
+
|
| 23 |
import { Spinner } from "@/components/shared/Spinner/Spinner";
|
| 24 |
import SeekableProgressBar from "@/components/shared/ProgressBar/SeekableProgressBar";
|
| 25 |
+
import {
|
| 26 |
+
getFileNameWithoutExtension,
|
| 27 |
+
formatTime,
|
| 28 |
+
getStorageKey,
|
| 29 |
+
} from "./utils";
|
| 30 |
|
| 31 |
+
export default function Player({ videoUrl, title, type, episode = null }) {
|
| 32 |
const router = useRouter();
|
| 33 |
const videoRef = useRef(null);
|
| 34 |
const [isPlaying, setIsPlaying] = useState(false);
|
|
|
|
| 38 |
const [buffer, setBuffer] = useState(0);
|
| 39 |
const [isFullscreen, setIsFullscreen] = useState(false);
|
| 40 |
const [showControls, setShowControls] = useState(true);
|
| 41 |
+
const [isBuffering, setIsBuffering] = useState(true);
|
| 42 |
const overlayTimeout = useRef(null);
|
| 43 |
const [contextMenu, setContextMenu] = useState({
|
| 44 |
visible: false,
|
| 45 |
x: 0,
|
| 46 |
y: 0,
|
| 47 |
});
|
| 48 |
+
const playerVersion = "0.0.3 Alpha";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
useEffect(() => {
|
| 51 |
const videoElement = videoRef.current;
|
| 52 |
+
const savedProgress = localStorage.getItem(
|
| 53 |
+
getStorageKey(type, title, episode)
|
| 54 |
+
);
|
| 55 |
+
if (savedProgress) {
|
| 56 |
+
videoElement.currentTime = parseFloat(savedProgress);
|
| 57 |
+
}
|
| 58 |
|
| 59 |
const handlePlay = () => setIsPlaying(true);
|
| 60 |
const handlePause = () => setIsPlaying(false);
|
| 61 |
const handleTimeUpdate = () => {
|
| 62 |
setProgress((videoElement.currentTime / videoElement.duration) * 100);
|
| 63 |
+
localStorage.setItem(
|
| 64 |
+
getStorageKey(type, title, episode),
|
| 65 |
+
videoElement.currentTime.toString()
|
| 66 |
+
);
|
| 67 |
updateBuffer(); // Update buffer on time update
|
| 68 |
};
|
| 69 |
const handleWaiting = () => setIsBuffering(true);
|
|
|
|
| 203 |
setShowControls(true);
|
| 204 |
};
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
const updateBuffer = () => {
|
| 207 |
const videoElement = videoRef.current;
|
| 208 |
if (videoElement.buffered.length > 0) {
|
|
|
|
| 224 |
onMouseMove={handleMouseMove}
|
| 225 |
onContextMenu={handleContextMenu}
|
| 226 |
>
|
| 227 |
+
<div className="player-indication-overlay"></div>
|
| 228 |
<div className={`player-overlay ${showControls ? "show" : "hide"}`}>
|
| 229 |
<h2 className="video-title">
|
| 230 |
<div className="control-btn player-exit" onClick={handleExitClick}>
|
| 231 |
<FontAwesomeIcon icon={faArrowLeft} size="lg" />
|
| 232 |
</div>
|
| 233 |
+
{episode ? getFileNameWithoutExtension(episode) : title}
|
| 234 |
</h2>
|
| 235 |
+
<div className="player-controls-center">
|
| 236 |
+
<button onClick={handleRewind} className="control-btn">
|
| 237 |
+
<label className="rewind-label-left">10</label>
|
| 238 |
+
<FontAwesomeIcon icon={faArrowRotateLeft} size="xl" />
|
| 239 |
+
</button>
|
| 240 |
+
<button onClick={togglePlayPause} className="play-pause-btn">
|
| 241 |
+
{isPlaying ? (
|
| 242 |
+
<FontAwesomeIcon icon={faPause} size="xl" />
|
| 243 |
+
) : (
|
| 244 |
+
<FontAwesomeIcon icon={faPlay} size="xl" />
|
| 245 |
+
)}
|
| 246 |
+
</button>
|
| 247 |
+
<button onClick={handleFastForward} className="control-btn">
|
| 248 |
+
<FontAwesomeIcon icon={faArrowRotateRight} size="xl" />
|
| 249 |
+
<label className="rewind-label-right">10</label>
|
| 250 |
+
</button>
|
| 251 |
+
</div>
|
| 252 |
<div className="controls">
|
| 253 |
<div className="player-controls-top">
|
| 254 |
<label className="current-time">
|
|
|
|
| 265 |
</div>
|
| 266 |
<div className="player-controls-down">
|
| 267 |
<div className="player-controls-left">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
<button onClick={toggleMute} className="control-btn">
|
| 269 |
<FontAwesomeIcon
|
| 270 |
icon={isMuted ? faVolumeMute : faVolumeUp}
|
|
|
|
| 280 |
value={volume}
|
| 281 |
onChange={handleVolumeChange}
|
| 282 |
/>
|
| 283 |
+
<button onClick={handleRewind} className="previous-btn control-btn">
|
| 284 |
+
<FontAwesomeIcon icon={faBackwardStep} size="xl" />
|
| 285 |
+
</button>
|
| 286 |
+
|
| 287 |
+
<button onClick={handleFastForward} className="next-btn control-btn">
|
| 288 |
+
<FontAwesomeIcon icon={faForwardStep} size="xl" />
|
| 289 |
+
</button>
|
| 290 |
+
{isPlaying ? (
|
| 291 |
+
<button className="playlist-btn control-btn">
|
| 292 |
+
<FontAwesomeIcon icon={playlistOn} size="xl" />
|
| 293 |
+
</button>
|
| 294 |
+
) : (
|
| 295 |
+
<button className="playlist-btn control-btn" disabled={true}>
|
| 296 |
+
<FontAwesomeIcon icon={playlistOff} size="xl" />
|
| 297 |
+
</button>
|
| 298 |
+
)}
|
| 299 |
+
</div>
|
| 300 |
+
<div className="player-controls-right">
|
| 301 |
+
{isPlaying ? (
|
| 302 |
+
<button className="cc-btn control-btn">
|
| 303 |
+
<FontAwesomeIcon icon={ccOn} size="xl" />
|
| 304 |
+
</button>
|
| 305 |
+
) : (
|
| 306 |
+
<button className="cc-btn control-btn" disabled={true}>
|
| 307 |
+
<FontAwesomeIcon icon={ccOff} size="xl" />
|
| 308 |
+
</button>
|
| 309 |
+
)}
|
| 310 |
<button onClick={toggleFullscreen} className="control-btn">
|
| 311 |
<FontAwesomeIcon
|
| 312 |
icon={isFullscreen ? faCompress : faExpand}
|
| 313 |
size="xl"
|
| 314 |
/>
|
| 315 |
</button>
|
| 316 |
+
{/* {videoUrl && (
|
| 317 |
<a href={videoUrl} download className="control-btn">
|
| 318 |
<FontAwesomeIcon icon={faDownload} size="xl" />
|
| 319 |
</a>
|
| 320 |
+
)} */}
|
| 321 |
</div>
|
| 322 |
</div>
|
| 323 |
</div>
|
|
|
|
| 329 |
src={videoUrl}
|
| 330 |
autoPlay={true}
|
| 331 |
>
|
| 332 |
+
{/* <track
|
| 333 |
kind="subtitles"
|
| 334 |
label="English"
|
| 335 |
srcLang="en"
|
| 336 |
src="/My.Spy.The.Eternal.City.(2024).WEB.vtt"
|
| 337 |
default
|
| 338 |
+
/> */}
|
| 339 |
Your browser does not support the video tag.
|
| 340 |
</video>
|
| 341 |
{isBuffering && (
|
|
|
|
| 357 |
)}
|
| 358 |
</div>
|
| 359 |
);
|
| 360 |
+
}
|
frontend/src/components/Player/utils.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const getFileNameWithoutExtension = (episodeFile) => {
|
| 2 |
+
if (!episodeFile) return null;
|
| 3 |
+
|
| 4 |
+
// Regular expression to match the file extension and common video quality suffixes
|
| 5 |
+
const regex = /\s*(HDTV-720p|HDTV-1080p|HDTV|720p|1080p|WEB-DL|WEBRip|BluRay|BRRip|DVDRip|CAM|TS|HDRip|BDRip|DVDScr)?(\.[^.]+)?$/i;
|
| 6 |
+
|
| 7 |
+
// Remove the matched part (file extension and video quality suffixes if present)
|
| 8 |
+
return episodeFile.replace(regex, '').trim();
|
| 9 |
+
};
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
export const formatTime = (seconds) => {
|
| 13 |
+
if (isNaN(seconds)) {
|
| 14 |
+
return "00:00:00";
|
| 15 |
+
}
|
| 16 |
+
const wholeSeconds = Math.floor(seconds);
|
| 17 |
+
const hours = Math.floor(wholeSeconds / 3600);
|
| 18 |
+
const minutes = Math.floor((wholeSeconds % 3600) / 60);
|
| 19 |
+
const secs = wholeSeconds % 60;
|
| 20 |
+
|
| 21 |
+
const formattedHours = String(hours).padStart(2, "0");
|
| 22 |
+
const formattedMinutes = String(minutes).padStart(2, "0");
|
| 23 |
+
const formattedSeconds = String(secs).padStart(2, "0");
|
| 24 |
+
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
export const getStorageKey = (type, title, episode) => {
|
| 28 |
+
return type === "tvshow" ? `${title}-${episode}` : title;
|
| 29 |
+
};
|
frontend/src/components/shared/ProgressBar/SeekableProgressBar.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
.MuiLinearProgress-barColorPrimary{
|
| 2 |
-
background-color:
|
| 3 |
}
|
| 4 |
|
| 5 |
.MuiLinearProgress-bar2Buffer{
|
|
|
|
| 1 |
.MuiLinearProgress-barColorPrimary{
|
| 2 |
+
background-color: var(--player-primary) !important;
|
| 3 |
}
|
| 4 |
|
| 5 |
.MuiLinearProgress-bar2Buffer{
|