usehlsvideoplayer 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.1
4
+
5
+ - Added a shorter React quick start and a usage guide for core playback, live controls, recovery, and authenticated streams.
6
+ - Separated the API reference, browser compatibility, troubleshooting, and migration guides.
7
+ - Restored the build toolchain used for 2.0.0 to fix clean development installs.
8
+
3
9
  ## 2.0.0
4
10
 
5
11
  - Added an independent playback controller and native and hls.js adapters.
package/README.md CHANGED
@@ -1,74 +1,53 @@
1
1
  # usehlsvideoplayer
2
2
 
3
- Headless HLS playback for an HTML video element. Use the React hooks or the framework-independent TypeScript controller. The library selects hls.js where MSE or Managed Media Source is supported and falls back to browser-native HLS.
3
+ Play HLS streams in an HTML video element with React hooks or a TypeScript controller. You supply the video element and choose between the browser's controls and your own.
4
4
 
5
- The examples below target version 2.0.0.
5
+ ## Play a stream in React
6
6
 
7
- ## Quick start
7
+ 1. Install the package in a React app. The React entry supports React 18.2 and React 19.
8
8
 
9
- ```sh
10
- npm install usehlsvideoplayer
11
- ```
9
+ ```sh
10
+ npm install usehlsvideoplayer
11
+ ```
12
12
 
13
- The React entry supports React 18.2 and 19. The `/core` entry can be installed without React.
14
-
15
- ```tsx
16
- "use client";
17
-
18
- import { useHlsPlayer } from "usehlsvideoplayer";
19
-
20
- export function StreamPlayer({ src }: { src: string }) {
21
- const {
22
- videoRef,
23
- state,
24
- controls,
25
- quality,
26
- audio,
27
- subtitles,
28
- live,
29
- metrics,
30
- error,
31
- } = useHlsPlayer({ src, autoPlay: false, lowLatency: true });
32
-
33
- return (
34
- <>
35
- <video ref={videoRef} controls playsInline />
36
- <button onClick={() => void controls.play()}>Play</button>
37
- <button disabled={!live.canSeekToLive} onClick={controls.seekToLive}>
38
- Seek to live
39
- </button>
40
- <p role="status">{state.status}</p>
41
- {error?.fatal && state.status === "error" && (
42
- <p role="alert">{error.message}</p>
43
- )}
44
- </>
45
- );
46
- }
47
- ```
13
+ 2. Create `StreamPlayer.tsx` with this component.
48
14
 
49
- Give the hook one video element and leave its `src` and `<source>` children unset. Pass a new `src` to replace the stream, or `null` to clear it. For Next.js App Router, put the player in a Client Component as shown above and pass a serializable URL from the Server Component. Server rendering yields an idle snapshot and starts no media requests.
15
+ ```tsx
16
+ "use client";
50
17
 
51
- ## Framework-independent usage
18
+ import { useHlsPlayer } from "usehlsvideoplayer";
52
19
 
53
- ```ts
54
- import { createPlaybackController } from "usehlsvideoplayer/core";
20
+ export function StreamPlayer({ src }: { src: string }) {
21
+ const { videoRef, state, error } = useHlsPlayer({ src });
55
22
 
56
- const player = createPlaybackController();
57
- const unsubscribe = player.subscribe(() => render(player.getState()));
58
- player.setSource(videoElement, "/streams/master.m3u8");
23
+ return (
24
+ <>
25
+ <video ref={videoRef} controls playsInline />
26
+ <p role="status">{state.status}</p>
27
+ {state.status === "error" && error && (
28
+ <p role="alert">{error.message}</p>
29
+ )}
30
+ </>
31
+ );
32
+ }
33
+ ```
59
34
 
60
- // On teardown
61
- unsubscribe();
62
- player.destroy();
63
- ```
35
+ 3. Render `<StreamPlayer src="/streams/master.m3u8" />` where you want the player. Replace the path with a reachable HLS playlist. If the stream uses a different origin, allow your app's origin on the HLS server for hls.js requests.
36
+ 4. Once the status reaches `ready`, select **Play** in the video controls. The status changes to `playing` when playback starts.
37
+
38
+ Pass the playlist URL to the hook. Leave the video's `src` attribute and `<source>` children unset. In Next.js App Router, keep `"use client"` at the top of the player module and pass the URL as a string prop. The server renders the `idle` state. Playback starts in the browser.
39
+
40
+ ## Framework-independent usage
41
+
42
+ The `usehlsvideoplayer/core` entry works without React. See how to [attach a stream to a video element and release it on teardown](docs/usage.md#play-a-stream-without-react).
64
43
 
65
44
  ## Documentation
66
45
 
67
- - [API, state transitions and streaming examples](docs/api.md)
68
- - [Browser compatibility and troubleshooting](docs/compatibility.md)
69
- - [Migrating from `Usehlsplayer`](docs/migration.md)
70
- - [Security and authenticated requests](SECURITY.md)
71
- - [Repository audit and engineering evidence](docs/modernization.md)
46
+ - [Usage guide](docs/usage.md) for source changes, live playback, track selection, recovery, and authenticated streams.
47
+ - [Playback API reference](docs/api.md) for options, commands, state, events, and metrics.
48
+ - [Browser compatibility](docs/compatibility.md) and [playback troubleshooting](docs/troubleshooting.md).
49
+ - [Migrate from 1.x](docs/migration.md) if you use `Usehlsplayer` or `useHlsVideoPlayer`.
50
+ - [Security policy](SECURITY.md) for request policy limits and private vulnerability reporting.
72
51
 
73
52
  ## Development
74
53
 
@@ -83,8 +62,8 @@ npm run fixtures
83
62
  npm run dev
84
63
  ```
85
64
 
86
- The loopback-only playground plays generated VOD, live, alternate audio and captions. Run `npx playwright install chromium` and `npm run test:browser:chromium` for the MSE suite. Run `npm run test:browser:native` with Playwright WebKit on a system with native HLS support.
65
+ Open the local URL printed by Vite to play generated VOD, live, alternate audio, and captions. See the [engineering record](docs/modernization.md) for browser checks and release notes.
87
66
 
88
67
  ## License
89
68
 
90
- ISC
69
+ [ISC](LICENSE)
package/SECURITY.md CHANGED
@@ -14,15 +14,7 @@ Report suspected vulnerabilities privately through GitHub Security Advisories wh
14
14
 
15
15
  Without `request`, hls.js or the native media element uses its normal network behaviour. Native playback can use signed URLs and browser cookies, but cannot receive injected headers or enforce the per-request allowlist. Set native `crossOrigin` on the element before attaching the source when required. Custom hls.js loaders, DRM and interstitials are trusted application integrations. Restricted request mode overrides custom loaders and disables EME and interstitial playback. Allowlisted origins use HTTPS, with HTTP permitted for local loopback development.
16
16
 
17
- ```tsx
18
- const request = {
19
- allowedOrigins: ["https://media.example.com"],
20
- credentials: "omit" as const,
21
- headers: () => ({ Authorization: `Bearer ${getCurrentToken()}` }),
22
- };
23
-
24
- const { videoRef, error } = useHlsPlayer({ src: signedManifest, request });
25
- ```
17
+ See [Send credentials with HLS requests](docs/usage.md#send-credentials-with-hls-requests) for a React example.
26
18
 
27
19
  Use a stable header provider in a component. A server receiving allowed credentials can read them. The browser and server still control CORS, cookie policy, caching and redirects outside the restricted path. A redirect blocked by Fetch can surface as a generic network or manifest failure because Fetch withholds response details.
28
20
 
package/docs/api.md CHANGED
@@ -1,98 +1,106 @@
1
- # Playback API
1
+ # Playback API reference
2
2
 
3
3
  ## React hooks
4
4
 
5
- | Hook | Contract |
6
- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
- | `useHlsPlayer(options)` | Owns one video and subscribes to playback and optional metrics. Returns `videoRef`, `store`, `state`, `controls`, `quality`, `audio`, `subtitles`, `live`, `metrics`, and `error`. |
8
- | `useHlsController(options)` | Returns a stable callback ref, the store, and stable controls. Its component does not subscribe to playback updates. |
9
- | `usePlaybackState(store, selector?, isEqual?)` | Reads the current snapshot or a selected value. Equality defaults to `Object.is`. Use a pure selector and provide equality when it returns a newly allocated object. |
10
- | `useHlsMetrics(store)` | Subscribes to the separate metrics store. Returns `null` until collection is enabled and a source is attached. |
11
-
12
- ```tsx
13
- import {
14
- useHlsController,
15
- usePlaybackState,
16
- type PlaybackStore,
17
- } from "usehlsvideoplayer";
18
-
19
- function Status({ store }: { store: PlaybackStore }) {
20
- const status = usePlaybackState(store, (state) => state.status);
21
- return <p role="status">{status}</p>;
22
- }
23
-
24
- export function Player({ src }: { src: string }) {
25
- const { videoRef, store } = useHlsController({ src });
26
- return (
27
- <>
28
- <video ref={videoRef} controls playsInline />
29
- <Status store={store} />
30
- </>
31
- );
32
- }
33
- ```
34
-
35
- ### Options
36
-
37
- | Option | Default and effect |
38
- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
- | `src` | No source. Accepts relative or absolute HTTP(S) URLs, resolved against the video's document. Empty or null clears playback. |
40
- | `autoPlay` | `false`. Requests playback at first readiness. Browser autoplay policy still applies. |
41
- | `lowLatency` | Uses the hls.js default when omitted. Setting it overrides `hlsConfig.lowLatencyMode`. Native playback uses browser policy. |
42
- | `engine` | `"auto"` prefers hls.js, then native. `"hls.js"` and `"native"` require the chosen capability. `request` requires hls.js. |
43
- | `metrics` | `false`. Enables a separate one-second metrics sample. |
44
- | `recovery` | Two automatic attempts after terminal upstream transient errors. Set `false` to disable, or configure attempts (0–5), base delay (default 500 ms), max delay (5 s) and per-attempt timeout (30 s). |
45
- | `hlsConfig` | Trusted partial hls.js configuration. Keep nested objects and callbacks stable between renders. |
46
- | `request` | Opt-in credential and origin policy. See [Security](../SECURITY.md). |
47
- | `onEvent` | Receives synchronous, URL-free transition, error and recovery events. Changing the callback keeps the session. |
48
-
49
- ## Controller and commands
50
-
51
- The root React entry and `usehlsvideoplayer/core` expose types. The core entry exports `createPlaybackController(factory?)` and `selectEngine`. An optional engine factory implements `EngineFactory`, creates an unattached adapter, and gives the controller sole ownership of attaching and destroying it. Use only one controller per media element.
52
-
53
- `setSource(video, src, options)` always replaces the previous session. `setSource(null)` clears it. `destroy()` clears subscribers, releases resources and permanently closes the controller. `subscribe`, `subscribeMetrics` and `onEvent` each return an unsubscribe function. Snapshot reads retain identity until a value changes. Treat snapshots as read-only.
54
-
55
- Controls return `true` when accepted and `false` when unavailable, invalid or rejected. `play()` resolves to a boolean and reports a browser autoplay rejection as a non-fatal `playback-blocked` error. Track selection confirms an accepted request. The selected rendition may take one or more segments to become current.
56
-
57
- | Commands | Semantics |
58
- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
59
- | `play()`, `pause()` | Request playback or pause. Use a user gesture when autoplay is blocked. |
60
- | `seek(seconds)` | Clamps finite seconds to the nearest seekable range, or finite media duration when ranges are unavailable. |
61
- | `setMuted(value)`, `setVolume(value)`, `setPlaybackRate(value)` | Applies a boolean, a volume clamped to 0–1, or a finite positive speed accepted by the browser. |
62
- | `setQuality(id \| "auto")` | Selects a listed level or restores ABR. Native selection is unavailable. |
63
- | `setAudioTrack(id)`, `setSubtitleTrack(id \| "off")` | Selects IDs from the current snapshot. IDs are scoped to the active track group. |
64
- | `seekToLive()` | Seeks to the advertised sync position, or slightly inside the last native seekable range. |
65
- | `startLoading()` | Starts hls.js loading, including streams configured with `autoStartLoad: false`. |
66
- | `retry()` | Starts a fresh attempt with a new budget after a terminal error on a retained source. Use a new URL when the old signature has expired. |
67
-
68
- ### State and events
69
-
70
- The snapshot contains a lifecycle `status`, active or last `engine`, current time, finite duration or `null`, buffered and seekable ranges, volume, mute and rate. `quality` separates requested selection from current level. `audio` and `subtitles` describe available tracks and selected IDs. `live` provides nullable detection, edge, sync position and latency estimate in seconds, plus a seek availability flag and a two-second tolerance around the sync target. `error` is a structured failure. `recovery` includes the current attempt, limit, phase and delay.
71
-
72
- The normal path is `idle → initialising → loading → ready → playing`. Browser events may skip ready when playback starts immediately. After readiness, actual playback may enter `paused`, `buffering`, `seeking` or `ended` and resume as media events permit. A fatal retryable failure can move the session to `recovering`, with success returning it to readiness. Non-retryable failure or exhaustion ends in `error`. Replacement goes through `idle`. `destroyed` is absorbing.
73
-
74
- Non-fatal hls.js errors emit diagnostics while its own retry and failover logic runs. Fatal transient failure destroys the old engine before scheduling a new one. The source-wide attempt budget persists across successful recoveries. Recovery attempts time out if playback never reaches readiness. Automatic reload restores the last known position when possible and preserves playback intent. Track choices can revert to stream defaults after reattachment.
75
-
76
- ### Live, quality and recovery
77
-
78
- ```tsx
79
- const { controls, state, live, quality } = useHlsPlayer({
80
- src: liveManifest,
81
- lowLatency: true,
82
- recovery: { maxAttempts: 2, baseDelayMs: 500 },
83
- onEvent(event) {
84
- if (event.type === "recovery") recordRecovery(event.recovery);
85
- },
86
- });
87
-
88
- // Wire these commands to UI actions.
89
- const goLive = () => live.canSeekToLive && controls.seekToLive();
90
- const selectQuality = (id: number | "auto") => controls.setQuality(id);
91
- const retry = () => state.status === "error" && controls.retry();
92
- ```
93
-
94
- Map `quality.levels` to your selector and include an Auto option. hls.js and the browser handle decoding, playlist parsing, ABR, LL-HLS protocol, buffer management and discontinuities. Server playlist design controls DVR availability. Live latency is the distance from the player-visible media edge, which can differ from glass-to-glass latency.
95
-
96
- ### Metrics
97
-
98
- `startupMs` measures from attachment to first playing, including any time waiting for the user. `playingMs`, `rebufferMs` and `rebufferCount` accumulate media-state intervals, excluding startup waits, seeks and explicit recovery. `rebufferRatio` is rebuffer time divided by playing plus rebuffer time. Frame counters depend on `getVideoPlaybackQuality()`. `liveLatency` is an estimate in seconds. Playback time samples from `timeupdate` are capped at four per second. Status and command changes can publish immediately.
5
+ The package root exports the hooks and playback types. The React hooks expose an idle server snapshot and attach media after a client render.
6
+
7
+ | Hook | Result |
8
+ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9
+ | `useHlsPlayer(options)` | Returns `videoRef`, `store`, `state`, `controls`, `quality`, `audio`, `subtitles`, `live`, `metrics`, and `error`. The component subscribes to state and metrics. |
10
+ | `useHlsController(options)` | Returns a stable callback `videoRef`, a stable `store`, and stable `controls`. The hook leaves playback and metrics subscriptions to the caller. |
11
+ | `usePlaybackState(store, selector?, isEqual?)` | Returns the full snapshot or the result of a pure selector. Equality defaults to `Object.is`. A selector that returns a new object needs an equality function to suppress updates when the selected values are unchanged. |
12
+ | `useHlsMetrics(store)` | Subscribes to metrics separately from playback state. Returns `null` until the controller has a source with metrics enabled. |
13
+
14
+ ## Options
15
+
16
+ | Option | Default and effect |
17
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `src` | React only. Omitted, `null`, or an empty string leaves playback idle. The controller resolves relative HTTP or HTTPS URLs against the video's document base URL. A URL with a username or password is invalid. |
19
+ | `onEvent` | React only. Receives transition, error, and recovery events. Changing the callback preserves the active session. The core controller provides an `onEvent(listener)` method. |
20
+ | `autoPlay` | `false`. When true, the controller requests playback on first readiness, subject to the browser's autoplay policy. |
21
+ | `lowLatency` | Defaults to the hls.js setting. An explicit value overrides `hlsConfig.lowLatencyMode`. The browser controls latency on the native path. |
22
+ | `engine` | `"auto"` prefers hls.js and falls back to native HLS when hls.js is unavailable. `"hls.js"` and `"native"` require the selected engine. The `request` option requires hls.js. |
23
+ | `metrics` | `false`. When true, the controller publishes a separate metrics snapshot on attachment and samples it every second. |
24
+ | `recovery` | Two automatic attempts by default. `false` disables controller recovery. An object overrides the settings below. |
25
+ | `hlsConfig` | Partial hls.js configuration from trusted application code. See the [security policy](../SECURITY.md) for overrides and limits. |
26
+ | `request` | Off by default. Enables the origin allowlist and optional request headers. `credentials` defaults to `"same-origin"` in this mode. See the [security policy](../SECURITY.md) for the request rules. |
27
+
28
+ ### Recovery options
29
+
30
+ | Field | Default | Accepted range |
31
+ | ------------------ | ------- | -------------- |
32
+ | `maxAttempts` | 2 | 0 to 5 |
33
+ | `baseDelayMs` | 500 | 0 to 30,000 |
34
+ | `maxDelayMs` | 5,000 | 0 to 60,000 |
35
+ | `attemptTimeoutMs` | 30,000 | 1 to 120,000 |
36
+
37
+ The controller rounds finite numbers down and clamps them to the accepted range. Omitted and non-finite values use the defaults. The delay grows exponentially with jitter up to `maxDelayMs`. The timeout applies to each recovery attempt.
38
+
39
+ ## Core controller
40
+
41
+ `usehlsvideoplayer/core` exports the controller, engine selection function, and playback and adapter types. The entry has no React runtime dependency.
42
+
43
+ | Method | Contract |
44
+ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
45
+ | `createPlaybackController(factory?)` | Creates an idle controller. An optional `EngineFactory` returns an unattached adapter or `null` when playback is unsupported. |
46
+ | `selectEngine(mode, mse, native, requiresMse?)` | Selects `"hls.js"`, `"native"`, or `null` from the supplied mode and capabilities. `requiresMse` defaults to `false`. |
47
+ | `setSource(video, src, options?)` | Replaces the previous session, even for the same URL. `setSource(null)` clears the session and leaves the controller reusable. |
48
+ | `getState()`, `getMetrics()` | Return read-only snapshots. Repeated reads return the same snapshot until the store publishes an update. |
49
+ | `subscribe(listener)`, `subscribeMetrics(listener)` | Subscribe to changes in the respective store. The listener takes no arguments. Each method returns an unsubscribe function. |
50
+ | `onEvent(listener)` | Subscribes to `PlaybackEvent` values and returns an unsubscribe function. |
51
+ | `destroy()` | Releases media resources, clears subscribers, and closes the controller permanently. |
52
+
53
+ Each video element needs one controller. The controller owns adapter attachment and teardown. An adapter's `destroy()` method releases the media attachment synchronously and cancels any work the adapter started.
54
+
55
+ ## Controls
56
+
57
+ Commands return `true` for an accepted request and `false` for an unavailable, invalid, or rejected request. `play()` returns a promise for that result.
58
+
59
+ | Command | Result |
60
+ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
61
+ | `play()`, `pause()` | Request playback or pause. A browser autoplay rejection from `play()` yields a non-fatal `playback-blocked` error. |
62
+ | `seek(seconds)` | Clamps finite seconds to the nearest seekable range. If no seekable range is available, the command uses the finite duration. |
63
+ | `setMuted(muted)`, `setVolume(volume)`, `setPlaybackRate(rate)` | Set mute, volume, or playback rate. Volume is clamped to 0 through 1. Playback rate must be finite, positive, and supported by the browser. |
64
+ | `setQuality(level)` | Selects a level ID from the snapshot or restores adaptive bitrate selection with `"auto"`. Available with hls.js when levels exist. |
65
+ | `setAudioTrack(id)` | Selects an audio track ID from the snapshot. Available with hls.js when tracks exist. |
66
+ | `setSubtitleTrack(id)` | Selects a subtitle track ID from the snapshot or turns subtitles off with `"off"`. |
67
+ | `seekToLive()` | Seeks to the live sync position when a seekable range is available. |
68
+ | `startLoading()` | Starts hls.js loading, including when `hlsConfig.autoStartLoad` is false. |
69
+ | `retry()` | Starts a new session with the retained source and a fresh recovery budget. Requires a retained source and `state.status === "error"`. |
70
+
71
+ Track and level IDs refer to the choices in the current snapshot. A successful selection accepts the request. Media from the selected rendition may appear after one or more segments.
72
+
73
+ ## State and events
74
+
75
+ `PlaybackState` contains `status`, the active or last `engine`, playback time, duration, buffered and seekable ranges, volume, mute, and playback rate. Time and range values are in seconds. Unknown or infinite duration appears as `null`.
76
+
77
+ - `quality` reports the available `levels`, selection `mode`, requested `selected` level, and `current` level.
78
+ - `audio` and `subtitles` each report `tracks` and the selected track ID or `null`.
79
+ - `live` reports `isLive`, `edge`, `syncPosition`, and `latency`, each nullable. `canSeekToLive` requires a sync position and a seekable range. `atEdge` is true when playback reaches two seconds before the sync position or later.
80
+ - `error` contains a `PlaybackError` or `null`. A playback error includes a code, fixed message, fatal and retryable flags, the engine or `null`, and an HTTP status when available.
81
+ - `recovery` contains the attempt number, limit, phase, and delay, or `null`.
82
+
83
+ A typical session moves through `idle`, `initialising`, `loading`, `ready`, and `playing`. Media events can skip `ready`. After playback starts, the browser can report `paused`, `buffering`, `seeking`, or `ended`. Source replacement returns to `idle`. Destruction leaves the controller in `destroyed`.
84
+
85
+ The controller delivers `PlaybackEvent` values synchronously as it publishes changes or reports failures.
86
+
87
+ | `type` | Fields |
88
+ | -------------- | -------------------------------------------------------------------------------- |
89
+ | `"transition"` | `from` and `to` contain the previous and next status. |
90
+ | `"error"` | `error` contains the structured failure, including non-fatal hls.js diagnostics. |
91
+ | `"recovery"` | `recovery` contains the current attempt, limit, delay, and phase. |
92
+
93
+ Non-fatal hls.js errors appear as events while hls.js retries or switches renditions. A fatal, retryable error can move the controller to `recovering`. The controller destroys the old engine before it schedules a replacement. The attempt budget applies to the entire source session, including after a successful recovery. Automatic recovery preserves playback intent and restores the last known position when possible. Track choices may revert to stream defaults. Non-retryable errors and exhausted budgets end in `error`.
94
+
95
+ ## Metrics
96
+
97
+ | Field | Measurement |
98
+ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
99
+ | `startupMs` | Milliseconds from attachment to first playback, including time spent waiting for viewer input. `null` before first playback. |
100
+ | `playingMs` | Milliseconds in `playing`. |
101
+ | `rebufferMs`, `rebufferCount` | Time in `buffering` and entries into `buffering` after first playback. Startup waits, seeks, and explicit recovery are separate states. |
102
+ | `rebufferRatio` | `rebufferMs / (playingMs + rebufferMs)`, or zero before any measured playback or rebuffering. |
103
+ | `droppedFrames`, `totalFrames` | Counts from `getVideoPlaybackQuality()` when available, otherwise `null`. |
104
+ | `liveLatency` | Estimated seconds between the playback position and the live edge visible to the player, or `null`. |
105
+
106
+ State samples from `timeupdate` publish at most four times per second. Media events and commands can publish state changes immediately.
@@ -1,24 +1,14 @@
1
- # Browser compatibility and troubleshooting
1
+ # Browser compatibility
2
2
 
3
- | Environment | Playback path | Feature boundary |
4
- | ---------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
5
- | Chromium or Firefox with supported MSE and codecs | hls.js | ABR, manual quality, audio groups, subtitles, authenticated fetch, VOD and live. |
6
- | Safari or iOS browsers with a supported hls.js Media Source path | hls.js in auto mode | Availability depends on OS and media capabilities. |
7
- | Safari or another browser advertising native HLS | Native when selected or as fallback | The browser manages ABR, loading and audio. The library exposes browser text tracks and seekable ranges when available. |
8
- | Browser with neither HLS path | Unsupported | The controller reports a typed error. |
9
- | Node and Next.js server rendering | Inactive | Imports and initial snapshots work without browser globals. |
3
+ `engine: "auto"` selects hls.js when the browser exposes a supported Media Source Extensions (MSE) or Managed Media Source path. Otherwise, it selects native HLS when the browser advertises native support and the options permit it. This choice happens when the controller attaches a source. A failure after attachment follows the recovery rules in the [API reference](api.md#state-and-events).
10
4
 
11
- This table describes capability paths. The [verification record](modernization.md) gives the actual browser coverage. The codec, manifest, transport and platform must all support the stream.
5
+ | Playback path | Available features |
6
+ | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | hls.js on browsers with a supported media source path | Adaptive bitrate selection, manual quality, alternate audio, subtitles, authenticated requests, and live playback. Each stream needs compatible codecs and the relevant playlist entries. |
8
+ | Native HLS on browsers that advertise support | The browser manages quality, loading, and audio. The library exposes available browser subtitle and caption tracks, and seekable ranges. Request header injection and per-request origin rules require hls.js. |
9
+ | No compatible playback path | The controller reports `unsupported`. |
10
+ | Node or Next.js server rendering | Imports and the idle snapshot work without browser globals. Media attachment requires a video element in the browser. |
12
11
 
13
- | Symptom | Check |
14
- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
15
- | Stream remains idle | Pass a source and attach the ref. Keep the video's own source attribute and children clear. |
16
- | Stream starts loading but never plays | Use `controls.play()` from a click. If `autoStartLoad` is false, call `controls.startLoading()`. |
17
- | Unsupported engine | Leave `engine` on auto, verify MSE or native HLS, and check the encoded codecs. |
18
- | Request fails across origins | Configure CORS for the manifest, variants, segments, subtitles and keys. With credentialled CORS, allow the exact application origin. |
19
- | Authentication failure | Obtain a fresh signed URL or token through the application and replace the source. Native playback may not reveal HTTP status. |
20
- | No manual quality or audio options | Native playback does not expose library-managed rendition selection. Check that the master playlist actually lists alternatives. |
21
- | Seek-to-live disabled | Wait for a live playlist and a seekable range. Native detection depends on the browser exposing an infinite duration. |
22
- | Metrics remain empty | Enable `metrics` on the attachment. Frame quality and latency can remain unavailable on some browsers. |
12
+ Safari and iOS browsers can select different paths depending on the OS and media capabilities. The engine check establishes browser capability. Decoding still depends on the stream's codecs and container.
23
13
 
24
- The live playground uses a rolling local transport-stream fixture. It is suitable for lifecycle and DVR checks. LL-HLS needs an origin serving partial segments, blocking reload and compliant holdback values.
14
+ The local playground uses a rolling MPEG-TS live stream. See the [engineering record](modernization.md) for browser test coverage or [playback troubleshooting](troubleshooting.md) for help with a stream.
package/docs/migration.md CHANGED
@@ -1,21 +1,36 @@
1
1
  # Migrate from 1.x
2
2
 
3
- `Usehlsplayer(url, options)` and `useHlsVideoPlayer(url, options)` remain available as deprecated adapters. Both return the legacy `{ videoRef }` object ref. Existing flat hls.js options, including legacy timeout names supported by hls.js, continue to reach the engine. The default capability order stays hls.js first.
3
+ `Usehlsplayer(url, options)` and `useHlsVideoPlayer(url, options)` remain available as deprecated adapters. They return the original `{ videoRef }` object ref and pass flat hls.js options to the engine.
4
+
5
+ ## Switch to `useHlsPlayer`
6
+
7
+ 1. Replace the legacy import with `useHlsPlayer` from the package root.
8
+ 2. Pass the URL as `src` and move hls.js options under `hlsConfig`. The old call `Usehlsplayer(url, { startLevel: -1 })` becomes `useHlsPlayer({ src: url, hlsConfig: { startLevel: -1 } })`.
9
+ 3. Attach `videoRef` directly to the video element. Remove any `src` attribute or `<source>` children. The new ref is a callback. For imperative playback, use `controls`.
10
+ 4. Replace the legacy `onError` callback with `onEvent` if you need diagnostics. Use `state.status === "error"` and `error` to show a terminal failure in the UI.
4
11
 
5
12
  ```tsx
6
- // 1.x compatibility
7
- const { videoRef } = Usehlsplayer(url, { startLevel: -1 });
8
-
9
- // 2.x
10
- const { videoRef, state, controls } = useHlsPlayer({
11
- src: url,
12
- hlsConfig: { startLevel: -1 },
13
- });
13
+ "use client";
14
+
15
+ import { useHlsPlayer } from "usehlsvideoplayer";
16
+
17
+ export function Player({ url }: { url: string }) {
18
+ const { videoRef, state, error } = useHlsPlayer({
19
+ src: url,
20
+ hlsConfig: { startLevel: -1 },
21
+ });
22
+
23
+ return (
24
+ <>
25
+ <video ref={videoRef} controls playsInline />
26
+ {state.status === "error" && error && <p role="alert">{error.message}</p>}
27
+ </>
28
+ );
29
+ }
14
30
  ```
15
31
 
16
- - Remove the `<source>` child and any video `src` binding. The controller owns the active source.
17
- - The primary hook returns a callback ref. Attach it directly. For imperative playback, use `controls`.
18
- - The deprecated hooks keep the old error discriminator. The `error` or `cause` payload now contains a structured, sanitized `PlaybackError`. Code that consumed hls.js response data or a browser exception should switch to the public error codes.
19
- - hls.js debugging is disabled to keep signed URLs out of library diagnostics. Application diagnostics should use structured events.
20
- - Use the package root in ESM and CommonJS. Deep imports of generated 1.x files are superseded by the explicit root and `/core` entries.
21
- - The new hook uses a bounded recovery budget. The legacy adapters leave upstream retries in charge, preserving their original recovery behaviour.
32
+ If you keep a legacy adapter, update any code that reads raw hls.js response data or browser exceptions. The `kind` discriminator stays the same, and the `error` or `cause` field now contains a structured `PlaybackError` when present. The library disables hls.js debugging and exposes structured events for diagnostics.
33
+
34
+ The new hook makes up to two automatic recovery attempts by default. To keep the legacy recovery behaviour, set `recovery: false` and leave retries to the engine.
35
+
36
+ Replace deep imports of generated 1.x files with the package root for React or `usehlsvideoplayer/core` for the controller. Both entries support ESM and CommonJS. See the [API reference](api.md) for options and commands.
@@ -0,0 +1,19 @@
1
+ # Troubleshoot playback
2
+
3
+ Start with `state.status`, `state.engine`, and `state.error?.code`. For hls.js, subscribe to `onEvent` to see non-fatal errors while the engine handles retries. Keep signed URLs and credentials out of logs.
4
+
5
+ | Symptom | What to check |
6
+ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | The player stays idle | Attach `videoRef` and pass a playlist URL to the React hook, or call `setSource(video, url)` on the core controller. Leave the video's own `src` and `<source>` children unset. |
8
+ | Playback reports `setup` | Check that the video element has one controller. If you supplied a custom adapter, check its factory and attachment. |
9
+ | The player reaches `ready` but stays paused | Use the video controls or call `controls.play()` from a click. If the browser denied autoplay, the player can report `playback-blocked`. |
10
+ | The player stays in `loading` with `autoStartLoad: false` | Call `controls.startLoading()` for hls.js. |
11
+ | The player reports `unsupported` | Check the available playback paths and the stream's codecs. If you forced an engine, try `"auto"`. If you supplied `request`, check that the browser supports hls.js. |
12
+ | A cross-origin request fails | Check CORS on every origin that serves playlists, segments, subtitles, or keys. If the request includes credentials, allow the exact app origin and enable credentialed CORS. |
13
+ | The player reports `request-policy` | Check the exact origins in `request.allowedOrigins`, including the scheme and port. Use final URLs. A blocked redirect can also appear as a network or manifest error. |
14
+ | The player reports `authentication` | Get a fresh signed URL or refresh the token through your application. The native engine may report a generic media error when the server rejects the request. |
15
+ | Manual quality or alternate audio is missing | Check `state.engine`. With hls.js, check whether the master playlist advertises alternate levels or audio tracks. |
16
+ | **Go live** stays disabled | Check whether `live.isLive` is true and `state.seekable` contains a range. Native live detection depends on the browser reporting an infinite duration. |
17
+ | Metrics stay empty | Pass `metrics: true` when you attach the stream. Frame counts and live latency can remain unavailable when the browser or stream cannot provide them. |
18
+
19
+ See [browser compatibility](compatibility.md) for engine capabilities and the [usage guide](usage.md#send-credentials-with-hls-requests) for the protected request setup.
package/docs/usage.md ADDED
@@ -0,0 +1,161 @@
1
+ # Use the player
2
+
3
+ ## Play a stream without React
4
+
5
+ Import from `usehlsvideoplayer/core`. Pass a video element with no `src` or `<source>` children, and an element for the status. Call the returned cleanup function before removing the player.
6
+
7
+ ```ts
8
+ import { createPlaybackController } from "usehlsvideoplayer/core";
9
+
10
+ export function mountPlayer(
11
+ video: HTMLVideoElement,
12
+ status: HTMLElement,
13
+ src: string,
14
+ ) {
15
+ const player = createPlaybackController();
16
+ const render = () => {
17
+ const state = player.getState();
18
+ status.textContent =
19
+ state.status === "error"
20
+ ? (state.error?.message ?? "Playback failed.")
21
+ : state.status;
22
+ };
23
+
24
+ const unsubscribe = player.subscribe(render);
25
+ video.controls = true;
26
+ video.playsInline = true;
27
+ render();
28
+ player.setSource(video, src);
29
+
30
+ return () => {
31
+ unsubscribe();
32
+ player.destroy();
33
+ };
34
+ }
35
+ ```
36
+
37
+ ## Replace or clear a stream
38
+
39
+ Give each player its own video element. In React, change `src` to replace the playlist or pass `src: null` to clear it. The hook clears the source on unmount. With the core controller, call `setSource(video, nextUrl, options)` to replace the playlist or `setSource(null)` to clear it.
40
+
41
+ Keep nested `hlsConfig` values and `request.headers` stable across React renders. For example, put static configuration at module scope. Use `useCallback` for a header provider that depends on props. A changed option can replace the playback session.
42
+
43
+ ## Add live controls
44
+
45
+ Keep the browser's video controls for play, pause, volume, and seeking. Enable **Go live** when `live.canSeekToLive` is true. A live playlist needs a seekable range before the command becomes available.
46
+
47
+ ```tsx
48
+ "use client";
49
+
50
+ import { useHlsPlayer } from "usehlsvideoplayer";
51
+
52
+ export function LivePlayer({ src }: { src: string }) {
53
+ const { videoRef, controls, live } = useHlsPlayer({ src, lowLatency: true });
54
+
55
+ return (
56
+ <>
57
+ <video ref={videoRef} controls playsInline />
58
+ <button
59
+ type="button"
60
+ disabled={!live.canSeekToLive}
61
+ onClick={controls.seekToLive}
62
+ >
63
+ Go live
64
+ </button>
65
+ </>
66
+ );
67
+ }
68
+ ```
69
+
70
+ For LL-HLS, serve an LL-HLS playlist with partial segments and blocking reloads. The origin and CDN need to support those requests. To offer rewind on a live stream, keep enough segments in the live playlist and available on the server.
71
+
72
+ ## Add quality, audio, and subtitle choices
73
+
74
+ Build the menus from the current snapshot. Rebuild them as the available choices change.
75
+
76
+ - Offer **Auto** with `controls.setQuality("auto")`. For manual choices, pass an ID from `quality.levels`. Hide the selector when `quality.mode` is `"unavailable"`.
77
+ - Pass an ID from `audio.tracks` to `controls.setAudioTrack(id)`. Hide the selector when there are no audio choices.
78
+ - Pass an ID from `subtitles.tracks` to `controls.setSubtitleTrack(id)`. Add an **Off** choice that passes `"off"`.
79
+
80
+ Use `quality.current` to display the level in use. The requested level can take one or more segments to become current.
81
+
82
+ ## Recover from a playback failure
83
+
84
+ - If the browser blocks autoplay, call `controls.play()` from a click or let the viewer use the video controls.
85
+ - While `state.status` is `"recovering"`, show progress and let the controller finish its automatic attempts.
86
+ - If `state.status` is `"error"` and the URL and configuration still apply, offer `controls.retry()`. A return value of `true` means the controller started a fresh attempt.
87
+ - If the URL expired, get a fresh URL and replace `src`. If credentials expired, refresh them through your application before starting another attempt.
88
+
89
+ To collect diagnostics, use `onEvent` for transitions, errors, and recovery. For playback measurements, set `metrics: true`.
90
+
91
+ ## Subscribe to part of the playback state
92
+
93
+ If only one part of a React player needs playback updates, use `useHlsController` in the parent. Select the field you need in the child. In this example, the store updates `PlaybackStatus` only when `state.status` changes.
94
+
95
+ ```tsx
96
+ "use client";
97
+
98
+ import {
99
+ type PlaybackStore,
100
+ useHlsController,
101
+ usePlaybackState,
102
+ } from "usehlsvideoplayer";
103
+
104
+ function PlaybackStatus({ store }: { store: PlaybackStore }) {
105
+ const status = usePlaybackState(store, (state) => state.status);
106
+ return <p role="status">{status}</p>;
107
+ }
108
+
109
+ export function Player({ src }: { src: string }) {
110
+ const { videoRef, store } = useHlsController({ src });
111
+ return (
112
+ <>
113
+ <video ref={videoRef} controls playsInline />
114
+ <PlaybackStatus store={store} />
115
+ </>
116
+ );
117
+ }
118
+ ```
119
+
120
+ ## Send credentials with HLS requests
121
+
122
+ If the stream requires an authorization header, pass `request`. This mode requires hls.js. Include the exact origins of the master playlist, child playlists, segments, subtitles, and keys in `allowedOrigins`. Use final URLs because this mode rejects redirects.
123
+
124
+ ```tsx
125
+ "use client";
126
+
127
+ import { useCallback } from "react";
128
+ import { useHlsPlayer } from "usehlsvideoplayer";
129
+
130
+ export function ProtectedPlayer({
131
+ src,
132
+ getAccessToken,
133
+ }: {
134
+ src: string;
135
+ getAccessToken: () => string;
136
+ }) {
137
+ const headers = useCallback(
138
+ () => ({ Authorization: `Bearer ${getAccessToken()}` }),
139
+ [getAccessToken],
140
+ );
141
+ const { videoRef, state, error } = useHlsPlayer({
142
+ src,
143
+ request: {
144
+ allowedOrigins: ["https://media.example.com"],
145
+ credentials: "omit",
146
+ headers,
147
+ },
148
+ });
149
+
150
+ return (
151
+ <>
152
+ <video ref={videoRef} controls playsInline />
153
+ {state.status === "error" && error && <p role="alert">{error.message}</p>}
154
+ </>
155
+ );
156
+ }
157
+ ```
158
+
159
+ Replace the example origin with the media origin and pass a stable `getAccessToken` function that reads the current token. The header callback in this example sends the token to every allowed origin. If different origins need different credentials, use the callback's URL argument to decide which headers to return.
160
+
161
+ For cross-origin requests, allow your app's origin and the `Authorization` header in the server's CORS response. For cookie authentication across origins, use `credentials: "include"` and configure the server to allow credentials for the exact app origin. For native playback, use a signed URL or browser-managed cookies. See the [security policy](../SECURITY.md) for request and credential limits.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usehlsvideoplayer",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Headless HLS playback controller and React hooks",
5
5
  "type": "module",
6
6
  "scripts": {