Skip to main content

DRM Downloading

Download protected HLS/DASH streams for offline playback. At a minimum you need: encrypted media, a license server that issues offline (persistent) licenses, and a short‑lived token from your backend.

Prerequisites

Critical requirement

Your DRM provider must support persistent (offline) licenses. Without persistent/offline license support, offline downloads will not work.

  • DRM vendor account or your own Widevine/FairPlay license server
  • Packaged encrypted HLS/DASH e.g., with Shaka Packager
  • Backend entitlement/token endpoint (recommended)
  • SDK installed and registered

You can quickly verify your client setup using our Free DRM Token Generator for Video before wiring your own entitlement backend.

High-level Flow

  1. Package and encrypt your content (HLS/DASH, CMAF/CENC).
  2. Obtain license server URLs (Widevine/FairPlay) and the FairPlay certificate.
  3. Implement an entitlement endpoint that returns a short-lived token.
  4. On the client, fetch token and call downloadStream with drm config.

Entitlement endpoint (overview)

For production, your app should request a short‑lived token from your backend and include it in DRM license requests. You can set up a simple endpoint (for example, with an Express server) that validates user entitlement and returns a signed token required by your DRM provider. Keep vendor secrets on the server and issue tokens scoped to user and asset with short expirations.

Client Usage (React Native)

Quick link
import React from "react";
import { Platform } from "react-native";
import {
downloadStream,
registerPlugin,
} from "@TheWidlarzGroup/react-native-video-stream-downloader";

const API_KEY = "YOUR_SDK_API_KEY";

async function getDrmToken(assetId: string) {
const resp = await fetch("https://your-backend.example.com/drm/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: "CURRENT_USER_ID", assetId }),
});
if (!resp.ok) throw new Error("Failed to get DRM token");
const json = await resp.json();
return json.token as string;
}

export async function downloadDrmExample() {
await registerPlugin(API_KEY);

const platform = Platform.OS as "ios" | "android";

const licenseServer =
platform === "ios"
? "https://your-fairplay-license-server.com/license"
: "https://your-widevine-license-server.com/license";

const drmConfig: {
licenseServer?: string;
certificateUrl?: string;
headers?: Record<string, string>;
getLicense?: (
spcString: string,
contentId: string,
licenseUrl: string,
loadedLicenseUrl: string
) => Promise<string> | string;
} = {
licenseServer,
certificateUrl:
platform === "ios"
? "https://your-fairplay-certificate.com/certificate"
: undefined,
headers: { "x-drm-usertoken": await getDrmToken("asset-1234") },
};

await downloadStream("https://example.com/drm-video.m3u8", {
drm: drmConfig,
});
}

For a complete, runnable DRM example, see the Examples page.

Custom License Acquisition (iOS Only)

Instead of using licenseServer, you can manually acquire the license using the getLicense function. This gives you full control over the license request process:

const drmConfig = {
certificateUrl: "https://your-fairplay-certificate.com/certificate",
getLicense: (spcString, contentId, licenseUrl, loadedLicenseUrl) => {
const base64spc = Base64.encode(spcString);
const formData = new FormData();
formData.append("spc", base64spc);

return fetch(`https://license.pallycon.com/ri/licenseManager.do`, {
method: "POST",
headers: {
"pallycon-customdata-v2": "your-custom-header",
"Content-Type": "application/x-www-form-urlencoded",
},
body: formData,
})
.then((response) => response.text())
.then((response) => response)
.catch((error) => {
console.error("License acquisition error:", error);
throw error;
});
},
};

getLicense Parameters

  • spcString: The SPC (Server Playback Context) used for DRM validation
  • contentId: The content ID from the DRM object or loadingRequest.request.url?.host
  • licenseUrl: The URL passed in the DRM object
  • loadedLicenseUrl: The URL retrieved from loadingRequest.request.URL.absoluteString, starting with skd:// or clearkey://

You should return a Base64-encoded CKC response, either directly or as a Promise.

Testing

Prefer real devices over emulators/simulators for DRM and offline license flows. Virtual devices often lack proper DRM components and may not behave reliably.

FairPlay Notes (iOS)

DRM configuration in this SDK mirrors the approach used by react-native-video (1:1 style props for license server, certificate, headers). Platform specifics are handled internally; use real devices for reliable testing.

License Expiration & Renewal

  • Use short-lived entitlement tokens; include asset and user context.
  • Track expiresAt in your app; prompt renewal when near expiry.

Helpful References