Commit
·
b16295e
1
Parent(s):
876c6fa
player patch
Browse files
frontend/src/app/player/movie/[title]/page.js
CHANGED
|
@@ -137,6 +137,7 @@ export default function FilmPlayer({ params }) {
|
|
| 137 |
videoUrl={videoUrl}
|
| 138 |
title={`${metadata.title} (${metadata.year})`}
|
| 139 |
type="movie"
|
|
|
|
| 140 |
/>
|
| 141 |
)
|
| 142 |
)}
|
|
|
|
| 137 |
videoUrl={videoUrl}
|
| 138 |
title={`${metadata.title} (${metadata.year})`}
|
| 139 |
type="movie"
|
| 140 |
+
videoThumbnail={metadata.image}
|
| 141 |
/>
|
| 142 |
)
|
| 143 |
)}
|
frontend/src/app/player/tvshow/[title]/[season]/[episode]/page.js
CHANGED
|
@@ -140,6 +140,7 @@ export default function FilmPlayer({ params }) {
|
|
| 140 |
title={`${metadata.title}`}
|
| 141 |
type="tvshow"
|
| 142 |
episode={episode}
|
|
|
|
| 143 |
/>
|
| 144 |
)
|
| 145 |
)}
|
|
|
|
| 140 |
title={`${metadata.title}`}
|
| 141 |
type="tvshow"
|
| 142 |
episode={episode}
|
| 143 |
+
videoThumbnail={metadata.image}
|
| 144 |
/>
|
| 145 |
)
|
| 146 |
)}
|
frontend/src/components/Player/Player.css
CHANGED
|
@@ -21,11 +21,19 @@
|
|
| 21 |
text-align: left;
|
| 22 |
padding: 15px;
|
| 23 |
font-size: 1.5em;
|
| 24 |
-
|
| 25 |
background-image: linear-gradient(#0e0f19cb 50%, transparent 100%);
|
| 26 |
}
|
| 27 |
|
| 28 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
padding-bottom: 0 !important;
|
| 30 |
}
|
| 31 |
|
|
@@ -177,7 +185,7 @@
|
|
| 177 |
|
| 178 |
.play-pause-btn:hover,
|
| 179 |
.control-btn:hover {
|
| 180 |
-
|
| 181 |
}
|
| 182 |
|
| 183 |
.control-btn:disabled {
|
|
@@ -185,12 +193,13 @@
|
|
| 185 |
cursor: not-allowed;
|
| 186 |
}
|
| 187 |
|
|
|
|
| 188 |
.progress-bar,
|
| 189 |
.volume-control {
|
| 190 |
margin: 0 5px;
|
| 191 |
}
|
| 192 |
|
| 193 |
-
.volumn-btn{
|
| 194 |
padding-bottom: 0;
|
| 195 |
}
|
| 196 |
|
|
@@ -305,4 +314,4 @@ input[type="range"]::-moz-range-thumb {
|
|
| 305 |
background-color: rgba(0, 0, 0, 0.7);
|
| 306 |
font-size: 1.3em;
|
| 307 |
line-height: 1.2; /* Adjust line height for spacing */
|
| 308 |
-
}
|
|
|
|
| 21 |
text-align: left;
|
| 22 |
padding: 15px;
|
| 23 |
font-size: 1.5em;
|
| 24 |
+
|
| 25 |
background-image: linear-gradient(#0e0f19cb 50%, transparent 100%);
|
| 26 |
}
|
| 27 |
|
| 28 |
+
.video-title-text {
|
| 29 |
+
font-weight: 100;
|
| 30 |
+
font-size: 1.2rem;
|
| 31 |
+
overflow: hidden;
|
| 32 |
+
white-space: nowrap;
|
| 33 |
+
text-overflow: ellipsis;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.player-exit {
|
| 37 |
padding-bottom: 0 !important;
|
| 38 |
}
|
| 39 |
|
|
|
|
| 185 |
|
| 186 |
.play-pause-btn:hover,
|
| 187 |
.control-btn:hover {
|
| 188 |
+
scale: 1.1;
|
| 189 |
}
|
| 190 |
|
| 191 |
.control-btn:disabled {
|
|
|
|
| 193 |
cursor: not-allowed;
|
| 194 |
}
|
| 195 |
|
| 196 |
+
|
| 197 |
.progress-bar,
|
| 198 |
.volume-control {
|
| 199 |
margin: 0 5px;
|
| 200 |
}
|
| 201 |
|
| 202 |
+
.volumn-btn {
|
| 203 |
padding-bottom: 0;
|
| 204 |
}
|
| 205 |
|
|
|
|
| 314 |
background-color: rgba(0, 0, 0, 0.7);
|
| 315 |
font-size: 1.3em;
|
| 316 |
line-height: 1.2; /* Adjust line height for spacing */
|
| 317 |
+
}
|
frontend/src/components/Player/Player.js
CHANGED
|
@@ -2,27 +2,6 @@
|
|
| 2 |
import { useEffect, useRef, useState } from "react";
|
| 3 |
import { useRouter } from "next/navigation";
|
| 4 |
import "./Player.css";
|
| 5 |
-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
| 6 |
-
import {
|
| 7 |
-
faPause,
|
| 8 |
-
faPlay,
|
| 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 {
|
| 22 |
-
faClosedCaptioning as ccOff,
|
| 23 |
-
faRectangleList as playlistOff,
|
| 24 |
-
} from "@fortawesome/free-regular-svg-icons";
|
| 25 |
-
|
| 26 |
import { Spinner } from "@/components/shared/Spinner/Spinner";
|
| 27 |
import SeekableProgressBar from "@/components/shared/ProgressBar/SeekableProgressBar";
|
| 28 |
import {
|
|
@@ -31,7 +10,7 @@ import {
|
|
| 31 |
getStorageKey,
|
| 32 |
} from "./utils";
|
| 33 |
|
| 34 |
-
export default function Player({ videoUrl, title, type, episode = null }) {
|
| 35 |
const router = useRouter();
|
| 36 |
const videoRef = useRef(null);
|
| 37 |
const [isPlaying, setIsPlaying] = useState(false);
|
|
@@ -51,6 +30,34 @@ export default function Player({ videoUrl, title, type, episode = null }) {
|
|
| 51 |
const playerVersion = "0.0.4 Alpha";
|
| 52 |
const seekTime = 5;
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
useEffect(() => {
|
| 55 |
const videoElement = videoRef.current;
|
| 56 |
const savedData = localStorage.getItem(getStorageKey(type, title, episode));
|
|
@@ -341,15 +348,16 @@ export default function Player({ videoUrl, title, type, episode = null }) {
|
|
| 341 |
>
|
| 342 |
<div className="player-indication-overlay"></div>
|
| 343 |
<div className={`player-overlay ${showControls ? "show" : "hide"}`}>
|
| 344 |
-
<
|
| 345 |
<div className="control-btn player-exit" onClick={handleExitClick}>
|
| 346 |
<span className="material-symbols-outlined medium">
|
| 347 |
keyboard_tab_rtl
|
| 348 |
</span>
|
| 349 |
</div>
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
|
|
|
| 353 |
<div className="player-controls-center">
|
| 354 |
<button onClick={handleRewind} className="control-btn">
|
| 355 |
<label className="rewind-label-left">{seekTime}s</label>
|
|
|
|
| 2 |
import { useEffect, useRef, useState } from "react";
|
| 3 |
import { useRouter } from "next/navigation";
|
| 4 |
import "./Player.css";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
import { Spinner } from "@/components/shared/Spinner/Spinner";
|
| 6 |
import SeekableProgressBar from "@/components/shared/ProgressBar/SeekableProgressBar";
|
| 7 |
import {
|
|
|
|
| 10 |
getStorageKey,
|
| 11 |
} from "./utils";
|
| 12 |
|
| 13 |
+
export default function Player({ videoUrl, title, type, episode = null, videoThumbnail=null }) {
|
| 14 |
const router = useRouter();
|
| 15 |
const videoRef = useRef(null);
|
| 16 |
const [isPlaying, setIsPlaying] = useState(false);
|
|
|
|
| 30 |
const playerVersion = "0.0.4 Alpha";
|
| 31 |
const seekTime = 5;
|
| 32 |
|
| 33 |
+
useEffect(() => {
|
| 34 |
+
if ('mediaSession' in navigator) {
|
| 35 |
+
navigator.mediaSession.metadata = new MediaMetadata({
|
| 36 |
+
title: episode? episode: title,
|
| 37 |
+
artwork: [
|
| 38 |
+
{ src: videoThumbnail, sizes: '680x1000', type: 'image/png' }
|
| 39 |
+
]
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
navigator.mediaSession.setActionHandler('play', () => {
|
| 43 |
+
videoRef.current.play();
|
| 44 |
+
});
|
| 45 |
+
navigator.mediaSession.setActionHandler('pause', () => {
|
| 46 |
+
videoRef.current.pause();
|
| 47 |
+
});
|
| 48 |
+
navigator.mediaSession.setActionHandler('seekbackward', (details) => {
|
| 49 |
+
videoRef.current.currentTime = Math.max(videoRef.current.currentTime - (details.seekOffset || 10), 0);
|
| 50 |
+
});
|
| 51 |
+
navigator.mediaSession.setActionHandler('seekforward', (details) => {
|
| 52 |
+
videoRef.current.currentTime = Math.min(videoRef.current.currentTime + (details.seekOffset || 10), videoRef.current.duration);
|
| 53 |
+
});
|
| 54 |
+
navigator.mediaSession.setActionHandler('stop', () => {
|
| 55 |
+
videoRef.current.pause();
|
| 56 |
+
videoRef.current.currentTime = 0;
|
| 57 |
+
});
|
| 58 |
+
}
|
| 59 |
+
}, [title, videoThumbnail]);
|
| 60 |
+
|
| 61 |
useEffect(() => {
|
| 62 |
const videoElement = videoRef.current;
|
| 63 |
const savedData = localStorage.getItem(getStorageKey(type, title, episode));
|
|
|
|
| 348 |
>
|
| 349 |
<div className="player-indication-overlay"></div>
|
| 350 |
<div className={`player-overlay ${showControls ? "show" : "hide"}`}>
|
| 351 |
+
<div className="video-title">
|
| 352 |
<div className="control-btn player-exit" onClick={handleExitClick}>
|
| 353 |
<span className="material-symbols-outlined medium">
|
| 354 |
keyboard_tab_rtl
|
| 355 |
</span>
|
| 356 |
</div>
|
| 357 |
+
<label className="video-title-text">
|
| 358 |
+
{episode ? getFileNameWithoutExtension(episode) : title}
|
| 359 |
+
</label>
|
| 360 |
+
</div>
|
| 361 |
<div className="player-controls-center">
|
| 362 |
<button onClick={handleRewind} className="control-btn">
|
| 363 |
<label className="rewind-label-left">{seekTime}s</label>
|
frontend/src/components/Player/utils.js
CHANGED
|
@@ -11,14 +11,14 @@ export const getFileNameWithoutExtension = (episodeFile) => {
|
|
| 11 |
|
| 12 |
export const formatTime = (seconds) => {
|
| 13 |
if (isNaN(seconds)) {
|
| 14 |
-
return "
|
| 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(
|
| 22 |
const formattedMinutes = String(minutes).padStart(2, "0");
|
| 23 |
const formattedSeconds = String(secs).padStart(2, "0");
|
| 24 |
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
|
|
|
|
| 11 |
|
| 12 |
export const formatTime = (seconds) => {
|
| 13 |
if (isNaN(seconds)) {
|
| 14 |
+
return "0: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(1, "0");
|
| 22 |
const formattedMinutes = String(minutes).padStart(2, "0");
|
| 23 |
const formattedSeconds = String(secs).padStart(2, "0");
|
| 24 |
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
|