import { useEffect, MutableRefObject } from 'react' import { usePlayerContext } from '../contexts/PlayerContext' import type { TouchConfig } from '../types' interface TouchData { startX: number startY: number startTime: number lastTapTime: number tapCount: number } export const useTouchGestures = (containerRef: MutableRefObject, touchConfig?: TouchConfig) => { const { videoState, togglePlay, seek, setVolume } = usePlayerContext() useEffect(() => { const container = containerRef.current if (!container) return const maxSeekSeconds = touchConfig?.maxSeekSeconds ?? 30 const maxVolumeChange = touchConfig?.maxVolumeChange ?? 0.5 const doubleTapSeekSeconds = touchConfig?.doubleTapSeekSeconds ?? 10 const touchData: TouchData = { startX: 0, startY: 0, startTime: 0, lastTapTime: 0, tapCount: 0, } const handleTouchStart = (e: TouchEvent) => { const touch = e.touches[0] touchData.startX = touch.clientX touchData.startY = touch.clientY touchData.startTime = Date.now() } const handleTouchEnd = (e: TouchEvent) => { const touch = e.changedTouches[0] const endX = touch.clientX const endY = touch.clientY const endTime = Date.now() const deltaX = endX - touchData.startX const deltaY = endY - touchData.startY const deltaTime = endTime - touchData.startTime // Tap/Double tap detection if (Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10 && deltaTime < 300) { const timeSinceLastTap = endTime - touchData.lastTapTime if (timeSinceLastTap < 400) { // Double tap touchData.tapCount++ if (touchData.tapCount === 2) { handleDoubleTap(endX, container.getBoundingClientRect()) touchData.tapCount = 0 } } else { // Single tap touchData.tapCount = 1 setTimeout(() => { if (touchData.tapCount === 1) { togglePlay() } touchData.tapCount = 0 }, 400) } touchData.lastTapTime = endTime } // Swipe detection if (Math.abs(deltaX) > 50 || Math.abs(deltaY) > 50) { if (Math.abs(deltaX) > Math.abs(deltaY)) { // Horizontal swipe - seek const seekAmount = (deltaX / container.clientWidth) * maxSeekSeconds seek(Math.max(0, Math.min(videoState.duration, videoState.currentTime + seekAmount))) } else { // Vertical swipe - volume const volumeChange = -(deltaY / container.clientHeight) * maxVolumeChange setVolume(Math.max(0, Math.min(1, videoState.volume + volumeChange))) } } } const handleDoubleTap = (x: number, rect: DOMRect) => { const relativeX = x - rect.left const isLeftSide = relativeX < rect.width / 2 if (isLeftSide) { seek(Math.max(0, videoState.currentTime - doubleTapSeekSeconds)) } else { seek(Math.min(videoState.duration, videoState.currentTime + doubleTapSeekSeconds)) } showDoubleTapFeedback(isLeftSide) } const showDoubleTapFeedback = (isLeft: boolean) => { const feedback = document.createElement('div') feedback.className = 'double-tap-feedback' feedback.style.position = 'absolute' feedback.style.top = '50%' feedback.style.left = isLeft ? '25%' : '75%' feedback.style.transform = 'translate(-50%, -50%)' feedback.style.color = 'white' feedback.style.fontSize = '48px' feedback.style.pointerEvents = 'none' feedback.style.animation = 'sp-fade-out 0.5s ease-out forwards' feedback.textContent = isLeft ? `« ${doubleTapSeekSeconds}s` : `${doubleTapSeekSeconds}s »` container?.appendChild(feedback) setTimeout(() => feedback.remove(), 500) } container.addEventListener('touchstart', handleTouchStart, { passive: true }) container.addEventListener('touchend', handleTouchEnd, { passive: true }) return () => { container.removeEventListener('touchstart', handleTouchStart) container.removeEventListener('touchend', handleTouchEnd) } }, [containerRef, videoState.currentTime, videoState.duration, videoState.volume, togglePlay, seek, setVolume, touchConfig]) }