diff options
| author | Pinapelz <yukais@pinapelz.com> | 2026-06-04 00:16:34 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2026-06-04 00:16:40 -0700 |
| commit | d8e3e8cf13b83e2ea90abed8b83364730630eaf4 (patch) | |
| tree | 7e23c4fee933ee6842a3c1a258235b09cfa752af | |
| parent | 44ace7e6b704d90b8e71906805cff07202ddf50b (diff) | |
feat: volume slider for regular media player
| -rw-r--r-- | src/components/Player/index.tsx | 57 | ||||
| -rw-r--r-- | src/components/YTPlayer/index.styled.ts | 22 |
2 files changed, 77 insertions, 2 deletions
diff --git a/src/components/Player/index.tsx b/src/components/Player/index.tsx index 6a947d4..841d256 100644 --- a/src/components/Player/index.tsx +++ b/src/components/Player/index.tsx @@ -9,6 +9,21 @@ interface Props { } const MAX_TIME = 16; +const DEFAULT_VOLUME = 0.7; + +const loadVolume = () => { + try { + const storedVolume = localStorage.getItem("playerVolume"); + if (storedVolume === null) return DEFAULT_VOLUME; + + const parsedVolume = Number(storedVolume); + if (!Number.isFinite(parsedVolume)) return DEFAULT_VOLUME; + + return Math.max(0, Math.min(1, parsedVolume)); + } catch { + return DEFAULT_VOLUME; + } +}; export function Player({ currentTry }: Props) { const audioRef = React.useRef<HTMLAudioElement | null>(null); @@ -18,6 +33,7 @@ export function Player({ currentTry }: Props) { const [play, setPlay] = React.useState(false); const [currentTime, setCurrentTime] = React.useState(0); const [isReady, setIsReady] = React.useState(false); + const [volume, setVolume] = React.useState(loadVolume); const CDN_URL = import.meta.env.VITE_CDN_URL || "localhost"; @@ -41,8 +57,16 @@ export function Player({ currentTry }: Props) { setPlay(false); }, []); + const updateVolume = React.useCallback( + (event: React.ChangeEvent<HTMLInputElement>) => { + setVolume(Number(event.target.value)); + }, + [] + ); + React.useEffect(() => { const audio = new Audio(`${CDN_URL}/${dateString}.mp3`); + audio.volume = loadVolume(); audioRef.current = audio; audio.addEventListener("loadeddata", () => { @@ -62,13 +86,26 @@ export function Player({ currentTry }: Props) { audio.pause(); audio.src = ""; }; - }, [dateString]); + }, [CDN_URL, dateString]); + + React.useEffect(() => { + if (!audioRef.current) return; + + audioRef.current.volume = volume; + + try { + localStorage.setItem("playerVolume", String(volume)); + } catch { + } + }, [volume]); React.useEffect(() => { if (!play || !audioRef.current) return; const interval = setInterval(() => { - const a = audioRef.current!; + const a = audioRef.current; + if (!a) return; + const t = a.currentTime * 1000; setCurrentTime(a.currentTime); @@ -134,6 +171,22 @@ export function Player({ currentTry }: Props) { onClick={stopPlayback} /> )} + + <Styled.VolumeControl> + <Styled.VolumeLabel htmlFor="player-volume"> + Volume {Math.round(volume * 100)}% + </Styled.VolumeLabel> + <Styled.VolumeSlider + id="player-volume" + type="range" + min="0" + max="1" + step="0.01" + value={volume} + onChange={updateVolume} + aria-label="Volume" + /> + </Styled.VolumeControl> </> ) : ( <p>Loading audio...</p> diff --git a/src/components/YTPlayer/index.styled.ts b/src/components/YTPlayer/index.styled.ts index 3c98f1e..c437b9c 100644 --- a/src/components/YTPlayer/index.styled.ts +++ b/src/components/YTPlayer/index.styled.ts @@ -37,3 +37,25 @@ export const TimeStamp = styled.p` font-size: 0.7rem; color: var(--cl-gray-5); `; + +export const VolumeControl = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + width: 100%; + max-width: 240px; + margin: 12px auto 0; +`; + +export const VolumeLabel = styled.label` + font-family: "Roboto Mono", monospace; + font-size: 0.75rem; + color: var(--cl-gray-5); +`; + +export const VolumeSlider = styled.input` + width: 100%; + accent-color: ${({ theme }) => theme.green}; + cursor: pointer; +`; |
