diff --git a/README.md b/README.md
index 8a5633a..2d2c895 100644
--- a/README.md
+++ b/README.md
@@ -175,6 +175,17 @@ function App() {
/>
```
+### With Volume and Playback Control
+
+```tsx
+
+```
+
### 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
diff --git a/package.json b/package.json
index 2139527..8b57f9d 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/components/VideoElement.tsx b/src/components/VideoElement.tsx
index 5f5e3fb..f8ba0b1 100644
--- a/src/components/VideoElement.tsx
+++ b/src/components/VideoElement.tsx
@@ -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 = ({
autoplay = false,
loop = false,
muted = false,
+ volume,
+ playbackRate,
+ currentTime: initialCurrentTime,
subtitles = [],
onPlay,
onPause,
@@ -47,6 +60,13 @@ export const VideoElement: React.FC = ({
onLoadedMetadata,
onSeeking,
onSeeked,
+ onProgress,
+ onDurationChange,
+ onRateChange,
+ onFullscreenChange,
+ onPictureInPictureChange,
+ onWaiting,
+ onCanPlay,
onAudioTracksLoaded,
onQualityLevelsLoaded,
onSubtitleTracksLoaded,
@@ -129,7 +149,9 @@ export const VideoElement: React.FC = ({
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 = ({
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 = ({
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 = ({
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 = ({
onSeeked={handleSeeked}
onWaiting={handleWaiting}
onCanPlay={handleCanPlay}
+ onProgress={handleProgress}
+ onRateChange={handleRateChange}
onEnded={handleEnded}
onError={handleError}
onClick={handleVideoClick}
diff --git a/src/components/VideoPlayer.tsx b/src/components/VideoPlayer.tsx
index 44cfa23..e8e5128 100644
--- a/src/components/VideoPlayer.tsx
+++ b/src/components/VideoPlayer.tsx
@@ -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 = ({
autoplay = false,
loop = false,
muted = false,
+ volume,
+ playbackRate,
+ currentTime,
controls = true,
subtitles = [],
theme,
@@ -126,6 +149,13 @@ export const VideoPlayer: React.FC = ({
onLoadedMetadata,
onSeeking,
onSeeked,
+ onProgress,
+ onDurationChange,
+ onRateChange,
+ onFullscreenChange,
+ onPictureInPictureChange,
+ onWaiting,
+ onCanPlay,
}) => {
const [audioTracks, setAudioTracks] = useState([])
const [qualities, setQualities] = useState([])
@@ -162,6 +192,9 @@ export const VideoPlayer: React.FC = ({
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 = ({
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}
diff --git a/src/index.ts b/src/index.ts
index 60c154c..9dd327f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -8,6 +8,7 @@ export { PlayerProvider, usePlayerContext } from './contexts/PlayerContext'
export type {
VideoPlayerProps,
SubtitleTrack,
+ AudioTrack,
VideoQuality,
PlayerTheme,
VideoState,
diff --git a/src/types/index.ts b/src/types/index.ts
index d960339..faa6e14 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -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 {