@oddin-gg/havik-player 2.10.0 → 2.11.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/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 +43 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +654 -325
- package/dist/index.js.map +1 -1
- package/docs/API.md +43 -16
- package/embed/index.html +1 -1
- package/examples/vanilla.html +1 -1
- package/package.json +4 -1
package/docs/API.md
CHANGED
|
@@ -247,6 +247,7 @@ player.on('error', (e) => console.error(e.code, e.httpStatus, e.message));
|
|
|
247
247
|
| `maxBitrate` | `number` | — | Cap ABR to renditions at/below this bitrate (bps). |
|
|
248
248
|
| `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.5×, so this is the only live‑latency knob you need. Lower = closer to live but more rebuffer‑prone. Live/LL only. |
|
|
249
249
|
| `snapToLiveOnRefocus` | `boolean` | `true` | Snap to the live edge when a backgrounded tab is refocused while far behind. Live/LL only. |
|
|
250
|
+
| `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. |
|
|
250
251
|
| `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. |
|
|
251
252
|
| `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`. |
|
|
252
253
|
| `analytics` | `boolean \| { endpoint?: string }` | `true` | QoE beacons. Active only when the playback response carries a server-minted `sid`. The beacons host is derived from `baseUrl` by hostname convention (`feed[-dev].<domain>` → `beacons[-dev].<domain>`). **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). |
|
|
@@ -276,10 +277,11 @@ don't need to set any of the live or ABR knobs:
|
|
|
276
277
|
on its own (its live‑sync only ever corrects being _behind_), so the stutter
|
|
277
278
|
would otherwise persist for the rest of the session. Live only: VOD seeks are
|
|
278
279
|
untouched, and the clamp stands down while chasing is suspended on a stale
|
|
279
|
-
playlist (there is no live edge left to protect at that point).
|
|
280
|
-
control bar
|
|
281
|
-
the
|
|
282
|
-
|
|
280
|
+
playlist (there is no live edge left to protect at that point). The custom
|
|
281
|
+
control bar hides its seek bar on a live stream (the Go‑live button and
|
|
282
|
+
`seekToLive()` are the way back to the edge); on VOD the bar covers the full
|
|
283
|
+
duration. Bring‑your‑own‑player (Mode B) owns its own seek UI and needs the
|
|
284
|
+
equivalent clamp itself.
|
|
283
285
|
- **Audio‑hole bridge**: `maxBufferHole` is raised to **0.5 s** so the ~0.45 s
|
|
284
286
|
audio "remainder" gap at each segment boundary is bridged as a sub‑perceptible
|
|
285
287
|
hitch instead of a rebuffer.
|
|
@@ -392,7 +394,8 @@ Notes:
|
|
|
392
394
|
|
|
393
395
|
The managed player ships a built-in, fully **skinnable** control bar. By default
|
|
394
396
|
(`controls: 'custom'`) it wraps your `<video>` in a `.havik-player` container and
|
|
395
|
-
renders the Oddin-branded UI: a center play/overlay, a seek bar
|
|
397
|
+
renders the Oddin-branded UI: a center play/overlay, a seek bar (VOD only; a
|
|
398
|
+
live stream shows none, see below), play/pause,
|
|
396
399
|
volume, a live badge + go-live, quality / audio / subtitle menus,
|
|
397
400
|
Picture-in-Picture, fullscreen, a buffering spinner, and **branded status cards**
|
|
398
401
|
for the stopped/pre-play states: _ended_ ("This stream has ended"), _fatal
|
|
@@ -740,8 +743,9 @@ if (next.notModified) {
|
|
|
740
743
|
function watchStatus(opts: WatchStatusOptions): StatusWatcher;
|
|
741
744
|
```
|
|
742
745
|
|
|
743
|
-
Polls a single match's live status and fires `onChange` on
|
|
744
|
-
|
|
746
|
+
Polls a single match's live status and fires `onChange` once on first read and then
|
|
747
|
+
on every change of the status, the teams (a logo resolved later) or the planned
|
|
748
|
+
start; unchanged polls stay silent. Returns a `StatusWatcher` with `stop()`. For a **push** channel (no
|
|
745
749
|
polling) use [`subscribeLiveState`](#subscribelivestate) instead; `watchStatus` is the
|
|
746
750
|
zero-dependency polling alternative when the SSE endpoint isn't available.
|
|
747
751
|
|
|
@@ -759,14 +763,14 @@ const watcher = watchStatus({
|
|
|
759
763
|
|
|
760
764
|
#### `WatchStatusOptions`
|
|
761
765
|
|
|
762
|
-
| Option | Type | Default | Description
|
|
763
|
-
| ------------ | --------------------------------------- | ------- |
|
|
764
|
-
| `baseUrl` | `string` | — | **Required.**
|
|
765
|
-
| `matchUrn` | `string` | — | **Required.**
|
|
766
|
-
| `credential` | [`CredentialSource`](#credentialsource) | — | **Required.**
|
|
767
|
-
| `onChange` | `(status: MatchStatus) => void` | — | **Required.** Fired on status
|
|
768
|
-
| `onError` | `(err: PlaybackError) => void` | — | Fired on a poll error; polling continues.
|
|
769
|
-
| `intervalMs` | `number` | `10000` | Poll interval (floored at `2000`).
|
|
766
|
+
| Option | Type | Default | Description |
|
|
767
|
+
| ------------ | --------------------------------------- | ------- | ----------------------------------------------------------------------------- |
|
|
768
|
+
| `baseUrl` | `string` | — | **Required.** |
|
|
769
|
+
| `matchUrn` | `string` | — | **Required.** |
|
|
770
|
+
| `credential` | [`CredentialSource`](#credentialsource) | — | **Required.** |
|
|
771
|
+
| `onChange` | `(status: MatchStatus) => void` | — | **Required.** Fired on first read, then on any status, teams or start change. |
|
|
772
|
+
| `onError` | `(err: PlaybackError) => void` | — | Fired on a poll error; polling continues. |
|
|
773
|
+
| `intervalMs` | `number` | `10000` | Poll interval (floored at `2000`). |
|
|
770
774
|
|
|
771
775
|
<a id="live-state-sse"></a>
|
|
772
776
|
|
|
@@ -1113,7 +1117,7 @@ type CredentialSource = Credential | (() => Credential | Promise<Credential>);
|
|
|
1113
1117
|
|
|
1114
1118
|
<a id="catalogmatch"></a>
|
|
1115
1119
|
|
|
1116
|
-
#### `Catalog`, `CatalogTournament`, `CatalogMatch`
|
|
1120
|
+
#### `Catalog`, `CatalogTournament`, `CatalogMatch`, `CatalogTeam`
|
|
1117
1121
|
|
|
1118
1122
|
```ts
|
|
1119
1123
|
interface Catalog {
|
|
@@ -1141,9 +1145,29 @@ interface CatalogMatch {
|
|
|
1141
1145
|
tournamentUrn: string;
|
|
1142
1146
|
tournamentName: string;
|
|
1143
1147
|
sport: string;
|
|
1148
|
+
// The two sides, home first. Absent when no usable team survives parsing:
|
|
1149
|
+
// the server sent no `teams` (an older havik-streams), or every entry
|
|
1150
|
+
// lacked both a URN and a name. A team with a URN but no name IS kept.
|
|
1151
|
+
// `matchName` stays the display string; this is its structured form.
|
|
1152
|
+
teams?: CatalogTeam[];
|
|
1153
|
+
}
|
|
1154
|
+
interface CatalogTeam {
|
|
1155
|
+
urn: string; // competitor URN, e.g. "od:competitor:26406"; "" if the server sent a name only
|
|
1156
|
+
name: string;
|
|
1157
|
+
isHome: boolean;
|
|
1158
|
+
// An https image URL for the team logo, present only when the deployment
|
|
1159
|
+
// resolves logos and this team has one (most do). Can still fail to load —
|
|
1160
|
+
// always render a fallback for a missing or broken image. Logos are ~150 px
|
|
1161
|
+
// on the long side. Allow cdn.oddin.gg (and cdn.runestone.gg) in img-src.
|
|
1162
|
+
logoUrl?: string;
|
|
1144
1163
|
}
|
|
1145
1164
|
```
|
|
1146
1165
|
|
|
1166
|
+
Parsing is tolerant: a non-array `teams` is ignored, entries with neither a
|
|
1167
|
+
URN nor a name are dropped, `isHome` is `true` only for a literal `true`, and a
|
|
1168
|
+
`logoUrl` that is not an absolute `https:` URL is dropped (an `http:` image is
|
|
1169
|
+
mixed content on an https page).
|
|
1170
|
+
|
|
1147
1171
|
#### `MatchStatus`, `MatchLiveStatus`
|
|
1148
1172
|
|
|
1149
1173
|
`pending` is "kickoff has passed, the stream is being packaged": not playable
|
|
@@ -1159,6 +1183,9 @@ interface MatchStatus {
|
|
|
1159
1183
|
// The server's status string verbatim — see CatalogMatch.statusRaw.
|
|
1160
1184
|
statusRaw?: string;
|
|
1161
1185
|
datePlannedStart?: string;
|
|
1186
|
+
// The teams as of this report (see CatalogTeam). watchStatus fires again
|
|
1187
|
+
// when they change, so a logo resolved later is re-reported.
|
|
1188
|
+
teams?: CatalogTeam[];
|
|
1162
1189
|
}
|
|
1163
1190
|
```
|
|
1164
1191
|
|
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.11.1/dist/havik-player.global.js" integrity="sha384-Obf3G9MSlv0HGCIg8N1q+sE5paXAcmESENBeaJshLhGlQnjO4nqg94zUkDoOvN0y" 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.11.1/dist/havik-player.global.js" integrity="sha384-Obf3G9MSlv0HGCIg8N1q+sE5paXAcmESENBeaJshLhGlQnjO4nqg94zUkDoOvN0y" 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.11.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,
|
|
@@ -77,6 +77,9 @@
|
|
|
77
77
|
"sbom": "npx --yes @cyclonedx/cyclonedx-npm@2 --omit dev --output-file sbom.json",
|
|
78
78
|
"prepack": "npm run build",
|
|
79
79
|
"test:localqoe": "playwright test -c localqoe/playwright.config.ts",
|
|
80
|
+
"test:liveedge": "playwright test -c localqoe/liveedge.config.ts",
|
|
81
|
+
"test:drmlive": "playwright test -c localqoe/drmlive.config.ts",
|
|
82
|
+
"localqoe:pick-live": "node localqoe/drmlive-pick-live.mjs",
|
|
80
83
|
"localqoe:media": "bash localqoe/make-media.sh && bash localqoe/make-live-media.sh",
|
|
81
84
|
"test:blackhole": "npm run build:global && playwright test -c blackhole/playwright.config.ts",
|
|
82
85
|
"blackhole:serve": "npm run build:global && node blackhole/server.mjs"
|