@oddin-gg/havik-player 1.2.1 → 1.3.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/dist/havik-player.global.js +16 -16
- 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 +41 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +462 -282
- package/dist/index.js.map +1 -1
- package/docs/API.md +89 -39
- package/embed/index.html +1 -1
- package/package.json +1 -1
package/docs/API.md
CHANGED
|
@@ -148,21 +148,31 @@ See [`WaitForLive`](#waitforlive) for tuning, and [`createPlayer`](#createplayer
|
|
|
148
148
|
parameters. Use the **same api‑key** to resolve playback and to fetch licenses.
|
|
149
149
|
The managed and iframe players handle this for you.
|
|
150
150
|
|
|
151
|
-
> **FairPlay (Safari/iOS):**
|
|
152
|
-
>
|
|
153
|
-
>
|
|
154
|
-
>
|
|
151
|
+
> **FairPlay (Safari/iOS):** implemented on the hls.js engine. When the playback
|
|
152
|
+
> descriptor carries FairPlay material and the platform is WebKit, the player
|
|
153
|
+
> offers `com.apple.fps` alongside Widevine: hls.js fetches the application
|
|
154
|
+
> certificate, handles the encrypted event, POSTs the SPC to the signed license
|
|
155
|
+
> URL and applies the returned CKC. No configuration is needed beyond the same
|
|
156
|
+
> api‑key used for playback.
|
|
157
|
+
>
|
|
158
|
+
> **iOS third‑party browsers (Chrome/Firefox/Edge on iOS):** iOS has no
|
|
159
|
+
> Widevine CDM, so protected playback in these browsers depends entirely on
|
|
160
|
+
> whether the WebKit build grants them FairPlay. Don't assume either way —
|
|
161
|
+
> call [`detectDrmSupport`](#detectdrmsupport) and let the probe decide, so
|
|
162
|
+
> viewers see an accurate message instead of a black player.
|
|
155
163
|
|
|
156
164
|
### Browser support
|
|
157
165
|
|
|
158
|
-
| Browser | Engine
|
|
159
|
-
| -------------------------------- |
|
|
160
|
-
| Chrome, Edge (desktop & Android) | hls.js
|
|
161
|
-
| Firefox | hls.js
|
|
162
|
-
| Safari
|
|
166
|
+
| Browser | Engine | DRM |
|
|
167
|
+
| -------------------------------- | --------------------- | -------------------------------------------------- |
|
|
168
|
+
| Chrome, Edge (desktop & Android) | hls.js | Widevine ✓ |
|
|
169
|
+
| Firefox | hls.js | Widevine ✓ |
|
|
170
|
+
| Safari (macOS, iOS 17.1+) | hls.js (via MMS) | FairPlay ✓ (`com.apple.fps`) |
|
|
171
|
+
| Older Safari / iOS | native HLS (fallback) | FairPlay — OS passthrough only |
|
|
172
|
+
| Chrome/Firefox/Edge **on iOS** | hls.js | probe with `detectDrmSupport` (no Widevine on iOS) |
|
|
163
173
|
|
|
164
|
-
Requires a browser with Media Source Extensions
|
|
165
|
-
(all evergreen browsers).
|
|
174
|
+
Requires a browser with Media Source Extensions (or ManagedMediaSource) and
|
|
175
|
+
Encrypted Media Extensions (all evergreen browsers).
|
|
166
176
|
|
|
167
177
|
---
|
|
168
178
|
|
|
@@ -217,7 +227,7 @@ player.on('error', (e) => console.error(e.code, e.httpStatus, e.message));
|
|
|
217
227
|
| `errorMessage` | `string` | the error's message | Message on the fatal-error card (otherwise the error's own message). |
|
|
218
228
|
| `waitForLive` | [`WaitForLive`](#waitforlive) | — | Arm on an upcoming match and auto‑attach at go‑live. |
|
|
219
229
|
| `lowLatency` | `'auto' \| boolean` | `'auto'` | `'auto'` lets the player decide from the manifest. |
|
|
220
|
-
| `engine` | `'auto' \| 'hls' \| 'native'` | `'auto'` | `'auto'`
|
|
230
|
+
| `engine` | `'auto' \| 'hls' \| 'native'` | `'auto'` | `'auto'` prefers hls.js wherever it is supported — including modern Safari/iOS via ManagedMediaSource — and falls back to native HLS otherwise. |
|
|
221
231
|
| `userId` | `string` | — | Optional viewer id (sent as `X-User-Id` on license requests). |
|
|
222
232
|
| `debug` | `boolean` | `false` | Turn on hls.js's internal console logging — fragment/part scheduling, ABR switches, EME key sessions and license traffic. For diagnosing a playback problem with Oddin support; leave it off in production (it logs on every fragment, and the EME lines include license request/response metadata). hls.js engine only. `hlsConfig.debug` still wins if you set both. |
|
|
223
233
|
| `hlsConfig` | `Partial<HlsConfig>` | — | Escape hatch for advanced hls.js tuning. |
|
|
@@ -338,23 +348,23 @@ Notes:
|
|
|
338
348
|
|
|
339
349
|
`on(event, cb)` — the payload type depends on the event ([`PlayerEventMap`](#playereventmap)):
|
|
340
350
|
|
|
341
|
-
| Event | Payload | Fires when
|
|
342
|
-
| ------------------ | --------------------------------- |
|
|
343
|
-
| `ready` | `void` | The manifest is parsed and the player is attached.
|
|
344
|
-
| `waiting` | [`WaitState`](#waitstate) | Armed and waiting for go‑live
|
|
345
|
-
| `playing` | `void` | Playback is progressing.
|
|
346
|
-
| `buffering` | `void` | The player is rebuffering.
|
|
347
|
-
| `paused` | `void` | Playback is paused.
|
|
348
|
-
| `ended` | `void` | The live stream ended. Playback is stopped and the last frame is kept.
|
|
349
|
-
| `error` | [`PlaybackError`](#playbackerror) | A fatal error occurred (playback stopped).
|
|
350
|
-
| `warning` | [`PlaybackError`](#playbackerror) | A non-fatal condition (e.g. FairPlay passthrough, a failed license refresh). Playback continues.
|
|
351
|
-
| `autoplayblocked` | `void` | The browser blocked autoplay (`NotAllowedError`). Show a tap-to-play / tap-to-unmute affordance, then call `player.play()` from the gesture.
|
|
352
|
-
| `qualitychange` | `number` | Active rendition changed (level index, `-1` = auto).
|
|
353
|
-
| `audiotrackchange` | `number` | Active audio track changed (track id).
|
|
354
|
-
| `texttrackchange` | `number` | Active text track changed (track id, `-1` = off).
|
|
355
|
-
| `pipchange` | `boolean` | Picture-in-Picture entered (`true`) or exited (`false`).
|
|
356
|
-
| `stats` | [`PlaybackStats`](#playbackstats) | Periodic stats sample (`statsIntervalMs`).
|
|
357
|
-
| `statechange` | [`PlayerState`](#playerstate) | Any state transition.
|
|
351
|
+
| Event | Payload | Fires when |
|
|
352
|
+
| ------------------ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
353
|
+
| `ready` | `void` | The manifest is parsed and the player is attached. |
|
|
354
|
+
| `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). |
|
|
355
|
+
| `playing` | `void` | Playback is progressing. |
|
|
356
|
+
| `buffering` | `void` | The player is rebuffering. |
|
|
357
|
+
| `paused` | `void` | Playback is paused. |
|
|
358
|
+
| `ended` | `void` | The live stream ended. Playback is stopped and the last frame is kept. |
|
|
359
|
+
| `error` | [`PlaybackError`](#playbackerror) | A fatal error occurred (playback stopped). |
|
|
360
|
+
| `warning` | [`PlaybackError`](#playbackerror) | A non-fatal condition (e.g. FairPlay passthrough, a failed license refresh). Playback continues. |
|
|
361
|
+
| `autoplayblocked` | `void` | The browser blocked autoplay (`NotAllowedError`). Show a tap-to-play / tap-to-unmute affordance, then call `player.play()` from the gesture. |
|
|
362
|
+
| `qualitychange` | `number` | Active rendition changed (level index, `-1` = auto). |
|
|
363
|
+
| `audiotrackchange` | `number` | Active audio track changed (track id). |
|
|
364
|
+
| `texttrackchange` | `number` | Active text track changed (track id, `-1` = off). |
|
|
365
|
+
| `pipchange` | `boolean` | Picture-in-Picture entered (`true`) or exited (`false`). |
|
|
366
|
+
| `stats` | [`PlaybackStats`](#playbackstats) | Periodic stats sample (`statsIntervalMs`). |
|
|
367
|
+
| `statechange` | [`PlayerState`](#playerstate) | Any state transition. |
|
|
358
368
|
|
|
359
369
|
<a id="controls--theming"></a>
|
|
360
370
|
|
|
@@ -550,6 +560,45 @@ hls.loadSource(stream.manifestUrl);
|
|
|
550
560
|
hls.attachMedia(videoElement);
|
|
551
561
|
```
|
|
552
562
|
|
|
563
|
+
<a id="detectdrmsupport"></a>
|
|
564
|
+
|
|
565
|
+
### `detectDrmSupport`
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
function detectDrmSupport(opts?: {
|
|
569
|
+
systems?: DrmSystem[]; // default: ['widevine', 'fairplay'], probed in order
|
|
570
|
+
timeoutMs?: number; // OVERALL budget for the whole call, default 5000
|
|
571
|
+
}): Promise<DrmSupportResult>;
|
|
572
|
+
|
|
573
|
+
interface DrmSupportResult {
|
|
574
|
+
verdict: 'supported' | 'no-cdm' | 'insecure-context' | 'blocked-by-policy' | 'probe-timeout';
|
|
575
|
+
system?: DrmSystem; // first working system, when supported
|
|
576
|
+
detail: Partial<Record<DrmSystem, string>>; // per-system outcome, for support bundles
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Answers "can protected playback work in this browser at all?" so you can show
|
|
581
|
+
an accurate message instead of a black player. It probes
|
|
582
|
+
`requestMediaKeySystemAccess` **and** `createMediaKeys()` per system (access
|
|
583
|
+
alone is not proof), with configs matching what the engine will actually
|
|
584
|
+
request, bounded by a timeout. It never rejects.
|
|
585
|
+
|
|
586
|
+
Rules of use:
|
|
587
|
+
|
|
588
|
+
- **Call it lazily, cache the verdict yourself** — only once you know the
|
|
589
|
+
content is DRM‑protected (`descriptor.drmEnabled`). The EME spec allows the
|
|
590
|
+
probe to trigger consent prompts or CDM downloads, and results are not
|
|
591
|
+
memoized; never run it on page load, and never probe for a clear stream.
|
|
592
|
+
- **Probe, don't UA‑sniff.** The verdict is the playability gate; use the UA
|
|
593
|
+
only to word the message.
|
|
594
|
+
- `insecure-context` means EME is unavailable by spec — typical when testing
|
|
595
|
+
over `http://<LAN‑IP>`; `localhost` is fine.
|
|
596
|
+
- `blocked-by-policy` means an embedding problem (an iframe missing
|
|
597
|
+
`allow="encrypted-media"`), not an unsupported browser — surface it to the
|
|
598
|
+
integrator, not the viewer.
|
|
599
|
+
- `supported` is necessary, not sufficient: licensing, CORS onboarding and
|
|
600
|
+
packaging still apply.
|
|
601
|
+
|
|
553
602
|
<a id="getdeviceid"></a>
|
|
554
603
|
|
|
555
604
|
### `getDeviceId` / `clearDeviceId`
|
|
@@ -1123,16 +1172,17 @@ published at [status.oddin-video.gg](https://status.oddin-video.gg).
|
|
|
1123
1172
|
|
|
1124
1173
|
## Troubleshooting
|
|
1125
1174
|
|
|
1126
|
-
| Symptom | Likely cause
|
|
1127
|
-
| ---------------------------------------------------------- |
|
|
1128
|
-
| `NETWORK` error on every call; preflight fails in devtools | Your origin isn't on the key's allowed‑origins / DRM CORS allow‑list. See [Onboarding](#onboarding--cors).
|
|
1129
|
-
| `UNAUTHORIZED` (401) | Wrong/expired api‑key, or wrong `baseUrl` environment (test vs prod key).
|
|
1130
|
-
| `FORBIDDEN` (403) on license requests | The license URL was modified, or a different api‑key was used for the license than for playback. POST the URL verbatim with the same key.
|
|
1131
|
-
| `NOT_FOUND` (404) for a match you can see | The key isn't entitled to that tournament (404 is intentionally indistinguishable from "unknown").
|
|
1132
|
-
| Player stays in `waiting` forever | The match hasn't gone live yet; the SDK is polling. Set `waitForLive.timeoutMs` to bound it.
|
|
1133
|
-
| Autoplay doesn't start | Browser sound policy — set `muted: true` for autoplay, or start playback from a user gesture.
|
|
1134
|
-
| Black video on Safari with DRM |
|
|
1135
|
-
| Black video
|
|
1175
|
+
| Symptom | Likely cause |
|
|
1176
|
+
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1177
|
+
| `NETWORK` error on every call; preflight fails in devtools | Your origin isn't on the key's allowed‑origins / DRM CORS allow‑list. See [Onboarding](#onboarding--cors). |
|
|
1178
|
+
| `UNAUTHORIZED` (401) | Wrong/expired api‑key, or wrong `baseUrl` environment (test vs prod key). |
|
|
1179
|
+
| `FORBIDDEN` (403) on license requests | The license URL was modified, or a different api‑key was used for the license than for playback. POST the URL verbatim with the same key. |
|
|
1180
|
+
| `NOT_FOUND` (404) for a match you can see | The key isn't entitled to that tournament (404 is intentionally indistinguishable from "unknown"). |
|
|
1181
|
+
| Player stays in `waiting` forever | The match hasn't gone live yet; the SDK is polling. Set `waitForLive.timeoutMs` to bound it. |
|
|
1182
|
+
| Autoplay doesn't start | Browser sound policy — set `muted: true` for autoplay, or start playback from a user gesture. |
|
|
1183
|
+
| Black video on Safari with DRM | Check the console for an EME/CDM error; verify the origin is on the DRM CORS allow‑list. On very old Safari the native fallback plays only OS‑trusted passthrough. |
|
|
1184
|
+
| Black video in Chrome/Firefox/Edge **on iOS** | iOS has no Widevine CDM, and FairPlay availability outside Safari varies by WebKit build. Call `detectDrmSupport()`: on `no-cdm`, tell the viewer this browser can't play protected streams. |
|
|
1185
|
+
| Black video, `levelHeight`/`droppedFrames` stuck | Check the browser console for an EME/CDM error and verify DRM CORS onboarding. |
|
|
1136
1186
|
|
|
1137
1187
|
Include `PlaybackError.requestId` (when present) when contacting support — it correlates
|
|
1138
1188
|
to server‑side logs.
|
package/embed/index.html
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
apiKey, parentOrigin). When injected, the URL query string is IGNORED for
|
|
19
19
|
security; the api-key must NOT travel in the query in production.
|
|
20
20
|
-->
|
|
21
|
-
<script src="https://cdn.jsdelivr.net/npm/@oddin-gg/havik-player@1.
|
|
21
|
+
<script src="https://cdn.jsdelivr.net/npm/@oddin-gg/havik-player@1.3.0/dist/havik-player.global.js" integrity="sha384-F3IxzdIz3HFRw1ocTFFP1C3j1Q49vcrjE53xL+Bzf2rrS/+oSTT5LtFGbz9LxUwx" crossorigin="anonymous"></script>
|
|
22
22
|
<script>
|
|
23
23
|
HavikPlayer.bootEmbed();
|
|
24
24
|
</script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oddin-gg/havik-player",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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,
|