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
+28 -8
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState, useCallback, lazy, Suspense } from 'react'
import React, { useEffect, useRef, useState, useCallback, lazy, Suspense, type ReactNode } from 'react'
import { usePlayerContext } from '../contexts/PlayerContext'
import { PlayPauseButton } from './controls/PlayPauseButton'
import { ProgressBar } from './controls/ProgressBar'
@@ -11,25 +11,38 @@ import { LoadingSpinner } from './overlays/LoadingSpinner'
import { CenterPlayButton } from './controls/CenterPlayButton'
import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts'
import { useTouchGestures } from '../hooks/useTouchGestures'
import type { SubtitleTrack, AudioTrack, VideoQuality } from '../types'
import { features } from '../utils/polyfills'
import type { SubtitleTrack, AudioTrack, VideoQuality, KeyboardShortcutConfig, TouchConfig } from '../types'
import './ControlsLayer.css'
const SettingsMenu = lazy(() => import('./menus/SettingsMenu').then(module => ({ default: module.SettingsMenu })))
interface ControlsLayerProps {
keyboardShortcuts?: boolean
keyboardShortcutConfig?: KeyboardShortcutConfig
pictureInPicture?: boolean
subtitles?: SubtitleTrack[]
audioTracks?: AudioTrack[]
qualities?: VideoQuality[]
controlsAutoHideDelay?: number
playbackRates?: number[]
touchConfig?: TouchConfig
controlsLeftExtra?: ReactNode
controlsRightExtra?: ReactNode
}
export const ControlsLayer: React.FC<ControlsLayerProps> = ({
keyboardShortcuts = true,
keyboardShortcutConfig,
pictureInPicture = true,
subtitles = [],
audioTracks = [],
qualities = [],
controlsAutoHideDelay = 3000,
playbackRates,
touchConfig,
controlsLeftExtra,
controlsRightExtra,
}) => {
const { videoState, uiState, togglePlay, toggleFullscreen, showControls, hideControls, translations } =
usePlayerContext()
@@ -61,8 +74,8 @@ export const ControlsLayer: React.FC<ControlsLayerProps> = ({
clearHideTimeout()
hideTimeoutRef.current = window.setTimeout(() => {
hideControls()
}, 3000)
}, [autoHideEnabled, clearHideTimeout, hideControls])
}, controlsAutoHideDelay)
}, [autoHideEnabled, clearHideTimeout, hideControls, controlsAutoHideDelay])
// Keep controls visible when not playing or when any menu is open
useEffect(() => {
@@ -143,10 +156,10 @@ export const ControlsLayer: React.FC<ControlsLayerProps> = ({
}, [autoHideEnabled, showControls])
// Keyboard shortcuts
useKeyboardShortcuts(keyboardShortcuts)
useKeyboardShortcuts(keyboardShortcuts, keyboardShortcutConfig)
// Touch gestures
useTouchGestures(containerRef)
useTouchGestures(containerRef, touchConfig)
// Handle click for play/pause and double-click for fullscreen
const handleClick = useCallback(
@@ -221,7 +234,7 @@ export const ControlsLayer: React.FC<ControlsLayerProps> = ({
<div className="controls-row">
<div className="controls-left">
<PlayPauseButton />
<VolumeControl />
{features.hasVolumeControl() && <VolumeControl />}
{/* Time display - hidden for live broadcasts */}
{!videoState.isLiveBroadcast && <TimeDisplay />}
{/* Show "LIVE" badge for live broadcasts */}
@@ -231,13 +244,20 @@ export const ControlsLayer: React.FC<ControlsLayerProps> = ({
<span className="live-text">{translations.live}</span>
</div>
)}
{controlsLeftExtra}
</div>
<div className="controls-right">
{controlsRightExtra}
<div style={{ position: 'relative' }}>
<SettingsButton />
<Suspense fallback={null}>
<SettingsMenu subtitles={subtitles} audioTracks={audioTracks} qualities={qualities} />
<SettingsMenu
subtitles={subtitles}
audioTracks={audioTracks}
qualities={qualities}
playbackRates={playbackRates}
/>
</Suspense>
</div>
{pictureInPicture && <PIPButton />}