@zezosoft/react-player
Version:
A lightweight and customizable video player by Zezosoft, built for seamless streaming with advanced controls, adaptive playback, and modern UI. Perfect for web and React applications.
682 lines (574 loc) • 21.8 kB
Markdown
# /react-player 🎬
A powerful and flexible **React video player** by **Zezosoft**, supporting HLS, MP4, DASH, YouTube, preview thumbnails, tracking, subtitles, episode playback, ads, and advanced controls.
---
## 🚀 Features
✅ **Multiple Video Formats** - HLS, MP4, DASH, YouTube
✅ **Preview Thumbnails on Hover** - Show video previews while scrubbing
✅ **Event Tracking** - Track views, watch time, and user interactions
✅ **Customizable Player Size & Controls** - Full control over player appearance
✅ **Time-Stamped Labels** - Video chapters with time markers
✅ **Subtitles (WebVTT)** - Multi-language subtitle support with custom styling
✅ **Intro Skipping** - Automatic skip intro button
✅ **Episode Playback** - Next episode auto-play and playlist support
✅ **Ad Support** - Pre-roll, mid-roll, post-roll, and overlay ads
✅ **Watch History** - Track user progress and resume playback
---
## 📦 Installation
Install the package using **npm** or **yarn**:
```sh
npm install /react-player
```
or
```sh
yarn add /react-player
```
---
## 🛠️ Quick Start
Here's a simple example to get you started:
```tsx
import { VideoPlayer } from "@zezosoft/react-player";
function App() {
return (
<VideoPlayer
video={{
src: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8",
title: "My Video",
poster: "https://example.com/poster.jpg",
}}
style={{
width: "100%",
height: "auto",
}}
/>
);
}
```
---
## 📖 Complete Example
Here's a comprehensive example showing all available features:
```tsx
import { useCallback, useRef } from "react";
import { VideoPlayer } from "@zezosoft/react-player";
function App() {
const previewImage = useRef("");
const updatePreviewImage = (hoverTime: number) => {
const url = `https://fakeimg.pl/720x405?text=${hoverTime}`;
const image = document.createElement("img");
image.src = url;
image.onload = () => {
previewImage.current = url;
};
};
const handleGettingPreview = useCallback((hoverTime: number) => {
updatePreviewImage(hoverTime);
return previewImage.current;
}, []);
return (
<div style={{ width: "720px", margin: "0 auto" }}>
<VideoPlayer
video={{
src: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8",
title: "Mehmaan",
poster: "https://i.ytimg.com/vi/VAUfyxw-Yvk/maxresdefault.jpg",
type: "hls",
isTrailer: false,
showControls: true,
isMute: false,
startFrom: 0,
}}
style={{
width: "720px",
height: "405px",
className: "my-video-player",
subtitleStyle: {
fontSize: "1.5rem",
backgroundColor: "rgba(0, 0, 0, 0.8)",
textColor: "#ffffff",
position: "bottom",
borderRadius: "8px",
padding: "8px 16px",
maxWidth: "80%",
},
}}
events={{
onEnded: (e) => console.log("Video ended", e),
onError: (e) => console.error("Video error", e),
onClose: () => console.log("Player closed"),
onWatchHistoryUpdate: (data) => {
console.log("Watch history:", data);
// Save to your backend
},
}}
features={{
timeCodes: [
{ fromMs: 0, description: "Introduction" },
{ fromMs: 130000, description: "Exciting Scene" },
{ fromMs: 270000, description: "Climax" },
],
getPreviewScreenUrl: handleGettingPreview,
tracking: {
onViewed: () => console.log("Video Viewed"),
onWatchTimeUpdated: (e) => {
console.log("Watch Time Updated", {
watchTime: e.watchTime,
currentTime: e.currentTime,
duration: e.duration,
});
},
},
subtitles: [
{
lang: "en",
label: "English",
url: "https://example.com/subtitles-en.vtt",
},
{
lang: "hi",
label: "Hindi",
url: "https://example.com/subtitles-hi.vtt",
},
],
episodeList: [
{ id: 1, title: "Episode 1", url: "https://example.com/ep1.m3u8" },
{ id: 2, title: "Episode 2", url: "https://example.com/ep2.m3u8" },
],
currentEpisodeIndex: 0,
intro: { start: 5, end: 20 },
nextEpisodeConfig: { showAtTime: 300, showAtEnd: true },
ads: {
preRoll: {
id: "preroll-1",
type: "pre-roll",
time: 0,
adUrl: "https://example.com/ad.mp4",
skipable: true,
skipAfter: 5,
},
midRoll: [
{
id: "midroll-1",
type: "mid-roll",
time: 120,
adUrl: "https://example.com/mid-ad.mp4",
skipable: false,
},
],
onAdStart: (adBreak) => console.log("Ad started", adBreak),
onAdEnd: (adBreak) => console.log("Ad ended", adBreak),
},
}}
/>
</div>
);
}
export default App;
```
---
## 📚 Props Reference
The `VideoPlayer` component accepts four main prop objects:
### `video` (Required)
Video source and basic configuration.
| Prop | Type | Default | Description |
| -------------- | -------------------------------------------------- | ------------ | ------------------------------------------------------- |
| `src` | `string` | **Required** | Video source URL (MP4, HLS, DASH, YouTube, etc.) |
| `title` | `string` | `""` | Title of the video displayed in the player header |
| `poster` | `string` | `""` | URL of the poster/thumbnail image shown before playback |
| `type` | `"hls" \| "dash" \| "mp4" \| "youtube" \| "other"` | `undefined` | Video format type (auto-detected if not provided) |
| `isTrailer` | `boolean` | `false` | If `true`, shows trailer-specific UI elements |
| `showControls` | `boolean` | `true` | Show/hide player controls |
| `isMute` | `boolean` | `false` | Start video muted |
| `startFrom` | `number` | `0` | Start playback from specific time in seconds |
### `style` (Optional)
Styling and appearance configuration.
| Prop | Type | Default | Description |
| --------------- | --------------------- | ----------- | ---------------------------------------------- |
| `className` | `string` | `undefined` | Custom CSS class name for the player container |
| `width` | `string` | `"100%"` | Player width (e.g., `"720px"`, `"100%"`) |
| `height` | `string` | `"auto"` | Player height (e.g., `"405px"`, `"auto"`) |
| `subtitleStyle` | `SubtitleStyleConfig` | `undefined` | Custom styling for subtitles (see below) |
**SubtitleStyleConfig:**
| Prop | Type | Default | Description |
| ----------------- | ------------------------------- | --------------------------------------------- | ---------------------------------- |
| `fontSize` | `string` | `"1.75rem"` | Subtitle font size |
| `backgroundColor` | `string` | `"linear-gradient(135deg, #fbbf24, #f59e0b)"` | Subtitle background color/gradient |
| `textColor` | `string` | `"#000000"` | Subtitle text color |
| `position` | `"top" \| "center" \| "bottom"` | `"bottom"` | Vertical position of subtitles |
| `borderRadius` | `string` | `"12px"` | Border radius of subtitle box |
| `padding` | `string` | `"12px 20px"` | Padding inside subtitle box |
| `maxWidth` | `string` | `"80%"` | Maximum width of subtitle box |
### `events` (Optional)
Event callbacks for player lifecycle.
| Prop | Type | Description |
| ---------------------- | ------------------------------------------------------------- | -------------------------------------------------- |
| `onEnded` | `(e: React.SyntheticEvent<HTMLVideoElement>) => void` | Called when video playback ends |
| `onError` | `(e?: React.SyntheticEvent<HTMLVideoElement, Event>) => void` | Called when video encounters an error |
| `onClose` | `() => void` | Called when player is closed |
| `onWatchHistoryUpdate` | `(data: WatchHistoryData) => void` | Called when player closes with watch progress data |
**WatchHistoryData:**
```typescript
{
currentTime: number; // Current playback time in seconds
duration: number; // Total video duration in seconds
progress: number; // Progress percentage (0-100)
isCompleted: boolean; // Whether video was fully watched
watchedAt: number; // Timestamp when watch session ended
}
```
### `features` (Optional)
Advanced features and functionality.
| Prop | Type | Default | Description |
| --------------------- | ------------------------------------------------ | ----------- | ------------------------------------------------------------------------ |
| `timeCodes` | `Array<{ fromMs: number, description: string }>` | `[]` | Time-based chapter markers (in milliseconds) |
| `getPreviewScreenUrl` | `(hoverTimeValue: number) => string` | `undefined` | Function to generate preview thumbnail URLs while hovering over seek bar |
| `tracking` | `TrackingConfig` | `undefined` | Event tracking configuration (see below) |
| `subtitles` | `Array<SubtitleTrack>` | `[]` | Subtitle tracks in WebVTT format |
| `episodeList` | `Array<Episode>` | `[]` | List of episodes for playlist/autoplay |
| `currentEpisodeIndex` | `number` | `0` | Index of currently playing episode |
| `intro` | `{ start: number, end: number }` | `undefined` | Intro skip configuration (times in seconds) |
| `nextEpisodeConfig` | `{ showAtTime?: number, showAtEnd?: boolean }` | `undefined` | Configuration for next episode button |
| `ads` | `AdConfig` | `undefined` | Advertisement configuration (see below) |
**TrackingConfig:**
```typescript
{
onViewed?: () => void;
onWatchTimeUpdated?: (e: {
watchTime?: number; // Total watch time in seconds
currentTime: number; // Current playback position
duration: number; // Total video duration
}) => void;
}
```
**SubtitleTrack:**
```typescript
{
lang: string; // Language code (e.g., "en", "hi", "fr")
label: string; // Display label (e.g., "English", "Hindi")
url: string; // URL to WebVTT subtitle file
}
```
**Episode:**
```typescript
{
id: number; // Unique episode identifier
title: string; // Episode title
url: string; // Episode video URL
}
```
**AdConfig:**
```typescript
{
preRoll?: AdBreak; // Pre-roll ad (plays before video)
midRoll?: AdBreak[]; // Mid-roll ads (plays during video)
postRoll?: AdBreak; // Post-roll ad (plays after video)
overlay?: { // Overlay ad (image overlay)
imageUrl: string;
clickUrl?: string;
showDuration: number;
position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
};
smartPlacement?: { // Smart ad placement
enabled: boolean;
minVideoDuration?: number;
minGapBetweenAds?: number;
avoidNearEnd?: number;
preferNaturalBreaks?: boolean;
};
onAdStart?: (adBreak: AdBreak) => void;
onAdEnd?: (adBreak: AdBreak) => void;
onAdSkip?: (adBreak: AdBreak) => void;
onAdError?: (adBreak: AdBreak, error: Error) => void;
}
```
**AdBreak:**
```typescript
{
id: string; // Unique ad identifier
type: "pre-roll" | "mid-roll" | "post-roll" | "overlay";
time: number; // Time in seconds when ad should play
adUrl: string; // URL to ad video
skipable?: boolean; // Whether ad can be skipped
skipAfter?: number; // Seconds before skip button appears
duration?: number; // Ad duration in seconds
sponsoredUrl?: string; // URL for sponsored content
title?: string; // Ad title
description?: string; // Ad description
relevance?: "high" | "medium" | "low";
}
```
---
## 🎯 Usage Examples
### Basic Video Player
```tsx
<VideoPlayer
video={{
src: "https://example.com/video.mp4",
title: "My Video",
}}
/>
```
### HLS Video with Poster
```tsx
<VideoPlayer
video={{
src: "https://example.com/playlist.m3u8",
type: "hls",
poster: "https://example.com/poster.jpg",
title: "Streaming Video",
}}
style={{
width: "100%",
height: "450px",
}}
/>
```
### Video with Preview Thumbnails
```tsx
const getPreview = (hoverTime: number) => {
// Generate thumbnail URL based on hover time
return `https://example.com/thumbnails/${Math.floor(hoverTime)}.jpg`;
};
<VideoPlayer
video={{ src: "https://example.com/video.mp4" }}
features={{
getPreviewScreenUrl: getPreview,
}}
/>;
```
### Video with Chapters
```tsx
<VideoPlayer
video={{ src: "https://example.com/video.mp4" }}
features={{
timeCodes: [
{ fromMs: 0, description: "Introduction" },
{ fromMs: 60000, description: "Chapter 1" },
{ fromMs: 120000, description: "Chapter 2" },
{ fromMs: 180000, description: "Conclusion" },
],
}}
/>
```
### Video with Subtitles
```tsx
<VideoPlayer
video={{ src: "https://example.com/video.mp4" }}
features={{
subtitles: [
{
lang: "en",
label: "English",
url: "https://example.com/subtitles-en.vtt",
},
{
lang: "es",
label: "Spanish",
url: "https://example.com/subtitles-es.vtt",
},
],
}}
style={{
subtitleStyle: {
fontSize: "1.25rem",
backgroundColor: "rgba(0, 0, 0, 0.75)",
textColor: "#ffffff",
position: "bottom",
},
}}
/>
```
### Video with Intro Skip
```tsx
<VideoPlayer
video={{ src: "https://example.com/video.mp4" }}
features={{
intro: {
start: 10, // Intro starts at 10 seconds
end: 45, // Intro ends at 45 seconds
},
}}
/>
```
### Episode Playlist
```tsx
const [currentEpisode, setCurrentEpisode] = useState(0);
const episodes = [
{ id: 1, title: "Episode 1", url: "https://example.com/ep1.m3u8" },
{ id: 2, title: "Episode 2", url: "https://example.com/ep2.m3u8" },
{ id: 3, title: "Episode 3", url: "https://example.com/ep3.m3u8" },
];
<VideoPlayer
video={{
src: episodes[currentEpisode].url,
title: episodes[currentEpisode].title,
}}
features={{
episodeList: episodes,
currentEpisodeIndex: currentEpisode,
nextEpisodeConfig: {
showAtTime: 300, // Show next episode button 5 minutes before end
showAtEnd: true, // Also show at video end
},
}}
events={{
onEnded: () => {
if (currentEpisode < episodes.length - 1) {
setCurrentEpisode(currentEpisode + 1);
}
},
}}
/>;
```
### Video with Tracking
```tsx
<VideoPlayer
video={{ src: "https://example.com/video.mp4" }}
features={{
tracking: {
onViewed: () => {
// Track video view
analytics.track("video_viewed", { videoId: "123" });
},
onWatchTimeUpdated: ({ watchTime, currentTime, duration }) => {
// Track watch time
analytics.track("watch_time_updated", {
watchTime,
progress: (currentTime / duration) * 100,
});
},
},
}}
/>
```
### Video with Watch History
```tsx
<VideoPlayer
video={{ src: "https://example.com/video.mp4" }}
events={{
onWatchHistoryUpdate: async (data) => {
// Save watch history to backend
await fetch("/api/watch-history", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
videoId: "123",
currentTime: data.currentTime,
duration: data.duration,
progress: data.progress,
isCompleted: data.isCompleted,
}),
});
},
}}
/>
```
### Video with Ads
```tsx
<VideoPlayer
video={{ src: "https://example.com/video.mp4" }}
features={{
ads: {
preRoll: {
id: "preroll-1",
type: "pre-roll",
time: 0,
adUrl: "https://example.com/pre-roll-ad.mp4",
skipable: true,
skipAfter: 5,
},
midRoll: [
{
id: "midroll-1",
type: "mid-roll",
time: 120,
adUrl: "https://example.com/mid-roll-ad.mp4",
skipable: false,
},
],
onAdStart: (adBreak) => {
console.log("Ad started:", adBreak.id);
},
onAdEnd: (adBreak) => {
console.log("Ad ended:", adBreak.id);
},
},
}}
/>
```
### Resume from Last Position
```tsx
const [watchHistory, setWatchHistory] = useState(null);
// Load watch history on mount
useEffect(() => {
fetch("/api/watch-history/123")
.then((res) => res.json())
.then((data) => setWatchHistory(data));
}, []);
<VideoPlayer
video={{
src: "https://example.com/video.mp4",
startFrom: watchHistory?.currentTime || 0,
}}
events={{
onWatchHistoryUpdate: (data) => {
// Save progress
setWatchHistory(data);
},
}}
/>;
```
---
## ❓ Troubleshooting
### Video Not Playing?
- **Check the video URL**: Ensure `video.src` is a valid, accessible URL
- **Check video format**: Verify the video format is supported (HLS, MP4, DASH, YouTube)
- **CORS issues**: If using HLS or external sources, ensure CORS headers are properly configured
- **Specify video type**: Try explicitly setting `video.type` to help with format detection
```tsx
<VideoPlayer
video={{
src: "https://example.com/video.m3u8",
type: "hls", // Explicitly specify type
}}
/>
```
### Subtitles Not Showing?
- **Check VTT file URL**: Ensure subtitle URLs are publicly accessible
- **Verify VTT format**: Ensure WebVTT files are properly formatted
- **Check CORS**: Subtitle files must be accessible from your domain
- **Test subtitle URL**: Open the subtitle URL directly in a browser to verify it loads
### Preview Thumbnails Not Loading?
- **Verify function returns URL**: Ensure `getPreviewScreenUrl` returns a valid image URL
- **Check image loading**: The function should return a URL that loads successfully
- **Use browser console**: Check for image loading errors in the browser console (F12)
### Player Not Responsive?
- **Use percentage widths**: Set `style.width` to `"100%"` for responsive behavior
- **Container styling**: Wrap the player in a container with appropriate CSS
- **Height auto**: Use `style.height: "auto"` to maintain aspect ratio
```tsx
<div style={{ maxWidth: "1200px", margin: "0 auto" }}>
<VideoPlayer
video={{ src: "https://example.com/video.mp4" }}
style={{
width: "100%",
height: "auto",
}}
/>
</div>
```
### Ads Not Playing?
- **Check ad URLs**: Ensure ad video URLs are valid and accessible
- **Verify ad timing**: For mid-roll ads, ensure `time` is less than video duration
- **Check ad format**: Ad videos should be in supported formats (MP4, HLS)
---
## 🔗 Related Links
- 📚 [Official Documentation](https://github.com/zezosoft/react-player)
- 🛠 [Issues & Support](https://github.com/zezosoft/react-player/issues)
---
## 📝 License
Licensed under the MIT License.
Developed by [Zezosoft](https://zezosoft.com). 🚀
---
## 🙌 Credits
This project includes modifications of the seek bar functionality inspired by [`react-video-seek-slider`](https://www.npmjs.com/package/react-video-seek-slider).
---
# 🌟 Enjoy seamless video playback with /react-player! 🎥