ChandimaPrabath commited on
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: #4237b8;
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 #3640f7;
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: #4237b8;
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: #4237b8;
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 MoviePlayer({ videoUrl, title, type, episode=null }) {
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.2 Alpha";
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: #4237b8 !important;
3
  }
4
 
5
  .MuiLinearProgress-bar2Buffer{
 
1
  .MuiLinearProgress-barColorPrimary{
2
+ background-color: var(--player-primary) !important;
3
  }
4
 
5
  .MuiLinearProgress-bar2Buffer{