feat: add configurable props for DX improvements

- Configurable keyboard shortcuts (seekSmall, seekLarge, volumeStep, disabled keys)
- Configurable touch gestures (maxSeekSeconds, maxVolumeChange, doubleTapSeekSeconds)
- Configurable auto-hide timeout via controlsAutoHideDelay prop
- Configurable playback rates via playbackRates prop
- Aspect ratio support (16:9, 4:3, 21:9, 1:1, 9:16, custom)
- Extended theme system (fontFamily, borderRadius, overlayOpacity, controlsBackground, etc.)
- Custom translations support via translations prop
- Children/slot system (children, controlsLeftExtra, controlsRightExtra)
- Ref forwarding with VideoPlayerHandle imperative API
- Analytics events (onFirstPlay, onBufferStart, onBufferEnd, onQualityChange)
- iOS Safari volume slider auto-hiding
- SSR guards for feature detection utilities
- prefers-reduced-motion CSS media query support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hibna
2026-02-12 19:23:54 +03:00
parent 73d5d65d2b
commit 58a405d895
12 changed files with 572 additions and 273 deletions
+12 -10
View File
@@ -1,5 +1,6 @@
import { useEffect, MutableRefObject } from 'react'
import { usePlayerContext } from '../contexts/PlayerContext'
import type { TouchConfig } from '../types'
interface TouchData {
startX: number
@@ -9,13 +10,17 @@ interface TouchData {
tapCount: number
}
export const useTouchGestures = (containerRef: MutableRefObject<HTMLDivElement | null>) => {
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,
@@ -71,11 +76,11 @@ export const useTouchGestures = (containerRef: MutableRefObject<HTMLDivElement |
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
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) * 0.5 // Max 0.5 volume change
const volumeChange = -(deltaY / container.clientHeight) * maxVolumeChange
setVolume(Math.max(0, Math.min(1, videoState.volume + volumeChange)))
}
}
@@ -86,14 +91,11 @@ export const useTouchGestures = (containerRef: MutableRefObject<HTMLDivElement |
const isLeftSide = relativeX < rect.width / 2
if (isLeftSide) {
// Double tap left - rewind 10 seconds
seek(Math.max(0, videoState.currentTime - 10))
seek(Math.max(0, videoState.currentTime - doubleTapSeekSeconds))
} else {
// Double tap right - forward 10 seconds
seek(Math.min(videoState.duration, videoState.currentTime + 10))
seek(Math.min(videoState.duration, videoState.currentTime + doubleTapSeekSeconds))
}
// Show feedback animation (optional - can be implemented later)
showDoubleTapFeedback(isLeftSide)
}
@@ -108,7 +110,7 @@ export const useTouchGestures = (containerRef: MutableRefObject<HTMLDivElement |
feedback.style.fontSize = '48px'
feedback.style.pointerEvents = 'none'
feedback.style.animation = 'fadeOut 0.5s ease-out forwards'
feedback.textContent = isLeft ? '« 10s' : '10s »'
feedback.textContent = isLeft ? `« ${doubleTapSeekSeconds}s` : `${doubleTapSeekSeconds}s »`
container?.appendChild(feedback)
setTimeout(() => feedback.remove(), 500)
@@ -121,5 +123,5 @@ export const useTouchGestures = (containerRef: MutableRefObject<HTMLDivElement |
container.removeEventListener('touchstart', handleTouchStart)
container.removeEventListener('touchend', handleTouchEnd)
}
}, [containerRef, videoState.currentTime, videoState.duration, videoState.volume, togglePlay, seek, setVolume])
}, [containerRef, videoState.currentTime, videoState.duration, videoState.volume, togglePlay, seek, setVolume, touchConfig])
}