@oddin-gg/havik-player 2.16.13 → 2.18.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 +4 -1
- package/dist/havik-player.global.js +18 -18
- package/dist/havik-player.global.js.map +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +434 -315
- package/dist/index.js.map +1 -1
- package/docs/API.md +44 -20
- package/docs/STABILITY.md +1 -1
- package/embed/index.html +1 -1
- package/examples/vanilla.html +1 -1
- package/package.json +1 -1
package/docs/API.md
CHANGED
|
@@ -249,8 +249,9 @@ player.on('error', (e) => console.error(e.code, e.httpStatus, e.message));
|
|
|
249
249
|
| `maxBitrate` | `number` | — | Cap ABR to renditions at/below this bitrate (bps). |
|
|
250
250
|
| `liveLatencyTarget` | `number` | `2` | Target live‑edge latency in seconds (hls.js `liveSyncDuration`). The player derives a matching latency **ceiling** internally and catches up at 1.1×, so this is the only live‑latency knob you need. Lower = closer to live but more rebuffer‑prone. Live/LL only. |
|
|
251
251
|
| `snapToLiveOnRefocus` | `boolean` | `true` | Snap to the live edge when a backgrounded tab is refocused: on hls.js while far behind; on the native player (iPhone, iPad, FairPlay) on every return after 2 s or more away and on every back/forward‑cache restore, 3 s behind the edge once the first playlist refresh after the return moves it (up to 6 s wait; the edge on return is still the one from before leaving), which also redraws a picture iOS left black. Live/LL only. |
|
|
252
|
+
| `rememberBandwidth` | `boolean` | `true` | hls.js engine: start from the bandwidth this device measured in a recent session (`localStorage` key `havik.player.bandwidth`, per connection type, up to 3 days old, reused at 80%) instead of the conservative ~1 Mbps start; a slow record starts from that lower figure (0.3 Mbps floor). A first visit, a stale record or blocked storage keeps the conservative start; an explicit `hlsConfig.abrEwmaDefaultEstimate` or `startLevel` wins. Reset with [`clearRememberedBandwidth()`](#clearrememberedbandwidth). |
|
|
252
253
|
| `mediaDuringKey` | `boolean` | `true` | DRM cold start: download the first init segment and parts while the first key is still being acquired, instead of after it. Bytes reach hls.js only when it asks for them; only the network time moves off the critical path (~0.5 s on Widevine LL-HLS). Ignored on clear streams and when `hlsConfig.fLoader` is set. Pass `false` to opt out. |
|
|
253
|
-
| `liveStateEvents` | `boolean` | `true` | Use the push-based **live-state SSE** channel for go-live and end-of-stream (see [Live state](#live-state-sse)). Graceful: falls back to polling if the endpoint is unreachable. Set `false` to disable.
|
|
254
|
+
| `liveStateEvents` | `boolean` | `true` | Use the push-based **live-state SSE** channel for go-live and end-of-stream (see [Live state](#live-state-sse)). Graceful: falls back to polling if the endpoint is unreachable. On a return from the background (5 s or more away, or a back/forward-cache restore) the subscription is reopened so the server replays the current state: a page suspended through the end of a match does not sit buffering on a socket that will never deliver it. Set `false` to disable. |
|
|
254
255
|
| `rejoinOnLive` | `boolean` | `true` | Rejoin automatically when a stream that ended comes back live. An ingest outage longer than the bridge's reconnect grace recreates the session under a **new manifest URL** (the old playlist freezes at HTTP 200), so the player keeps the live-state subscription open after `ended` (until `gone`) and re-arms on a pushed `live`. Set `false` to keep `ended` terminal. Needs `liveStateEvents`. |
|
|
255
256
|
| `analytics` | `boolean \| { endpoint?: string }` | `true` | QoE beacons. Active only when the playback response carries a server-minted `sid`. **havik-streams v0.10.0+ names the beacons host in that response, and it is used whatever `baseUrl` is**; it also wins over `{ endpoint }` (a differing value is ignored with one `console.warn`). Against an older server the host is `{ endpoint }` if given, else derived from `baseUrl` by hostname convention (`feed[-dev].<domain>` → `beacons[-dev].<domain>`); there, **if `baseUrl` is not a `feed` host nothing derives and beacons are disabled** — the SDK then logs one `console.warn` naming the host and emits a non-fatal [`warning`](#player-events) event carrying the same sentence. Recovery: pass `{ endpoint: 'https://beacons[-dev].<domain>' }`. Pass `false` to opt out deliberately (no warning). |
|
|
256
257
|
| `eventsBaseUrl` | `string` | derived | Override the SSE endpoint base. Default: `baseUrl` with the host prefixed by `events.` (e.g. `feed.<d>` → `events.<d>`). |
|
|
@@ -305,7 +306,14 @@ don't need to set any of the live or ABR knobs:
|
|
|
305
306
|
rendition is one a modest link can sustain (**~360p**) and then **ramps up**
|
|
306
307
|
within a few seconds as measured bandwidth allows, rather than grabbing a high
|
|
307
308
|
rung and rebuffering on a constrained link. This trades a slightly softer first
|
|
308
|
-
~2 s for a **stall‑free start** (the first‑seconds buffering fix).
|
|
309
|
+
~2 s for a **stall‑free start** (the first‑seconds buffering fix). A device
|
|
310
|
+
that has **measured a fast line in the last 3 days** starts from that
|
|
311
|
+
measurement instead (`rememberBandwidth`, on by default: the estimate after
|
|
312
|
+
~10 s of playback, stored per connection type and reused at 80%), so a
|
|
313
|
+
returning viewer on a fast line starts near full quality without a 360p jump,
|
|
314
|
+
and a device that measured a slow line starts from that lower figure (also at
|
|
315
|
+
80%, never below 0.3 Mbps). A first visit or a device with no usable record
|
|
316
|
+
keeps the ~1 Mbps start. In addition,
|
|
309
317
|
the session is bounded to the player's pixel box, so a small/embedded player
|
|
310
318
|
won't fetch 1080p, but by an SDK‑managed, **debounced** size cap rather than
|
|
311
319
|
hls.js's own `capLevelToPlayerSize` poller: a size change only reaches ABR
|
|
@@ -314,7 +322,9 @@ don't need to set any of the live or ABR knobs:
|
|
|
314
322
|
switch, and its ~1 s stall, mid‑transition (the higher rendition simply
|
|
315
323
|
downscales into the smaller box until the down‑cap lands). Passing
|
|
316
324
|
`hlsConfig.capLevelToPlayerSize: true` restores hls.js's immediate poller and
|
|
317
|
-
stands the SDK manager down.
|
|
325
|
+
stands the SDK manager down. The ceiling in force and which limit set it
|
|
326
|
+
are reported in every `stats` event (`maxLevelHeight`, `capReason`), so a
|
|
327
|
+
player stuck at 540p in a 758 px box can say so. Pin a fixed rung with `startLevel`,
|
|
318
328
|
cap the top with `maxBitrate`, or raise the seed via
|
|
319
329
|
`hlsConfig.abrEwmaDefaultEstimate` if you want a sharper (higher‑risk) start.
|
|
320
330
|
|
|
@@ -408,23 +418,23 @@ Notes:
|
|
|
408
418
|
|
|
409
419
|
`on(event, cb)`: the payload type depends on the event ([`PlayerEventMap`](#playereventmap)):
|
|
410
420
|
|
|
411
|
-
| Event | Payload | Fires when
|
|
412
|
-
| ------------------ | --------------------------------- |
|
|
413
|
-
| `ready` | `void` | The manifest is parsed and the player is attached.
|
|
414
|
-
| `waiting` | [`WaitState`](#waitstate) | Armed and waiting for go‑live. On the polling path it fires per poll with the countdown; on the SSE arming path it fires once, just after `createPlayer` resolves (with `ttkMs` when the server sent a kickoff time).
|
|
415
|
-
| `playing` | `void` | Playback is progressing.
|
|
416
|
-
| `buffering` | `void` | The player is rebuffering.
|
|
417
|
-
| `paused` | `void` | Playback is paused.
|
|
418
|
-
| `ended` | `EndedReason \| undefined` | The live stream ended; playback stops and the last frame is kept. The payload says **why**: `'interrupted'` (the match is still running, and its stream dropped, so show "reconnecting", **never** "ended"; the player rejoins by itself) or `'match_ended'` (a real end). `undefined` = the end came from the
|
|
419
|
-
| `error` | [`PlaybackError`](#playbackerror) | A fatal error occurred (playback stopped).
|
|
420
|
-
| `warning` | [`PlaybackError`](#playbackerror) | A non-fatal condition (e.g. FairPlay passthrough, a failed license refresh, **QoE beacons disabled because the endpoint could not be derived** — see [`analytics`](#createplayeroptions) — or **QoE beacons refused by the page's Content-Security-Policy**, naming the origin to add to `connect-src`). Playback continues.
|
|
421
|
-
| `autoplayblocked` | `void` | The browser blocked autoplay (`NotAllowedError`). Show a tap-to-play / tap-to-unmute affordance, then call `player.play()` from the gesture.
|
|
422
|
-
| `qualitychange` | `number` | Active rendition changed (level index, `-1` = auto).
|
|
423
|
-
| `audiotrackchange` | `number` | Active audio track changed (track id).
|
|
424
|
-
| `texttrackchange` | `number` | Active text track changed (track id, `-1` = off).
|
|
425
|
-
| `pipchange` | `boolean` | Picture-in-Picture entered (`true`) or exited (`false`).
|
|
426
|
-
| `stats` | [`PlaybackStats`](#playbackstats) | Periodic stats sample (`statsIntervalMs`).
|
|
427
|
-
| `statechange` | [`PlayerState`](#playerstate) | Any state transition.
|
|
421
|
+
| Event | Payload | Fires when |
|
|
422
|
+
| ------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
423
|
+
| `ready` | `void` | The manifest is parsed and the player is attached. |
|
|
424
|
+
| `waiting` | [`WaitState`](#waitstate) | Armed and waiting for go‑live. On the polling path it fires per poll with the countdown; on the SSE arming path it fires once, just after `createPlayer` resolves (with `ttkMs` when the server sent a kickoff time). |
|
|
425
|
+
| `playing` | `void` | Playback is progressing. |
|
|
426
|
+
| `buffering` | `void` | The player is rebuffering. |
|
|
427
|
+
| `paused` | `void` | Playback is paused. |
|
|
428
|
+
| `ended` | `EndedReason \| undefined` | The live stream ended; playback stops and the last frame is kept. The payload says **why**: `'interrupted'` (the match is still running, and its stream dropped, so show "reconnecting", **never** "ended"; the player rejoins by itself) or `'match_ended'` (a real end). `undefined` = the end came from the staleness watchdog (either engine: a live playlist that stopped advancing for 12 s), or a server predating the field; treat it as a real end. Fires **again** with `'match_ended'` if an interruption later turns out to be the end, so don't latch on the first one. Also readable any time as `player.endedReason`. |
|
|
429
|
+
| `error` | [`PlaybackError`](#playbackerror) | A fatal error occurred (playback stopped). |
|
|
430
|
+
| `warning` | [`PlaybackError`](#playbackerror) | A non-fatal condition (e.g. FairPlay passthrough, a failed license refresh, **QoE beacons disabled because the endpoint could not be derived** — see [`analytics`](#createplayeroptions) — or **QoE beacons refused by the page's Content-Security-Policy**, naming the origin to add to `connect-src`). Playback continues. |
|
|
431
|
+
| `autoplayblocked` | `void` | The browser blocked autoplay (`NotAllowedError`). Show a tap-to-play / tap-to-unmute affordance, then call `player.play()` from the gesture. |
|
|
432
|
+
| `qualitychange` | `number` | Active rendition changed (level index, `-1` = auto). |
|
|
433
|
+
| `audiotrackchange` | `number` | Active audio track changed (track id). |
|
|
434
|
+
| `texttrackchange` | `number` | Active text track changed (track id, `-1` = off). |
|
|
435
|
+
| `pipchange` | `boolean` | Picture-in-Picture entered (`true`) or exited (`false`). |
|
|
436
|
+
| `stats` | [`PlaybackStats`](#playbackstats) | Periodic stats sample (`statsIntervalMs`). |
|
|
437
|
+
| `statechange` | [`PlayerState`](#playerstate) | Any state transition. |
|
|
428
438
|
|
|
429
439
|
<a id="controls--theming"></a>
|
|
430
440
|
|
|
@@ -767,6 +777,18 @@ function clearDeviceId(): void;
|
|
|
767
777
|
> under GDPR/ePrivacy). Disclose it in your privacy policy and offer a reset:
|
|
768
778
|
> `clearDeviceId()` removes the stored id so the next call generates a fresh one.
|
|
769
779
|
|
|
780
|
+
### `clearRememberedBandwidth`
|
|
781
|
+
|
|
782
|
+
```ts
|
|
783
|
+
function clearRememberedBandwidth(): void;
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
Forgets the bandwidth the player remembered for this device (the
|
|
787
|
+
`rememberBandwidth` option, `localStorage` key `havik.player.bandwidth`). The next
|
|
788
|
+
session starts from the conservative ~1 Mbps estimate again. The record holds
|
|
789
|
+
only an estimated speed and a timestamp per connection type; offer it beside
|
|
790
|
+
`clearDeviceId()` in a "reset player data" control.
|
|
791
|
+
|
|
770
792
|
### `fetchCatalog`
|
|
771
793
|
|
|
772
794
|
```ts
|
|
@@ -1285,6 +1307,8 @@ interface PlaybackStats {
|
|
|
1285
1307
|
atLiveEdge?: boolean; // playback is at/near the live edge
|
|
1286
1308
|
liveRetargets?: number; // times a futile catch-up moved the live target (hls.js engine)
|
|
1287
1309
|
fpsDownCaps?: number; // times quality was capped because the device dropped frames (hls.js engine)
|
|
1310
|
+
maxLevelHeight?: number; // highest rendition ABR may pick right now; absent when uncapped (hls.js engine)
|
|
1311
|
+
capReason?: 'size' | 'bitrate' | 'fps'; // which SDK cap set maxLevelHeight: player pixel box, maxBitrate, frame drops
|
|
1288
1312
|
}
|
|
1289
1313
|
```
|
|
1290
1314
|
|
package/docs/STABILITY.md
CHANGED
|
@@ -13,7 +13,7 @@ breaking change → major. **Pin a version in production** (npm and the CDN URL)
|
|
|
13
13
|
|
|
14
14
|
- `createPlayer` + the `Player` interface (Mode A)
|
|
15
15
|
- `resolveStream`, `fetchCatalog`, `flattenMatches`, `watchStatus`,
|
|
16
|
-
`licenseRequestHeaders`, `getDeviceId`, `clearDeviceId`, `isLowLatencyManifest` (Mode B)
|
|
16
|
+
`licenseRequestHeaders`, `getDeviceId`, `clearDeviceId`, `clearRememberedBandwidth`, `isLowLatencyManifest` (Mode B)
|
|
17
17
|
- `mountEmbed` / `bootEmbed` + `EmbedHandle` / the `oddin:*` postMessage protocol (Mode C)
|
|
18
18
|
- `PlaybackError` and all exported `type` definitions
|
|
19
19
|
|
package/embed/index.html
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
beacons endpoint can be derived and QoE analytics is silently off.
|
|
29
29
|
`analytics` is honoured from the injected config only, never the query.
|
|
30
30
|
-->
|
|
31
|
-
<script src="https://cdn.jsdelivr.net/npm/@oddin-gg/havik-player@2.
|
|
31
|
+
<script src="https://cdn.jsdelivr.net/npm/@oddin-gg/havik-player@2.18.0/dist/havik-player.global.js" integrity="sha384-bna/v7G8EjUwpvfY3vAwrzvlkh2RO3sMM8ccsC/A0LO1gYZyIluCR/6t5Mj8Qmmj" crossorigin="anonymous"></script>
|
|
32
32
|
<script>
|
|
33
33
|
HavikPlayer.bootEmbed();
|
|
34
34
|
</script>
|
package/examples/vanilla.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
shipped in each npm release is pinned to that release with Subresource
|
|
13
13
|
Integrity, injected at publish time (scripts/inject-sri.mjs). In
|
|
14
14
|
production, always use a pinned + SRI'd tag like the released copy. -->
|
|
15
|
-
<script src="https://cdn.jsdelivr.net/npm/@oddin-gg/havik-player@2.
|
|
15
|
+
<script src="https://cdn.jsdelivr.net/npm/@oddin-gg/havik-player@2.18.0/dist/havik-player.global.js" integrity="sha384-bna/v7G8EjUwpvfY3vAwrzvlkh2RO3sMM8ccsC/A0LO1gYZyIluCR/6t5Mj8Qmmj" crossorigin="anonymous"></script>
|
|
16
16
|
<script>
|
|
17
17
|
(async () => {
|
|
18
18
|
const player = await HavikPlayer.createPlayer({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oddin-gg/havik-player",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"description": "Havik HTML5 player SDK — LL-HLS + DRM playback for Oddin live streams (managed, bring-your-own-player, and iframe-embed modes).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|