Skip to main content

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

1
Install the DRM plugin
2
npm install @react-native-video/drm
3
Enable the plugin at app startup
4
Before creating any video players, enable the DRM plugin:
5
// 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)

PropertyTypeRequiredDescription
type'widevine'NoDRM type (auto-inferred on Android)
licenseUrlstringYesURL of the Widevine license server
licenseHeadersRecord<string, string>NoAdditional headers for license requests
multiSessionbooleanNoAllow 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)

PropertyTypeRequiredDescription
type'fairplay'NoDRM type (auto-inferred on iOS)
certificateUrlstringYesURL to fetch the FairPlay application certificate
licenseUrlstringYesURL of the license (CKC) server
contentIdstringNoContent identifier (derived from skd:// URL if omitted)
getLicense(payload) => Promise<string>NoCustom 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:
FieldTypeDescription
contentIdstringContent identifier for the asset
licenseUrlstringLicense server URL
keyUrlstringKey URL from the stream (typically skd:// URL)
spcstringServer 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 },
});

Platform Detection

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:
  1. Installed @react-native-video/drm
  2. Called enable() before creating any players
  3. Rebuilt your app after installation

iOS: Headers Not Working

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

Build docs developers (and LLMs) love