diff --git a/API.md b/API.md index aa9de5d6..a5e9e089 100644 --- a/API.md +++ b/API.md @@ -345,6 +345,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 @@ -1351,8 +1358,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/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) diff --git a/Video.js b/Video.js index 46796785..287573a1 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..6622e7f9 --- /dev/null +++ b/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java @@ -0,0 +1,107 @@ +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) { + 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; + } + + 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) { + 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); + } + } + + + @ReactMethod + public void isHEVCSupported(Promise p) { + isCodecSupported("video/hevc", 1920, 1080, p); + } + + public VideoDecoderPropertiesModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + } + +} 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()}