Skip to content

Instantly share code, notes, and snippets.

@stephyswe
Created June 12, 2023 16:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stephyswe/b481171dcc5069dbc45d40610c457bb1 to your computer and use it in GitHub Desktop.
Save stephyswe/b481171dcc5069dbc45d40610c457bb1 to your computer and use it in GitHub Desktop.
PlayerContent, (upd) only update sound when thumb is inactive
"use client";
import { useEffect, useRef, useState } from "react";
import { AiFillStepBackward, AiFillStepForward } from "react-icons/ai";
import { BsPauseFill, BsPlayFill } from "react-icons/bs";
import { HiSpeakerWave, HiSpeakerXMark } from "react-icons/hi2";
import useSound from "use-sound";
import Slider from "@/components/Slider";
import SliderTrack from "@/components/SliderTrack";
import usePlayer from "@/hooks/usePlayer";
import { Song } from "@/types";
import LikeButton from "./LikeButton";
import MediaItem from "./MediaItem";
interface PlayerContentProps {
song: Song | undefined;
songUrl: string;
}
function formatTime(seconds: number) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs < 10 ? "0" : ""}${secs}`;
}
const PlayerContent: React.FC<PlayerContentProps> = ({ song, songUrl }) => {
const player = usePlayer();
const [volume, setVolume] = useState(1);
const [isPlaying, setIsPlaying] = useState(false);
const [progress, setProgress] = useState(0);
//
const [isDragging, setIsDragging] = useState(false);
const [draggingPosition, setDraggingPosition] = useState(0);
const Icon = isPlaying ? BsPauseFill : BsPlayFill;
const VolumeIcon = volume === 0 ? HiSpeakerXMark : HiSpeakerWave;
const onPlayNext = () => {
if (player.ids.length === 0) {
return;
}
const currentIndex = player.ids.findIndex((id) => id === player.activeId);
const nextSong = player.ids[currentIndex + 1];
if (!nextSong) {
return player.setId(player.ids[0]);
}
player.setId(nextSong);
};
const onPlayPrevious = () => {
if (player.ids.length === 0) {
return;
}
const currentIndex = player.ids.findIndex((id) => id === player.activeId);
const previousSong = player.ids[currentIndex - 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]);
// Create a reference for the interval id
const intervalId = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
// Only start the interval if the sound is loaded and is playing
if (sound && isPlaying) {
intervalId.current = setInterval(() => {
if (!isDragging) {
setProgress(sound.seek() as number);
}
}, 1000);
} else {
// If the sound is not playing, clear the interval
if (intervalId.current) {
clearInterval(intervalId.current);
}
}
// Clean up function to clear the interval when the component unmounts
return () => {
if (intervalId.current) {
clearInterval(intervalId.current);
}
};
}, [sound, isPlaying, isDragging]);
const handlePlay = () => {
if (!isPlaying) {
play();
} else {
pause();
}
};
const toggleMute = () => {
if (volume === 0) {
setVolume(1);
} else {
setVolume(0);
}
};
const handleSeekChange = () => {
if (!sound) {
return;
}
setIsDragging(false);
sound.seek(draggingPosition);
};
// Update handleSliderChange function
const handleSliderChange = (value: any) => {
if (!sound) {
return;
}
setDraggingPosition(value);
setIsDragging(true);
setProgress(value);
};
return (
<div className="grid grid-cols-2 md:grid-cols-3 h-full">
<div className="flex w-full justify-start">
{song ? (
<div className="flex items-center gap-x-4">
<MediaItem data={song} />
<LikeButton songId={song.id} />
</div>
) : null}
</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
flex-col
justify-center
items-center
w-full
max-w-[722px]
gap-x-4
"
>
<div
className="
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>
<div className="w-full md:flex justify-center items-center gap-x-2">
<span className="text-white text-xs">{formatTime(progress)}</span>
<SliderTrack
value={progress}
onChange={(value) => handleSliderChange(value)}
onSeek={handleSeekChange}
max={sound ? Math.floor(sound.duration()) : 0}
setIsDragging={setIsDragging}
/>
<span className="text-white text-xs">
{sound ? formatTime(Math.floor(sound.duration())) : "0:00"}
</span>
</div>
</div>
<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={34}
/>
<Slider value={volume} onChange={(value) => setVolume(value)} />
</div>
</div>
</div>
);
};
export default PlayerContent;
"use client";
import * as RadixSlider from "@radix-ui/react-slider";
interface SlideProps {
value?: number;
onChange?: (value: number) => void;
max?: number;
}
const Slider: React.FC<SlideProps> = ({ value = 1, onChange, max = 1 }) => {
const handleChange = (newValue: number[]) => {
onChange?.(newValue[0]);
};
return (
<RadixSlider.Root
className="
relative
flex
items-center
select-none
touch-none
w-full
h-2
group
"
defaultValue={[1]}
value={[value]}
onValueChange={handleChange}
max={max}
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
group-hover:bg-green-500
"
/>
</RadixSlider.Track>
<RadixSlider.Thumb
className="
block
w-3
h-3
bg-transparent
group-hover:bg-white
rounded-full
cursor-pointer
border-none
focus:outline-none
"
/>
</RadixSlider.Root>
);
};
export default Slider;
"use client";
import * as RadixSlider from "@radix-ui/react-slider";
interface SlideProps {
value?: number;
onChange?: (value: number) => void;
max?: number;
setIsDragging: (value: boolean) => void;
onSeek: () => void;
}
const SliderTrack: React.FC<SlideProps> = ({
value = 1,
onChange,
max = 1,
onSeek,
}) => {
return (
<RadixSlider.Root
className="
relative
flex
items-center
select-none
touch-none
w-full
h-2
group
"
defaultValue={[1]}
value={[value]}
onValueChange={(value) => onChange?.(value[0])}
onPointerUp={onSeek}
max={max}
step={0.0001}
aria-label="Volume"
>
<RadixSlider.Track
className="
bg-neutral-600
relative
grow
rounded-full
h-[3px]
"
>
<RadixSlider.Range
className="
absolute
bg-white
group-hover:bg-green-500
rounded-full
h-full
left-0
transition-width
"
/>
</RadixSlider.Track>
<RadixSlider.Thumb
className="
block
w-3
h-3
bg-transparent
group-hover:bg-white
rounded-full
cursor-pointer
focus:outline-none
"
/>
</RadixSlider.Root>
);
};
export default SliderTrack;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment