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:
@@ -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
@@ -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",
|
||||
|
||||
@@ -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,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}
|
||||
|
||||
@@ -8,6 +8,7 @@ export { PlayerProvider, usePlayerContext } from './contexts/PlayerContext'
|
||||
export type {
|
||||
VideoPlayerProps,
|
||||
SubtitleTrack,
|
||||
AudioTrack,
|
||||
VideoQuality,
|
||||
PlayerTheme,
|
||||
VideoState,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user