react-helios 2.0.0 → 2.1.0
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 +74 -10
- package/dist/index.d.mts +59 -48
- package/dist/index.d.ts +59 -48
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +15 -9
- package/dist/styles.d.ts +1 -0
- package/package.json +11 -5
- package/dist/index.css +0 -2
- package/dist/index.css.map +0 -1
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-helios
|
|
2
2
|
|
|
3
|
-
Production-grade React video player with HLS streaming, adaptive quality selection, live stream support, subtitle tracks, thumbnail preview, Picture-in-Picture, and full keyboard control.
|
|
3
|
+
Production-grade React video player with HLS streaming, adaptive quality selection, live stream support, subtitle tracks, VTT sprite sheet thumbnail preview, Picture-in-Picture, and full keyboard control.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install
|
|
8
|
+
npm install react-helios
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
**Peer dependencies** — install if not already in your project:
|
|
@@ -17,8 +17,8 @@ npm install react react-dom
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
19
|
```tsx
|
|
20
|
-
import { VideoPlayer } from "
|
|
21
|
-
import "
|
|
20
|
+
import { VideoPlayer } from "react-helios";
|
|
21
|
+
import "react-helios/styles";
|
|
22
22
|
|
|
23
23
|
export default function App() {
|
|
24
24
|
return (
|
|
@@ -47,6 +47,42 @@ Pass any `.m3u8` URL — HLS.js is initialised automatically:
|
|
|
47
47
|
|
|
48
48
|
On Safari the browser's native HLS engine is used. A **LIVE** badge and **GO LIVE** button appear automatically for live streams.
|
|
49
49
|
|
|
50
|
+
## Thumbnail Preview
|
|
51
|
+
|
|
52
|
+
Hover over the progress bar to see a time tooltip. For rich sprite-sheet thumbnails, pass a `thumbnailVtt` URL pointing to a [WebVTT thumbnail file](https://developer.bitmovin.com/playback/docs/webvtt-based-thumbnails).
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
<VideoPlayer
|
|
56
|
+
src="https://example.com/video.mp4"
|
|
57
|
+
thumbnailVtt="https://example.com/thumbs/storyboard.vtt"
|
|
58
|
+
/>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### VTT format
|
|
62
|
+
|
|
63
|
+
Each cue in the `.vtt` file maps a time range to a rectangular region inside a sprite image using the `#xywh=x,y,w,h` fragment:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
WEBVTT
|
|
67
|
+
|
|
68
|
+
00:00:00.000 --> 00:00:05.000
|
|
69
|
+
https://example.com/thumbs/sprite.jpg#xywh=0,0,160,90
|
|
70
|
+
|
|
71
|
+
00:00:05.000 --> 00:00:10.000
|
|
72
|
+
https://example.com/thumbs/sprite.jpg#xywh=160,0,160,90
|
|
73
|
+
|
|
74
|
+
00:00:10.000 --> 00:00:15.000
|
|
75
|
+
https://example.com/thumbs/sprite.jpg#xywh=320,0,160,90
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The player fetches the VTT file once, parses all cues, and uses CSS `background-position` to display the correct sprite cell during hover — **no additional network requests per hover**.
|
|
79
|
+
|
|
80
|
+
To disable the preview entirely:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
<VideoPlayer src="..." enablePreview={false} />
|
|
84
|
+
```
|
|
85
|
+
|
|
50
86
|
## Props
|
|
51
87
|
|
|
52
88
|
| Prop | Type | Default | Description |
|
|
@@ -60,7 +96,8 @@ On Safari the browser's native HLS engine is used. A **LIVE** badge and **GO LIV
|
|
|
60
96
|
| `preload` | `"none" \| "metadata" \| "auto"` | `"metadata"` | Native `preload` attribute |
|
|
61
97
|
| `playbackRates` | `PlaybackRate[]` | `[0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]` | Available speed options |
|
|
62
98
|
| `enableHLS` | `boolean` | `true` | Enable HLS.js for `.m3u8` sources |
|
|
63
|
-
| `enablePreview` | `boolean` | `true` | Show thumbnail
|
|
99
|
+
| `enablePreview` | `boolean` | `true` | Show thumbnail / time tooltip on progress bar hover |
|
|
100
|
+
| `thumbnailVtt` | `string` | — | URL to a WebVTT sprite sheet file for rich thumbnail preview |
|
|
64
101
|
| `hlsConfig` | `Partial<HlsConfig>` | — | Override any [hls.js configuration](https://github.com/video-dev/hls.js/blob/master/docs/API.md#fine-tuning) option |
|
|
65
102
|
| `subtitles` | `SubtitleTrack[]` | — | Subtitle / caption tracks |
|
|
66
103
|
| `crossOrigin` | `"anonymous" \| "use-credentials"` | — | CORS attribute for the video element |
|
|
@@ -79,7 +116,7 @@ Use a `ref` to control the player programmatically:
|
|
|
79
116
|
|
|
80
117
|
```tsx
|
|
81
118
|
import { useRef } from "react";
|
|
82
|
-
import { VideoPlayer, VideoPlayerRef } from "
|
|
119
|
+
import { VideoPlayer, VideoPlayerRef } from "react-helios";
|
|
83
120
|
|
|
84
121
|
export default function App() {
|
|
85
122
|
const playerRef = useRef<VideoPlayerRef>(null);
|
|
@@ -165,7 +202,11 @@ import type {
|
|
|
165
202
|
BufferedRange,
|
|
166
203
|
VideoError,
|
|
167
204
|
VideoErrorCode,
|
|
168
|
-
|
|
205
|
+
ThumbnailCue,
|
|
206
|
+
} from "react-helios";
|
|
207
|
+
|
|
208
|
+
// VTT utilities (useful for server-side pre-parsing or custom UIs)
|
|
209
|
+
import { parseThumbnailVtt, findThumbnailCue } from "react-helios";
|
|
169
210
|
```
|
|
170
211
|
|
|
171
212
|
### `PlayerState`
|
|
@@ -207,14 +248,37 @@ interface VideoError {
|
|
|
207
248
|
}
|
|
208
249
|
```
|
|
209
250
|
|
|
251
|
+
### `ThumbnailCue`
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
interface ThumbnailCue {
|
|
255
|
+
start: number; // seconds
|
|
256
|
+
end: number; // seconds
|
|
257
|
+
url: string; // absolute URL to the sprite image
|
|
258
|
+
x: number; // pixel offset within sprite
|
|
259
|
+
y: number;
|
|
260
|
+
w: number; // cell width in pixels
|
|
261
|
+
h: number; // cell height in pixels
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Performance
|
|
266
|
+
|
|
267
|
+
The player is architected to produce **zero React re-renders during playback**:
|
|
268
|
+
|
|
269
|
+
- `timeupdate` and `progress` events are handled by direct DOM mutation (refs), not React state.
|
|
270
|
+
- `ProgressBar` and `TimeDisplay` self-subscribe to the video element — the parent tree never re-renders on seek or time change.
|
|
271
|
+
- VTT sprite thumbnails are looked up via binary search (O(log n)) and rendered via CSS `background-position` — no hidden `<video>` element, no canvas, no network requests per hover.
|
|
272
|
+
- Buffered ranges are the only state that triggers a re-render (fires every few seconds during buffering, not 60× per second).
|
|
273
|
+
|
|
210
274
|
## Project Structure
|
|
211
275
|
|
|
212
276
|
```
|
|
213
|
-
react-
|
|
277
|
+
react-helios/
|
|
214
278
|
├── src/ # Library source
|
|
215
279
|
│ ├── components/ # VideoPlayer, Controls, control elements
|
|
216
280
|
│ ├── hooks/ # useVideoPlayer (state + HLS init)
|
|
217
|
-
│ ├── lib/ # Types, HLS utilities, format helpers
|
|
281
|
+
│ ├── lib/ # Types, HLS utilities, VTT parser, format helpers
|
|
218
282
|
│ └── styles/ # CSS
|
|
219
283
|
├── examples/
|
|
220
284
|
│ └── nextjs-demo/ # Standalone Next.js demo app
|
package/dist/index.d.mts
CHANGED
|
@@ -11,12 +11,12 @@ interface VideoError {
|
|
|
11
11
|
code: VideoErrorCode;
|
|
12
12
|
message: string;
|
|
13
13
|
}
|
|
14
|
+
/** Display name e.g. "1080p", "720p", "Auto" */
|
|
14
15
|
interface HLSQualityLevel {
|
|
15
16
|
id: number;
|
|
16
17
|
height: number;
|
|
17
18
|
width: number;
|
|
18
19
|
bitrate: number;
|
|
19
|
-
/** Display name e.g. "1080p", "720p", "Auto" */
|
|
20
20
|
name: string;
|
|
21
21
|
}
|
|
22
22
|
interface SubtitleTrack {
|
|
@@ -38,11 +38,8 @@ interface PlayerState {
|
|
|
38
38
|
error: VideoError | null;
|
|
39
39
|
isFullscreen: boolean;
|
|
40
40
|
isPictureInPicture: boolean;
|
|
41
|
-
/** True when the stream is a live HLS stream (Infinity duration) */
|
|
42
41
|
isLive: boolean;
|
|
43
|
-
/** Available HLS quality levels; empty for non-HLS sources */
|
|
44
42
|
qualityLevels: HLSQualityLevel[];
|
|
45
|
-
/** Currently active quality level id; -1 = ABR auto */
|
|
46
43
|
currentQualityLevel: number;
|
|
47
44
|
}
|
|
48
45
|
type PlaybackRate = 0.25 | 0.5 | 0.75 | 1 | 1.25 | 1.5 | 1.75 | 2;
|
|
@@ -51,12 +48,9 @@ interface VideoPlayerRef {
|
|
|
51
48
|
pause: () => void;
|
|
52
49
|
seek: (time: number) => void;
|
|
53
50
|
setVolume: (volume: number) => void;
|
|
54
|
-
/** Toggle mute while remembering the pre-mute volume */
|
|
55
51
|
toggleMute: () => void;
|
|
56
52
|
setPlaybackRate: (rate: PlaybackRate) => void;
|
|
57
|
-
/** Set HLS quality level; pass -1 for automatic ABR */
|
|
58
53
|
setQualityLevel: (level: number) => void;
|
|
59
|
-
/** Jump to the live edge of an HLS live stream */
|
|
60
54
|
seekToLive: () => void;
|
|
61
55
|
toggleFullscreen: () => Promise<void>;
|
|
62
56
|
togglePictureInPicture: () => Promise<void>;
|
|
@@ -75,11 +69,26 @@ interface VideoPlayerProps {
|
|
|
75
69
|
className?: string;
|
|
76
70
|
enableHLS?: boolean;
|
|
77
71
|
enablePreview?: boolean;
|
|
78
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* URL to a WebVTT thumbnail track for sprite-sheet preview on the progress bar.
|
|
74
|
+
*
|
|
75
|
+
* The VTT file should map time ranges to sprite-sheet coordinates using the
|
|
76
|
+
* standard `#xywh=x,y,w,h` fragment format:
|
|
77
|
+
*
|
|
78
|
+
* ```
|
|
79
|
+
* WEBVTT
|
|
80
|
+
*
|
|
81
|
+
* 00:00:00.000 --> 00:00:05.000
|
|
82
|
+
* https://cdn.example.com/thumbs/storyboard0.jpg#xywh=0,0,160,90
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* When provided, hovering the progress bar shows a thumbnail instead of
|
|
86
|
+
* requiring a second video decode. If omitted, only the timestamp tooltip
|
|
87
|
+
* is shown.
|
|
88
|
+
*/
|
|
89
|
+
thumbnailVtt?: string;
|
|
79
90
|
hlsConfig?: Partial<HlsConfig>;
|
|
80
|
-
/** Subtitle / caption tracks */
|
|
81
91
|
subtitles?: SubtitleTrack[];
|
|
82
|
-
/** crossorigin attribute for CORS-enabled preview thumbnails */
|
|
83
92
|
crossOrigin?: "anonymous" | "use-credentials";
|
|
84
93
|
onPlay?: () => void;
|
|
85
94
|
onPause?: () => void;
|
|
@@ -93,14 +102,13 @@ interface VideoPlayerProps {
|
|
|
93
102
|
declare const VideoPlayer: react__default.ForwardRefExoticComponent<VideoPlayerProps & react__default.RefAttributes<VideoPlayerRef>>;
|
|
94
103
|
|
|
95
104
|
interface ControlsProps {
|
|
105
|
+
videoRef: react__default.RefObject<HTMLVideoElement | null>;
|
|
96
106
|
playerRef: VideoPlayerRef;
|
|
97
|
-
/** Ref to the outer player container; used to scope keyboard shortcuts to the focused player */
|
|
98
107
|
playerContainerRef: react__default.RefObject<HTMLElement | null>;
|
|
99
108
|
playbackRates: PlaybackRate[];
|
|
100
109
|
enablePreview: boolean;
|
|
110
|
+
thumbnailVtt?: string;
|
|
101
111
|
isPlaying: boolean;
|
|
102
|
-
currentTime: number;
|
|
103
|
-
duration: number;
|
|
104
112
|
volume: number;
|
|
105
113
|
isMuted: boolean;
|
|
106
114
|
playbackRate: number;
|
|
@@ -109,25 +117,17 @@ interface ControlsProps {
|
|
|
109
117
|
isLive: boolean;
|
|
110
118
|
qualityLevels: HLSQualityLevel[];
|
|
111
119
|
currentQualityLevel: number;
|
|
112
|
-
bufferedRanges: BufferedRange[];
|
|
113
120
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Controls is intentionally NOT wrapped in React.memo here – it receives
|
|
116
|
-
* currentTime which changes every tick, so memo wouldn't help at this level.
|
|
117
|
-
* Instead, all its CHILDREN are memoized so they skip renders when their
|
|
118
|
-
* specific props haven't changed.
|
|
119
|
-
*/
|
|
120
121
|
declare const Controls: react__default.FC<ControlsProps>;
|
|
121
122
|
|
|
122
123
|
interface TimeDisplayProps {
|
|
123
|
-
|
|
124
|
-
duration: number;
|
|
124
|
+
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
125
125
|
isLive?: boolean;
|
|
126
126
|
}
|
|
127
127
|
/**
|
|
128
|
-
* TimeDisplay
|
|
129
|
-
*
|
|
130
|
-
*
|
|
128
|
+
* TimeDisplay subscribes directly to the video element's timeupdate and
|
|
129
|
+
* durationchange events, updating the DOM via refs. It never re-renders
|
|
130
|
+
* during playback — only when isLive changes (once per source change).
|
|
131
131
|
*/
|
|
132
132
|
declare const TimeDisplay: react.NamedExoticComponent<TimeDisplayProps>;
|
|
133
133
|
|
|
@@ -139,22 +139,13 @@ interface SettingsMenuProps {
|
|
|
139
139
|
currentQualityLevel?: number;
|
|
140
140
|
onQualityChange?: (level: number) => void;
|
|
141
141
|
}
|
|
142
|
-
/**
|
|
143
|
-
* SettingsMenu wrapped in React.memo.
|
|
144
|
-
* playbackRate and qualityLevel rarely change → this component skips
|
|
145
|
-
* almost all re-renders during normal playback.
|
|
146
|
-
*
|
|
147
|
-
* sortedLevels is memoized so the .sort() only runs when qualityLevels
|
|
148
|
-
* actually changes (usually once after manifest is parsed).
|
|
149
|
-
*/
|
|
150
142
|
declare const SettingsMenu: react.NamedExoticComponent<SettingsMenuProps>;
|
|
151
143
|
|
|
152
144
|
interface ProgressBarProps {
|
|
145
|
+
videoRef: react__default.RefObject<HTMLVideoElement | null>;
|
|
153
146
|
playerRef: VideoPlayerRef;
|
|
154
|
-
currentTime: number;
|
|
155
|
-
duration: number;
|
|
156
|
-
bufferedRanges: BufferedRange[];
|
|
157
147
|
enablePreview?: boolean;
|
|
148
|
+
thumbnailVtt?: string;
|
|
158
149
|
}
|
|
159
150
|
declare const ProgressBar: react__default.FC<ProgressBarProps>;
|
|
160
151
|
|
|
@@ -164,11 +155,6 @@ interface VolumeControlProps {
|
|
|
164
155
|
onVolumeChange: (volume: number) => void;
|
|
165
156
|
onToggleMute: () => void;
|
|
166
157
|
}
|
|
167
|
-
/**
|
|
168
|
-
* VolumeControl wrapped in React.memo.
|
|
169
|
-
* volume and isMuted don't change during timeupdate → 0 re-renders during
|
|
170
|
-
* normal playback when the user isn't touching volume controls.
|
|
171
|
-
*/
|
|
172
158
|
declare const VolumeControl: react.NamedExoticComponent<VolumeControlProps>;
|
|
173
159
|
|
|
174
160
|
interface PlayButtonProps {
|
|
@@ -185,12 +171,6 @@ interface PiPButtonProps {
|
|
|
185
171
|
onClick: () => void;
|
|
186
172
|
isPiP?: boolean;
|
|
187
173
|
}
|
|
188
|
-
/**
|
|
189
|
-
* All button components are wrapped in React.memo.
|
|
190
|
-
* They receive stable callback refs (useCallback in Controls), so they
|
|
191
|
-
* only re-render when their specific props (isFullscreen, isPiP, etc.)
|
|
192
|
-
* actually change – not on every timeupdate tick.
|
|
193
|
-
*/
|
|
194
174
|
declare const PlayButton: react.NamedExoticComponent<PlayButtonProps>;
|
|
195
175
|
declare const PauseButton: react.NamedExoticComponent<PauseButtonProps>;
|
|
196
176
|
declare const FullscreenButton: react.NamedExoticComponent<FullscreenButtonProps>;
|
|
@@ -241,4 +221,35 @@ declare function isHLSUrl(url: string): boolean;
|
|
|
241
221
|
*/
|
|
242
222
|
declare function getMimeType(url: string): string;
|
|
243
223
|
|
|
244
|
-
|
|
224
|
+
interface ThumbnailCue {
|
|
225
|
+
start: number;
|
|
226
|
+
end: number;
|
|
227
|
+
/** Absolute URL to the sprite image */
|
|
228
|
+
url: string;
|
|
229
|
+
/** Pixel offset from the left of the sprite sheet */
|
|
230
|
+
x: number;
|
|
231
|
+
/** Pixel offset from the top of the sprite sheet */
|
|
232
|
+
y: number;
|
|
233
|
+
/** Width of a single thumbnail cell */
|
|
234
|
+
w: number;
|
|
235
|
+
/** Height of a single thumbnail cell */
|
|
236
|
+
h: number;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Parse a WebVTT thumbnail track into an array of cues.
|
|
240
|
+
*
|
|
241
|
+
* Supports the standard sprite-sheet format:
|
|
242
|
+
* 00:00:00.000 --> 00:00:05.000
|
|
243
|
+
* https://cdn.example.com/thumbs/s0.jpg#xywh=0,0,160,90
|
|
244
|
+
*
|
|
245
|
+
* @param text Raw VTT file text
|
|
246
|
+
* @param baseUrl VTT file URL — used to resolve relative image paths
|
|
247
|
+
*/
|
|
248
|
+
declare function parseThumbnailVtt(text: string, baseUrl?: string): ThumbnailCue[];
|
|
249
|
+
/**
|
|
250
|
+
* Binary-search for the cue that covers `time` (seconds).
|
|
251
|
+
* Returns null if no cue covers that timestamp.
|
|
252
|
+
*/
|
|
253
|
+
declare function findThumbnailCue(cues: ThumbnailCue[], time: number): ThumbnailCue | null;
|
|
254
|
+
|
|
255
|
+
export { type BufferedRange, index as ControlElements, Controls, type HLSQualityLevel, type PlaybackRate, type PlayerState, type SubtitleTrack, type ThumbnailCue, type VideoError, type VideoErrorCode, VideoPlayer, type VideoPlayerProps, type VideoPlayerRef, findThumbnailCue, formatTime, getMimeType, isHLSUrl, parseThumbnailVtt };
|
package/dist/index.d.ts
CHANGED
|
@@ -11,12 +11,12 @@ interface VideoError {
|
|
|
11
11
|
code: VideoErrorCode;
|
|
12
12
|
message: string;
|
|
13
13
|
}
|
|
14
|
+
/** Display name e.g. "1080p", "720p", "Auto" */
|
|
14
15
|
interface HLSQualityLevel {
|
|
15
16
|
id: number;
|
|
16
17
|
height: number;
|
|
17
18
|
width: number;
|
|
18
19
|
bitrate: number;
|
|
19
|
-
/** Display name e.g. "1080p", "720p", "Auto" */
|
|
20
20
|
name: string;
|
|
21
21
|
}
|
|
22
22
|
interface SubtitleTrack {
|
|
@@ -38,11 +38,8 @@ interface PlayerState {
|
|
|
38
38
|
error: VideoError | null;
|
|
39
39
|
isFullscreen: boolean;
|
|
40
40
|
isPictureInPicture: boolean;
|
|
41
|
-
/** True when the stream is a live HLS stream (Infinity duration) */
|
|
42
41
|
isLive: boolean;
|
|
43
|
-
/** Available HLS quality levels; empty for non-HLS sources */
|
|
44
42
|
qualityLevels: HLSQualityLevel[];
|
|
45
|
-
/** Currently active quality level id; -1 = ABR auto */
|
|
46
43
|
currentQualityLevel: number;
|
|
47
44
|
}
|
|
48
45
|
type PlaybackRate = 0.25 | 0.5 | 0.75 | 1 | 1.25 | 1.5 | 1.75 | 2;
|
|
@@ -51,12 +48,9 @@ interface VideoPlayerRef {
|
|
|
51
48
|
pause: () => void;
|
|
52
49
|
seek: (time: number) => void;
|
|
53
50
|
setVolume: (volume: number) => void;
|
|
54
|
-
/** Toggle mute while remembering the pre-mute volume */
|
|
55
51
|
toggleMute: () => void;
|
|
56
52
|
setPlaybackRate: (rate: PlaybackRate) => void;
|
|
57
|
-
/** Set HLS quality level; pass -1 for automatic ABR */
|
|
58
53
|
setQualityLevel: (level: number) => void;
|
|
59
|
-
/** Jump to the live edge of an HLS live stream */
|
|
60
54
|
seekToLive: () => void;
|
|
61
55
|
toggleFullscreen: () => Promise<void>;
|
|
62
56
|
togglePictureInPicture: () => Promise<void>;
|
|
@@ -75,11 +69,26 @@ interface VideoPlayerProps {
|
|
|
75
69
|
className?: string;
|
|
76
70
|
enableHLS?: boolean;
|
|
77
71
|
enablePreview?: boolean;
|
|
78
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* URL to a WebVTT thumbnail track for sprite-sheet preview on the progress bar.
|
|
74
|
+
*
|
|
75
|
+
* The VTT file should map time ranges to sprite-sheet coordinates using the
|
|
76
|
+
* standard `#xywh=x,y,w,h` fragment format:
|
|
77
|
+
*
|
|
78
|
+
* ```
|
|
79
|
+
* WEBVTT
|
|
80
|
+
*
|
|
81
|
+
* 00:00:00.000 --> 00:00:05.000
|
|
82
|
+
* https://cdn.example.com/thumbs/storyboard0.jpg#xywh=0,0,160,90
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* When provided, hovering the progress bar shows a thumbnail instead of
|
|
86
|
+
* requiring a second video decode. If omitted, only the timestamp tooltip
|
|
87
|
+
* is shown.
|
|
88
|
+
*/
|
|
89
|
+
thumbnailVtt?: string;
|
|
79
90
|
hlsConfig?: Partial<HlsConfig>;
|
|
80
|
-
/** Subtitle / caption tracks */
|
|
81
91
|
subtitles?: SubtitleTrack[];
|
|
82
|
-
/** crossorigin attribute for CORS-enabled preview thumbnails */
|
|
83
92
|
crossOrigin?: "anonymous" | "use-credentials";
|
|
84
93
|
onPlay?: () => void;
|
|
85
94
|
onPause?: () => void;
|
|
@@ -93,14 +102,13 @@ interface VideoPlayerProps {
|
|
|
93
102
|
declare const VideoPlayer: react__default.ForwardRefExoticComponent<VideoPlayerProps & react__default.RefAttributes<VideoPlayerRef>>;
|
|
94
103
|
|
|
95
104
|
interface ControlsProps {
|
|
105
|
+
videoRef: react__default.RefObject<HTMLVideoElement | null>;
|
|
96
106
|
playerRef: VideoPlayerRef;
|
|
97
|
-
/** Ref to the outer player container; used to scope keyboard shortcuts to the focused player */
|
|
98
107
|
playerContainerRef: react__default.RefObject<HTMLElement | null>;
|
|
99
108
|
playbackRates: PlaybackRate[];
|
|
100
109
|
enablePreview: boolean;
|
|
110
|
+
thumbnailVtt?: string;
|
|
101
111
|
isPlaying: boolean;
|
|
102
|
-
currentTime: number;
|
|
103
|
-
duration: number;
|
|
104
112
|
volume: number;
|
|
105
113
|
isMuted: boolean;
|
|
106
114
|
playbackRate: number;
|
|
@@ -109,25 +117,17 @@ interface ControlsProps {
|
|
|
109
117
|
isLive: boolean;
|
|
110
118
|
qualityLevels: HLSQualityLevel[];
|
|
111
119
|
currentQualityLevel: number;
|
|
112
|
-
bufferedRanges: BufferedRange[];
|
|
113
120
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Controls is intentionally NOT wrapped in React.memo here – it receives
|
|
116
|
-
* currentTime which changes every tick, so memo wouldn't help at this level.
|
|
117
|
-
* Instead, all its CHILDREN are memoized so they skip renders when their
|
|
118
|
-
* specific props haven't changed.
|
|
119
|
-
*/
|
|
120
121
|
declare const Controls: react__default.FC<ControlsProps>;
|
|
121
122
|
|
|
122
123
|
interface TimeDisplayProps {
|
|
123
|
-
|
|
124
|
-
duration: number;
|
|
124
|
+
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
125
125
|
isLive?: boolean;
|
|
126
126
|
}
|
|
127
127
|
/**
|
|
128
|
-
* TimeDisplay
|
|
129
|
-
*
|
|
130
|
-
*
|
|
128
|
+
* TimeDisplay subscribes directly to the video element's timeupdate and
|
|
129
|
+
* durationchange events, updating the DOM via refs. It never re-renders
|
|
130
|
+
* during playback — only when isLive changes (once per source change).
|
|
131
131
|
*/
|
|
132
132
|
declare const TimeDisplay: react.NamedExoticComponent<TimeDisplayProps>;
|
|
133
133
|
|
|
@@ -139,22 +139,13 @@ interface SettingsMenuProps {
|
|
|
139
139
|
currentQualityLevel?: number;
|
|
140
140
|
onQualityChange?: (level: number) => void;
|
|
141
141
|
}
|
|
142
|
-
/**
|
|
143
|
-
* SettingsMenu wrapped in React.memo.
|
|
144
|
-
* playbackRate and qualityLevel rarely change → this component skips
|
|
145
|
-
* almost all re-renders during normal playback.
|
|
146
|
-
*
|
|
147
|
-
* sortedLevels is memoized so the .sort() only runs when qualityLevels
|
|
148
|
-
* actually changes (usually once after manifest is parsed).
|
|
149
|
-
*/
|
|
150
142
|
declare const SettingsMenu: react.NamedExoticComponent<SettingsMenuProps>;
|
|
151
143
|
|
|
152
144
|
interface ProgressBarProps {
|
|
145
|
+
videoRef: react__default.RefObject<HTMLVideoElement | null>;
|
|
153
146
|
playerRef: VideoPlayerRef;
|
|
154
|
-
currentTime: number;
|
|
155
|
-
duration: number;
|
|
156
|
-
bufferedRanges: BufferedRange[];
|
|
157
147
|
enablePreview?: boolean;
|
|
148
|
+
thumbnailVtt?: string;
|
|
158
149
|
}
|
|
159
150
|
declare const ProgressBar: react__default.FC<ProgressBarProps>;
|
|
160
151
|
|
|
@@ -164,11 +155,6 @@ interface VolumeControlProps {
|
|
|
164
155
|
onVolumeChange: (volume: number) => void;
|
|
165
156
|
onToggleMute: () => void;
|
|
166
157
|
}
|
|
167
|
-
/**
|
|
168
|
-
* VolumeControl wrapped in React.memo.
|
|
169
|
-
* volume and isMuted don't change during timeupdate → 0 re-renders during
|
|
170
|
-
* normal playback when the user isn't touching volume controls.
|
|
171
|
-
*/
|
|
172
158
|
declare const VolumeControl: react.NamedExoticComponent<VolumeControlProps>;
|
|
173
159
|
|
|
174
160
|
interface PlayButtonProps {
|
|
@@ -185,12 +171,6 @@ interface PiPButtonProps {
|
|
|
185
171
|
onClick: () => void;
|
|
186
172
|
isPiP?: boolean;
|
|
187
173
|
}
|
|
188
|
-
/**
|
|
189
|
-
* All button components are wrapped in React.memo.
|
|
190
|
-
* They receive stable callback refs (useCallback in Controls), so they
|
|
191
|
-
* only re-render when their specific props (isFullscreen, isPiP, etc.)
|
|
192
|
-
* actually change – not on every timeupdate tick.
|
|
193
|
-
*/
|
|
194
174
|
declare const PlayButton: react.NamedExoticComponent<PlayButtonProps>;
|
|
195
175
|
declare const PauseButton: react.NamedExoticComponent<PauseButtonProps>;
|
|
196
176
|
declare const FullscreenButton: react.NamedExoticComponent<FullscreenButtonProps>;
|
|
@@ -241,4 +221,35 @@ declare function isHLSUrl(url: string): boolean;
|
|
|
241
221
|
*/
|
|
242
222
|
declare function getMimeType(url: string): string;
|
|
243
223
|
|
|
244
|
-
|
|
224
|
+
interface ThumbnailCue {
|
|
225
|
+
start: number;
|
|
226
|
+
end: number;
|
|
227
|
+
/** Absolute URL to the sprite image */
|
|
228
|
+
url: string;
|
|
229
|
+
/** Pixel offset from the left of the sprite sheet */
|
|
230
|
+
x: number;
|
|
231
|
+
/** Pixel offset from the top of the sprite sheet */
|
|
232
|
+
y: number;
|
|
233
|
+
/** Width of a single thumbnail cell */
|
|
234
|
+
w: number;
|
|
235
|
+
/** Height of a single thumbnail cell */
|
|
236
|
+
h: number;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Parse a WebVTT thumbnail track into an array of cues.
|
|
240
|
+
*
|
|
241
|
+
* Supports the standard sprite-sheet format:
|
|
242
|
+
* 00:00:00.000 --> 00:00:05.000
|
|
243
|
+
* https://cdn.example.com/thumbs/s0.jpg#xywh=0,0,160,90
|
|
244
|
+
*
|
|
245
|
+
* @param text Raw VTT file text
|
|
246
|
+
* @param baseUrl VTT file URL — used to resolve relative image paths
|
|
247
|
+
*/
|
|
248
|
+
declare function parseThumbnailVtt(text: string, baseUrl?: string): ThumbnailCue[];
|
|
249
|
+
/**
|
|
250
|
+
* Binary-search for the cue that covers `time` (seconds).
|
|
251
|
+
* Returns null if no cue covers that timestamp.
|
|
252
|
+
*/
|
|
253
|
+
declare function findThumbnailCue(cues: ThumbnailCue[], time: number): ThumbnailCue | null;
|
|
254
|
+
|
|
255
|
+
export { type BufferedRange, index as ControlElements, Controls, type HLSQualityLevel, type PlaybackRate, type PlayerState, type SubtitleTrack, type ThumbnailCue, type VideoError, type VideoErrorCode, VideoPlayer, type VideoPlayerProps, type VideoPlayerRef, findThumbnailCue, formatTime, getMimeType, isHLSUrl, parseThumbnailVtt };
|