DRM
Havik streams are DRM-protected (CMAF/fMP4, cbcs encryption) with Widevine and FairPlay key systems. In the managed player and the hosted embed this is all automatic; this page covers the cases where it isn't.
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 case worth handling is third-party browsers on iOS, which have no Widevine and unreliable FairPlay. Tell the viewer to open Safari instead of leaving them on black video.
In a native app none of this browser variance applies: the iOS SDK uses FairPlay natively and the Android SDK uses Widevine natively.
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, because 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 a rewritten license URL is the most common cause of a 403.
CORS on the license request
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. There is no license and no error dialog. The viewer sees black video with frozen frames and no audio. This is fixed during onboarding; no code change helps.
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, because it correlates to server-side logs.
More patterns in Troubleshooting.