Add iOS and Android basic DRM support (#1445)
This PR adds support for DRM streams on iOS (Fairplay) and Android (Playready, Widevine, Clearkey) I am neither Android nor iOS developer, so feel free to provide feedback to improve this PR. **Test stream for ANDROID:** ``` testStream = { uri: 'http://profficialsite.origin.mediaservices.windows.net/c51358ea-9a5e-4322-8951-897d640fdfd7/tearsofsteel_4k.ism/manifest(format=mpd-time-csf)', type: 'mpd', drm: { type: DRMType.PLAYREADY, licenseServer: 'http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(persist:false,sl:150)' } }; ``` or ``` { uri: 'https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p.mpd', drm: { type: 'widevine', //or DRMType.WIDEVINE licenseServer: 'https://drm-widevine-licensing.axtest.net/AcquireLicense', headers: { 'X-AxDRM-Message': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImZpcnN0X3BsYXlfZXhwaXJhdGlvbiI6NjAsInBsYXlyZWFkeSI6eyJyZWFsX3RpbWVfZXhwaXJhdGlvbiI6dHJ1ZX0sImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.FAbIiPxX8BHi9RwfzD7Yn-wugU19ghrkBFKsaCPrZmU' }, } } ``` **Test stream for iOS:** Sorry but I can not provide free streams to test. If anyone can provide test streams, or found some we can use, please let me know to also test them. It has been tested with a private provider and they work, at least with the `getLicense` override method. (An example implementation is provided in the README)
This commit is contained in:
parent
dbf1a4e034
commit
81b42e7ca7
@ -1,5 +1,9 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### Version 5.1.0-alpha7
|
||||||
|
|
||||||
|
- Basic support for DRM on iOS and Android [#1445](https://github.com/react-native-community/react-native-video/pull/1445)
|
||||||
|
|
||||||
### Version 5.1.0-alpha6 [WIP]
|
### Version 5.1.0-alpha6 [WIP]
|
||||||
|
|
||||||
- Fix iOS bug which would break size of views when video is displayed with controls on a non full-screen React view. [#1931](https://github.com/react-native-community/react-native-video/pull/1931)
|
- Fix iOS bug which would break size of views when video is displayed with controls on a non full-screen React view. [#1931](https://github.com/react-native-community/react-native-video/pull/1931)
|
||||||
|
139
DRM.md
Normal file
139
DRM.md
Normal file
@ -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
DRMType.js
Normal file
6
DRMType.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
WIDEVINE: 'widevine',
|
||||||
|
PLAYREADY: 'playready',
|
||||||
|
CLEARKEY: 'clearkey',
|
||||||
|
FAIRPLAY: 'fairplay'
|
||||||
|
};
|
16
README.md
16
README.md
@ -415,6 +415,11 @@ Determines whether video audio should override background music/audio in Android
|
|||||||
|
|
||||||
Platforms: Android Exoplayer
|
Platforms: Android Exoplayer
|
||||||
|
|
||||||
|
### DRM
|
||||||
|
To setup DRM please follow [this guide](./DRM.md)
|
||||||
|
|
||||||
|
Platforms: Android Exoplayer, iOS
|
||||||
|
|
||||||
#### filter
|
#### filter
|
||||||
Add video filter
|
Add video filter
|
||||||
* **FilterType.NONE (default)** - No Filter
|
* **FilterType.NONE (default)** - No Filter
|
||||||
@ -799,6 +804,17 @@ Note: Using this feature adding an entry for NSAppleMusicUsageDescription to you
|
|||||||
|
|
||||||
Platforms: iOS
|
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
|
###### Other protocols
|
||||||
|
|
||||||
The following other types are supported on some platforms, but aren't fully documented yet:
|
The following other types are supported on some platforms, but aren't fully documented yet:
|
||||||
|
34
Video.js
34
Video.js
@ -4,6 +4,7 @@ import { StyleSheet, requireNativeComponent, NativeModules, View, ViewPropTypes,
|
|||||||
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
|
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
|
||||||
import TextTrackType from './TextTrackType';
|
import TextTrackType from './TextTrackType';
|
||||||
import FilterType from './FilterType';
|
import FilterType from './FilterType';
|
||||||
|
import DRMType from './DRMType';
|
||||||
import VideoResizeMode from './VideoResizeMode.js';
|
import VideoResizeMode from './VideoResizeMode.js';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
@ -12,7 +13,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export { TextTrackType, FilterType };
|
export { TextTrackType, FilterType, DRMType };
|
||||||
|
|
||||||
export default class Video extends Component {
|
export default class Video extends Component {
|
||||||
|
|
||||||
@ -232,6 +233,26 @@ export default class Video extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_onGetLicense = (event) => {
|
||||||
|
if (this.props.drm && this.props.drm.getLicense instanceof Function) {
|
||||||
|
const data = event.nativeEvent;
|
||||||
|
if (data && data.spc) {
|
||||||
|
const getLicenseOverride = this.props.drm.getLicense(data.spc, data.contentId, data.spcBase64, this.props);
|
||||||
|
const getLicensePromise = Promise.resolve(getLicenseOverride); // Handles both scenarios, getLicenseOverride being a promise and not.
|
||||||
|
getLicensePromise.then((result => {
|
||||||
|
if (result !== undefined) {
|
||||||
|
NativeModules.VideoManager.setLicenseResult(result, findNodeHandle(this._root));
|
||||||
|
} else {
|
||||||
|
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError('Empty license result', findNodeHandle(this._root));
|
||||||
|
}
|
||||||
|
})).catch((error) => {
|
||||||
|
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError(error, findNodeHandle(this._root));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError("No spc received", findNodeHandle(this._root));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
getViewManagerConfig = viewManagerName => {
|
getViewManagerConfig = viewManagerName => {
|
||||||
if (!NativeModules.UIManager.getViewManagerConfig) {
|
if (!NativeModules.UIManager.getViewManagerConfig) {
|
||||||
return NativeModules.UIManager[viewManagerName];
|
return NativeModules.UIManager[viewManagerName];
|
||||||
@ -304,6 +325,7 @@ export default class Video extends Component {
|
|||||||
onPlaybackRateChange: this._onPlaybackRateChange,
|
onPlaybackRateChange: this._onPlaybackRateChange,
|
||||||
onAudioFocusChanged: this._onAudioFocusChanged,
|
onAudioFocusChanged: this._onAudioFocusChanged,
|
||||||
onAudioBecomingNoisy: this._onAudioBecomingNoisy,
|
onAudioBecomingNoisy: this._onAudioBecomingNoisy,
|
||||||
|
onGetLicense: nativeProps.drm && nativeProps.drm.getLicense && this._onGetLicense,
|
||||||
onPictureInPictureStatusChanged: this._onPictureInPictureStatusChanged,
|
onPictureInPictureStatusChanged: this._onPictureInPictureStatusChanged,
|
||||||
onRestoreUserInterfaceForPictureInPictureStop: this._onRestoreUserInterfaceForPictureInPictureStop,
|
onRestoreUserInterfaceForPictureInPictureStop: this._onRestoreUserInterfaceForPictureInPictureStop,
|
||||||
});
|
});
|
||||||
@ -379,6 +401,16 @@ Video.propTypes = {
|
|||||||
// Opaque type returned by require('./video.mp4')
|
// Opaque type returned by require('./video.mp4')
|
||||||
PropTypes.number,
|
PropTypes.number,
|
||||||
]),
|
]),
|
||||||
|
drm: PropTypes.shape({
|
||||||
|
type: PropTypes.oneOf([
|
||||||
|
DRMType.CLEARKEY, DRMType.FAIRPLAY, DRMType.WIDEVINE, DRMType.PLAYREADY
|
||||||
|
]),
|
||||||
|
licenseServer: PropTypes.string,
|
||||||
|
headers: PropTypes.shape({}),
|
||||||
|
base64Certificate: PropTypes.bool,
|
||||||
|
certificateUrl: PropTypes.string,
|
||||||
|
getLicense: PropTypes.func,
|
||||||
|
}),
|
||||||
minLoadRetryCount: PropTypes.number,
|
minLoadRetryCount: PropTypes.number,
|
||||||
maxBitRate: PropTypes.number,
|
maxBitRate: PropTypes.number,
|
||||||
resizeMode: PropTypes.string,
|
resizeMode: PropTypes.string,
|
||||||
|
@ -19,6 +19,11 @@ android {
|
|||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -22,6 +22,7 @@ public class DataSourceUtil {
|
|||||||
|
|
||||||
private static DataSource.Factory rawDataSourceFactory = null;
|
private static DataSource.Factory rawDataSourceFactory = null;
|
||||||
private static DataSource.Factory defaultDataSourceFactory = null;
|
private static DataSource.Factory defaultDataSourceFactory = null;
|
||||||
|
private static HttpDataSource.Factory defaultHttpDataSourceFactory = null;
|
||||||
private static String userAgent = null;
|
private static String userAgent = null;
|
||||||
|
|
||||||
public static void setUserAgent(String userAgent) {
|
public static void setUserAgent(String userAgent) {
|
||||||
@ -58,6 +59,17 @@ public class DataSourceUtil {
|
|||||||
DataSourceUtil.defaultDataSourceFactory = factory;
|
DataSourceUtil.defaultDataSourceFactory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HttpDataSource.Factory getDefaultHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {
|
||||||
|
if (defaultHttpDataSourceFactory == null || (requestHeaders != null && !requestHeaders.isEmpty())) {
|
||||||
|
defaultHttpDataSourceFactory = buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders);
|
||||||
|
}
|
||||||
|
return defaultHttpDataSourceFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDefaultHttpDataSourceFactory(HttpDataSource.Factory factory) {
|
||||||
|
DataSourceUtil.defaultHttpDataSourceFactory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
private static DataSource.Factory buildRawDataSourceFactory(ReactContext context) {
|
private static DataSource.Factory buildRawDataSourceFactory(ReactContext context) {
|
||||||
return new RawResourceDataSourceFactory(context.getApplicationContext());
|
return new RawResourceDataSourceFactory(context.getApplicationContext());
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,13 @@ import com.google.android.exoplayer2.PlaybackParameters;
|
|||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionEventListener;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||||
|
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||||
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
|
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
@ -70,6 +77,7 @@ import java.net.CookieManager;
|
|||||||
import java.net.CookiePolicy;
|
import java.net.CookiePolicy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
@ -79,7 +87,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
BandwidthMeter.EventListener,
|
BandwidthMeter.EventListener,
|
||||||
BecomingNoisyListener,
|
BecomingNoisyListener,
|
||||||
AudioManager.OnAudioFocusChangeListener,
|
AudioManager.OnAudioFocusChangeListener,
|
||||||
MetadataOutput {
|
MetadataOutput,
|
||||||
|
DefaultDrmSessionEventListener {
|
||||||
|
|
||||||
private static final String TAG = "ReactExoplayerView";
|
private static final String TAG = "ReactExoplayerView";
|
||||||
|
|
||||||
@ -124,6 +133,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
|
private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
|
||||||
private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
|
private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
|
||||||
|
|
||||||
|
private Handler mainHandler;
|
||||||
|
|
||||||
// Props from React
|
// Props from React
|
||||||
private Uri srcUri;
|
private Uri srcUri;
|
||||||
private String extension;
|
private String extension;
|
||||||
@ -141,6 +152,9 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private boolean playInBackground = false;
|
private boolean playInBackground = false;
|
||||||
private Map<String, String> requestHeaders;
|
private Map<String, String> requestHeaders;
|
||||||
private boolean mReportBandwidth = false;
|
private boolean mReportBandwidth = false;
|
||||||
|
private UUID drmUUID = null;
|
||||||
|
private String drmLicenseUrl = null;
|
||||||
|
private String[] drmLicenseHeader = null;
|
||||||
private boolean controls;
|
private boolean controls;
|
||||||
// \ End props
|
// \ End props
|
||||||
|
|
||||||
@ -189,8 +203,6 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||||
themedReactContext.addLifecycleEventListener(this);
|
themedReactContext.addLifecycleEventListener(this);
|
||||||
audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext);
|
audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext);
|
||||||
|
|
||||||
initializePlayer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -214,6 +226,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
exoPlayerView.setLayoutParams(layoutParams);
|
exoPlayerView.setLayoutParams(layoutParams);
|
||||||
|
|
||||||
addView(exoPlayerView, 0, layoutParams);
|
addView(exoPlayerView, 0, layoutParams);
|
||||||
|
|
||||||
|
mainHandler = new Handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -395,9 +409,23 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
DefaultRenderersFactory renderersFactory =
|
DefaultRenderersFactory renderersFactory =
|
||||||
new DefaultRenderersFactory(getContext())
|
new DefaultRenderersFactory(getContext())
|
||||||
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
|
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
|
||||||
// TODO: Add drmSessionManager to 5th param from: https://github.com/react-native-community/react-native-video/pull/1445
|
// DRM
|
||||||
|
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
|
||||||
|
if (self.drmUUID != null) {
|
||||||
|
try {
|
||||||
|
drmSessionManager = buildDrmSessionManager(self.drmUUID, self.drmLicenseUrl,
|
||||||
|
self.drmLicenseHeader);
|
||||||
|
} catch (UnsupportedDrmException e) {
|
||||||
|
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
|
||||||
|
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
||||||
|
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
|
||||||
|
eventEmitter.error(getResources().getString(errorStringId), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// End DRM
|
||||||
player = ExoPlayerFactory.newSimpleInstance(getContext(), renderersFactory,
|
player = ExoPlayerFactory.newSimpleInstance(getContext(), renderersFactory,
|
||||||
trackSelector, defaultLoadControl, null, bandwidthMeter);
|
trackSelector, defaultLoadControl, drmSessionManager, bandwidthMeter);
|
||||||
player.addListener(self);
|
player.addListener(self);
|
||||||
player.addMetadataOutput(self);
|
player.addMetadataOutput(self);
|
||||||
exoPlayerView.setPlayer(player);
|
exoPlayerView.setPlayer(player);
|
||||||
@ -444,6 +472,23 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(UUID uuid,
|
||||||
|
String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
|
||||||
|
if (Util.SDK_INT < 18) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
|
||||||
|
buildHttpDataSourceFactory(false));
|
||||||
|
if (keyRequestPropertiesArray != null) {
|
||||||
|
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
|
||||||
|
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
|
||||||
|
keyRequestPropertiesArray[i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new DefaultDrmSessionManager<>(uuid,
|
||||||
|
FrameworkMediaDrm.newInstance(uuid), drmCallback, null, false, 3);
|
||||||
|
}
|
||||||
|
|
||||||
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
|
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
|
||||||
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
|
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
|
||||||
: uri.getLastPathSegment());
|
: uri.getLastPathSegment());
|
||||||
@ -615,6 +660,18 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
useBandwidthMeter ? bandwidthMeter : null, requestHeaders);
|
useBandwidthMeter ? bandwidthMeter : null, requestHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new HttpDataSource factory.
|
||||||
|
*
|
||||||
|
* @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new
|
||||||
|
* DataSource factory.
|
||||||
|
* @return A new HttpDataSource factory.
|
||||||
|
*/
|
||||||
|
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
|
||||||
|
return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, useBandwidthMeter ? bandwidthMeter : null, requestHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// AudioManager.OnAudioFocusChangeListener implementation
|
// AudioManager.OnAudioFocusChangeListener implementation
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -924,10 +981,12 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getTrackRendererIndex(int trackType) {
|
public int getTrackRendererIndex(int trackType) {
|
||||||
int rendererCount = player.getRendererCount();
|
if (player != null) {
|
||||||
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
|
int rendererCount = player.getRendererCount();
|
||||||
if (player.getRendererType(rendererIndex) == trackType) {
|
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
|
||||||
return rendererIndex;
|
if (player.getRendererType(rendererIndex) == trackType) {
|
||||||
|
return rendererIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return C.INDEX_UNSET;
|
return C.INDEX_UNSET;
|
||||||
@ -1182,12 +1241,12 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setRateModifier(float newRate) {
|
public void setRateModifier(float newRate) {
|
||||||
rate = newRate;
|
rate = newRate;
|
||||||
|
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
PlaybackParameters params = new PlaybackParameters(rate, 1f);
|
PlaybackParameters params = new PlaybackParameters(rate, 1f);
|
||||||
player.setPlaybackParameters(params);
|
player.setPlaybackParameters(params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMaxBitRateModifier(int newMaxBitRate) {
|
public void setMaxBitRateModifier(int newMaxBitRate) {
|
||||||
@ -1246,7 +1305,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setUseTextureView(boolean useTextureView) {
|
public void setUseTextureView(boolean useTextureView) {
|
||||||
exoPlayerView.setUseTextureView(useTextureView);
|
boolean finallyUseTextureView = useTextureView && this.drmUUID == null;
|
||||||
|
exoPlayerView.setUseTextureView(finallyUseTextureView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHideShutterView(boolean hideShutterView) {
|
public void setHideShutterView(boolean hideShutterView) {
|
||||||
@ -1262,6 +1322,40 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
initializePlayer();
|
initializePlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDrmType(UUID drmType) {
|
||||||
|
this.drmUUID = drmType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrmLicenseUrl(String licenseUrl){
|
||||||
|
this.drmLicenseUrl = licenseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrmLicenseHeader(String[] header){
|
||||||
|
this.drmLicenseHeader = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrmKeysLoaded() {
|
||||||
|
Log.d("DRM Info", "onDrmKeysLoaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrmSessionManagerError(Exception e) {
|
||||||
|
Log.d("DRM Info", "onDrmSessionManagerError");
|
||||||
|
eventEmitter.error("onDrmSessionManagerError", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrmKeysRestored() {
|
||||||
|
Log.d("DRM Info", "onDrmKeysRestored");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrmKeysRemoved() {
|
||||||
|
Log.d("DRM Info", "onDrmKeysRemoved");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handling controls prop
|
* Handling controls prop
|
||||||
*
|
*
|
||||||
|
@ -3,19 +3,25 @@ package com.brentvatne.exoplayer;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.facebook.react.bridge.Dynamic;
|
import com.facebook.react.bridge.Dynamic;
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||||
import com.facebook.react.common.MapBuilder;
|
import com.facebook.react.common.MapBuilder;
|
||||||
import com.facebook.react.uimanager.ThemedReactContext;
|
import com.facebook.react.uimanager.ThemedReactContext;
|
||||||
import com.facebook.react.uimanager.ViewGroupManager;
|
import com.facebook.react.uimanager.ViewGroupManager;
|
||||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer2.upstream.RawResourceDataSource;
|
import com.google.android.exoplayer2.upstream.RawResourceDataSource;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@ -26,6 +32,10 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
private static final String PROP_SRC = "src";
|
private static final String PROP_SRC = "src";
|
||||||
private static final String PROP_SRC_URI = "uri";
|
private static final String PROP_SRC_URI = "uri";
|
||||||
private static final String PROP_SRC_TYPE = "type";
|
private static final String PROP_SRC_TYPE = "type";
|
||||||
|
private static final String PROP_DRM = "drm";
|
||||||
|
private static final String PROP_DRM_TYPE = "type";
|
||||||
|
private static final String PROP_DRM_LICENSESERVER = "licenseServer";
|
||||||
|
private static final String PROP_DRM_HEADERS = "headers";
|
||||||
private static final String PROP_SRC_HEADERS = "requestHeaders";
|
private static final String PROP_SRC_HEADERS = "requestHeaders";
|
||||||
private static final String PROP_RESIZE_MODE = "resizeMode";
|
private static final String PROP_RESIZE_MODE = "resizeMode";
|
||||||
private static final String PROP_REPEAT = "repeat";
|
private static final String PROP_REPEAT = "repeat";
|
||||||
@ -101,6 +111,31 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = PROP_DRM)
|
||||||
|
public void setDRM(final ReactExoplayerView videoView, @Nullable ReadableMap drm) {
|
||||||
|
if (drm != null && drm.hasKey(PROP_DRM_TYPE)) {
|
||||||
|
String drmType = drm.hasKey(PROP_DRM_TYPE) ? drm.getString(PROP_DRM_TYPE) : null;
|
||||||
|
String drmLicenseServer = drm.hasKey(PROP_DRM_LICENSESERVER) ? drm.getString(PROP_DRM_LICENSESERVER) : null;
|
||||||
|
ReadableMap drmHeaders = drm.hasKey(PROP_DRM_HEADERS) ? drm.getMap(PROP_DRM_HEADERS) : null;
|
||||||
|
if (drmType != null && drmLicenseServer != null && Util.getDrmUuid(drmType) != null) {
|
||||||
|
UUID drmUUID = Util.getDrmUuid(drmType);
|
||||||
|
videoView.setDrmType(drmUUID);
|
||||||
|
videoView.setDrmLicenseUrl(drmLicenseServer);
|
||||||
|
if (drmHeaders != null) {
|
||||||
|
ArrayList<String> drmKeyRequestPropertiesList = new ArrayList<>();
|
||||||
|
ReadableMapKeySetIterator itr = drmHeaders.keySetIterator();
|
||||||
|
while (itr.hasNextKey()) {
|
||||||
|
String key = itr.nextKey();
|
||||||
|
drmKeyRequestPropertiesList.add(key);
|
||||||
|
drmKeyRequestPropertiesList.add(drmHeaders.getString(key));
|
||||||
|
}
|
||||||
|
videoView.setDrmLicenseHeader(drmKeyRequestPropertiesList.toArray(new String[0]));
|
||||||
|
}
|
||||||
|
videoView.setUseTextureView(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_SRC)
|
@ReactProp(name = PROP_SRC)
|
||||||
public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) {
|
public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) {
|
||||||
Context context = videoView.getContext().getApplicationContext();
|
Context context = videoView.getContext().getApplicationContext();
|
||||||
@ -108,7 +143,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
String extension = src.hasKey(PROP_SRC_TYPE) ? src.getString(PROP_SRC_TYPE) : null;
|
String extension = src.hasKey(PROP_SRC_TYPE) ? src.getString(PROP_SRC_TYPE) : null;
|
||||||
Map<String, String> headers = src.hasKey(PROP_SRC_HEADERS) ? toStringMap(src.getMap(PROP_SRC_HEADERS)) : null;
|
Map<String, String> headers = src.hasKey(PROP_SRC_HEADERS) ? toStringMap(src.getMap(PROP_SRC_HEADERS)) : null;
|
||||||
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(uriString)) {
|
if (TextUtils.isEmpty(uriString)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,12 @@
|
|||||||
<string name="error_querying_decoders">Unable to query device decoders</string>
|
<string name="error_querying_decoders">Unable to query device decoders</string>
|
||||||
|
|
||||||
<string name="error_instantiating_decoder">Unable to instantiate decoder <xliff:g id="decoder_name">%1$s</xliff:g></string>
|
<string name="error_instantiating_decoder">Unable to instantiate decoder <xliff:g id="decoder_name">%1$s</xliff:g></string>
|
||||||
|
|
||||||
|
<string name="error_drm_not_supported">Protected content not supported on API levels below 18</string>
|
||||||
|
|
||||||
<string name="unrecognized_media_format">Unrecognized media format</string>
|
<string name="unrecognized_media_format">Unrecognized media format</string>
|
||||||
|
|
||||||
|
<string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string>
|
||||||
|
|
||||||
|
<string name="error_drm_unknown">An unknown DRM error occurred</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
|
|
||||||
@class RCTEventDispatcher;
|
@class RCTEventDispatcher;
|
||||||
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
||||||
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, DVAssetLoaderDelegatesDelegate>
|
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, DVAssetLoaderDelegatesDelegate, AVAssetResourceLoaderDelegate>
|
||||||
#elif TARGET_OS_TV
|
#elif TARGET_OS_TV
|
||||||
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate>
|
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVAssetResourceLoaderDelegate>
|
||||||
#else
|
#else
|
||||||
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate>
|
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate, AVAssetResourceLoaderDelegate>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;
|
||||||
@ -42,11 +42,26 @@
|
|||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;
|
@property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;
|
@property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onGetLicense;
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, RCTVideoError) {
|
||||||
|
RCTVideoErrorFromJSPart,
|
||||||
|
RCTVideoErrorLicenseRequestNotOk,
|
||||||
|
RCTVideoErrorNoDataFromLicenseRequest,
|
||||||
|
RCTVideoErrorNoSPC,
|
||||||
|
RCTVideoErrorNoDataRequest,
|
||||||
|
RCTVideoErrorNoCertificateData,
|
||||||
|
RCTVideoErrorNoCertificateURL,
|
||||||
|
RCTVideoErrorNoFairplayDRM,
|
||||||
|
RCTVideoErrorNoDRMData
|
||||||
|
};
|
||||||
|
|
||||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem;
|
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem;
|
||||||
|
|
||||||
- (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
|
- (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
|
||||||
|
- (void)setLicenseResult:(NSString * )license;
|
||||||
|
- (BOOL)setLicenseResultError:(NSString * )error;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@ RCT_EXPORT_MODULE();
|
|||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
|
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(drm, NSDictionary);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(maxBitRate, float);
|
RCT_EXPORT_VIEW_PROPERTY(maxBitRate, float);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
|
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);
|
||||||
@ -68,6 +69,7 @@ RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTDirectEventBlock);
|
|||||||
RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock);
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(onGetLicense, RCTDirectEventBlock);
|
||||||
RCT_REMAP_METHOD(save,
|
RCT_REMAP_METHOD(save,
|
||||||
options:(NSDictionary *)options
|
options:(NSDictionary *)options
|
||||||
reactTag:(nonnull NSNumber *)reactTag
|
reactTag:(nonnull NSNumber *)reactTag
|
||||||
@ -82,7 +84,34 @@ RCT_REMAP_METHOD(save,
|
|||||||
[view save:options resolve:resolve reject:reject];
|
[view save:options resolve:resolve reject:reject];
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
};
|
||||||
|
RCT_REMAP_METHOD(setLicenseResult,
|
||||||
|
license:(NSString *)license
|
||||||
|
reactTag:(nonnull NSNumber *)reactTag)
|
||||||
|
{
|
||||||
|
[self.bridge.uiManager prependUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTVideo *> *viewRegistry) {
|
||||||
|
RCTVideo *view = viewRegistry[reactTag];
|
||||||
|
if (![view isKindOfClass:[RCTVideo class]]) {
|
||||||
|
RCTLogError(@"Invalid view returned from registry, expecting RCTVideo, got: %@", view);
|
||||||
|
} else {
|
||||||
|
[view setLicenseResult:license];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
RCT_REMAP_METHOD(setLicenseResultError,
|
||||||
|
error:(NSString *)error
|
||||||
|
reactTag:(nonnull NSNumber *)reactTag)
|
||||||
|
{
|
||||||
|
[self.bridge.uiManager prependUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTVideo *> *viewRegistry) {
|
||||||
|
RCTVideo *view = viewRegistry[reactTag];
|
||||||
|
if (![view isKindOfClass:[RCTVideo class]]) {
|
||||||
|
RCTLogError(@"Invalid view returned from registry, expecting RCTVideo, got: %@", view);
|
||||||
|
} else {
|
||||||
|
[view setLicenseResultError:error];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
};
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
"ios",
|
"ios",
|
||||||
"windows",
|
"windows",
|
||||||
"FilterType.js",
|
"FilterType.js",
|
||||||
|
"DRMType.js",
|
||||||
"TextTrackType.js",
|
"TextTrackType.js",
|
||||||
"VideoResizeMode.js",
|
"VideoResizeMode.js",
|
||||||
"react-native-video.podspec"
|
"react-native-video.podspec"
|
||||||
|
@ -32,4 +32,8 @@ Pod::Spec.new do |s|
|
|||||||
s.dependency "React"
|
s.dependency "React"
|
||||||
|
|
||||||
s.default_subspec = "Video"
|
s.default_subspec = "Video"
|
||||||
|
|
||||||
|
s.xcconfig = {
|
||||||
|
'OTHER_LDFLAGS': '-ObjC',
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user