diff --git a/CHANGELOG.md b/CHANGELOG.md index de721463..876f9912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ ## Changelog +### Version 4.1.1 +* Don't initialize filters on iOS unless a filter is set. This was causing a startup performance regression [#1360](https://github.com/react-native-community/react-native-video/pull/1360) + +### Version 4.1.0 +* Generate onSeek on Android ExoPlayer & MediaPlayer after seek completes [#1351](https://github.com/react-native-community/react-native-video/pull/1351) +* Remove unneeded onVideoSaved event [#1350](https://github.com/react-native-community/react-native-video/pull/1350) +* Disable AirPlay if sidecar text tracks are enabled [#1304](https://github.com/react-native-community/react-native-video/pull/1304) +* Add possibility to remove black screen while video is loading in Exoplayer [#1355](https://github.com/react-native-community/react-native-video/pull/1355) + ### Version 4.0.1 * Add missing files to package.json [#1342](https://github.com/react-native-community/react-native-video/pull/1342) diff --git a/README.md b/README.md index 8f9926bb..24d8d1c7 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,7 @@ var styles = StyleSheet.create({ * [fullscreenAutorotate](#fullscreenautorotate) * [fullscreenOrientation](#fullscreenorientation) * [headers](#headers) +* [hideShutterView](#hideshutterview) * [id](#id) * [ignoreSilentSwitch](#ignoresilentswitch) * [maxBitRate](#maxbitrate) @@ -296,6 +297,7 @@ var styles = StyleSheet.create({ * [onLoad](#onload) * [onLoadStart](#onloadstart) * [onProgress](#onprogress) +* [onSeek](#onseek) * [onTimedMetadata](#ontimedmetadata) ### Methods @@ -417,6 +419,14 @@ headers={{ Platforms: Android ExoPlayer +#### hideShutterView +Controls whether the ExoPlayer shutter view (black screen while loading) is enabled. + +* **false (default)** - Show shutter view +* **true** - Hide shutter view + +Platforms: Android ExoPlayer + #### id Set the DOM id element so you can use document.getElementById on web platforms. Accepts string values. @@ -667,6 +677,8 @@ uri | URL for the text track. Currently, only tracks hosted on a webserver are s On iOS, sidecar text tracks are only supported for individual files, not HLS playlists. For HLS, you should include the text tracks as part of the playlist. +Note: Due to iOS limitations, sidecar text tracks are not compatible with Airplay. If textTracks are specified, AirPlay support will be automatically disabled. + Example: ``` import { TextTrackType }, Video from 'react-native-video'; @@ -860,6 +872,29 @@ Example: Platforms: all +#### onSeek +Callback function that is called when a seek completes. + +Payload: + +Property | Type | Description +--- | --- | --- +currentTime | number | The current time after the seek +seekTime | number | The requested time + +Example: +``` +{ + currentTime: 100.5 + seekTime: 100 +} +``` + +Both the currentTime & seekTime are reported because the video player may not seek to the exact requested position in order to improve seek performance. + + +Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP + #### onTimedMetadata Callback function that is called when timed metadata becomes available @@ -953,7 +988,7 @@ Platforms: iOS Seek to the specified position represented by seconds. seconds is a float value. -`seek()` can only be called after the `onLoad` event has fired. +`seek()` can only be called after the `onLoad` event has fired. Once completed, the [onSeek](#onseek) event will be called. Example: ``` diff --git a/Video.js b/Video.js index a974d995..a43b2e0e 100644 --- a/Video.js +++ b/Video.js @@ -384,6 +384,7 @@ Video.propTypes = { fullscreenOrientation: PropTypes.oneOf(['all','landscape','portrait']), progressUpdateInterval: PropTypes.number, useTextureView: PropTypes.bool, + hideShutterView: PropTypes.bool, onLoadStart: PropTypes.func, onLoad: PropTypes.func, onBuffer: PropTypes.func, 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 85876709..35d56512 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -39,6 +39,7 @@ public final class ExoPlayerView extends FrameLayout { private ViewGroup.LayoutParams layoutParams; private boolean useTextureView = false; + private boolean hideShutterView = false; public ExoPlayerView(Context context) { this(context, null); @@ -106,6 +107,10 @@ public final class ExoPlayerView extends FrameLayout { } } + private void updateShutterViewVisibility() { + shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE); + } + /** * Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and * {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous @@ -161,6 +166,11 @@ public final class ExoPlayerView extends FrameLayout { updateSurfaceView(); } + public void setHideShutterView(boolean hideShutterView) { + this.hideShutterView = hideShutterView; + updateShutterViewVisibility(); + } + private final Runnable measureAndLayout = new Runnable() { @Override public void run() { 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 7dc02c83..c6d23ca1 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -111,6 +111,7 @@ class ReactExoplayerView extends FrameLayout implements private float rate = 1f; private float audioVolume = 1f; private int maxBitRate = 0; + private long seekTime = C.TIME_UNSET; private int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS; private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS; @@ -131,6 +132,7 @@ class ReactExoplayerView extends FrameLayout implements private float mProgressUpdateInterval = 250.0f; private boolean playInBackground = false; private boolean useTextureView = false; + private boolean hideShutterView = false; private Map requestHeaders; // \ End props @@ -609,7 +611,8 @@ class ReactExoplayerView extends FrameLayout implements @Override public void onSeekProcessed() { - // Do nothing. + eventEmitter.seek(player.getCurrentPosition(), seekTime); + seekTime = C.TIME_UNSET; } @Override @@ -896,7 +899,7 @@ class ReactExoplayerView extends FrameLayout implements public void seekTo(long positionMs) { if (player != null) { - eventEmitter.seek(player.getCurrentPosition(), positionMs); + seekTime = positionMs; player.seekTo(positionMs); } } @@ -966,6 +969,10 @@ class ReactExoplayerView extends FrameLayout implements exoPlayerView.setUseTextureView(useTextureView); } + public void setHideShutterView(boolean hideShutterView) { + exoPlayerView.setHideShutterView(hideShutterView); + } + public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs) { minBufferMs = newMinBufferMs; maxBufferMs = newMaxBufferMs; diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 031f8636..84cc620a 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -52,6 +52,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager= 23) { @@ -606,15 +609,18 @@ public class ReactVideoView extends ScalableVideoView implements mVideoBufferedDuration = (int) Math.round((double) (mVideoDuration * percent) / 100.0); } + public void onSeekComplete(MediaPlayer mp) { + WritableMap event = Arguments.createMap(); + event.putDouble(EVENT_PROP_CURRENT_TIME, getCurrentPosition() / 1000.0); + event.putDouble(EVENT_PROP_SEEK_TIME, mSeekTime / 1000.0); + mEventEmitter.receiveEvent(getId(), Events.EVENT_SEEK.toString(), event); + mSeekTime = 0; + } + @Override public void seekTo(int msec) { - if (mMediaPlayerValid) { - WritableMap event = Arguments.createMap(); - event.putDouble(EVENT_PROP_CURRENT_TIME, getCurrentPosition() / 1000.0); - event.putDouble(EVENT_PROP_SEEK_TIME, msec / 1000.0); - mEventEmitter.receiveEvent(getId(), Events.EVENT_SEEK.toString(), event); - + mSeekTime = msec; super.seekTo(msec); if (isCompleted && mVideoDuration != 0 && msec < mVideoDuration) { isCompleted = false; diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index be109821..76122984 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -404,10 +404,13 @@ static int const RCTVideoUnset = -1; - (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSDictionary * __nullable)assetOptions withCallback:(void(^)(AVPlayerItem *))handler { - if (!_textTracks) { + if (!_textTracks || _textTracks.count==0) { handler([AVPlayerItem playerItemWithAsset:asset]); return; } + + // AVPlayer can't airplay AVMutableCompositions + _allowsExternalPlayback = NO; // sideload text tracks AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init]; @@ -1276,39 +1279,29 @@ static int const RCTVideoUnset = -1; } - (void)setFilter:(NSString *)filterName { - _filterName = filterName; - AVAsset *asset = _playerItem.asset; - - if (asset != nil) { - - CIFilter *filter = [CIFilter filterWithName:filterName]; - - _playerItem.videoComposition = [AVVideoComposition - videoCompositionWithAsset:asset - applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest *_Nonnull request) { - - if (filter == nil) { - - [request finishWithImage:request.sourceImage context:nil]; - - } else { - - CIImage *image = request.sourceImage.imageByClampingToExtent; - - [filter setValue:image forKey:kCIInputImageKey]; - - CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent]; - - [request finishWithImage:output context:nil]; - - } - - }]; - + + if (!asset) { + return; + } else if (!_playerItem.videoComposition && (filterName == nil || [filterName isEqualToString:@""])) { + return; // Setting up an empty filter has a cost so avoid whenever possible } + // TODO: filters don't work for HLS, check & return + CIFilter *filter = [CIFilter filterWithName:filterName]; + _playerItem.videoComposition = [AVVideoComposition + videoCompositionWithAsset:asset + applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest *_Nonnull request) { + if (filter == nil) { + [request finishWithImage:request.sourceImage context:nil]; + } else { + CIImage *image = request.sourceImage.imageByClampingToExtent; + [filter setValue:image forKey:kCIInputImageKey]; + CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent]; + [request finishWithImage:output context:nil]; + } + }]; } #pragma mark - React View Management diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index a1b98c6f..b5d94ebc 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -60,7 +60,6 @@ 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(onVideoSaved, RCTBubblingEventBlock); RCT_REMAP_METHOD(save, options:(NSDictionary *)options reactTag:(nonnull NSNumber *)reactTag diff --git a/package.json b/package.json index 3392e479..c73f6124 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-video", - "version": "4.0.1", + "version": "4.1.0", "description": "A