diff --git a/CHANGELOG.md b/CHANGELOG.md
index a91fd638..a7e9c1c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,22 @@
## 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 `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
* Fix tvOS picture-in-picture compilation regression [#1518](https://github.com/react-native-community/react-native-video/pull/1518)
diff --git a/README.md b/README.md
index 20c4f9e7..2c40b188 100644
--- a/README.md
+++ b/README.md
@@ -130,11 +130,11 @@ From version >= 5.0.0, you have to apply this changes:
dependencies {
...
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**
Migrating to AndroidX (needs version >= 5.0.0):
@@ -320,6 +320,7 @@ var styles = StyleSheet.create({
* [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)
* [onLoad](#onload)
* [onLoadStart](#onloadstart)
+* [onReadyForDisplay](#onreadyfordisplay)
* [onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged)
* [onPlaybackRateChange](#onplaybackratechange)
* [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
+#### 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
Add video filter
* **FilterType.NONE (default)** - No Filter
@@ -972,6 +980,17 @@ Example:
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
Callback function that is called when picture in picture becomes active or inactive.
diff --git a/Video.js b/Video.js
index 81c1b802..474d7b26 100644
--- a/Video.js
+++ b/Video.js
@@ -20,7 +20,7 @@ export default class Video extends Component {
super(props);
this.state = {
- showPoster: true,
+ showPoster: !!props.poster
};
}
@@ -86,6 +86,12 @@ export default class Video extends Component {
this._root = component;
};
+ _hidePoster = () => {
+ if (this.state.showPoster) {
+ this.setState({showPoster: false});
+ }
+ }
+
_onLoadStart = (event) => {
if (this.props.onLoadStart) {
this.props.onLoadStart(event.nativeEvent);
@@ -93,6 +99,10 @@ export default class Video extends Component {
};
_onLoad = (event) => {
+ // Need to hide poster here for windows as onReadyForDisplay is not implemented
+ if (Platform.OS === 'windows') {
+ this._hidePoster();
+ }
if (this.props.onLoad) {
this.props.onLoad(event.nativeEvent);
}
@@ -117,10 +127,6 @@ export default class Video extends Component {
};
_onSeek = (event) => {
- if (this.state.showPoster && !this.props.audioOnly) {
- this.setState({showPoster: false});
- }
-
if (this.props.onSeek) {
this.props.onSeek(event.nativeEvent);
}
@@ -163,6 +169,7 @@ export default class Video extends Component {
};
_onReadyForDisplay = (event) => {
+ this._hidePoster();
if (this.props.onReadyForDisplay) {
this.props.onReadyForDisplay(event.nativeEvent);
}
@@ -181,10 +188,6 @@ export default class Video extends Component {
};
_onPlaybackRateChange = (event) => {
- if (this.state.showPoster && event.nativeEvent.playbackRate !== 0 && !this.props.audioOnly) {
- this.setState({showPoster: false});
- }
-
if (this.props.onPlaybackRateChange) {
this.props.onPlaybackRateChange(event.nativeEvent);
}
@@ -308,15 +311,16 @@ export default class Video extends Component {
};
return (
-
-
- {this.props.poster &&
- this.state.showPoster && (
-
-
-
- )}
-
+
+
+ {this.state.showPoster && (
+
+ )}
+
);
}
}
diff --git a/android-exoplayer/build.gradle b/android-exoplayer/build.gradle
index 5290c88c..5baf953a 100644
--- a/android-exoplayer/build.gradle
+++ b/android-exoplayer/build.gradle
@@ -22,7 +22,7 @@ android {
}
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') {
exclude group: 'com.android.support'
}
diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java
index 9f3d09be..fc966005 100644
--- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java
+++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java
@@ -2,7 +2,7 @@ package com.brentvatne.exoplayer;
import android.annotation.TargetApi;
import android.content.Context;
-import androidx.core.content.ContextCompat;
+import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
index 175ef84f..1edb8c7a 100644
--- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
+++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
@@ -144,6 +144,7 @@ class ReactExoplayerView extends FrameLayout implements
private boolean playInBackground = false;
private Map requestHeaders;
private boolean mReportBandwidth = false;
+ private boolean controls;
// \ End props
// React
@@ -267,6 +268,7 @@ class ReactExoplayerView extends FrameLayout implements
* Toggling the visibility of the player control view
*/
private void togglePlayerControlVisibility() {
+ if(player == null) return;
reLayout(playerControlView);
if (playerControlView.isVisible()) {
playerControlView.hide();
@@ -312,10 +314,15 @@ class ReactExoplayerView extends FrameLayout implements
* Adding Player control to the frame layout
*/
private void addPlayerControl() {
+ if(player == null) return;
LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
playerControlView.setLayoutParams(layoutParams);
+ int indexOfPC = indexOfChild(playerControlView);
+ if (indexOfPC != -1) {
+ removeViewAt(indexOfPC);
+ }
addView(playerControlView, 1, layoutParams);
}
@@ -333,53 +340,62 @@ class ReactExoplayerView extends FrameLayout implements
}
private void initializePlayer() {
- if (player == null) {
- TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
- trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
- trackSelector.setParameters(trackSelector.buildUponParameters()
+ ReactExoplayerView self = this;
+ // This ensures all props have been setted, to avoid async racing conditions.
+ new Handler().postDelayed(new Runnable() {
+ @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));
- DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
- DefaultLoadControl defaultLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, -1, true);
- player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, defaultLoadControl);
- player.addListener(this);
- player.setMetadataOutput(this);
- exoPlayerView.setPlayer(player);
- audioBecomingNoisyReceiver.setListener(this);
- BANDWIDTH_METER.addEventListener(new Handler(), this);
- setPlayWhenReady(!isPaused);
- playerNeedsSource = true;
+ DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
+ DefaultLoadControl defaultLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, -1, true);
+ player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, defaultLoadControl);
+ player.addListener(self);
+ player.setMetadataOutput(self);
+ exoPlayerView.setPlayer(player);
+ audioBecomingNoisyReceiver.setListener(self);
+ BANDWIDTH_METER.addEventListener(new Handler(), self);
+ setPlayWhenReady(!isPaused);
+ playerNeedsSource = true;
- PlaybackParameters params = new PlaybackParameters(rate, 1f);
- player.setPlaybackParameters(params);
- }
- if (playerNeedsSource && srcUri != null) {
- ArrayList mediaSourceList = buildTextSources();
- MediaSource videoSource = buildMediaSource(srcUri, extension);
- MediaSource mediaSource;
- if (mediaSourceList.size() == 0) {
- mediaSource = videoSource;
- } else {
- mediaSourceList.add(0, videoSource);
- MediaSource[] textSourceArray = mediaSourceList.toArray(
- new MediaSource[mediaSourceList.size()]
- );
- mediaSource = new MergingMediaSource(textSourceArray);
+ PlaybackParameters params = new PlaybackParameters(rate, 1f);
+ player.setPlaybackParameters(params);
+ }
+ if (playerNeedsSource && srcUri != null) {
+ ArrayList mediaSourceList = buildTextSources();
+ MediaSource videoSource = buildMediaSource(srcUri, extension);
+ MediaSource mediaSource;
+ if (mediaSourceList.size() == 0) {
+ mediaSource = videoSource;
+ } else {
+ mediaSourceList.add(0, videoSource);
+ MediaSource[] textSourceArray = mediaSourceList.toArray(
+ new MediaSource[mediaSourceList.size()]
+ );
+ 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();
}
-
- 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();
+ }, 1);
}
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
@@ -439,8 +455,8 @@ class ReactExoplayerView extends FrameLayout implements
updateResumePosition();
player.release();
player.setMetadataOutput(null);
- player = null;
trackSelector = null;
+ player = null;
}
progressHandler.removeMessages(SHOW_PROGRESS);
themedReactContext.removeLifecycleEventListener(this);
@@ -449,7 +465,7 @@ class ReactExoplayerView extends FrameLayout implements
}
private boolean requestAudioFocus() {
- if (disableFocus) {
+ if (disableFocus || srcUri == null) {
return true;
}
int result = audioManager.requestAudioFocus(this,
@@ -894,6 +910,10 @@ class ReactExoplayerView extends FrameLayout implements
exoPlayerView.setResizeMode(resizeMode);
}
+ private void applyModifiers() {
+ setRepeatModifier(repeat);
+ }
+
public void setRepeatModifier(boolean repeat) {
if (player != null) {
if (repeat) {
@@ -906,6 +926,7 @@ class ReactExoplayerView extends FrameLayout implements
}
public void setSelectedTrack(int trackType, String type, Dynamic value) {
+ if (player == null) return;
int rendererIndex = getTrackRendererIndex(trackType);
if (rendererIndex == C.INDEX_UNSET) {
return;
@@ -1156,10 +1177,15 @@ class ReactExoplayerView extends FrameLayout implements
* @param controls Controls prop, if true enable controls, if false disable them
*/
public void setControls(boolean controls) {
- if (controls && exoPlayerView != null) {
+ this.controls = controls;
+ if (player == null || exoPlayerView == null) return;
+ if (controls) {
addPlayerControl();
- } else if (getChildAt(1) instanceof PlayerControlView && exoPlayerView != null) {
- removeViewAt(1);
+ } else {
+ int indexOfPC = indexOfChild(playerControlView);
+ if (indexOfPC != -1) {
+ removeViewAt(indexOfPC);
+ }
}
}
}
diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ResizeMode.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ResizeMode.java
index ceea420c..002c32ad 100644
--- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ResizeMode.java
+++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ResizeMode.java
@@ -1,6 +1,6 @@
package com.brentvatne.exoplayer;
-import androidx.annotation.IntDef;
+import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java
index 24d51e02..00f51c94 100644
--- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java
+++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java
@@ -1,6 +1,6 @@
package com.brentvatne.exoplayer;
-import androidx.annotation.StringDef;
+import android.support.annotation.StringDef;
import android.view.View;
import com.facebook.react.bridge.Arguments;
diff --git a/android/build.gradle b/android/build.gradle
index fbe5fad6..2fb8dfd2 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -21,6 +21,6 @@ android {
dependencies {
//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'
}
diff --git a/dom/RCTVideo.js b/dom/RCTVideo.js
index 9831b61d..32128973 100644
--- a/dom/RCTVideo.js
+++ b/dom/RCTVideo.js
@@ -37,6 +37,7 @@ class RCTVideo extends RCTView {
this.videoElement = this.initializeVideoElement();
this.videoElement.addEventListener("ended", this.onEnd);
this.videoElement.addEventListener("loadeddata", this.onLoad);
+ this.videoElement.addEventListener("canplay", this.onReadyForDisplay);
this.videoElement.addEventListener("loadstart", this.onLoadStart);
this.videoElement.addEventListener("pause", this.onPause);
this.videoElement.addEventListener("play", this.onPlay);
@@ -51,6 +52,7 @@ class RCTVideo extends RCTView {
detachFromView(view: UIView) {
this.videoElement.removeEventListener("ended", this.onEnd);
this.videoElement.removeEventListener("loadeddata", this.onLoad);
+ this.videoElement.removeEventListener("canplay", this.onReadyForDisplay);
this.videoElement.removeEventListener("loadstart", this.onLoadStart);
this.videoElement.removeEventListener("pause", this.onPause);
this.videoElement.removeEventListener("play", this.onPlay);
@@ -203,6 +205,10 @@ class RCTVideo extends RCTView {
this.sendEvent("topVideoLoad", payload);
}
+ onReadyForDisplay = () => {
+ this.sendEvent("onReadyForDisplay");
+ }
+
onLoadStart = () => {
const src = this.videoElement.currentSrc;
const payload = {
diff --git a/examples/basic/android/gradle.properties b/examples/basic/android/gradle.properties
index e77af9f2..1fd964e9 100644
--- a/examples/basic/android/gradle.properties
+++ b/examples/basic/android/gradle.properties
@@ -18,5 +18,3 @@
# org.gradle.parallel=true
android.useDeprecatedNdk=true
-android.useAndroidX=true
-android.enableJetifier=true
\ No newline at end of file
diff --git a/ios/Video/RCTVideo.h b/ios/Video/RCTVideo.h
index 19d5d19f..26d436c2 100644
--- a/ios/Video/RCTVideo.h
+++ b/ios/Video/RCTVideo.h
@@ -21,27 +21,27 @@
@interface RCTVideo : UIView
#endif
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoLoadStart;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoLoad;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoBuffer;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoError;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoProgress;
-@property (nonatomic, copy) RCTBubblingEventBlock onBandwidthUpdate;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoSeek;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoEnd;
-@property (nonatomic, copy) RCTBubblingEventBlock onTimedMetadata;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoAudioBecomingNoisy;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillPresent;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidPresent;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillDismiss;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidDismiss;
-@property (nonatomic, copy) RCTBubblingEventBlock onReadyForDisplay;
-@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackStalled;
-@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackResume;
-@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackRateChange;
-@property (nonatomic, copy) RCTBubblingEventBlock onVideoExternalPlaybackChange;
-@property (nonatomic, copy) RCTBubblingEventBlock onPictureInPictureStatusChanged;
-@property (nonatomic, copy) RCTBubblingEventBlock onRestoreUserInterfaceForPictureInPictureStop;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoLoad;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoBuffer;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoError;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoProgress;
+@property (nonatomic, copy) RCTDirectEventBlock onBandwidthUpdate;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoSeek;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoEnd;
+@property (nonatomic, copy) RCTDirectEventBlock onTimedMetadata;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoAudioBecomingNoisy;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillPresent;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidPresent;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillDismiss;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidDismiss;
+@property (nonatomic, copy) RCTDirectEventBlock onReadyForDisplay;
+@property (nonatomic, copy) RCTDirectEventBlock onPlaybackStalled;
+@property (nonatomic, copy) RCTDirectEventBlock onPlaybackResume;
+@property (nonatomic, copy) RCTDirectEventBlock onPlaybackRateChange;
+@property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;
+@property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;
+@property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m
index dcadee85..4710afaa 100644
--- a/ios/Video/RCTVideo.m
+++ b/ios/Video/RCTVideo.m
@@ -223,6 +223,7 @@ static int const RCTVideoUnset = -1;
if (_playInBackground) {
// Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html
[_playerLayer setPlayer:nil];
+ [_playerViewController setPlayer:nil];
}
}
@@ -231,6 +232,7 @@ static int const RCTVideoUnset = -1;
[self applyModifiers];
if (_playInBackground) {
[_playerLayer setPlayer:_player];
+ [_playerViewController setPlayer:_player];
}
}
@@ -354,8 +356,6 @@ static int const RCTVideoUnset = -1;
[self setMaxBitRate:_maxBitRate];
[_player pause];
- [_playerViewController.view removeFromSuperview];
- _playerViewController = nil;
if (_playbackRateObserverRegistered) {
[_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
{
- // 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];
- 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
- return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+
+ if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
+ self.onReadyForDisplay(@{@"target": self.reactTag});
+ return;
}
-
if (object == _playerItem) {
// When timeMetadata is read the event onTimedMetadata is triggered
if ([keyPath isEqualToString:timedMetadata]) {
@@ -690,12 +674,6 @@ static int const RCTVideoUnset = -1;
_playerBufferEmpty = NO;
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) {
if([keyPath isEqualToString:playbackRate]) {
if(self.onPlaybackRateChange) {
@@ -716,7 +694,25 @@ static int const RCTVideoUnset = -1;
@"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];
}
}
@@ -961,7 +957,9 @@ static int const RCTVideoUnset = -1;
- (void)applyModifiers
{
if (_muted) {
- [_player setVolume:0];
+ if (!_controls) {
+ [_player setVolume:0];
+ }
[_player setMuted:YES];
} else {
[_player setVolume:_volume];
@@ -1283,7 +1281,9 @@ static int const RCTVideoUnset = -1;
{
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'
// resize mode must be set before subview is added
[self setResizeMode:_resizeMode];
@@ -1293,6 +1293,8 @@ static int const RCTVideoUnset = -1;
[viewController addChildViewController:_playerViewController];
[self addSubview:_playerViewController.view];
}
+
+ [_playerViewController addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil];
[_playerViewController.contentOverlayView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
}
@@ -1488,7 +1490,10 @@ static int const RCTVideoUnset = -1;
[self removePlayerLayer];
[_playerViewController.contentOverlayView removeObserver:self forKeyPath:@"frame"];
+ [_playerViewController removeObserver:self forKeyPath:readyForDisplayKeyPath];
[_playerViewController.view removeFromSuperview];
+ _playerViewController.rctDelegate = nil;
+ _playerViewController.player = nil;
_playerViewController = nil;
[self removePlayerTimeObserver];
diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m
index a608f32e..1614eab1 100644
--- a/ios/Video/RCTVideoManager.m
+++ b/ios/Video/RCTVideoManager.m
@@ -45,25 +45,25 @@ RCT_EXPORT_VIEW_PROPERTY(filterEnabled, BOOL);
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
-RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoBuffer, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoError, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoProgress, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onBandwidthUpdate, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoSeek, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoEnd, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onTimedMetadata, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoAudioBecomingNoisy, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillPresent, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidDismiss, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTBubblingEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoBuffer, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoError, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoProgress, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onBandwidthUpdate, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoSeek, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoEnd, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onTimedMetadata, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoAudioBecomingNoisy, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillPresent, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidDismiss, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock);
RCT_REMAP_METHOD(save,
options:(NSDictionary *)options
reactTag:(nonnull NSNumber *)reactTag
@@ -79,8 +79,8 @@ RCT_REMAP_METHOD(save,
}
}];
}
-RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTBubblingEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTBubblingEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);
- (NSDictionary *)constantsToExport
{
diff --git a/package.json b/package.json
index 7b33428a..b1af1564 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-video",
- "version": "4.4.1",
+ "version": "4.4.4",
"description": "A element for react-native",
"main": "Video.js",
"license": "MIT",