DRM (Digital Rights Management)
DRM allows you to protect copyrighted content from unauthorized use and distribution. React Native Video supports Widevine (Android) and FairPlay (iOS/visionOS) through an official DRM plugin.
What is DRM?
DRM is a set of access control technologies used to protect copyrighted content. It allows content owners to:
- Control how digital media is used and distributed
- Prevent unauthorized copying and sharing
- Enforce licensing and subscription models
- Track content usage and consumption
When Do You Need DRM?
You need DRM if you’re working with:
- Premium video content (movies, TV shows)
- Streaming services with paid subscriptions
- E-learning platforms with proprietary content
- Live sports or events
- Any copyrighted content that needs protection from piracy
Installation
npm install @react-native-video/drm
Enable the plugin at app startup
Before creating any video players, enable the DRM plugin:
// App.tsx
import { enable } from '@react-native-video/drm';
enable();
The plugin uses Nitro Modules and autolinks on both Android and iOS. Make sure to rebuild your app after installation.
Basic Usage
Pass DRM configuration via the drm property when creating a player:
import { VideoView, useVideoPlayer } from 'react-native-video';
export function DRMPlayer() {
const player = useVideoPlayer({
uri: 'https://example.com/manifest.mpd', // DASH for Widevine
drm: {
licenseUrl: 'https://license.example.com/widevine',
},
});
return <VideoView player={player} />;
}
Never hardcode authorization tokens in your code. Retrieve them from your backend at runtime.
Android: Widevine
Widevine is Google’s DRM technology used on Android devices.
Configuration
const player = useVideoPlayer({
uri: 'https://example.com/manifest.mpd', // DASH manifest
drm: {
type: 'widevine', // Optional: inferred automatically on Android
licenseUrl: 'https://license.example.com/widevine',
licenseHeaders: {
'X-Custom-Header': 'value',
'Authorization': 'Bearer YOUR_TOKEN',
},
multiSession: false,
},
});
DRM Configuration Properties (Android)
| Property | Type | Required | Description |
|---|
type | 'widevine' | No | DRM type (auto-inferred on Android) |
licenseUrl | string | Yes | URL of the Widevine license server |
licenseHeaders | Record<string, string> | No | Additional headers for license requests |
multiSession | boolean | No | Allow multiple Widevine sessions |
Implementation Details
The plugin uses ExoPlayer’s DefaultDrmSessionManager with HttpMediaDrmCallback. If the initial license request fails due to device security level issues, the plugin automatically retries with Widevine L3 security level.
Complete Example
import { VideoView, useVideoPlayer } from 'react-native-video';
import { useState, useEffect } from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
export function WidevinePlayer() {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
// Fetch token from your backend
fetchAuthToken().then(setToken);
}, []);
if (!token) {
return <ActivityIndicator />;
}
const player = useVideoPlayer({
uri: 'https://example.com/manifest.mpd',
drm: {
type: 'widevine',
licenseUrl: 'https://license.example.com/widevine',
licenseHeaders: {
'Authorization': `Bearer ${token}`,
},
},
});
return (
<View style={styles.container}>
<VideoView player={player} style={styles.video} controls />
</View>
);
}
async function fetchAuthToken(): Promise<string> {
const response = await fetch('https://your-backend.com/auth/token');
const data = await response.json();
return data.token;
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#000' },
video: { width: '100%', height: 300 },
});
iOS and visionOS: FairPlay
FairPlay is Apple’s DRM technology used on iOS, iPadOS, tvOS, and visionOS.
Default License Flow
The simplest approach uses the default license acquisition flow:
const player = useVideoPlayer({
uri: 'https://example.com/fairplay.m3u8', // HLS manifest
headers: {
'Authorization': 'Bearer YOUR_TOKEN', // Used for license requests
},
drm: {
type: 'fairplay', // Optional: inferred automatically on iOS
certificateUrl: 'https://license.example.com/fps-cert',
licenseUrl: 'https://license.example.com/fps',
contentId: 'my-content-id', // Optional: derived from skd:// URL if omitted
},
});
On iOS, the default flow uses source.headers for license requests, not drm.licenseHeaders.
DRM Configuration Properties (iOS)
| Property | Type | Required | Description |
|---|
type | 'fairplay' | No | DRM type (auto-inferred on iOS) |
certificateUrl | string | Yes | URL to fetch the FairPlay application certificate |
licenseUrl | string | Yes | URL of the license (CKC) server |
contentId | string | No | Content identifier (derived from skd:// URL if omitted) |
getLicense | (payload) => Promise<string> | No | Custom license acquisition function |
Custom License Acquisition
For advanced use cases, implement custom license logic with getLicense:
const player = useVideoPlayer({
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 }) => {
// The SPC (Server Playback Context) is base64-encoded
// Most servers expect raw bytes, so decode it
const spcBuffer = Buffer.from(spc, 'base64');
const response = await fetch(licenseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream',
'X-Content-ID': contentId,
'X-Asset-Id': keyUrl,
'Authorization': 'Bearer YOUR_TOKEN',
},
body: spcBuffer,
});
if (!response.ok) {
throw new Error(`License request failed: ${response.status}`);
}
// Get the CKC (Content Key Context) response
const ckc = await response.arrayBuffer();
// Must return base64-encoded CKC string
return Buffer.from(ckc).toString('base64');
},
},
});
getLicense Payload
The getLicense function receives a payload object:
| Field | Type | Description |
|---|
contentId | string | Content identifier for the asset |
licenseUrl | string | License server URL |
keyUrl | string | Key URL from the stream (typically skd:// URL) |
spc | string | Server Playback Context (SPC) as base64-encoded string |
The getLicense function must return a base64-encoded CKC string. Returning raw bytes or JSON will cause playback to fail.
Complete FairPlay Example
import { VideoView, useVideoPlayer } from 'react-native-video';
import { View, StyleSheet, Platform, Alert } from 'react-native';
export function FairPlayPlayer() {
const player = useVideoPlayer(
{
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 }) => {
try {
// Decode base64 SPC to raw bytes
const spcBuffer = Buffer.from(spc, 'base64');
// Request license from your server
const response = await fetch(licenseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream',
'X-Content-ID': contentId,
},
body: spcBuffer,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const ckc = await response.arrayBuffer();
return Buffer.from(ckc).toString('base64');
} catch (error) {
console.error('FairPlay license error:', error);
throw error;
}
},
},
},
(player) => {
player.addEventListener('onError', (error) => {
Alert.alert('Playback Error', error.message);
});
}
);
return (
<View style={styles.container}>
<VideoView player={player} style={styles.video} controls />
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#000' },
video: { width: '100%', height: 300 },
});
Automatically select the correct DRM type based on platform:
import { Platform } from 'react-native';
const player = useVideoPlayer({
uri: Platform.select({
android: 'https://example.com/manifest.mpd', // DASH
ios: 'https://example.com/fairplay.m3u8', // HLS
}),
drm: Platform.select({
android: {
licenseUrl: 'https://license.example.com/widevine',
licenseHeaders: { 'Authorization': 'Bearer TOKEN' },
},
ios: {
certificateUrl: 'https://license.example.com/fps-cert',
licenseUrl: 'https://license.example.com/fps',
},
}),
});
Troubleshooting
DRMPluginNotFound Error
Ensure you:
- Installed
@react-native-video/drm
- Called
enable() before creating any players
- Rebuilt your app after installation
On iOS, use source.headers instead of drm.licenseHeaders for the default license flow:
// ✅ Correct
const player = useVideoPlayer({
uri: 'https://example.com/video.m3u8',
headers: { 'Authorization': 'Bearer TOKEN' }, // Used for license requests
drm: { certificateUrl: '...', licenseUrl: '...' },
});
// ❌ Incorrect
const player = useVideoPlayer({
uri: 'https://example.com/video.m3u8',
drm: {
licenseHeaders: { 'Authorization': 'Bearer TOKEN' }, // Not used on iOS
},
});
Invalid CKC Error
If using getLicense, ensure you return a base64-encoded string, not raw bytes or JSON:
// ✅ Correct
return Buffer.from(ckc).toString('base64');
// ❌ Incorrect
return ckc; // Raw ArrayBuffer
return JSON.stringify(ckc); // JSON
403/415 from License Server
Check:
- Authentication headers are correct
- Content-Type is set properly (usually
application/octet-stream)
- Server expects raw SPC bytes (decode base64 before sending)
Android Security Level Issues
The plugin automatically retries with Widevine L3 if L1 fails. Check device capabilities:
adb shell dumpsys media.drm | grep -i widevine
iOS Simulator
DRM is not supported in the iOS Simulator. Always test on a real device.
Offline Playback
For offline playback with DRM, check out the Offline Video SDK which provides comprehensive support for downloading and playing DRM-protected content, including persistent licenses.
See the Offline Playback guide for more information.
Next Steps