From 52f008d7eb638143e1319d140d7058bfa1766674 Mon Sep 17 00:00:00 2001
From: Pinapelz <yukais@pinapelz.com>
Date: Sun, 7 Jun 2026 03:19:34 -0700
Subject: make unlimited mode start at random point in video

---
 src/components/YTPlayer/index.tsx | 149 +++++++++++++++++++++++++++++++-------
 1 file changed, 122 insertions(+), 27 deletions(-)

(limited to 'src/components/YTPlayer/index.tsx')

diff --git a/src/components/YTPlayer/index.tsx b/src/components/YTPlayer/index.tsx
index 1aac9ac..9910351 100644
--- a/src/components/YTPlayer/index.tsx
+++ b/src/components/YTPlayer/index.tsx
@@ -9,81 +9,160 @@ interface Props {
   currentTry: number;
 }
 
+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({ id, currentTry }: Props) {
   const opts = {
     width: "0",
     height: "0",
   };
 
-  // react-youtube doesn't export types for this
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   const playerRef = React.useRef<any>(null);
 
+  const MIN_REMAINING_SEC = 17;
   const currentPlayTime = playTimes[currentTry];
+  const totalPlayTime = playTimes[playTimes.length - 1];
+  const progressDurationSec = totalPlayTime / 1000;
 
-  const [play, setPlay] = React.useState<boolean>(false);
-
-  const [currentTime, setCurrentTime] = React.useState<number>(0);
+  const [play, setPlay] = React.useState(false);
+  const [startTime, setStartTime] = React.useState(0);
+  const [currentTime, setCurrentTime] = React.useState(0);
+  const [isReady, setIsReady] = React.useState(false);
+  const [volume, setVolume] = React.useState(loadVolume);
 
-  const [isReady, setIsReady] = React.useState<boolean>(false);
+  const progressValue = Math.min(
+    progressDurationSec,
+    Math.max(0, currentTime - startTime),
+  );
 
   React.useEffect(() => {
-    setInterval(() => {
+    const interval = setInterval(() => {
       playerRef.current?.internalPlayer
-        .getCurrentTime()
-        .then((time: number) => {
-          setCurrentTime(time);
-        });
+        ?.getCurrentTime()
+        .then((time: number) => setCurrentTime(time));
     }, 250);
+
+    return () => clearInterval(interval);
   }, []);
 
   React.useEffect(() => {
-    if (play) {
-      if (currentTime * 1000 >= currentPlayTime) {
-        playerRef.current?.internalPlayer.pauseVideo();
-        playerRef.current?.internalPlayer.seekTo(0);
-        setPlay(false);
-      }
+    if (!play) return;
+
+    const elapsedMs = Math.max(0, (currentTime - startTime) * 1000);
+
+    if (elapsedMs >= currentPlayTime) {
+      playerRef.current?.internalPlayer.pauseVideo();
+      playerRef.current?.internalPlayer.seekTo(startTime, true);
+      setPlay(false);
     }
-  }, [play, currentTime]);
+  }, [play, currentTime, startTime, currentPlayTime]);
 
-  // don't call play video each time currentTime changes
   const startPlayback = React.useCallback(() => {
-    playerRef.current?.internalPlayer.playVideo();
+    const player = playerRef.current?.internalPlayer;
+    if (!player) return;
+
+    player.seekTo(startTime, true);
+    player.playVideo();
     setPlay(true);
-  }, []);
+  }, [startTime]);
+
+  const updateVolume = React.useCallback(
+    (event: React.ChangeEvent<HTMLInputElement>) => {
+      setVolume(Number(event.target.value));
+    },
+    [],
+  );
+
+  const setReady = React.useCallback(async () => {
 
-  const setReady = React.useCallback(() => {
+    const player = playerRef.current?.internalPlayer;
+    if (!player) return;
+
+    const duration = await player.getDuration();
+
+    const maxStart = Math.max(0, duration - MIN_REMAINING_SEC);
+    const randomStart = Math.random() * maxStart;
+
+    setStartTime(randomStart);
+    player.setVolume(volume * 100);
+    player.seekTo(randomStart, true);
     setIsReady(true);
-  }, []);
+  }, [volume]);
+
+  React.useEffect(() => {
+    if (!isReady) return;
+
+    const player = playerRef.current?.internalPlayer;
+    if (!player) return;
+
+    player.getDuration().then((duration: number) => {
+      const maxStart = Math.max(0, duration - MIN_REMAINING_SEC);
+      const randomStart = Math.random() * maxStart;
+
+      setStartTime(randomStart);
+      setPlay(false);
+      setCurrentTime(0);
+
+      player.seekTo(randomStart, true);
+    });
+  }, [id, isReady]);
+
+  React.useEffect(() => {
+    if (!isReady) return;
+
+    playerRef.current?.internalPlayer?.setVolume(volume * 100);
+
+    try {
+      localStorage.setItem("playerVolume", String(volume));
+    } catch {
+    }
+  }, [isReady, volume]);
 
   return (
     <>
       <YouTube opts={opts} videoId={id} onReady={setReady} ref={playerRef} />
+
       {isReady ? (
         <>
           <Styled.ProgressBackground>
-            {currentTime !== 0 && <Styled.Progress value={currentTime} />}
+            <Styled.Progress value={progressValue} />
+
             {playTimes.map((playTime) => (
               <Styled.Separator
-                style={{ left: `${(playTime / 16000) * 100}%` }}
                 key={playTime}
+                style={{ left: `${(playTime / totalPlayTime) * 100}%` }}
               />
             ))}
           </Styled.ProgressBackground>
+
           <Styled.TimeStamps>
             <Styled.TimeStamp>1s</Styled.TimeStamp>
             <Styled.TimeStamp>16s</Styled.TimeStamp>
           </Styled.TimeStamps>
-          {!play && (
+
+          {!play ? (
             <IoPlay
               style={{ cursor: "pointer" }}
               size={36}
               color="var(--cl-green-6)"
               onClick={startPlayback}
             />
-          )}
-          {play && (
+          ) : (
             <IoPause
               style={{ cursor: "pointer" }}
               size={36}
@@ -91,6 +170,22 @@ export function Player({ id, currentTry }: Props) {
               onClick={startPlayback}
             />
           )}
+
+          <Styled.VolumeControl>
+            <Styled.VolumeLabel htmlFor="youtube-player-volume">
+              Volume {Math.round(volume * 100)}%
+            </Styled.VolumeLabel>
+            <Styled.VolumeSlider
+              id="youtube-player-volume"
+              type="range"
+              min="0"
+              max="1"
+              step="0.01"
+              value={volume}
+              onChange={updateVolume}
+              aria-label="Volume"
+            />
+          </Styled.VolumeControl>
         </>
       ) : (
         <p>Loading player...</p>
-- 
cgit v1.2.3

