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 this is all automatic; this page covers the cases where it isn't.

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

  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, because it correlates to server-side logs.

More patterns in Troubleshooting.

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