import { useCallback, useMemo } from "react"; import { isSafari } from "react-device-detect"; import { LuPause, LuPlay } from "react-icons/lu"; import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, } from "../ui/dropdown-menu"; import { MdForward10, MdReplay10, MdVolumeDown, MdVolumeMute, MdVolumeOff, MdVolumeUp, } from "react-icons/md"; import useKeyboardListener from "@/hooks/use-keyboard-listener"; import { VolumeSlider } from "../ui/slider"; type VideoControls = { volume?: boolean; seek?: boolean; playbackRate?: boolean; }; const CONTROLS_DEFAULT: VideoControls = { volume: true, seek: true, playbackRate: true, }; const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16]; type VideoControlsProps = { className?: string; video?: HTMLVideoElement | null; features?: VideoControls; isPlaying: boolean; show: boolean; muted?: boolean; volume?: number; controlsOpen?: boolean; playbackRates?: number[]; playbackRate: number; hotKeys?: boolean; setControlsOpen?: (open: boolean) => void; setMuted?: (muted: boolean) => void; onPlayPause: (play: boolean) => void; onSeek: (diff: number) => void; onSetPlaybackRate: (rate: number) => void; }; export default function VideoControls({ className, video, features = CONTROLS_DEFAULT, isPlaying, show, muted, volume, controlsOpen, playbackRates = PLAYBACK_RATE_DEFAULT, playbackRate, hotKeys = true, setControlsOpen, setMuted, onPlayPause, onSeek, onSetPlaybackRate, }: VideoControlsProps) { const onReplay = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); onSeek(-10); }, [onSeek], ); const onSkip = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); onSeek(10); }, [onSeek], ); const onTogglePlay = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); onPlayPause(!isPlaying); }, [isPlaying, onPlayPause], ); // volume control const VolumeIcon = useMemo(() => { if (!volume || volume == 0.0 || muted) { return MdVolumeOff; } else if (volume <= 0.33) { return MdVolumeMute; } else if (volume <= 0.67) { return MdVolumeDown; } else { return MdVolumeUp; } // only update when specific fields change // eslint-disable-next-line react-hooks/exhaustive-deps }, [volume, muted]); const onKeyboardShortcut = useCallback( (key: string, down: boolean, repeat: boolean) => { switch (key) { case "ArrowLeft": if (down) { onSeek(-10); } break; case "ArrowRight": if (down) { onSeek(10); } break; case "m": if (setMuted && down && !repeat && video) { setMuted(!muted); } break; case " ": if (down) { onPlayPause(!isPlaying); } break; } }, // only update when preview only changes // eslint-disable-next-line react-hooks/exhaustive-deps [video, isPlaying, onSeek], ); useKeyboardListener( hotKeys ? ["ArrowLeft", "ArrowRight", "m", " "] : [], onKeyboardShortcut, ); if (!show) { return; } return (
{video && features.volume && (
{ e.stopPropagation(); if (setMuted) { setMuted(!muted); } }} /> {muted == false && ( (video.volume = value[0])} /> )}
)} {features.seek && ( )}
{isPlaying ? ( ) : ( )}
{features.seek && ( )} {features.playbackRate && ( { if (setControlsOpen) { setControlsOpen(open); } }} > {`${playbackRate}x`} onSetPlaybackRate(parseFloat(rate))} > {playbackRates.map((rate) => ( {rate}x ))} )}
); }