ChandimaPrabath commited on
Commit
ba6c831
·
1 Parent(s): 89a38e9

major update: add playlist

Browse files
frontend/src/app/globals.css CHANGED
@@ -13,6 +13,9 @@
13
  --foreground-2: #0b5da9;
14
  --foreground-3: #25253c;
15
  --foreground-4: #878787;
 
 
 
16
  }
17
 
18
  html,
@@ -40,7 +43,6 @@ footer {
40
  margin-right: 10px;
41
  flex-grow: 1;
42
  overflow-y: auto;
43
-
44
  /* Styled scrollbar */
45
  scrollbar-width: thin; /* Firefox */
46
  scrollbar-color: #888 #222;
@@ -64,3 +66,11 @@ footer {
64
  .app-container::-webkit-scrollbar-thumb:hover {
65
  background-color: #555; /* Thumb color on hover */
66
  }
 
 
 
 
 
 
 
 
 
13
  --foreground-2: #0b5da9;
14
  --foreground-3: #25253c;
15
  --foreground-4: #878787;
16
+ --foreground-5: #403f4f;
17
+ --highlight-color: #00aaff53;
18
+ --text-highlight: rgb(31, 242, 179);
19
  }
20
 
21
  html,
 
43
  margin-right: 10px;
44
  flex-grow: 1;
45
  overflow-y: auto;
 
46
  /* Styled scrollbar */
47
  scrollbar-width: thin; /* Firefox */
48
  scrollbar-color: #888 #222;
 
66
  .app-container::-webkit-scrollbar-thumb:hover {
67
  background-color: #555; /* Thumb color on hover */
68
  }
69
+
70
+ @media screen and (orientation: portrait) {
71
+ footer{
72
+ display: flex;
73
+ flex-direction: column;
74
+ }
75
+
76
+ }
frontend/src/app/layout.js CHANGED
@@ -1,12 +1,12 @@
1
- // RootLayout.js
2
  "use client";
3
 
4
  import localFont from "next/font/local";
5
  import "./globals.css";
6
  import MusicPlayer from "@/components/MusicPlayer";
7
  import { MusicPlayerProvider } from "@/context/MusicPlayerContext";
8
- import { AppProgressBar as ProgressBar } from 'next-nprogress-bar';
9
  import Header from "@/components/Header";
 
10
 
11
  const geistSans = localFont({
12
  src: "./fonts/GeistVF.woff",
@@ -32,9 +32,10 @@ export default function RootLayout({ children }) {
32
  options={{ showSpinner: false }}
33
  shallowRouting
34
  />
35
- <Header/>
36
- <div className="app-container">{children}</div>
37
  <footer className="bottom-0 flex items-center w-full">
 
38
  <MusicPlayer />
39
  </footer>
40
  </body>
 
 
1
  "use client";
2
 
3
  import localFont from "next/font/local";
4
  import "./globals.css";
5
  import MusicPlayer from "@/components/MusicPlayer";
6
  import { MusicPlayerProvider } from "@/context/MusicPlayerContext";
7
+ import { AppProgressBar as ProgressBar } from "next-nprogress-bar";
8
  import Header from "@/components/Header";
9
+ import Playlist from "@/components/Playlist";
10
 
11
  const geistSans = localFont({
12
  src: "./fonts/GeistVF.woff",
 
32
  options={{ showSpinner: false }}
33
  shallowRouting
34
  />
35
+ <Header />
36
+ <div className="app-container">{children} </div>
37
  <footer className="bottom-0 flex items-center w-full">
38
+ <Playlist />
39
  <MusicPlayer />
40
  </footer>
41
  </body>
frontend/src/app/playlists/page.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import SavedPlaylists from "@/components/SavedPlaylists";
2
+
3
+ export default function PlaylistsPage() {
4
+ return (
5
+ <div className="playlists-page font-[family-name:var(--font-geist-sans)]">
6
+ <SavedPlaylists/>
7
+ </div>
8
+ );
9
+ }
frontend/src/components/Card.css CHANGED
@@ -1,6 +1,6 @@
1
  .music-card {
2
  width: 250px;
3
- height: 40;
4
  background-image: linear-gradient(var(--background-4), var(--foreground-3));
5
  border-top-left-radius: 20px;
6
  border-bottom-left-radius: 20px;
@@ -8,9 +8,10 @@
8
  border-bottom-right-radius: 20px;
9
  display: flex;
10
  align-items: center;
11
- gap: 10px;
 
12
  overflow: hidden;
13
- transition: scale .3s ease;
14
  }
15
 
16
  .card-title {
@@ -22,6 +23,7 @@
22
  white-space: normal;
23
  font-size: 0.8rem;
24
  cursor: pointer;
 
25
  }
26
 
27
  .card-icon {
@@ -29,11 +31,22 @@
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;
@@ -43,7 +56,7 @@
43
  scale: 1.03;
44
  }
45
 
46
- .music-card.now-playing .card-icon{
47
  animation: rotateInfinite 1.5s linear infinite;
48
  }
49
 
 
1
  .music-card {
2
  width: 250px;
3
+ height: 50px; /* Increased height to accommodate the button */
4
  background-image: linear-gradient(var(--background-4), var(--foreground-3));
5
  border-top-left-radius: 20px;
6
  border-bottom-left-radius: 20px;
 
8
  border-bottom-right-radius: 20px;
9
  display: flex;
10
  align-items: center;
11
+ justify-content: space-between; /* Space between elements */
12
+ padding: 0 5px; /* Added padding for better spacing */
13
  overflow: hidden;
14
+ transition: scale 0.3s ease;
15
  }
16
 
17
  .card-title {
 
23
  white-space: normal;
24
  font-size: 0.8rem;
25
  cursor: pointer;
26
+ flex-grow: 1; /* Allow the title to take available space */
27
  }
28
 
29
  .card-icon {
 
31
  overflow: visible;
32
  }
33
 
34
+ .add-to-playlist {
35
+ background: none; /* Remove default button background */
36
+ border: none; /* Remove default button border */
37
+ cursor: pointer; /* Change cursor to pointer */
38
+ }
39
+
40
+ .add-icon {
41
+ font-size: 20px; /* Adjust the icon size as needed */
42
+ color: var(--foreground-secondary); /* Use a theme color */
43
+ }
44
+
45
+ .music-card:hover {
46
  scale: 1.03;
47
  }
48
 
49
+ .music-card.now-playing {
50
  background-image: linear-gradient(var(--background-2), var(--foreground-2));
51
  border: 2px solid var(--foreground-secondary);
52
  border-right: none;
 
56
  scale: 1.03;
57
  }
58
 
59
+ .music-card.now-playing .card-icon {
60
  animation: rotateInfinite 1.5s linear infinite;
61
  }
62
 
frontend/src/components/Card.js CHANGED
@@ -1,24 +1,36 @@
1
  "use client";
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>
21
  </button>
 
 
 
 
22
  );
23
  };
24
 
 
1
  "use client";
2
  import "./Card.css";
3
+ import { FaCompactDisc } from "react-icons/fa"; // Importing the add icon
4
+ import { TbPlaylistAdd } from "react-icons/tb";
5
  import { useMusicPlayer } from "@/context/MusicPlayerContext";
6
  import { formatTitle } from "@/lib/utils";
7
 
8
  const Card = ({ src }) => {
9
+ const { initializePlayer, nowPlaying, addToPlaylist } = useMusicPlayer();
10
  const title = formatTitle(src);
11
 
12
+ const handlePlayButtonClick = () => {
13
  initializePlayer(src, title);
14
  };
15
 
16
+ const handleAddToPlaylistClick = (e) => {
17
+ e.stopPropagation(); // Prevent the button click from triggering the card play
18
+ addToPlaylist({ source: src, title });
19
+ console.log(`${title} added to playlist`)
20
+ };
21
+
22
  return (
23
+ <>
24
+ <button onClick={handlePlayButtonClick}>
25
  <div className={`music-card ${nowPlaying === title ? "now-playing" : ""}`}>
26
  <FaCompactDisc className="card-icon" />
27
  <label className="card-title">{title}</label>
28
  </div>
29
  </button>
30
+ <button className="add-to-playlist" onClick={handleAddToPlaylistClick}>
31
+ <TbPlaylistAdd className="add-icon" />
32
+ </button>
33
+ </>
34
  );
35
  };
36
 
frontend/src/components/Header.js CHANGED
@@ -14,6 +14,9 @@ const Header = () => {
14
  <Link href="/" className="nav-link">
15
  Home
16
  </Link>
 
 
 
17
  </nav>
18
  </div>
19
  </header>
 
14
  <Link href="/" className="nav-link">
15
  Home
16
  </Link>
17
+ <Link href="/playlists" className="nav-link">
18
+ Playlists
19
+ </Link>
20
  </nav>
21
  </div>
22
  </header>
frontend/src/components/MusicPlayer.css CHANGED
@@ -257,7 +257,7 @@ input[type="range"]::-webkit-slider-thumb {
257
  /******** Firefox styles ********/
258
  /* slider track */
259
  input[type="range"]::-moz-range-track {
260
- background-color: #403f4f;
261
  border-radius: 0.5rem;
262
  height: 0.5rem;
263
  }
@@ -278,3 +278,7 @@ input[type="range"]::-moz-range-thumb {
278
  font-size: 1.3em;
279
  line-height: 1.2; /* Adjust line height for spacing */
280
  }
 
 
 
 
 
257
  /******** Firefox styles ********/
258
  /* slider track */
259
  input[type="range"]::-moz-range-track {
260
+ background-color: var(--foreground-5);
261
  border-radius: 0.5rem;
262
  height: 0.5rem;
263
  }
 
278
  font-size: 1.3em;
279
  line-height: 1.2; /* Adjust line height for spacing */
280
  }
281
+
282
+ button:disabled{
283
+ color: var(--foreground-5);
284
+ }
frontend/src/components/MusicPlayer.js CHANGED
@@ -29,10 +29,14 @@ export default function MusicPlayer() {
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,7 +50,8 @@ export default function MusicPlayer() {
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) {
@@ -54,7 +59,7 @@ export default function MusicPlayer() {
54
  setProgress((videoElement.currentTime / videoElement.duration) * 100);
55
  }
56
  };
57
-
58
  const handleLoadedMetadata = (videoElement) => {
59
  if (videoElement) {
60
  setDuration(videoElement.duration);
@@ -63,63 +68,132 @@ export default function MusicPlayer() {
63
  videoElement.play();
64
  }
65
  };
66
-
67
  const handleProgress = (videoElement) => {
68
  if (videoElement && videoElement.buffered.length > 0) {
69
- const bufferEnd = videoElement.buffered.end(videoElement.buffered.length - 1);
 
 
70
  const bufferValue = (bufferEnd / videoElement.duration) * 100;
71
  setBufferProgress(bufferValue);
72
  }
73
  };
74
-
75
  const handlePlay = () => {
76
  setIsPlaying(true);
77
  setNowPlaying(title);
78
  };
79
-
80
  const handlePause = () => {
81
  setIsPlaying(false);
82
  setNowPlaying("");
83
  };
84
-
85
  const attachEventListeners = (videoElement) => {
86
  if (videoElement) {
87
- videoElement.addEventListener("timeupdate", () => handleTimeUpdate(videoElement));
88
- videoElement.addEventListener("loadedmetadata", () => handleLoadedMetadata(videoElement));
89
- videoElement.addEventListener("progress", () => handleProgress(videoElement));
 
 
 
 
 
 
90
  videoElement.addEventListener("play", handlePlay);
91
  videoElement.addEventListener("pause", handlePause);
92
  }
93
  };
94
-
95
  const detachEventListeners = (videoElement) => {
96
  if (videoElement) {
97
- videoElement.removeEventListener("timeupdate", () => handleTimeUpdate(videoElement));
98
- videoElement.removeEventListener("loadedmetadata", () => handleLoadedMetadata(videoElement));
99
- videoElement.removeEventListener("progress", () => handleProgress(videoElement));
 
 
 
 
 
 
100
  videoElement.removeEventListener("play", handlePlay);
101
  videoElement.removeEventListener("pause", handlePause);
102
  }
103
  };
104
-
105
  useEffect(() => {
106
  const videoElement = videoRef.current;
107
-
108
  // Attach event listeners
109
  attachEventListeners(videoElement);
110
-
111
  return () => {
112
  // Detach event listeners
113
  detachEventListeners(videoElement);
114
  };
115
  }, [videoRef, currentSrc, nowPlaying, didDestroy]);
116
-
117
  useEffect(() => {
118
  if (src !== currentSrc) {
119
  setCurrentSrc(src);
120
  }
121
  }, [src, currentSrc]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
 
123
  useEffect(() => {
124
  if (showControls) {
125
  if (overlayTimeout.current) {
@@ -127,10 +201,10 @@ export default function MusicPlayer() {
127
  }
128
  overlayTimeout.current = setTimeout(() => setShowControls(false), 3000);
129
  }
130
-
131
  return () => clearTimeout(overlayTimeout.current);
132
  }, [showControls]);
133
-
134
  const togglePlayPause = () => {
135
  if (videoRef.current) {
136
  if (isPlaying) {
@@ -141,20 +215,20 @@ export default function MusicPlayer() {
141
  setIsPlaying(!isPlaying);
142
  }
143
  };
144
-
145
  const handleVolumeChange = (event) => {
146
  let volumeValue = parseFloat(event.target.value);
147
-
148
  // Ensure the volume is a finite number between 0 and 1
149
  volumeValue = Math.max(0, Math.min(1, volumeValue));
150
-
151
  setVolume(volumeValue);
152
  if (videoRef.current) {
153
  videoRef.current.volume = volumeValue;
154
  }
155
  setIsMuted(volumeValue === 0);
156
  };
157
-
158
  const toggleMute = () => {
159
  if (isMuted) {
160
  videoRef.current.volume = volume;
@@ -164,35 +238,36 @@ export default function MusicPlayer() {
164
  setIsMuted(true);
165
  }
166
  };
167
-
168
  const toggleFullscreen = () => {
169
  const doc = window.document;
170
  const docEl = doc.documentElement;
171
-
172
  const requestFullscreen =
173
  docEl.requestFullscreen ||
174
  docEl.mozRequestFullScreen ||
175
  docEl.webkitRequestFullscreen ||
176
  docEl.msRequestFullscreen;
177
-
178
  const exitFullscreen =
179
  doc.exitFullscreen ||
180
  doc.mozCancelFullScreen ||
181
  docEl.webkitExitFullscreen ||
182
  doc.msExitFullscreen;
183
-
184
  if (!isFullscreen) {
185
  requestFullscreen.call(docEl);
186
  } else {
187
  exitFullscreen.call(doc);
188
  }
189
-
190
  setIsFullscreen(!isFullscreen);
191
  };
192
-
193
  const destroyPlayer = () => {
194
  if (videoRef.current) {
195
  videoRef.current.pause();
 
196
  videoRef.current.removeAttribute("src"); // Clear the source
197
  videoRef.current.load(); // Reload to reset duration/currentTime
198
  setNowPlaying("");
@@ -207,7 +282,7 @@ export default function MusicPlayer() {
207
  console.log("setting didDestroy to true");
208
  }
209
  };
210
-
211
  const handleProgressClick = (e) => {
212
  const progressBar = e.currentTarget;
213
  const rect = progressBar.getBoundingClientRect();
@@ -218,7 +293,7 @@ export default function MusicPlayer() {
218
  setCurrentTime(newTime);
219
  }
220
  };
221
-
222
  const handleFastForward = () => {
223
  if (videoRef.current) {
224
  videoRef.current.currentTime = Math.min(
@@ -227,7 +302,7 @@ export default function MusicPlayer() {
227
  );
228
  }
229
  };
230
-
231
  const handleRewind = () => {
232
  if (videoRef.current) {
233
  videoRef.current.currentTime = Math.max(
@@ -236,13 +311,13 @@ export default function MusicPlayer() {
236
  );
237
  }
238
  };
239
-
240
  const handleMouseMove = () => {
241
  setShowControls(true);
242
  };
243
-
244
  if (!isPlayerVisible || !currentSrc) return null;
245
-
246
  return (
247
  <div
248
  className={
@@ -309,15 +384,17 @@ export default function MusicPlayer() {
309
  onChange={handleVolumeChange}
310
  />
311
  <button
312
- onClick={handleRewind}
313
  className="previous-btn player-min-button"
 
314
  >
315
  <TbPlayerSkipBack />
316
  </button>
317
 
318
  <button
319
- onClick={handleFastForward}
320
  className="next-btn player-min-button"
 
321
  >
322
  <TbPlayerSkipForward />
323
  </button>
@@ -357,7 +434,7 @@ export default function MusicPlayer() {
357
  <video
358
  ref={videoRef}
359
  src={currentSrc}
360
- poster="https://dlcdnwebimgs.asus.com/gain/4BB18AEF-347E-4DB6-B78C-C0FFE1F20385/w750/h470"
361
  preload="metadata"
362
  className="video-element"
363
  autoPlay
@@ -372,6 +449,14 @@ export default function MusicPlayer() {
372
  </button>
373
  </div>
374
  <div className="player-mini-control-center">
 
 
 
 
 
 
 
 
375
  <button className="player-min-button" onClick={handleRewind}>
376
  <TbRewindBackward5 />
377
  </button>
@@ -381,6 +466,14 @@ export default function MusicPlayer() {
381
  <button className="player-min-button" onClick={handleFastForward}>
382
  <TbRewindForward5 />
383
  </button>
 
 
 
 
 
 
 
 
384
  <button className="player-min-button" onClick={destroyPlayer}>
385
  <TbPlayerStop />
386
  </button>
 
29
  title,
30
  setNowPlaying,
31
  nowPlaying,
32
+ didDestroy,
33
  setDidDestroy,
34
+ playNext,
35
+ playPrevious,
36
+ canPlayPrevious,
37
+ canPlayNext,
38
  } = useMusicPlayer();
39
+
40
  const [currentSrc, setCurrentSrc] = useState(src);
41
  const [currentTime, setCurrentTime] = useState(0);
42
  const [duration, setDuration] = useState(0);
 
50
  const [isFullscreen, setIsFullscreen] = useState(false);
51
  const overlayTimeout = useRef(null);
52
  const seekTime = 5;
53
+ const poster = "https://dlcdnwebimgs.asus.com/gain/4BB18AEF-347E-4DB6-B78C-C0FFE1F20385/w750/h470"
54
+
55
  // Event Handlers
56
  const handleTimeUpdate = (videoElement) => {
57
  if (videoElement) {
 
59
  setProgress((videoElement.currentTime / videoElement.duration) * 100);
60
  }
61
  };
62
+
63
  const handleLoadedMetadata = (videoElement) => {
64
  if (videoElement) {
65
  setDuration(videoElement.duration);
 
68
  videoElement.play();
69
  }
70
  };
71
+
72
  const handleProgress = (videoElement) => {
73
  if (videoElement && videoElement.buffered.length > 0) {
74
+ const bufferEnd = videoElement.buffered.end(
75
+ videoElement.buffered.length - 1
76
+ );
77
  const bufferValue = (bufferEnd / videoElement.duration) * 100;
78
  setBufferProgress(bufferValue);
79
  }
80
  };
81
+
82
  const handlePlay = () => {
83
  setIsPlaying(true);
84
  setNowPlaying(title);
85
  };
86
+
87
  const handlePause = () => {
88
  setIsPlaying(false);
89
  setNowPlaying("");
90
  };
91
+
92
  const attachEventListeners = (videoElement) => {
93
  if (videoElement) {
94
+ videoElement.addEventListener("timeupdate", () =>
95
+ handleTimeUpdate(videoElement)
96
+ );
97
+ videoElement.addEventListener("loadedmetadata", () =>
98
+ handleLoadedMetadata(videoElement)
99
+ );
100
+ videoElement.addEventListener("progress", () =>
101
+ handleProgress(videoElement)
102
+ );
103
  videoElement.addEventListener("play", handlePlay);
104
  videoElement.addEventListener("pause", handlePause);
105
  }
106
  };
107
+
108
  const detachEventListeners = (videoElement) => {
109
  if (videoElement) {
110
+ videoElement.removeEventListener("timeupdate", () =>
111
+ handleTimeUpdate(videoElement)
112
+ );
113
+ videoElement.removeEventListener("loadedmetadata", () =>
114
+ handleLoadedMetadata(videoElement)
115
+ );
116
+ videoElement.removeEventListener("progress", () =>
117
+ handleProgress(videoElement)
118
+ );
119
  videoElement.removeEventListener("play", handlePlay);
120
  videoElement.removeEventListener("pause", handlePause);
121
  }
122
  };
123
+
124
  useEffect(() => {
125
  const videoElement = videoRef.current;
126
+
127
  // Attach event listeners
128
  attachEventListeners(videoElement);
129
+
130
  return () => {
131
  // Detach event listeners
132
  detachEventListeners(videoElement);
133
  };
134
  }, [videoRef, currentSrc, nowPlaying, didDestroy]);
135
+
136
  useEffect(() => {
137
  if (src !== currentSrc) {
138
  setCurrentSrc(src);
139
  }
140
  }, [src, currentSrc]);
141
+
142
+ useEffect(() => {
143
+ if ('mediaSession' in navigator) {
144
+ navigator.mediaSession.metadata = new MediaMetadata({
145
+ title: title,
146
+ artwork: [
147
+ { src: poster } // Ensure 'poster' is defined in your component
148
+ ]
149
+ });
150
+
151
+ navigator.mediaSession.setActionHandler('play', () => {
152
+ videoRef.current.play();
153
+ });
154
+
155
+ navigator.mediaSession.setActionHandler('pause', () => {
156
+ videoRef.current.pause();
157
+ });
158
+
159
+ navigator.mediaSession.setActionHandler('seekbackward', (details) => {
160
+ videoRef.current.currentTime = Math.max(videoRef.current.currentTime - (details.seekOffset || 10), 0);
161
+ });
162
+
163
+ navigator.mediaSession.setActionHandler('seekforward', (details) => {
164
+ videoRef.current.currentTime = Math.min(videoRef.current.currentTime + (details.seekOffset || 10), videoRef.current.duration);
165
+ });
166
+
167
+ navigator.mediaSession.setActionHandler('stop', () => {
168
+ destroyPlayer();
169
+ });
170
+
171
+ // New action handlers for previous and next
172
+ navigator.mediaSession.setActionHandler('previoustrack', () => {
173
+ playPrevious(); // Assuming playPrevious is a function that plays the previous track
174
+ });
175
+
176
+ navigator.mediaSession.setActionHandler('nexttrack', () => {
177
+ playNext(); // Assuming playNext is a function that plays the next track
178
+ });
179
+ }
180
+
181
+ // Cleanup function to reset media session when component unmounts
182
+ return () => {
183
+ if ('mediaSession' in navigator) {
184
+ navigator.mediaSession.metadata = null;
185
+ navigator.mediaSession.setActionHandler('play', null);
186
+ navigator.mediaSession.setActionHandler('pause', null);
187
+ navigator.mediaSession.setActionHandler('seekbackward', null);
188
+ navigator.mediaSession.setActionHandler('seekforward', null);
189
+ navigator.mediaSession.setActionHandler('stop', null);
190
+ navigator.mediaSession.setActionHandler('previoustrack', null);
191
+ navigator.mediaSession.setActionHandler('nexttrack', null);
192
+ }
193
+ };
194
+ }, [title, poster, playPrevious, playNext]);
195
 
196
+
197
  useEffect(() => {
198
  if (showControls) {
199
  if (overlayTimeout.current) {
 
201
  }
202
  overlayTimeout.current = setTimeout(() => setShowControls(false), 3000);
203
  }
204
+
205
  return () => clearTimeout(overlayTimeout.current);
206
  }, [showControls]);
207
+
208
  const togglePlayPause = () => {
209
  if (videoRef.current) {
210
  if (isPlaying) {
 
215
  setIsPlaying(!isPlaying);
216
  }
217
  };
218
+
219
  const handleVolumeChange = (event) => {
220
  let volumeValue = parseFloat(event.target.value);
221
+
222
  // Ensure the volume is a finite number between 0 and 1
223
  volumeValue = Math.max(0, Math.min(1, volumeValue));
224
+
225
  setVolume(volumeValue);
226
  if (videoRef.current) {
227
  videoRef.current.volume = volumeValue;
228
  }
229
  setIsMuted(volumeValue === 0);
230
  };
231
+
232
  const toggleMute = () => {
233
  if (isMuted) {
234
  videoRef.current.volume = volume;
 
238
  setIsMuted(true);
239
  }
240
  };
241
+
242
  const toggleFullscreen = () => {
243
  const doc = window.document;
244
  const docEl = doc.documentElement;
245
+
246
  const requestFullscreen =
247
  docEl.requestFullscreen ||
248
  docEl.mozRequestFullScreen ||
249
  docEl.webkitRequestFullscreen ||
250
  docEl.msRequestFullscreen;
251
+
252
  const exitFullscreen =
253
  doc.exitFullscreen ||
254
  doc.mozCancelFullScreen ||
255
  docEl.webkitExitFullscreen ||
256
  doc.msExitFullscreen;
257
+
258
  if (!isFullscreen) {
259
  requestFullscreen.call(docEl);
260
  } else {
261
  exitFullscreen.call(doc);
262
  }
263
+
264
  setIsFullscreen(!isFullscreen);
265
  };
266
+
267
  const destroyPlayer = () => {
268
  if (videoRef.current) {
269
  videoRef.current.pause();
270
+ videoRef.current.currentTime = 0;
271
  videoRef.current.removeAttribute("src"); // Clear the source
272
  videoRef.current.load(); // Reload to reset duration/currentTime
273
  setNowPlaying("");
 
282
  console.log("setting didDestroy to true");
283
  }
284
  };
285
+
286
  const handleProgressClick = (e) => {
287
  const progressBar = e.currentTarget;
288
  const rect = progressBar.getBoundingClientRect();
 
293
  setCurrentTime(newTime);
294
  }
295
  };
296
+
297
  const handleFastForward = () => {
298
  if (videoRef.current) {
299
  videoRef.current.currentTime = Math.min(
 
302
  );
303
  }
304
  };
305
+
306
  const handleRewind = () => {
307
  if (videoRef.current) {
308
  videoRef.current.currentTime = Math.max(
 
311
  );
312
  }
313
  };
314
+
315
  const handleMouseMove = () => {
316
  setShowControls(true);
317
  };
318
+
319
  if (!isPlayerVisible || !currentSrc) return null;
320
+
321
  return (
322
  <div
323
  className={
 
384
  onChange={handleVolumeChange}
385
  />
386
  <button
387
+ onClick={playPrevious}
388
  className="previous-btn player-min-button"
389
+ disabled={!canPlayPrevious}
390
  >
391
  <TbPlayerSkipBack />
392
  </button>
393
 
394
  <button
395
+ onClick={playNext}
396
  className="next-btn player-min-button"
397
+ disabled={!canPlayNext}
398
  >
399
  <TbPlayerSkipForward />
400
  </button>
 
434
  <video
435
  ref={videoRef}
436
  src={currentSrc}
437
+ poster={poster}
438
  preload="metadata"
439
  className="video-element"
440
  autoPlay
 
449
  </button>
450
  </div>
451
  <div className="player-mini-control-center">
452
+ <button
453
+ onClick={playPrevious}
454
+ className="previous-btn player-min-button"
455
+ disabled={!canPlayPrevious}
456
+ >
457
+ <TbPlayerSkipBack />
458
+ </button>
459
+
460
  <button className="player-min-button" onClick={handleRewind}>
461
  <TbRewindBackward5 />
462
  </button>
 
466
  <button className="player-min-button" onClick={handleFastForward}>
467
  <TbRewindForward5 />
468
  </button>
469
+
470
+ <button
471
+ onClick={playNext}
472
+ className="next-btn player-min-button"
473
+ disabled={!canPlayNext}
474
+ >
475
+ <TbPlayerSkipForward />
476
+ </button>
477
  <button className="player-min-button" onClick={destroyPlayer}>
478
  <TbPlayerStop />
479
  </button>
frontend/src/components/Playlist.css ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .playlist-container {
2
+ padding: 20px;
3
+ background-color: var(--foreground-1);
4
+ border-radius: 10px;
5
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
6
+ width: 100%;
7
+ }
8
+
9
+ .playlist {
10
+ list-style: none;
11
+ padding: 0;
12
+ margin: 0;
13
+ max-height: 30vh;
14
+ overflow-y: auto;
15
+ }
16
+
17
+ /* For WebKit browsers (Chrome, Edge, Safari) */
18
+ .playlist::-webkit-scrollbar {
19
+ width: 8px; /* Width of the scrollbar */
20
+ }
21
+
22
+ .playlist::-webkit-scrollbar-track {
23
+ background: #222; /* Track color */
24
+ border-radius: 4px;
25
+ }
26
+
27
+ .playlist::-webkit-scrollbar-thumb {
28
+ background-color: #888; /* Scrollbar thumb color */
29
+ border-radius: 4px;
30
+ }
31
+
32
+ .playlist::-webkit-scrollbar-thumb:hover {
33
+ background-color: #555; /* Thumb color on hover */
34
+ }
35
+
36
+ .playlist-item {
37
+ display: flex;
38
+ justify-content: space-between;
39
+ align-items: center;
40
+ padding: 10px;
41
+ border-bottom: 1px solid var(--border-color);
42
+ transition: background-color 0.3s; /* Smooth transition for background change */
43
+ }
44
+
45
+ .playlist-item.playing {
46
+ background-color: var(--highlight-color);
47
+ color: var(--text-highlight);
48
+ }
49
+
50
+ .track-title {
51
+ cursor: pointer;
52
+ font-size: 1rem;
53
+ color: var(--text-color);
54
+ }
55
+
56
+ .remove-button,
57
+ .save-button,
58
+ .clear-button {
59
+ background: none;
60
+ border: none;
61
+ cursor: pointer;
62
+ color: var(--foreground-secondary);
63
+ }
64
+
65
+ .remove-button:hover,
66
+ .clear-button:hover {
67
+ color: red; /* Change color on hover */
68
+ }
69
+
70
+ .playlist-action-container{
71
+ display: flex;
72
+ }
73
+
74
+ .save-button {
75
+ margin-left: 10px; /* Add some spacing */
76
+ }
77
+
78
+ .clear-button {
79
+ margin-left: 10px; /* Add some spacing */
80
+ display: flex;
81
+ }
82
+
83
+ .modal {
84
+ position: fixed;
85
+ top: 0;
86
+ left: 0;
87
+ right: 0;
88
+ bottom: 0;
89
+ background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ z-index: 1000; /* Ensure modal is on top */
94
+ }
95
+
96
+ .modal-content {
97
+ background: var(--foreground-5);
98
+ padding: 20px;
99
+ border-radius: 10px;
100
+ }
101
+
102
+ .modal-close-button {
103
+ background: none;
104
+ border: none;
105
+ color: var(--foreground-secondary);
106
+ cursor: pointer;
107
+ }
108
+
109
+ .save-confirm-button {
110
+ background-color: var(--primary-color);
111
+ color: white;
112
+ border: none;
113
+ padding: 5px 10px;
114
+ border-radius: 5px;
115
+ cursor: pointer;
116
+ }
117
+
118
+ .save-confirm-button:hover {
119
+ background-color: var(--primary-color-dark); /* Darker shade on hover */
120
+ }
121
+
122
+ .playlist-name-input {
123
+ background: var(--foreground-3);
124
+ padding: 5px;
125
+ border-radius: 5px;
126
+ }
frontend/src/components/Playlist.js ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import React, { useState } from "react";
3
+ import { useMusicPlayer } from "@/context/MusicPlayerContext";
4
+ import { formatTitle } from "@/lib/utils";
5
+ import { FaTrash } from "react-icons/fa";
6
+ import { TbPlaylistX } from "react-icons/tb";
7
+ import "./Playlist.css";
8
+
9
+ const Playlist = () => {
10
+ const { playlist, initializePlayer, removeFromPlaylist, currentIndex, setPlaylist } = useMusicPlayer();
11
+ const [isModalOpen, setIsModalOpen] = useState(false);
12
+ const [playlistName, setPlaylistName] = useState("");
13
+
14
+ const handlePlayTrack = (src, title) => {
15
+ initializePlayer(src, title);
16
+ };
17
+
18
+ const handleClearPlaylist = () => {
19
+ if (window.confirm("Are you sure you want to clear the playlist?")) {
20
+ setPlaylist([]); // Clear the playlist and trigger a re-render
21
+ }
22
+ };
23
+
24
+ const handleSavePlaylist = () => {
25
+ if (!playlistName.trim()) return;
26
+ const existingPlaylists = JSON.parse(localStorage.getItem("playlists")) || {};
27
+ existingPlaylists[playlistName] = playlist;
28
+ localStorage.setItem("playlists", JSON.stringify(existingPlaylists));
29
+ setIsModalOpen(false);
30
+ setPlaylistName("");
31
+ };
32
+
33
+ return (
34
+ <div className="playlist-container">
35
+ <h2>Your Playlist</h2>
36
+ <div className="playlist-action-container">
37
+ <button onClick={() => setIsModalOpen(true)} className="save-button">
38
+ Save Playlist
39
+ </button>
40
+ <button onClick={handleClearPlaylist} className="clear-button">
41
+ <TbPlaylistX /> Clear Playlist
42
+ </button>
43
+ </div>
44
+ {isModalOpen && (
45
+ <div className="modal">
46
+ <div className="modal-content">
47
+ <h3>Save Playlist</h3>
48
+ <input
49
+ className="playlist-name-input"
50
+ type="text"
51
+ placeholder="Enter playlist name"
52
+ value={playlistName}
53
+ onChange={(e) => setPlaylistName(e.target.value)}
54
+ />
55
+ <button onClick={handleSavePlaylist} className="save-confirm-button">
56
+ Save
57
+ </button>
58
+ <button onClick={() => setIsModalOpen(false)} className="modal-close-button">
59
+ Cancel
60
+ </button>
61
+ </div>
62
+ </div>
63
+ )}
64
+
65
+ {playlist.length === 0 ? (
66
+ <p>No tracks in the playlist</p>
67
+ ) : (
68
+ <ul className="playlist">
69
+ {playlist.map((track, index) => (
70
+ <li
71
+ key={index}
72
+ className={`playlist-item ${currentIndex === index ? "playing" : ""}`}
73
+ >
74
+ <div className="track-info">
75
+ <span
76
+ className="track-title"
77
+ onClick={() => handlePlayTrack(track.source, track.title)}
78
+ >
79
+ {formatTitle(track.source)}
80
+ </span>
81
+ </div>
82
+ <button className="remove-button" onClick={() => removeFromPlaylist(track.source)}>
83
+ <FaTrash />
84
+ </button>
85
+ </li>
86
+ ))}
87
+ </ul>
88
+ )}
89
+ </div>
90
+ );
91
+ };
92
+
93
+ export default Playlist;
frontend/src/components/SavedPlaylists.css ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .saved-playlists-container {
2
+ padding: 20px;
3
+ background-color: var(--foreground-1);
4
+ border-radius: 10px;
5
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
6
+ }
7
+
8
+ .saved-playlists {
9
+ list-style: none;
10
+ padding: 0;
11
+ margin: 0;
12
+ }
13
+
14
+ .saved-playlist-item {
15
+ display: flex;
16
+ justify-content: space-between;
17
+ align-items: center;
18
+ padding: 10px;
19
+ border-bottom: 1px solid var(--border-color);
20
+ }
21
+
22
+ .playlist-name {
23
+ font-size: 1rem;
24
+ color: var(--text-color);
25
+ }
26
+
27
+ .playlist-controls {
28
+ display: flex;
29
+ gap: 5px;
30
+ }
31
+
32
+ .play-button,
33
+ .edit-button,
34
+ .remove-button {
35
+ background: none;
36
+ border: none;
37
+ cursor: pointer;
38
+ color: var(--foreground-secondary);
39
+ }
40
+
41
+ .play-button:hover {
42
+ color: green; /* Change color on hover */
43
+ }
44
+
45
+ .edit-button:hover {
46
+ color: blue; /* Change color on hover */
47
+ }
48
+
49
+ .remove-button:hover {
50
+ color: red; /* Change color on hover */
51
+ }
52
+
53
+ .delete-all-button {
54
+ margin-bottom: 10px;
55
+ background-color: red;
56
+ color: white;
57
+ border: none;
58
+ padding: 5px 10px;
59
+ border-radius: 5px;
60
+ cursor: pointer;
61
+ }
62
+
63
+ .delete-all-button:hover {
64
+ background-color: darkred; /* Darker shade on hover */
65
+ }
frontend/src/components/SavedPlaylists.js ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import React, { useState } from "react";
3
+ import { useMusicPlayer } from "@/context/MusicPlayerContext";
4
+ import { formatTitle } from "@/lib/utils";
5
+ import { FaTrash, FaEdit } from "react-icons/fa"; // Import icons for edit and delete
6
+ import "./SavedPlaylists.css"; // Import a CSS file for styling
7
+
8
+ const SavedPlaylists = () => {
9
+ const { initializePlayer, addToPlaylist } = useMusicPlayer();
10
+ const [playlists, setPlaylists] = useState(() => JSON.parse(localStorage.getItem("playlists")) || {});
11
+
12
+ const handlePlayPlaylist = (tracks) => {
13
+ // Check if there's at least one track to play
14
+ if (tracks.length > 0) {
15
+ // Initialize the player with the first track
16
+ initializePlayer(tracks[0].source, tracks[0].title); // Start playing the first track
17
+
18
+ // Add the rest of the tracks to the playlist
19
+ tracks.slice(1).forEach(track => {
20
+ addToPlaylist({ source: track.source, title: track.title });
21
+ });
22
+ }
23
+ };
24
+
25
+ const handleDeletePlaylist = (name) => {
26
+ const updatedPlaylists = { ...playlists };
27
+ delete updatedPlaylists[name];
28
+ setPlaylists(updatedPlaylists);
29
+ localStorage.setItem("playlists", JSON.stringify(updatedPlaylists));
30
+ };
31
+
32
+ const handleDeleteAllPlaylists = () => {
33
+ setPlaylists({});
34
+ localStorage.removeItem("playlists");
35
+ };
36
+
37
+ const handleEditPlaylist = (name) => {
38
+ // Logic to edit the playlist can be implemented as needed
39
+ };
40
+
41
+ return (
42
+ <div className="saved-playlists-container">
43
+ <h2>Saved Playlists</h2>
44
+ <button onClick={handleDeleteAllPlaylists} className="delete-all-button">Delete All Playlists</button>
45
+ {Object.keys(playlists).length === 0 ? (
46
+ <p>No saved playlists</p>
47
+ ) : (
48
+ <ul className="saved-playlists">
49
+ {Object.entries(playlists).map(([name, tracks]) => (
50
+ <li key={name} className="saved-playlist-item">
51
+ <span className="playlist-name">{name}</span>
52
+ <div className="playlist-controls">
53
+ <button onClick={() => handlePlayPlaylist(tracks)} className="play-button">Play</button>
54
+ <button onClick={() => handleEditPlaylist(name)} className="edit-button"><FaEdit /></button>
55
+ <button onClick={() => handleDeletePlaylist(name)} className="remove-button"><FaTrash /></button>
56
+ </div>
57
+ </li>
58
+ ))}
59
+ </ul>
60
+ )}
61
+ </div>
62
+ );
63
+ };
64
+
65
+ export default SavedPlaylists;
frontend/src/context/MusicPlayerContext.js CHANGED
@@ -10,14 +10,33 @@ export const MusicPlayerProvider = ({ children }) => {
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
 
@@ -26,7 +45,7 @@ export const MusicPlayerProvider = ({ children }) => {
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,
@@ -97,7 +116,6 @@ export const MusicPlayerProvider = ({ children }) => {
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) {
@@ -108,6 +126,58 @@ export const MusicPlayerProvider = ({ children }) => {
108
 
109
  const togglePlayerSize = () => setIsPlayerMaximized(!isPlayerMaximized);
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  return (
112
  <MusicPlayerContext.Provider
113
  value={{
@@ -124,6 +194,16 @@ export const MusicPlayerProvider = ({ children }) => {
124
  setNowPlaying,
125
  didDestroy,
126
  setDidDestroy,
 
 
 
 
 
 
 
 
 
 
127
  }}
128
  >
129
  {children}
 
10
  const [isPlayerVisible, setIsPlayerVisible] = useState(false);
11
  const [isPlayerMaximized, setIsPlayerMaximized] = useState(false);
12
  const [loadingProgress, setLoadingProgress] = useState(null);
13
+ const [abortController, setAbortController] = useState(null);
14
  const [didDestroy, setDidDestroy] = useState(false);
15
 
16
+ // Playlist and index
17
+ const [playlist, setPlaylist] = useState([]);
18
+ const [currentIndex, setCurrentIndex] = useState(0);
19
+
20
+ const canPlayPrevious = currentIndex > 0;
21
+ const canPlayNext = currentIndex < playlist.length - 1;
22
+
23
  const initializePlayer = async (source, title) => {
24
+ // Check if song already exists in playlist
25
+ const songIndex = playlist.findIndex(song => song.source === source);
26
+
27
+ if (songIndex !== -1) {
28
+ setCurrentIndex(songIndex); // Set to existing song index
29
+ } else {
30
+ // Add new song and set to that new index
31
+ const newSong = { source, title };
32
+ setPlaylist((prev) => [...prev, newSong]);
33
+ setCurrentIndex(playlist.length); // Set index to the end (new song position)
34
+ }
35
+
36
  if (abortController) {
37
  abortController.abort();
38
  }
39
+
40
  const newAbortController = new AbortController();
41
  setAbortController(newAbortController);
42
 
 
45
  if (videoRef.current) {
46
  videoRef.current.pause();
47
  }
48
+
49
  try {
50
  const response = await fetch(`/api/get/music/${encodeURIComponent(extractedFileName)}`, {
51
  signal: newAbortController.signal,
 
116
 
117
  const startPlayer = (source) => {
118
  setDidDestroy(false);
 
119
  setSrc(source);
120
  setIsPlayerVisible(true);
121
  if (videoRef.current) {
 
126
 
127
  const togglePlayerSize = () => setIsPlayerMaximized(!isPlayerMaximized);
128
 
129
+ const addToPlaylist = (song) => {
130
+ const songIndex = playlist.findIndex(track => track.source === song.source);
131
+ if (songIndex === -1) {
132
+ setPlaylist((prev) => [...prev, song]);
133
+ }
134
+ };
135
+
136
+ const removeFromPlaylist = (source) => {
137
+ setPlaylist((prev) => prev.filter(track => track.source !== source));
138
+ if (currentIndex >= playlist.length) {
139
+ setCurrentIndex(playlist.length - 1);
140
+ }
141
+ };
142
+
143
+ const playNext = () => {
144
+ if (canPlayNext) {
145
+ const nextIndex = currentIndex + 1;
146
+ setCurrentIndex(nextIndex);
147
+ const { source, title } = playlist[nextIndex];
148
+ initializePlayer(source, title);
149
+ }
150
+ };
151
+
152
+ const playPrevious = () => {
153
+ if (canPlayPrevious) {
154
+ const prevIndex = currentIndex - 1;
155
+ setCurrentIndex(prevIndex);
156
+ const { source, title } = playlist[prevIndex];
157
+ initializePlayer(source, title);
158
+ }
159
+ };
160
+
161
+ const playAtIndex = (index) => {
162
+ if (index >= 0 && index < playlist.length) {
163
+ setCurrentIndex(index);
164
+ const { source, title } = playlist[index];
165
+ initializePlayer(source, title);
166
+ }
167
+ };
168
+
169
+ useEffect(() => {
170
+ const handleEnded = () => playNext();
171
+ if (videoRef.current) {
172
+ videoRef.current.addEventListener("ended", handleEnded);
173
+ }
174
+ return () => {
175
+ if (videoRef.current) {
176
+ videoRef.current.removeEventListener("ended", handleEnded);
177
+ }
178
+ };
179
+ }, [currentIndex, playlist]);
180
+
181
  return (
182
  <MusicPlayerContext.Provider
183
  value={{
 
194
  setNowPlaying,
195
  didDestroy,
196
  setDidDestroy,
197
+ playlist,
198
+ setPlaylist,
199
+ addToPlaylist,
200
+ removeFromPlaylist,
201
+ playNext,
202
+ playPrevious,
203
+ playAtIndex,
204
+ currentIndex,
205
+ canPlayPrevious,
206
+ canPlayNext,
207
  }}
208
  >
209
  {children}