128 lines
4.2 KiB
TypeScript
128 lines
4.2 KiB
TypeScript
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<HTMLDivElement | null>, 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])
|
|
}
|