react-helios 2.0.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/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/index.css +2 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +244 -0
- package/dist/index.d.ts +244 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +501 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sanish Manandhar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# @sanishmdhr/react-video-player
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @sanishmdhr/react-video-player
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Peer dependencies** — install if not already in your project:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install react react-dom
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { VideoPlayer } from "@sanishmdhr/react-video-player";
|
|
21
|
+
import "@sanishmdhr/react-video-player/styles";
|
|
22
|
+
|
|
23
|
+
export default function App() {
|
|
24
|
+
return (
|
|
25
|
+
<VideoPlayer
|
|
26
|
+
src="https://example.com/video.mp4"
|
|
27
|
+
controls
|
|
28
|
+
autoplay={false}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> **Next.js** — import the styles in your root `layout.tsx` and mark the component as `"use client"` or wrap it in a client component.
|
|
35
|
+
|
|
36
|
+
## HLS Streaming
|
|
37
|
+
|
|
38
|
+
Pass any `.m3u8` URL — HLS.js is initialised automatically:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<VideoPlayer
|
|
42
|
+
src="https://example.com/stream.m3u8"
|
|
43
|
+
controls
|
|
44
|
+
enableHLS // default: true
|
|
45
|
+
/>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
On Safari the browser's native HLS engine is used. A **LIVE** badge and **GO LIVE** button appear automatically for live streams.
|
|
49
|
+
|
|
50
|
+
## Props
|
|
51
|
+
|
|
52
|
+
| Prop | Type | Default | Description |
|
|
53
|
+
|------|------|---------|-------------|
|
|
54
|
+
| `src` | `string` | — | Video URL (MP4, WebM, HLS `.m3u8`, …) |
|
|
55
|
+
| `poster` | `string` | — | Poster image shown before playback |
|
|
56
|
+
| `controls` | `boolean` | `true` | Show the built-in control bar |
|
|
57
|
+
| `autoplay` | `boolean` | `false` | Start playback on mount |
|
|
58
|
+
| `muted` | `boolean` | `false` | Start muted |
|
|
59
|
+
| `loop` | `boolean` | `false` | Loop the video |
|
|
60
|
+
| `preload` | `"none" \| "metadata" \| "auto"` | `"metadata"` | Native `preload` attribute |
|
|
61
|
+
| `playbackRates` | `PlaybackRate[]` | `[0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]` | Available speed options |
|
|
62
|
+
| `enableHLS` | `boolean` | `true` | Enable HLS.js for `.m3u8` sources |
|
|
63
|
+
| `enablePreview` | `boolean` | `true` | Show thumbnail preview on progress bar hover (disabled automatically for HLS) |
|
|
64
|
+
| `hlsConfig` | `Partial<HlsConfig>` | — | Override any [hls.js configuration](https://github.com/video-dev/hls.js/blob/master/docs/API.md#fine-tuning) option |
|
|
65
|
+
| `subtitles` | `SubtitleTrack[]` | — | Subtitle / caption tracks |
|
|
66
|
+
| `crossOrigin` | `"anonymous" \| "use-credentials"` | — | CORS attribute for the video element |
|
|
67
|
+
| `className` | `string` | — | CSS class on the player container |
|
|
68
|
+
| `onPlay` | `() => void` | — | Fired when playback starts |
|
|
69
|
+
| `onPause` | `() => void` | — | Fired when playback pauses |
|
|
70
|
+
| `onEnded` | `() => void` | — | Fired when playback ends |
|
|
71
|
+
| `onError` | `(error: VideoError) => void` | — | Fired on playback or stream errors |
|
|
72
|
+
| `onTimeUpdate` | `(time: number) => void` | — | Fired every ~250 ms during playback |
|
|
73
|
+
| `onDurationChange` | `(duration: number) => void` | — | Fired when video duration becomes known |
|
|
74
|
+
| `onBuffering` | `(isBuffering: boolean) => void` | — | Fired when buffering starts / stops |
|
|
75
|
+
|
|
76
|
+
## Imperative API (Ref)
|
|
77
|
+
|
|
78
|
+
Use a `ref` to control the player programmatically:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { useRef } from "react";
|
|
82
|
+
import { VideoPlayer, VideoPlayerRef } from "@sanishmdhr/react-video-player";
|
|
83
|
+
|
|
84
|
+
export default function App() {
|
|
85
|
+
const playerRef = useRef<VideoPlayerRef>(null);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<>
|
|
89
|
+
<VideoPlayer ref={playerRef} src="..." controls />
|
|
90
|
+
<button onClick={() => playerRef.current?.play()}>Play</button>
|
|
91
|
+
<button onClick={() => playerRef.current?.pause()}>Pause</button>
|
|
92
|
+
<button onClick={() => playerRef.current?.seek(30)}>Jump to 30s</button>
|
|
93
|
+
<button onClick={() => playerRef.current?.setVolume(0.5)}>50% volume</button>
|
|
94
|
+
<button onClick={() => playerRef.current?.setPlaybackRate(1.5)}>1.5× speed</button>
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `VideoPlayerRef` methods
|
|
101
|
+
|
|
102
|
+
| Method | Signature | Description |
|
|
103
|
+
|--------|-----------|-------------|
|
|
104
|
+
| `play` | `() => Promise<void>` | Start playback |
|
|
105
|
+
| `pause` | `() => void` | Pause playback |
|
|
106
|
+
| `seek` | `(time: number) => void` | Seek to a time in seconds |
|
|
107
|
+
| `setVolume` | `(volume: number) => void` | Set volume `0–1` |
|
|
108
|
+
| `toggleMute` | `() => void` | Toggle mute, restoring the pre-mute volume |
|
|
109
|
+
| `setPlaybackRate` | `(rate: PlaybackRate) => void` | Set playback speed |
|
|
110
|
+
| `setQualityLevel` | `(level: number) => void` | Set HLS quality level; `-1` = auto ABR |
|
|
111
|
+
| `seekToLive` | `() => void` | Jump to the live edge (HLS live streams) |
|
|
112
|
+
| `toggleFullscreen` | `() => Promise<void>` | Toggle fullscreen |
|
|
113
|
+
| `togglePictureInPicture` | `() => Promise<void>` | Toggle Picture-in-Picture |
|
|
114
|
+
| `getState` | `() => PlayerState` | Snapshot of current player state |
|
|
115
|
+
| `getVideoElement` | `() => HTMLVideoElement \| null` | Access the underlying `<video>` element |
|
|
116
|
+
|
|
117
|
+
## Subtitles
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<VideoPlayer
|
|
121
|
+
src="https://example.com/video.mp4"
|
|
122
|
+
subtitles={[
|
|
123
|
+
{ id: "en", src: "/subs/en.vtt", label: "English", srclang: "en", default: true },
|
|
124
|
+
{ id: "es", src: "/subs/es.vtt", label: "Español", srclang: "es" },
|
|
125
|
+
]}
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Keyboard Shortcuts
|
|
130
|
+
|
|
131
|
+
Shortcuts activate when the player has focus (click the player or tab to it).
|
|
132
|
+
|
|
133
|
+
| Key | Action |
|
|
134
|
+
|-----|--------|
|
|
135
|
+
| `Space` / `K` | Play / Pause |
|
|
136
|
+
| `←` / `→` | Seek −5 s / +5 s |
|
|
137
|
+
| `↑` / `↓` | Volume +10% / −10% |
|
|
138
|
+
| `M` | Toggle mute (restores previous volume) |
|
|
139
|
+
| `F` | Toggle fullscreen |
|
|
140
|
+
| `P` | Toggle Picture-in-Picture |
|
|
141
|
+
| `L` | Seek to live edge (live streams only) |
|
|
142
|
+
| `0`–`9` | Jump to 0%–90% of duration |
|
|
143
|
+
|
|
144
|
+
Progress bar keyboard (when the progress bar has focus):
|
|
145
|
+
|
|
146
|
+
| Key | Action |
|
|
147
|
+
|-----|--------|
|
|
148
|
+
| `←` / `→` | Seek −5 s / +5 s |
|
|
149
|
+
| `Shift + ←` / `Shift + →` | Seek −10 s / +10 s |
|
|
150
|
+
| `Home` | Jump to start |
|
|
151
|
+
| `End` | Jump to end |
|
|
152
|
+
|
|
153
|
+
## TypeScript
|
|
154
|
+
|
|
155
|
+
All types are exported from the package:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import type {
|
|
159
|
+
VideoPlayerProps,
|
|
160
|
+
VideoPlayerRef,
|
|
161
|
+
PlayerState,
|
|
162
|
+
PlaybackRate,
|
|
163
|
+
HLSQualityLevel,
|
|
164
|
+
SubtitleTrack,
|
|
165
|
+
BufferedRange,
|
|
166
|
+
VideoError,
|
|
167
|
+
VideoErrorCode,
|
|
168
|
+
} from "@sanishmdhr/react-video-player";
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### `PlayerState`
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
interface PlayerState {
|
|
175
|
+
isPlaying: boolean;
|
|
176
|
+
currentTime: number;
|
|
177
|
+
duration: number;
|
|
178
|
+
volume: number;
|
|
179
|
+
isMuted: boolean;
|
|
180
|
+
playbackRate: number;
|
|
181
|
+
bufferedRanges: BufferedRange[];
|
|
182
|
+
isBuffering: boolean;
|
|
183
|
+
error: VideoError | null;
|
|
184
|
+
isFullscreen: boolean;
|
|
185
|
+
isPictureInPicture: boolean;
|
|
186
|
+
isLive: boolean;
|
|
187
|
+
qualityLevels: HLSQualityLevel[];
|
|
188
|
+
currentQualityLevel: number; // -1 = ABR auto
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `VideoError`
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
type VideoErrorCode =
|
|
196
|
+
| "MEDIA_ERR_ABORTED"
|
|
197
|
+
| "MEDIA_ERR_NETWORK"
|
|
198
|
+
| "MEDIA_ERR_DECODE"
|
|
199
|
+
| "MEDIA_ERR_SRC_NOT_SUPPORTED"
|
|
200
|
+
| "HLS_NETWORK_ERROR"
|
|
201
|
+
| "HLS_FATAL_ERROR"
|
|
202
|
+
| "UNKNOWN";
|
|
203
|
+
|
|
204
|
+
interface VideoError {
|
|
205
|
+
code: VideoErrorCode;
|
|
206
|
+
message: string;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Project Structure
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
react-video-player/
|
|
214
|
+
├── src/ # Library source
|
|
215
|
+
│ ├── components/ # VideoPlayer, Controls, control elements
|
|
216
|
+
│ ├── hooks/ # useVideoPlayer (state + HLS init)
|
|
217
|
+
│ ├── lib/ # Types, HLS utilities, format helpers
|
|
218
|
+
│ └── styles/ # CSS
|
|
219
|
+
├── examples/
|
|
220
|
+
│ └── nextjs-demo/ # Standalone Next.js demo app
|
|
221
|
+
├── dist/ # Build output (ESM + CJS + DTS)
|
|
222
|
+
├── package.json
|
|
223
|
+
├── tsconfig.json
|
|
224
|
+
└── tsup.config.ts
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Install dependencies
|
|
231
|
+
npm install
|
|
232
|
+
|
|
233
|
+
# Build the library
|
|
234
|
+
npm run build
|
|
235
|
+
|
|
236
|
+
# Watch mode (rebuild on changes)
|
|
237
|
+
npm run dev
|
|
238
|
+
|
|
239
|
+
# Type-check only
|
|
240
|
+
npm run typecheck
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
To run the demo app against your local build:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
cd examples/nextjs-demo
|
|
247
|
+
npm install
|
|
248
|
+
npm run dev
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Publishing
|
|
252
|
+
|
|
253
|
+
`prepublishOnly` runs the build automatically:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
npm publish
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
MIT
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
.controlButton{background:none;border:none;color:#fff;cursor:pointer;padding:10px;min-width:40px;min-height:40px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:opacity .15s,background-color .15s,transform .1s;flex-shrink:0}.controlButton:hover{background-color:#ffffff1f;opacity:1}.controlButton:active{transform:scale(.92)}.controlButton svg{width:20px;height:20px;pointer-events:none}@media(max-width:480px){.controlButton{padding:8px;min-width:36px;min-height:36px}}.volumeContainer{position:relative;display:flex;align-items:center}.volumeSlider{width:80px;cursor:pointer;-webkit-appearance:none;appearance:none;background:#ffffff4d;border-radius:2px;height:4px;outline:none;transition:width .15s;flex-shrink:0}.volumeSlider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:12px;height:12px;background:#fff;border-radius:50%;cursor:pointer;box-shadow:0 1px 4px #0006}.volumeSlider::-moz-range-thumb{width:12px;height:12px;background:#fff;border-radius:50%;cursor:pointer;border:none;box-shadow:0 1px 4px #0006}.timeDisplay{color:#fff;font-size:13px;font-weight:500;user-select:none;white-space:nowrap;padding:0 4px;letter-spacing:.01em}.settingsContainer{position:relative}.settingsDropdown{position:absolute;bottom:calc(100% + 8px);right:0;background-color:#0f0f0ff2;border-radius:6px;padding:6px;min-width:150px;z-index:30;box-shadow:0 4px 24px #0009;backdrop-filter:blur(8px)}.settingsTabs{display:flex;border-bottom:1px solid rgba(255,255,255,.12);margin-bottom:4px}.settingsTab{flex:1;background:none;border:none;color:#fff9;cursor:pointer;font-size:12px;font-weight:600;padding:6px 0;letter-spacing:.04em;border-bottom:2px solid transparent;transition:color .15s,border-color .15s;text-transform:uppercase}.settingsTab.active{color:#fff;border-bottom-color:#3b82f6}.settingsTab:hover:not(.active){color:#ffffffe6}.settingsPanelLabel{color:#ffffff80;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;padding:4px 8px 2px}.settingsOption{display:flex;align-items:center;justify-content:space-between;width:100%;padding:7px 10px;background:none;border:none;color:#ffffffd9;cursor:pointer;text-align:left;border-radius:4px;font-size:13px;transition:background-color .15s}.settingsOption:hover{background-color:#ffffff1a;color:#fff}.settingsOption.active{color:#60a5fa;font-weight:600}.settingsOptionBadge{font-size:10px;color:#fff6;margin-left:8px;flex-shrink:0}.progressContainer{position:relative;width:100%;padding:10px 0;cursor:pointer;box-sizing:content-box}.progressContainer:focus{outline:none}.progressContainer:focus-visible{outline:2px solid #60a5fa;outline-offset:2px;border-radius:2px}.previewVideo{display:none}.previewTooltip{position:absolute;bottom:calc(100% + 6px);transform:translate(-50%);pointer-events:none;z-index:20;background-color:#000;border-radius:4px;overflow:hidden;box-shadow:0 4px 16px #00000080}.previewCanvas{display:block;width:160px;height:90px}.previewTime{padding:3px 8px;font-size:11px;font-weight:600;color:#fff;text-align:center;background-color:#000000b3}.progressBackground{position:relative;width:100%;height:4px;background-color:#ffffff40;border-radius:2px;overflow:hidden;transition:height .15s;will-change:height}.progressContainer:hover .progressBackground{height:6px}.bufferedSegment{position:absolute;top:0;height:100%;background-color:#ffffff73;border-radius:2px}.progressFilled{position:absolute;top:0;left:0;height:100%;background-color:#3b82f6;border-radius:2px;will-change:width}.hoverIndicator{position:absolute;top:50%;transform:translate(-50%,-50%);width:2px;height:100%;background-color:#fffc;pointer-events:none}.scrubHandle{position:absolute;top:50%;transform:translate(-50%,-50%);width:14px;height:14px;background-color:#fff;border-radius:50%;box-shadow:0 1px 6px #00000080;pointer-events:none;transition:transform .12s,opacity .12s;opacity:0;z-index:2;will-change:transform,opacity}.progressContainer:hover .scrubHandle,.scrubHandle.dragging{opacity:1}.scrubHandle.dragging{transform:translate(-50%,-50%) scale(1.25);transition:none}
|
|
2
|
+
/*# sourceMappingURL=index.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/styles/ControlElements.css","../src/styles/ProgressBar.css"],"sourcesContent":["/* ─── Control button base ────────────────────────────────────────────────── */\n.controlButton {\n background: none;\n border: none;\n color: #fff;\n cursor: pointer;\n padding: 10px;\n min-width: 40px;\n min-height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n transition: opacity 0.15s, background-color 0.15s, transform 0.1s;\n flex-shrink: 0;\n}\n\n.controlButton:hover {\n background-color: rgba(255, 255, 255, 0.12);\n opacity: 1;\n}\n\n.controlButton:active {\n transform: scale(0.92);\n}\n\n.controlButton svg {\n width: 20px;\n height: 20px;\n pointer-events: none;\n}\n\n@media (max-width: 480px) {\n .controlButton {\n padding: 8px;\n min-width: 36px;\n min-height: 36px;\n }\n}\n\n/* ─── Volume control ─────────────────────────────────────────────────────── */\n.volumeContainer {\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.volumeSlider {\n width: 80px;\n cursor: pointer;\n -webkit-appearance: none;\n appearance: none;\n background: rgba(255, 255, 255, 0.3);\n border-radius: 2px;\n height: 4px;\n outline: none;\n transition: width 0.15s;\n flex-shrink: 0;\n}\n\n.volumeSlider::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 12px;\n height: 12px;\n background: #fff;\n border-radius: 50%;\n cursor: pointer;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);\n}\n\n.volumeSlider::-moz-range-thumb {\n width: 12px;\n height: 12px;\n background: #fff;\n border-radius: 50%;\n cursor: pointer;\n border: none;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);\n}\n\n/* ─── Time display ───────────────────────────────────────────────────────── */\n.timeDisplay {\n color: #fff;\n font-size: 13px;\n font-weight: 500;\n user-select: none;\n white-space: nowrap;\n padding: 0 4px;\n letter-spacing: 0.01em;\n}\n\n/* ─── Settings menu ──────────────────────────────────────────────────────── */\n.settingsContainer {\n position: relative;\n}\n\n.settingsDropdown {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background-color: rgba(15, 15, 15, 0.95);\n border-radius: 6px;\n padding: 6px;\n min-width: 150px;\n z-index: 30;\n box-shadow: 0 4px 24px rgba(0, 0, 0, 0.6);\n backdrop-filter: blur(8px);\n}\n\n/* Tabs (Speed / Quality) */\n.settingsTabs {\n display: flex;\n border-bottom: 1px solid rgba(255, 255, 255, 0.12);\n margin-bottom: 4px;\n}\n\n.settingsTab {\n flex: 1;\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n font-size: 12px;\n font-weight: 600;\n padding: 6px 0;\n letter-spacing: 0.04em;\n border-bottom: 2px solid transparent;\n transition: color 0.15s, border-color 0.15s;\n text-transform: uppercase;\n}\n\n.settingsTab.active {\n color: #fff;\n border-bottom-color: #3b82f6;\n}\n\n.settingsTab:hover:not(.active) {\n color: rgba(255, 255, 255, 0.9);\n}\n\n/* Panel section label */\n.settingsPanelLabel {\n color: rgba(255, 255, 255, 0.5);\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n padding: 4px 8px 2px;\n}\n\n/* Option rows */\n.settingsOption {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n padding: 7px 10px;\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.85);\n cursor: pointer;\n text-align: left;\n border-radius: 4px;\n font-size: 13px;\n transition: background-color 0.15s;\n}\n\n.settingsOption:hover {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n}\n\n.settingsOption.active {\n color: #60a5fa;\n font-weight: 600;\n}\n\n.settingsOptionBadge {\n font-size: 10px;\n color: rgba(255, 255, 255, 0.4);\n margin-left: 8px;\n flex-shrink: 0;\n}\n","/* ─── Progress bar container ─────────────────────────────────────────────── */\n.progressContainer {\n position: relative;\n width: 100%;\n /* Tall hit area so scrub feels easy to grab on touch and mouse */\n padding: 10px 0;\n cursor: pointer;\n /* Expand the clickable zone without affecting layout neighbours */\n box-sizing: content-box;\n}\n\n.progressContainer:focus {\n outline: none;\n}\n\n.progressContainer:focus-visible {\n outline: 2px solid #60a5fa;\n outline-offset: 2px;\n border-radius: 2px;\n}\n\n/* ─── Hidden preview video ───────────────────────────────────────────────── */\n.previewVideo {\n display: none;\n}\n\n/* ─── Thumbnail tooltip ──────────────────────────────────────────────────── */\n.previewTooltip {\n position: absolute;\n /* Sits above the container; bottom 100% + a small gap */\n bottom: calc(100% + 6px);\n transform: translateX(-50%);\n pointer-events: none;\n z-index: 20;\n background-color: #000;\n border-radius: 4px;\n overflow: hidden;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);\n}\n\n.previewCanvas {\n display: block;\n width: 160px;\n height: 90px;\n}\n\n.previewTime {\n padding: 3px 8px;\n font-size: 11px;\n font-weight: 600;\n color: #fff;\n text-align: center;\n background-color: rgba(0, 0, 0, 0.7);\n}\n\n/* ─── Track background ───────────────────────────────────────────────────── */\n.progressBackground {\n position: relative;\n width: 100%;\n height: 4px;\n background-color: rgba(255, 255, 255, 0.25);\n border-radius: 2px;\n /* Keep overflow:hidden for buffered / filled bars ONLY.\n The scrub handle must live OUTSIDE this element. */\n overflow: hidden;\n transition: height 0.15s;\n will-change: height;\n}\n\n/* Grow the track on hover for a \"YouTube-style\" feel */\n.progressContainer:hover .progressBackground {\n height: 6px;\n}\n\n/* ─── Buffered range indicator ───────────────────────────────────────────── */\n.bufferedSegment {\n position: absolute;\n top: 0;\n height: 100%;\n background-color: rgba(255, 255, 255, 0.45);\n border-radius: 2px;\n}\n\n/* ─── Played progress fill ───────────────────────────────────────────────── */\n.progressFilled {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background-color: #3b82f6;\n border-radius: 2px;\n will-change: width;\n}\n\n/* ─── Hover position indicator (thin white line) ────────────────────────── */\n.hoverIndicator {\n position: absolute;\n top: 50%;\n transform: translate(-50%, -50%);\n width: 2px;\n height: 100%;\n background-color: rgba(255, 255, 255, 0.8);\n pointer-events: none;\n}\n\n/* ─── Scrub handle ───────────────────────────────────────────────────────── */\n/*\n * The handle is a SIBLING of .progressBackground (outside overflow:hidden).\n * It is positioned absolutely within .progressContainer, which provides\n * the full padded height. `top: 50%` centers it on the container mid-line,\n * which aligns with the track regardless of the container's padding.\n *\n * Fix: previously the handle was inside overflow:hidden and only a tiny\n * sliver was visible on the 4px-tall track.\n */\n.scrubHandle {\n position: absolute;\n top: 50%;\n transform: translate(-50%, -50%);\n width: 14px;\n height: 14px;\n background-color: #fff;\n border-radius: 50%;\n box-shadow: 0 1px 6px rgba(0, 0, 0, 0.5);\n pointer-events: none;\n transition: transform 0.12s, opacity 0.12s;\n /* Hidden by default; shown on container hover */\n opacity: 0;\n z-index: 2;\n will-change: transform, opacity;\n}\n\n.progressContainer:hover .scrubHandle,\n.scrubHandle.dragging {\n opacity: 1;\n}\n\n.scrubHandle.dragging {\n transform: translate(-50%, -50%) scale(1.25);\n transition: none;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import react__default from 'react';
|
|
3
|
+
import { HlsConfig } from 'hls.js';
|
|
4
|
+
|
|
5
|
+
interface BufferedRange {
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
}
|
|
9
|
+
type VideoErrorCode = "MEDIA_ERR_ABORTED" | "MEDIA_ERR_NETWORK" | "MEDIA_ERR_DECODE" | "MEDIA_ERR_SRC_NOT_SUPPORTED" | "HLS_NETWORK_ERROR" | "HLS_FATAL_ERROR" | "UNKNOWN";
|
|
10
|
+
interface VideoError {
|
|
11
|
+
code: VideoErrorCode;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
interface HLSQualityLevel {
|
|
15
|
+
id: number;
|
|
16
|
+
height: number;
|
|
17
|
+
width: number;
|
|
18
|
+
bitrate: number;
|
|
19
|
+
/** Display name e.g. "1080p", "720p", "Auto" */
|
|
20
|
+
name: string;
|
|
21
|
+
}
|
|
22
|
+
interface SubtitleTrack {
|
|
23
|
+
id: string;
|
|
24
|
+
src: string;
|
|
25
|
+
label: string;
|
|
26
|
+
srclang: string;
|
|
27
|
+
default?: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface PlayerState {
|
|
30
|
+
isPlaying: boolean;
|
|
31
|
+
currentTime: number;
|
|
32
|
+
duration: number;
|
|
33
|
+
volume: number;
|
|
34
|
+
isMuted: boolean;
|
|
35
|
+
playbackRate: number;
|
|
36
|
+
bufferedRanges: BufferedRange[];
|
|
37
|
+
isBuffering: boolean;
|
|
38
|
+
error: VideoError | null;
|
|
39
|
+
isFullscreen: boolean;
|
|
40
|
+
isPictureInPicture: boolean;
|
|
41
|
+
/** True when the stream is a live HLS stream (Infinity duration) */
|
|
42
|
+
isLive: boolean;
|
|
43
|
+
/** Available HLS quality levels; empty for non-HLS sources */
|
|
44
|
+
qualityLevels: HLSQualityLevel[];
|
|
45
|
+
/** Currently active quality level id; -1 = ABR auto */
|
|
46
|
+
currentQualityLevel: number;
|
|
47
|
+
}
|
|
48
|
+
type PlaybackRate = 0.25 | 0.5 | 0.75 | 1 | 1.25 | 1.5 | 1.75 | 2;
|
|
49
|
+
interface VideoPlayerRef {
|
|
50
|
+
play: () => Promise<void>;
|
|
51
|
+
pause: () => void;
|
|
52
|
+
seek: (time: number) => void;
|
|
53
|
+
setVolume: (volume: number) => void;
|
|
54
|
+
/** Toggle mute while remembering the pre-mute volume */
|
|
55
|
+
toggleMute: () => void;
|
|
56
|
+
setPlaybackRate: (rate: PlaybackRate) => void;
|
|
57
|
+
/** Set HLS quality level; pass -1 for automatic ABR */
|
|
58
|
+
setQualityLevel: (level: number) => void;
|
|
59
|
+
/** Jump to the live edge of an HLS live stream */
|
|
60
|
+
seekToLive: () => void;
|
|
61
|
+
toggleFullscreen: () => Promise<void>;
|
|
62
|
+
togglePictureInPicture: () => Promise<void>;
|
|
63
|
+
getState: () => PlayerState;
|
|
64
|
+
getVideoElement: () => HTMLVideoElement | null;
|
|
65
|
+
}
|
|
66
|
+
interface VideoPlayerProps {
|
|
67
|
+
src: string;
|
|
68
|
+
poster?: string;
|
|
69
|
+
autoplay?: boolean;
|
|
70
|
+
muted?: boolean;
|
|
71
|
+
loop?: boolean;
|
|
72
|
+
controls?: boolean;
|
|
73
|
+
preload?: "none" | "metadata" | "auto";
|
|
74
|
+
playbackRates?: PlaybackRate[];
|
|
75
|
+
className?: string;
|
|
76
|
+
enableHLS?: boolean;
|
|
77
|
+
enablePreview?: boolean;
|
|
78
|
+
/** Additional hls.js configuration options */
|
|
79
|
+
hlsConfig?: Partial<HlsConfig>;
|
|
80
|
+
/** Subtitle / caption tracks */
|
|
81
|
+
subtitles?: SubtitleTrack[];
|
|
82
|
+
/** crossorigin attribute for CORS-enabled preview thumbnails */
|
|
83
|
+
crossOrigin?: "anonymous" | "use-credentials";
|
|
84
|
+
onPlay?: () => void;
|
|
85
|
+
onPause?: () => void;
|
|
86
|
+
onEnded?: () => void;
|
|
87
|
+
onError?: (error: VideoError) => void;
|
|
88
|
+
onTimeUpdate?: (time: number) => void;
|
|
89
|
+
onDurationChange?: (duration: number) => void;
|
|
90
|
+
onBuffering?: (isBuffering: boolean) => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
declare const VideoPlayer: react__default.ForwardRefExoticComponent<VideoPlayerProps & react__default.RefAttributes<VideoPlayerRef>>;
|
|
94
|
+
|
|
95
|
+
interface ControlsProps {
|
|
96
|
+
playerRef: VideoPlayerRef;
|
|
97
|
+
/** Ref to the outer player container; used to scope keyboard shortcuts to the focused player */
|
|
98
|
+
playerContainerRef: react__default.RefObject<HTMLElement | null>;
|
|
99
|
+
playbackRates: PlaybackRate[];
|
|
100
|
+
enablePreview: boolean;
|
|
101
|
+
isPlaying: boolean;
|
|
102
|
+
currentTime: number;
|
|
103
|
+
duration: number;
|
|
104
|
+
volume: number;
|
|
105
|
+
isMuted: boolean;
|
|
106
|
+
playbackRate: number;
|
|
107
|
+
isFullscreen: boolean;
|
|
108
|
+
isPictureInPicture: boolean;
|
|
109
|
+
isLive: boolean;
|
|
110
|
+
qualityLevels: HLSQualityLevel[];
|
|
111
|
+
currentQualityLevel: number;
|
|
112
|
+
bufferedRanges: BufferedRange[];
|
|
113
|
+
}
|
|
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
|
+
declare const Controls: react__default.FC<ControlsProps>;
|
|
121
|
+
|
|
122
|
+
interface TimeDisplayProps {
|
|
123
|
+
currentTime: number;
|
|
124
|
+
duration: number;
|
|
125
|
+
isLive?: boolean;
|
|
126
|
+
}
|
|
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.
|
|
131
|
+
*/
|
|
132
|
+
declare const TimeDisplay: react.NamedExoticComponent<TimeDisplayProps>;
|
|
133
|
+
|
|
134
|
+
interface SettingsMenuProps {
|
|
135
|
+
currentRate: number;
|
|
136
|
+
playbackRates: PlaybackRate[];
|
|
137
|
+
onRateChange: (rate: PlaybackRate) => void;
|
|
138
|
+
qualityLevels?: HLSQualityLevel[];
|
|
139
|
+
currentQualityLevel?: number;
|
|
140
|
+
onQualityChange?: (level: number) => void;
|
|
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
|
+
declare const SettingsMenu: react.NamedExoticComponent<SettingsMenuProps>;
|
|
151
|
+
|
|
152
|
+
interface ProgressBarProps {
|
|
153
|
+
playerRef: VideoPlayerRef;
|
|
154
|
+
currentTime: number;
|
|
155
|
+
duration: number;
|
|
156
|
+
bufferedRanges: BufferedRange[];
|
|
157
|
+
enablePreview?: boolean;
|
|
158
|
+
}
|
|
159
|
+
declare const ProgressBar: react__default.FC<ProgressBarProps>;
|
|
160
|
+
|
|
161
|
+
interface VolumeControlProps {
|
|
162
|
+
volume: number;
|
|
163
|
+
isMuted: boolean;
|
|
164
|
+
onVolumeChange: (volume: number) => void;
|
|
165
|
+
onToggleMute: () => void;
|
|
166
|
+
}
|
|
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
|
+
declare const VolumeControl: react.NamedExoticComponent<VolumeControlProps>;
|
|
173
|
+
|
|
174
|
+
interface PlayButtonProps {
|
|
175
|
+
onClick: () => void;
|
|
176
|
+
}
|
|
177
|
+
interface PauseButtonProps {
|
|
178
|
+
onClick: () => void;
|
|
179
|
+
}
|
|
180
|
+
interface FullscreenButtonProps {
|
|
181
|
+
onClick: () => void;
|
|
182
|
+
isFullscreen?: boolean;
|
|
183
|
+
}
|
|
184
|
+
interface PiPButtonProps {
|
|
185
|
+
onClick: () => void;
|
|
186
|
+
isPiP?: boolean;
|
|
187
|
+
}
|
|
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
|
+
declare const PlayButton: react.NamedExoticComponent<PlayButtonProps>;
|
|
195
|
+
declare const PauseButton: react.NamedExoticComponent<PauseButtonProps>;
|
|
196
|
+
declare const FullscreenButton: react.NamedExoticComponent<FullscreenButtonProps>;
|
|
197
|
+
declare const PiPButton: react.NamedExoticComponent<PiPButtonProps>;
|
|
198
|
+
|
|
199
|
+
declare const ControlElements: {
|
|
200
|
+
PlayButton: react.NamedExoticComponent<PlayButtonProps>;
|
|
201
|
+
PauseButton: react.NamedExoticComponent<PauseButtonProps>;
|
|
202
|
+
FullscreenButton: react.NamedExoticComponent<FullscreenButtonProps>;
|
|
203
|
+
PiPButton: react.NamedExoticComponent<PiPButtonProps>;
|
|
204
|
+
VolumeControl: react.NamedExoticComponent<VolumeControlProps>;
|
|
205
|
+
ProgressBar: react.FC<ProgressBarProps>;
|
|
206
|
+
SettingsMenu: react.NamedExoticComponent<SettingsMenuProps>;
|
|
207
|
+
TimeDisplay: react.NamedExoticComponent<TimeDisplayProps>;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
declare const index_ControlElements: typeof ControlElements;
|
|
211
|
+
declare const index_FullscreenButton: typeof FullscreenButton;
|
|
212
|
+
type index_FullscreenButtonProps = FullscreenButtonProps;
|
|
213
|
+
declare const index_PauseButton: typeof PauseButton;
|
|
214
|
+
type index_PauseButtonProps = PauseButtonProps;
|
|
215
|
+
declare const index_PiPButton: typeof PiPButton;
|
|
216
|
+
type index_PiPButtonProps = PiPButtonProps;
|
|
217
|
+
declare const index_PlayButton: typeof PlayButton;
|
|
218
|
+
type index_PlayButtonProps = PlayButtonProps;
|
|
219
|
+
declare const index_ProgressBar: typeof ProgressBar;
|
|
220
|
+
type index_ProgressBarProps = ProgressBarProps;
|
|
221
|
+
declare const index_SettingsMenu: typeof SettingsMenu;
|
|
222
|
+
type index_SettingsMenuProps = SettingsMenuProps;
|
|
223
|
+
declare const index_TimeDisplay: typeof TimeDisplay;
|
|
224
|
+
type index_TimeDisplayProps = TimeDisplayProps;
|
|
225
|
+
declare const index_VolumeControl: typeof VolumeControl;
|
|
226
|
+
type index_VolumeControlProps = VolumeControlProps;
|
|
227
|
+
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 };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Format seconds → MM:SS or HH:MM:SS
|
|
233
|
+
*/
|
|
234
|
+
declare function formatTime(seconds: number): string;
|
|
235
|
+
/**
|
|
236
|
+
* Detect an HLS stream URL regardless of query-string parameters.
|
|
237
|
+
*/
|
|
238
|
+
declare function isHLSUrl(url: string): boolean;
|
|
239
|
+
/**
|
|
240
|
+
* Return the MIME type for a given video URL.
|
|
241
|
+
*/
|
|
242
|
+
declare function getMimeType(url: string): string;
|
|
243
|
+
|
|
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 };
|