diff --git a/CHANGELOG.md b/CHANGELOG.md
index e1704a35..8668a915 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@
* 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
diff --git a/README.md b/README.md
index b5f99f21..c8242d5a 100644
--- a/README.md
+++ b/README.md
@@ -302,6 +302,7 @@ var styles = StyleSheet.create({
* [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)
* [onLoad](#onload)
* [onLoadStart](#onloadstart)
+* [onReadyForDisplay](#onreadyfordisplay)
* [onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged)
* [onPlaybackRateChange](#onplaybackratechange)
* [onProgress](#onprogress)
@@ -954,6 +955,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/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/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m
index 271433b3..71bf82d8 100644
--- a/ios/Video/RCTVideo.m
+++ b/ios/Video/RCTVideo.m
@@ -356,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];
@@ -600,7 +598,10 @@ static int const RCTVideoUnset = -1;
} 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]) {
@@ -692,12 +693,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) {
@@ -1285,7 +1280,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];
@@ -1295,6 +1292,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];
}
@@ -1490,6 +1489,7 @@ static int const RCTVideoUnset = -1;
[self removePlayerLayer];
[_playerViewController.contentOverlayView removeObserver:self forKeyPath:@"frame"];
+ [_playerViewController removeObserver:self forKeyPath:readyForDisplayKeyPath];
[_playerViewController.view removeFromSuperview];
_playerViewController = nil;