'use client'; import React from 'react'; import { useDesktopPlayerState } from './hooks/useDesktopPlayerState'; import { useDesktopPlayerLogic } from './hooks/useDesktopPlayerLogic'; import { useHlsPlayer } from './hooks/useHlsPlayer'; import { useAutoSkip } from './hooks/useAutoSkip'; import { useStallDetection } from './hooks/useStallDetection'; import { DesktopControlsWrapper } from './desktop/DesktopControlsWrapper'; import { DesktopOverlayWrapper } from './desktop/DesktopOverlayWrapper'; import { usePlayerSettings } from './hooks/usePlayerSettings'; import { useIsIOS, useIsMobile } from '@/lib/hooks/mobile/useDeviceDetection'; import { useDoubleTap } from '@/lib/hooks/mobile/useDoubleTap'; import './web-fullscreen.css'; interface DesktopVideoPlayerProps { src: string; poster?: string; onError?: (error: string) => void; onTimeUpdate?: (currentTime: number, duration: number) => void; initialTime?: number; shouldAutoPlay?: boolean; // Episode navigation props for auto-skip/auto-next totalEpisodes?: number; currentEpisodeIndex?: number; onNextEpisode?: () => void; isReversed?: boolean; } export function DesktopVideoPlayer({ src, poster, onError, onTimeUpdate, initialTime = 0, shouldAutoPlay = false, totalEpisodes = 1, currentEpisodeIndex = 0, onNextEpisode, isReversed = false, }: DesktopVideoPlayerProps) { const { refs, data, actions } = useDesktopPlayerState(); const { fullscreenType: settingsFullscreenType } = usePlayerSettings(); const isIOS = useIsIOS(); const isMobile = useIsMobile(); // State to track if device is in landscape mode const [isLandscape, setIsLandscape] = React.useState(true); React.useEffect(() => { const checkOrientation = () => { // Check if width > height if (typeof window !== 'undefined') { setIsLandscape(window.innerWidth > window.innerHeight); } }; checkOrientation(); window.addEventListener('resize', checkOrientation); window.addEventListener('orientationchange', checkOrientation); return () => { window.removeEventListener('resize', checkOrientation); window.removeEventListener('orientationchange', checkOrientation); }; }, []); // Use user preference for fullscreen type, resolving 'auto' to device default // Auto Rules: // - Mobile: Window Fullscreen (Better for Danmaku/Controls) // - Desktop: Native Fullscreen (Better for PiP/Performance) const fullscreenType = settingsFullscreenType === 'auto' ? (isIOS ? 'window' : isMobile ? 'window' : 'native') // Treat all mobile as window for consistency if auto : settingsFullscreenType; // Check if we need to force landscape (iOS + Fullscreen + Portrait) const shouldForceLandscape = data.isFullscreen && fullscreenType === 'window' && isIOS && !isLandscape; // Initialize HLS Player useHlsPlayer({ videoRef: refs.videoRef, src, autoPlay: shouldAutoPlay }); const { videoRef, containerRef, } = refs; const { isPlaying, currentTime, duration, } = data; const { setShowControls, setIsLoading, setCurrentTime, setDuration, } = actions; // Reset loading state and show spinner when source changes React.useEffect(() => { setIsLoading(true); }, [src, setIsLoading]); const logic = useDesktopPlayerLogic({ src, initialTime, shouldAutoPlay, onError, onTimeUpdate, refs, data, actions, fullscreenType, isForceLandscape: shouldForceLandscape }); // Auto-skip intro/outro and auto-next episode const { isOutroActive, isTransitioningToNextEpisode } = useAutoSkip({ videoRef, currentTime, duration, isPlaying, totalEpisodes, currentEpisodeIndex, onNextEpisode, isReversed, src, }); // Sensitive stalling detection (e.g. video stuck but HTML5 state says playing) useStallDetection({ videoRef, isPlaying: data.isPlaying, isDraggingProgressRef: refs.isDraggingProgressRef, setIsLoading: actions.setIsLoading, isTransitioningToNextEpisode }); const { handleMouseMove, handleTouchToggleControls, togglePlay, handlePlay, handlePause, handleTimeUpdateEvent, handleLoadedMetadata, handleVideoError, } = logic; // Mobile double-tap gesture for skip forward/backward const { handleTap } = useDoubleTap({ onSingleTap: handleTouchToggleControls, onDoubleTapLeft: () => { logic.skipBackward(); handleMouseMove(); // Reset 3s auto-hide timer }, onDoubleTapRight: () => { logic.skipForward(); handleMouseMove(); // Reset 3s auto-hide timer }, onSkipContinueLeft: () => { logic.skipBackward(); handleMouseMove(); }, onSkipContinueRight: () => { logic.skipForward(); handleMouseMove(); }, isSkipModeActive: data.showSkipForwardIndicator || data.showSkipBackwardIndicator, }); return (