-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8c1d76f
commit a8472bd
Showing
5 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import React from 'react'; | ||
|
||
const Track = ({ isPlaying, isActive, activeSong }) => ( | ||
<div className="flex-1 flex items-center justify-start"> | ||
<div className={`${isPlaying && isActive ? 'animate-[spin_3s_linear_infinite]' : ''} hidden sm:block h-16 w-16 mr-4`}> | ||
<img src={activeSong?.images?.coverart} alt="cover art" className="rounded-full" /> | ||
</div> | ||
<div className="w-[50%]"> | ||
<p className="truncate text-white font-bold text-lg"> | ||
{activeSong?.title ? activeSong?.title : 'No active Song'} | ||
</p> | ||
<p className="truncate text-gray-300"> | ||
{activeSong?.subtitle ? activeSong?.subtitle : 'No active Song'} | ||
</p> | ||
</div> | ||
</div> | ||
); | ||
|
||
export default Track; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import React, { useState, useEffect } from 'react'; | ||
import { useSelector, useDispatch } from 'react-redux'; | ||
|
||
import { nextSong, prevSong, playPause } from '../../redux/features/playerSlice'; | ||
import Controls from './Controls'; | ||
import Player from './Player'; | ||
import Seekbar from './Seekbar'; | ||
import Track from './Track'; | ||
import VolumeBar from './VolumeBar'; | ||
|
||
const MusicPlayer = () => { | ||
const { activeSong, currentSongs, currentIndex, isActive, isPlaying } = useSelector((state) => state.player); | ||
const [duration, setDuration] = useState(0); | ||
const [seekTime, setSeekTime] = useState(0); | ||
const [appTime, setAppTime] = useState(0); | ||
const [volume, setVolume] = useState(0.3); | ||
const [repeat, setRepeat] = useState(false); | ||
const [shuffle, setShuffle] = useState(false); | ||
const dispatch = useDispatch(); | ||
|
||
useEffect(() => { | ||
if (currentSongs.length) dispatch(playPause(true)); | ||
}, [currentIndex]); | ||
|
||
const handlePlayPause = () => { | ||
if (!isActive) return; | ||
|
||
if (isPlaying) { | ||
dispatch(playPause(false)); | ||
} else { | ||
dispatch(playPause(true)); | ||
} | ||
}; | ||
|
||
const handleNextSong = () => { | ||
dispatch(playPause(false)); | ||
|
||
if (!shuffle) { | ||
dispatch(nextSong((currentIndex + 1) % currentSongs.length)); | ||
} else { | ||
dispatch(nextSong(Math.floor(Math.random() * currentSongs.length))); | ||
} | ||
}; | ||
|
||
const handlePrevSong = () => { | ||
if (currentIndex === 0) { | ||
dispatch(prevSong(currentSongs.length - 1)); | ||
} else if (shuffle) { | ||
dispatch(prevSong(Math.floor(Math.random() * currentSongs.length))); | ||
} else { | ||
dispatch(prevSong(currentIndex - 1)); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="relative sm:px-12 px-8 w-full flex items-center justify-between"> | ||
<Track isPlaying={isPlaying} isActive={isActive} activeSong={activeSong} /> | ||
<div className="flex-1 flex flex-col items-center justify-center"> | ||
<Controls | ||
isPlaying={isPlaying} | ||
isActive={isActive} | ||
repeat={repeat} | ||
setRepeat={setRepeat} | ||
shuffle={shuffle} | ||
setShuffle={setShuffle} | ||
currentSongs={currentSongs} | ||
handlePlayPause={handlePlayPause} | ||
handlePrevSong={handlePrevSong} | ||
handleNextSong={handleNextSong} | ||
/> | ||
<Seekbar | ||
value={appTime} | ||
min="0" | ||
max={duration} | ||
onInput={(event) => setSeekTime(event.target.value)} | ||
setSeekTime={setSeekTime} | ||
appTime={appTime} | ||
/> | ||
<Player | ||
activeSong={activeSong} | ||
volume={volume} | ||
isPlaying={isPlaying} | ||
seekTime={seekTime} | ||
repeat={repeat} | ||
currentIndex={currentIndex} | ||
onEnded={handleNextSong} | ||
onTimeUpdate={(event) => setAppTime(event.target.currentTime)} | ||
onLoadedData={(event) => setDuration(event.target.duration)} | ||
/> | ||
</div> | ||
<VolumeBar value={volume} min="0" max="1" onChange={(event) => setVolume(event.target.value)} setVolume={setVolume} /> | ||
</div> | ||
); | ||
}; | ||
|
||
export default MusicPlayer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import React from "react"; | ||
import { useDispatch, useSelector } from "react-redux"; | ||
|
||
import { Error, Loader, SongCard } from "../components"; | ||
import { selectGenreListId } from "../redux/features/playerSlice"; | ||
import { useGetSongsByGenreQuery } from "../redux/services/shazamCore"; | ||
import { genres } from "../assets/constants"; | ||
|
||
const Discover = () => { | ||
const dispatch = useDispatch(); | ||
const { genreListId } = useSelector((state) => state.player); | ||
const { activeSong, isPlaying } = useSelector((state) => state.player); | ||
const { data, isFetching, error } = useGetSongsByGenreQuery( | ||
genreListId || "POP" | ||
); | ||
|
||
if (isFetching) return <Loader title="Loading songs..." />; | ||
|
||
if (error) return <Error />; | ||
|
||
const genreTitle = genres.find(({ value }) => value === genreListId)?.title; | ||
|
||
return ( | ||
<div className="flex flex-col"> | ||
<div className="w-full flex justify-between items-center sm:flex-row flex-col mt-4 mb-10"> | ||
<h2 className="font-bold text-3xl text-white text-left"> | ||
Discover {genreTitle} | ||
</h2> | ||
|
||
<select | ||
onChange={(e) => dispatch(selectGenreListId(e.target.value))} | ||
value={genreListId || "pop"} | ||
className="bg-black text-gray-300 p-3 text-sm rounded-lg outline-none sm:mt-0 mt-5" | ||
> | ||
{genres.map((genre) => ( | ||
<option key={genre.value} value={genre.value}> | ||
{genre.title} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
|
||
<div className="flex flex-wrap sm:justify-start justify-center gap-8"> | ||
{data?.map((song, i) => ( | ||
<SongCard | ||
key={song.key} | ||
song={song} | ||
isPlaying={isPlaying} | ||
activeSong={activeSong} | ||
data={data} | ||
i={i} | ||
/> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Discover; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Discover from './Discover'; | ||
import TopArtists from './TopArtists'; | ||
import ArtistDetails from './ArtistDetails'; | ||
import SongDetails from './SongDetails'; | ||
import Search from './Search'; | ||
import TopCharts from './TopCharts'; | ||
import AroundYou from './AroundYou'; | ||
|
||
export { | ||
Discover, | ||
Search, | ||
TopArtists, | ||
ArtistDetails, | ||
SongDetails, | ||
TopCharts, | ||
AroundYou, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { createSlice } from '@reduxjs/toolkit'; | ||
|
||
const initialState = { | ||
currentSongs: [], | ||
currentIndex: 0, | ||
isActive: false, | ||
isPlaying: false, | ||
activeSong: {}, | ||
genreListId: '', | ||
}; | ||
|
||
const playerSlice = createSlice({ | ||
name: 'player', | ||
initialState, | ||
reducers: { | ||
setActiveSong: (state, action) => { | ||
state.activeSong = action.payload.song; | ||
|
||
if (action.payload?.data?.tracks?.hits) { | ||
state.currentSongs = action.payload.data.tracks.hits; | ||
} else if (action.payload?.data?.properties) { | ||
state.currentSongs = action.payload?.data?.tracks; | ||
} else { | ||
state.currentSongs = action.payload.data; | ||
} | ||
|
||
state.currentIndex = action.payload.i; | ||
state.isActive = true; | ||
}, | ||
|
||
nextSong: (state, action) => { | ||
if (state.currentSongs[action.payload]?.track) { | ||
state.activeSong = state.currentSongs[action.payload]?.track; | ||
} else { | ||
state.activeSong = state.currentSongs[action.payload]; | ||
} | ||
|
||
state.currentIndex = action.payload; | ||
state.isActive = true; | ||
}, | ||
|
||
prevSong: (state, action) => { | ||
if (state.currentSongs[action.payload]?.track) { | ||
state.activeSong = state.currentSongs[action.payload]?.track; | ||
} else { | ||
state.activeSong = state.currentSongs[action.payload]; | ||
} | ||
|
||
state.currentIndex = action.payload; | ||
state.isActive = true; | ||
}, | ||
|
||
playPause: (state, action) => { | ||
state.isPlaying = action.payload; | ||
}, | ||
|
||
selectGenreListId: (state, action) => { | ||
state.genreListId = action.payload; | ||
}, | ||
}, | ||
}); | ||
|
||
export const { setActiveSong, nextSong, prevSong, playPause, selectGenreListId } = playerSlice.actions; | ||
|
||
export default playerSlice.reducer; |