Skip to content

Commit 3a7be63

Browse files
authored
Merge pull request #1627 from jenshandersson/posterfix
Fixes bug where poster and video was displayed simultaneously
2 parents 88d2aed + 6ea6583 commit 3a7be63

File tree

5 files changed

+52
-28
lines changed

5 files changed

+52
-28
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* 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)
55
* Replaced RCTBubblingEventBlock events by RCTDirectEventBlock to avoid event name collisions [#1625](https://github.com/react-native-community/react-native-video/pull/1625)
66
* Added `onPlaybackRateChange` to README [#1578](https://github.com/react-native-community/react-native-video/pull/1578)
7+
* Added `onReadyForDisplay` to README [#1627](https://github.com/react-native-community/react-native-video/pull/1627)
8+
* 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)
79
* Fix background audio stopping on iOS when using `controls` [#1614](https://github.com/react-native-community/react-native-video/pull/1614)
810

911
### Version 4.4.1

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ var styles = StyleSheet.create({
302302
* [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)
303303
* [onLoad](#onload)
304304
* [onLoadStart](#onloadstart)
305+
* [onReadyForDisplay](#onreadyfordisplay)
305306
* [onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged)
306307
* [onPlaybackRateChange](#onplaybackratechange)
307308
* [onProgress](#onprogress)
@@ -954,6 +955,17 @@ Example:
954955

955956
Platforms: all
956957

958+
#### onReadyForDisplay
959+
Callback function that is called when the first video frame is ready for display. This is when the poster is removed.
960+
961+
Payload: none
962+
963+
* iOS: [readyForDisplay](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/1615830-readyfordisplay?language=objc)
964+
* Android: [MEDIA_INFO_VIDEO_RENDERING_START](https://developer.android.com/reference/android/media/MediaPlayer#MEDIA_INFO_VIDEO_RENDERING_START)
965+
* Android ExoPlayer [STATE_READY](https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#STATE_READY)
966+
967+
Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Web
968+
957969
#### onPictureInPictureStatusChanged
958970
Callback function that is called when picture in picture becomes active or inactive.
959971

Video.js

+22-18
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default class Video extends Component {
2020
super(props);
2121

2222
this.state = {
23-
showPoster: true,
23+
showPoster: !!props.poster
2424
};
2525
}
2626

@@ -86,13 +86,23 @@ export default class Video extends Component {
8686
this._root = component;
8787
};
8888

89+
_hidePoster = () => {
90+
if (this.state.showPoster) {
91+
this.setState({showPoster: false});
92+
}
93+
}
94+
8995
_onLoadStart = (event) => {
9096
if (this.props.onLoadStart) {
9197
this.props.onLoadStart(event.nativeEvent);
9298
}
9399
};
94100

95101
_onLoad = (event) => {
102+
// Need to hide poster here for windows as onReadyForDisplay is not implemented
103+
if (Platform.OS === 'windows') {
104+
this._hidePoster();
105+
}
96106
if (this.props.onLoad) {
97107
this.props.onLoad(event.nativeEvent);
98108
}
@@ -117,10 +127,6 @@ export default class Video extends Component {
117127
};
118128

119129
_onSeek = (event) => {
120-
if (this.state.showPoster && !this.props.audioOnly) {
121-
this.setState({showPoster: false});
122-
}
123-
124130
if (this.props.onSeek) {
125131
this.props.onSeek(event.nativeEvent);
126132
}
@@ -163,6 +169,7 @@ export default class Video extends Component {
163169
};
164170

165171
_onReadyForDisplay = (event) => {
172+
this._hidePoster();
166173
if (this.props.onReadyForDisplay) {
167174
this.props.onReadyForDisplay(event.nativeEvent);
168175
}
@@ -181,10 +188,6 @@ export default class Video extends Component {
181188
};
182189

183190
_onPlaybackRateChange = (event) => {
184-
if (this.state.showPoster && event.nativeEvent.playbackRate !== 0 && !this.props.audioOnly) {
185-
this.setState({showPoster: false});
186-
}
187-
188191
if (this.props.onPlaybackRateChange) {
189192
this.props.onPlaybackRateChange(event.nativeEvent);
190193
}
@@ -308,15 +311,16 @@ export default class Video extends Component {
308311
};
309312

310313
return (
311-
<React.Fragment>
312-
<RCTVideo ref={this._assignRoot} {...nativeProps} />
313-
{this.props.poster &&
314-
this.state.showPoster && (
315-
<View style={nativeProps.style}>
316-
<Image style={posterStyle} source={{ uri: this.props.poster }} />
317-
</View>
318-
)}
319-
</React.Fragment>
314+
<View style={nativeProps.style}>
315+
<RCTVideo
316+
ref={this._assignRoot}
317+
{...nativeProps}
318+
style={StyleSheet.absoluteFill}
319+
/>
320+
{this.state.showPoster && (
321+
<Image style={posterStyle} source={{ uri: this.props.poster }} />
322+
)}
323+
</View>
320324
);
321325
}
322326
}

dom/RCTVideo.js

+6
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class RCTVideo extends RCTView {
3737
this.videoElement = this.initializeVideoElement();
3838
this.videoElement.addEventListener("ended", this.onEnd);
3939
this.videoElement.addEventListener("loadeddata", this.onLoad);
40+
this.videoElement.addEventListener("canplay", this.onReadyForDisplay);
4041
this.videoElement.addEventListener("loadstart", this.onLoadStart);
4142
this.videoElement.addEventListener("pause", this.onPause);
4243
this.videoElement.addEventListener("play", this.onPlay);
@@ -51,6 +52,7 @@ class RCTVideo extends RCTView {
5152
detachFromView(view: UIView) {
5253
this.videoElement.removeEventListener("ended", this.onEnd);
5354
this.videoElement.removeEventListener("loadeddata", this.onLoad);
55+
this.videoElement.removeEventListener("canplay", this.onReadyForDisplay);
5456
this.videoElement.removeEventListener("loadstart", this.onLoadStart);
5557
this.videoElement.removeEventListener("pause", this.onPause);
5658
this.videoElement.removeEventListener("play", this.onPlay);
@@ -203,6 +205,10 @@ class RCTVideo extends RCTView {
203205
this.sendEvent("topVideoLoad", payload);
204206
}
205207

208+
onReadyForDisplay = () => {
209+
this.sendEvent("onReadyForDisplay");
210+
}
211+
206212
onLoadStart = () => {
207213
const src = this.videoElement.currentSrc;
208214
const payload = {

ios/Video/RCTVideo.m

+10-10
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,6 @@ - (void)setSrc:(NSDictionary *)source
356356
[self setMaxBitRate:_maxBitRate];
357357

358358
[_player pause];
359-
[_playerViewController.view removeFromSuperview];
360-
_playerViewController = nil;
361359

362360
if (_playbackRateObserverRegistered) {
363361
[_player removeObserver:self forKeyPath:playbackRate context:nil];
@@ -600,7 +598,10 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
600598
} else
601599
return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
602600
}
603-
601+
if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
602+
self.onReadyForDisplay(@{@"target": self.reactTag});
603+
return;
604+
}
604605
if (object == _playerItem) {
605606
// When timeMetadata is read the event onTimedMetadata is triggered
606607
if ([keyPath isEqualToString:timedMetadata]) {
@@ -692,12 +693,6 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
692693
_playerBufferEmpty = NO;
693694
self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag});
694695
}
695-
} else if (object == _playerLayer) {
696-
if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey]) {
697-
if([change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
698-
self.onReadyForDisplay(@{@"target": self.reactTag});
699-
}
700-
}
701696
} else if (object == _player) {
702697
if([keyPath isEqualToString:playbackRate]) {
703698
if(self.onPlaybackRateChange) {
@@ -1285,7 +1280,9 @@ - (void)usePlayerViewController
12851280
{
12861281
if( _player )
12871282
{
1288-
_playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem];
1283+
if (!_playerViewController) {
1284+
_playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem];
1285+
}
12891286
// to prevent video from being animated when resizeMode is 'cover'
12901287
// resize mode must be set before subview is added
12911288
[self setResizeMode:_resizeMode];
@@ -1295,6 +1292,8 @@ - (void)usePlayerViewController
12951292
[viewController addChildViewController:_playerViewController];
12961293
[self addSubview:_playerViewController.view];
12971294
}
1295+
1296+
[_playerViewController addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil];
12981297

12991298
[_playerViewController.contentOverlayView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
13001299
}
@@ -1490,6 +1489,7 @@ - (void)removeFromSuperview
14901489
[self removePlayerLayer];
14911490

14921491
[_playerViewController.contentOverlayView removeObserver:self forKeyPath:@"frame"];
1492+
[_playerViewController removeObserver:self forKeyPath:readyForDisplayKeyPath];
14931493
[_playerViewController.view removeFromSuperview];
14941494
_playerViewController = nil;
14951495

0 commit comments

Comments
 (0)