react-helios 2.4.0 → 2.5.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 +177 -71
- package/dist/index.d.mts +34 -52
- package/dist/index.d.ts +34 -52
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +135 -0
- package/package.json +5 -3
- 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, VTT sprite sheet thumbnail preview, Picture-in-Picture, and full keyboard control.
|
|
3
|
+
Production-grade React video player with HLS streaming, audio mode, 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
|
|
|
@@ -24,8 +24,13 @@ export default function App() {
|
|
|
24
24
|
return (
|
|
25
25
|
<VideoPlayer
|
|
26
26
|
src="https://example.com/video.mp4"
|
|
27
|
+
poster="https://example.com/poster.jpg"
|
|
27
28
|
controls
|
|
28
|
-
|
|
29
|
+
options={{
|
|
30
|
+
autoplay: false,
|
|
31
|
+
loop: false,
|
|
32
|
+
thumbnailVtt: "https://example.com/thumbs/storyboard.vtt",
|
|
33
|
+
}}
|
|
29
34
|
/>
|
|
30
35
|
);
|
|
31
36
|
}
|
|
@@ -41,12 +46,64 @@ Pass any `.m3u8` URL — HLS.js is initialised automatically:
|
|
|
41
46
|
<VideoPlayer
|
|
42
47
|
src="https://example.com/stream.m3u8"
|
|
43
48
|
controls
|
|
44
|
-
|
|
49
|
+
options={{
|
|
50
|
+
enableHLS: true, // default: true
|
|
51
|
+
hlsConfig: {
|
|
52
|
+
maxBufferLength: 60,
|
|
53
|
+
capLevelToPlayerSize: true,
|
|
54
|
+
},
|
|
55
|
+
}}
|
|
45
56
|
/>
|
|
46
57
|
```
|
|
47
58
|
|
|
48
59
|
On Safari the browser's native HLS engine is used. A **LIVE** badge and **GO LIVE** button appear automatically for live streams.
|
|
49
60
|
|
|
61
|
+
## Audio Mode
|
|
62
|
+
|
|
63
|
+
Audio mode hides the video, shows the poster artwork, and keeps the audio stream playing — useful when bandwidth is poor or the user wants to background the content.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<VideoPlayer
|
|
67
|
+
src="https://example.com/stream.m3u8"
|
|
68
|
+
poster="https://example.com/artwork.jpg"
|
|
69
|
+
controls
|
|
70
|
+
options={{
|
|
71
|
+
audioSrc: "https://example.com/audio-only.m3u8",
|
|
72
|
+
audioModeLabel: "Switch to Audio",
|
|
73
|
+
videoModeLabel: "Switch to Video",
|
|
74
|
+
defaultAudioMode: false,
|
|
75
|
+
onAudioModeChange: (isAudio) => console.log("audio mode:", isAudio),
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The audio toggle button only appears in the control bar when `audioSrc` is provided. Custom icons can be passed via `audioModeIcon` / `videoModeIcon`.
|
|
81
|
+
|
|
82
|
+
### Automatic bandwidth switching
|
|
83
|
+
|
|
84
|
+
For HLS streams the player monitors bandwidth and switches to audio mode automatically when conditions drop below a threshold:
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { AUDIO_BANDWIDTH_THRESHOLDS } from "react-helios";
|
|
88
|
+
|
|
89
|
+
<VideoPlayer
|
|
90
|
+
src="https://example.com/stream.m3u8"
|
|
91
|
+
options={{
|
|
92
|
+
audioBandwidthThreshold: AUDIO_BANDWIDTH_THRESHOLDS.POOR, // 300 Kbps (default)
|
|
93
|
+
// audioBandwidthThreshold: 0, // disable automatic switching
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| Preset | Kbps | Typical connection |
|
|
99
|
+
|--------|------|--------------------|
|
|
100
|
+
| `EXTREME` | 100 | 2G / Edge |
|
|
101
|
+
| `POOR` | 300 | Slow 3G ← **default** |
|
|
102
|
+
| `FAIR` | 700 | 3G |
|
|
103
|
+
| `GOOD` | 1500 | 4G / Wi-Fi |
|
|
104
|
+
|
|
105
|
+
After the user manually toggles audio mode a 60-second cooldown suppresses automatic switching so the player respects their choice.
|
|
106
|
+
|
|
50
107
|
## Thumbnail Preview
|
|
51
108
|
|
|
52
109
|
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).
|
|
@@ -54,7 +111,9 @@ Hover over the progress bar to see a time tooltip. For rich sprite-sheet thumbna
|
|
|
54
111
|
```tsx
|
|
55
112
|
<VideoPlayer
|
|
56
113
|
src="https://example.com/video.mp4"
|
|
57
|
-
|
|
114
|
+
options={{
|
|
115
|
+
thumbnailVtt: "https://example.com/thumbs/storyboard.vtt",
|
|
116
|
+
}}
|
|
58
117
|
/>
|
|
59
118
|
```
|
|
60
119
|
|
|
@@ -80,38 +139,88 @@ The player fetches the VTT file once, parses all cues, and uses CSS `background-
|
|
|
80
139
|
To disable the preview entirely:
|
|
81
140
|
|
|
82
141
|
```tsx
|
|
83
|
-
<VideoPlayer src="..."
|
|
142
|
+
<VideoPlayer src="..." options={{ enablePreview: false }} />
|
|
84
143
|
```
|
|
85
144
|
|
|
86
145
|
## Props
|
|
87
146
|
|
|
147
|
+
### Top-level props
|
|
148
|
+
|
|
88
149
|
| Prop | Type | Default | Description |
|
|
89
150
|
|------|------|---------|-------------|
|
|
90
151
|
| `src` | `string` | — | Video URL (MP4, WebM, HLS `.m3u8`, …) |
|
|
91
|
-
| `poster` | `string` | — | Poster image shown before playback |
|
|
152
|
+
| `poster` | `string` | — | Poster image shown before playback and in audio mode |
|
|
92
153
|
| `controls` | `boolean` | `true` | Show the built-in control bar |
|
|
154
|
+
| `className` | `string` | — | CSS class on the player container |
|
|
155
|
+
| `options` | `VideoPlayerOptions` | `{}` | All configuration (see below) |
|
|
156
|
+
|
|
157
|
+
### `options` — Playback
|
|
158
|
+
|
|
159
|
+
| Option | Type | Default | Description |
|
|
160
|
+
|--------|------|---------|-------------|
|
|
93
161
|
| `autoplay` | `boolean` | `false` | Start playback on mount |
|
|
94
162
|
| `muted` | `boolean` | `false` | Start muted |
|
|
95
163
|
| `loop` | `boolean` | `false` | Loop the video |
|
|
96
164
|
| `preload` | `"none" \| "metadata" \| "auto"` | `"metadata"` | Native `preload` attribute |
|
|
97
|
-
| `playbackRates` | `PlaybackRate[]` | `[0.25
|
|
165
|
+
| `playbackRates` | `PlaybackRate[]` | `[0.25 … 2]` | Available speed options |
|
|
166
|
+
| `crossOrigin` | `"anonymous" \| "use-credentials"` | — | CORS attribute for the video element |
|
|
167
|
+
| `subtitles` | `SubtitleTrack[]` | — | Subtitle / caption tracks |
|
|
168
|
+
|
|
169
|
+
### `options` — HLS
|
|
170
|
+
|
|
171
|
+
| Option | Type | Default | Description |
|
|
172
|
+
|--------|------|---------|-------------|
|
|
98
173
|
| `enableHLS` | `boolean` | `true` | Enable HLS.js for `.m3u8` sources |
|
|
174
|
+
| `hlsConfig` | `Partial<HlsConfig>` | — | Override any [hls.js config](https://github.com/video-dev/hls.js/blob/master/docs/API.md#fine-tuning) option |
|
|
175
|
+
|
|
176
|
+
### `options` — Preview
|
|
177
|
+
|
|
178
|
+
| Option | Type | Default | Description |
|
|
179
|
+
|--------|------|---------|-------------|
|
|
99
180
|
| `enablePreview` | `boolean` | `true` | Show thumbnail / time tooltip on progress bar hover |
|
|
100
181
|
| `thumbnailVtt` | `string` | — | URL to a WebVTT sprite sheet file for rich thumbnail preview |
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
|
105
|
-
|
|
106
|
-
| `
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
|
111
|
-
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
182
|
+
|
|
183
|
+
### `options` — UI
|
|
184
|
+
|
|
185
|
+
| Option | Type | Default | Description |
|
|
186
|
+
|--------|------|---------|-------------|
|
|
187
|
+
| `autoHideControls` | `boolean` | `true` | Hide control bar on mouse leave when playing (video mode only) |
|
|
188
|
+
|
|
189
|
+
### `options` — Audio mode
|
|
190
|
+
|
|
191
|
+
| Option | Type | Default | Description |
|
|
192
|
+
|--------|------|---------|-------------|
|
|
193
|
+
| `audioSrc` | `string` | — | Audio-only stream URL; the audio toggle button only shows when this is set |
|
|
194
|
+
| `showAudioButton` | `boolean` | `!!audioSrc` | Force-show or hide the audio toggle button |
|
|
195
|
+
| `defaultAudioMode` | `boolean` | `false` | Start in audio mode |
|
|
196
|
+
| `audioModeLabel` | `string` | `"Audio"` | Label on the toggle button when in video mode |
|
|
197
|
+
| `videoModeLabel` | `string` | `"Video"` | Label on the toggle button when in audio mode |
|
|
198
|
+
| `audioModeIcon` | `ReactNode` | built-in headphones icon | Icon shown when in video mode (click → audio) |
|
|
199
|
+
| `videoModeIcon` | `ReactNode` | built-in video icon | Icon shown when in audio mode (click → video) |
|
|
200
|
+
| `audioModeFallback` | `ReactNode` | — | Custom content shown in audio mode when no `poster` is provided |
|
|
201
|
+
| `logo` | `string \| ReactNode` | — | Logo shown in audio mode when no `poster` or `audioModeFallback` is provided |
|
|
202
|
+
| `audioBandwidthThreshold` | `number` | `300` | Kbps — auto-switch to audio mode below this bandwidth. `0` = disabled (HLS only) |
|
|
203
|
+
|
|
204
|
+
### `options` — Callbacks
|
|
205
|
+
|
|
206
|
+
| Option | Type | Description |
|
|
207
|
+
|--------|------|-------------|
|
|
208
|
+
| `onPlay` | `() => void` | Fired when playback starts |
|
|
209
|
+
| `onPause` | `() => void` | Fired when playback pauses |
|
|
210
|
+
| `onEnded` | `() => void` | Fired when playback ends |
|
|
211
|
+
| `onError` | `(error: VideoError) => void` | Fired on playback or stream errors |
|
|
212
|
+
| `onTimeUpdate` | `(time: number) => void` | Fired every ~250 ms during playback |
|
|
213
|
+
| `onDurationChange` | `(duration: number) => void` | Fired when video duration becomes known |
|
|
214
|
+
| `onBuffering` | `(isBuffering: boolean) => void` | Fired when buffering starts / stops |
|
|
215
|
+
| `onTheaterModeChange` | `(isTheater: boolean) => void` | Fired when theater mode is toggled |
|
|
216
|
+
| `onAudioModeChange` | `(isAudio: boolean) => void` | Fired when audio mode is toggled (manual or automatic) |
|
|
217
|
+
|
|
218
|
+
### `options` — Custom controls
|
|
219
|
+
|
|
220
|
+
| Option | Type | Description |
|
|
221
|
+
|--------|------|-------------|
|
|
222
|
+
| `contextMenuItems` | `ContextMenuItem[]` | Extra items appended to the right-click context menu |
|
|
223
|
+
| `controlBarItems` | `ControlBarItem[]` | Extra icon buttons appended to the right side of the control bar |
|
|
115
224
|
|
|
116
225
|
## Quality Selection
|
|
117
226
|
|
|
@@ -120,8 +229,6 @@ For HLS streams (`.m3u8`) the player automatically parses the available quality
|
|
|
120
229
|
- **Speed tab** — always visible, lets you change playback rate.
|
|
121
230
|
- **Quality tab** — appears only for HLS streams. Lists all levels sorted by bitrate (e.g. 1080p, 720p, 480p) plus an **Auto** option that enables ABR (adaptive bitrate). The current auto-selected level is shown in parentheses next to "Auto".
|
|
122
231
|
|
|
123
|
-
For plain MP4/WebM files there are no quality levels to switch between, so the Quality tab never appears.
|
|
124
|
-
|
|
125
232
|
You can also switch quality programmatically via the ref:
|
|
126
233
|
|
|
127
234
|
```tsx
|
|
@@ -131,49 +238,41 @@ playerRef.current?.setQualityLevel(-1); // back to ABR auto
|
|
|
131
238
|
|
|
132
239
|
## Custom Control Bar Buttons
|
|
133
240
|
|
|
134
|
-
Inject your own icon buttons into the right side of the control bar
|
|
241
|
+
Inject your own icon buttons into the right side of the control bar using `controlBarItems`:
|
|
135
242
|
|
|
136
243
|
```tsx
|
|
137
|
-
import { VideoPlayer
|
|
244
|
+
import { VideoPlayer } from "react-helios";
|
|
245
|
+
import type { ControlBarItem } from "react-helios";
|
|
138
246
|
|
|
139
247
|
const items: ControlBarItem[] = [
|
|
140
248
|
{
|
|
141
|
-
key: "
|
|
142
|
-
label: "
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
key: "share",
|
|
148
|
-
label: "Share",
|
|
149
|
-
title: "Share this video",
|
|
150
|
-
icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11A2.99 2.99 0 0 0 18 8a3 3 0 1 0-3-3c0 .24.04.47.09.7L8.04 9.81A2.99 2.99 0 0 0 6 9a3 3 0 1 0 3 3c0-.24-.04-.47-.09-.7l7.05-4.11c.52.47 1.2.77 1.96.77a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"/></svg>,
|
|
151
|
-
onClick: () => openShareDialog(),
|
|
249
|
+
key: "bookmark",
|
|
250
|
+
label: "Bookmark",
|
|
251
|
+
title: "Save current position",
|
|
252
|
+
icon: <BookmarkIcon />,
|
|
253
|
+
onClick: () => saveBookmark(playerRef.current?.getState().currentTime ?? 0),
|
|
152
254
|
},
|
|
153
255
|
];
|
|
154
256
|
|
|
155
|
-
<VideoPlayer src="..."
|
|
257
|
+
<VideoPlayer src="..." options={{ controlBarItems: items }} />
|
|
156
258
|
```
|
|
157
259
|
|
|
158
|
-
Buttons receive the same `controlButton` CSS class as built-in buttons (hover highlight, active press scale, no focus outline).
|
|
159
|
-
|
|
160
260
|
## Context Menu
|
|
161
261
|
|
|
162
|
-
Right-clicking the player shows a built-in menu (Play/Pause, Loop, Copy URL, Picture-in-Picture).
|
|
262
|
+
Right-clicking the player shows a built-in menu (Play/Pause, Loop, Copy URL, Picture-in-Picture). Append your own items via `contextMenuItems`:
|
|
163
263
|
|
|
164
264
|
```tsx
|
|
165
|
-
import { VideoPlayer
|
|
265
|
+
import { VideoPlayer } from "react-helios";
|
|
266
|
+
import type { ContextMenuItem } from "react-helios";
|
|
166
267
|
|
|
167
268
|
const items: ContextMenuItem[] = [
|
|
168
269
|
{ label: "Add to Watchlist", onClick: () => addToWatchlist() },
|
|
169
270
|
{ label: "Share", onClick: () => openShareDialog() },
|
|
170
271
|
];
|
|
171
272
|
|
|
172
|
-
<VideoPlayer src="..."
|
|
273
|
+
<VideoPlayer src="..." options={{ contextMenuItems: items }} />
|
|
173
274
|
```
|
|
174
275
|
|
|
175
|
-
Each item closes the menu automatically after its `onClick` is called.
|
|
176
|
-
|
|
177
276
|
## Imperative API (Ref)
|
|
178
277
|
|
|
179
278
|
Use a `ref` to control the player programmatically:
|
|
@@ -192,7 +291,7 @@ export default function App() {
|
|
|
192
291
|
<button onClick={() => playerRef.current?.pause()}>Pause</button>
|
|
193
292
|
<button onClick={() => playerRef.current?.seek(30)}>Jump to 30s</button>
|
|
194
293
|
<button onClick={() => playerRef.current?.setVolume(0.5)}>50% volume</button>
|
|
195
|
-
<button onClick={() => playerRef.current?.
|
|
294
|
+
<button onClick={() => playerRef.current?.toggleAudioMode()}>Toggle Audio</button>
|
|
196
295
|
</>
|
|
197
296
|
);
|
|
198
297
|
}
|
|
@@ -213,12 +312,13 @@ export default function App() {
|
|
|
213
312
|
| `toggleFullscreen` | `() => Promise<void>` | Toggle fullscreen |
|
|
214
313
|
| `togglePictureInPicture` | `() => Promise<void>` | Toggle Picture-in-Picture |
|
|
215
314
|
| `toggleTheaterMode` | `() => void` | Toggle theater (wide) mode |
|
|
315
|
+
| `toggleAudioMode` | `() => void` | Toggle audio-only mode |
|
|
216
316
|
| `getState` | `() => PlayerState` | Snapshot of current player state |
|
|
217
317
|
| `getVideoElement` | `() => HTMLVideoElement \| null` | Access the underlying `<video>` element |
|
|
218
318
|
|
|
219
319
|
## Theater Mode
|
|
220
320
|
|
|
221
|
-
The player fires `onTheaterModeChange` when theater mode is toggled
|
|
321
|
+
The player fires `onTheaterModeChange` when theater mode is toggled. Wire it to your layout state to widen your container:
|
|
222
322
|
|
|
223
323
|
```tsx
|
|
224
324
|
"use client";
|
|
@@ -237,24 +337,26 @@ export default function Page() {
|
|
|
237
337
|
<VideoPlayer
|
|
238
338
|
src="https://example.com/stream.m3u8"
|
|
239
339
|
controls
|
|
240
|
-
|
|
340
|
+
options={{
|
|
341
|
+
onTheaterModeChange: (t) => setIsTheater(t),
|
|
342
|
+
}}
|
|
241
343
|
/>
|
|
242
344
|
</main>
|
|
243
345
|
);
|
|
244
346
|
}
|
|
245
347
|
```
|
|
246
348
|
|
|
247
|
-
The player itself does not manage your page layout — it only notifies you so you can adapt your design.
|
|
248
|
-
|
|
249
349
|
## Subtitles
|
|
250
350
|
|
|
251
351
|
```tsx
|
|
252
352
|
<VideoPlayer
|
|
253
353
|
src="https://example.com/video.mp4"
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
354
|
+
options={{
|
|
355
|
+
subtitles: [
|
|
356
|
+
{ id: "en", src: "/subs/en.vtt", label: "English", srclang: "en", default: true },
|
|
357
|
+
{ id: "es", src: "/subs/es.vtt", label: "Español", srclang: "es" },
|
|
358
|
+
],
|
|
359
|
+
}}
|
|
258
360
|
/>
|
|
259
361
|
```
|
|
260
362
|
|
|
@@ -292,6 +394,7 @@ All types are exported from the package:
|
|
|
292
394
|
```ts
|
|
293
395
|
import type {
|
|
294
396
|
VideoPlayerProps,
|
|
397
|
+
VideoPlayerOptions,
|
|
295
398
|
VideoPlayerRef,
|
|
296
399
|
PlayerState,
|
|
297
400
|
PlaybackRate,
|
|
@@ -300,13 +403,15 @@ import type {
|
|
|
300
403
|
BufferedRange,
|
|
301
404
|
VideoError,
|
|
302
405
|
VideoErrorCode,
|
|
303
|
-
ThumbnailCue,
|
|
304
406
|
ContextMenuItem,
|
|
305
407
|
ControlBarItem,
|
|
306
408
|
} from "react-helios";
|
|
307
409
|
|
|
410
|
+
import { AUDIO_BANDWIDTH_THRESHOLDS } from "react-helios";
|
|
411
|
+
|
|
308
412
|
// VTT utilities (useful for server-side pre-parsing or custom UIs)
|
|
309
413
|
import { parseThumbnailVtt, findThumbnailCue } from "react-helios";
|
|
414
|
+
import type { ThumbnailCue } from "react-helios";
|
|
310
415
|
```
|
|
311
416
|
|
|
312
417
|
### `PlayerState`
|
|
@@ -325,6 +430,7 @@ interface PlayerState {
|
|
|
325
430
|
isFullscreen: boolean;
|
|
326
431
|
isPictureInPicture: boolean;
|
|
327
432
|
isTheaterMode: boolean;
|
|
433
|
+
isAudioMode: boolean;
|
|
328
434
|
isLive: boolean;
|
|
329
435
|
qualityLevels: HLSQualityLevel[];
|
|
330
436
|
currentQualityLevel: number; // -1 = ABR auto
|
|
@@ -349,20 +455,6 @@ interface VideoError {
|
|
|
349
455
|
}
|
|
350
456
|
```
|
|
351
457
|
|
|
352
|
-
### `ThumbnailCue`
|
|
353
|
-
|
|
354
|
-
```ts
|
|
355
|
-
interface ThumbnailCue {
|
|
356
|
-
start: number; // seconds
|
|
357
|
-
end: number; // seconds
|
|
358
|
-
url: string; // absolute URL to the sprite image
|
|
359
|
-
x: number; // pixel offset within sprite
|
|
360
|
-
y: number;
|
|
361
|
-
w: number; // cell width in pixels
|
|
362
|
-
h: number; // cell height in pixels
|
|
363
|
-
}
|
|
364
|
-
```
|
|
365
|
-
|
|
366
458
|
### `ControlBarItem`
|
|
367
459
|
|
|
368
460
|
```ts
|
|
@@ -384,9 +476,21 @@ interface ContextMenuItem {
|
|
|
384
476
|
}
|
|
385
477
|
```
|
|
386
478
|
|
|
387
|
-
|
|
479
|
+
### `ThumbnailCue`
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
interface ThumbnailCue {
|
|
483
|
+
start: number; // seconds
|
|
484
|
+
end: number; // seconds
|
|
485
|
+
url: string; // absolute URL to the sprite image
|
|
486
|
+
x: number; // pixel offset within sprite
|
|
487
|
+
y: number;
|
|
488
|
+
w: number; // cell width in pixels
|
|
489
|
+
h: number; // cell height in pixels
|
|
490
|
+
}
|
|
491
|
+
```
|
|
388
492
|
|
|
389
|
-
|
|
493
|
+
## Utility Functions
|
|
390
494
|
|
|
391
495
|
```ts
|
|
392
496
|
import { formatTime, isHLSUrl, getMimeType } from "react-helios";
|
|
@@ -422,15 +526,17 @@ The player is architected to produce **zero React re-renders during playback**:
|
|
|
422
526
|
|
|
423
527
|
- `timeupdate` and `progress` events are handled by direct DOM mutation (refs), not React state.
|
|
424
528
|
- `ProgressBar` and `TimeDisplay` self-subscribe to the video element — the parent tree never re-renders on seek or time change.
|
|
529
|
+
- `Controls` and `AudioModeOverlay` are wrapped in `React.memo` — they only re-render when their own props change, not when unrelated state (buffering, errors) updates.
|
|
425
530
|
- 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.
|
|
426
531
|
- Buffered ranges are the only state that triggers a re-render (fires every few seconds during buffering, not 60× per second).
|
|
532
|
+
- In audio mode the `<video>` element stays mounted with `visibility: hidden` — audio keeps playing without any re-initialisation cost on toggle.
|
|
427
533
|
|
|
428
534
|
## Project Structure
|
|
429
535
|
|
|
430
536
|
```
|
|
431
537
|
react-helios/
|
|
432
538
|
├── src/ # Library source
|
|
433
|
-
│ ├── components/ # VideoPlayer, Controls, control elements
|
|
539
|
+
│ ├── components/ # VideoPlayer, Controls, AudioModeOverlay, control elements
|
|
434
540
|
│ ├── hooks/ # useVideoPlayer (state + HLS init)
|
|
435
541
|
│ ├── lib/ # Types, HLS utilities, VTT parser, format helpers
|
|
436
542
|
│ └── styles/ # CSS
|
package/dist/index.d.mts
CHANGED
|
@@ -101,39 +101,36 @@ interface ControlBarItem {
|
|
|
101
101
|
title?: string;
|
|
102
102
|
onClick: () => void;
|
|
103
103
|
}
|
|
104
|
-
interface
|
|
105
|
-
src: string;
|
|
106
|
-
poster?: string;
|
|
104
|
+
interface VideoPlayerOptions {
|
|
107
105
|
autoplay?: boolean;
|
|
108
106
|
muted?: boolean;
|
|
109
107
|
loop?: boolean;
|
|
110
|
-
controls?: boolean;
|
|
111
108
|
preload?: "none" | "metadata" | "auto";
|
|
112
109
|
playbackRates?: PlaybackRate[];
|
|
113
|
-
className?: string;
|
|
114
110
|
enableHLS?: boolean;
|
|
111
|
+
hlsConfig?: Partial<HlsConfig>;
|
|
115
112
|
enablePreview?: boolean;
|
|
116
|
-
/**
|
|
117
|
-
* URL to a WebVTT thumbnail track for sprite-sheet preview on the progress bar.
|
|
118
|
-
*
|
|
119
|
-
* The VTT file should map time ranges to sprite-sheet coordinates using the
|
|
120
|
-
* standard `#xywh=x,y,w,h` fragment format:
|
|
121
|
-
*
|
|
122
|
-
* ```
|
|
123
|
-
* WEBVTT
|
|
124
|
-
*
|
|
125
|
-
* 00:00:00.000 --> 00:00:05.000
|
|
126
|
-
* https://cdn.example.com/thumbs/storyboard0.jpg#xywh=0,0,160,90
|
|
127
|
-
* ```
|
|
128
|
-
*
|
|
129
|
-
* When provided, hovering the progress bar shows a thumbnail instead of
|
|
130
|
-
* requiring a second video decode. If omitted, only the timestamp tooltip
|
|
131
|
-
* is shown.
|
|
132
|
-
*/
|
|
133
113
|
thumbnailVtt?: string;
|
|
134
|
-
|
|
114
|
+
autoHideControls?: boolean;
|
|
135
115
|
subtitles?: SubtitleTrack[];
|
|
136
116
|
crossOrigin?: "anonymous" | "use-credentials";
|
|
117
|
+
logo?: string | ReactNode;
|
|
118
|
+
audioSrc?: string;
|
|
119
|
+
showAudioButton?: boolean;
|
|
120
|
+
audioModeIcon?: ReactNode;
|
|
121
|
+
videoModeIcon?: ReactNode;
|
|
122
|
+
/**
|
|
123
|
+
* Custom content shown in audio mode when no `poster` is provided.
|
|
124
|
+
* Replaces the default animated-gradient + waveform fallback entirely.
|
|
125
|
+
* The `logo` prop is still rendered on top of this if also provided.
|
|
126
|
+
*/
|
|
127
|
+
audioModeFallback?: ReactNode;
|
|
128
|
+
/** Label shown next to the icon when in video mode (click → switches to audio). Default: "Audio" */
|
|
129
|
+
audioModeLabel?: string;
|
|
130
|
+
/** Label shown next to the icon when in audio mode (click → switches to video). Default: "Video" */
|
|
131
|
+
videoModeLabel?: string;
|
|
132
|
+
defaultAudioMode?: boolean;
|
|
133
|
+
audioBandwidthThreshold?: number;
|
|
137
134
|
onPlay?: () => void;
|
|
138
135
|
onPause?: () => void;
|
|
139
136
|
onEnded?: () => void;
|
|
@@ -142,37 +139,17 @@ interface VideoPlayerProps {
|
|
|
142
139
|
onDurationChange?: (duration: number) => void;
|
|
143
140
|
onBuffering?: (isBuffering: boolean) => void;
|
|
144
141
|
onTheaterModeChange?: (isTheater: boolean) => void;
|
|
145
|
-
/**
|
|
146
|
-
* Image URL or ReactNode shown as artwork in audio mode.
|
|
147
|
-
* Priority: `poster` prop → `logo` string/ReactNode → waveform-only.
|
|
148
|
-
* If a string URL is provided the image is rendered white-normalised (filter invert)
|
|
149
|
-
* so it stands out on the dark background.
|
|
150
|
-
*/
|
|
151
|
-
logo?: string | ReactNode;
|
|
152
|
-
/**
|
|
153
|
-
* Show the headphones / audio-mode toggle button in the control bar.
|
|
154
|
-
* @default true
|
|
155
|
-
*/
|
|
156
|
-
showAudioButton?: boolean;
|
|
157
|
-
/**
|
|
158
|
-
* Start the player in audio-only mode on mount.
|
|
159
|
-
* @default false
|
|
160
|
-
*/
|
|
161
|
-
defaultAudioMode?: boolean;
|
|
162
|
-
/**
|
|
163
|
-
* Bandwidth threshold in **Kbps**. When the measured download speed falls below
|
|
164
|
-
* this value the player automatically switches to audio mode.
|
|
165
|
-
* Use the exported `AUDIO_BANDWIDTH_THRESHOLDS` presets for convenience.
|
|
166
|
-
* Set to `0` to disable automatic switching.
|
|
167
|
-
* Only applies to HLS streams (where hls.js measures real segment bandwidth).
|
|
168
|
-
* @default 300 (AUDIO_BANDWIDTH_THRESHOLDS.POOR)
|
|
169
|
-
*/
|
|
170
|
-
audioBandwidthThreshold?: number;
|
|
171
|
-
/** Fired whenever audio mode is toggled — either automatically or by the user. */
|
|
172
142
|
onAudioModeChange?: (isAudio: boolean) => void;
|
|
173
143
|
contextMenuItems?: ContextMenuItem[];
|
|
174
144
|
controlBarItems?: ControlBarItem[];
|
|
175
145
|
}
|
|
146
|
+
interface VideoPlayerProps {
|
|
147
|
+
src: string;
|
|
148
|
+
poster?: string;
|
|
149
|
+
className?: string;
|
|
150
|
+
controls?: boolean;
|
|
151
|
+
options?: VideoPlayerOptions;
|
|
152
|
+
}
|
|
176
153
|
|
|
177
154
|
declare const VideoPlayer: react__default.ForwardRefExoticComponent<VideoPlayerProps & react__default.RefAttributes<VideoPlayerRef>>;
|
|
178
155
|
|
|
@@ -192,12 +169,17 @@ interface ControlsProps {
|
|
|
192
169
|
isTheaterMode: boolean;
|
|
193
170
|
isAudioMode: boolean;
|
|
194
171
|
showAudioButton: boolean;
|
|
172
|
+
audioModeIcon?: ReactNode;
|
|
173
|
+
videoModeIcon?: ReactNode;
|
|
174
|
+
audioModeLabel?: string;
|
|
175
|
+
videoModeLabel?: string;
|
|
195
176
|
isLive: boolean;
|
|
196
177
|
qualityLevels: HLSQualityLevel[];
|
|
197
178
|
currentQualityLevel: number;
|
|
198
179
|
controlBarItems?: ControlBarItem[];
|
|
180
|
+
autoHideControls: boolean;
|
|
199
181
|
}
|
|
200
|
-
declare const Controls: react__default.
|
|
182
|
+
declare const Controls: react__default.NamedExoticComponent<ControlsProps>;
|
|
201
183
|
|
|
202
184
|
interface TimeDisplayProps {
|
|
203
185
|
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
@@ -339,4 +321,4 @@ declare function parseThumbnailVtt(text: string, baseUrl?: string): ThumbnailCue
|
|
|
339
321
|
*/
|
|
340
322
|
declare function findThumbnailCue(cues: ThumbnailCue[], time: number): ThumbnailCue | null;
|
|
341
323
|
|
|
342
|
-
export { AUDIO_BANDWIDTH_THRESHOLDS, type BufferedRange, type ContextMenuItem, type ControlBarItem, 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 };
|
|
324
|
+
export { AUDIO_BANDWIDTH_THRESHOLDS, type BufferedRange, type ContextMenuItem, type ControlBarItem, index as ControlElements, Controls, type HLSQualityLevel, type PlaybackRate, type PlayerState, type SubtitleTrack, type ThumbnailCue, type VideoError, type VideoErrorCode, VideoPlayer, type VideoPlayerOptions, type VideoPlayerProps, type VideoPlayerRef, findThumbnailCue, formatTime, getMimeType, isHLSUrl, parseThumbnailVtt };
|