Merge branch 'release/5.0.0' into update-androidx

This commit is contained in:
Daniel Mariño Ruiz 2019-07-25 10:05:53 +02:00 committed by GitHub
commit a45e857bbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 223 additions and 150 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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>
); );
} }
} }

View File

@ -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'
} }

View File

@ -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;

View File

@ -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);
}
} }
} }
} }

View File

@ -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;

View File

@ -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;

View File

@ -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'
} }

View File

@ -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 = {

View File

@ -18,5 +18,3 @@
# org.gradle.parallel=true # org.gradle.parallel=true
android.useDeprecatedNdk=true android.useDeprecatedNdk=true
android.useAndroidX=true
android.enableJetifier=true

View File

@ -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;

View File

@ -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];

View File

@ -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
{ {

View File

@ -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",