import { Ionicons, MaterialIcons } from "@expo/vector-icons"; import type { BaseItemDto, MediaSourceInfo, } from "@jellyfin/sdk/lib/generated-client"; import { useRouter } from "expo-router"; import { type FC, useCallback, useState } from "react"; import { Platform, TouchableOpacity, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { PlaybackSpeedSelector } from "@/components/PlaybackSpeedSelector"; import { useHaptic } from "@/hooks/useHaptic"; import { useOrientation } from "@/hooks/useOrientation"; import { OrientationLock } from "@/packages/expo-screen-orientation"; import { useSettings, VideoPlayerIOS } from "@/utils/atoms/settings"; import { ICON_SIZES } from "./constants"; import DropdownView from "./dropdown/DropdownView"; import { PlaybackSpeedScope } from "./utils/playback-speed-settings"; import { type AspectRatio, AspectRatioSelector, } from "./VideoScalingModeSelector"; import { type ScaleFactor, VlcZoomControl } from "./VlcZoomControl"; import { ZoomToggle } from "./ZoomToggle"; interface HeaderControlsProps { item: BaseItemDto; showControls: boolean; offline: boolean; mediaSource?: MediaSourceInfo | null; startPictureInPicture?: () => Promise; switchOnEpisodeMode: () => void; goToPreviousItem: () => void; goToNextItem: (options: { isAutoPlay?: boolean }) => void; previousItem?: BaseItemDto | null; nextItem?: BaseItemDto | null; useVlcPlayer?: boolean; // VLC-specific props aspectRatio?: AspectRatio; setVideoAspectRatio?: (aspectRatio: string | null) => Promise; scaleFactor?: ScaleFactor; setVideoScaleFactor?: (scaleFactor: number) => Promise; // KSPlayer-specific props isZoomedToFill?: boolean; onZoomToggle?: () => void; // Playback speed props playbackSpeed?: number; setPlaybackSpeed?: (speed: number, scope: PlaybackSpeedScope) => void; } export const HeaderControls: FC = ({ item, showControls, offline, mediaSource, startPictureInPicture, switchOnEpisodeMode, goToPreviousItem, goToNextItem, previousItem, nextItem, useVlcPlayer = false, aspectRatio = "default", setVideoAspectRatio, scaleFactor = 0, setVideoScaleFactor, isZoomedToFill = false, onZoomToggle, playbackSpeed = 1.0, setPlaybackSpeed, }) => { const { settings } = useSettings(); const router = useRouter(); const insets = useSafeAreaInsets(); const lightHapticFeedback = useHaptic("light"); const { orientation, lockOrientation } = useOrientation(); const [isTogglingOrientation, setIsTogglingOrientation] = useState(false); const onClose = async () => { lightHapticFeedback(); router.back(); }; const toggleOrientation = useCallback(async () => { if (isTogglingOrientation) return; setIsTogglingOrientation(true); lightHapticFeedback(); try { const isPortrait = orientation === OrientationLock.PORTRAIT_UP || orientation === OrientationLock.PORTRAIT_DOWN; await lockOrientation( isPortrait ? OrientationLock.LANDSCAPE : OrientationLock.PORTRAIT_UP, ); } finally { setIsTogglingOrientation(false); } }, [ orientation, lockOrientation, isTogglingOrientation, lightHapticFeedback, ]); return ( {!Platform.isTV && (!offline || !mediaSource?.TranscodingUrl) && ( )} {!Platform.isTV && ( )} {!Platform.isTV && startPictureInPicture && settings?.videoPlayerIOS !== VideoPlayerIOS.VLC && ( )} {item?.Type === "Episode" && ( )} {previousItem && ( )} {nextItem && ( goToNextItem({ isAutoPlay: false })} className='aspect-square flex flex-col rounded-xl items-center justify-center p-2' > )} {/* Playback Speed Control */} {!Platform.isTV && setPlaybackSpeed && ( )} {/* VLC-specific controls: Aspect Ratio and Scale/Zoom */} {useVlcPlayer && ( { if (setVideoAspectRatio) { const aspectRatioString = newRatio === "default" ? null : newRatio; await setVideoAspectRatio(aspectRatioString); } }} disabled={!setVideoAspectRatio} /> )} {useVlcPlayer && ( { if (setVideoScaleFactor) { await setVideoScaleFactor(newScale); } }} disabled={!setVideoScaleFactor} /> )} {/* KSPlayer-specific control: Zoom to Fill */} {!useVlcPlayer && ( {})} disabled={!onZoomToggle} /> )} ); };