react-helios 2.0.1 → 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 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 preview on progress bar hover (disabled automatically for HLS) |
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-video-player/
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
- /** Additional hls.js configuration options */
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
- currentTime: number;
124
- duration: number;
124
+ videoRef: React.RefObject<HTMLVideoElement | null>;
125
125
  isLive?: boolean;
126
126
  }
127
127
  /**
128
- * TimeDisplay re-renders every timeupdate tick (currentTime changes).
129
- * Wrapped in memo anyway so that if its specific props haven't changed
130
- * (e.g. when only volume changes) it skips the render.
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
- export { type BufferedRange, index as ControlElements, Controls, type HLSQualityLevel, type PlaybackRate, type PlayerState, type SubtitleTrack, type VideoError, type VideoErrorCode, VideoPlayer, type VideoPlayerProps, type VideoPlayerRef, formatTime, getMimeType, isHLSUrl };
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
- /** Additional hls.js configuration options */
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
- currentTime: number;
124
- duration: number;
124
+ videoRef: React.RefObject<HTMLVideoElement | null>;
125
125
  isLive?: boolean;
126
126
  }
127
127
  /**
128
- * TimeDisplay re-renders every timeupdate tick (currentTime changes).
129
- * Wrapped in memo anyway so that if its specific props haven't changed
130
- * (e.g. when only volume changes) it skips the render.
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
- export { type BufferedRange, index as ControlElements, Controls, type HLSQualityLevel, type PlaybackRate, type PlayerState, type SubtitleTrack, type VideoError, type VideoErrorCode, VideoPlayer, type VideoPlayerProps, type VideoPlayerRef, formatTime, getMimeType, isHLSUrl };
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 };