mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
159 lines
3.6 KiB
TypeScript
159 lines
3.6 KiB
TypeScript
'use client';
|
|
|
|
import { useScreenOrientation } from '@/lib/hooks/useMobilePlayer';
|
|
import { useMobilePlayerState } from './hooks/useMobilePlayerState';
|
|
import { useMobilePlayerLogic } from './hooks/useMobilePlayerLogic';
|
|
import { useMobileGestures } from './hooks/useMobileGestures';
|
|
import { useAutoSkip } from './hooks/useAutoSkip';
|
|
import { useHlsPlayer } from './hooks/useHlsPlayer';
|
|
import { MobileControlsWrapper } from './mobile/MobileControlsWrapper';
|
|
import { MobileOverlay } from './mobile/MobileOverlay';
|
|
import { MobileSkipIndicator } from './mobile/MobileSkipIndicator';
|
|
|
|
interface MobileVideoPlayerProps {
|
|
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;
|
|
}
|
|
|
|
export function MobileVideoPlayer({
|
|
src,
|
|
poster,
|
|
onError,
|
|
onTimeUpdate,
|
|
initialTime = 0,
|
|
shouldAutoPlay = false,
|
|
totalEpisodes = 1,
|
|
currentEpisodeIndex = 0,
|
|
onNextEpisode,
|
|
}: MobileVideoPlayerProps) {
|
|
const { refs, state } = useMobilePlayerState();
|
|
|
|
const {
|
|
videoRef,
|
|
containerRef,
|
|
controlsTimeoutRef
|
|
} = refs;
|
|
|
|
// Initialize HLS Player (same as Desktop - fixes Android Chrome playback)
|
|
useHlsPlayer({
|
|
videoRef,
|
|
src,
|
|
autoPlay: shouldAutoPlay,
|
|
onError,
|
|
});
|
|
|
|
const {
|
|
isPlaying,
|
|
isFullscreen,
|
|
showControls,
|
|
isLoading,
|
|
showSkipIndicator,
|
|
skipAmount,
|
|
skipSide,
|
|
toastMessage,
|
|
showToast,
|
|
currentTime,
|
|
duration,
|
|
setShowControls,
|
|
setIsLoading
|
|
} = state;
|
|
|
|
const logic = useMobilePlayerLogic({
|
|
src,
|
|
initialTime,
|
|
shouldAutoPlay,
|
|
onError,
|
|
onTimeUpdate,
|
|
refs,
|
|
state
|
|
});
|
|
|
|
// Auto-skip intro/outro and auto-next episode
|
|
useAutoSkip({
|
|
videoRef,
|
|
currentTime,
|
|
duration,
|
|
isPlaying,
|
|
totalEpisodes,
|
|
currentEpisodeIndex,
|
|
onNextEpisode,
|
|
});
|
|
|
|
const {
|
|
skipVideo,
|
|
togglePlay,
|
|
handlePlay,
|
|
handlePause,
|
|
handleTimeUpdateEvent,
|
|
handleLoadedMetadata,
|
|
handleVideoError,
|
|
} = logic;
|
|
|
|
// Screen orientation management
|
|
useScreenOrientation(isFullscreen);
|
|
|
|
// Double tap handler
|
|
const { handleTap } = useMobileGestures({
|
|
skipVideo,
|
|
showSkipIndicator,
|
|
showControls,
|
|
setShowControls,
|
|
controlsTimeoutRef,
|
|
isPlaying,
|
|
togglePlay,
|
|
});
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
className="relative aspect-video bg-black rounded-[var(--radius-2xl)] overflow-hidden"
|
|
>
|
|
{/* Video Element - src is set by useHlsPlayer hook */}
|
|
<video
|
|
ref={videoRef}
|
|
className="w-full h-full object-contain touch-none"
|
|
poster={poster}
|
|
onPlay={handlePlay}
|
|
onPause={handlePause}
|
|
onTimeUpdate={handleTimeUpdateEvent}
|
|
onLoadedMetadata={handleLoadedMetadata}
|
|
onError={handleVideoError}
|
|
onWaiting={() => setIsLoading(true)}
|
|
onCanPlay={() => setIsLoading(false)}
|
|
onTouchEnd={handleTap}
|
|
onClick={(e) => e.preventDefault()}
|
|
playsInline
|
|
webkit-playsinline="true"
|
|
x-webkit-airplay="allow"
|
|
/>
|
|
|
|
<MobileOverlay
|
|
isLoading={isLoading}
|
|
showToast={showToast}
|
|
toastMessage={toastMessage}
|
|
/>
|
|
|
|
<MobileSkipIndicator
|
|
showSkipIndicator={showSkipIndicator}
|
|
skipSide={skipSide}
|
|
skipAmount={skipAmount}
|
|
/>
|
|
|
|
<MobileControlsWrapper
|
|
src={src}
|
|
state={state}
|
|
logic={logic}
|
|
refs={refs}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|