From 3a4cb7f6d939a85d2c6118c821f3dc8970dee115 Mon Sep 17 00:00:00 2001 From: olivier bouillet Date: Thu, 23 Jun 2022 22:54:03 +0200 Subject: [PATCH 1/4] feat(android): add new apis to query device capabilities getWidevineLevel => integer between 0 and 3 isCodecSupported(codec, width, height) => boolean isHEVCSupported() => boolean --- API.md | 66 +++++++++++ Video.js | 3 +- .../brentvatne/react/ReactVideoPackage.java | 4 +- .../react/VideoDecoderPropertiesModule.java | 105 ++++++++++++++++++ 4 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java diff --git a/API.md b/API.md index 9ac52616..b735a026 100644 --- a/API.md +++ b/API.md @@ -344,6 +344,13 @@ var styles = StyleSheet.create({ |[restoreUserInterfaceForPictureInPictureStop](#restoreuserinterfaceforpictureinpicturestop)|iOS| |[seek](#seek)|All| +### Static methods + +| Name |Plateforms Support | +|--|--| +|[getWidevineLevel](#getWidevineLevel)|Android| +|[isCodecSupported](#isCodecSupported)|Android| +|[isHEVCSupported](#isHEVCSupported)|Android| ### Configurable props @@ -1333,8 +1340,67 @@ this.player.seek(120, 50); // Seek to 2 minutes with +/- 50 milliseconds accurac Platforms: iOS +#### Static methods +### Video Decoding capabilities +A module embed in ReactNativeVideo allow to query device supported feature. +To use it include the module as following: +```javascript +import { VideoDecoderProperties } from '@ifs/react-native-video-enhanced' +``` + +Platforms: Android + +#### getWidevineLevel + +Indicates whether the widevine level supported by device. + +Possible results: +- **0** - unable to determine widevine support (typically not supported) +- **1**, **2**, **3** - Widevine level supported + +Platforms: Android + +Example: + +``` +VideoDecoderProperties.getWidevineLevel().then((widevineLevel) => { + ... +} +``` + +#### isCodecSupported + +Indicates whether the provided codec is supported level supported by device. + +parameters: +- **mimetype**: mime type of codec to query +- **width**, **height**: resolution to query + +Possible results: +- **true** - codec supported +- **false** - codec is not supported + +Example: +``` +VideoDecoderProperties.isCodecSupported('video/avc', 1920, 1080).then( + ... +} +``` +Platforms: Android + +#### isHEVCSupported + +Helper which Indicates whether the provided HEVC/1920*1080 is supported level supported by device. +It uses isCodecSupported internally. + +Example: +``` +VideoDecoderProperties.isHEVCSupported().then((hevcSupported) => { + ... +} +``` ### iOS App Transport Security diff --git a/Video.js b/Video.js index dad262b9..7fd563bb 100644 --- a/Video.js +++ b/Video.js @@ -14,7 +14,8 @@ const styles = StyleSheet.create({ }, }); -export { TextTrackType, FilterType, DRMType }; +const { VideoDecoderProperties } = NativeModules +export { TextTrackType, FilterType, DRMType, VideoDecoderProperties } export default class Video extends Component { diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java b/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java index f7a5d02a..23bedad6 100644 --- a/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java +++ b/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java @@ -25,7 +25,9 @@ public class ReactVideoPackage implements ReactPackage { @Override public List createNativeModules(ReactApplicationContext reactContext) { - return Collections.emptyList(); + return Collections.singletonList( + new VideoDecoderPropertiesModule(reactContext) + ); } // Deprecated RN 0.47 diff --git a/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java b/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java new file mode 100644 index 00000000..12ae388c --- /dev/null +++ b/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java @@ -0,0 +1,105 @@ +package com.brentvatne.react; + +import android.annotation.SuppressLint; +import android.media.MediaCodecList; +import android.media.MediaDrm; +import android.media.MediaFormat; +import android.media.UnsupportedSchemeException; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; + +import java.util.UUID; + +@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) +public class VideoDecoderPropertiesModule extends ReactContextBaseJavaModule { + + ReactApplicationContext reactContext; + + @NonNull + @Override + public String getName() { + return "VideoDecoderProperties"; + } + + @SuppressLint("ObsoleteSdkInt") + @ReactMethod + public void getWidevineLevel(Promise p) { + int widevineLevel = 0; + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { + final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); + final String WIDEVINE_SECURITY_LEVEL_1 = "L1"; + final String WIDEVINE_SECURITY_LEVEL_2 = "L2"; + final String WIDEVINE_SECURITY_LEVEL_3 = "L3"; + final String SECURITY_LEVEL_PROPERTY = "securityLevel"; + + String securityProperty = null; + try { + MediaDrm mediaDrm = new MediaDrm(WIDEVINE_UUID); + securityProperty = mediaDrm.getPropertyString(SECURITY_LEVEL_PROPERTY); + } catch (UnsupportedSchemeException e) { + e.printStackTrace(); + } + if (securityProperty == null) { + p.resolve(widevineLevel); + return; + } + + switch (securityProperty) { + case WIDEVINE_SECURITY_LEVEL_1: { + widevineLevel = 1; + break; + } + case WIDEVINE_SECURITY_LEVEL_2: { + widevineLevel = 2; + break; + } + case WIDEVINE_SECURITY_LEVEL_3: { + widevineLevel = 3; + break; + } + default: { + // widevineLevel 0 + break; + } + } + } + p.resolve(widevineLevel); + } + + @SuppressLint("ObsoleteSdkInt") + @ReactMethod + public void isCodecSupported(String mimeType, int width, int height, Promise p) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + MediaCodecList mRegularCodecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS); + MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height); + String codecName = mRegularCodecs.findDecoderForFormat(format); + if (codecName == null) { + p.resolve(false); + } else { + p.resolve(true); + } + } else { + p.resolve(false); + } + } + + + @ReactMethod + public void isHEVCSupported(Promise p) { + isCodecSupported("video/hevc", 1920, 1080, p); + } + + public VideoDecoderPropertiesModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + } + +} From 8408664600d4b1316bcac13e1d50756b84074cf1 Mon Sep 17 00:00:00 2001 From: olivier bouillet Date: Thu, 23 Jun 2022 22:54:47 +0200 Subject: [PATCH 2/4] feat(android): add sample to test decoder capabilities --- examples/basic/src/VideoPlayer.android.tsx | 39 +++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/examples/basic/src/VideoPlayer.android.tsx b/examples/basic/src/VideoPlayer.android.tsx index 0ca36fb6..ef53c752 100644 --- a/examples/basic/src/VideoPlayer.android.tsx +++ b/examples/basic/src/VideoPlayer.android.tsx @@ -16,7 +16,7 @@ import { import { Picker } from '@react-native-picker/picker' -import Video, { TextTrackType } from 'react-native-video'; +import Video, { VideoDecoderProperties, TextTrackType } from 'react-native-video'; class VideoPlayer extends Component { @@ -77,6 +77,28 @@ class VideoPlayer extends Component { video: Video; seekPanResponder: PanResponder | undefined; + popupInfo = () => { + VideoDecoderProperties.getWidevineLevel().then((widevineLevel: number) => { + VideoDecoderProperties.isHEVCSupported().then((hevcSupported: boolean) => { + VideoDecoderProperties.isCodecSupported('video/avc', 1920, 1080).then( + (avcSupported: boolean) => { + this.toast( + true, + 'Widevine level: ' + + widevineLevel + + '\n hevc: ' + + (hevcSupported ? '' : 'NOT') + + 'supported' + + '\n avc: ' + + (avcSupported ? '' : 'NOT') + + 'supported', + ) + }, + ) + }) + }) + } + onLoad = (data: any) => { this.setState({ duration: data.duration, loading: false, }); this.onAudioTracks(data.audioTracks) @@ -288,6 +310,18 @@ class VideoPlayer extends Component { ) } + renderInfoControl() { + return ( + { + this.popupInfo() + }} + > + {'decoderInfo'} + + ) + } + renderFullScreenControl() { return ( + + {this.renderInfoControl()} + {this.renderPause()} {this.renderRepeatModeControl()} From 0ff6d5f657e9d7cae109ccd1dc594d3af3e80fcd Mon Sep 17 00:00:00 2001 From: olivier bouillet Date: Sat, 20 Aug 2022 14:34:27 +0200 Subject: [PATCH 3/4] doc: update changeLog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b2a97e..a7196459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Version 6.0.0-alpha.2 +- Feature add new APIs to query supported features of device decoder (widevine level & codec capabilities) on android [#2740](https://github.com/react-native-video/react-native-video/pull/2740) - Feature add support of subtitle styling on android [#2759](https://github.com/react-native-video/react-native-video/pull/2759) - Fix Android #2690 ensure onEnd is not sent twice [#2690](https://github.com/react-native-video/react-native-video/issues/2690) - Fix Exoplayer progress not reported when paused [#2664](https://github.com/react-native-video/react-native-video/pull/2664) From 271206940843418e7cb266f252160e08c933f2f9 Mon Sep 17 00:00:00 2001 From: olivier bouillet Date: Sat, 20 Aug 2022 15:12:40 +0200 Subject: [PATCH 4/4] chore(android): change test logic for simplier code --- .../react/VideoDecoderPropertiesModule.java | 90 ++++++++++--------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java b/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java index 12ae388c..6622e7f9 100644 --- a/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java +++ b/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java @@ -33,42 +33,44 @@ public class VideoDecoderPropertiesModule extends ReactContextBaseJavaModule { public void getWidevineLevel(Promise p) { int widevineLevel = 0; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { - final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); - final String WIDEVINE_SECURITY_LEVEL_1 = "L1"; - final String WIDEVINE_SECURITY_LEVEL_2 = "L2"; - final String WIDEVINE_SECURITY_LEVEL_3 = "L3"; - final String SECURITY_LEVEL_PROPERTY = "securityLevel"; + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { + p.resolve(widevineLevel); + return; + } + final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); + final String WIDEVINE_SECURITY_LEVEL_1 = "L1"; + final String WIDEVINE_SECURITY_LEVEL_2 = "L2"; + final String WIDEVINE_SECURITY_LEVEL_3 = "L3"; + final String SECURITY_LEVEL_PROPERTY = "securityLevel"; - String securityProperty = null; - try { - MediaDrm mediaDrm = new MediaDrm(WIDEVINE_UUID); - securityProperty = mediaDrm.getPropertyString(SECURITY_LEVEL_PROPERTY); - } catch (UnsupportedSchemeException e) { - e.printStackTrace(); - } - if (securityProperty == null) { - p.resolve(widevineLevel); - return; - } + String securityProperty = null; + try { + MediaDrm mediaDrm = new MediaDrm(WIDEVINE_UUID); + securityProperty = mediaDrm.getPropertyString(SECURITY_LEVEL_PROPERTY); + } catch (UnsupportedSchemeException e) { + e.printStackTrace(); + } + if (securityProperty == null) { + p.resolve(widevineLevel); + return; + } - switch (securityProperty) { - case WIDEVINE_SECURITY_LEVEL_1: { - widevineLevel = 1; - break; - } - case WIDEVINE_SECURITY_LEVEL_2: { - widevineLevel = 2; - break; - } - case WIDEVINE_SECURITY_LEVEL_3: { - widevineLevel = 3; - break; - } - default: { - // widevineLevel 0 - break; - } + switch (securityProperty) { + case WIDEVINE_SECURITY_LEVEL_1: { + widevineLevel = 1; + break; + } + case WIDEVINE_SECURITY_LEVEL_2: { + widevineLevel = 2; + break; + } + case WIDEVINE_SECURITY_LEVEL_3: { + widevineLevel = 3; + break; + } + default: { + // widevineLevel 0 + break; } } p.resolve(widevineLevel); @@ -77,17 +79,17 @@ public class VideoDecoderPropertiesModule extends ReactContextBaseJavaModule { @SuppressLint("ObsoleteSdkInt") @ReactMethod public void isCodecSupported(String mimeType, int width, int height, Promise p) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - MediaCodecList mRegularCodecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS); - MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height); - String codecName = mRegularCodecs.findDecoderForFormat(format); - if (codecName == null) { - p.resolve(false); - } else { - p.resolve(true); - } - } else { + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { p.resolve(false); + return; + } + MediaCodecList mRegularCodecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS); + MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height); + String codecName = mRegularCodecs.findDecoderForFormat(format); + if (codecName == null) { + p.resolve(false); + } else { + p.resolve(true); } }