@oddin-gg/havik-player 2.21.0 → 2.22.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/docs/API.md CHANGED
@@ -250,6 +250,7 @@ player.on('error', (e) => console.error(e.code, e.httpStatus, e.message));
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
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). |
253
+ | `sizeCapHeadroom` | `number` | unset | hls.js engine: rendition rungs ABR may pick above the one that merely covers the player's pixel box. Unset: `1` on displays below 2× `devicePixelRatio`, `0` at 2× and above. A 960 px box at 1x covers with 960×540; one rung of headroom plays 720p downscaled instead, which looks sharper for the same box; phones and 2× screens keep the cover-only cap (their covering rung already matches their device pixels; a rung above it mostly costs data). An explicit value applies on every display: `0` = strict cover-only cap, `Infinity` = no size cap. Never past the top rung; `maxBitrate` and the frame-drop cap still apply. `hlsConfig.capLevelToPlayerSize: true` caps to the covering rung with no headroom. |
253
254
  | `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. |
254
255
  | `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. |
255
256
  | `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`. |
@@ -314,15 +315,22 @@ don't need to set any of the live or ABR knobs:
314
315
  and a device that measured a slow line starts from that lower figure (also at
315
316
  80%, never below 0.3 Mbps). A first visit or a device with no usable record
316
317
  keeps the ~1 Mbps start. In addition,
317
- the session is bounded to the player's pixel box, so a small/embedded player
318
- won't fetch 1080p, but by an SDK‑managed, **debounced** size cap rather than
318
+ the session is bounded to the player's pixel box **plus one rung of
319
+ headroom** on displays below 2× `devicePixelRatio` (`sizeCapHeadroom`; set
320
+ it explicitly to apply a value on every display), so a small/embedded player won't fetch 1080p, while a 960 px
321
+ desktop embed gets 720p downscaled rather than 540p at 1:1 (a lower encode
322
+ shown pixel‑for‑pixel looks softer than a higher one scaled down; measured on
323
+ production, Windows viewers sat at ≤540p 73% of the time with bandwidth to
324
+ spare). Phones and 2× screens keep the cover‑only cap. The cap is
325
+ SDK‑managed and **debounced** rather than
319
326
  hls.js's own `capLevelToPlayerSize` poller: a size change only reaches ABR
320
327
  once the box has been stable for **~2 s** (raise), and a shrink is applied
321
328
  **lazily (~30 s)**, so a fullscreen enter/exit never forces a quality
322
329
  switch, and its ~1 s stall, mid‑transition (the higher rendition simply
323
330
  downscales into the smaller box until the down‑cap lands). Passing
324
331
  `hlsConfig.capLevelToPlayerSize: true` restores hls.js's immediate poller and
325
- stands the SDK manager down. The ceiling in force and which limit set it
332
+ stands the SDK manager down, including the headroom: the poller caps to the
333
+ covering rung only. The ceiling in force and which limit set it
326
334
  are reported in every `stats` event (`maxLevelHeight`, `capReason`), so a
327
335
  player stuck at 540p in a 758 px box can say so. Pin a fixed rung with `startLevel`,
328
336
  cap the top with `maxBitrate`, or raise the seed via
@@ -338,7 +346,9 @@ is derived above it.
338
346
 
339
347
  - **Quality steps down when the device can't decode it.** If the browser keeps
340
348
  dropping more than 20% of frames for two 3 s checks in a row, the SDK caps
341
- ABR one rendition below the one playing (counted in `fpsDownCaps`). This is
349
+ ABR one rendition below the one playing (counted in `fpsDownCaps`). The two
350
+ checks right after a quality switch are not counted: the decoder restarts on
351
+ the new rendition and drops frames for a few seconds on its own. This is
342
352
  what keeps a laptop without hardware decoding from showing a stuttering
343
353
  picture that drifts away from the sound. It folds into the same cap as the
344
354
  player‑size and `maxBitrate` limits, never applies to a quality the viewer
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.21.0/dist/havik-player.global.js" integrity="sha384-KGq+XypxWv0FVoUTM6e/yelF8vOuAmx2dJbIj3E/Ee2E6Ky8bTHorPnRapaKqfY+" crossorigin="anonymous"></script>
31
+ <script src="https://cdn.jsdelivr.net/npm/@oddin-gg/havik-player@2.22.1/dist/havik-player.global.js" integrity="sha384-O1vQDOlf0kbqRybKaeTfXlJ6leLt9fSwx+6WMhXVc6Z6QV3QV1e8fvR+XbGbVsJx" crossorigin="anonymous"></script>
32
32
  <script>
33
33
  HavikPlayer.bootEmbed();
34
34
  </script>
@@ -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.21.0/dist/havik-player.global.js" integrity="sha384-KGq+XypxWv0FVoUTM6e/yelF8vOuAmx2dJbIj3E/Ee2E6Ky8bTHorPnRapaKqfY+" crossorigin="anonymous"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/@oddin-gg/havik-player@2.22.1/dist/havik-player.global.js" integrity="sha384-O1vQDOlf0kbqRybKaeTfXlJ6leLt9fSwx+6WMhXVc6Z6QV3QV1e8fvR+XbGbVsJx" 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.21.0",
3
+ "version": "2.22.1",
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,