Skip to content

Instantly share code, notes, and snippets.

@EvanMarie
Last active September 14, 2024 18:21
Show Gist options
  • Save EvanMarie/17f1784a861db984a3990a45ae5cd005 to your computer and use it in GitHub Desktop.
Save EvanMarie/17f1784a861db984a3990a45ae5cd005 to your computer and use it in GitHub Desktop.
import { useRef, useState } from "react";
import { motion } from "framer-motion";
import FlexFull from "~/components/buildingBlocks/flexFull";
import Text from "~/components/buildingBlocks/text";
import VStackFull from "~/components/buildingBlocks/vStackFull";
import { useAudioPlayer } from "~/hooks/useAudioPlayer";
import HStack from "~/components/buildingBlocks/hStack";
import Icon from "~/components/buildingBlocks/icon";
import { FaPlay, FaStop } from "react-icons/fa";
import Tooltip from "~/components/buildingBlocks/tooltip";
import { BsPlayFill, BsStopFill } from "react-icons/bs";
import { HiVolumeUp } from "react-icons/hi";
import Box from "~/components/buildingBlocks/box";
interface AudioPlayerProps {
buttonOnly?: boolean;
darkMode?: boolean;
audioUrl?: string;
audioName?: string;
baseAudioUrl?: string;
border?: string;
shadow?: string;
bg?: string;
title?: string;
showTitle?: boolean;
containerPadding?: string;
}
export default function AudioPlayer({
buttonOnly,
darkMode = true,
audioUrl,
audioName,
bg = darkMode
? "bg-slate-900 bg-gradient-to-br from-indigo-900/30 via-indigo-900/20 to-indigo-900/30"
: "bg-col-500 bg-gradient-to-br from-indigo-300/30 via-indigo-300/20 to-indigo-300/30",
border = darkMode ? "border-900-lg" : "border-200-md",
shadow = darkMode ? "insetShadow6xl" : "insetGlow6xl",
baseAudioUrl = "https://database-base-url.com,
showTitle = false,
containerPadding = "px-[3vh] pt-[0.3vh]",
}: AudioPlayerProps) {
const audioRef = useRef<HTMLAudioElement>(null);
const {
isPlaying,
currentTime,
duration,
togglePlayPause,
handleTimeUpdate,
handleLoadedMetadata,
handleSeek,
} = useAudioPlayer(audioRef);
// Use a local state to manually reset the currentTime to 0 on audio end
const [sliderValue, setSliderValue] = useState(0);
const url = audioUrl ? audioUrl : `${baseAudioUrl}/${audioName}`;
// Handle resetting the audio and range slider on audio end
const handleAudioEnd = () => {
setSliderValue(0); // Reset slider to 0
};
// Sync slider with audio time
const handleTimeUpdateWithSync = () => {
if (audioRef.current) {
setSliderValue(audioRef.current.currentTime); // Sync slider value with current time
}
handleTimeUpdate(); // Call the original time update logic
};
const iconStyles = `${
darkMode
? "text-col-500"
: "text-col-900 border-900-sm bg-col-260 hover:bg-col-200 transition-300 shadowNarrowTight"
} p-[0.4vh] rounded-full text-[2.5vh] h-full flex justify-center items-center hover:metallicEdgesXs transition-300 `;
const darkModeSlideStyle = `
bg-purple-300
[&::-moz-range-thumb]:border-900-sm
[&::-moz-range-thumb]:hover:boxGlowXs
[&::-moz-range-thumb]:hover:bg-sky-400
[&::-moz-range-thumb]:bg-col-500
[&::-webkit-slider-thumb]:border-900-sm
[&::-webkit-slider-thumb]:hover:boxGlowXs
[&::-webkit-slider-thumb]:hover:bg-sky-400
[&::-webkit-slider-thumb]:bg-col-500
transition-300
`;
const lightModeSlideStyle = `
bg-col-800
[&::-moz-range-thumb]:border-200-md
[&::-moz-range-thumb]:hover:boxGlowXs
[&::-moz-range-thumb]:hover:bg-col-600
[&::-moz-range-thumb]:bg-col-700
[&::-webkit-slider-thumb]:border-200-md
[&::-webkit-slider-thumb]:hover:metallicEdgesSm
[&::-webkit-slider-thumb]:hover:bg-col-600
[&::-webkit-slider-thumb]:bg-col-700
transition-300
`;
return (
<>
{buttonOnly ? (
<>
<audio
ref={audioRef}
src={url}
onTimeUpdate={handleTimeUpdateWithSync} // Sync slider with audio time
onLoadedMetadata={handleLoadedMetadata}
onEnded={handleAudioEnd} // Reset slider when audio ends
className="w-full"
/>
<Tooltip placement="topRight" label="listen">
<motion.button
onClick={togglePlayPause}
className={`${iconStyles}}`}
>
{isPlaying ? (
<BsStopFill style={{ fontSize: "inherit" }} />
) : (
<BsPlayFill style={{ fontSize: "inherit" }} />
)}
</motion.button>
</Tooltip>
</>
) : (
<FlexFull
className={`relative h-fit rounded-[6vh] ${bg} ${border} ${shadow}`}
>
<Box
className={`absolute -top-[1vh] left-[1vh] bg-col-800 border-200-sm metallicEdgesSm p-[0.4vh] rounded-full`}
>
<Icon
icon={HiVolumeUp}
iconClassName={`text-col-200 text-[2.4vh]`}
/>
</Box>
<VStackFull
className={`${containerPadding} overflow-hidden`}
gap="gap-[0px]"
>
<audio
ref={audioRef}
src={url}
onTimeUpdate={handleTimeUpdateWithSync} // Sync slider with audio time
onLoadedMetadata={handleLoadedMetadata}
onEnded={handleAudioEnd} // Reset slider when audio ends
className="w-full"
/>
{title && showTitle && (
<FlexFull className=" justify-center items-center pb-[0.5vh]">
<Text
noOfLines={1}
className={`text-[2vh] ${
darkMode
? "text-col-500 textShadow"
: "text-col-900 font-semibold lightTextShadow"
}`}
>
{title}
</Text>
</FlexFull>
)}
<input
type="range"
value={sliderValue} // Bind slider value to the local state
min={0}
max={duration}
onChange={(e) => {
handleSeek(e); // Seek when the slider is moved
setSliderValue(Number(e.target.value)); // Keep sliderValue in sync
}}
className={`h-[0.6vh] w-full shadowNarrowNormal
appearance-none
focus:outline-black hover:cursor-pointer
[&::-moz-range-thumb]:size-[1.8vh]
[&::-moz-range-thumb]:appearance-none
${darkMode ? darkModeSlideStyle : lightModeSlideStyle}
[&::-webkit-slider-thumb]:shadowNarrowNormal
[&::-moz-range-thumb]:shadowNarrowNormal
[&::-moz-range-thumb]:hover:cursor-pointer
active:[&::-moz-range-thumb]:scale-110
[&::-webkit-slider-thumb]:size-[1.8vh]
[&::-webkit-slider-thumb]:appearance-none
active:[&::-webkit-slider-thumb]:scale-110
[&::-moz-range-thumb]:rounded-full
[&::-webkit-slider-thumb]:rounded-full
transition-300`}
/>
<FlexFull
className={`justify-between text-sm text-col-200 px-[1vh] pt-[0.5vh]`}
>
<Text
className={`text-[1.5vh] text-col-500 group-hover:text-col-pink transition-300 ${
darkMode
? "text-col-500 textShadow"
: "text-col-900 font-semibold lightTextShadow"
}
hoverCursor="hover:cursor-pointer`}
>
{formatTime(currentTime)}
</Text>
<motion.button
className="hover:cursor-pointer"
onClick={togglePlayPause}
>
<HStack
className="items-center hover:cursor-pointer group"
gap="gap-[0.3vh]"
>
<Icon
icon={isPlaying ? FaStop : FaPlay}
iconClassName={`text-[1.3vh] text-col-500 transition-300 ${
darkMode
? "text-col-500 textShadow group-hover:text-col-pink"
: "text-col-900 font-semibold lightTextShadow group-hover:text-indigo-800"
}
hoverCursor="hover:cursor-pointer`}
/>
<Text
className={`text-[1.6vh] transition-300 ${
darkMode
? "text-col-500 textShadow group-hover:text-col-pink "
: "text-col-900 font-semibold lightTextShadow group-hover:text-indigo-800"
}
hoverCursor="hover:cursor-pointer`}
>
{isPlaying ? "stop" : "play"}
</Text>
</HStack>
</motion.button>
<Text
className={`text-[1.5vh] text-col-500 group-hover:text-col-pink transition-300 ${
darkMode
? "text-col-500 textShadow"
: "text-col-900 font-semibold lightTextShadow"
}
hoverCursor="hover:cursor-pointer`}
>
{formatTime(duration)}
</Text>
</FlexFull>
</VStackFull>
</FlexFull>
)}
</>
);
}
function formatTime(time: number): string {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment