Skip to content

Commit

Permalink
Merge pull request TheWidlarzGroup#1445 from 24i:feature/ios-drm-support
Browse files Browse the repository at this point in the history
Add iOS and Android basic DRM support
  • Loading branch information
yurtaev committed Aug 3, 2020
1 parent 367031b commit 7e1b9af
Show file tree
Hide file tree
Showing 29 changed files with 4,288 additions and 2,591 deletions.
66 changes: 66 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

# Bug

<!--
Please provide a clear and concise description of what the bug is.
Include screenshots if needed.
Please test using the latest release of the library, as maybe said bug has been already fixed.
If the library has multiple install methods, describe installation method (e.g., pod, not pod, with jetifier etc)
-->

## Platform
<!--
Platform where your bug is happening. If Android, report if using Android or Android Exoplayer
-->
Which player are you experiencing the problem on:
* iOS
* Android ExoPlayer
* Android MediaPlayer
* Windows UWP
* Windows WPF

## Environment info

<!--
Run `react-native info` in your terminal and copy the results here. Also, include the *precise* version number of this library that you are using in the project
-->

React native info output:

```bash
// paste it here
```

Library version: x.x.x

## Steps To Reproduce

<!--
Issues without reproduction steps or code are likely to stall.
-->

1.
2.
...

## Expected behaviour

1.
2.

## Reproducible sample code

<!--
Please add to your issue a repro, a fresh codebase with the minimal changes so that the bug can be tested in isolation
-->

## Video sample
If possible, include a link to the video that has the problem that can be streamed or downloaded from.
32 changes: 32 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

# Feature Request

<!--
This issue should serve for you to present or pitch an idea to the maintainers - but remember that it would be better if you were to submit a PR instead 🤗
-->

## Why it is needed

<!--
Please tell us a bit more of why you want this feature to be added, what's its origin
-->

## Possible implementation

<!--
It really helps if you could describe from a technical POV how this new feature would work, which code it rely on, etc
-->

### Code sample

<!--
Please show how the new code could work, if doable
-->
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Changelog

### next
* Basic support for DRM on iOS and Android [#1445](https://github.com/react-native-community/react-native-video/pull/1445)

### Version 5.0.2
* Fix crash when RCTVideo's superclass doesn't observe the keyPath 'frame' (iOS) [#1720](https://github.com/react-native-community/react-native-video/pull/1720)

Expand Down
139 changes: 139 additions & 0 deletions DRM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# DRM

## Provide DRM data (only tested with http/https assets)

You can provide some configuration to allow DRM playback.
This feature will disable the use of `TextureView` on Android.

DRM object allows this members:

| Property | Type | Default | Platform | Description |
| --- | --- | --- | --- | --- |
| [`type`](#type) | DRMType | undefined | iOS/Android | Specifies which type of DRM you are going to use, DRMType is an enum exposed on the JS module ('fairplay', 'playready', ...) |
| [`licenseServer`](#licenseserver) | string | undefined | iOS/Android | Specifies the license server URL |
| [`headers`](#headers) | Object | undefined | iOS/Android | Specifies the headers send to the license server URL on license acquisition |
| [`contentId`](#contentid) | string | undefined | iOS | Specify the content id of the stream, otherwise it will take the host value from `loadingRequest.request.URL.host` (f.e: `skd://testAsset` -> will take `testAsset`) |
| [`certificateUrl`](#certificateurl) | string | undefined | iOS | Specifies the url to obtain your ios certificate for fairplay, Url to the .cer file |
| [`base64Certificate`](#base64certificate) | bool | false | iOS | Specifies whether or not the certificate returned by the `certificateUrl` is on base64 |
| [`getLicense`](#getlicense)| function | undefined | iOS | Rather than setting the `licenseServer` url to get the license, you can manually get the license on the JS part, and send the result to the native part to configure FairplayDRM for the stream |

### `base64Certificate`

Whether or not the certificate url returns it on base64.

Platforms: iOS

### `certificateUrl`

URL to fetch a valid certificate for FairPlay.

Platforms: iOS

### `getLicense`

`licenseServer` and `headers` will be ignored. You will obtain as argument the `SPC` (as ASCII string, you will probably need to convert it to base 64) obtained from your `contentId` + the provided certificate via `[loadingRequest streamingContentKeyRequestDataForApp:certificateData contentIdentifier:contentIdData options:nil error:&spcError];`.
You should return on this method a `CKC` in Base64, either by just returning it or returning a `Promise` that resolves with the `CKC`.

With this prop you can override the license acquisition flow, as an example:

```js
getLicense: (spcString) => {
const base64spc = Base64.encode(spcString);
const formData = new FormData();
formData.append('spc', base64spc);
return fetch(`https://license.pallycon.com/ri/licenseManager.do`, {
method: 'POST',
headers: {
'pallycon-customdata-v2': 'd2VpcmRiYXNlNjRzdHJpbmcgOlAgRGFuaWVsIE1hcmnxbyB3YXMgaGVyZQ==',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData
}).then(response => response.text()).then((response) => {
return response;
}).catch((error) => {
console.error('Error', error);
});
}
```

Platforms: iOS

### `headers`

You can customize headers send to the licenseServer.

Example:

```js
source={{
uri: 'https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p.mpd',
}}
drm={{
type: DRMType.WIDEVINE,
licenseServer: 'https://drm-widevine-licensing.axtest.net/AcquireLicense',
headers: {
'X-AxDRM-Message': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImZpcnN0X3BsYXlfZXhwaXJhdGlvbiI6NjAsInBsYXlyZWFkeSI6eyJyZWFsX3RpbWVfZXhwaXJhdGlvbiI6dHJ1ZX0sImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.FAbIiPxX8BHi9RwfzD7Yn-wugU19ghrkBFKsaCPrZmU'
},
}}
```

### `licenseServer`

The URL pointing to the licenseServer that will provide the authorization to play the protected stream.

### `type`

You can specify the DRM type, either by string or using the exported DRMType enum.
Valid values are, for Android: DRMType.WIDEVINE / DRMType.PLAYREADY / DRMType.CLEARKEY.
for iOS: DRMType.FAIRPLAY

## Common Usage Scenarios

### Send cookies to license server

You can send Cookies to the license server via `headers` prop. Example:

```js
drm: {
type: DRMType.WIDEVINE
licenseServer: 'https://drm-widevine-licensing.axtest.net/AcquireLicense',
headers: {
'Cookie': 'PHPSESSID=etcetc; csrftoken=mytoken; _gat=1; foo=bar'
},
}
```

### Custom License Acquisition (only iOS for now)

```js
drm: {
type: DRMType.FAIRPLAY,
getLicense: (spcString) => {
const base64spc = Base64.encode(spcString);
return fetch('YOUR LICENSE SERVER HERE', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
getFairplayLicense: {
foo: 'bar',
spcMessage: base64spc,
}
})
})
.then(response => response.json())
.then((response) => {
if (response && response.getFairplayLicenseResponse
&& response.getFairplayLicenseResponse.ckcResponse) {
return response.getFairplayLicenseResponse.ckcResponse;
}
throw new Error('No correct response');
})
.catch((error) => {
console.error('CKC error', error);
});
}
}
```
6 changes: 6 additions & 0 deletions DRMType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
WIDEVINE: 'widevine',
PLAYREADY: 'playready',
CLEARKEY: 'clearkey',
FAIRPLAY: 'fairplay'
};
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,10 @@ var styles = StyleSheet.create({
### Configurable props
* [allowsExternalPlayback](#allowsexternalplayback)
* [audioOnly](#audioonly)
* [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling)
* [bufferConfig](#bufferconfig)
* [controls](#controls)
* [disableFocus](#disableFocus)
* [filter](#filter)
* [filterEnabled](#filterEnabled)
* [fullscreen](#fullscreen)
Expand Down Expand Up @@ -370,6 +372,13 @@ For this to work, the poster prop must be set.

Platforms: all

#### automaticallyWaitsToMinimizeStalling
A Boolean value that indicates whether the player should automatically delay playback in order to minimize stalling. For clients linked against iOS 10.0 and later
* **false** - Immediately starts playback
* **true (default)** - Delays playback in order to minimize stalling

Platforms: iOS

#### bufferConfig
Adjust the buffer settings. This prop takes an object with one or more of the properties listed below.

Expand Down Expand Up @@ -412,6 +421,11 @@ Determines whether video audio should override background music/audio in Android

Platforms: Android Exoplayer

### DRM
To setup DRM please follow [this guide](./DRM.md)

Platforms: Android Exoplayer, iOS

#### filter
Add video filter
* **FilterType.NONE (default)** - No Filter
Expand Down Expand Up @@ -776,6 +790,17 @@ Note: Using this feature adding an entry for NSAppleMusicUsageDescription to you

Platforms: iOS

##### Explicit mimetype for the stream

Provide a member `type` with value (`mpd`/`m3u8`/`ism`) inside the source object.
Sometimes is needed when URL extension does not match with the mimetype that you are expecting, as seen on the next example. (Extension is .ism -smooth streaming- but file served is on format mpd -mpeg dash-)

Example:
```
source={{ uri: 'http://host-serving-a-type-different-than-the-extension.ism/manifest(format=mpd-time-csf)',
type: 'mpd' }}
```

###### Other protocols

The following other types are supported on some platforms, but aren't fully documented yet:
Expand Down
Loading

0 comments on commit 7e1b9af

Please sign in to comment.