Skip to content

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

PlatformEngineKey system
Chrome / Firefox / Edgehls.jsWidevine
Android browsershls.jsWidevine
Safari (macOS)hls.js (ManagedMediaSource) or nativeFairPlay
Safari (iOS / iPadOS)hls.js (ManagedMediaSource) or nativeFairPlay
Chrome / Firefox / Edge on iOSNo Widevine CDM on iOS — check support first

There is no PlayReady path — Edge uses Widevine.

Check support before rendering

ts
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 optionally X-User-Id (userId option).
  • 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

  1. 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.
  2. player.on('warning', …) surfaces non-fatal DRM conditions (e.g. a failed background license refresh, FairPlay passthrough notes) without stopping playback.
  3. Include PlaybackError.requestId when contacting havik-support@oddin.gg — it correlates to server-side logs.

More patterns in Troubleshooting.

ISC licensed. Bundles hls.js (Apache-2.0).