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; + } + +}