@zezosoft/react-player 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +536 -146
- package/dist/VideoPlayer/VideoPlayer.d.ts +1 -0
- package/dist/VideoPlayer/components/AdOverlay.d.ts +10 -0
- package/dist/VideoPlayer/components/ErrorOverlay.d.ts +8 -0
- package/dist/VideoPlayer/components/Overlay.d.ts +4 -0
- package/dist/VideoPlayer/components/SubtitleOverlay.d.ts +7 -0
- package/dist/VideoPlayer/components/controls/BottomControls.d.ts +5 -0
- package/dist/VideoPlayer/components/controls/ControlsHeader.d.ts +5 -0
- package/dist/VideoPlayer/components/controls/MiddleControls.d.ts +3 -0
- package/dist/VideoPlayer/components/controls/VideoPlayerControls.d.ts +4 -0
- package/dist/VideoPlayer/components/controls/index.d.ts +4 -0
- package/dist/VideoPlayer/components/time-line/TimeLine.d.ts +21 -0
- package/dist/VideoPlayer/components/time-line/components/HoverTimeWithPreview.d.ts +16 -0
- package/dist/VideoPlayer/components/time-line/components/Thumb.d.ts +9 -0
- package/dist/VideoPlayer/components/time-line/components/TimeCodeItem.d.ts +21 -0
- package/dist/VideoPlayer/components/time-line/components/TimeCodes.d.ts +15 -0
- package/dist/VideoPlayer/components/time-line/utils/getEndTimeByIndex.d.ts +2 -0
- package/dist/VideoPlayer/components/time-line/utils/getHoverTimePosition.d.ts +3 -0
- package/dist/VideoPlayer/components/time-line/utils/getPositionPercent.d.ts +1 -0
- package/dist/VideoPlayer/components/time-line/utils/getTimeScale.d.ts +1 -0
- package/dist/VideoPlayer/components/time-line/utils/isInRange.d.ts +1 -0
- package/dist/VideoPlayer/components/time-line/utils/positionToMs.d.ts +1 -0
- package/dist/VideoPlayer/components/time-line/utils/secondsToTime.d.ts +6 -0
- package/dist/VideoPlayer/components/time-line/utils/timeToTimeString.d.ts +1 -0
- package/dist/VideoPlayer/constants.d.ts +3 -0
- package/dist/VideoPlayer/hooks/index.d.ts +4 -0
- package/dist/VideoPlayer/hooks/useAdManager.d.ts +8 -0
- package/dist/VideoPlayer/hooks/useNetworkSpeed.d.ts +7 -0
- package/dist/VideoPlayer/hooks/usePrimaryVideoLifecycle.d.ts +17 -0
- package/dist/VideoPlayer/hooks/useVideoError.d.ts +7 -0
- package/dist/VideoPlayer/hooks/useVideoSource.d.ts +1 -14
- package/dist/VideoPlayer/hooks/useVideoTracking.d.ts +2 -2
- package/dist/VideoPlayer/types/AdTypes.d.ts +33 -0
- package/dist/VideoPlayer/types/VideoPlayerTypes.d.ts +34 -10
- package/dist/VideoPlayer/utils/index.d.ts +1 -1
- package/dist/VideoPlayer/utils/qualityManager.d.ts +6 -32
- package/dist/components/ui/FullScreenToggle.d.ts +1 -1
- package/dist/components/ui/PiPictureInPictureToggle.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2487 -493
- package/dist/store/slices/adsSlice.d.ts +24 -0
- package/dist/store/slices/errorSlice.d.ts +5 -0
- package/dist/store/slices/index.d.ts +2 -0
- package/dist/store/types/StoreTypes.d.ts +41 -9
- package/package.json +12 -11
package/README.md
CHANGED
|
@@ -1,35 +1,69 @@
|
|
|
1
1
|
# @zezosoft/react-player 🎬
|
|
2
2
|
|
|
3
|
-
A powerful and flexible **React video player** by **Zezosoft**, supporting HLS, MP4, preview thumbnails, tracking, subtitles, episode playback, and advanced controls.
|
|
3
|
+
A powerful and flexible **React video player** by **Zezosoft**, supporting HLS, MP4, DASH, YouTube, preview thumbnails, tracking, subtitles, episode playback, ads, and advanced controls.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## 🚀 Features
|
|
8
8
|
|
|
9
|
-
✅ **
|
|
10
|
-
✅ **Preview Thumbnails on Hover**
|
|
11
|
-
✅ **Event Tracking
|
|
12
|
-
✅ **Customizable Player Size & Controls**
|
|
13
|
-
✅ **Time-Stamped Labels
|
|
14
|
-
✅ **Subtitles (WebVTT)**
|
|
15
|
-
✅ **Intro Skipping**
|
|
16
|
-
✅ **Next
|
|
9
|
+
✅ **Multiple Video Formats** - HLS, MP4, DASH, YouTube
|
|
10
|
+
✅ **Preview Thumbnails on Hover** - Show video previews while scrubbing
|
|
11
|
+
✅ **Event Tracking** - Track views, watch time, and user interactions
|
|
12
|
+
✅ **Customizable Player Size & Controls** - Full control over player appearance
|
|
13
|
+
✅ **Time-Stamped Labels** - Video chapters with time markers
|
|
14
|
+
✅ **Subtitles (WebVTT)** - Multi-language subtitle support with custom styling
|
|
15
|
+
✅ **Intro Skipping** - Automatic skip intro button
|
|
16
|
+
✅ **Episode Playback** - Next episode auto-play and playlist support
|
|
17
|
+
✅ **Ad Support** - Pre-roll, mid-roll, post-roll, and overlay ads
|
|
18
|
+
✅ **Watch History** - Track user progress and resume playback
|
|
17
19
|
|
|
18
20
|
---
|
|
19
21
|
|
|
20
22
|
## 📦 Installation
|
|
21
23
|
|
|
22
|
-
Install the package using **npm**
|
|
24
|
+
Install the package using **npm** or **yarn**:
|
|
23
25
|
|
|
24
26
|
```sh
|
|
25
27
|
npm install @zezosoft/react-player
|
|
26
28
|
```
|
|
27
29
|
|
|
30
|
+
or
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
yarn add @zezosoft/react-player
|
|
34
|
+
```
|
|
35
|
+
|
|
28
36
|
---
|
|
29
37
|
|
|
30
|
-
## 🛠️
|
|
38
|
+
## 🛠️ Quick Start
|
|
31
39
|
|
|
32
|
-
|
|
40
|
+
Here's a simple example to get you started:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { VideoPlayer } from "@zezosoft/react-player";
|
|
44
|
+
|
|
45
|
+
function App() {
|
|
46
|
+
return (
|
|
47
|
+
<VideoPlayer
|
|
48
|
+
video={{
|
|
49
|
+
src: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8",
|
|
50
|
+
title: "My Video",
|
|
51
|
+
poster: "https://example.com/poster.jpg",
|
|
52
|
+
}}
|
|
53
|
+
style={{
|
|
54
|
+
width: "100%",
|
|
55
|
+
height: "auto",
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 📖 Complete Example
|
|
65
|
+
|
|
66
|
+
Here's a comprehensive example showing all available features:
|
|
33
67
|
|
|
34
68
|
```tsx
|
|
35
69
|
import { useCallback, useRef } from "react";
|
|
@@ -53,42 +87,99 @@ function App() {
|
|
|
53
87
|
}, []);
|
|
54
88
|
|
|
55
89
|
return (
|
|
56
|
-
<div
|
|
90
|
+
<div style={{ width: "720px", margin: "0 auto" }}>
|
|
57
91
|
<VideoPlayer
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
video={{
|
|
93
|
+
src: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8",
|
|
94
|
+
title: "Mehmaan",
|
|
95
|
+
poster: "https://i.ytimg.com/vi/VAUfyxw-Yvk/maxresdefault.jpg",
|
|
96
|
+
type: "hls",
|
|
97
|
+
isTrailer: false,
|
|
98
|
+
showControls: true,
|
|
99
|
+
isMute: false,
|
|
100
|
+
startFrom: 0,
|
|
101
|
+
}}
|
|
102
|
+
style={{
|
|
103
|
+
width: "720px",
|
|
104
|
+
height: "405px",
|
|
105
|
+
className: "my-video-player",
|
|
106
|
+
subtitleStyle: {
|
|
107
|
+
fontSize: "1.5rem",
|
|
108
|
+
backgroundColor: "rgba(0, 0, 0, 0.8)",
|
|
109
|
+
textColor: "#ffffff",
|
|
110
|
+
position: "bottom",
|
|
111
|
+
borderRadius: "8px",
|
|
112
|
+
padding: "8px 16px",
|
|
113
|
+
maxWidth: "80%",
|
|
114
|
+
},
|
|
115
|
+
}}
|
|
116
|
+
events={{
|
|
117
|
+
onEnded: (e) => console.log("Video ended", e),
|
|
118
|
+
onError: (e) => console.error("Video error", e),
|
|
119
|
+
onClose: () => console.log("Player closed"),
|
|
120
|
+
onWatchHistoryUpdate: (data) => {
|
|
121
|
+
console.log("Watch history:", data);
|
|
122
|
+
// Save to your backend
|
|
123
|
+
},
|
|
72
124
|
}}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
125
|
+
features={{
|
|
126
|
+
timeCodes: [
|
|
127
|
+
{ fromMs: 0, description: "Introduction" },
|
|
128
|
+
{ fromMs: 130000, description: "Exciting Scene" },
|
|
129
|
+
{ fromMs: 270000, description: "Climax" },
|
|
130
|
+
],
|
|
131
|
+
getPreviewScreenUrl: handleGettingPreview,
|
|
132
|
+
tracking: {
|
|
133
|
+
onViewed: () => console.log("Video Viewed"),
|
|
134
|
+
onWatchTimeUpdated: (e) => {
|
|
135
|
+
console.log("Watch Time Updated", {
|
|
136
|
+
watchTime: e.watchTime,
|
|
137
|
+
currentTime: e.currentTime,
|
|
138
|
+
duration: e.duration,
|
|
139
|
+
});
|
|
140
|
+
},
|
|
78
141
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
142
|
+
subtitles: [
|
|
143
|
+
{
|
|
144
|
+
lang: "en",
|
|
145
|
+
label: "English",
|
|
146
|
+
url: "https://example.com/subtitles-en.vtt",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
lang: "hi",
|
|
150
|
+
label: "Hindi",
|
|
151
|
+
url: "https://example.com/subtitles-hi.vtt",
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
episodeList: [
|
|
155
|
+
{ id: 1, title: "Episode 1", url: "https://example.com/ep1.m3u8" },
|
|
156
|
+
{ id: 2, title: "Episode 2", url: "https://example.com/ep2.m3u8" },
|
|
157
|
+
],
|
|
158
|
+
currentEpisodeIndex: 0,
|
|
159
|
+
intro: { start: 5, end: 20 },
|
|
160
|
+
nextEpisodeConfig: { showAtTime: 300, showAtEnd: true },
|
|
161
|
+
ads: {
|
|
162
|
+
preRoll: {
|
|
163
|
+
id: "preroll-1",
|
|
164
|
+
type: "pre-roll",
|
|
165
|
+
time: 0,
|
|
166
|
+
adUrl: "https://example.com/ad.mp4",
|
|
167
|
+
skipable: true,
|
|
168
|
+
skipAfter: 5,
|
|
169
|
+
},
|
|
170
|
+
midRoll: [
|
|
171
|
+
{
|
|
172
|
+
id: "midroll-1",
|
|
173
|
+
type: "mid-roll",
|
|
174
|
+
time: 120,
|
|
175
|
+
adUrl: "https://example.com/mid-ad.mp4",
|
|
176
|
+
skipable: false,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
onAdStart: (adBreak) => console.log("Ad started", adBreak),
|
|
180
|
+
onAdEnd: (adBreak) => console.log("Ad ended", adBreak),
|
|
83
181
|
},
|
|
84
|
-
|
|
85
|
-
episodeList={[
|
|
86
|
-
{ id: 1, title: "Episode 1", url: "https://example.com/ep1.m3u8" },
|
|
87
|
-
{ id: 2, title: "Episode 2", url: "https://example.com/ep2.m3u8" },
|
|
88
|
-
]}
|
|
89
|
-
currentEpisodeIndex={0}
|
|
90
|
-
intro={{ start: 5, end: 20 }}
|
|
91
|
-
nextEpisodeConfig={{ showAtTime: 300, showAtEnd: true }}
|
|
182
|
+
}}
|
|
92
183
|
/>
|
|
93
184
|
</div>
|
|
94
185
|
);
|
|
@@ -99,185 +190,484 @@ export default App;
|
|
|
99
190
|
|
|
100
191
|
---
|
|
101
192
|
|
|
102
|
-
##
|
|
103
|
-
|
|
104
|
-
| Prop Name | Type | Default | Description |
|
|
105
|
-
| --------------------- | ----------------------------------------------------- | ----------- | ---------------------------------------------------------- |
|
|
106
|
-
| `trackPoster` | `string` | `""` | URL of the video poster image. |
|
|
107
|
-
| `trackSrc` | `string` | `""` | Video source URL (MP4, HLS, etc.). |
|
|
108
|
-
| `trackTitle` | `string` | `""` | Title of the video. |
|
|
109
|
-
| `isTrailer` | `boolean` | `false` | Specifies if the video is a trailer. |
|
|
110
|
-
| `width` | `string` | `"100%"` | Width of the video player. |
|
|
111
|
-
| `height` | `string` | `"auto"` | Height of the video player. |
|
|
112
|
-
| `timeCodes` | `Array<{ fromMs: number, description: string }>` | `[]` | List of time-based markers with descriptions. |
|
|
113
|
-
| `getPreviewScreenUrl` | `(timeMs: number) => string` | `null` | Function to generate preview screen URLs based on time. |
|
|
114
|
-
| `tracking` | `object` | `{}` | Tracking event callbacks. |
|
|
115
|
-
| `subtitles` | `Array<{ lang: string; label: string; url: string }>` | `[]` | Subtitle tracks in WebVTT format. |
|
|
116
|
-
| `episodeList` | `Array<{ id: number; title: string; url: string }>` | `[]` | List of episodes to support autoplay/playlist. |
|
|
117
|
-
| `currentEpisodeIndex` | `number` | `0` | Index of currently playing episode. |
|
|
118
|
-
| `intro` | `{ start: number; end: number }` | `undefined` | Defines intro duration in seconds. |
|
|
119
|
-
| `nextEpisodeConfig` | `{ showAtTime?: number; showAtEnd?: boolean }` | `undefined` | When to show next episode UI — at specific time or at end. |
|
|
120
|
-
| |
|
|
193
|
+
## 📚 Props Reference
|
|
121
194
|
|
|
122
|
-
|
|
195
|
+
The `VideoPlayer` component accepts four main prop objects:
|
|
123
196
|
|
|
124
|
-
|
|
197
|
+
### `video` (Required)
|
|
125
198
|
|
|
126
|
-
|
|
127
|
-
| -------------------- | ------------------------------------------------ |
|
|
128
|
-
| `onViewed` | Triggered when the video starts playing. |
|
|
129
|
-
| `onWatchTimeUpdated` | Triggered when user leaves with >30s watch time. |
|
|
199
|
+
Video source and basic configuration.
|
|
130
200
|
|
|
131
|
-
|
|
201
|
+
| Prop | Type | Default | Description |
|
|
202
|
+
| -------------- | -------------------------------------------------- | ------------ | ------------------------------------------------------- |
|
|
203
|
+
| `src` | `string` | **Required** | Video source URL (MP4, HLS, DASH, YouTube, etc.) |
|
|
204
|
+
| `title` | `string` | `""` | Title of the video displayed in the player header |
|
|
205
|
+
| `poster` | `string` | `""` | URL of the poster/thumbnail image shown before playback |
|
|
206
|
+
| `type` | `"hls" \| "dash" \| "mp4" \| "youtube" \| "other"` | `undefined` | Video format type (auto-detected if not provided) |
|
|
207
|
+
| `isTrailer` | `boolean` | `false` | If `true`, shows trailer-specific UI elements |
|
|
208
|
+
| `showControls` | `boolean` | `true` | Show/hide player controls |
|
|
209
|
+
| `isMute` | `boolean` | `false` | Start video muted |
|
|
210
|
+
| `startFrom` | `number` | `0` | Start playback from specific time in seconds |
|
|
132
211
|
|
|
133
|
-
|
|
134
|
-
tracking={{
|
|
135
|
-
onViewed: () => console.log("Viewed!"),
|
|
136
|
-
onWatchTimeUpdated: ({ watchTime }) =>
|
|
137
|
-
console.log("Total watch time (sec):", watchTime),
|
|
138
|
-
}}
|
|
139
|
-
```
|
|
212
|
+
### `style` (Optional)
|
|
140
213
|
|
|
141
|
-
|
|
214
|
+
Styling and appearance configuration.
|
|
142
215
|
|
|
143
|
-
|
|
216
|
+
| Prop | Type | Default | Description |
|
|
217
|
+
| --------------- | --------------------- | ----------- | ---------------------------------------------- |
|
|
218
|
+
| `className` | `string` | `undefined` | Custom CSS class name for the player container |
|
|
219
|
+
| `width` | `string` | `"100%"` | Player width (e.g., `"720px"`, `"100%"`) |
|
|
220
|
+
| `height` | `string` | `"auto"` | Player height (e.g., `"405px"`, `"auto"`) |
|
|
221
|
+
| `subtitleStyle` | `SubtitleStyleConfig` | `undefined` | Custom styling for subtitles (see below) |
|
|
144
222
|
|
|
145
|
-
|
|
223
|
+
**SubtitleStyleConfig:**
|
|
146
224
|
|
|
147
|
-
|
|
225
|
+
| Prop | Type | Default | Description |
|
|
226
|
+
| ----------------- | ------------------------------- | --------------------------------------------- | ---------------------------------- |
|
|
227
|
+
| `fontSize` | `string` | `"1.75rem"` | Subtitle font size |
|
|
228
|
+
| `backgroundColor` | `string` | `"linear-gradient(135deg, #fbbf24, #f59e0b)"` | Subtitle background color/gradient |
|
|
229
|
+
| `textColor` | `string` | `"#000000"` | Subtitle text color |
|
|
230
|
+
| `position` | `"top" \| "center" \| "bottom"` | `"bottom"` | Vertical position of subtitles |
|
|
231
|
+
| `borderRadius` | `string` | `"12px"` | Border radius of subtitle box |
|
|
232
|
+
| `padding` | `string` | `"12px 20px"` | Padding inside subtitle box |
|
|
233
|
+
| `maxWidth` | `string` | `"80%"` | Maximum width of subtitle box |
|
|
148
234
|
|
|
149
|
-
|
|
150
|
-
|
|
235
|
+
### `events` (Optional)
|
|
236
|
+
|
|
237
|
+
Event callbacks for player lifecycle.
|
|
238
|
+
|
|
239
|
+
| Prop | Type | Description |
|
|
240
|
+
| ---------------------- | ------------------------------------------------------------- | -------------------------------------------------- |
|
|
241
|
+
| `onEnded` | `(e: React.SyntheticEvent<HTMLVideoElement>) => void` | Called when video playback ends |
|
|
242
|
+
| `onError` | `(e?: React.SyntheticEvent<HTMLVideoElement, Event>) => void` | Called when video encounters an error |
|
|
243
|
+
| `onClose` | `() => void` | Called when player is closed |
|
|
244
|
+
| `onWatchHistoryUpdate` | `(data: WatchHistoryData) => void` | Called when player closes with watch progress data |
|
|
245
|
+
|
|
246
|
+
**WatchHistoryData:**
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
{
|
|
250
|
+
currentTime: number; // Current playback time in seconds
|
|
251
|
+
duration: number; // Total video duration in seconds
|
|
252
|
+
progress: number; // Progress percentage (0-100)
|
|
253
|
+
isCompleted: boolean; // Whether video was fully watched
|
|
254
|
+
watchedAt: number; // Timestamp when watch session ended
|
|
255
|
+
}
|
|
151
256
|
```
|
|
152
257
|
|
|
153
|
-
|
|
258
|
+
### `features` (Optional)
|
|
259
|
+
|
|
260
|
+
Advanced features and functionality.
|
|
261
|
+
|
|
262
|
+
| Prop | Type | Default | Description |
|
|
263
|
+
| --------------------- | ------------------------------------------------ | ----------- | ------------------------------------------------------------------------ |
|
|
264
|
+
| `timeCodes` | `Array<{ fromMs: number, description: string }>` | `[]` | Time-based chapter markers (in milliseconds) |
|
|
265
|
+
| `getPreviewScreenUrl` | `(hoverTimeValue: number) => string` | `undefined` | Function to generate preview thumbnail URLs while hovering over seek bar |
|
|
266
|
+
| `tracking` | `TrackingConfig` | `undefined` | Event tracking configuration (see below) |
|
|
267
|
+
| `subtitles` | `Array<SubtitleTrack>` | `[]` | Subtitle tracks in WebVTT format |
|
|
268
|
+
| `episodeList` | `Array<Episode>` | `[]` | List of episodes for playlist/autoplay |
|
|
269
|
+
| `currentEpisodeIndex` | `number` | `0` | Index of currently playing episode |
|
|
270
|
+
| `intro` | `{ start: number, end: number }` | `undefined` | Intro skip configuration (times in seconds) |
|
|
271
|
+
| `nextEpisodeConfig` | `{ showAtTime?: number, showAtEnd?: boolean }` | `undefined` | Configuration for next episode button |
|
|
272
|
+
| `ads` | `AdConfig` | `undefined` | Advertisement configuration (see below) |
|
|
273
|
+
|
|
274
|
+
**TrackingConfig:**
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
{
|
|
278
|
+
onViewed?: () => void;
|
|
279
|
+
onWatchTimeUpdated?: (e: {
|
|
280
|
+
watchTime?: number; // Total watch time in seconds
|
|
281
|
+
currentTime: number; // Current playback position
|
|
282
|
+
duration: number; // Total video duration
|
|
283
|
+
}) => void;
|
|
284
|
+
}
|
|
285
|
+
```
|
|
154
286
|
|
|
155
|
-
|
|
287
|
+
**SubtitleTrack:**
|
|
156
288
|
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
289
|
+
```typescript
|
|
290
|
+
{
|
|
291
|
+
lang: string; // Language code (e.g., "en", "hi", "fr")
|
|
292
|
+
label: string; // Display label (e.g., "English", "Hindi")
|
|
293
|
+
url: string; // URL to WebVTT subtitle file
|
|
294
|
+
}
|
|
161
295
|
```
|
|
162
296
|
|
|
163
|
-
|
|
297
|
+
**Episode:**
|
|
164
298
|
|
|
165
|
-
|
|
299
|
+
```typescript
|
|
300
|
+
{
|
|
301
|
+
id: number; // Unique episode identifier
|
|
302
|
+
title: string; // Episode title
|
|
303
|
+
url: string; // Episode video URL
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**AdConfig:**
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
{
|
|
311
|
+
preRoll?: AdBreak; // Pre-roll ad (plays before video)
|
|
312
|
+
midRoll?: AdBreak[]; // Mid-roll ads (plays during video)
|
|
313
|
+
postRoll?: AdBreak; // Post-roll ad (plays after video)
|
|
314
|
+
overlay?: { // Overlay ad (image overlay)
|
|
315
|
+
imageUrl: string;
|
|
316
|
+
clickUrl?: string;
|
|
317
|
+
showDuration: number;
|
|
318
|
+
position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
319
|
+
};
|
|
320
|
+
smartPlacement?: { // Smart ad placement
|
|
321
|
+
enabled: boolean;
|
|
322
|
+
minVideoDuration?: number;
|
|
323
|
+
minGapBetweenAds?: number;
|
|
324
|
+
avoidNearEnd?: number;
|
|
325
|
+
preferNaturalBreaks?: boolean;
|
|
326
|
+
};
|
|
327
|
+
onAdStart?: (adBreak: AdBreak) => void;
|
|
328
|
+
onAdEnd?: (adBreak: AdBreak) => void;
|
|
329
|
+
onAdSkip?: (adBreak: AdBreak) => void;
|
|
330
|
+
onAdError?: (adBreak: AdBreak, error: Error) => void;
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**AdBreak:**
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
{
|
|
338
|
+
id: string; // Unique ad identifier
|
|
339
|
+
type: "pre-roll" | "mid-roll" | "post-roll" | "overlay";
|
|
340
|
+
time: number; // Time in seconds when ad should play
|
|
341
|
+
adUrl: string; // URL to ad video
|
|
342
|
+
skipable?: boolean; // Whether ad can be skipped
|
|
343
|
+
skipAfter?: number; // Seconds before skip button appears
|
|
344
|
+
duration?: number; // Ad duration in seconds
|
|
345
|
+
sponsoredUrl?: string; // URL for sponsored content
|
|
346
|
+
title?: string; // Ad title
|
|
347
|
+
description?: string; // Ad description
|
|
348
|
+
relevance?: "high" | "medium" | "low";
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 🎯 Usage Examples
|
|
355
|
+
|
|
356
|
+
### Basic Video Player
|
|
166
357
|
|
|
167
358
|
```tsx
|
|
168
359
|
<VideoPlayer
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
360
|
+
video={{
|
|
361
|
+
src: "https://example.com/video.mp4",
|
|
362
|
+
title: "My Video",
|
|
363
|
+
}}
|
|
173
364
|
/>
|
|
174
365
|
```
|
|
175
366
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
Add support for multiple subtitle tracks in .vtt format:
|
|
367
|
+
### HLS Video with Poster
|
|
179
368
|
|
|
180
369
|
```tsx
|
|
181
|
-
|
|
182
|
-
{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
370
|
+
<VideoPlayer
|
|
371
|
+
video={{
|
|
372
|
+
src: "https://example.com/playlist.m3u8",
|
|
373
|
+
type: "hls",
|
|
374
|
+
poster: "https://example.com/poster.jpg",
|
|
375
|
+
title: "Streaming Video",
|
|
376
|
+
}}
|
|
377
|
+
style={{
|
|
378
|
+
width: "100%",
|
|
379
|
+
height: "450px",
|
|
380
|
+
}}
|
|
381
|
+
/>
|
|
193
382
|
```
|
|
194
383
|
|
|
195
|
-
|
|
384
|
+
### Video with Preview Thumbnails
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
const getPreview = (hoverTime: number) => {
|
|
388
|
+
// Generate thumbnail URL based on hover time
|
|
389
|
+
return `https://example.com/thumbnails/${Math.floor(hoverTime)}.jpg`;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
<VideoPlayer
|
|
393
|
+
video={{ src: "https://example.com/video.mp4" }}
|
|
394
|
+
features={{
|
|
395
|
+
getPreviewScreenUrl: getPreview,
|
|
396
|
+
}}
|
|
397
|
+
/>;
|
|
398
|
+
```
|
|
196
399
|
|
|
197
|
-
|
|
400
|
+
### Video with Chapters
|
|
198
401
|
|
|
199
402
|
```tsx
|
|
200
|
-
<VideoPlayer
|
|
403
|
+
<VideoPlayer
|
|
404
|
+
video={{ src: "https://example.com/video.mp4" }}
|
|
405
|
+
features={{
|
|
406
|
+
timeCodes: [
|
|
407
|
+
{ fromMs: 0, description: "Introduction" },
|
|
408
|
+
{ fromMs: 60000, description: "Chapter 1" },
|
|
409
|
+
{ fromMs: 120000, description: "Chapter 2" },
|
|
410
|
+
{ fromMs: 180000, description: "Conclusion" },
|
|
411
|
+
],
|
|
412
|
+
}}
|
|
413
|
+
/>
|
|
201
414
|
```
|
|
202
415
|
|
|
203
|
-
|
|
416
|
+
### Video with Subtitles
|
|
204
417
|
|
|
205
|
-
|
|
418
|
+
```tsx
|
|
419
|
+
<VideoPlayer
|
|
420
|
+
video={{ src: "https://example.com/video.mp4" }}
|
|
421
|
+
features={{
|
|
422
|
+
subtitles: [
|
|
423
|
+
{
|
|
424
|
+
lang: "en",
|
|
425
|
+
label: "English",
|
|
426
|
+
url: "https://example.com/subtitles-en.vtt",
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
lang: "es",
|
|
430
|
+
label: "Spanish",
|
|
431
|
+
url: "https://example.com/subtitles-es.vtt",
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
}}
|
|
435
|
+
style={{
|
|
436
|
+
subtitleStyle: {
|
|
437
|
+
fontSize: "1.25rem",
|
|
438
|
+
backgroundColor: "rgba(0, 0, 0, 0.75)",
|
|
439
|
+
textColor: "#ffffff",
|
|
440
|
+
position: "bottom",
|
|
441
|
+
},
|
|
442
|
+
}}
|
|
443
|
+
/>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Video with Intro Skip
|
|
206
447
|
|
|
207
448
|
```tsx
|
|
208
449
|
<VideoPlayer
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
450
|
+
video={{ src: "https://example.com/video.mp4" }}
|
|
451
|
+
features={{
|
|
452
|
+
intro: {
|
|
453
|
+
start: 10, // Intro starts at 10 seconds
|
|
454
|
+
end: 45, // Intro ends at 45 seconds
|
|
455
|
+
},
|
|
212
456
|
}}
|
|
213
457
|
/>
|
|
214
458
|
```
|
|
215
459
|
|
|
216
|
-
|
|
460
|
+
### Episode Playlist
|
|
461
|
+
|
|
462
|
+
```tsx
|
|
463
|
+
const [currentEpisode, setCurrentEpisode] = useState(0);
|
|
464
|
+
|
|
465
|
+
const episodes = [
|
|
466
|
+
{ id: 1, title: "Episode 1", url: "https://example.com/ep1.m3u8" },
|
|
467
|
+
{ id: 2, title: "Episode 2", url: "https://example.com/ep2.m3u8" },
|
|
468
|
+
{ id: 3, title: "Episode 3", url: "https://example.com/ep3.m3u8" },
|
|
469
|
+
];
|
|
470
|
+
|
|
471
|
+
<VideoPlayer
|
|
472
|
+
video={{
|
|
473
|
+
src: episodes[currentEpisode].url,
|
|
474
|
+
title: episodes[currentEpisode].title,
|
|
475
|
+
}}
|
|
476
|
+
features={{
|
|
477
|
+
episodeList: episodes,
|
|
478
|
+
currentEpisodeIndex: currentEpisode,
|
|
479
|
+
nextEpisodeConfig: {
|
|
480
|
+
showAtTime: 300, // Show next episode button 5 minutes before end
|
|
481
|
+
showAtEnd: true, // Also show at video end
|
|
482
|
+
},
|
|
483
|
+
}}
|
|
484
|
+
events={{
|
|
485
|
+
onEnded: () => {
|
|
486
|
+
if (currentEpisode < episodes.length - 1) {
|
|
487
|
+
setCurrentEpisode(currentEpisode + 1);
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
}}
|
|
491
|
+
/>;
|
|
492
|
+
```
|
|
217
493
|
|
|
218
|
-
|
|
494
|
+
### Video with Tracking
|
|
219
495
|
|
|
220
496
|
```tsx
|
|
221
497
|
<VideoPlayer
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
498
|
+
video={{ src: "https://example.com/video.mp4" }}
|
|
499
|
+
features={{
|
|
500
|
+
tracking: {
|
|
501
|
+
onViewed: () => {
|
|
502
|
+
// Track video view
|
|
503
|
+
analytics.track("video_viewed", { videoId: "123" });
|
|
504
|
+
},
|
|
505
|
+
onWatchTimeUpdated: ({ watchTime, currentTime, duration }) => {
|
|
506
|
+
// Track watch time
|
|
507
|
+
analytics.track("watch_time_updated", {
|
|
508
|
+
watchTime,
|
|
509
|
+
progress: (currentTime / duration) * 100,
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
}}
|
|
227
514
|
/>
|
|
228
515
|
```
|
|
229
516
|
|
|
230
|
-
|
|
517
|
+
### Video with Watch History
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
<VideoPlayer
|
|
521
|
+
video={{ src: "https://example.com/video.mp4" }}
|
|
522
|
+
events={{
|
|
523
|
+
onWatchHistoryUpdate: async (data) => {
|
|
524
|
+
// Save watch history to backend
|
|
525
|
+
await fetch("/api/watch-history", {
|
|
526
|
+
method: "POST",
|
|
527
|
+
headers: { "Content-Type": "application/json" },
|
|
528
|
+
body: JSON.stringify({
|
|
529
|
+
videoId: "123",
|
|
530
|
+
currentTime: data.currentTime,
|
|
531
|
+
duration: data.duration,
|
|
532
|
+
progress: data.progress,
|
|
533
|
+
isCompleted: data.isCompleted,
|
|
534
|
+
}),
|
|
535
|
+
});
|
|
536
|
+
},
|
|
537
|
+
}}
|
|
538
|
+
/>
|
|
539
|
+
```
|
|
231
540
|
|
|
232
|
-
|
|
541
|
+
### Video with Ads
|
|
233
542
|
|
|
234
543
|
```tsx
|
|
235
544
|
<VideoPlayer
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
545
|
+
video={{ src: "https://example.com/video.mp4" }}
|
|
546
|
+
features={{
|
|
547
|
+
ads: {
|
|
548
|
+
preRoll: {
|
|
549
|
+
id: "preroll-1",
|
|
550
|
+
type: "pre-roll",
|
|
551
|
+
time: 0,
|
|
552
|
+
adUrl: "https://example.com/pre-roll-ad.mp4",
|
|
553
|
+
skipable: true,
|
|
554
|
+
skipAfter: 5,
|
|
555
|
+
},
|
|
556
|
+
midRoll: [
|
|
557
|
+
{
|
|
558
|
+
id: "midroll-1",
|
|
559
|
+
type: "mid-roll",
|
|
560
|
+
time: 120,
|
|
561
|
+
adUrl: "https://example.com/mid-roll-ad.mp4",
|
|
562
|
+
skipable: false,
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
onAdStart: (adBreak) => {
|
|
566
|
+
console.log("Ad started:", adBreak.id);
|
|
567
|
+
},
|
|
568
|
+
onAdEnd: (adBreak) => {
|
|
569
|
+
console.log("Ad ended:", adBreak.id);
|
|
570
|
+
},
|
|
571
|
+
},
|
|
240
572
|
}}
|
|
241
573
|
/>
|
|
242
574
|
```
|
|
243
575
|
|
|
576
|
+
### Resume from Last Position
|
|
577
|
+
|
|
578
|
+
```tsx
|
|
579
|
+
const [watchHistory, setWatchHistory] = useState(null);
|
|
580
|
+
|
|
581
|
+
// Load watch history on mount
|
|
582
|
+
useEffect(() => {
|
|
583
|
+
fetch("/api/watch-history/123")
|
|
584
|
+
.then((res) => res.json())
|
|
585
|
+
.then((data) => setWatchHistory(data));
|
|
586
|
+
}, []);
|
|
587
|
+
|
|
588
|
+
<VideoPlayer
|
|
589
|
+
video={{
|
|
590
|
+
src: "https://example.com/video.mp4",
|
|
591
|
+
startFrom: watchHistory?.currentTime || 0,
|
|
592
|
+
}}
|
|
593
|
+
events={{
|
|
594
|
+
onWatchHistoryUpdate: (data) => {
|
|
595
|
+
// Save progress
|
|
596
|
+
setWatchHistory(data);
|
|
597
|
+
},
|
|
598
|
+
}}
|
|
599
|
+
/>;
|
|
600
|
+
```
|
|
601
|
+
|
|
244
602
|
---
|
|
245
603
|
|
|
246
604
|
## ❓ Troubleshooting
|
|
247
605
|
|
|
248
|
-
|
|
606
|
+
### Video Not Playing?
|
|
249
607
|
|
|
250
|
-
- Check
|
|
251
|
-
-
|
|
252
|
-
- If using HLS
|
|
608
|
+
- **Check the video URL**: Ensure `video.src` is a valid, accessible URL
|
|
609
|
+
- **Check video format**: Verify the video format is supported (HLS, MP4, DASH, YouTube)
|
|
610
|
+
- **CORS issues**: If using HLS or external sources, ensure CORS headers are properly configured
|
|
611
|
+
- **Specify video type**: Try explicitly setting `video.type` to help with format detection
|
|
253
612
|
|
|
254
|
-
|
|
613
|
+
```tsx
|
|
614
|
+
<VideoPlayer
|
|
615
|
+
video={{
|
|
616
|
+
src: "https://example.com/video.m3u8",
|
|
617
|
+
type: "hls", // Explicitly specify type
|
|
618
|
+
}}
|
|
619
|
+
/>
|
|
620
|
+
```
|
|
255
621
|
|
|
256
|
-
|
|
257
|
-
- Ensure the subtitles prop includes proper lang, label, and url.
|
|
622
|
+
### Subtitles Not Showing?
|
|
258
623
|
|
|
259
|
-
|
|
624
|
+
- **Check VTT file URL**: Ensure subtitle URLs are publicly accessible
|
|
625
|
+
- **Verify VTT format**: Ensure WebVTT files are properly formatted
|
|
626
|
+
- **Check CORS**: Subtitle files must be accessible from your domain
|
|
627
|
+
- **Test subtitle URL**: Open the subtitle URL directly in a browser to verify it loads
|
|
260
628
|
|
|
261
|
-
|
|
262
|
-
|
|
629
|
+
### Preview Thumbnails Not Loading?
|
|
630
|
+
|
|
631
|
+
- **Verify function returns URL**: Ensure `getPreviewScreenUrl` returns a valid image URL
|
|
632
|
+
- **Check image loading**: The function should return a URL that loads successfully
|
|
633
|
+
- **Use browser console**: Check for image loading errors in the browser console (F12)
|
|
634
|
+
|
|
635
|
+
### Player Not Responsive?
|
|
636
|
+
|
|
637
|
+
- **Use percentage widths**: Set `style.width` to `"100%"` for responsive behavior
|
|
638
|
+
- **Container styling**: Wrap the player in a container with appropriate CSS
|
|
639
|
+
- **Height auto**: Use `style.height: "auto"` to maintain aspect ratio
|
|
640
|
+
|
|
641
|
+
```tsx
|
|
642
|
+
<div style={{ maxWidth: "1200px", margin: "0 auto" }}>
|
|
643
|
+
<VideoPlayer
|
|
644
|
+
video={{ src: "https://example.com/video.mp4" }}
|
|
645
|
+
style={{
|
|
646
|
+
width: "100%",
|
|
647
|
+
height: "auto",
|
|
648
|
+
}}
|
|
649
|
+
/>
|
|
650
|
+
</div>
|
|
651
|
+
```
|
|
263
652
|
|
|
264
|
-
|
|
653
|
+
### Ads Not Playing?
|
|
265
654
|
|
|
266
|
-
-
|
|
267
|
-
-
|
|
655
|
+
- **Check ad URLs**: Ensure ad video URLs are valid and accessible
|
|
656
|
+
- **Verify ad timing**: For mid-roll ads, ensure `time` is less than video duration
|
|
657
|
+
- **Check ad format**: Ad videos should be in supported formats (MP4, HLS)
|
|
268
658
|
|
|
269
659
|
---
|
|
270
660
|
|
|
271
661
|
## 🔗 Related Links
|
|
272
662
|
|
|
273
663
|
- 📚 [Official Documentation](https://github.com/zezosoft/react-player)
|
|
274
|
-
- 🛠 [Issues & Support](https://github.com/zezosoft/react-player)
|
|
664
|
+
- 🛠 [Issues & Support](https://github.com/zezosoft/react-player/issues)
|
|
275
665
|
|
|
276
666
|
---
|
|
277
667
|
|
|
278
668
|
## 📝 License
|
|
279
669
|
|
|
280
|
-
Licensed under the MIT License.
|
|
670
|
+
Licensed under the MIT License.
|
|
281
671
|
Developed by [Zezosoft](https://zezosoft.com). 🚀
|
|
282
672
|
|
|
283
673
|
---
|