react-helios 2.4.0 → 2.7.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 +216 -72
- package/dist/index.d.mts +93 -67
- package/dist/index.d.ts +93 -67
- 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, zero-cost 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,100 @@ 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 pauses the video element completely (stopping all video decoding), shows the poster artwork, and hands playback off to a lightweight `<audio>` element — so the player uses roughly the same CPU/GPU as a music app instead of a playing video.
|
|
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
|
+
When switching between modes, position, volume, and playback rate are synced automatically — the listener hears no gap.
|
|
83
|
+
|
|
84
|
+
### Automatic switching
|
|
85
|
+
|
|
86
|
+
The player uses two independent signals to detect poor conditions and switch to audio mode automatically. Either one firing is enough.
|
|
87
|
+
|
|
88
|
+
**Bandwidth-based** — measures the actual download speed of each HLS fragment and switches when the rolling average drops below a threshold:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { AUDIO_BANDWIDTH_THRESHOLDS } from "react-helios";
|
|
92
|
+
|
|
93
|
+
<VideoPlayer
|
|
94
|
+
src="https://example.com/stream.m3u8"
|
|
95
|
+
options={{
|
|
96
|
+
audioBandwidthThreshold: AUDIO_BANDWIDTH_THRESHOLDS.FAIR, // recommended
|
|
97
|
+
// audioBandwidthThreshold: 0, // disable bandwidth-based switching
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| Preset | Kbps | Typical connection |
|
|
103
|
+
|--------|------|--------------------|
|
|
104
|
+
| `EXTREME` | 100 | 2G / Edge |
|
|
105
|
+
| `POOR` | 300 | Slow 3G |
|
|
106
|
+
| `FAIR` | 800 | Marginal 3G ← **recommended** |
|
|
107
|
+
| `GOOD` | 1500 | Weak 4G / congested Wi-Fi |
|
|
108
|
+
|
|
109
|
+
**Level-based** — switches when HLS.js drops to a specific quality level (its own ABR algorithm already does the hard work):
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { AUDIO_SWITCH_LEVELS } from "react-helios";
|
|
113
|
+
|
|
114
|
+
<VideoPlayer
|
|
115
|
+
src="https://example.com/stream.m3u8"
|
|
116
|
+
options={{
|
|
117
|
+
audioModeSwitchLevel: AUDIO_SWITCH_LEVELS.LOWEST, // switch at lowest quality level
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| Preset | Value | Meaning |
|
|
123
|
+
|--------|-------|---------|
|
|
124
|
+
| `LOWEST` | 0 | Switch when HLS.js is at the lowest available quality |
|
|
125
|
+
| `SECOND_LOWEST` | 1 | Switch one level above the lowest |
|
|
126
|
+
| `DISABLED` | -1 | Disable level-based switching |
|
|
127
|
+
|
|
128
|
+
Using **both together** is the most reliable approach:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
<VideoPlayer
|
|
132
|
+
src="https://example.com/stream.m3u8"
|
|
133
|
+
options={{
|
|
134
|
+
audioSrc: "https://example.com/audio-only.m3u8",
|
|
135
|
+
audioBandwidthThreshold: AUDIO_BANDWIDTH_THRESHOLDS.FAIR,
|
|
136
|
+
audioModeSwitchLevel: AUDIO_SWITCH_LEVELS.LOWEST,
|
|
137
|
+
}}
|
|
138
|
+
/>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
After the user manually toggles audio mode a 60-second cooldown suppresses automatic switching. The player also probes for bandwidth recovery every 30 seconds while in auto-switched audio mode (configurable via `audioModeRecoveryInterval`).
|
|
142
|
+
|
|
50
143
|
## Thumbnail Preview
|
|
51
144
|
|
|
52
145
|
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 +147,9 @@ Hover over the progress bar to see a time tooltip. For rich sprite-sheet thumbna
|
|
|
54
147
|
```tsx
|
|
55
148
|
<VideoPlayer
|
|
56
149
|
src="https://example.com/video.mp4"
|
|
57
|
-
|
|
150
|
+
options={{
|
|
151
|
+
thumbnailVtt: "https://example.com/thumbs/storyboard.vtt",
|
|
152
|
+
}}
|
|
58
153
|
/>
|
|
59
154
|
```
|
|
60
155
|
|
|
@@ -80,38 +175,90 @@ The player fetches the VTT file once, parses all cues, and uses CSS `background-
|
|
|
80
175
|
To disable the preview entirely:
|
|
81
176
|
|
|
82
177
|
```tsx
|
|
83
|
-
<VideoPlayer src="..."
|
|
178
|
+
<VideoPlayer src="..." options={{ enablePreview: false }} />
|
|
84
179
|
```
|
|
85
180
|
|
|
86
181
|
## Props
|
|
87
182
|
|
|
183
|
+
### Top-level props
|
|
184
|
+
|
|
88
185
|
| Prop | Type | Default | Description |
|
|
89
186
|
|------|------|---------|-------------|
|
|
90
187
|
| `src` | `string` | — | Video URL (MP4, WebM, HLS `.m3u8`, …) |
|
|
91
|
-
| `poster` | `string` | — | Poster image shown before playback |
|
|
188
|
+
| `poster` | `string` | — | Poster image shown before playback and in audio mode |
|
|
92
189
|
| `controls` | `boolean` | `true` | Show the built-in control bar |
|
|
190
|
+
| `className` | `string` | — | CSS class on the player container |
|
|
191
|
+
| `options` | `VideoPlayerOptions` | `{}` | All configuration (see below) |
|
|
192
|
+
|
|
193
|
+
### `options` — Playback
|
|
194
|
+
|
|
195
|
+
| Option | Type | Default | Description |
|
|
196
|
+
|--------|------|---------|-------------|
|
|
93
197
|
| `autoplay` | `boolean` | `false` | Start playback on mount |
|
|
94
198
|
| `muted` | `boolean` | `false` | Start muted |
|
|
95
199
|
| `loop` | `boolean` | `false` | Loop the video |
|
|
96
200
|
| `preload` | `"none" \| "metadata" \| "auto"` | `"metadata"` | Native `preload` attribute |
|
|
97
|
-
| `playbackRates` | `PlaybackRate[]` | `[0.25
|
|
201
|
+
| `playbackRates` | `PlaybackRate[]` | `[0.25 … 2]` | Available speed options |
|
|
202
|
+
| `crossOrigin` | `"anonymous" \| "use-credentials"` | — | CORS attribute for the video element |
|
|
203
|
+
| `subtitles` | `SubtitleTrack[]` | — | Subtitle / caption tracks |
|
|
204
|
+
|
|
205
|
+
### `options` — HLS
|
|
206
|
+
|
|
207
|
+
| Option | Type | Default | Description |
|
|
208
|
+
|--------|------|---------|-------------|
|
|
98
209
|
| `enableHLS` | `boolean` | `true` | Enable HLS.js for `.m3u8` sources |
|
|
210
|
+
| `hlsConfig` | `Partial<HlsConfig>` | — | Override any [hls.js config](https://github.com/video-dev/hls.js/blob/master/docs/API.md#fine-tuning) option |
|
|
211
|
+
|
|
212
|
+
### `options` — Preview
|
|
213
|
+
|
|
214
|
+
| Option | Type | Default | Description |
|
|
215
|
+
|--------|------|---------|-------------|
|
|
99
216
|
| `enablePreview` | `boolean` | `true` | Show thumbnail / time tooltip on progress bar hover |
|
|
100
217
|
| `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
|
-
| `
|
|
218
|
+
|
|
219
|
+
### `options` — UI
|
|
220
|
+
|
|
221
|
+
| Option | Type | Default | Description |
|
|
222
|
+
|--------|------|---------|-------------|
|
|
223
|
+
| `autoHideControls` | `boolean` | `true` | Hide control bar on mouse leave when playing (video mode only) |
|
|
224
|
+
|
|
225
|
+
### `options` — Audio mode
|
|
226
|
+
|
|
227
|
+
| Option | Type | Default | Description |
|
|
228
|
+
|--------|------|---------|-------------|
|
|
229
|
+
| `audioSrc` | `string` | — | Audio-only stream URL; the audio toggle button only shows when this is set |
|
|
230
|
+
| `showAudioButton` | `boolean` | `!!audioSrc` | Force-show or hide the audio toggle button |
|
|
231
|
+
| `defaultAudioMode` | `boolean` | `false` | Start in audio mode |
|
|
232
|
+
| `audioModeLabel` | `string` | `"Audio"` | Label on the toggle button when in video mode |
|
|
233
|
+
| `videoModeLabel` | `string` | `"Video"` | Label on the toggle button when in audio mode |
|
|
234
|
+
| `audioModeIcon` | `ReactNode` | built-in headphones icon | Icon shown when in video mode (click → audio) |
|
|
235
|
+
| `videoModeIcon` | `ReactNode` | built-in video icon | Icon shown when in audio mode (click → video) |
|
|
236
|
+
| `audioModeFallback` | `ReactNode` | — | Custom content shown in audio mode when no `poster` is provided |
|
|
237
|
+
| `logo` | `string \| ReactNode` | — | Logo shown in audio mode when no `poster` or `audioModeFallback` is provided |
|
|
238
|
+
| `audioBandwidthThreshold` | `number` | `300` | Kbps — switch when per-fragment bandwidth average drops below this. `0` = disabled (HLS only) |
|
|
239
|
+
| `audioModeSwitchLevel` | `number` | — | HLS quality level index — switch when HLS.js drops to this level or below. `0` = lowest. `-1` = disabled |
|
|
240
|
+
| `audioModeRecoveryInterval` | `number` | `30000` | Ms between recovery probes while in auto-switched audio mode |
|
|
241
|
+
|
|
242
|
+
### `options` — Callbacks
|
|
243
|
+
|
|
244
|
+
| Option | Type | Description |
|
|
245
|
+
|--------|------|-------------|
|
|
246
|
+
| `onPlay` | `() => void` | Fired when playback starts |
|
|
247
|
+
| `onPause` | `() => void` | Fired when playback pauses |
|
|
248
|
+
| `onEnded` | `() => void` | Fired when playback ends |
|
|
249
|
+
| `onError` | `(error: VideoError) => void` | Fired on playback or stream errors |
|
|
250
|
+
| `onTimeUpdate` | `(time: number) => void` | Fired every ~250 ms during playback |
|
|
251
|
+
| `onDurationChange` | `(duration: number) => void` | Fired when video duration becomes known |
|
|
252
|
+
| `onBuffering` | `(isBuffering: boolean) => void` | Fired when buffering starts / stops |
|
|
253
|
+
| `onTheaterModeChange` | `(isTheater: boolean) => void` | Fired when theater mode is toggled |
|
|
254
|
+
| `onAudioModeChange` | `(isAudio: boolean) => void` | Fired when audio mode is toggled (manual or automatic) |
|
|
255
|
+
|
|
256
|
+
### `options` — Custom controls
|
|
257
|
+
|
|
258
|
+
| Option | Type | Description |
|
|
259
|
+
|--------|------|-------------|
|
|
260
|
+
| `contextMenuItems` | `ContextMenuItem[]` | Extra items appended to the right-click context menu |
|
|
261
|
+
| `controlBarItems` | `ControlBarItem[]` | Extra icon buttons appended to the right side of the control bar |
|
|
115
262
|
|
|
116
263
|
## Quality Selection
|
|
117
264
|
|
|
@@ -120,8 +267,6 @@ For HLS streams (`.m3u8`) the player automatically parses the available quality
|
|
|
120
267
|
- **Speed tab** — always visible, lets you change playback rate.
|
|
121
268
|
- **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
269
|
|
|
123
|
-
For plain MP4/WebM files there are no quality levels to switch between, so the Quality tab never appears.
|
|
124
|
-
|
|
125
270
|
You can also switch quality programmatically via the ref:
|
|
126
271
|
|
|
127
272
|
```tsx
|
|
@@ -131,49 +276,41 @@ playerRef.current?.setQualityLevel(-1); // back to ABR auto
|
|
|
131
276
|
|
|
132
277
|
## Custom Control Bar Buttons
|
|
133
278
|
|
|
134
|
-
Inject your own icon buttons into the right side of the control bar
|
|
279
|
+
Inject your own icon buttons into the right side of the control bar using `controlBarItems`:
|
|
135
280
|
|
|
136
281
|
```tsx
|
|
137
|
-
import { VideoPlayer
|
|
282
|
+
import { VideoPlayer } from "react-helios";
|
|
283
|
+
import type { ControlBarItem } from "react-helios";
|
|
138
284
|
|
|
139
285
|
const items: ControlBarItem[] = [
|
|
140
286
|
{
|
|
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(),
|
|
287
|
+
key: "bookmark",
|
|
288
|
+
label: "Bookmark",
|
|
289
|
+
title: "Save current position",
|
|
290
|
+
icon: <BookmarkIcon />,
|
|
291
|
+
onClick: () => saveBookmark(playerRef.current?.getState().currentTime ?? 0),
|
|
152
292
|
},
|
|
153
293
|
];
|
|
154
294
|
|
|
155
|
-
<VideoPlayer src="..."
|
|
295
|
+
<VideoPlayer src="..." options={{ controlBarItems: items }} />
|
|
156
296
|
```
|
|
157
297
|
|
|
158
|
-
Buttons receive the same `controlButton` CSS class as built-in buttons (hover highlight, active press scale, no focus outline).
|
|
159
|
-
|
|
160
298
|
## Context Menu
|
|
161
299
|
|
|
162
|
-
Right-clicking the player shows a built-in menu (Play/Pause, Loop, Copy URL, Picture-in-Picture).
|
|
300
|
+
Right-clicking the player shows a built-in menu (Play/Pause, Loop, Copy URL, Picture-in-Picture). Append your own items via `contextMenuItems`:
|
|
163
301
|
|
|
164
302
|
```tsx
|
|
165
|
-
import { VideoPlayer
|
|
303
|
+
import { VideoPlayer } from "react-helios";
|
|
304
|
+
import type { ContextMenuItem } from "react-helios";
|
|
166
305
|
|
|
167
306
|
const items: ContextMenuItem[] = [
|
|
168
307
|
{ label: "Add to Watchlist", onClick: () => addToWatchlist() },
|
|
169
308
|
{ label: "Share", onClick: () => openShareDialog() },
|
|
170
309
|
];
|
|
171
310
|
|
|
172
|
-
<VideoPlayer src="..."
|
|
311
|
+
<VideoPlayer src="..." options={{ contextMenuItems: items }} />
|
|
173
312
|
```
|
|
174
313
|
|
|
175
|
-
Each item closes the menu automatically after its `onClick` is called.
|
|
176
|
-
|
|
177
314
|
## Imperative API (Ref)
|
|
178
315
|
|
|
179
316
|
Use a `ref` to control the player programmatically:
|
|
@@ -192,7 +329,7 @@ export default function App() {
|
|
|
192
329
|
<button onClick={() => playerRef.current?.pause()}>Pause</button>
|
|
193
330
|
<button onClick={() => playerRef.current?.seek(30)}>Jump to 30s</button>
|
|
194
331
|
<button onClick={() => playerRef.current?.setVolume(0.5)}>50% volume</button>
|
|
195
|
-
<button onClick={() => playerRef.current?.
|
|
332
|
+
<button onClick={() => playerRef.current?.toggleAudioMode()}>Toggle Audio</button>
|
|
196
333
|
</>
|
|
197
334
|
);
|
|
198
335
|
}
|
|
@@ -213,12 +350,13 @@ export default function App() {
|
|
|
213
350
|
| `toggleFullscreen` | `() => Promise<void>` | Toggle fullscreen |
|
|
214
351
|
| `togglePictureInPicture` | `() => Promise<void>` | Toggle Picture-in-Picture |
|
|
215
352
|
| `toggleTheaterMode` | `() => void` | Toggle theater (wide) mode |
|
|
353
|
+
| `toggleAudioMode` | `() => void` | Toggle audio-only mode |
|
|
216
354
|
| `getState` | `() => PlayerState` | Snapshot of current player state |
|
|
217
355
|
| `getVideoElement` | `() => HTMLVideoElement \| null` | Access the underlying `<video>` element |
|
|
218
356
|
|
|
219
357
|
## Theater Mode
|
|
220
358
|
|
|
221
|
-
The player fires `onTheaterModeChange` when theater mode is toggled
|
|
359
|
+
The player fires `onTheaterModeChange` when theater mode is toggled. Wire it to your layout state to widen your container:
|
|
222
360
|
|
|
223
361
|
```tsx
|
|
224
362
|
"use client";
|
|
@@ -237,24 +375,26 @@ export default function Page() {
|
|
|
237
375
|
<VideoPlayer
|
|
238
376
|
src="https://example.com/stream.m3u8"
|
|
239
377
|
controls
|
|
240
|
-
|
|
378
|
+
options={{
|
|
379
|
+
onTheaterModeChange: (t) => setIsTheater(t),
|
|
380
|
+
}}
|
|
241
381
|
/>
|
|
242
382
|
</main>
|
|
243
383
|
);
|
|
244
384
|
}
|
|
245
385
|
```
|
|
246
386
|
|
|
247
|
-
The player itself does not manage your page layout — it only notifies you so you can adapt your design.
|
|
248
|
-
|
|
249
387
|
## Subtitles
|
|
250
388
|
|
|
251
389
|
```tsx
|
|
252
390
|
<VideoPlayer
|
|
253
391
|
src="https://example.com/video.mp4"
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
392
|
+
options={{
|
|
393
|
+
subtitles: [
|
|
394
|
+
{ id: "en", src: "/subs/en.vtt", label: "English", srclang: "en", default: true },
|
|
395
|
+
{ id: "es", src: "/subs/es.vtt", label: "Español", srclang: "es" },
|
|
396
|
+
],
|
|
397
|
+
}}
|
|
258
398
|
/>
|
|
259
399
|
```
|
|
260
400
|
|
|
@@ -292,6 +432,7 @@ All types are exported from the package:
|
|
|
292
432
|
```ts
|
|
293
433
|
import type {
|
|
294
434
|
VideoPlayerProps,
|
|
435
|
+
VideoPlayerOptions,
|
|
295
436
|
VideoPlayerRef,
|
|
296
437
|
PlayerState,
|
|
297
438
|
PlaybackRate,
|
|
@@ -300,13 +441,15 @@ import type {
|
|
|
300
441
|
BufferedRange,
|
|
301
442
|
VideoError,
|
|
302
443
|
VideoErrorCode,
|
|
303
|
-
ThumbnailCue,
|
|
304
444
|
ContextMenuItem,
|
|
305
445
|
ControlBarItem,
|
|
306
446
|
} from "react-helios";
|
|
307
447
|
|
|
448
|
+
import { AUDIO_BANDWIDTH_THRESHOLDS, AUDIO_SWITCH_LEVELS } from "react-helios";
|
|
449
|
+
|
|
308
450
|
// VTT utilities (useful for server-side pre-parsing or custom UIs)
|
|
309
451
|
import { parseThumbnailVtt, findThumbnailCue } from "react-helios";
|
|
452
|
+
import type { ThumbnailCue } from "react-helios";
|
|
310
453
|
```
|
|
311
454
|
|
|
312
455
|
### `PlayerState`
|
|
@@ -325,6 +468,7 @@ interface PlayerState {
|
|
|
325
468
|
isFullscreen: boolean;
|
|
326
469
|
isPictureInPicture: boolean;
|
|
327
470
|
isTheaterMode: boolean;
|
|
471
|
+
isAudioMode: boolean;
|
|
328
472
|
isLive: boolean;
|
|
329
473
|
qualityLevels: HLSQualityLevel[];
|
|
330
474
|
currentQualityLevel: number; // -1 = ABR auto
|
|
@@ -349,20 +493,6 @@ interface VideoError {
|
|
|
349
493
|
}
|
|
350
494
|
```
|
|
351
495
|
|
|
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
496
|
### `ControlBarItem`
|
|
367
497
|
|
|
368
498
|
```ts
|
|
@@ -384,9 +514,21 @@ interface ContextMenuItem {
|
|
|
384
514
|
}
|
|
385
515
|
```
|
|
386
516
|
|
|
387
|
-
|
|
517
|
+
### `ThumbnailCue`
|
|
388
518
|
|
|
389
|
-
|
|
519
|
+
```ts
|
|
520
|
+
interface ThumbnailCue {
|
|
521
|
+
start: number; // seconds
|
|
522
|
+
end: number; // seconds
|
|
523
|
+
url: string; // absolute URL to the sprite image
|
|
524
|
+
x: number; // pixel offset within sprite
|
|
525
|
+
y: number;
|
|
526
|
+
w: number; // cell width in pixels
|
|
527
|
+
h: number; // cell height in pixels
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Utility Functions
|
|
390
532
|
|
|
391
533
|
```ts
|
|
392
534
|
import { formatTime, isHLSUrl, getMimeType } from "react-helios";
|
|
@@ -421,16 +563,18 @@ if (cue) {
|
|
|
421
563
|
The player is architected to produce **zero React re-renders during playback**:
|
|
422
564
|
|
|
423
565
|
- `timeupdate` and `progress` events are handled by direct DOM mutation (refs), not React state.
|
|
424
|
-
- `ProgressBar` and `TimeDisplay` self-subscribe to the
|
|
566
|
+
- `ProgressBar` and `TimeDisplay` self-subscribe to the active media element — the parent tree never re-renders on seek or time change.
|
|
567
|
+
- `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
568
|
- 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
569
|
- Buffered ranges are the only state that triggers a re-render (fires every few seconds during buffering, not 60× per second).
|
|
570
|
+
- In audio mode the `<video>` element is **paused** — the browser stops decoding frames entirely. A lightweight `<audio>` element takes over with `preload="none"` (no network cost at startup). The `<audio>` element only loads its source the first time the user switches to audio mode.
|
|
427
571
|
|
|
428
572
|
## Project Structure
|
|
429
573
|
|
|
430
574
|
```
|
|
431
575
|
react-helios/
|
|
432
576
|
├── src/ # Library source
|
|
433
|
-
│ ├── components/ # VideoPlayer, Controls, control elements
|
|
577
|
+
│ ├── components/ # VideoPlayer, Controls, AudioModeOverlay, control elements
|
|
434
578
|
│ ├── hooks/ # useVideoPlayer (state + HLS init)
|
|
435
579
|
│ ├── lib/ # Types, HLS utilities, VTT parser, format helpers
|
|
436
580
|
│ └── styles/ # CSS
|