Initial commit: modern React video player library
Add all source files for a feature-rich, reusable video player built with React, TypeScript, and Vite. Includes core components, context, hooks, utilities, styles, demo app, and configuration files.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { useEffect, RefObject } from 'react'
|
||||
import { usePlayerContext } from '../contexts/PlayerContext'
|
||||
|
||||
interface TouchData {
|
||||
startX: number
|
||||
startY: number
|
||||
startTime: number
|
||||
lastTapTime: number
|
||||
tapCount: number
|
||||
}
|
||||
|
||||
export const useTouchGestures = (containerRef: RefObject<HTMLDivElement>) => {
|
||||
const { videoState, togglePlay, seek, setVolume } = usePlayerContext()
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current
|
||||
if (!container) return
|
||||
|
||||
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) * 30 // Max 30 seconds
|
||||
seek(Math.max(0, Math.min(videoState.duration, videoState.currentTime + seekAmount)))
|
||||
} else {
|
||||
// Vertical swipe - volume
|
||||
const volumeChange = -(deltaY / container.clientHeight) * 0.5 // Max 0.5 volume change
|
||||
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) {
|
||||
// Double tap left - rewind 10 seconds
|
||||
seek(Math.max(0, videoState.currentTime - 10))
|
||||
} else {
|
||||
// Double tap right - forward 10 seconds
|
||||
seek(Math.min(videoState.duration, videoState.currentTime + 10))
|
||||
}
|
||||
|
||||
// Show feedback animation (optional - can be implemented later)
|
||||
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 = 'fadeOut 0.5s ease-out forwards'
|
||||
feedback.textContent = isLeft ? '« 10s' : '10s »'
|
||||
|
||||
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])
|
||||
}
|
||||
Reference in New Issue
Block a user