Spaces:
Running
Running
| import React from "react"; | |
| import { useTime } from "../context/time-context"; | |
| import { | |
| FaPlay, | |
| FaPause, | |
| FaBackward, | |
| FaForward, | |
| FaUndoAlt, | |
| FaArrowDown, | |
| FaArrowUp, | |
| } from "react-icons/fa"; | |
| const PlaybackBar: React.FC = () => { | |
| const { duration, isPlaying, setIsPlaying, currentTime, setCurrentTime } = | |
| useTime(); | |
| const sliderActiveRef = React.useRef(false); | |
| const wasPlayingRef = React.useRef(false); | |
| const [sliderValue, setSliderValue] = React.useState(currentTime); | |
| // Only update sliderValue from context if not dragging | |
| React.useEffect(() => { | |
| if (!sliderActiveRef.current) { | |
| setSliderValue(currentTime); | |
| } | |
| }, [currentTime]); | |
| const handleSliderChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const t = Number(e.target.value); | |
| setSliderValue(t); | |
| // Seek videos immediately while dragging (no debounce) | |
| setCurrentTime(t); | |
| }; | |
| const handleSliderMouseDown = () => { | |
| sliderActiveRef.current = true; | |
| wasPlayingRef.current = isPlaying; | |
| setIsPlaying(false); | |
| }; | |
| const handleSliderMouseUp = () => { | |
| sliderActiveRef.current = false; | |
| // Final seek to exact slider position | |
| setCurrentTime(sliderValue); | |
| if (wasPlayingRef.current) { | |
| setIsPlaying(true); | |
| } | |
| }; | |
| return ( | |
| <div className="flex items-center gap-4 w-full max-w-4xl mx-auto sticky bottom-0 bg-slate-900/95 px-4 py-3 rounded-3xl mt-auto"> | |
| <button | |
| title="Jump backward 5 seconds" | |
| onClick={() => setCurrentTime(Math.max(0, currentTime - 5))} | |
| className="text-2xl hidden md:block" | |
| > | |
| <FaBackward size={24} /> | |
| </button> | |
| <button | |
| className={`text-3xl transition-transform ${isPlaying ? "scale-90 opacity-60" : "scale-110"}`} | |
| title="Play. Toggle with Space" | |
| onClick={() => setIsPlaying(true)} | |
| style={{ display: isPlaying ? "none" : "inline-block" }} | |
| > | |
| <FaPlay size={24} /> | |
| </button> | |
| <button | |
| className={`text-3xl transition-transform ${!isPlaying ? "scale-90 opacity-60" : "scale-110"}`} | |
| title="Pause. Toggle with Space" | |
| onClick={() => setIsPlaying(false)} | |
| style={{ display: !isPlaying ? "none" : "inline-block" }} | |
| > | |
| <FaPause size={24} /> | |
| </button> | |
| <button | |
| title="Jump forward 5 seconds" | |
| onClick={() => setCurrentTime(Math.min(duration, currentTime + 5))} | |
| className="text-2xl hidden md:block" | |
| > | |
| <FaForward size={24} /> | |
| </button> | |
| <button | |
| title="Rewind from start" | |
| onClick={() => setCurrentTime(0)} | |
| className="text-2xl hidden md:block" | |
| > | |
| <FaUndoAlt size={24} /> | |
| </button> | |
| <input | |
| type="range" | |
| min={0} | |
| max={duration} | |
| step={0.01} | |
| value={sliderValue} | |
| onChange={handleSliderChange} | |
| onMouseDown={handleSliderMouseDown} | |
| onMouseUp={handleSliderMouseUp} | |
| onTouchStart={handleSliderMouseDown} | |
| onTouchEnd={handleSliderMouseUp} | |
| className="flex-1 mx-2 accent-orange-500 focus:outline-none focus:ring-0" | |
| aria-label="Seek video" | |
| /> | |
| <span className="w-16 text-right tabular-nums text-xs text-slate-200 shrink-0"> | |
| {Math.floor(sliderValue)} / {Math.floor(duration)} | |
| </span> | |
| <div className="text-xs text-slate-300 select-none ml-8 flex-col gap-y-0.5 hidden md:flex"> | |
| <p> | |
| <span className="inline-flex items-center gap-1 font-mono align-middle"> | |
| <span className="px-2 py-0.5 rounded border border-slate-400 bg-slate-800 text-slate-200 text-xs shadow-inner"> | |
| Space | |
| </span> | |
| </span>{" "} | |
| to pause/unpause | |
| </p> | |
| <p> | |
| <span className="inline-flex items-center gap-1 font-mono align-middle"> | |
| <FaArrowUp size={14} />/<FaArrowDown size={14} /> | |
| </span>{" "} | |
| to previous/next episode | |
| </p> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default PlaybackBar; | |