diff --git a/frontend/src/components/video-player.tsx b/frontend/src/components/video-player.tsx index 39adee9..b9ce57a 100644 --- a/frontend/src/components/video-player.tsx +++ b/frontend/src/components/video-player.tsx @@ -10,13 +10,10 @@ import { Minimize, Pause, Play, - RotateCcw, - Settings, - SkipBack, + Rewind, Volume1, Volume2, VolumeX, - Zap, } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import type Hls from "hls.js"; @@ -35,8 +32,6 @@ const CONTINUOUS_PLAY_MAX_DT = 3; const ALLOWED_SPEEDS = [0.5, 0.75, 1, 1.25, 1.5, 2] as const; type Speed = (typeof ALLOWED_SPEEDS)[number]; const MAX_RETRY_DELAY_MS = 30_000; -const AMBIENT_FPS = 8; -const AMBIENT_INTERVAL = 1000 / AMBIENT_FPS; const DOUBLE_TAP_MS = 300; const DOUBLE_TAP_SLOP = 60; @@ -54,7 +49,6 @@ function writeStorage(key: string, value: unknown) { } const isNumber = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v); -const isBoolean = (v: unknown): v is boolean => typeof v === "boolean"; // ─── Main Component ─────────────────────────────────────────────────────────── export function VideoPlayer({ @@ -73,9 +67,6 @@ export function VideoPlayer({ // ── Refs ──────────────────────────────────────────────────────────────────── const videoRef = useRef(null); const containerRef = useRef(null); - const ambientCanvasRef = useRef(null); - const ambientRafRef = useRef(0); - const ambientLastTickRef = useRef(0); const hlsRef = useRef(null); const lockedSrcRef = useRef(videoUrl); @@ -98,7 +89,6 @@ export function VideoPlayer({ // ── Persisted prefs ───────────────────────────────────────────────────────── const [volume, setVolume] = useState(() => readStorage("pargar:volume", 1, isNumber)); - const [ambientMode, setAmbientMode] = useState(() => readStorage("pargar:ambient", true, isBoolean)); const [speed, setSpeed] = useState(() => { const s = readStorage("pargar:speed", 1, isNumber); return (ALLOWED_SPEEDS as readonly number[]).includes(s) ? s as Speed : 1; @@ -121,12 +111,9 @@ export function VideoPlayer({ const [mediaDuration, setMediaDuration] = useState(duration); const [fullscreen, setFullscreen] = useState(false); const [theaterMode, setTheaterMode] = useState(false); - const [settingsOpen, setSettingsOpen] = useState(false); const [seekIndicator, setSeekIndicator] = useState<"back" | null>(null); const hideTimer = useRef(null); - const settingsPanelRef = useRef(null); - const settingsBtnRef = useRef(null); // ── Utilities ─────────────────────────────────────────────────────────────── const warnSeekBlocked = useCallback(() => { @@ -209,12 +196,6 @@ export function VideoPlayer({ changeSpeed(next); }, [changeSpeed]); - const toggleAmbient = useCallback(() => { - setAmbientMode((prev) => { - writeStorage("pargar:ambient", !prev); - return !prev; - }); - }, []); // ── HLS init ──────────────────────────────────────────────────────────────── useEffect(() => { @@ -445,25 +426,6 @@ export function VideoPlayer({ return () => document.removeEventListener("fullscreenchange", onFsChange); }, []); - // ── Ambient mode rAF loop ─────────────────────────────────────────────────── - useEffect(() => { - if (!ambientMode) { cancelAnimationFrame(ambientRafRef.current); return; } - const tick = (now: DOMHighResTimeStamp) => { - ambientRafRef.current = requestAnimationFrame(tick); - if (now - ambientLastTickRef.current < AMBIENT_INTERVAL) return; - ambientLastTickRef.current = now; - const v = videoRef.current; - const canvas = ambientCanvasRef.current; - if (!v || !canvas || v.readyState < 2 || v.paused) return; - try { - const ctx = canvas.getContext("2d"); - if (ctx) ctx.drawImage(v, 0, 0, canvas.width, canvas.height); - } catch { setAmbientMode(false); } // CORS taint - }; - ambientRafRef.current = requestAnimationFrame(tick); - return () => cancelAnimationFrame(ambientRafRef.current); - }, [ambientMode, playing]); - // ── Keyboard shortcuts ────────────────────────────────────────────────────── useEffect(() => { const onKey = (e: KeyboardEvent) => { @@ -492,19 +454,6 @@ export function VideoPlayer({ return () => window.removeEventListener("keydown", onKey); }, [toggle, skipBack, warnSeekBlocked, poke, toggleFullscreen]); - // ── Settings click-outside close ──────────────────────────────────────────── - useEffect(() => { - if (!settingsOpen) return; - const onMouseDown = (e: MouseEvent) => { - const target = e.target as Node; - if (settingsPanelRef.current?.contains(target)) return; - if (settingsBtnRef.current?.contains(target)) return; - setSettingsOpen(false); - }; - document.addEventListener("mousedown", onMouseDown); - return () => document.removeEventListener("mousedown", onMouseDown); - }, [settingsOpen]); - // ── Cleanup timers ────────────────────────────────────────────────────────── useEffect(() => { return () => { @@ -611,8 +560,8 @@ export function VideoPlayer({
{/* ── Player container ─── */} @@ -627,24 +576,6 @@ export function VideoPlayer({ onMouseMove={poke} onPointerDown={handlePointerDown} > - {/* Ambient canvas (behind video) */} -
- -
- {/* Video element */}
- {/* Seek indicator (double-tap feedback) */} + {/* Seek indicator (rewind feedback — physical right side) */} {seekIndicator && (
- +
۱۰ ثانیه
@@ -749,158 +680,90 @@ export function VideoPlayer({ {fmt(position)} / {fmt(mediaDuration)} - {/* Progress bar with hover tooltip */} + {/* Timeline — unified dir="ltr" coordinate space */}
setHoverTime(null)} > {/* Hover timestamp tooltip */} {hoverTime !== null && (
{fmt(hoverTime)}
)} - {/* Buffer track */} -
+ {/* Track layers stacked bottom-to-top: base → buffer → progress → thumb */} +
+ {/* Buffer layer */}
+ {/* Play progress layer */} +
- - {/* Playhead thumb */} + {/* Playhead thumb — positioned outside overflow:hidden, same dir="ltr" space */}
- {/* Bottom control row */} -
- {/* Play/Pause */} - + {/* Bottom control row — dir="ltr" for physical left→right placement */} +
+ {/* Left group: playback controls + volume + speed */} +
+ {/* Play/Pause */} + - {/* Skip back */} - + {/* Rewind 10s */} + - {/* Restart */} - - - {/* Volume */} -
- - { const v = parseFloat(e.target.value); setVolume(v); if (v > 0 && muted) setMuted(false); }} className="h-1 w-16 cursor-pointer accent-white opacity-80 hover:opacity-100" aria-label="میزان صدا" /> -
+ { const v = parseFloat(e.target.value); setVolume(v); if (v > 0 && muted) setMuted(false); }} className="hidden h-1 w-16 cursor-pointer accent-white opacity-80 hover:opacity-100 sm:block" aria-label="میزان صدا" /> - {/* Speed pill — click cycles through speeds */} - - - {/* Spacer */} - - - {/* Watched pct */} - {Math.round(watchedPct)}٪ - - {/* Settings gear — opens ambient / global settings only */} - - - {/* Theater mode */} - - - {/* Fullscreen */} - -
-
- - {/* Settings flyout — anchored above the controls bar, closes only on outside click */} - - {settingsOpen && ( - e.stopPropagation()} - > -

تنظیمات پخش

- - {/* Speed picker (full grid) */} -

سرعت

-
- {ALLOWED_SPEEDS.map((s) => ( - - ))} -
- -
- - {/* Ambient toggle */} + {/* Speed pill */} - - )} - +
+ + {/* Right group: watched%, theater, fullscreen */} +
+ {Math.round(watchedPct)}٪ + + {/* Theater mode */} + + + {/* Fullscreen */} + +
+
+
{/* Quiz unlock CTA */} @@ -933,7 +796,7 @@ export function VideoPlayer({
-

{Math.round(watchedPct)}٪

+

{Math.round(watchedPct)}٪

{quizUnlocked ? (