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​