Skip to content

Plugin: DRM

DrmPlugin (id 'drm') coordinates Encrypted Media Extensions (EME) license acquisition for Widevine, FairPlay, and PlayReady, and carries HDCP / output-protection requirements. Registration and license-fetch routing are real; the actual key-system handshake is gated behind requestMediaKeySystemAccess, so environments without EME (headless test runners, older browsers) stay quiet instead of throwing.

Options

OptionTypePurpose
keySystemstringEME key system identifier, 'com.widevine.alpha', 'com.apple.fps', 'com.microsoft.playready', etc.
licenseUrlstringLicense server URL. Required before fetchLicense() can be called.
certificateArrayBuffer | stringFairPlay service certificate. Optional for Widevine/PlayReady.
customSignRequest(request: Request) => Request | Promise<Request>Sign the license request (HMAC, proprietary auth) before it goes out.
transformLicenseRequest(challenge: ArrayBuffer) => ArrayBuffer | Promise<ArrayBuffer>Transform the challenge body before sending.
transformLicenseResponse(response: ArrayBuffer) => ArrayBuffer | Promise<ArrayBuffer>Transform the license response body after receiving.
hdcpRequired'type-0' | 'type-1' | 'none'Output protection requirement.

Events

EventPayloadFires when
key:error{ sessionId, error }The key-system handshake fails.
unsupported{ reason }EME isn’t available in this environment, either at plugin registration or at handshake time.

How activation works

The plugin listens for the player’s 'item' event, when the incoming item carries a drm.keySystem hint (a consumer-supplied field on your own item shape, not part of the canonical VideoPlaylistItem), it attempts the EME handshake for that key system, falling back to the plugin’s own keySystem option when the item doesn’t specify one:

TypeScript
import nmplayer from '@nomercy-entertainment/nomercy-video-player';
import { DrmPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins/drm';

const player = nmplayer('player');

player.addPlugin(DrmPlugin, {
keySystem: 'com.widevine.alpha',
licenseUrl: 'https://license.example.com/widevine',
});
await player.ready();

fetchLicense(challenge) posts the challenge to licenseUrl, wrapped through customSignRequest when provided, and through transformLicenseRequest / transformLicenseResponse for payload shaping either direction:

TypeScript
const drm = player.getPlugin(DrmPlugin);
const licenseBlob = await drm?.fetchLicense(challengeBuffer);

Rules & restrictions

  • requestMediaKeySystemAccess missing at registration time emits unsupported immediately and the plugin does nothing further, it does not throw and does not retry.
  • fetchLicense() throws core:drm/license-url-missing if licenseUrl was never configured, this is a real, addressable configuration error, not a soft failure.
  • All license network I/O goes through the kit’s Plugin.fetch(), the same auth pipeline every other plugin uses, so the auth config from setup() (bearer token, custom headers, request signing) already applies without extra config here.
  • mediaKeys() returns null whenever EME isn’t supported or the handshake hasn’t completed, always guard before use.

How to extend it

Subclass to add key-system-specific certificate handling or a different license transport (a WebSocket-based DRM proxy, for instance):

TypeScript
import { DrmPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins/drm';

class MyDrmPlugin extends DrmPlugin {
override async fetchLicense(challenge: ArrayBuffer): Promise<ArrayBuffer> {
// custom transport, then fall back to the base implementation if needed
return super.fetchLicense(challenge);
}
}

player.addPlugin(MyDrmPlugin, { keySystem: 'com.widevine.alpha', licenseUrl });

Next steps