aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2026-06-04 00:16:34 -0700
committerPinapelz <yukais@pinapelz.com>2026-06-04 00:16:40 -0700
commitd8e3e8cf13b83e2ea90abed8b83364730630eaf4 (patch)
tree7e23c4fee933ee6842a3c1a258235b09cfa752af
parent44ace7e6b704d90b8e71906805cff07202ddf50b (diff)
feat: volume slider for regular media player
-rw-r--r--src/components/Player/index.tsx57
-rw-r--r--src/components/YTPlayer/index.styled.ts22
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;
+`;
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage