Merge branch 'master' of https://github.com/react-native-video/react-native-video into feat/add_new_events_on_tracks_changed
# Conflicts: # android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
This commit is contained in:
commit
5aa4d6697d
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -10,10 +10,12 @@ assignees: ''
|
|||||||
# Bug
|
# Bug
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please provide a clear and concise description of what the bug is.
|
Before opening a ticket
|
||||||
Include screenshots if needed.
|
* Ensure the issue has not been already reported
|
||||||
Please test using the latest release of the library, as maybe said bug has been already fixed.
|
* 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)
|
* Provide a clear and concise description of what the bug is.
|
||||||
|
* If the library has multiple install methods, describe installation method (e.g., pod, not pod, with jetifier etc)
|
||||||
|
* Include screenshots if needed.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Platform
|
## Platform
|
||||||
|
94
API.md
94
API.md
@ -214,7 +214,7 @@ Follow the manual linking instuctions for React Native Windows 0.62 above, but s
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Run `yarn xbasic install` before running any of the examples.
|
Run `yarn xbasic install` in the root directory before running any of the examples.
|
||||||
|
|
||||||
### iOS Example
|
### iOS Example
|
||||||
```
|
```
|
||||||
@ -304,6 +304,7 @@ var styles = StyleSheet.create({
|
|||||||
|[selectedTextTrack](#selectedtexttrack)|Android, iOS|
|
|[selectedTextTrack](#selectedtexttrack)|Android, iOS|
|
||||||
|[selectedVideoTrack](#selectedvideotrack)|Android|
|
|[selectedVideoTrack](#selectedvideotrack)|Android|
|
||||||
|[source](#source)|All|
|
|[source](#source)|All|
|
||||||
|
|[subtitleStyle](#subtitleStyle)|Android|
|
||||||
|[textTracks](#texttracks)|Android, iOS|
|
|[textTracks](#texttracks)|Android, iOS|
|
||||||
|[trackId](#trackId)|Android|
|
|[trackId](#trackId)|Android|
|
||||||
|[useTextureView](#usetextureview)|Android|
|
|[useTextureView](#usetextureview)|Android|
|
||||||
@ -347,6 +348,13 @@ var styles = StyleSheet.create({
|
|||||||
|[restoreUserInterfaceForPictureInPictureStop](#restoreuserinterfaceforpictureinpicturestop)|iOS|
|
|[restoreUserInterfaceForPictureInPictureStop](#restoreuserinterfaceforpictureinpicturestop)|iOS|
|
||||||
|[seek](#seek)|All|
|
|[seek](#seek)|All|
|
||||||
|
|
||||||
|
### Static methods
|
||||||
|
|
||||||
|
| Name |Plateforms Support |
|
||||||
|
|--|--|
|
||||||
|
|[getWidevineLevel](#getWidevineLevel)|Android|
|
||||||
|
|[isCodecSupported](#isCodecSupported)|Android|
|
||||||
|
|[isHEVCSupported](#isHEVCSupported)|Android|
|
||||||
|
|
||||||
### Configurable props
|
### Configurable props
|
||||||
|
|
||||||
@ -426,9 +434,11 @@ Platforms: Android, iOS
|
|||||||
|
|
||||||
#### disableFocus
|
#### disableFocus
|
||||||
Determines whether video audio should override background music/audio in Android devices.
|
Determines whether video audio should override background music/audio in Android devices.
|
||||||
* ** false (default)** - Override background audio/music
|
* **false (default)** - Override background audio/music
|
||||||
* **true** - Let background audio/music from other apps play
|
* **true** - Let background audio/music from other apps play
|
||||||
|
|
||||||
|
Note: Allows multiple videos to play if set to `true`. If `false`, when one video is playing and another is started, the first video will be paused.
|
||||||
|
|
||||||
Platforms: Android
|
Platforms: Android
|
||||||
|
|
||||||
#### disableDisconnectError
|
#### disableDisconnectError
|
||||||
@ -439,7 +449,7 @@ Determines if the player needs to throw an error when connection is lost or not
|
|||||||
Platforms: Android
|
Platforms: Android
|
||||||
|
|
||||||
### DRM
|
### DRM
|
||||||
To setup DRM please follow [this guide](./DRM.md)
|
To setup DRM please follow [this guide](./docs/DRM.md)
|
||||||
|
|
||||||
Platforms: Android, iOS
|
Platforms: Android, iOS
|
||||||
|
|
||||||
@ -866,6 +876,23 @@ The following other types are supported on some platforms, but aren't fully docu
|
|||||||
`content://, ms-appx://, ms-appdata://, assets-library://`
|
`content://, ms-appx://, ms-appdata://, assets-library://`
|
||||||
|
|
||||||
|
|
||||||
|
#### subtitleStyle
|
||||||
|
|
||||||
|
Property | Description | Platforms
|
||||||
|
--- | --- | ---
|
||||||
|
fontSizeTrack | Adjust the font size of the subtitles. Default: font size of the device | Android
|
||||||
|
paddingTop | Adjust the top padding of the subtitles. Default: 0| Android
|
||||||
|
paddingBottom | Adjust the bottom padding of the subtitles. Default: 0| Android
|
||||||
|
paddingLeft | Adjust the left padding of the subtitles. Default: 0| Android
|
||||||
|
paddingRight | Adjust the right padding of the subtitles. Default: 0| Android
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
subtitleStyle={{ paddingBottom: 50, fontSize: 20 }}
|
||||||
|
```
|
||||||
|
|
||||||
#### textTracks
|
#### textTracks
|
||||||
Load one or more "sidecar" text tracks. This takes an array of objects representing each track. Each object should have the format:
|
Load one or more "sidecar" text tracks. This takes an array of objects representing each track. Each object should have the format:
|
||||||
|
|
||||||
@ -1427,8 +1454,67 @@ this.player.seek(120, 50); // Seek to 2 minutes with +/- 50 milliseconds accurac
|
|||||||
|
|
||||||
Platforms: iOS
|
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
|
### iOS App Transport Security
|
||||||
|
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
### Version 6.0.0-alpha3
|
||||||
|
- Upgrade ExoPlayer to 2.18.1 [#2846](https://github.com/react-native-video/react-native-video/pull/2846)
|
||||||
|
|
||||||
### Version 6.0.0-alpha.2
|
### 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)
|
- Fix Exoplayer progress not reported when paused [#2664](https://github.com/react-native-video/react-native-video/pull/2664)
|
||||||
- Call playbackRateChange onPlay and onPause [#1493](https://github.com/react-native-video/react-native-video/pull/1493)
|
- Call playbackRateChange onPlay and onPause [#1493](https://github.com/react-native-video/react-native-video/pull/1493)
|
||||||
- Fix being unable to disable sideloaded texttracks in the AVPlayer [#2679](https://github.com/react-native-video/react-native-video/pull/2679)
|
- Fix being unable to disable sideloaded texttracks in the AVPlayer [#2679](https://github.com/react-native-video/react-native-video/pull/2679)
|
||||||
- Fixed crash when iOS seek method called reject on the promise [#2743](https://github.com/react-native-video/react-native-video/pull/2743)
|
- Fixed crash when iOS seek method called reject on the promise [#2743](https://github.com/react-native-video/react-native-video/pull/2743)
|
||||||
- Fix maxBitRate property being ignored on Android [#2670](https://github.com/react-native-video/react-native-video/pull/2670)
|
- Fix maxBitRate property being ignored on Android [#2670](https://github.com/react-native-video/react-native-video/pull/2670)
|
||||||
|
- Fix crash when the source is a cameraroll [#2639] (https://github.com/react-native-video/react-native-video/pull/2639)
|
||||||
|
- Fix IOS UI frame drop on loading video [#2848] (https://github.com/react-native-video/react-native-video/pull/2848)
|
||||||
|
|
||||||
### Version 6.0.0-alpha.1
|
### Version 6.0.0-alpha.1
|
||||||
|
|
||||||
- Remove Android MediaPlayer support [#2724](https://github.com/react-native-video/react-native-video/pull/2724)
|
- Remove Android MediaPlayer support [#2724](https://github.com/react-native-video/react-native-video/pull/2724)
|
||||||
|
**WARNING**: when switching from older version to V6, you need to remove all refrerences of android-exoplayer. This android-exoplayer folder has been renamed to android. Exoplayer is now the only player implementation supported.
|
||||||
|
|
||||||
- Replace Image.propTypes with ImagePropTypes. [#2718](https://github.com/react-native-video/react-native-video/pull/2718)
|
- Replace Image.propTypes with ImagePropTypes. [#2718](https://github.com/react-native-video/react-native-video/pull/2718)
|
||||||
- Fix iOS build caused by type mismatch [#2720](https://github.com/react-native-video/react-native-video/pull/2720)
|
- Fix iOS build caused by type mismatch [#2720](https://github.com/react-native-video/react-native-video/pull/2720)
|
||||||
- ERROR TypeError: undefined is not an object (evaluating '_reactNative.Image.propTypes.resizeMode') [#2714](https://github.com/react-native-video/react-native-video/pull/2714)
|
- ERROR TypeError: undefined is not an object (evaluating '_reactNative.Image.propTypes.resizeMode') [#2714](https://github.com/react-native-video/react-native-video/pull/2714)
|
||||||
|
12
Video.js
12
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 {
|
export default class Video extends Component {
|
||||||
|
|
||||||
@ -302,7 +303,7 @@ export default class Video extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isNetwork = !!(uri && uri.match(/^https?:/));
|
const isNetwork = !!(uri && uri.match(/^https?:/));
|
||||||
const isAsset = !!(uri && uri.match(/^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/));
|
const isAsset = !!(uri && uri.match(/^(assets-library|ph|ipod-library|file|content|ms-appx|ms-appdata):/));
|
||||||
|
|
||||||
let nativeResizeMode;
|
let nativeResizeMode;
|
||||||
const RCTVideoInstance = this.getViewManagerConfig('RCTVideo');
|
const RCTVideoInstance = this.getViewManagerConfig('RCTVideo');
|
||||||
@ -510,6 +511,13 @@ Video.propTypes = {
|
|||||||
fullscreenAutorotate: PropTypes.bool,
|
fullscreenAutorotate: PropTypes.bool,
|
||||||
fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']),
|
fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']),
|
||||||
progressUpdateInterval: PropTypes.number,
|
progressUpdateInterval: PropTypes.number,
|
||||||
|
subtitleStyle: PropTypes.shape({
|
||||||
|
paddingTop: PropTypes.number,
|
||||||
|
paddingBottom: PropTypes.number,
|
||||||
|
paddingLeft: PropTypes.number,
|
||||||
|
paddingRight: PropTypes.number,
|
||||||
|
fontSize: PropTypes.number,
|
||||||
|
}),
|
||||||
useTextureView: PropTypes.bool,
|
useTextureView: PropTypes.bool,
|
||||||
useSecureView: PropTypes.bool,
|
useSecureView: PropTypes.bool,
|
||||||
hideShutterView: PropTypes.bool,
|
hideShutterView: PropTypes.bool,
|
||||||
|
@ -33,7 +33,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
|
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
|
||||||
implementation('com.google.android.exoplayer:exoplayer:2.17.1') {
|
implementation('com.google.android.exoplayer:exoplayer:2.18.1') {
|
||||||
exclude group: 'com.android.support'
|
exclude group: 'com.android.support'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,8 +41,9 @@ dependencies {
|
|||||||
implementation "androidx.annotation:annotation:1.1.0"
|
implementation "androidx.annotation:annotation:1.1.0"
|
||||||
implementation "androidx.core:core:1.1.0"
|
implementation "androidx.core:core:1.1.0"
|
||||||
implementation "androidx.media:media:1.1.0"
|
implementation "androidx.media:media:1.1.0"
|
||||||
|
implementation 'androidx.activity:activity:1.4.0'
|
||||||
|
|
||||||
implementation('com.google.android.exoplayer:extension-okhttp:2.17.1') {
|
implementation('com.google.android.exoplayer:extension-okhttp:2.18.1') {
|
||||||
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
|
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
|
||||||
}
|
}
|
||||||
implementation 'com.squareup.okhttp3:okhttp:${OKHTTP_VERSION}'
|
implementation 'com.squareup.okhttp3:okhttp:${OKHTTP_VERSION}'
|
||||||
|
22
android/src/main/java/com/brentvatne/ReactBridgeUtils.java
Normal file
22
android/src/main/java/com/brentvatne/ReactBridgeUtils.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package com.brentvatne;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file define static helpers to parse in an easier way input props
|
||||||
|
*/
|
||||||
|
public class ReactBridgeUtils {
|
||||||
|
/*
|
||||||
|
retrieve key from map as int. fallback is returned if not available
|
||||||
|
*/
|
||||||
|
static public int safeGetInt(ReadableMap map, String key, int fallback) {
|
||||||
|
return map != null && map.hasKey(key) && !map.isNull(key) ? map.getInt(key) : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
retrieve key from map as double. fallback is returned if not available
|
||||||
|
*/
|
||||||
|
static public double safeGetDouble(ReadableMap map, String key, double fallback) {
|
||||||
|
return map != null && map.hasKey(key) && !map.isNull(key) ? map.getDouble(key) : fallback;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import android.content.Context;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.SurfaceView;
|
import android.view.SurfaceView;
|
||||||
import android.view.TextureView;
|
import android.view.TextureView;
|
||||||
@ -18,7 +19,7 @@ import com.google.android.exoplayer2.PlaybackParameters;
|
|||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.TracksInfo;
|
import com.google.android.exoplayer2.Tracks;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||||
@ -100,6 +101,16 @@ public final class ExoPlayerView extends FrameLayout {
|
|||||||
player.setVideoSurfaceView((SurfaceView) surfaceView);
|
player.setVideoSurfaceView((SurfaceView) surfaceView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void setSubtitleStyle(SubtitleStyle style) {
|
||||||
|
// ensure we reset subtile style before reapplying it
|
||||||
|
subtitleLayout.setUserDefaultStyle();
|
||||||
|
subtitleLayout.setUserDefaultTextSize();
|
||||||
|
|
||||||
|
if (style.getFontSize() > 0) {
|
||||||
|
subtitleLayout.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, style.getFontSize());
|
||||||
|
}
|
||||||
|
subtitleLayout.setPadding(style.getPaddingLeft(), style.getPaddingTop(), style.getPaddingRight(), style.getPaddingBottom());
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSurfaceView() {
|
private void updateSurfaceView() {
|
||||||
View view;
|
View view;
|
||||||
@ -230,7 +241,7 @@ public final class ExoPlayerView extends FrameLayout {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCues(List<Cue> cues) {
|
public void onCues(List<Cue> cues) {
|
||||||
subtitleLayout.onCues(cues);
|
subtitleLayout.setCues(cues);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExoPlayer.VideoListener implementation
|
// ExoPlayer.VideoListener implementation
|
||||||
@ -284,7 +295,7 @@ public final class ExoPlayerView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
public void onTracksChanged(Tracks tracks) {
|
||||||
updateForCurrentTrackSelections();
|
updateForCurrentTrackSelections();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
package com.brentvatne.exoplayer;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
|
import androidx.activity.OnBackPressedCallback;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ui.PlayerControlView;
|
||||||
|
|
||||||
|
public class FullScreenPlayerView extends Dialog {
|
||||||
|
private final PlayerControlView playerControlView;
|
||||||
|
private final ExoPlayerView exoPlayerView;
|
||||||
|
private ViewGroup parent;
|
||||||
|
private final FrameLayout containerView;
|
||||||
|
private final OnBackPressedCallback onBackPressedCallback;
|
||||||
|
|
||||||
|
public FullScreenPlayerView(Context context, ExoPlayerView exoPlayerView, PlayerControlView playerControlView, OnBackPressedCallback onBackPressedCallback) {
|
||||||
|
super(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
|
||||||
|
this.playerControlView = playerControlView;
|
||||||
|
this.exoPlayerView = exoPlayerView;
|
||||||
|
this.onBackPressedCallback = onBackPressedCallback;
|
||||||
|
containerView = new FrameLayout(context);
|
||||||
|
setContentView(containerView, generateDefaultLayoutParams());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
super.onBackPressed();
|
||||||
|
onBackPressedCallback.handleOnBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
parent = (FrameLayout)(exoPlayerView.getParent());
|
||||||
|
|
||||||
|
parent.removeView(exoPlayerView);
|
||||||
|
containerView.addView(exoPlayerView, generateDefaultLayoutParams());
|
||||||
|
|
||||||
|
if (playerControlView != null) {
|
||||||
|
ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen);
|
||||||
|
imageButton.setImageResource(com.google.android.exoplayer2.ui.R.drawable.exo_icon_fullscreen_exit);
|
||||||
|
imageButton.setContentDescription(getContext().getString(com.google.android.exoplayer2.ui.R.string.exo_controls_fullscreen_exit_description));
|
||||||
|
parent.removeView(playerControlView);
|
||||||
|
containerView.addView(playerControlView, generateDefaultLayoutParams());
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
containerView.removeView(exoPlayerView);
|
||||||
|
parent.addView(exoPlayerView, generateDefaultLayoutParams());
|
||||||
|
|
||||||
|
if (playerControlView != null) {
|
||||||
|
ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen);
|
||||||
|
imageButton.setImageResource(com.google.android.exoplayer2.ui.R.drawable.exo_icon_fullscreen_enter);
|
||||||
|
imageButton.setContentDescription(getContext().getString(com.google.android.exoplayer2.ui.R.string.exo_controls_fullscreen_enter_description));
|
||||||
|
containerView.removeView(playerControlView);
|
||||||
|
parent.addView(playerControlView, generateDefaultLayoutParams());
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.requestLayout();
|
||||||
|
parent = null;
|
||||||
|
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FrameLayout.LayoutParams generateDefaultLayoutParams() {
|
||||||
|
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
|
);
|
||||||
|
layoutParams.setMargins(0, 0, 0, 0);
|
||||||
|
return layoutParams;
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ import android.widget.FrameLayout;
|
|||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
import androidx.activity.OnBackPressedCallback;
|
||||||
|
|
||||||
import com.brentvatne.common.Track;
|
import com.brentvatne.common.Track;
|
||||||
import com.brentvatne.common.VideoTrack;
|
import com.brentvatne.common.VideoTrack;
|
||||||
@ -39,8 +40,9 @@ import com.google.android.exoplayer2.PlaybackParameters;
|
|||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.TracksInfo;
|
import com.google.android.exoplayer2.Tracks;
|
||||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
|
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
|
||||||
@ -129,6 +131,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private Player.Listener eventListener;
|
private Player.Listener eventListener;
|
||||||
|
|
||||||
private ExoPlayerView exoPlayerView;
|
private ExoPlayerView exoPlayerView;
|
||||||
|
private FullScreenPlayerView fullScreenPlayerView;
|
||||||
|
|
||||||
private DataSource.Factory mediaDataSourceFactory;
|
private DataSource.Factory mediaDataSourceFactory;
|
||||||
private ExoPlayer player;
|
private ExoPlayer player;
|
||||||
@ -356,7 +359,6 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
// Setting the player for the playerControlView
|
// Setting the player for the playerControlView
|
||||||
playerControlView.setPlayer(player);
|
playerControlView.setPlayer(player);
|
||||||
playerControlView.show();
|
|
||||||
playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container);
|
playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container);
|
||||||
|
|
||||||
// Invoking onClick event for exoplayerView
|
// Invoking onClick event for exoplayerView
|
||||||
@ -388,6 +390,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Handling the fullScreenButton click event
|
||||||
|
ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
|
||||||
|
fullScreenButton.setOnClickListener(v -> setFullscreen(!isFullscreen));
|
||||||
|
|
||||||
// Invoking onPlaybackStateChanged and onPlayWhenReadyChanged events for Player
|
// Invoking onPlaybackStateChanged and onPlayWhenReadyChanged events for Player
|
||||||
eventListener = new Player.Listener() {
|
eventListener = new Player.Listener() {
|
||||||
@Override
|
@Override
|
||||||
@ -430,6 +436,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
removeViewAt(indexOfPC);
|
removeViewAt(indexOfPC);
|
||||||
}
|
}
|
||||||
addView(playerControlView, 1, layoutParams);
|
addView(playerControlView, 1, layoutParams);
|
||||||
|
reLayout(playerControlView);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -557,7 +564,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
private void initializePlayerCore(ReactExoplayerView self) {
|
private void initializePlayerCore(ReactExoplayerView self) {
|
||||||
ExoTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
|
ExoTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
|
||||||
self.trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
self.trackSelector = new DefaultTrackSelector(getContext(), videoTrackSelectionFactory);
|
||||||
self.trackSelector.setParameters(trackSelector.buildUponParameters()
|
self.trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||||
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
|
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
|
||||||
|
|
||||||
@ -653,6 +660,12 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
setControls(controls);
|
setControls(controls);
|
||||||
applyModifiers();
|
applyModifiers();
|
||||||
startBufferCheckTimer();
|
startBufferCheckTimer();
|
||||||
|
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, playerControlView, new OnBackPressedCallback(true) {
|
||||||
|
@Override
|
||||||
|
public void handleOnBackPressed() {
|
||||||
|
setFullscreen(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
|
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
|
||||||
@ -707,6 +720,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
return drmSessionManager;
|
return drmSessionManager;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
drmProvider = new DefaultDrmSessionManagerProvider();
|
||||||
}
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case C.TYPE_SS:
|
case C.TYPE_SS:
|
||||||
@ -813,7 +828,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
player.setPlayWhenReady(true);
|
player.setPlayWhenReady(true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
player.setPlayWhenReady(false);
|
// ensure playback is not ENDED, else it will trigger another ended event
|
||||||
|
if (player.getPlaybackState() != Player.STATE_ENDED) {
|
||||||
|
player.setPlayWhenReady(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1290,11 +1308,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
public void onTracksChanged(Tracks tracks) {
|
||||||
eventEmitter.textTracks(getTextTrackInfo());
|
eventEmitter.textTracks(getTextTrackInfo());
|
||||||
eventEmitter.audioTracks(getAudioTrackInfo());
|
eventEmitter.audioTracks(getAudioTrackInfo());
|
||||||
eventEmitter.videoTracks(getVideoTrackInfo());
|
eventEmitter.videoTracks(getVideoTrackInfo());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1605,10 +1622,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
TrackSelectionOverride selectionOverride = new TrackSelectionOverride(groups.get(groupIndex), tracks);
|
TrackSelectionOverride selectionOverride = new TrackSelectionOverride(groups.get(groupIndex), tracks);
|
||||||
|
|
||||||
DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters()
|
DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters()
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
.setRendererDisabled(rendererIndex, false)
|
.setRendererDisabled(rendererIndex, false)
|
||||||
.setTrackSelectionOverrides(new TrackSelectionOverrides.Builder().addOverride(selectionOverride).build())
|
.addOverride(selectionOverride)
|
||||||
.build();
|
.build();
|
||||||
trackSelector.setParameters(selectionParameters);
|
trackSelector.setParameters(selectionParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1767,6 +1784,16 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fullScreenPlayerView == null) {
|
||||||
|
fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, playerControlView, new OnBackPressedCallback(true) {
|
||||||
|
@Override
|
||||||
|
public void handleOnBackPressed() {
|
||||||
|
setFullscreen(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Window window = activity.getWindow();
|
Window window = activity.getWindow();
|
||||||
View decorView = window.getDecorView();
|
View decorView = window.getDecorView();
|
||||||
int uiOptions;
|
int uiOptions;
|
||||||
@ -1780,13 +1807,24 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
| SYSTEM_UI_FLAG_FULLSCREEN;
|
| SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
}
|
}
|
||||||
eventEmitter.fullscreenWillPresent();
|
eventEmitter.fullscreenWillPresent();
|
||||||
decorView.setSystemUiVisibility(uiOptions);
|
post(() -> {
|
||||||
eventEmitter.fullscreenDidPresent();
|
decorView.setSystemUiVisibility(uiOptions);
|
||||||
|
if (controls) {
|
||||||
|
fullScreenPlayerView.show();
|
||||||
|
}
|
||||||
|
eventEmitter.fullscreenDidPresent();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
|
uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
|
||||||
eventEmitter.fullscreenWillDismiss();
|
eventEmitter.fullscreenWillDismiss();
|
||||||
decorView.setSystemUiVisibility(uiOptions);
|
post(() -> {
|
||||||
eventEmitter.fullscreenDidDismiss();
|
decorView.setSystemUiVisibility(uiOptions);
|
||||||
|
if (controls) {
|
||||||
|
fullScreenPlayerView.dismiss();
|
||||||
|
reLayout(exoPlayerView);
|
||||||
|
}
|
||||||
|
eventEmitter.fullscreenDidDismiss();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1866,4 +1904,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSubtitleStyle(SubtitleStyle style) {
|
||||||
|
exoPlayerView.setSubtitleStyle(style);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,8 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView";
|
private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView";
|
||||||
private static final String PROP_CONTROLS = "controls";
|
private static final String PROP_CONTROLS = "controls";
|
||||||
|
|
||||||
|
private static final String PROP_SUBTITLE_STYLE = "subtitleStyle";
|
||||||
|
|
||||||
private ReactExoplayerConfig config;
|
private ReactExoplayerConfig config;
|
||||||
|
|
||||||
public ReactExoplayerViewManager(ReactExoplayerConfig config) {
|
public ReactExoplayerViewManager(ReactExoplayerConfig config) {
|
||||||
@ -347,6 +349,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
videoView.setControls(controls);
|
videoView.setControls(controls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = PROP_SUBTITLE_STYLE)
|
||||||
|
public void setSubtitleStyle(final ReactExoplayerView videoView, @Nullable final ReadableMap src) {
|
||||||
|
videoView.setSubtitleStyle(SubtitleStyle.parse(src));
|
||||||
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_BUFFER_CONFIG)
|
@ReactProp(name = PROP_BUFFER_CONFIG)
|
||||||
public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) {
|
public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) {
|
||||||
int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.brentvatne.exoplayer;
|
||||||
|
import com.brentvatne.ReactBridgeUtils;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper file to parse SubtitleStyle prop and build a dedicated class
|
||||||
|
*/
|
||||||
|
public class SubtitleStyle {
|
||||||
|
private static final String PROP_FONT_SIZE_TRACK = "fontSize";
|
||||||
|
private static final String PROP_PADDING_BOTTOM = "paddingBottom";
|
||||||
|
private static final String PROP_PADDING_TOP = "paddingTop";
|
||||||
|
private static final String PROP_PADDING_LEFT = "paddingLeft";
|
||||||
|
private static final String PROP_PADDING_RIGHT = "paddingRight";
|
||||||
|
|
||||||
|
private int fontSize = -1;
|
||||||
|
private int paddingLeft = 0;
|
||||||
|
private int paddingRight = 0;
|
||||||
|
private int paddingTop = 0;
|
||||||
|
private int paddingBottom = 0;
|
||||||
|
|
||||||
|
private SubtitleStyle() {}
|
||||||
|
|
||||||
|
int getFontSize() {return fontSize;}
|
||||||
|
int getPaddingBottom() {return paddingBottom;}
|
||||||
|
int getPaddingTop() {return paddingTop;}
|
||||||
|
int getPaddingLeft() {return paddingLeft;}
|
||||||
|
int getPaddingRight() {return paddingRight;}
|
||||||
|
|
||||||
|
public static SubtitleStyle parse(ReadableMap src) {
|
||||||
|
SubtitleStyle subtitleStyle = new SubtitleStyle();
|
||||||
|
subtitleStyle.fontSize = ReactBridgeUtils.safeGetInt(src, PROP_FONT_SIZE_TRACK, -1);
|
||||||
|
subtitleStyle.paddingBottom = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_BOTTOM, 0);
|
||||||
|
subtitleStyle.paddingTop = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_TOP, 0);
|
||||||
|
subtitleStyle.paddingLeft = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_LEFT, 0);
|
||||||
|
subtitleStyle.paddingRight = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_RIGHT, 0);
|
||||||
|
return subtitleStyle;
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,9 @@ public class ReactVideoPackage implements ReactPackage {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||||
return Collections.emptyList();
|
return Collections.singletonList(
|
||||||
|
new VideoDecoderPropertiesModule(reactContext)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated RN 0.47
|
// Deprecated RN 0.47
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -71,6 +71,14 @@
|
|||||||
android:paddingRight="4dp"
|
android:paddingRight="4dp"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:textColor="#FFBEBEBE"/>
|
android:textColor="#FFBEBEBE"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@id/exo_fullscreen"
|
||||||
|
style="@style/ExoMediaButton.FullScreen"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:scaleType="fitCenter" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
7
android/src/main/res/values/styles.xml
Normal file
7
android/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="ExoMediaButton.FullScreen">
|
||||||
|
<item name="android:src">@drawable/exo_icon_fullscreen_enter</item>
|
||||||
|
<item name="android:contentDescription">@string/exo_controls_fullscreen_enter_description</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
@ -16,7 +16,7 @@ import {
|
|||||||
|
|
||||||
import { Picker } from '@react-native-picker/picker'
|
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 {
|
class VideoPlayer extends Component {
|
||||||
|
|
||||||
@ -77,6 +77,28 @@ class VideoPlayer extends Component {
|
|||||||
video: Video;
|
video: Video;
|
||||||
seekPanResponder: PanResponder | undefined;
|
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) => {
|
onLoad = (data: any) => {
|
||||||
this.setState({ duration: data.duration, loading: false, });
|
this.setState({ duration: data.duration, loading: false, });
|
||||||
this.onAudioTracks(data)
|
this.onAudioTracks(data)
|
||||||
@ -288,6 +310,18 @@ class VideoPlayer extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderInfoControl() {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
this.popupInfo()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={[styles.controlOption]}>{'decoderInfo'}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
renderFullScreenControl() {
|
renderFullScreenControl() {
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@ -541,6 +575,9 @@ class VideoPlayer extends Component {
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.bottomControls}>
|
<View style={styles.bottomControls}>
|
||||||
<View style={styles.generalControls}>
|
<View style={styles.generalControls}>
|
||||||
|
<View style={styles.generalControls}>
|
||||||
|
<View style={styles.resizeModeControl}>{this.renderInfoControl()}</View>
|
||||||
|
</View>
|
||||||
<View style={styles.resizeModeControl}>{this.renderPause()}</View>
|
<View style={styles.resizeModeControl}>{this.renderPause()}</View>
|
||||||
<View style={styles.resizeModeControl}>
|
<View style={styles.resizeModeControl}>
|
||||||
{this.renderRepeatModeControl()}
|
{this.renderRepeatModeControl()}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import AVFoundation
|
import AVFoundation
|
||||||
import Promises
|
import Promises
|
||||||
|
import Photos
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Collection of pure functions
|
* Collection of pure functions
|
||||||
@ -264,8 +265,23 @@ enum RCTVideoUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func preparePHAsset(uri: String) -> Promise<AVAsset?> {
|
||||||
|
return Promise<AVAsset?>(on: .global()) { fulfill, reject in
|
||||||
|
let assetId = String(uri[uri.index(uri.startIndex, offsetBy: "ph://".count)...])
|
||||||
|
guard let phAsset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil).firstObject else {
|
||||||
|
reject(NSError(domain: "", code: 0, userInfo: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let options = PHVideoRequestOptions()
|
||||||
|
options.isNetworkAccessAllowed = true
|
||||||
|
PHCachingImageManager().requestAVAsset(forVideo: phAsset, options: options) { data, _, _ in
|
||||||
|
fulfill(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static func prepareAsset(source:VideoSource) -> (asset:AVURLAsset?, assetOptions:NSMutableDictionary?)? {
|
static func prepareAsset(source:VideoSource) -> (asset:AVURLAsset?, assetOptions:NSMutableDictionary?)? {
|
||||||
guard source.uri != nil && source.uri != "" else { return nil }
|
guard let sourceUri = source.uri, sourceUri != "" else { return nil }
|
||||||
var asset:AVURLAsset!
|
var asset:AVURLAsset!
|
||||||
let bundlePath = Bundle.main.path(forResource: source.uri, ofType: source.type) ?? ""
|
let bundlePath = Bundle.main.path(forResource: source.uri, ofType: source.type) ?? ""
|
||||||
let url = source.isNetwork || source.isAsset
|
let url = source.isNetwork || source.isAsset
|
||||||
|
@ -227,10 +227,20 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
RCTVideoUtils.delay()
|
RCTVideoUtils.delay()
|
||||||
.then{ [weak self] in
|
.then{ [weak self] in
|
||||||
guard let self = self else {throw NSError(domain: "", code: 0, userInfo: nil)}
|
guard let self = self else {throw NSError(domain: "", code: 0, userInfo: nil)}
|
||||||
guard let source = self._source,
|
guard let source = self._source else {
|
||||||
let assetResult = RCTVideoUtils.prepareAsset(source: source),
|
DebugLog("The source not exist")
|
||||||
let asset = assetResult.asset,
|
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||||
let assetOptions = assetResult.assetOptions else {
|
}
|
||||||
|
if let uri = source.uri, uri.starts(with: "ph://") {
|
||||||
|
return Promise {
|
||||||
|
RCTVideoUtils.preparePHAsset(uri: uri).then { asset in
|
||||||
|
return self.playerItemPrepareText(asset:asset, assetOptions:nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard let assetResult = RCTVideoUtils.prepareAsset(source: source),
|
||||||
|
let asset = assetResult.asset,
|
||||||
|
let assetOptions = assetResult.assetOptions else {
|
||||||
DebugLog("Could not find video URL in source '\(self._source)'")
|
DebugLog("Could not find video URL in source '\(self._source)'")
|
||||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||||
}
|
}
|
||||||
@ -264,7 +274,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
self._playerItem?.preferredPeakBitRate = Double(maxBitRate)
|
self._playerItem?.preferredPeakBitRate = Double(maxBitRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
self._player = AVPlayer(playerItem: self._playerItem)
|
self._player = AVPlayer()
|
||||||
|
DispatchQueue.global(qos: .default).async {
|
||||||
|
self._player?.replaceCurrentItem(with: playerItem)
|
||||||
|
}
|
||||||
self._playerObserver.player = self._player
|
self._playerObserver.player = self._player
|
||||||
self.applyModifiers()
|
self.applyModifiers()
|
||||||
self._player?.actionAtItemEnd = .none
|
self._player?.actionAtItemEnd = .none
|
||||||
|
@ -19,8 +19,11 @@ class RCTVideoPlayerViewController: AVPlayerViewController {
|
|||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
super.viewDidDisappear(animated)
|
super.viewDidDisappear(animated)
|
||||||
rctDelegate.videoPlayerViewControllerWillDismiss(playerViewController: self)
|
|
||||||
rctDelegate.videoPlayerViewControllerDidDismiss(playerViewController: self)
|
if rctDelegate != nil {
|
||||||
|
rctDelegate.videoPlayerViewControllerWillDismiss(playerViewController: self)
|
||||||
|
rctDelegate.videoPlayerViewControllerDidDismiss(playerViewController: self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
|
Loading…
Reference in New Issue
Block a user