DRM
What is DRM (Digital Rights Management)?
DRM is a set of access control technologies that are used to protect copyrighted content from unauthorized use and distribution. It allows content owners to control how their digital media is used and distributed.
When do you need it?
If you are working with copyrighted content and want to prevent unauthorized access or distribution, you will need DRM. It is especially important for streaming services, e-learning platforms, and any application that delivers premium content that you want to protect from piracy.
What next?
This page explains how to play DRM‑protected content with React Native Video using the official DRM plugin. It covers installing and enabling the plugin, configuring sources with DRM, and platform‑specific notes for Android (Widevine) and iOS/visionOS (FairPlay).
Install and enable the DRM plugin
React Native Video uses a plugin architecture. DRM support is provided by the @twg/react-native-video-drm
plugin and is not built into the core package.
- Install dependencies in your app:
npm install @twg/react-native-video-drm
- Enable the plugin at app startup (before creating any players):
// App.tsx (or any place you want to initialize the plugin)
import { enable } from '@twg/react-native-video-drm';
enable();
The plugin autolinks on both Android and iOS. Nitro Modules are required because the plugin uses Nitro under the hood.
Quick start
You pass DRM configuration via VideoConfig.drm
when creating a player or using the useVideoPlayer
hook. If drm.type
is omitted, the default is inferred per platform (widevine
on Android, fairplay
on iOS/visionOS).
import { VideoView, useVideoPlayer } from 'react-native-video';
export function Player() {
const player = useVideoPlayer({
source: {
uri: 'https://example.com/manifest.mpd', // or HLS .m3u8 for FairPlay
// On iOS these headers are also used for the default license request
headers: { Authorization: 'Bearer <token>' },
drm: {
// type: 'widevine' | 'fairplay' // optional; inferred by platform
licenseUrl: 'https://license.example.com/widevine',
},
},
});
return <VideoView player={player} />;
}
You shouldn't include your authorization token directly in the code. Instead, use a backend method to retrieve it at runtime.
DRM config reference
All properties are optional unless marked otherwise for a platform. The table below describes each property, the expected type, platforms where it applies, whether it's required, and important notes.
Property | Type | Platform | Required | Notes |
---|---|---|---|---|
type | 'widevine' | 'fairplay' | Android, iOS, visionOS | No (defaulted) | Default inferred per platform when drm is present and type omitted (Android → widevine , iOS/visionOS → fairplay ). |
licenseUrl | string | Android, iOS, visionOS | Android: Yes; iOS/visionOS: Yes for default/custom flows | URL of the license (CKC) service. Required for license acquisition. |
licenseHeaders | Record<string, string> | Android | No | Extra headers sent with the Widevine license request. (On iOS, use source.headers for license requests.) |
multiSession | boolean | Android | No | Whether to allow multiple Widevine sessions. |
certificateUrl | string | iOS, visionOS | Yes (for FairPlay) | URL to fetch the FairPlay application certificate (used to create the SPC). |
contentId | string | iOS, visionOS | No | If omitted, derived from the skd:// key URL. Used when creating the SPC. |
getLicense | (payload) => Promise<string> | iOS, visionOS | No | Optional hook for custom FairPlay license logic; must resolve to a base64‑encoded CKC string. |
Payload shape passed to getLicense
(iOS/visionOS):
Field | Type | Description |
---|---|---|
contentId | string | Content identifier for the asset. If not provided the player will try to derive it from the skd:// key URL. |
licenseUrl | string | The license server URL that should be used for license acquisition. |
keyUrl | string | The key URL/identifier received from the stream (typically an skd:// URL). |
spc | string | The SPC (secure playback context) as a base64‑encoded string. Send raw SPC bytes to your license server (server side may expect raw bytes rather than base64). |
Android: Widevine
- Set
drm.type
to'widevine'
or omit it (the library will default to Widevine on Android ifdrm
is present). licenseUrl
is required;licenseHeaders
andmultiSession
are optional.- Implementation details:
- The plugin uses ExoPlayer’s
DefaultDrmSessionManager
andHttpMediaDrmCallback
with yourlicenseUrl
andlicenseHeaders
. - If a first attempt fails due to device security level issues, the plugin retries with
L3
security level.
- The plugin uses ExoPlayer’s
Example:
useVideoPlayer({
source: {
uri: 'https://example.com/manifest.mpd',
drm: {
// type: 'widevine', // optional
licenseUrl: 'https://license.example.com/widevine',
licenseHeaders: { 'X-Custom-Header': 'value' },
multiSession: false,
},
},
});
iOS and visionOS: FairPlay
Two ways to get the CKC (license):
-
Default flow (no
getLicense
):- Required:
certificateUrl
,licenseUrl
. - The plugin requests the application certificate, generates the SPC, then POSTs the SPC to
licenseUrl
. - It uses
source.headers
(notdrm.licenseHeaders
) for the license request.
- Required:
-
Custom flow (provide
getLicense
):- Required:
certificateUrl
,licenseUrl
, and agetLicense
implementation. - You receive
{ contentId, licenseUrl, keyUrl, spc }
and must return a base64‑encoded CKC string.
- Required:
Notes:
- DRM isn’t supported in the iOS Simulator; the plugin returns
null
for DRM in Simulator builds. - If
contentId
isn’t provided, it is derived from theskd://
key URL.
Default flow example:
useVideoPlayer({
source: {
uri: 'https://example.com/fairplay.m3u8',
headers: { Authorization: 'Bearer <token>' }, // used for the license request
drm: {
// type: 'fairplay', // optional
certificateUrl: 'https://license.example.com/fps-cert',
licenseUrl: 'https://license.example.com/fps',
// contentId: 'my-content-id', // optional
},
},
});
Custom getLicense
example:
This is example code for a custom getLicense
implementation. it may differ from your actual implementation provided by your DRM provider
useVideoPlayer({
source: {
uri: 'https://example.com/fairplay.m3u8',
drm: {
certificateUrl: 'https://license.example.com/fps-cert',
licenseUrl: 'https://license.example.com/fps',
getLicense: async ({ contentId, licenseUrl, keyUrl, spc }) => {
// Example: POST SPC to your license server and return base64 CKC
const res = await fetch(licenseUrl, {
method: 'POST',
body: Buffer.from(spc, 'base64'), // server expects raw SPC bytes
headers: {
'Content-Type': 'application/octet-stream',
'X-Content-ID': contentId,
'X-Asset-Id': keyUrl,
},
});
if (!res.ok) throw new Error(`License request failed: ${res.status}`);
const ckc = await res.arrayBuffer();
// return base64 CKC string
return Buffer.from(ckc).toString('base64');
},
},
},
});
Offline
If you are looking for implementing offline playback with DRM, make sure to checkout our Offline Video SDK. It provides a comprehensive solution for downloading and playing Streams and DRM-protected content.
Troubleshooting
- DRMPluginNotFound: Ensure you installed
@twg/react-native-video-drm
, imported it, and calledenable()
before creating any players. - iOS headers: The default FairPlay flow uses
source.headers
for license requests;drm.licenseHeaders
are not used on iOS. - Invalid CKC:
getLicense
must return a base64 string. Returning raw bytes or JSON will fail. - 403/415 from license server: Verify required auth headers, content type, and whether the server expects raw SPC bytes vs base64.
- Android security level issues: The plugin retries with Widevine L3 if the first attempt fails.
- iOS Simulator: DRM isn’t supported in Simulator. Test on a real device.
Notes and defaults
- If
drm
is provided withouttype
, the library sets a platform default: Android → Widevine, iOS/visionOS → FairPlay. - For custom DRM systems or advanced pipelines, you can implement your own plugin. See the Plugin Interface docs.