Add volume, playbackRate, and currentTime props

Introduces new props (volume, playbackRate, currentTime) to VideoPlayer and VideoElement components, allowing external control of volume, playback speed, and initial playback position. Also adds related event handlers (onRateChange, onFullscreenChange, onPictureInPictureChange, onProgress, onDurationChange, onWaiting, onCanPlay) and updates documentation and types accordingly.
This commit is contained in:
hibna
2025-11-04 07:38:06 +03:00
parent 164450dd13
commit 80bd589c03
6 changed files with 176 additions and 13 deletions
+41 -7
View File
@@ -175,6 +175,17 @@ function App() {
/>
```
### With Volume and Playback Control
```tsx
<VideoPlayer
src="https://example.com/video.mp4"
volume={0.5} // 50% volume
playbackRate={1.5} // 1.5x speed
currentTime={30} // Start at 30 seconds
/>
```
### With Event Handlers
```tsx
@@ -184,6 +195,9 @@ function App() {
onPause={() => console.log('Video paused')}
onEnded={() => console.log('Video ended')}
onTimeUpdate={(time) => console.log('Current time:', time)}
onVolumeChange={(volume) => console.log('Volume:', volume)}
onRateChange={(rate) => console.log('Playback rate:', rate)}
onFullscreenChange={(isFs) => console.log('Fullscreen:', isFs)}
onError={(error) => console.error('Video error:', error)}
/>
```
@@ -272,6 +286,8 @@ video-player/
### VideoPlayer Props
#### Basic Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `src` | `string` | **required** | Video source URL (MP4, WebM, HLS, IPTV .ts) |
@@ -279,20 +295,38 @@ video-player/
| `autoplay` | `boolean` | `false` | Auto-play video on load |
| `loop` | `boolean` | `false` | Loop video playback |
| `muted` | `boolean` | `false` | Start muted |
| `volume` | `number` | - | Initial volume (0-1) |
| `playbackRate` | `number` | - | Playback speed (0.25, 0.5, 1, 1.5, 2, etc.) |
| `currentTime` | `number` | - | Initial playback position in seconds |
| `controls` | `boolean` | `true` | Show player controls |
| `subtitles` | `SubtitleTrack[]` | `[]` | Subtitle tracks |
| `audioTracks` | `AudioTrack[]` | `[]` | Audio tracks |
| `theme` | `PlayerTheme` | - | Custom theme colors |
| `language` | `string` | `'en'` | UI language ('en' or 'tr') |
| `keyboardShortcuts` | `boolean` | `true` | Enable keyboard shortcuts |
| `pictureInPicture` | `boolean` | `true` | Enable PIP button |
| `className` | `string` | - | Custom CSS class |
| `style` | `CSSProperties` | - | Inline styles |
| `onPlay` | `() => void` | - | Play event handler |
| `onPause` | `() => void` | - | Pause event handler |
| `onEnded` | `() => void` | - | Ended event handler |
| `onTimeUpdate` | `(time: number) => void` | - | Time update handler |
| `onVolumeChange` | `(volume: number) => void` | - | Volume change handler |
| `onError` | `(error: Error) => void` | - | Error handler |
#### Event Handlers
| Prop | Type | Description |
|------|------|-------------|
| `onPlay` | `() => void` | Fired when playback starts |
| `onPause` | `() => void` | Fired when playback pauses |
| `onEnded` | `() => void` | Fired when playback ends |
| `onTimeUpdate` | `(currentTime: number) => void` | Fired during playback with current time |
| `onVolumeChange` | `(volume: number) => void` | Fired when volume changes |
| `onError` | `(error: Error) => void` | Fired on playback error |
| `onLoadedMetadata` | `() => void` | Fired when video metadata is loaded |
| `onSeeking` | `() => void` | Fired when seeking starts |
| `onSeeked` | `() => void` | Fired when seeking completes |
| `onProgress` | `(buffered: number) => void` | Fired during download progress |
| `onDurationChange` | `(duration: number) => void` | Fired when duration changes |
| `onRateChange` | `(playbackRate: number) => void` | Fired when playback rate changes |
| `onFullscreenChange` | `(isFullscreen: boolean) => void` | Fired when fullscreen state changes |
| `onPictureInPictureChange` | `(isPip: boolean) => void` | Fired when PIP state changes |
| `onWaiting` | `() => void` | Fired when buffering starts |
| `onCanPlay` | `() => void` | Fired when enough data is available to play |
### SubtitleTrack
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@alper/video-player",
"version": "0.1.13",
"version": "0.1.14",
"description": "Modern, feature-rich video player library for React",
"type": "module",
"main": "./dist/video-player.umd.cjs",
+82 -5
View File
@@ -16,6 +16,9 @@ interface VideoElementProps {
autoplay?: boolean
loop?: boolean
muted?: boolean
volume?: number
playbackRate?: number
currentTime?: number
subtitles?: SubtitleTrack[]
onPlay?: () => void
onPause?: () => void
@@ -26,6 +29,13 @@ interface VideoElementProps {
onLoadedMetadata?: () => void
onSeeking?: () => void
onSeeked?: () => void
onProgress?: (buffered: number) => void
onDurationChange?: (duration: number) => void
onRateChange?: (playbackRate: number) => void
onFullscreenChange?: (isFullscreen: boolean) => void
onPictureInPictureChange?: (isPictureInPicture: boolean) => void
onWaiting?: () => void
onCanPlay?: () => void
onAudioTracksLoaded?: (tracks: AudioTrack[]) => void
onQualityLevelsLoaded?: (qualities: VideoQuality[]) => void
onSubtitleTracksLoaded?: (tracks: SubtitleTrack[]) => void
@@ -37,6 +47,9 @@ export const VideoElement: React.FC<VideoElementProps> = ({
autoplay = false,
loop = false,
muted = false,
volume,
playbackRate,
currentTime: initialCurrentTime,
subtitles = [],
onPlay,
onPause,
@@ -47,6 +60,13 @@ export const VideoElement: React.FC<VideoElementProps> = ({
onLoadedMetadata,
onSeeking,
onSeeked,
onProgress,
onDurationChange,
onRateChange,
onFullscreenChange,
onPictureInPictureChange,
onWaiting,
onCanPlay,
onAudioTracksLoaded,
onQualityLevelsLoaded,
onSubtitleTracksLoaded,
@@ -129,7 +149,9 @@ export const VideoElement: React.FC<VideoElementProps> = ({
duration: video.duration,
isLiveBroadcast,
}))
}, [videoRef, setVideoState])
onDurationChange?.(video.duration)
}, [videoRef, setVideoState, onDurationChange])
const handleVolumeChange = useCallback(() => {
const video = videoRef.current
@@ -156,11 +178,29 @@ export const VideoElement: React.FC<VideoElementProps> = ({
const handleWaiting = useCallback(() => {
setVideoState((prev) => ({ ...prev, loading: true }))
}, [setVideoState])
onWaiting?.()
}, [setVideoState, onWaiting])
const handleCanPlay = useCallback(() => {
setVideoState((prev) => ({ ...prev, loading: false }))
}, [setVideoState])
onCanPlay?.()
}, [setVideoState, onCanPlay])
const handleProgress = useCallback(() => {
const video = videoRef.current
if (!video) return
const buffered = video.buffered.length > 0 ? video.buffered.end(video.buffered.length - 1) : 0
onProgress?.(buffered)
}, [videoRef, onProgress])
const handleRateChange = useCallback(() => {
const video = videoRef.current
if (!video) return
setVideoState((prev) => ({ ...prev, playbackRate: video.playbackRate }))
onRateChange?.(video.playbackRate)
}, [videoRef, setVideoState, onRateChange])
const handleEnded = useCallback(() => {
setVideoState((prev) => ({ ...prev, playing: false }))
@@ -212,19 +252,21 @@ export const VideoElement: React.FC<VideoElementProps> = ({
const handleFullscreenChange = () => {
const isFullscreen = !!document.fullscreenElement
setVideoState((prev) => ({ ...prev, fullscreen: isFullscreen }))
onFullscreenChange?.(isFullscreen)
}
document.addEventListener('fullscreenchange', handleFullscreenChange)
return () => {
document.removeEventListener('fullscreenchange', handleFullscreenChange)
}
}, [setVideoState])
}, [setVideoState, onFullscreenChange])
// Handle PIP changes
useEffect(() => {
const handlePIPChange = () => {
const isPIP = !!document.pictureInPictureElement
setVideoState((prev) => ({ ...prev, pictureInPicture: isPIP }))
onPictureInPictureChange?.(isPIP)
}
document.addEventListener('enterpictureinpicture', handlePIPChange)
@@ -234,7 +276,40 @@ export const VideoElement: React.FC<VideoElementProps> = ({
document.removeEventListener('enterpictureinpicture', handlePIPChange)
document.removeEventListener('leavepictureinpicture', handlePIPChange)
}
}, [setVideoState])
}, [setVideoState, onPictureInPictureChange])
// Apply volume prop to video element
useEffect(() => {
const video = videoRef.current
if (!video || volume === undefined) return
// Clamp volume between 0 and 1
const clampedVolume = Math.max(0, Math.min(1, volume))
if (video.volume !== clampedVolume) {
video.volume = clampedVolume
}
}, [volume, videoRef])
// Apply playbackRate prop to video element
useEffect(() => {
const video = videoRef.current
if (!video || playbackRate === undefined) return
if (video.playbackRate !== playbackRate) {
video.playbackRate = playbackRate
}
}, [playbackRate, videoRef])
// Apply currentTime prop to video element (only once on mount or when it changes)
useEffect(() => {
const video = videoRef.current
if (!video || initialCurrentTime === undefined) return
// Only seek if the difference is significant (more than 1 second)
if (Math.abs(video.currentTime - initialCurrentTime) > 1) {
video.currentTime = initialCurrentTime
}
}, [initialCurrentTime, videoRef])
// Process subtitles - convert SRT to VTT blob URLs and merge with HLS subtitles
useEffect(() => {
@@ -747,6 +822,8 @@ export const VideoElement: React.FC<VideoElementProps> = ({
onSeeked={handleSeeked}
onWaiting={handleWaiting}
onCanPlay={handleCanPlay}
onProgress={handleProgress}
onRateChange={handleRateChange}
onEnded={handleEnded}
onError={handleError}
onClick={handleVideoClick}
+40
View File
@@ -40,6 +40,9 @@ const VideoPlayerContent: React.FC<
autoplay = false,
loop = false,
muted = false,
volume,
playbackRate,
currentTime,
controls = true,
subtitles = [],
keyboardShortcuts = true,
@@ -55,6 +58,13 @@ const VideoPlayerContent: React.FC<
onLoadedMetadata,
onSeeking,
onSeeked,
onProgress,
onDurationChange,
onRateChange,
onFullscreenChange,
onPictureInPictureChange,
onWaiting,
onCanPlay,
audioTracks,
onAudioTracksLoadedInternal,
qualities,
@@ -76,6 +86,9 @@ const VideoPlayerContent: React.FC<
autoplay={autoplay}
loop={loop}
muted={muted}
volume={volume}
playbackRate={playbackRate}
currentTime={currentTime}
subtitles={subtitles}
onPlay={onPlay}
onPause={onPause}
@@ -86,6 +99,13 @@ const VideoPlayerContent: React.FC<
onLoadedMetadata={onLoadedMetadata}
onSeeking={onSeeking}
onSeeked={onSeeked}
onProgress={onProgress}
onDurationChange={onDurationChange}
onRateChange={onRateChange}
onFullscreenChange={onFullscreenChange}
onPictureInPictureChange={onPictureInPictureChange}
onWaiting={onWaiting}
onCanPlay={onCanPlay}
onAudioTracksLoaded={onAudioTracksLoadedInternal}
onQualityLevelsLoaded={onQualityLevelsLoadedInternal}
onSubtitleTracksLoaded={onSubtitleTracksLoadedInternal}
@@ -109,6 +129,9 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
autoplay = false,
loop = false,
muted = false,
volume,
playbackRate,
currentTime,
controls = true,
subtitles = [],
theme,
@@ -126,6 +149,13 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
onLoadedMetadata,
onSeeking,
onSeeked,
onProgress,
onDurationChange,
onRateChange,
onFullscreenChange,
onPictureInPictureChange,
onWaiting,
onCanPlay,
}) => {
const [audioTracks, setAudioTracks] = useState<AudioTrack[]>([])
const [qualities, setQualities] = useState<VideoQuality[]>([])
@@ -162,6 +192,9 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
autoplay={autoplay}
loop={loop}
muted={muted}
volume={volume}
playbackRate={playbackRate}
currentTime={currentTime}
controls={controls}
subtitles={subtitles}
keyboardShortcuts={keyboardShortcuts}
@@ -177,6 +210,13 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
onLoadedMetadata={onLoadedMetadata}
onSeeking={onSeeking}
onSeeked={onSeeked}
onProgress={onProgress}
onDurationChange={onDurationChange}
onRateChange={onRateChange}
onFullscreenChange={onFullscreenChange}
onPictureInPictureChange={onPictureInPictureChange}
onWaiting={onWaiting}
onCanPlay={onCanPlay}
audioTracks={audioTracks}
onAudioTracksLoadedInternal={handleAudioTracksLoaded}
qualities={qualities}
+1
View File
@@ -8,6 +8,7 @@ export { PlayerProvider, usePlayerContext } from './contexts/PlayerContext'
export type {
VideoPlayerProps,
SubtitleTrack,
AudioTrack,
VideoQuality,
PlayerTheme,
VideoState,
+11
View File
@@ -40,6 +40,9 @@ export interface VideoPlayerProps {
autoplay?: boolean
loop?: boolean
muted?: boolean
volume?: number // 0-1 arası ses seviyesi
playbackRate?: number // Oynatma hızı (0.25, 0.5, 1, 1.5, 2, vb.)
currentTime?: number // Başlangıç zamanı (saniye)
controls?: boolean
subtitles?: SubtitleTrack[]
theme?: PlayerTheme
@@ -48,6 +51,7 @@ export interface VideoPlayerProps {
pictureInPicture?: boolean
className?: string
style?: CSSProperties
// Event callbacks
onPlay?: () => void
onPause?: () => void
onEnded?: () => void
@@ -57,6 +61,13 @@ export interface VideoPlayerProps {
onLoadedMetadata?: () => void
onSeeking?: () => void
onSeeked?: () => void
onProgress?: (buffered: number) => void
onDurationChange?: (duration: number) => void
onRateChange?: (playbackRate: number) => void
onFullscreenChange?: (isFullscreen: boolean) => void
onPictureInPictureChange?: (isPictureInPicture: boolean) => void
onWaiting?: () => void
onCanPlay?: () => void
}
export interface VideoState {