Widewine with React Native

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}'