DRM
Havik streams are DRM-protected (CMAF/fMP4, cbcs encryption) with Widevine and FairPlay key systems. In the managed player and the hosted embed, all of this is automatic — this page is what you need to know around the edges.
Who plays what
| Platform | Engine | Key system |
|---|---|---|
| Chrome / Firefox / Edge | hls.js | Widevine |
| Android browsers | hls.js | Widevine |
| Safari (macOS) | hls.js (ManagedMediaSource) or native | FairPlay |
| Safari (iOS / iPadOS) | hls.js (ManagedMediaSource) or native | FairPlay |
| Chrome / Firefox / Edge on iOS | — | No Widevine CDM on iOS — check support first |
There is no PlayReady path — Edge uses Widevine.
Check support before rendering
import { detectDrmSupport } from '@oddin-gg/havik-player';
const support = await detectDrmSupport();
if (support.level === 'no-cdm') {
// e.g. Chrome on iOS: no Widevine CDM, FairPlay varies by WebKit build.
showUnsupportedBrowserNotice();
}See detectDrmSupport for the full result shape. The classic case worth handling: third-party browsers on iOS have no Widevine and unreliable FairPlay — tell the viewer to open Safari rather than letting them stare at black video.
What the SDK does for you
- Negotiates EME (
encrypted-media) and selects the key system. - Attaches the license request headers —
x-api-key,X-Match-Urn,X-Device-Id, and optionallyX-User-Id(userIdoption). - POSTs the signed license URL verbatim — the query string is the token.
- Silently refreshes the license URL every 8 minutes (
licenseRefreshMs) so long sessions don't die on an expired token. - Persists a stable per-browser device id (see
getDeviceId).
In Mode B the resolution and headers are yours to wire — the same rules apply, and breaking the verbatim-URL rule is the #1 cause of 403s.
CORS: the silent killer
The license POST is cross-origin. If your page's origin isn't on the DRM service's CORS allow-list, the request dies in preflight — no license, no error dialog, just black video with audio-less frozen frames. The fix is onboarding, not code.
Iframe embeds add one more origin to the list: the parent page's origin (AllowedIframeParents).
Debugging a DRM failure
- Open devtools → Network, filter the license URL. Preflight failed? Origin isn't allow-listed. 403? URL was modified or a different api-key was used than for playback. 200 but no video? Check the console for EME/CDM errors.
player.on('warning', …)surfaces non-fatal DRM conditions (e.g. a failed background license refresh, FairPlay passthrough notes) without stopping playback.- Include
PlaybackError.requestIdwhen contacting havik-support@oddin.gg — it correlates to server-side logs.
More patterns in Troubleshooting.