import { Info, Maximize, Minimize, Pause, PictureInPicture2, Play, RotateCcw, RotateCw, SkipBack, SkipForward, } from "lucide-react"; import { CircleButton } from "./CircleButton"; import { SeekBar, formatTime } from "./SeekBar"; import { VolumeControl } from "./VolumeControl"; import { QualityMenu } from "./QualityMenu"; import { SubtitleMenu } from "./SubtitleMenu"; import { AudioTrackMenu } from "./AudioTrackMenu"; import { ChaptersMenu } from "./ChaptersMenu"; import type { PlayerAudioTrack, PlayerChapter, PlayerSubtitleInfo, QualityOption } from "../types"; import type { VersionInfo } from "./QualityMenu"; import type { PlayerConfig } from "../context/PlayerConfigContext"; interface PlayerControlsProps { // Visibility visible: boolean; // Video state playing: boolean; currentTime: number; duration: number; buffered: TimeRanges | null; // Seek bar markers chapters?: PlayerChapter[]; introRegion?: { start: number; end: number } | null; creditsRegion?: { start: number; end: number } | null; volume: number; muted: boolean; isFullscreen: boolean; // Subtitles subtitleTracks: PlayerSubtitleInfo[]; activeSubtitleIndex: number | null; onSubtitleSelect: (index: number | null) => void; subtitleDelayMs: number; onSubtitleDelayChange: (ms: number) => void; mediaFileId?: number; playerConfig?: PlayerConfig; onRefreshSubtitles?: () => void; // Audio audioTracks: PlayerAudioTrack[]; activeAudioIndex: number; onAudioSelect?: (index: number, currentPosition: number) => void; // Quality qualityOptions: QualityOption[]; activeQualityId: string; isTranscoding: boolean; qualityError: string | null; onQualitySelect: (id: string) => void; // Version switching versions?: VersionInfo[]; onSwitchVersion?: (fileId: number) => void; // PiP onTogglePiP?: () => void; // Playback info showPlaybackInfo: boolean; onTogglePlaybackInfo: () => void; // Episode navigation hasPrevEpisode?: boolean; hasNextEpisode?: boolean; onPrevEpisode?: () => void; onNextEpisode?: () => void; // Title strip title?: string; subtitleLabel?: string; // Callbacks onPlayPause: () => void; onSeek: (seconds: number) => void; onVolumeChange: (volume: number) => void; onMutedChange: (muted: boolean) => void; onFullscreenToggle: () => void; } /** Skip amount for the ±seconds buttons, matching keyboard shortcuts. */ const SKIP_BACK_SECONDS = 10; const SKIP_FORWARD_SECONDS = 30; export function PlayerControls({ visible, playing, currentTime, duration, buffered, chapters, introRegion, creditsRegion, volume, muted, isFullscreen, subtitleTracks, activeSubtitleIndex, onSubtitleSelect, subtitleDelayMs, onSubtitleDelayChange, mediaFileId, playerConfig, onRefreshSubtitles, audioTracks, activeAudioIndex, onAudioSelect, qualityOptions, activeQualityId, isTranscoding, qualityError, onQualitySelect, versions, onSwitchVersion, onTogglePiP, showPlaybackInfo, onTogglePlaybackInfo, hasPrevEpisode = false, hasNextEpisode = false, onPrevEpisode, onNextEpisode, title, subtitleLabel, onPlayPause, onSeek, onVolumeChange, onMutedChange, onFullscreenToggle, }: PlayerControlsProps) { const safeDuration = duration > 0 ? duration : 0; const handleSkipBack = () => onSeek(Math.max(0, currentTime - SKIP_BACK_SECONDS)); const handleSkipForward = () => onSeek(Math.min(safeDuration || currentTime, currentTime + SKIP_FORWARD_SECONDS)); // When playing any episode in a series (even the first or last), reserve // both prev/next slots so the cluster remains symmetric around the play // button. Movies (no episode nav at all) skip the slots entirely. const showEpisodeSlots = hasPrevEpisode || hasNextEpisode; return (