-
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
12c656e
commit 97c80ff
Showing
15 changed files
with
458 additions
and
7 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
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
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
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
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
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,26 @@ | ||
"use client"; | ||
|
||
import useGetSongById from "@/hooks/useGetSongById"; | ||
import useLoadSongUrl from "@/hooks/useLoadSongUrl"; | ||
import usePlayer from "@/hooks/usePlayer"; | ||
import React from "react"; | ||
import PlayerContent from "./PlayerContent"; | ||
|
||
const Player = () => { | ||
const player = usePlayer(); | ||
const { song } = useGetSongById(player.activeId); | ||
|
||
const songUrl = useLoadSongUrl(song!); | ||
|
||
if (!song || !songUrl || !player.activeId) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className="fixed bottom-0 bg-black w-full py-2 h-[80px] px-4"> | ||
<PlayerContent song={song} songUrl={songUrl} key={songUrl} /> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Player; |
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,130 @@ | ||
import { Song } from "@/types"; | ||
import React, { useEffect, useState } from "react"; | ||
import LikeButton from "./LikeButton"; | ||
import MediaItem from "./MediaItem"; | ||
import { BsPauseFill, BsPlayFill } from "react-icons/bs"; | ||
import { AiFillStepBackward, AiFillStepForward } from "react-icons/ai"; | ||
import { HiSpeakerWave, HiSpeakerXMark } from "react-icons/hi2"; | ||
import Slider from "./Slider"; | ||
import usePlayer from "@/hooks/usePlayer"; | ||
import next from "next"; | ||
import useSound from "use-sound"; | ||
|
||
interface PlayerContentProps { | ||
song: Song; | ||
songUrl: string; | ||
} | ||
|
||
const PlayerContent: React.FC<PlayerContentProps> = ({ song, songUrl }) => { | ||
const player = usePlayer(); | ||
const [volume, setVolume] = useState(1); | ||
const [isPlaying, setIsPlaying] = useState<boolean>(false); | ||
|
||
const Icon = isPlaying ? BsPauseFill : BsPlayFill; | ||
const VolumeIcon = volume === 0 ? HiSpeakerXMark : HiSpeakerWave; | ||
|
||
const onPlayNext = () => { | ||
if (player.ids.length === 0) { | ||
return; | ||
} | ||
|
||
const currentIdx = player.ids.findIndex((id) => id === player.activeId); | ||
const nextSong = player.ids[currentIdx + 1]; | ||
|
||
if (!nextSong) return player.setId(player.ids[0]); | ||
|
||
player.setId(nextSong); | ||
}; | ||
|
||
const onPlayPrevious = () => { | ||
if (player.ids.length === 0) { | ||
return; | ||
} | ||
|
||
const currentIdx = player.ids.findIndex((id) => id === player.activeId); | ||
const previousSong = player.ids[currentIdx - 1]; | ||
|
||
if (!previousSong) return player.setId(player.ids[player.ids.length - 1]); | ||
|
||
player.setId(previousSong); | ||
}; | ||
|
||
const [play, { pause, sound }] = useSound(songUrl, { | ||
volume: volume, | ||
onplay: () => setIsPlaying(true), | ||
onend: () => { | ||
setIsPlaying(false); | ||
onPlayNext(); | ||
}, | ||
onpause: () => setIsPlaying(false), | ||
format: ["mp3"], | ||
}); | ||
|
||
useEffect(() => { | ||
sound?.play(); | ||
return () => { | ||
sound?.unload(); | ||
}; | ||
}, [sound]); | ||
|
||
const handlePlay = () => { | ||
if (!isPlaying) play(); | ||
else pause(); | ||
}; | ||
|
||
const toggleMute = () => { | ||
if (volume === 0) setVolume(1); | ||
else setVolume(0); | ||
}; | ||
|
||
return ( | ||
<div className="grid grid-cols-2 md:grid-cols-3 h-full"> | ||
<div className="flex w-full justify-start"> | ||
<div className="flex items-center gap-x-4"> | ||
<MediaItem data={song} /> | ||
<LikeButton songId={song.id} /> | ||
</div> | ||
</div> | ||
<div className="flex md:hidden col-auto w-full justify-end items-center"> | ||
<div | ||
onClick={handlePlay} | ||
className="h-10 w-10 flex items-center justify-center rounded-full bg-white p-1 cursor-pointer" | ||
> | ||
<Icon size={30} className="text-black" /> | ||
</div> | ||
</div> | ||
|
||
<div className="hidden h-full md:flex justify-center items-center w-full max-w-[722px] gap-x-6"> | ||
<AiFillStepBackward | ||
onClick={onPlayPrevious} | ||
size={30} | ||
className="text-neutral-400 cursor-pointer hover:text-white transition" | ||
/> | ||
<div | ||
onClick={handlePlay} | ||
className="flex items-center justify-center h-10 w-10 rounded-full bg-white p-1 cursor-pointer" | ||
> | ||
<Icon size={30} className="text-black" /> | ||
</div> | ||
<AiFillStepForward | ||
onClick={onPlayNext} | ||
size={30} | ||
className="text-neutral-400 cursor-pointer hover:text-white transition" | ||
/> | ||
|
||
<div className="hidden md:flex w-full justify-end pr-2"> | ||
<div className="flex items-center gap-x-2 w-[120px]"> | ||
<VolumeIcon | ||
onClick={toggleMute} | ||
className="cursor-pointer" | ||
size={30} | ||
/> | ||
<Slider value={volume} onChange={(val) => setVolume(val)} /> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default PlayerContent; |
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
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,33 @@ | ||
"use client"; | ||
|
||
import React from "react"; | ||
|
||
import * as RadixSlider from "@radix-ui/react-slider"; | ||
|
||
interface SliderProps { | ||
value?: number; | ||
onChange?: (value: number) => void; | ||
} | ||
|
||
const Slider: React.FC<SliderProps> = ({ value = 1, onChange }) => { | ||
const handleChange = (newValue: number[]) => { | ||
onChange?.(newValue[0]); | ||
}; | ||
return ( | ||
<RadixSlider.Root | ||
className="relative flex items-center select-none touchnone w-full h-10" | ||
defaultValue={[1]} | ||
value={[value]} | ||
onValueChange={handleChange} | ||
max={1} | ||
step={0.1} | ||
aria-label="Volume" | ||
> | ||
<RadixSlider.Track className="bg-neutral-600 relative grow rounded-full h-[3px]"> | ||
<RadixSlider.Range className="absolute bg-white rounded-full h-full" /> | ||
</RadixSlider.Track> | ||
</RadixSlider.Root> | ||
); | ||
}; | ||
|
||
export default Slider; |
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,36 @@ | ||
import { Song } from "@/types"; | ||
import { useSessionContext } from "@supabase/auth-helpers-react"; | ||
import { useEffect, useMemo, useState } from "react" | ||
import toast from "react-hot-toast"; | ||
|
||
const useGetSongById = (id?: string) => { | ||
const [isLoading, setIsLoading] = useState<boolean>(false); | ||
const [song, setSong] = useState<Song | undefined>(undefined); | ||
const { supabaseClient } = useSessionContext(); | ||
|
||
useEffect(() => { | ||
if (!id) return | ||
setIsLoading(true); | ||
|
||
const fetchSong = async () => { | ||
const { data, error } = await supabaseClient.from('songs').select('*').eq('id', id).single(); | ||
|
||
if (error) { | ||
setIsLoading(false) | ||
return toast.error(error.message) | ||
} | ||
|
||
setSong(data as Song) | ||
setIsLoading(false) | ||
} | ||
|
||
fetchSong(); | ||
}, [id, supabaseClient]) | ||
|
||
return useMemo(() => ({ | ||
isLoading, | ||
song | ||
}), [isLoading, song]) | ||
} | ||
|
||
export default useGetSongById |
Oops, something went wrong.