react-helios 2.0.1 → 2.1.1
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 +68 -4
- package/dist/index.d.mts +72 -49
- package/dist/index.d.ts +72 -49
- 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 +61 -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,6 +1,6 @@
|
|
|
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
|
|
|
@@ -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 |
|
|
@@ -165,7 +202,11 @@ import type {
|
|
|
165
202
|
BufferedRange,
|
|
166
203
|
VideoError,
|
|
167
204
|
VideoErrorCode,
|
|
205
|
+
ThumbnailCue,
|
|
168
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,9 @@ interface PlayerState {
|
|
|
38
38
|
error: VideoError | null;
|
|
39
39
|
isFullscreen: boolean;
|
|
40
40
|
isPictureInPicture: boolean;
|
|
41
|
-
|
|
41
|
+
isTheaterMode: boolean;
|
|
42
42
|
isLive: boolean;
|
|
43
|
-
/** Available HLS quality levels; empty for non-HLS sources */
|
|
44
43
|
qualityLevels: HLSQualityLevel[];
|
|
45
|
-
/** Currently active quality level id; -1 = ABR auto */
|
|
46
44
|
currentQualityLevel: number;
|
|
47
45
|
}
|
|
48
46
|
type PlaybackRate = 0.25 | 0.5 | 0.75 | 1 | 1.25 | 1.5 | 1.75 | 2;
|
|
@@ -51,15 +49,13 @@ interface VideoPlayerRef {
|
|
|
51
49
|
pause: () => void;
|
|
52
50
|
seek: (time: number) => void;
|
|
53
51
|
setVolume: (volume: number) => void;
|
|
54
|
-
/** Toggle mute while remembering the pre-mute volume */
|
|
55
52
|
toggleMute: () => void;
|
|
56
53
|
setPlaybackRate: (rate: PlaybackRate) => void;
|
|
57
|
-
/** Set HLS quality level; pass -1 for automatic ABR */
|
|
58
54
|
setQualityLevel: (level: number) => void;
|
|
59
|
-
/** Jump to the live edge of an HLS live stream */
|
|
60
55
|
seekToLive: () => void;
|
|
61
56
|
toggleFullscreen: () => Promise<void>;
|
|
62
57
|
togglePictureInPicture: () => Promise<void>;
|
|
58
|
+
toggleTheaterMode: () => void;
|
|
63
59
|
getState: () => PlayerState;
|
|
64
60
|
getVideoElement: () => HTMLVideoElement | null;
|
|
65
61
|
}
|
|
@@ -75,11 +71,26 @@ interface VideoPlayerProps {
|
|
|
75
71
|
className?: string;
|
|
76
72
|
enableHLS?: boolean;
|
|
77
73
|
enablePreview?: boolean;
|
|
78
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* URL to a WebVTT thumbnail track for sprite-sheet preview on the progress bar.
|
|
76
|
+
*
|
|
77
|
+
* The VTT file should map time ranges to sprite-sheet coordinates using the
|
|
78
|
+
* standard `#xywh=x,y,w,h` fragment format:
|
|
79
|
+
*
|
|
80
|
+
* ```
|
|
81
|
+
* WEBVTT
|
|
82
|
+
*
|
|
83
|
+
* 00:00:00.000 --> 00:00:05.000
|
|
84
|
+
* https://cdn.example.com/thumbs/storyboard0.jpg#xywh=0,0,160,90
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* When provided, hovering the progress bar shows a thumbnail instead of
|
|
88
|
+
* requiring a second video decode. If omitted, only the timestamp tooltip
|
|
89
|
+
* is shown.
|
|
90
|
+
*/
|
|
91
|
+
thumbnailVtt?: string;
|
|
79
92
|
hlsConfig?: Partial<HlsConfig>;
|
|
80
|
-
/** Subtitle / caption tracks */
|
|
81
93
|
subtitles?: SubtitleTrack[];
|
|
82
|
-
/** crossorigin attribute for CORS-enabled preview thumbnails */
|
|
83
94
|
crossOrigin?: "anonymous" | "use-credentials";
|
|
84
95
|
onPlay?: () => void;
|
|
85
96
|
onPause?: () => void;
|
|
@@ -88,46 +99,39 @@ interface VideoPlayerProps {
|
|
|
88
99
|
onTimeUpdate?: (time: number) => void;
|
|
89
100
|
onDurationChange?: (duration: number) => void;
|
|
90
101
|
onBuffering?: (isBuffering: boolean) => void;
|
|
102
|
+
onTheaterModeChange?: (isTheater: boolean) => void;
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
declare const VideoPlayer: react__default.ForwardRefExoticComponent<VideoPlayerProps & react__default.RefAttributes<VideoPlayerRef>>;
|
|
94
106
|
|
|
95
107
|
interface ControlsProps {
|
|
108
|
+
videoRef: react__default.RefObject<HTMLVideoElement | null>;
|
|
96
109
|
playerRef: VideoPlayerRef;
|
|
97
|
-
/** Ref to the outer player container; used to scope keyboard shortcuts to the focused player */
|
|
98
110
|
playerContainerRef: react__default.RefObject<HTMLElement | null>;
|
|
99
111
|
playbackRates: PlaybackRate[];
|
|
100
112
|
enablePreview: boolean;
|
|
113
|
+
thumbnailVtt?: string;
|
|
101
114
|
isPlaying: boolean;
|
|
102
|
-
currentTime: number;
|
|
103
|
-
duration: number;
|
|
104
115
|
volume: number;
|
|
105
116
|
isMuted: boolean;
|
|
106
117
|
playbackRate: number;
|
|
107
118
|
isFullscreen: boolean;
|
|
108
119
|
isPictureInPicture: boolean;
|
|
120
|
+
isTheaterMode: boolean;
|
|
109
121
|
isLive: boolean;
|
|
110
122
|
qualityLevels: HLSQualityLevel[];
|
|
111
123
|
currentQualityLevel: number;
|
|
112
|
-
bufferedRanges: BufferedRange[];
|
|
113
124
|
}
|
|
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
125
|
declare const Controls: react__default.FC<ControlsProps>;
|
|
121
126
|
|
|
122
127
|
interface TimeDisplayProps {
|
|
123
|
-
|
|
124
|
-
duration: number;
|
|
128
|
+
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
125
129
|
isLive?: boolean;
|
|
126
130
|
}
|
|
127
131
|
/**
|
|
128
|
-
* TimeDisplay
|
|
129
|
-
*
|
|
130
|
-
*
|
|
132
|
+
* TimeDisplay subscribes directly to the video element's timeupdate and
|
|
133
|
+
* durationchange events, updating the DOM via refs. It never re-renders
|
|
134
|
+
* during playback — only when isLive changes (once per source change).
|
|
131
135
|
*/
|
|
132
136
|
declare const TimeDisplay: react.NamedExoticComponent<TimeDisplayProps>;
|
|
133
137
|
|
|
@@ -139,22 +143,13 @@ interface SettingsMenuProps {
|
|
|
139
143
|
currentQualityLevel?: number;
|
|
140
144
|
onQualityChange?: (level: number) => void;
|
|
141
145
|
}
|
|
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
146
|
declare const SettingsMenu: react.NamedExoticComponent<SettingsMenuProps>;
|
|
151
147
|
|
|
152
148
|
interface ProgressBarProps {
|
|
149
|
+
videoRef: react__default.RefObject<HTMLVideoElement | null>;
|
|
153
150
|
playerRef: VideoPlayerRef;
|
|
154
|
-
currentTime: number;
|
|
155
|
-
duration: number;
|
|
156
|
-
bufferedRanges: BufferedRange[];
|
|
157
151
|
enablePreview?: boolean;
|
|
152
|
+
thumbnailVtt?: string;
|
|
158
153
|
}
|
|
159
154
|
declare const ProgressBar: react__default.FC<ProgressBarProps>;
|
|
160
155
|
|
|
@@ -164,11 +159,6 @@ interface VolumeControlProps {
|
|
|
164
159
|
onVolumeChange: (volume: number) => void;
|
|
165
160
|
onToggleMute: () => void;
|
|
166
161
|
}
|
|
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
162
|
declare const VolumeControl: react.NamedExoticComponent<VolumeControlProps>;
|
|
173
163
|
|
|
174
164
|
interface PlayButtonProps {
|
|
@@ -185,22 +175,22 @@ interface PiPButtonProps {
|
|
|
185
175
|
onClick: () => void;
|
|
186
176
|
isPiP?: boolean;
|
|
187
177
|
}
|
|
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
178
|
declare const PlayButton: react.NamedExoticComponent<PlayButtonProps>;
|
|
195
179
|
declare const PauseButton: react.NamedExoticComponent<PauseButtonProps>;
|
|
196
180
|
declare const FullscreenButton: react.NamedExoticComponent<FullscreenButtonProps>;
|
|
197
181
|
declare const PiPButton: react.NamedExoticComponent<PiPButtonProps>;
|
|
182
|
+
interface TheaterButtonProps {
|
|
183
|
+
onClick: () => void;
|
|
184
|
+
isTheater?: boolean;
|
|
185
|
+
}
|
|
186
|
+
declare const TheaterButton: react.NamedExoticComponent<TheaterButtonProps>;
|
|
198
187
|
|
|
199
188
|
declare const ControlElements: {
|
|
200
189
|
PlayButton: react.NamedExoticComponent<PlayButtonProps>;
|
|
201
190
|
PauseButton: react.NamedExoticComponent<PauseButtonProps>;
|
|
202
191
|
FullscreenButton: react.NamedExoticComponent<FullscreenButtonProps>;
|
|
203
192
|
PiPButton: react.NamedExoticComponent<PiPButtonProps>;
|
|
193
|
+
TheaterButton: react.NamedExoticComponent<TheaterButtonProps>;
|
|
204
194
|
VolumeControl: react.NamedExoticComponent<VolumeControlProps>;
|
|
205
195
|
ProgressBar: react.FC<ProgressBarProps>;
|
|
206
196
|
SettingsMenu: react.NamedExoticComponent<SettingsMenuProps>;
|
|
@@ -220,12 +210,14 @@ declare const index_ProgressBar: typeof ProgressBar;
|
|
|
220
210
|
type index_ProgressBarProps = ProgressBarProps;
|
|
221
211
|
declare const index_SettingsMenu: typeof SettingsMenu;
|
|
222
212
|
type index_SettingsMenuProps = SettingsMenuProps;
|
|
213
|
+
declare const index_TheaterButton: typeof TheaterButton;
|
|
214
|
+
type index_TheaterButtonProps = TheaterButtonProps;
|
|
223
215
|
declare const index_TimeDisplay: typeof TimeDisplay;
|
|
224
216
|
type index_TimeDisplayProps = TimeDisplayProps;
|
|
225
217
|
declare const index_VolumeControl: typeof VolumeControl;
|
|
226
218
|
type index_VolumeControlProps = VolumeControlProps;
|
|
227
219
|
declare namespace index {
|
|
228
|
-
export { index_ControlElements as ControlElements, index_FullscreenButton as FullscreenButton, type index_FullscreenButtonProps as FullscreenButtonProps, index_PauseButton as PauseButton, type index_PauseButtonProps as PauseButtonProps, index_PiPButton as PiPButton, type index_PiPButtonProps as PiPButtonProps, index_PlayButton as PlayButton, type index_PlayButtonProps as PlayButtonProps, index_ProgressBar as ProgressBar, type index_ProgressBarProps as ProgressBarProps, index_SettingsMenu as SettingsMenu, type index_SettingsMenuProps as SettingsMenuProps, index_TimeDisplay as TimeDisplay, type index_TimeDisplayProps as TimeDisplayProps, index_VolumeControl as VolumeControl, type index_VolumeControlProps as VolumeControlProps };
|
|
220
|
+
export { index_ControlElements as ControlElements, index_FullscreenButton as FullscreenButton, type index_FullscreenButtonProps as FullscreenButtonProps, index_PauseButton as PauseButton, type index_PauseButtonProps as PauseButtonProps, index_PiPButton as PiPButton, type index_PiPButtonProps as PiPButtonProps, index_PlayButton as PlayButton, type index_PlayButtonProps as PlayButtonProps, index_ProgressBar as ProgressBar, type index_ProgressBarProps as ProgressBarProps, index_SettingsMenu as SettingsMenu, type index_SettingsMenuProps as SettingsMenuProps, index_TheaterButton as TheaterButton, type index_TheaterButtonProps as TheaterButtonProps, index_TimeDisplay as TimeDisplay, type index_TimeDisplayProps as TimeDisplayProps, index_VolumeControl as VolumeControl, type index_VolumeControlProps as VolumeControlProps };
|
|
229
221
|
}
|
|
230
222
|
|
|
231
223
|
/**
|
|
@@ -241,4 +233,35 @@ declare function isHLSUrl(url: string): boolean;
|
|
|
241
233
|
*/
|
|
242
234
|
declare function getMimeType(url: string): string;
|
|
243
235
|
|
|
244
|
-
|
|
236
|
+
interface ThumbnailCue {
|
|
237
|
+
start: number;
|
|
238
|
+
end: number;
|
|
239
|
+
/** Absolute URL to the sprite image */
|
|
240
|
+
url: string;
|
|
241
|
+
/** Pixel offset from the left of the sprite sheet */
|
|
242
|
+
x: number;
|
|
243
|
+
/** Pixel offset from the top of the sprite sheet */
|
|
244
|
+
y: number;
|
|
245
|
+
/** Width of a single thumbnail cell */
|
|
246
|
+
w: number;
|
|
247
|
+
/** Height of a single thumbnail cell */
|
|
248
|
+
h: number;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Parse a WebVTT thumbnail track into an array of cues.
|
|
252
|
+
*
|
|
253
|
+
* Supports the standard sprite-sheet format:
|
|
254
|
+
* 00:00:00.000 --> 00:00:05.000
|
|
255
|
+
* https://cdn.example.com/thumbs/s0.jpg#xywh=0,0,160,90
|
|
256
|
+
*
|
|
257
|
+
* @param text Raw VTT file text
|
|
258
|
+
* @param baseUrl VTT file URL — used to resolve relative image paths
|
|
259
|
+
*/
|
|
260
|
+
declare function parseThumbnailVtt(text: string, baseUrl?: string): ThumbnailCue[];
|
|
261
|
+
/**
|
|
262
|
+
* Binary-search for the cue that covers `time` (seconds).
|
|
263
|
+
* Returns null if no cue covers that timestamp.
|
|
264
|
+
*/
|
|
265
|
+
declare function findThumbnailCue(cues: ThumbnailCue[], time: number): ThumbnailCue | null;
|
|
266
|
+
|
|
267
|
+
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,9 @@ interface PlayerState {
|
|
|
38
38
|
error: VideoError | null;
|
|
39
39
|
isFullscreen: boolean;
|
|
40
40
|
isPictureInPicture: boolean;
|
|
41
|
-
|
|
41
|
+
isTheaterMode: boolean;
|
|
42
42
|
isLive: boolean;
|
|
43
|
-
/** Available HLS quality levels; empty for non-HLS sources */
|
|
44
43
|
qualityLevels: HLSQualityLevel[];
|
|
45
|
-
/** Currently active quality level id; -1 = ABR auto */
|
|
46
44
|
currentQualityLevel: number;
|
|
47
45
|
}
|
|
48
46
|
type PlaybackRate = 0.25 | 0.5 | 0.75 | 1 | 1.25 | 1.5 | 1.75 | 2;
|
|
@@ -51,15 +49,13 @@ interface VideoPlayerRef {
|
|
|
51
49
|
pause: () => void;
|
|
52
50
|
seek: (time: number) => void;
|
|
53
51
|
setVolume: (volume: number) => void;
|
|
54
|
-
/** Toggle mute while remembering the pre-mute volume */
|
|
55
52
|
toggleMute: () => void;
|
|
56
53
|
setPlaybackRate: (rate: PlaybackRate) => void;
|
|
57
|
-
/** Set HLS quality level; pass -1 for automatic ABR */
|
|
58
54
|
setQualityLevel: (level: number) => void;
|
|
59
|
-
/** Jump to the live edge of an HLS live stream */
|
|
60
55
|
seekToLive: () => void;
|
|
61
56
|
toggleFullscreen: () => Promise<void>;
|
|
62
57
|
togglePictureInPicture: () => Promise<void>;
|
|
58
|
+
toggleTheaterMode: () => void;
|
|
63
59
|
getState: () => PlayerState;
|
|
64
60
|
getVideoElement: () => HTMLVideoElement | null;
|
|
65
61
|
}
|
|
@@ -75,11 +71,26 @@ interface VideoPlayerProps {
|
|
|
75
71
|
className?: string;
|
|
76
72
|
enableHLS?: boolean;
|
|
77
73
|
enablePreview?: boolean;
|
|
78
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* URL to a WebVTT thumbnail track for sprite-sheet preview on the progress bar.
|
|
76
|
+
*
|
|
77
|
+
* The VTT file should map time ranges to sprite-sheet coordinates using the
|
|
78
|
+
* standard `#xywh=x,y,w,h` fragment format:
|
|
79
|
+
*
|
|
80
|
+
* ```
|
|
81
|
+
* WEBVTT
|
|
82
|
+
*
|
|
83
|
+
* 00:00:00.000 --> 00:00:05.000
|
|
84
|
+
* https://cdn.example.com/thumbs/storyboard0.jpg#xywh=0,0,160,90
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* When provided, hovering the progress bar shows a thumbnail instead of
|
|
88
|
+
* requiring a second video decode. If omitted, only the timestamp tooltip
|
|
89
|
+
* is shown.
|
|
90
|
+
*/
|
|
91
|
+
thumbnailVtt?: string;
|
|
79
92
|
hlsConfig?: Partial<HlsConfig>;
|
|
80
|
-
/** Subtitle / caption tracks */
|
|
81
93
|
subtitles?: SubtitleTrack[];
|
|
82
|
-
/** crossorigin attribute for CORS-enabled preview thumbnails */
|
|
83
94
|
crossOrigin?: "anonymous" | "use-credentials";
|
|
84
95
|
onPlay?: () => void;
|
|
85
96
|
onPause?: () => void;
|
|
@@ -88,46 +99,39 @@ interface VideoPlayerProps {
|
|
|
88
99
|
onTimeUpdate?: (time: number) => void;
|
|
89
100
|
onDurationChange?: (duration: number) => void;
|
|
90
101
|
onBuffering?: (isBuffering: boolean) => void;
|
|
102
|
+
onTheaterModeChange?: (isTheater: boolean) => void;
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
declare const VideoPlayer: react__default.ForwardRefExoticComponent<VideoPlayerProps & react__default.RefAttributes<VideoPlayerRef>>;
|
|
94
106
|
|
|
95
107
|
interface ControlsProps {
|
|
108
|
+
videoRef: react__default.RefObject<HTMLVideoElement | null>;
|
|
96
109
|
playerRef: VideoPlayerRef;
|
|
97
|
-
/** Ref to the outer player container; used to scope keyboard shortcuts to the focused player */
|
|
98
110
|
playerContainerRef: react__default.RefObject<HTMLElement | null>;
|
|
99
111
|
playbackRates: PlaybackRate[];
|
|
100
112
|
enablePreview: boolean;
|
|
113
|
+
thumbnailVtt?: string;
|
|
101
114
|
isPlaying: boolean;
|
|
102
|
-
currentTime: number;
|
|
103
|
-
duration: number;
|
|
104
115
|
volume: number;
|
|
105
116
|
isMuted: boolean;
|
|
106
117
|
playbackRate: number;
|
|
107
118
|
isFullscreen: boolean;
|
|
108
119
|
isPictureInPicture: boolean;
|
|
120
|
+
isTheaterMode: boolean;
|
|
109
121
|
isLive: boolean;
|
|
110
122
|
qualityLevels: HLSQualityLevel[];
|
|
111
123
|
currentQualityLevel: number;
|
|
112
|
-
bufferedRanges: BufferedRange[];
|
|
113
124
|
}
|
|
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
125
|
declare const Controls: react__default.FC<ControlsProps>;
|
|
121
126
|
|
|
122
127
|
interface TimeDisplayProps {
|
|
123
|
-
|
|
124
|
-
duration: number;
|
|
128
|
+
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
125
129
|
isLive?: boolean;
|
|
126
130
|
}
|
|
127
131
|
/**
|
|
128
|
-
* TimeDisplay
|
|
129
|
-
*
|
|
130
|
-
*
|
|
132
|
+
* TimeDisplay subscribes directly to the video element's timeupdate and
|
|
133
|
+
* durationchange events, updating the DOM via refs. It never re-renders
|
|
134
|
+
* during playback — only when isLive changes (once per source change).
|
|
131
135
|
*/
|
|
132
136
|
declare const TimeDisplay: react.NamedExoticComponent<TimeDisplayProps>;
|
|
133
137
|
|
|
@@ -139,22 +143,13 @@ interface SettingsMenuProps {
|
|
|
139
143
|
currentQualityLevel?: number;
|
|
140
144
|
onQualityChange?: (level: number) => void;
|
|
141
145
|
}
|
|
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
146
|
declare const SettingsMenu: react.NamedExoticComponent<SettingsMenuProps>;
|
|
151
147
|
|
|
152
148
|
interface ProgressBarProps {
|
|
149
|
+
videoRef: react__default.RefObject<HTMLVideoElement | null>;
|
|
153
150
|
playerRef: VideoPlayerRef;
|
|
154
|
-
currentTime: number;
|
|
155
|
-
duration: number;
|
|
156
|
-
bufferedRanges: BufferedRange[];
|
|
157
151
|
enablePreview?: boolean;
|
|
152
|
+
thumbnailVtt?: string;
|
|
158
153
|
}
|
|
159
154
|
declare const ProgressBar: react__default.FC<ProgressBarProps>;
|
|
160
155
|
|
|
@@ -164,11 +159,6 @@ interface VolumeControlProps {
|
|
|
164
159
|
onVolumeChange: (volume: number) => void;
|
|
165
160
|
onToggleMute: () => void;
|
|
166
161
|
}
|
|
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
162
|
declare const VolumeControl: react.NamedExoticComponent<VolumeControlProps>;
|
|
173
163
|
|
|
174
164
|
interface PlayButtonProps {
|
|
@@ -185,22 +175,22 @@ interface PiPButtonProps {
|
|
|
185
175
|
onClick: () => void;
|
|
186
176
|
isPiP?: boolean;
|
|
187
177
|
}
|
|
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
178
|
declare const PlayButton: react.NamedExoticComponent<PlayButtonProps>;
|
|
195
179
|
declare const PauseButton: react.NamedExoticComponent<PauseButtonProps>;
|
|
196
180
|
declare const FullscreenButton: react.NamedExoticComponent<FullscreenButtonProps>;
|
|
197
181
|
declare const PiPButton: react.NamedExoticComponent<PiPButtonProps>;
|
|
182
|
+
interface TheaterButtonProps {
|
|
183
|
+
onClick: () => void;
|
|
184
|
+
isTheater?: boolean;
|
|
185
|
+
}
|
|
186
|
+
declare const TheaterButton: react.NamedExoticComponent<TheaterButtonProps>;
|
|
198
187
|
|
|
199
188
|
declare const ControlElements: {
|
|
200
189
|
PlayButton: react.NamedExoticComponent<PlayButtonProps>;
|
|
201
190
|
PauseButton: react.NamedExoticComponent<PauseButtonProps>;
|
|
202
191
|
FullscreenButton: react.NamedExoticComponent<FullscreenButtonProps>;
|
|
203
192
|
PiPButton: react.NamedExoticComponent<PiPButtonProps>;
|
|
193
|
+
TheaterButton: react.NamedExoticComponent<TheaterButtonProps>;
|
|
204
194
|
VolumeControl: react.NamedExoticComponent<VolumeControlProps>;
|
|
205
195
|
ProgressBar: react.FC<ProgressBarProps>;
|
|
206
196
|
SettingsMenu: react.NamedExoticComponent<SettingsMenuProps>;
|
|
@@ -220,12 +210,14 @@ declare const index_ProgressBar: typeof ProgressBar;
|
|
|
220
210
|
type index_ProgressBarProps = ProgressBarProps;
|
|
221
211
|
declare const index_SettingsMenu: typeof SettingsMenu;
|
|
222
212
|
type index_SettingsMenuProps = SettingsMenuProps;
|
|
213
|
+
declare const index_TheaterButton: typeof TheaterButton;
|
|
214
|
+
type index_TheaterButtonProps = TheaterButtonProps;
|
|
223
215
|
declare const index_TimeDisplay: typeof TimeDisplay;
|
|
224
216
|
type index_TimeDisplayProps = TimeDisplayProps;
|
|
225
217
|
declare const index_VolumeControl: typeof VolumeControl;
|
|
226
218
|
type index_VolumeControlProps = VolumeControlProps;
|
|
227
219
|
declare namespace index {
|
|
228
|
-
export { index_ControlElements as ControlElements, index_FullscreenButton as FullscreenButton, type index_FullscreenButtonProps as FullscreenButtonProps, index_PauseButton as PauseButton, type index_PauseButtonProps as PauseButtonProps, index_PiPButton as PiPButton, type index_PiPButtonProps as PiPButtonProps, index_PlayButton as PlayButton, type index_PlayButtonProps as PlayButtonProps, index_ProgressBar as ProgressBar, type index_ProgressBarProps as ProgressBarProps, index_SettingsMenu as SettingsMenu, type index_SettingsMenuProps as SettingsMenuProps, index_TimeDisplay as TimeDisplay, type index_TimeDisplayProps as TimeDisplayProps, index_VolumeControl as VolumeControl, type index_VolumeControlProps as VolumeControlProps };
|
|
220
|
+
export { index_ControlElements as ControlElements, index_FullscreenButton as FullscreenButton, type index_FullscreenButtonProps as FullscreenButtonProps, index_PauseButton as PauseButton, type index_PauseButtonProps as PauseButtonProps, index_PiPButton as PiPButton, type index_PiPButtonProps as PiPButtonProps, index_PlayButton as PlayButton, type index_PlayButtonProps as PlayButtonProps, index_ProgressBar as ProgressBar, type index_ProgressBarProps as ProgressBarProps, index_SettingsMenu as SettingsMenu, type index_SettingsMenuProps as SettingsMenuProps, index_TheaterButton as TheaterButton, type index_TheaterButtonProps as TheaterButtonProps, index_TimeDisplay as TimeDisplay, type index_TimeDisplayProps as TimeDisplayProps, index_VolumeControl as VolumeControl, type index_VolumeControlProps as VolumeControlProps };
|
|
229
221
|
}
|
|
230
222
|
|
|
231
223
|
/**
|
|
@@ -241,4 +233,35 @@ declare function isHLSUrl(url: string): boolean;
|
|
|
241
233
|
*/
|
|
242
234
|
declare function getMimeType(url: string): string;
|
|
243
235
|
|
|
244
|
-
|
|
236
|
+
interface ThumbnailCue {
|
|
237
|
+
start: number;
|
|
238
|
+
end: number;
|
|
239
|
+
/** Absolute URL to the sprite image */
|
|
240
|
+
url: string;
|
|
241
|
+
/** Pixel offset from the left of the sprite sheet */
|
|
242
|
+
x: number;
|
|
243
|
+
/** Pixel offset from the top of the sprite sheet */
|
|
244
|
+
y: number;
|
|
245
|
+
/** Width of a single thumbnail cell */
|
|
246
|
+
w: number;
|
|
247
|
+
/** Height of a single thumbnail cell */
|
|
248
|
+
h: number;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Parse a WebVTT thumbnail track into an array of cues.
|
|
252
|
+
*
|
|
253
|
+
* Supports the standard sprite-sheet format:
|
|
254
|
+
* 00:00:00.000 --> 00:00:05.000
|
|
255
|
+
* https://cdn.example.com/thumbs/s0.jpg#xywh=0,0,160,90
|
|
256
|
+
*
|
|
257
|
+
* @param text Raw VTT file text
|
|
258
|
+
* @param baseUrl VTT file URL — used to resolve relative image paths
|
|
259
|
+
*/
|
|
260
|
+
declare function parseThumbnailVtt(text: string, baseUrl?: string): ThumbnailCue[];
|
|
261
|
+
/**
|
|
262
|
+
* Binary-search for the cue that covers `time` (seconds).
|
|
263
|
+
* Returns null if no cue covers that timestamp.
|
|
264
|
+
*/
|
|
265
|
+
declare function findThumbnailCue(cues: ThumbnailCue[], time: number): ThumbnailCue | null;
|
|
266
|
+
|
|
267
|
+
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 };
|