Merge branch 'release/5.0.0' into update-androidx
This commit is contained in:
commit
a45e857bbc
17
CHANGELOG.md
17
CHANGELOG.md
@ -1,7 +1,22 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
### next
|
### Version 4.4.4
|
||||||
|
* Handle racing conditions when props are setted on exoplayer
|
||||||
|
|
||||||
|
### Version 4.4.3
|
||||||
|
* Fix mute/unmute when controls are present (iOS) [#1654](https://github.com/react-native-community/react-native-video/pull/1654)
|
||||||
|
* Fix Android videos being able to play with background music/audio from other apps.
|
||||||
|
* Fixed memory leak on iOS when using `controls` [#1647](https://github.com/react-native-community/react-native-video/pull/1647)
|
||||||
|
* (Android) Update gradle and target SDK [#1629](https://github.com/react-native-community/react-native-video/pull/1629)
|
||||||
|
* Fix iOS stressed mount/unmount crash [#1646](https://github.com/react-native-community/react-native-video/pull/1646)
|
||||||
|
|
||||||
|
### Version 4.4.2
|
||||||
|
* Change compileOnly to implementation on gradle (for newer gradle versions and react-native 0.59 support) [#1592](https://github.com/react-native-community/react-native-video/pull/1592)
|
||||||
|
* Replaced RCTBubblingEventBlock events by RCTDirectEventBlock to avoid event name collisions [#1625](https://github.com/react-native-community/react-native-video/pull/1625)
|
||||||
* Added `onPlaybackRateChange` to README [#1578](https://github.com/react-native-community/react-native-video/pull/1578)
|
* Added `onPlaybackRateChange` to README [#1578](https://github.com/react-native-community/react-native-video/pull/1578)
|
||||||
|
* Added `onReadyForDisplay` to README [#1627](https://github.com/react-native-community/react-native-video/pull/1627)
|
||||||
|
* Improved handling of poster image. Fixes bug with displaying video and poster simultaneously. [#1627](https://github.com/react-native-community/react-native-video/pull/1627)
|
||||||
|
* Fix background audio stopping on iOS when using `controls` [#1614](https://github.com/react-native-community/react-native-video/pull/1614)
|
||||||
|
|
||||||
### Version 4.4.1
|
### Version 4.4.1
|
||||||
* Fix tvOS picture-in-picture compilation regression [#1518](https://github.com/react-native-community/react-native-video/pull/1518)
|
* Fix tvOS picture-in-picture compilation regression [#1518](https://github.com/react-native-community/react-native-video/pull/1518)
|
||||||
|
23
README.md
23
README.md
@ -130,11 +130,11 @@ From version >= 5.0.0, you have to apply this changes:
|
|||||||
dependencies {
|
dependencies {
|
||||||
...
|
...
|
||||||
compile project(':react-native-video')
|
compile project(':react-native-video')
|
||||||
+ implementation "androidx.appcompat:appcompat:1.0.0"
|
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||||
- implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### **android/gradle.properties**
|
#### **android/gradle.properties**
|
||||||
|
|
||||||
Migrating to AndroidX (needs version >= 5.0.0):
|
Migrating to AndroidX (needs version >= 5.0.0):
|
||||||
@ -320,6 +320,7 @@ var styles = StyleSheet.create({
|
|||||||
* [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)
|
* [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)
|
||||||
* [onLoad](#onload)
|
* [onLoad](#onload)
|
||||||
* [onLoadStart](#onloadstart)
|
* [onLoadStart](#onloadstart)
|
||||||
|
* [onReadyForDisplay](#onreadyfordisplay)
|
||||||
* [onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged)
|
* [onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged)
|
||||||
* [onPlaybackRateChange](#onplaybackratechange)
|
* [onPlaybackRateChange](#onplaybackratechange)
|
||||||
* [onProgress](#onprogress)
|
* [onProgress](#onprogress)
|
||||||
@ -387,6 +388,13 @@ For Android MediaPlayer, you will need to build your own controls or use a packa
|
|||||||
|
|
||||||
Platforms: Android ExoPlayer, iOS, react-native-dom
|
Platforms: Android ExoPlayer, iOS, react-native-dom
|
||||||
|
|
||||||
|
#### disableFocus
|
||||||
|
Determines whether video audio should override background music/audio in Android devices.
|
||||||
|
* ** false (default)** - Override background audio/music
|
||||||
|
* **true** - Let background audio/music from other apps play
|
||||||
|
|
||||||
|
Platforms: Android Exoplayer
|
||||||
|
|
||||||
#### filter
|
#### filter
|
||||||
Add video filter
|
Add video filter
|
||||||
* **FilterType.NONE (default)** - No Filter
|
* **FilterType.NONE (default)** - No Filter
|
||||||
@ -972,6 +980,17 @@ Example:
|
|||||||
|
|
||||||
Platforms: all
|
Platforms: all
|
||||||
|
|
||||||
|
#### onReadyForDisplay
|
||||||
|
Callback function that is called when the first video frame is ready for display. This is when the poster is removed.
|
||||||
|
|
||||||
|
Payload: none
|
||||||
|
|
||||||
|
* iOS: [readyForDisplay](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/1615830-readyfordisplay?language=objc)
|
||||||
|
* Android: [MEDIA_INFO_VIDEO_RENDERING_START](https://developer.android.com/reference/android/media/MediaPlayer#MEDIA_INFO_VIDEO_RENDERING_START)
|
||||||
|
* Android ExoPlayer [STATE_READY](https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#STATE_READY)
|
||||||
|
|
||||||
|
Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Web
|
||||||
|
|
||||||
#### onPictureInPictureStatusChanged
|
#### onPictureInPictureStatusChanged
|
||||||
Callback function that is called when picture in picture becomes active or inactive.
|
Callback function that is called when picture in picture becomes active or inactive.
|
||||||
|
|
||||||
|
40
Video.js
40
Video.js
@ -20,7 +20,7 @@ export default class Video extends Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
showPoster: true,
|
showPoster: !!props.poster
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +86,12 @@ export default class Video extends Component {
|
|||||||
this._root = component;
|
this._root = component;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_hidePoster = () => {
|
||||||
|
if (this.state.showPoster) {
|
||||||
|
this.setState({showPoster: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onLoadStart = (event) => {
|
_onLoadStart = (event) => {
|
||||||
if (this.props.onLoadStart) {
|
if (this.props.onLoadStart) {
|
||||||
this.props.onLoadStart(event.nativeEvent);
|
this.props.onLoadStart(event.nativeEvent);
|
||||||
@ -93,6 +99,10 @@ export default class Video extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_onLoad = (event) => {
|
_onLoad = (event) => {
|
||||||
|
// Need to hide poster here for windows as onReadyForDisplay is not implemented
|
||||||
|
if (Platform.OS === 'windows') {
|
||||||
|
this._hidePoster();
|
||||||
|
}
|
||||||
if (this.props.onLoad) {
|
if (this.props.onLoad) {
|
||||||
this.props.onLoad(event.nativeEvent);
|
this.props.onLoad(event.nativeEvent);
|
||||||
}
|
}
|
||||||
@ -117,10 +127,6 @@ export default class Video extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_onSeek = (event) => {
|
_onSeek = (event) => {
|
||||||
if (this.state.showPoster && !this.props.audioOnly) {
|
|
||||||
this.setState({showPoster: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.onSeek) {
|
if (this.props.onSeek) {
|
||||||
this.props.onSeek(event.nativeEvent);
|
this.props.onSeek(event.nativeEvent);
|
||||||
}
|
}
|
||||||
@ -163,6 +169,7 @@ export default class Video extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_onReadyForDisplay = (event) => {
|
_onReadyForDisplay = (event) => {
|
||||||
|
this._hidePoster();
|
||||||
if (this.props.onReadyForDisplay) {
|
if (this.props.onReadyForDisplay) {
|
||||||
this.props.onReadyForDisplay(event.nativeEvent);
|
this.props.onReadyForDisplay(event.nativeEvent);
|
||||||
}
|
}
|
||||||
@ -181,10 +188,6 @@ export default class Video extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_onPlaybackRateChange = (event) => {
|
_onPlaybackRateChange = (event) => {
|
||||||
if (this.state.showPoster && event.nativeEvent.playbackRate !== 0 && !this.props.audioOnly) {
|
|
||||||
this.setState({showPoster: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.onPlaybackRateChange) {
|
if (this.props.onPlaybackRateChange) {
|
||||||
this.props.onPlaybackRateChange(event.nativeEvent);
|
this.props.onPlaybackRateChange(event.nativeEvent);
|
||||||
}
|
}
|
||||||
@ -308,15 +311,16 @@ export default class Video extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<View style={nativeProps.style}>
|
||||||
<RCTVideo ref={this._assignRoot} {...nativeProps} />
|
<RCTVideo
|
||||||
{this.props.poster &&
|
ref={this._assignRoot}
|
||||||
this.state.showPoster && (
|
{...nativeProps}
|
||||||
<View style={nativeProps.style}>
|
style={StyleSheet.absoluteFill}
|
||||||
<Image style={posterStyle} source={{ uri: this.props.poster }} />
|
/>
|
||||||
</View>
|
{this.state.showPoster && (
|
||||||
)}
|
<Image style={posterStyle} source={{ uri: this.props.poster }} />
|
||||||
</React.Fragment>
|
)}
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
|
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
|
||||||
implementation('com.google.android.exoplayer:exoplayer:2.9.3') {
|
implementation('com.google.android.exoplayer:exoplayer:2.9.3') {
|
||||||
exclude group: 'com.android.support'
|
exclude group: 'com.android.support'
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package com.brentvatne.exoplayer;
|
|||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.core.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
@ -144,6 +144,7 @@ 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 boolean controls;
|
||||||
// \ End props
|
// \ End props
|
||||||
|
|
||||||
// React
|
// React
|
||||||
@ -267,6 +268,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
* Toggling the visibility of the player control view
|
* Toggling the visibility of the player control view
|
||||||
*/
|
*/
|
||||||
private void togglePlayerControlVisibility() {
|
private void togglePlayerControlVisibility() {
|
||||||
|
if(player == null) return;
|
||||||
reLayout(playerControlView);
|
reLayout(playerControlView);
|
||||||
if (playerControlView.isVisible()) {
|
if (playerControlView.isVisible()) {
|
||||||
playerControlView.hide();
|
playerControlView.hide();
|
||||||
@ -312,10 +314,15 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
* Adding Player control to the frame layout
|
* Adding Player control to the frame layout
|
||||||
*/
|
*/
|
||||||
private void addPlayerControl() {
|
private void addPlayerControl() {
|
||||||
|
if(player == null) return;
|
||||||
LayoutParams layoutParams = new LayoutParams(
|
LayoutParams layoutParams = new LayoutParams(
|
||||||
LayoutParams.MATCH_PARENT,
|
LayoutParams.MATCH_PARENT,
|
||||||
LayoutParams.MATCH_PARENT);
|
LayoutParams.MATCH_PARENT);
|
||||||
playerControlView.setLayoutParams(layoutParams);
|
playerControlView.setLayoutParams(layoutParams);
|
||||||
|
int indexOfPC = indexOfChild(playerControlView);
|
||||||
|
if (indexOfPC != -1) {
|
||||||
|
removeViewAt(indexOfPC);
|
||||||
|
}
|
||||||
addView(playerControlView, 1, layoutParams);
|
addView(playerControlView, 1, layoutParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,53 +340,62 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializePlayer() {
|
private void initializePlayer() {
|
||||||
if (player == null) {
|
ReactExoplayerView self = this;
|
||||||
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
|
// This ensures all props have been setted, to avoid async racing conditions.
|
||||||
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
new Handler().postDelayed(new Runnable() {
|
||||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (player == null) {
|
||||||
|
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
|
||||||
|
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
||||||
|
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||||
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
|
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
|
||||||
|
|
||||||
DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
|
DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
|
||||||
DefaultLoadControl defaultLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, -1, true);
|
DefaultLoadControl defaultLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, -1, true);
|
||||||
player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, defaultLoadControl);
|
player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, defaultLoadControl);
|
||||||
player.addListener(this);
|
player.addListener(self);
|
||||||
player.setMetadataOutput(this);
|
player.setMetadataOutput(self);
|
||||||
exoPlayerView.setPlayer(player);
|
exoPlayerView.setPlayer(player);
|
||||||
audioBecomingNoisyReceiver.setListener(this);
|
audioBecomingNoisyReceiver.setListener(self);
|
||||||
BANDWIDTH_METER.addEventListener(new Handler(), this);
|
BANDWIDTH_METER.addEventListener(new Handler(), self);
|
||||||
setPlayWhenReady(!isPaused);
|
setPlayWhenReady(!isPaused);
|
||||||
playerNeedsSource = true;
|
playerNeedsSource = true;
|
||||||
|
|
||||||
PlaybackParameters params = new PlaybackParameters(rate, 1f);
|
PlaybackParameters params = new PlaybackParameters(rate, 1f);
|
||||||
player.setPlaybackParameters(params);
|
player.setPlaybackParameters(params);
|
||||||
}
|
}
|
||||||
if (playerNeedsSource && srcUri != null) {
|
if (playerNeedsSource && srcUri != null) {
|
||||||
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
||||||
MediaSource videoSource = buildMediaSource(srcUri, extension);
|
MediaSource videoSource = buildMediaSource(srcUri, extension);
|
||||||
MediaSource mediaSource;
|
MediaSource mediaSource;
|
||||||
if (mediaSourceList.size() == 0) {
|
if (mediaSourceList.size() == 0) {
|
||||||
mediaSource = videoSource;
|
mediaSource = videoSource;
|
||||||
} else {
|
} else {
|
||||||
mediaSourceList.add(0, videoSource);
|
mediaSourceList.add(0, videoSource);
|
||||||
MediaSource[] textSourceArray = mediaSourceList.toArray(
|
MediaSource[] textSourceArray = mediaSourceList.toArray(
|
||||||
new MediaSource[mediaSourceList.size()]
|
new MediaSource[mediaSourceList.size()]
|
||||||
);
|
);
|
||||||
mediaSource = new MergingMediaSource(textSourceArray);
|
mediaSource = new MergingMediaSource(textSourceArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
|
||||||
|
if (haveResumePosition) {
|
||||||
|
player.seekTo(resumeWindow, resumePosition);
|
||||||
|
}
|
||||||
|
player.prepare(mediaSource, !haveResumePosition, false);
|
||||||
|
playerNeedsSource = false;
|
||||||
|
|
||||||
|
eventEmitter.loadStart();
|
||||||
|
loadVideoStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializing the playerControlView
|
||||||
|
initializePlayerControl();
|
||||||
|
setControls(controls);
|
||||||
|
applyModifiers();
|
||||||
}
|
}
|
||||||
|
}, 1);
|
||||||
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
|
|
||||||
if (haveResumePosition) {
|
|
||||||
player.seekTo(resumeWindow, resumePosition);
|
|
||||||
}
|
|
||||||
player.prepare(mediaSource, !haveResumePosition, false);
|
|
||||||
playerNeedsSource = false;
|
|
||||||
|
|
||||||
eventEmitter.loadStart();
|
|
||||||
loadVideoStarted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initializing the playerControlView
|
|
||||||
initializePlayerControl();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
|
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
|
||||||
@ -439,8 +455,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
updateResumePosition();
|
updateResumePosition();
|
||||||
player.release();
|
player.release();
|
||||||
player.setMetadataOutput(null);
|
player.setMetadataOutput(null);
|
||||||
player = null;
|
|
||||||
trackSelector = null;
|
trackSelector = null;
|
||||||
|
player = null;
|
||||||
}
|
}
|
||||||
progressHandler.removeMessages(SHOW_PROGRESS);
|
progressHandler.removeMessages(SHOW_PROGRESS);
|
||||||
themedReactContext.removeLifecycleEventListener(this);
|
themedReactContext.removeLifecycleEventListener(this);
|
||||||
@ -449,7 +465,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean requestAudioFocus() {
|
private boolean requestAudioFocus() {
|
||||||
if (disableFocus) {
|
if (disableFocus || srcUri == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int result = audioManager.requestAudioFocus(this,
|
int result = audioManager.requestAudioFocus(this,
|
||||||
@ -894,6 +910,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
exoPlayerView.setResizeMode(resizeMode);
|
exoPlayerView.setResizeMode(resizeMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyModifiers() {
|
||||||
|
setRepeatModifier(repeat);
|
||||||
|
}
|
||||||
|
|
||||||
public void setRepeatModifier(boolean repeat) {
|
public void setRepeatModifier(boolean repeat) {
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
if (repeat) {
|
if (repeat) {
|
||||||
@ -906,6 +926,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedTrack(int trackType, String type, Dynamic value) {
|
public void setSelectedTrack(int trackType, String type, Dynamic value) {
|
||||||
|
if (player == null) return;
|
||||||
int rendererIndex = getTrackRendererIndex(trackType);
|
int rendererIndex = getTrackRendererIndex(trackType);
|
||||||
if (rendererIndex == C.INDEX_UNSET) {
|
if (rendererIndex == C.INDEX_UNSET) {
|
||||||
return;
|
return;
|
||||||
@ -1156,10 +1177,15 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
* @param controls Controls prop, if true enable controls, if false disable them
|
* @param controls Controls prop, if true enable controls, if false disable them
|
||||||
*/
|
*/
|
||||||
public void setControls(boolean controls) {
|
public void setControls(boolean controls) {
|
||||||
if (controls && exoPlayerView != null) {
|
this.controls = controls;
|
||||||
|
if (player == null || exoPlayerView == null) return;
|
||||||
|
if (controls) {
|
||||||
addPlayerControl();
|
addPlayerControl();
|
||||||
} else if (getChildAt(1) instanceof PlayerControlView && exoPlayerView != null) {
|
} else {
|
||||||
removeViewAt(1);
|
int indexOfPC = indexOfChild(playerControlView);
|
||||||
|
if (indexOfPC != -1) {
|
||||||
|
removeViewAt(indexOfPC);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.brentvatne.exoplayer;
|
package com.brentvatne.exoplayer;
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.brentvatne.exoplayer;
|
package com.brentvatne.exoplayer;
|
||||||
|
|
||||||
import androidx.annotation.StringDef;
|
import android.support.annotation.StringDef;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.facebook.react.bridge.Arguments;
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
@ -21,6 +21,6 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
//noinspection GradleDynamicVersion
|
//noinspection GradleDynamicVersion
|
||||||
compileOnly "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
|
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
|
||||||
implementation 'com.yqritc:android-scalablevideoview:1.0.4'
|
implementation 'com.yqritc:android-scalablevideoview:1.0.4'
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ class RCTVideo extends RCTView {
|
|||||||
this.videoElement = this.initializeVideoElement();
|
this.videoElement = this.initializeVideoElement();
|
||||||
this.videoElement.addEventListener("ended", this.onEnd);
|
this.videoElement.addEventListener("ended", this.onEnd);
|
||||||
this.videoElement.addEventListener("loadeddata", this.onLoad);
|
this.videoElement.addEventListener("loadeddata", this.onLoad);
|
||||||
|
this.videoElement.addEventListener("canplay", this.onReadyForDisplay);
|
||||||
this.videoElement.addEventListener("loadstart", this.onLoadStart);
|
this.videoElement.addEventListener("loadstart", this.onLoadStart);
|
||||||
this.videoElement.addEventListener("pause", this.onPause);
|
this.videoElement.addEventListener("pause", this.onPause);
|
||||||
this.videoElement.addEventListener("play", this.onPlay);
|
this.videoElement.addEventListener("play", this.onPlay);
|
||||||
@ -51,6 +52,7 @@ class RCTVideo extends RCTView {
|
|||||||
detachFromView(view: UIView) {
|
detachFromView(view: UIView) {
|
||||||
this.videoElement.removeEventListener("ended", this.onEnd);
|
this.videoElement.removeEventListener("ended", this.onEnd);
|
||||||
this.videoElement.removeEventListener("loadeddata", this.onLoad);
|
this.videoElement.removeEventListener("loadeddata", this.onLoad);
|
||||||
|
this.videoElement.removeEventListener("canplay", this.onReadyForDisplay);
|
||||||
this.videoElement.removeEventListener("loadstart", this.onLoadStart);
|
this.videoElement.removeEventListener("loadstart", this.onLoadStart);
|
||||||
this.videoElement.removeEventListener("pause", this.onPause);
|
this.videoElement.removeEventListener("pause", this.onPause);
|
||||||
this.videoElement.removeEventListener("play", this.onPlay);
|
this.videoElement.removeEventListener("play", this.onPlay);
|
||||||
@ -203,6 +205,10 @@ class RCTVideo extends RCTView {
|
|||||||
this.sendEvent("topVideoLoad", payload);
|
this.sendEvent("topVideoLoad", payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onReadyForDisplay = () => {
|
||||||
|
this.sendEvent("onReadyForDisplay");
|
||||||
|
}
|
||||||
|
|
||||||
onLoadStart = () => {
|
onLoadStart = () => {
|
||||||
const src = this.videoElement.currentSrc;
|
const src = this.videoElement.currentSrc;
|
||||||
const payload = {
|
const payload = {
|
||||||
|
@ -18,5 +18,3 @@
|
|||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
|
|
||||||
android.useDeprecatedNdk=true
|
android.useDeprecatedNdk=true
|
||||||
android.useAndroidX=true
|
|
||||||
android.enableJetifier=true
|
|
@ -21,27 +21,27 @@
|
|||||||
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate>
|
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoLoadStart;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoLoad;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoad;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoBuffer;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoBuffer;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoError;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoError;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoProgress;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoProgress;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onBandwidthUpdate;
|
@property (nonatomic, copy) RCTDirectEventBlock onBandwidthUpdate;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoSeek;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoSeek;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoEnd;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoEnd;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onTimedMetadata;
|
@property (nonatomic, copy) RCTDirectEventBlock onTimedMetadata;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoAudioBecomingNoisy;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoAudioBecomingNoisy;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillPresent;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillPresent;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidPresent;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidPresent;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillDismiss;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillDismiss;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidDismiss;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidDismiss;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onReadyForDisplay;
|
@property (nonatomic, copy) RCTDirectEventBlock onReadyForDisplay;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackStalled;
|
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackStalled;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackResume;
|
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackResume;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackRateChange;
|
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackRateChange;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onVideoExternalPlaybackChange;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onPictureInPictureStatusChanged;
|
@property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;
|
||||||
@property (nonatomic, copy) RCTBubblingEventBlock onRestoreUserInterfaceForPictureInPictureStop;
|
@property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;
|
||||||
|
|
||||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
|
@ -223,6 +223,7 @@ static int const RCTVideoUnset = -1;
|
|||||||
if (_playInBackground) {
|
if (_playInBackground) {
|
||||||
// Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html
|
// Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html
|
||||||
[_playerLayer setPlayer:nil];
|
[_playerLayer setPlayer:nil];
|
||||||
|
[_playerViewController setPlayer:nil];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,6 +232,7 @@ static int const RCTVideoUnset = -1;
|
|||||||
[self applyModifiers];
|
[self applyModifiers];
|
||||||
if (_playInBackground) {
|
if (_playInBackground) {
|
||||||
[_playerLayer setPlayer:_player];
|
[_playerLayer setPlayer:_player];
|
||||||
|
[_playerViewController setPlayer:_player];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,8 +356,6 @@ static int const RCTVideoUnset = -1;
|
|||||||
[self setMaxBitRate:_maxBitRate];
|
[self setMaxBitRate:_maxBitRate];
|
||||||
|
|
||||||
[_player pause];
|
[_player pause];
|
||||||
[_playerViewController.view removeFromSuperview];
|
|
||||||
_playerViewController = nil;
|
|
||||||
|
|
||||||
if (_playbackRateObserverRegistered) {
|
if (_playbackRateObserverRegistered) {
|
||||||
[_player removeObserver:self forKeyPath:playbackRate context:nil];
|
[_player removeObserver:self forKeyPath:playbackRate context:nil];
|
||||||
@ -578,27 +578,11 @@ static int const RCTVideoUnset = -1;
|
|||||||
|
|
||||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||||
{
|
{
|
||||||
// when controls==true, this is a hack to reset the rootview when rotation happens in fullscreen
|
|
||||||
if (object == _playerViewController.contentOverlayView) {
|
|
||||||
if ([keyPath isEqualToString:@"frame"]) {
|
|
||||||
|
|
||||||
CGRect oldRect = [change[NSKeyValueChangeOldKey] CGRectValue];
|
if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
|
||||||
CGRect newRect = [change[NSKeyValueChangeNewKey] CGRectValue];
|
self.onReadyForDisplay(@{@"target": self.reactTag});
|
||||||
|
return;
|
||||||
if (!CGRectEqualToRect(oldRect, newRect)) {
|
|
||||||
if (CGRectEqualToRect(newRect, [UIScreen mainScreen].bounds)) {
|
|
||||||
NSLog(@"in fullscreen");
|
|
||||||
} else NSLog(@"not fullscreen");
|
|
||||||
|
|
||||||
[self.reactViewController.view setFrame:[UIScreen mainScreen].bounds];
|
|
||||||
[self.reactViewController.view setNeedsLayout];
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
} else
|
|
||||||
return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object == _playerItem) {
|
if (object == _playerItem) {
|
||||||
// When timeMetadata is read the event onTimedMetadata is triggered
|
// When timeMetadata is read the event onTimedMetadata is triggered
|
||||||
if ([keyPath isEqualToString:timedMetadata]) {
|
if ([keyPath isEqualToString:timedMetadata]) {
|
||||||
@ -690,12 +674,6 @@ static int const RCTVideoUnset = -1;
|
|||||||
_playerBufferEmpty = NO;
|
_playerBufferEmpty = NO;
|
||||||
self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag});
|
self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag});
|
||||||
}
|
}
|
||||||
} else if (object == _playerLayer) {
|
|
||||||
if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey]) {
|
|
||||||
if([change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
|
|
||||||
self.onReadyForDisplay(@{@"target": self.reactTag});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (object == _player) {
|
} else if (object == _player) {
|
||||||
if([keyPath isEqualToString:playbackRate]) {
|
if([keyPath isEqualToString:playbackRate]) {
|
||||||
if(self.onPlaybackRateChange) {
|
if(self.onPlaybackRateChange) {
|
||||||
@ -716,7 +694,25 @@ static int const RCTVideoUnset = -1;
|
|||||||
@"target": self.reactTag});
|
@"target": self.reactTag});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (object == _playerViewController.contentOverlayView) {
|
||||||
|
// when controls==true, this is a hack to reset the rootview when rotation happens in fullscreen
|
||||||
|
if ([keyPath isEqualToString:@"frame"]) {
|
||||||
|
|
||||||
|
CGRect oldRect = [change[NSKeyValueChangeOldKey] CGRectValue];
|
||||||
|
CGRect newRect = [change[NSKeyValueChangeNewKey] CGRectValue];
|
||||||
|
|
||||||
|
if (!CGRectEqualToRect(oldRect, newRect)) {
|
||||||
|
if (CGRectEqualToRect(newRect, [UIScreen mainScreen].bounds)) {
|
||||||
|
NSLog(@"in fullscreen");
|
||||||
|
} else NSLog(@"not fullscreen");
|
||||||
|
|
||||||
|
[self.reactViewController.view setFrame:[UIScreen mainScreen].bounds];
|
||||||
|
[self.reactViewController.view setNeedsLayout];
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if ([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]) {
|
||||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -961,7 +957,9 @@ static int const RCTVideoUnset = -1;
|
|||||||
- (void)applyModifiers
|
- (void)applyModifiers
|
||||||
{
|
{
|
||||||
if (_muted) {
|
if (_muted) {
|
||||||
[_player setVolume:0];
|
if (!_controls) {
|
||||||
|
[_player setVolume:0];
|
||||||
|
}
|
||||||
[_player setMuted:YES];
|
[_player setMuted:YES];
|
||||||
} else {
|
} else {
|
||||||
[_player setVolume:_volume];
|
[_player setVolume:_volume];
|
||||||
@ -1283,7 +1281,9 @@ static int const RCTVideoUnset = -1;
|
|||||||
{
|
{
|
||||||
if( _player )
|
if( _player )
|
||||||
{
|
{
|
||||||
_playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem];
|
if (!_playerViewController) {
|
||||||
|
_playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem];
|
||||||
|
}
|
||||||
// to prevent video from being animated when resizeMode is 'cover'
|
// to prevent video from being animated when resizeMode is 'cover'
|
||||||
// resize mode must be set before subview is added
|
// resize mode must be set before subview is added
|
||||||
[self setResizeMode:_resizeMode];
|
[self setResizeMode:_resizeMode];
|
||||||
@ -1294,6 +1294,8 @@ static int const RCTVideoUnset = -1;
|
|||||||
[self addSubview:_playerViewController.view];
|
[self addSubview:_playerViewController.view];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[_playerViewController addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil];
|
||||||
|
|
||||||
[_playerViewController.contentOverlayView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
|
[_playerViewController.contentOverlayView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1488,7 +1490,10 @@ static int const RCTVideoUnset = -1;
|
|||||||
[self removePlayerLayer];
|
[self removePlayerLayer];
|
||||||
|
|
||||||
[_playerViewController.contentOverlayView removeObserver:self forKeyPath:@"frame"];
|
[_playerViewController.contentOverlayView removeObserver:self forKeyPath:@"frame"];
|
||||||
|
[_playerViewController removeObserver:self forKeyPath:readyForDisplayKeyPath];
|
||||||
[_playerViewController.view removeFromSuperview];
|
[_playerViewController.view removeFromSuperview];
|
||||||
|
_playerViewController.rctDelegate = nil;
|
||||||
|
_playerViewController.player = nil;
|
||||||
_playerViewController = nil;
|
_playerViewController = nil;
|
||||||
|
|
||||||
[self removePlayerTimeObserver];
|
[self removePlayerTimeObserver];
|
||||||
|
@ -45,25 +45,25 @@ RCT_EXPORT_VIEW_PROPERTY(filterEnabled, BOOL);
|
|||||||
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
|
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);
|
||||||
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
|
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoBuffer, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoBuffer, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoError, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoError, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoProgress, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoProgress, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onBandwidthUpdate, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onBandwidthUpdate, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoSeek, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoSeek, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoEnd, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoEnd, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onTimedMetadata, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onTimedMetadata, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoAudioBecomingNoisy, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoAudioBecomingNoisy, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillPresent, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillPresent, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidDismiss, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidDismiss, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock);
|
||||||
RCT_REMAP_METHOD(save,
|
RCT_REMAP_METHOD(save,
|
||||||
options:(NSDictionary *)options
|
options:(NSDictionary *)options
|
||||||
reactTag:(nonnull NSNumber *)reactTag
|
reactTag:(nonnull NSNumber *)reactTag
|
||||||
@ -79,8 +79,8 @@ RCT_REMAP_METHOD(save,
|
|||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTBubblingEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);
|
||||||
|
|
||||||
- (NSDictionary *)constantsToExport
|
- (NSDictionary *)constantsToExport
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "react-native-video",
|
"name": "react-native-video",
|
||||||
"version": "4.4.1",
|
"version": "4.4.4",
|
||||||
"description": "A <Video /> element for react-native",
|
"description": "A <Video /> element for react-native",
|
||||||
"main": "Video.js",
|
"main": "Video.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
Loading…
Reference in New Issue
Block a user