The following guide helps you set up DRM with React Native Video
React Native Video is very popular player to play videos on react native applications. It can play both HLS and DASH streams and supports DRM.
Android:
React Native Video supports both the new age Android ExoPlayer and the older Android Media Player but by default it uses the Android Media Player. Since the Android Media Player doesn't supports DRM we have to explicitly configure React Native Video to use the Android ExoPlayer to play the videos which have DRM enabled.
To keep the scope of this document limited we expect you have already setup React Native development environment for Android.
Secure your video assets from piracy and maximize ROI on your content. Easily set up Widevine DRM on your React Native apps with Gumlet.
Step 1: Install the react-native-video package
npm i --save react-native-video
Step 2: Setup React Native Video With Exoplayer (skip if already done)
Enable ExoPlayer in android/settings.gradle
include ':react-native-video'
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer')
Add React Native Video as a dependency in android/app/build.gradle
dependencies {
...
compile project(':react-native-video')
implementation "androidx.appcompat:appcompat:1.0.0"
}
Migrate to Android X, make the following changes if not already there in the file android/gradle.properties
android.useAndroidX=true
android.enableJetifier=true
Import React Native Video package in MainApplication.java
import com.brentvatne.react.ReactVideoPackage;
In the same MainApplication.java add ReactVideoPackage in the list of exported packages
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new ReactVideoPackage());
return packages;
}
Step 3: Import react-native-video in your component
import Video, {DRMType} from 'react-native-video';
Step 4: Add the video component to your code
For the simplicity of this document we are making the changes in App.js itself and our app would only have a video player in it. We have also added some basic styles to our video component.
const App: () => Node = () => {
return (
<>
<View style={{flex: 1}}>
<Video
source={{
uri: 'https://video.gumlet.io/5f462c1561cf8a766464ffc4/61ee745525fc01c00e08a2ec/1.m3u8',
}}
style={styles.mediaPlayer}
/>
</View>
</>
);
};
const styles = StyleSheet.create({
mediaPlayer: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
justifyContent: 'center',
backgroundColor: '#f8f8f8'
},
});
Step 5: Request for a license URL to play a video encrypted by DRM
DRM playback also needs Licence Server URL along with playback URL to play the content. The licence server URL will inform player the place from where the licence can be acquired.
Remember!
For security reasons, Licence Server URL needs authentication token to be passed so it knows the request for licence is legitimate, due to this reason the code for generating a Licence Server URL should be on the server and not reside in the application itself.
Refer to this guide to generate the licence server URL on a backend server with your choice of programming language.
Let us assume that your server root URL is https://example.com and sending a GET request on https://example.com/licence-url returns a licence server URL. We will call the fetch method in the components useEffect hook and set it to a state variable to be used to initialise the video player with the Licence Server URL.
const App: () => Node = () => {
const [isLoading, setLoading] = useState(true);
const [signedLicenseURL, setSignedLicenseURL] = React.useState('');
const getSignedLicenseURL = async () => {
try {
const response = await fetch('https://example.com/licence-url');
const json = await response.json();
setSignedLicenseURL(json.licence);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}
useEffect(() => {
getSignedLicenseURL();
}, []);
// Wait for the signedLicenseURL to be available until then you can show a placeholder..
if (isLoading) {
return (
<View style={{flex: 1}}>
<Text>Loading Signed URL</Text>
</View>
)
}
return (
<>
<View style={{flex: 1}}>
<Video
source={{
uri: 'https://video.gumlet.io/5f462c1561cf8a766464ffc4/627ccd5803f2239e6938820b/main.mpd',
type: 'mpd'
}}
drm={{
type: DRMType.WIDEVINE,
licenseServer: signedLicenseURL
}}
/>
</View>
</>
);
};
Done
You can now play your DRM protected videos in a React Native Application
Full Code Snippet
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {useEffect, useState} from 'react';
import type {Node} from 'react';
import {
Text,
useColorScheme,
View,
} from 'react-native';
import {
Colors,
} from 'react-native/Libraries/NewAppScreen';
import Video, {DRMType} from 'react-native-video';
const App: () => Node = () => {
const [isLoading, setLoading] = useState(true);
const [signedLicenseURL, setSignedLicenseURL] = React.useState('');
// Make a request to get a signed Licence Server URL
const getSignedLicenseURL = async () => {
try {
const response = await fetch('https://example.com/licence-url');
const json = await response.json();
setSignedLicenseURL(json.licence);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}
useEffect(() => {
getSignedLicenseURL();
}, []);
// Wait for the signedLicenseURL to be available until then you can show a placeholder..
if (isLoading) {
return (
<View style={{flex: 1}}>
<Text>Loading Signed URL</Text>
</View>
)
}
return (
<>
<View style={{flex: 1}}>
<Video
source={{
uri: 'https://video.gumlet.io/5f462c1561cf8a766464ffc4/627ccd5803f2239e6938820b/main.mpd',
type: 'mpd'
}}
drm={{
type: DRMType.WIDEVINE,
licenseServer: signedLicenseURL
}}
/>
</View>
</>
);
};
const styles = StyleSheet.create({
mediaPlayer: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
justifyContent: 'center',
backgroundColor: '#f8f8f8'
},
});
export default App;
Issues you can face:
Issue 1:
Execution failed for task ':app:checkDebugAarMetadata'.
> Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
> Could not find com.yqritc:android-scalablevideoview:1.0.4.
Resolution
jCenter does not allow to update package anymore, all other packages should be taken from mavenCentral.
You can add jcenter to android/build.gradle like this:
allprojects {
repositories {
.... # rest of your code
google()
jcenter() {
content {
includeModule("com.yqritc", "android-scalablevideoview")
}
}
maven { url 'https://www.jitpack.io' }
}
}
Issue 2:
Execution failed for task ':app:mergeDebugAssets'.
> Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
> Could not find com.google.android.exoplayer:exoplayer:X.XX.X
Resolution
Change the Android ExoPlayer version in the file ./node_modules/react-native-video/android-exoplayer/build.gradle
dependencies {
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
- implementation('com.google.android.exoplayer:exoplayer:X.XX.X') {
+ implementation('com.google.android.exoplayer:exoplayer:2.13.3') {
exclude group: 'com.android.support'
}
dependencies {
implementation "androidx.core:core:1.1.0"
implementation "androidx.media:media:1.1.0"
- implementation('com.google.android.exoplayer:extension-okhttp:X.XX.X') {
+ implementation('com.google.android.exoplayer:extension-okhttp:2.13.3') {
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
}
implementation 'com.squareup.okhttp3:okhttp:${OKHTTP_VERSION}'