Spaces:
Sleeping
Sleeping
Commit
·
f57be92
1
Parent(s):
ad5cee0
fix $ improv
Browse files
frontend/src/app/category/[category]/page.js
CHANGED
|
@@ -11,6 +11,7 @@ export default function CategoryPage({ params }) {
|
|
| 11 |
const [error, setError] = useState(null);
|
| 12 |
const [page, setPage] = useState(1);
|
| 13 |
const [hasMore, setHasMore] = useState(true);
|
|
|
|
| 14 |
|
| 15 |
useEffect(() => {
|
| 16 |
const fetchMusicByCategory = async () => {
|
|
@@ -31,8 +32,12 @@ export default function CategoryPage({ params }) {
|
|
| 31 |
return [...prevItems, ...newItems];
|
| 32 |
});
|
| 33 |
|
| 34 |
-
//
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
} catch (err) {
|
| 37 |
setError(err.message);
|
| 38 |
} finally {
|
|
@@ -43,7 +48,7 @@ export default function CategoryPage({ params }) {
|
|
| 43 |
fetchMusicByCategory();
|
| 44 |
}, [category, page]);
|
| 45 |
|
| 46 |
-
// Load more when scrolled to bottom of the app container
|
| 47 |
useEffect(() => {
|
| 48 |
const appContainer = document.querySelector('.app-container'); // Get the app-container element
|
| 49 |
if (!appContainer) return;
|
|
@@ -65,7 +70,7 @@ export default function CategoryPage({ params }) {
|
|
| 65 |
}, [hasMore, loading]);
|
| 66 |
|
| 67 |
const loadMore = () => {
|
| 68 |
-
if (hasMore) {
|
| 69 |
setPage(prevPage => prevPage + 1);
|
| 70 |
}
|
| 71 |
};
|
|
|
|
| 11 |
const [error, setError] = useState(null);
|
| 12 |
const [page, setPage] = useState(1);
|
| 13 |
const [hasMore, setHasMore] = useState(true);
|
| 14 |
+
const [maxPage, setMaxPage] = useState(Infinity); // Store the calculated max page
|
| 15 |
|
| 16 |
useEffect(() => {
|
| 17 |
const fetchMusicByCategory = async () => {
|
|
|
|
| 32 |
return [...prevItems, ...newItems];
|
| 33 |
});
|
| 34 |
|
| 35 |
+
// Calculate maxPage based on total_files and limit, then set it
|
| 36 |
+
const calculatedMaxPage = Math.ceil(data.total_files / data.limit);
|
| 37 |
+
setMaxPage(calculatedMaxPage);
|
| 38 |
+
|
| 39 |
+
// Update hasMore based on whether we have reached maxPage
|
| 40 |
+
setHasMore(page < calculatedMaxPage);
|
| 41 |
} catch (err) {
|
| 42 |
setError(err.message);
|
| 43 |
} finally {
|
|
|
|
| 48 |
fetchMusicByCategory();
|
| 49 |
}, [category, page]);
|
| 50 |
|
| 51 |
+
// Load more when scrolled to the bottom of the app container
|
| 52 |
useEffect(() => {
|
| 53 |
const appContainer = document.querySelector('.app-container'); // Get the app-container element
|
| 54 |
if (!appContainer) return;
|
|
|
|
| 70 |
}, [hasMore, loading]);
|
| 71 |
|
| 72 |
const loadMore = () => {
|
| 73 |
+
if (hasMore && page < maxPage) { // Ensure page does not exceed maxPage
|
| 74 |
setPage(prevPage => prevPage + 1);
|
| 75 |
}
|
| 76 |
};
|
frontend/src/app/globals.css
CHANGED
|
@@ -3,12 +3,13 @@
|
|
| 3 |
@tailwind utilities;
|
| 4 |
|
| 5 |
:root {
|
| 6 |
-
--background: #
|
| 7 |
--foreground: #ededed;
|
| 8 |
-
--background-secondary: #
|
| 9 |
--foreground-secondary: #1d85b9;
|
| 10 |
-
--background-2: #
|
| 11 |
-
--foreground-2: #
|
|
|
|
| 12 |
}
|
| 13 |
|
| 14 |
html,
|
|
|
|
| 3 |
@tailwind utilities;
|
| 4 |
|
| 5 |
:root {
|
| 6 |
+
--background: #080523;
|
| 7 |
--foreground: #ededed;
|
| 8 |
+
--background-secondary: #060a52;
|
| 9 |
--foreground-secondary: #1d85b9;
|
| 10 |
+
--background-2: #2a3477;
|
| 11 |
+
--foreground-2: #0b5da9;
|
| 12 |
+
--foreground-3: #0f0e60;
|
| 13 |
}
|
| 14 |
|
| 15 |
html,
|
frontend/src/components/Card.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
.music-card {
|
| 2 |
width: 250px;
|
| 3 |
height: 40;
|
| 4 |
-
background-image: linear-gradient(var(--background-2), var(--foreground-
|
| 5 |
border-top-left-radius: 20px;
|
| 6 |
border-bottom-left-radius: 20px;
|
| 7 |
border-top-right-radius: 5px;
|
|
@@ -10,6 +10,7 @@
|
|
| 10 |
align-items: center;
|
| 11 |
gap: 10px;
|
| 12 |
overflow: hidden;
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
.card-title {
|
|
@@ -27,3 +28,30 @@
|
|
| 27 |
font-size: 40px;
|
| 28 |
overflow: visible;
|
| 29 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
.music-card {
|
| 2 |
width: 250px;
|
| 3 |
height: 40;
|
| 4 |
+
background-image: linear-gradient(var(--background-2), var(--foreground-3));
|
| 5 |
border-top-left-radius: 20px;
|
| 6 |
border-bottom-left-radius: 20px;
|
| 7 |
border-top-right-radius: 5px;
|
|
|
|
| 10 |
align-items: center;
|
| 11 |
gap: 10px;
|
| 12 |
overflow: hidden;
|
| 13 |
+
transition: scale .3s ease;
|
| 14 |
}
|
| 15 |
|
| 16 |
.card-title {
|
|
|
|
| 28 |
font-size: 40px;
|
| 29 |
overflow: visible;
|
| 30 |
}
|
| 31 |
+
|
| 32 |
+
.music-card:hover{
|
| 33 |
+
scale: 1.03;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.music-card.music-card.now-playing{
|
| 37 |
+
background-image: linear-gradient(var(--background-2), var(--foreground-2));
|
| 38 |
+
border: 2px solid var(--foreground-secondary);
|
| 39 |
+
border-right: none;
|
| 40 |
+
border-bottom: none;
|
| 41 |
+
border-left: none;
|
| 42 |
+
box-shadow: 0 0 15px var(--foreground-secondary);
|
| 43 |
+
scale: 1.03;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.music-card.now-playing .card-icon{
|
| 47 |
+
animation: rotateInfinite 1.5s linear infinite;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
@keyframes rotateInfinite {
|
| 51 |
+
0% {
|
| 52 |
+
transform: rotateZ(0deg);
|
| 53 |
+
}
|
| 54 |
+
100% {
|
| 55 |
+
transform: rotateZ(360deg);
|
| 56 |
+
}
|
| 57 |
+
}
|
frontend/src/components/Card.js
CHANGED
|
@@ -2,20 +2,19 @@
|
|
| 2 |
import "./Card.css";
|
| 3 |
import { FaCompactDisc } from "react-icons/fa";
|
| 4 |
import { useMusicPlayer } from "@/context/MusicPlayerContext";
|
|
|
|
| 5 |
|
| 6 |
const Card = ({ src }) => {
|
| 7 |
-
const { initializePlayer } = useMusicPlayer();
|
| 8 |
-
|
| 9 |
-
// Extract title from the src by parsing the file name
|
| 10 |
-
const title = src.split('/').pop().split('.')[0]; // Remove path and extension
|
| 11 |
|
| 12 |
const handleButtonClick = () => {
|
| 13 |
-
initializePlayer(src);
|
| 14 |
};
|
| 15 |
|
| 16 |
return (
|
| 17 |
<button onClick={handleButtonClick}>
|
| 18 |
-
<div className=
|
| 19 |
<FaCompactDisc className="card-icon" />
|
| 20 |
<label className="card-title">{title}</label>
|
| 21 |
</div>
|
|
|
|
| 2 |
import "./Card.css";
|
| 3 |
import { FaCompactDisc } from "react-icons/fa";
|
| 4 |
import { useMusicPlayer } from "@/context/MusicPlayerContext";
|
| 5 |
+
import { formatTitle } from "@/lib/utils";
|
| 6 |
|
| 7 |
const Card = ({ src }) => {
|
| 8 |
+
const { initializePlayer, nowPlaying } = useMusicPlayer();
|
| 9 |
+
const title = formatTitle(src);
|
|
|
|
|
|
|
| 10 |
|
| 11 |
const handleButtonClick = () => {
|
| 12 |
+
initializePlayer(src, title);
|
| 13 |
};
|
| 14 |
|
| 15 |
return (
|
| 16 |
<button onClick={handleButtonClick}>
|
| 17 |
+
<div className={`music-card ${nowPlaying === title ? "now-playing" : ""}`}>
|
| 18 |
<FaCompactDisc className="card-icon" />
|
| 19 |
<label className="card-title">{title}</label>
|
| 20 |
</div>
|
frontend/src/components/MusicPlayer.js
CHANGED
|
@@ -26,9 +26,13 @@ export default function MusicPlayer() {
|
|
| 26 |
togglePlayerSize,
|
| 27 |
setIsPlayerVisible,
|
| 28 |
initializePlayer,
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
} = useMusicPlayer();
|
| 31 |
-
|
| 32 |
const [currentSrc, setCurrentSrc] = useState(src);
|
| 33 |
const [currentTime, setCurrentTime] = useState(0);
|
| 34 |
const [duration, setDuration] = useState(0);
|
|
@@ -42,69 +46,79 @@ export default function MusicPlayer() {
|
|
| 42 |
const [isFullscreen, setIsFullscreen] = useState(false);
|
| 43 |
const overlayTimeout = useRef(null);
|
| 44 |
const seekTime = 5;
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
| 80 |
if (videoElement) {
|
| 81 |
-
videoElement.addEventListener("timeupdate", handleTimeUpdate);
|
| 82 |
-
videoElement.addEventListener("loadedmetadata", handleLoadedMetadata);
|
| 83 |
-
videoElement.addEventListener("progress", handleProgress);
|
| 84 |
videoElement.addEventListener("play", handlePlay);
|
| 85 |
videoElement.addEventListener("pause", handlePause);
|
| 86 |
}
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
return () => {
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
videoElement.removeEventListener(
|
| 92 |
-
"loadedmetadata",
|
| 93 |
-
handleLoadedMetadata
|
| 94 |
-
);
|
| 95 |
-
videoElement.removeEventListener("progress", handleProgress);
|
| 96 |
-
videoElement.removeEventListener("play", handlePlay);
|
| 97 |
-
videoElement.removeEventListener("pause", handlePause);
|
| 98 |
-
}
|
| 99 |
};
|
| 100 |
-
}, [videoRef, currentSrc]);
|
| 101 |
-
|
| 102 |
useEffect(() => {
|
| 103 |
if (src !== currentSrc) {
|
| 104 |
setCurrentSrc(src);
|
| 105 |
}
|
| 106 |
}, [src, currentSrc]);
|
| 107 |
-
|
| 108 |
useEffect(() => {
|
| 109 |
if (showControls) {
|
| 110 |
if (overlayTimeout.current) {
|
|
@@ -112,10 +126,10 @@ export default function MusicPlayer() {
|
|
| 112 |
}
|
| 113 |
overlayTimeout.current = setTimeout(() => setShowControls(false), 3000);
|
| 114 |
}
|
| 115 |
-
|
| 116 |
return () => clearTimeout(overlayTimeout.current);
|
| 117 |
}, [showControls]);
|
| 118 |
-
|
| 119 |
const togglePlayPause = () => {
|
| 120 |
if (videoRef.current) {
|
| 121 |
if (isPlaying) {
|
|
@@ -126,23 +140,20 @@ export default function MusicPlayer() {
|
|
| 126 |
setIsPlaying(!isPlaying);
|
| 127 |
}
|
| 128 |
};
|
|
|
|
| 129 |
const handleVolumeChange = (event) => {
|
| 130 |
let volumeValue = parseFloat(event.target.value);
|
| 131 |
-
|
| 132 |
// Ensure the volume is a finite number between 0 and 1
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
} else if (volumeValue > 1) {
|
| 136 |
-
volumeValue = 1;
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
setVolume(volumeValue);
|
| 140 |
if (videoRef.current) {
|
| 141 |
videoRef.current.volume = volumeValue;
|
| 142 |
}
|
| 143 |
setIsMuted(volumeValue === 0);
|
| 144 |
};
|
| 145 |
-
|
| 146 |
const toggleMute = () => {
|
| 147 |
if (isMuted) {
|
| 148 |
videoRef.current.volume = volume;
|
|
@@ -152,38 +163,50 @@ export default function MusicPlayer() {
|
|
| 152 |
setIsMuted(true);
|
| 153 |
}
|
| 154 |
};
|
| 155 |
-
|
| 156 |
const toggleFullscreen = () => {
|
| 157 |
const doc = window.document;
|
| 158 |
const docEl = doc.documentElement;
|
| 159 |
-
|
| 160 |
const requestFullscreen =
|
| 161 |
docEl.requestFullscreen ||
|
| 162 |
docEl.mozRequestFullScreen ||
|
| 163 |
docEl.webkitRequestFullscreen ||
|
| 164 |
docEl.msRequestFullscreen;
|
|
|
|
| 165 |
const exitFullscreen =
|
| 166 |
doc.exitFullscreen ||
|
| 167 |
doc.mozCancelFullScreen ||
|
| 168 |
docEl.webkitExitFullscreen ||
|
| 169 |
doc.msExitFullscreen;
|
| 170 |
-
|
| 171 |
if (!isFullscreen) {
|
| 172 |
requestFullscreen.call(docEl);
|
| 173 |
} else {
|
| 174 |
exitFullscreen.call(doc);
|
| 175 |
}
|
| 176 |
-
|
| 177 |
setIsFullscreen(!isFullscreen);
|
| 178 |
};
|
| 179 |
-
|
| 180 |
const destroyPlayer = () => {
|
| 181 |
if (videoRef.current) {
|
| 182 |
-
videoRef.current.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
setIsPlayerVisible(false);
|
|
|
|
|
|
|
| 184 |
}
|
| 185 |
};
|
| 186 |
-
|
| 187 |
const handleProgressClick = (e) => {
|
| 188 |
const progressBar = e.currentTarget;
|
| 189 |
const rect = progressBar.getBoundingClientRect();
|
|
@@ -194,7 +217,7 @@ export default function MusicPlayer() {
|
|
| 194 |
setCurrentTime(newTime);
|
| 195 |
}
|
| 196 |
};
|
| 197 |
-
|
| 198 |
const handleFastForward = () => {
|
| 199 |
if (videoRef.current) {
|
| 200 |
videoRef.current.currentTime = Math.min(
|
|
@@ -203,7 +226,7 @@ export default function MusicPlayer() {
|
|
| 203 |
);
|
| 204 |
}
|
| 205 |
};
|
| 206 |
-
|
| 207 |
const handleRewind = () => {
|
| 208 |
if (videoRef.current) {
|
| 209 |
videoRef.current.currentTime = Math.max(
|
|
@@ -212,11 +235,13 @@ export default function MusicPlayer() {
|
|
| 212 |
);
|
| 213 |
}
|
| 214 |
};
|
|
|
|
| 215 |
const handleMouseMove = () => {
|
| 216 |
setShowControls(true);
|
| 217 |
};
|
|
|
|
| 218 |
if (!isPlayerVisible || !currentSrc) return null;
|
| 219 |
-
|
| 220 |
return (
|
| 221 |
<div
|
| 222 |
className={
|
|
@@ -230,7 +255,7 @@ export default function MusicPlayer() {
|
|
| 230 |
<div className={`player-controls ${showControls ? "show" : "hide"}`}>
|
| 231 |
<div className="player-controls-top">
|
| 232 |
<div className="name-container">
|
| 233 |
-
<label className="music-title">{
|
| 234 |
<button className="player-max-button" onClick={togglePlayerSize}>
|
| 235 |
<IoIosArrowDown />
|
| 236 |
</button>
|
|
@@ -339,7 +364,7 @@ export default function MusicPlayer() {
|
|
| 339 |
{!isPlayerMaximized && (
|
| 340 |
<div className="player-controls-mini">
|
| 341 |
<div className="player-mini-control-top">
|
| 342 |
-
<label className="music-title player-mini">{
|
| 343 |
<button className="player-min-button" onClick={togglePlayerSize}>
|
| 344 |
<IoIosArrowUp />
|
| 345 |
</button>
|
|
|
|
| 26 |
togglePlayerSize,
|
| 27 |
setIsPlayerVisible,
|
| 28 |
initializePlayer,
|
| 29 |
+
title,
|
| 30 |
+
setNowPlaying,
|
| 31 |
+
nowPlaying,
|
| 32 |
+
didDestroy,
|
| 33 |
+
setDidDestroy,
|
| 34 |
} = useMusicPlayer();
|
| 35 |
+
|
| 36 |
const [currentSrc, setCurrentSrc] = useState(src);
|
| 37 |
const [currentTime, setCurrentTime] = useState(0);
|
| 38 |
const [duration, setDuration] = useState(0);
|
|
|
|
| 46 |
const [isFullscreen, setIsFullscreen] = useState(false);
|
| 47 |
const overlayTimeout = useRef(null);
|
| 48 |
const seekTime = 5;
|
| 49 |
+
|
| 50 |
+
// Event Handlers
|
| 51 |
+
const handleTimeUpdate = (videoElement) => {
|
| 52 |
+
if (videoElement) {
|
| 53 |
+
setCurrentTime(videoElement.currentTime);
|
| 54 |
+
setProgress((videoElement.currentTime / videoElement.duration) * 100);
|
| 55 |
+
}
|
| 56 |
+
};
|
| 57 |
+
|
| 58 |
+
const handleLoadedMetadata = (videoElement) => {
|
| 59 |
+
if (videoElement) {
|
| 60 |
+
setDuration(videoElement.duration);
|
| 61 |
+
setProgress(0);
|
| 62 |
+
setCurrentTime(0);
|
| 63 |
+
}
|
| 64 |
+
};
|
| 65 |
+
|
| 66 |
+
const handleProgress = (videoElement) => {
|
| 67 |
+
if (videoElement && videoElement.buffered.length > 0) {
|
| 68 |
+
const bufferEnd = videoElement.buffered.end(videoElement.buffered.length - 1);
|
| 69 |
+
const bufferValue = (bufferEnd / videoElement.duration) * 100;
|
| 70 |
+
setBufferProgress(bufferValue);
|
| 71 |
+
}
|
| 72 |
+
};
|
| 73 |
+
|
| 74 |
+
const handlePlay = () => {
|
| 75 |
+
setIsPlaying(true);
|
| 76 |
+
setNowPlaying(title);
|
| 77 |
+
};
|
| 78 |
+
|
| 79 |
+
const handlePause = () => {
|
| 80 |
+
setIsPlaying(false);
|
| 81 |
+
setNowPlaying("");
|
| 82 |
+
};
|
| 83 |
+
|
| 84 |
+
const attachEventListeners = (videoElement) => {
|
| 85 |
if (videoElement) {
|
| 86 |
+
videoElement.addEventListener("timeupdate", () => handleTimeUpdate(videoElement));
|
| 87 |
+
videoElement.addEventListener("loadedmetadata", () => handleLoadedMetadata(videoElement));
|
| 88 |
+
videoElement.addEventListener("progress", () => handleProgress(videoElement));
|
| 89 |
videoElement.addEventListener("play", handlePlay);
|
| 90 |
videoElement.addEventListener("pause", handlePause);
|
| 91 |
}
|
| 92 |
+
};
|
| 93 |
+
|
| 94 |
+
const detachEventListeners = (videoElement) => {
|
| 95 |
+
if (videoElement) {
|
| 96 |
+
videoElement.removeEventListener("timeupdate", () => handleTimeUpdate(videoElement));
|
| 97 |
+
videoElement.removeEventListener("loadedmetadata", () => handleLoadedMetadata(videoElement));
|
| 98 |
+
videoElement.removeEventListener("progress", () => handleProgress(videoElement));
|
| 99 |
+
videoElement.removeEventListener("play", handlePlay);
|
| 100 |
+
videoElement.removeEventListener("pause", handlePause);
|
| 101 |
+
}
|
| 102 |
+
};
|
| 103 |
+
|
| 104 |
+
useEffect(() => {
|
| 105 |
+
const videoElement = videoRef.current;
|
| 106 |
+
|
| 107 |
+
// Attach event listeners
|
| 108 |
+
attachEventListeners(videoElement);
|
| 109 |
+
|
| 110 |
return () => {
|
| 111 |
+
// Detach event listeners
|
| 112 |
+
detachEventListeners(videoElement);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
};
|
| 114 |
+
}, [videoRef, currentSrc, nowPlaying, didDestroy]);
|
| 115 |
+
|
| 116 |
useEffect(() => {
|
| 117 |
if (src !== currentSrc) {
|
| 118 |
setCurrentSrc(src);
|
| 119 |
}
|
| 120 |
}, [src, currentSrc]);
|
| 121 |
+
|
| 122 |
useEffect(() => {
|
| 123 |
if (showControls) {
|
| 124 |
if (overlayTimeout.current) {
|
|
|
|
| 126 |
}
|
| 127 |
overlayTimeout.current = setTimeout(() => setShowControls(false), 3000);
|
| 128 |
}
|
| 129 |
+
|
| 130 |
return () => clearTimeout(overlayTimeout.current);
|
| 131 |
}, [showControls]);
|
| 132 |
+
|
| 133 |
const togglePlayPause = () => {
|
| 134 |
if (videoRef.current) {
|
| 135 |
if (isPlaying) {
|
|
|
|
| 140 |
setIsPlaying(!isPlaying);
|
| 141 |
}
|
| 142 |
};
|
| 143 |
+
|
| 144 |
const handleVolumeChange = (event) => {
|
| 145 |
let volumeValue = parseFloat(event.target.value);
|
| 146 |
+
|
| 147 |
// Ensure the volume is a finite number between 0 and 1
|
| 148 |
+
volumeValue = Math.max(0, Math.min(1, volumeValue));
|
| 149 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
setVolume(volumeValue);
|
| 151 |
if (videoRef.current) {
|
| 152 |
videoRef.current.volume = volumeValue;
|
| 153 |
}
|
| 154 |
setIsMuted(volumeValue === 0);
|
| 155 |
};
|
| 156 |
+
|
| 157 |
const toggleMute = () => {
|
| 158 |
if (isMuted) {
|
| 159 |
videoRef.current.volume = volume;
|
|
|
|
| 163 |
setIsMuted(true);
|
| 164 |
}
|
| 165 |
};
|
| 166 |
+
|
| 167 |
const toggleFullscreen = () => {
|
| 168 |
const doc = window.document;
|
| 169 |
const docEl = doc.documentElement;
|
| 170 |
+
|
| 171 |
const requestFullscreen =
|
| 172 |
docEl.requestFullscreen ||
|
| 173 |
docEl.mozRequestFullScreen ||
|
| 174 |
docEl.webkitRequestFullscreen ||
|
| 175 |
docEl.msRequestFullscreen;
|
| 176 |
+
|
| 177 |
const exitFullscreen =
|
| 178 |
doc.exitFullscreen ||
|
| 179 |
doc.mozCancelFullScreen ||
|
| 180 |
docEl.webkitExitFullscreen ||
|
| 181 |
doc.msExitFullscreen;
|
| 182 |
+
|
| 183 |
if (!isFullscreen) {
|
| 184 |
requestFullscreen.call(docEl);
|
| 185 |
} else {
|
| 186 |
exitFullscreen.call(doc);
|
| 187 |
}
|
| 188 |
+
|
| 189 |
setIsFullscreen(!isFullscreen);
|
| 190 |
};
|
| 191 |
+
|
| 192 |
const destroyPlayer = () => {
|
| 193 |
if (videoRef.current) {
|
| 194 |
+
videoRef.current.pause();
|
| 195 |
+
videoRef.current.removeAttribute("src"); // Clear the source
|
| 196 |
+
videoRef.current.load(); // Reload to reset duration/currentTime
|
| 197 |
+
setNowPlaying("");
|
| 198 |
+
setCurrentSrc("/reset");
|
| 199 |
+
setCurrentTime(0);
|
| 200 |
+
setDuration(0);
|
| 201 |
+
setProgress(0);
|
| 202 |
+
setBufferProgress(0);
|
| 203 |
+
setIsPlaying(false);
|
| 204 |
setIsPlayerVisible(false);
|
| 205 |
+
setDidDestroy(true);
|
| 206 |
+
console.log("setting didDestroy to true");
|
| 207 |
}
|
| 208 |
};
|
| 209 |
+
|
| 210 |
const handleProgressClick = (e) => {
|
| 211 |
const progressBar = e.currentTarget;
|
| 212 |
const rect = progressBar.getBoundingClientRect();
|
|
|
|
| 217 |
setCurrentTime(newTime);
|
| 218 |
}
|
| 219 |
};
|
| 220 |
+
|
| 221 |
const handleFastForward = () => {
|
| 222 |
if (videoRef.current) {
|
| 223 |
videoRef.current.currentTime = Math.min(
|
|
|
|
| 226 |
);
|
| 227 |
}
|
| 228 |
};
|
| 229 |
+
|
| 230 |
const handleRewind = () => {
|
| 231 |
if (videoRef.current) {
|
| 232 |
videoRef.current.currentTime = Math.max(
|
|
|
|
| 235 |
);
|
| 236 |
}
|
| 237 |
};
|
| 238 |
+
|
| 239 |
const handleMouseMove = () => {
|
| 240 |
setShowControls(true);
|
| 241 |
};
|
| 242 |
+
|
| 243 |
if (!isPlayerVisible || !currentSrc) return null;
|
| 244 |
+
|
| 245 |
return (
|
| 246 |
<div
|
| 247 |
className={
|
|
|
|
| 255 |
<div className={`player-controls ${showControls ? "show" : "hide"}`}>
|
| 256 |
<div className="player-controls-top">
|
| 257 |
<div className="name-container">
|
| 258 |
+
<label className="music-title">{title}</label>
|
| 259 |
<button className="player-max-button" onClick={togglePlayerSize}>
|
| 260 |
<IoIosArrowDown />
|
| 261 |
</button>
|
|
|
|
| 364 |
{!isPlayerMaximized && (
|
| 365 |
<div className="player-controls-mini">
|
| 366 |
<div className="player-mini-control-top">
|
| 367 |
+
<label className="music-title player-mini">{title}</label>
|
| 368 |
<button className="player-min-button" onClick={togglePlayerSize}>
|
| 369 |
<IoIosArrowUp />
|
| 370 |
</button>
|
frontend/src/context/MusicPlayerContext.js
CHANGED
|
@@ -1,52 +1,47 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
import React, { createContext, useContext, useRef, useState } from "react";
|
| 4 |
|
| 5 |
const MusicPlayerContext = createContext();
|
| 6 |
|
| 7 |
export const MusicPlayerProvider = ({ children }) => {
|
| 8 |
const videoRef = useRef(null);
|
| 9 |
const [src, setSrc] = useState("");
|
| 10 |
-
const [
|
|
|
|
| 11 |
const [isPlayerVisible, setIsPlayerVisible] = useState(false);
|
| 12 |
const [isPlayerMaximized, setIsPlayerMaximized] = useState(false);
|
| 13 |
const [loadingProgress, setLoadingProgress] = useState(null);
|
| 14 |
const [abortController, setAbortController] = useState(null); // AbortController for stopping
|
|
|
|
| 15 |
|
| 16 |
-
const initializePlayer = async (source) => {
|
| 17 |
-
// Stop any ongoing initializePlayer process if called again
|
| 18 |
if (abortController) {
|
| 19 |
-
abortController.abort();
|
| 20 |
}
|
| 21 |
-
|
| 22 |
-
// Set up a new AbortController
|
| 23 |
const newAbortController = new AbortController();
|
| 24 |
setAbortController(newAbortController);
|
| 25 |
|
| 26 |
-
// Extract the file name and start the player
|
| 27 |
const extractedFileName = source.split('/').pop();
|
| 28 |
-
|
| 29 |
-
if (videoRef.current){
|
| 30 |
videoRef.current.pause();
|
| 31 |
}
|
| 32 |
|
| 33 |
try {
|
| 34 |
-
// Call the API with the signal to allow aborting
|
| 35 |
const response = await fetch(`/api/get/music/${encodeURIComponent(extractedFileName)}`, {
|
| 36 |
signal: newAbortController.signal,
|
| 37 |
});
|
| 38 |
const data = await response.json();
|
| 39 |
|
| 40 |
-
// If the file is being downloaded, check progress
|
| 41 |
if (data.status === "Download started") {
|
| 42 |
const progressUrl = data.progress_url;
|
| 43 |
checkProgress(progressUrl, extractedFileName, newAbortController);
|
| 44 |
} else {
|
| 45 |
-
startPlayer(data.url);
|
| 46 |
}
|
| 47 |
} catch (error) {
|
| 48 |
if (error.name !== 'AbortError') {
|
| 49 |
-
console.error("Error initializing player:", error);
|
| 50 |
}
|
| 51 |
}
|
| 52 |
};
|
|
@@ -56,7 +51,6 @@ export const MusicPlayerProvider = ({ children }) => {
|
|
| 56 |
try {
|
| 57 |
const response = await fetch(progressUrl, { signal: abortController.signal });
|
| 58 |
const progressData = await response.json();
|
| 59 |
-
|
| 60 |
setLoadingProgress(progressData.progress);
|
| 61 |
|
| 62 |
if (progressData.progress.status === "Completed") {
|
|
@@ -65,7 +59,7 @@ export const MusicPlayerProvider = ({ children }) => {
|
|
| 65 |
}
|
| 66 |
} catch (error) {
|
| 67 |
if (error.name === 'AbortError') {
|
| 68 |
-
clearInterval(intervalId);
|
| 69 |
} else {
|
| 70 |
console.error("Error fetching progress:", error);
|
| 71 |
}
|
|
@@ -83,32 +77,35 @@ export const MusicPlayerProvider = ({ children }) => {
|
|
| 83 |
if (data.url) {
|
| 84 |
startPlayer(data.url);
|
| 85 |
} else {
|
| 86 |
-
// Retry after a delay if the URL is not available
|
| 87 |
retryFetchFinalUrl(fileName, abortController, attempt);
|
| 88 |
}
|
| 89 |
} catch (error) {
|
| 90 |
if (error.name !== 'AbortError') {
|
| 91 |
console.error("Error fetching final URL:", error);
|
| 92 |
-
// Retry after a delay if any error occurs
|
| 93 |
retryFetchFinalUrl(fileName, abortController, attempt);
|
| 94 |
}
|
| 95 |
}
|
| 96 |
};
|
| 97 |
|
| 98 |
const retryFetchFinalUrl = (fileName, abortController, attempt) => {
|
| 99 |
-
const retryDelay = 2000;
|
| 100 |
setTimeout(() => {
|
| 101 |
console.log(`Retry attempt ${attempt} for ${fileName}`);
|
| 102 |
-
fetchFinalUrl(fileName, abortController, attempt + 1);
|
| 103 |
}, retryDelay);
|
| 104 |
};
|
| 105 |
|
| 106 |
const startPlayer = (source) => {
|
|
|
|
|
|
|
| 107 |
setSrc(source);
|
| 108 |
setIsPlayerVisible(true);
|
| 109 |
if (videoRef.current) {
|
| 110 |
videoRef.current.src = source;
|
| 111 |
videoRef.current.load();
|
|
|
|
|
|
|
|
|
|
| 112 |
}
|
| 113 |
};
|
| 114 |
|
|
@@ -121,11 +118,15 @@ export const MusicPlayerProvider = ({ children }) => {
|
|
| 121 |
initializePlayer,
|
| 122 |
isPlayerVisible,
|
| 123 |
src,
|
| 124 |
-
|
| 125 |
isPlayerMaximized,
|
| 126 |
togglePlayerSize,
|
| 127 |
setIsPlayerVisible,
|
| 128 |
loadingProgress,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
}}
|
| 130 |
>
|
| 131 |
{children}
|
|
|
|
| 1 |
+
import React, { createContext, useContext, useRef, useState, useEffect } from "react";
|
|
|
|
|
|
|
| 2 |
|
| 3 |
const MusicPlayerContext = createContext();
|
| 4 |
|
| 5 |
export const MusicPlayerProvider = ({ children }) => {
|
| 6 |
const videoRef = useRef(null);
|
| 7 |
const [src, setSrc] = useState("");
|
| 8 |
+
const [title, setTitle] = useState("");
|
| 9 |
+
const [nowPlaying, setNowPlaying] = useState("");
|
| 10 |
const [isPlayerVisible, setIsPlayerVisible] = useState(false);
|
| 11 |
const [isPlayerMaximized, setIsPlayerMaximized] = useState(false);
|
| 12 |
const [loadingProgress, setLoadingProgress] = useState(null);
|
| 13 |
const [abortController, setAbortController] = useState(null); // AbortController for stopping
|
| 14 |
+
const [didDestroy, setDidDestroy] = useState(false);
|
| 15 |
|
| 16 |
+
const initializePlayer = async (source, title) => {
|
|
|
|
| 17 |
if (abortController) {
|
| 18 |
+
abortController.abort();
|
| 19 |
}
|
| 20 |
+
|
|
|
|
| 21 |
const newAbortController = new AbortController();
|
| 22 |
setAbortController(newAbortController);
|
| 23 |
|
|
|
|
| 24 |
const extractedFileName = source.split('/').pop();
|
| 25 |
+
setTitle(title);
|
| 26 |
+
if (videoRef.current) {
|
| 27 |
videoRef.current.pause();
|
| 28 |
}
|
| 29 |
|
| 30 |
try {
|
|
|
|
| 31 |
const response = await fetch(`/api/get/music/${encodeURIComponent(extractedFileName)}`, {
|
| 32 |
signal: newAbortController.signal,
|
| 33 |
});
|
| 34 |
const data = await response.json();
|
| 35 |
|
|
|
|
| 36 |
if (data.status === "Download started") {
|
| 37 |
const progressUrl = data.progress_url;
|
| 38 |
checkProgress(progressUrl, extractedFileName, newAbortController);
|
| 39 |
} else {
|
| 40 |
+
startPlayer(data.url);
|
| 41 |
}
|
| 42 |
} catch (error) {
|
| 43 |
if (error.name !== 'AbortError') {
|
| 44 |
+
console.error("Error initializing player:", error);
|
| 45 |
}
|
| 46 |
}
|
| 47 |
};
|
|
|
|
| 51 |
try {
|
| 52 |
const response = await fetch(progressUrl, { signal: abortController.signal });
|
| 53 |
const progressData = await response.json();
|
|
|
|
| 54 |
setLoadingProgress(progressData.progress);
|
| 55 |
|
| 56 |
if (progressData.progress.status === "Completed") {
|
|
|
|
| 59 |
}
|
| 60 |
} catch (error) {
|
| 61 |
if (error.name === 'AbortError') {
|
| 62 |
+
clearInterval(intervalId);
|
| 63 |
} else {
|
| 64 |
console.error("Error fetching progress:", error);
|
| 65 |
}
|
|
|
|
| 77 |
if (data.url) {
|
| 78 |
startPlayer(data.url);
|
| 79 |
} else {
|
|
|
|
| 80 |
retryFetchFinalUrl(fileName, abortController, attempt);
|
| 81 |
}
|
| 82 |
} catch (error) {
|
| 83 |
if (error.name !== 'AbortError') {
|
| 84 |
console.error("Error fetching final URL:", error);
|
|
|
|
| 85 |
retryFetchFinalUrl(fileName, abortController, attempt);
|
| 86 |
}
|
| 87 |
}
|
| 88 |
};
|
| 89 |
|
| 90 |
const retryFetchFinalUrl = (fileName, abortController, attempt) => {
|
| 91 |
+
const retryDelay = 2000;
|
| 92 |
setTimeout(() => {
|
| 93 |
console.log(`Retry attempt ${attempt} for ${fileName}`);
|
| 94 |
+
fetchFinalUrl(fileName, abortController, attempt + 1);
|
| 95 |
}, retryDelay);
|
| 96 |
};
|
| 97 |
|
| 98 |
const startPlayer = (source) => {
|
| 99 |
+
setDidDestroy(false);
|
| 100 |
+
console.log("setting didDestroy to false");
|
| 101 |
setSrc(source);
|
| 102 |
setIsPlayerVisible(true);
|
| 103 |
if (videoRef.current) {
|
| 104 |
videoRef.current.src = source;
|
| 105 |
videoRef.current.load();
|
| 106 |
+
videoRef.current.play().catch((error) => {
|
| 107 |
+
console.error("Error playing video:", error);
|
| 108 |
+
});
|
| 109 |
}
|
| 110 |
};
|
| 111 |
|
|
|
|
| 118 |
initializePlayer,
|
| 119 |
isPlayerVisible,
|
| 120 |
src,
|
| 121 |
+
title,
|
| 122 |
isPlayerMaximized,
|
| 123 |
togglePlayerSize,
|
| 124 |
setIsPlayerVisible,
|
| 125 |
loadingProgress,
|
| 126 |
+
nowPlaying,
|
| 127 |
+
setNowPlaying,
|
| 128 |
+
didDestroy,
|
| 129 |
+
setDidDestroy,
|
| 130 |
}}
|
| 131 |
>
|
| 132 |
{children}
|
frontend/src/lib/utils.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export function formatTitle(src){
|
| 2 |
+
return src.split('/').pop().split('.')[0];
|
| 3 |
+
}
|