From 438aa79494d3231bff643e7da65c5078bac6bc81 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Wed, 28 Feb 2018 17:42:49 +0100 Subject: [PATCH] Add cache property; Make playerItemForSource in 'RCTVideoManager.m' async --- Video.js | 1 + ios/RCTVideo.m | 547 ++++++++++++++++++++++-------------------- ios/RCTVideoManager.m | 1 + 3 files changed, 283 insertions(+), 266 deletions(-) diff --git a/Video.js b/Video.js index feb79861..4dd65db3 100644 --- a/Video.js +++ b/Video.js @@ -247,6 +247,7 @@ export default class Video extends Component { Video.propTypes = { /* Native only */ src: PropTypes.object, + cache: PropTypes.bool, seek: PropTypes.number, fullscreen: PropTypes.bool, onVideoLoadStart: PropTypes.func, diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 0f1227b1..ba08bd8b 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -20,20 +20,20 @@ static NSString *const timedMetadata = @"timedMetadata"; AVPlayerLayer *_playerLayer; AVPlayerViewController *_playerViewController; NSURL *_videoURL; - + /* Required to publish events */ RCTEventDispatcher *_eventDispatcher; BOOL _playbackRateObserverRegistered; - + bool _pendingSeek; float _pendingSeekTime; float _lastSeekTime; - + /* For sending videoProgress events */ Float64 _progressUpdateInterval; BOOL _controls; id _timeObserver; - + /* Keep track of any modifiers, need to be applied after each play */ float _volume; float _rate; @@ -53,7 +53,7 @@ static NSString *const timedMetadata = @"timedMetadata"; { if ((self = [super init])) { _eventDispatcher = eventDispatcher; - + _playbackRateObserverRegistered = NO; _playbackStalled = NO; _rate = 1.0; @@ -68,34 +68,34 @@ static NSString *const timedMetadata = @"timedMetadata"; _playInBackground = false; _playWhenInactive = false; _ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; } - + return self; } - (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem { - RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init]; - playerLayer.showsPlaybackControls = NO; - playerLayer.rctDelegate = self; - playerLayer.view.frame = self.bounds; - playerLayer.player = _player; - playerLayer.view.frame = self.bounds; - return playerLayer; + RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init]; + playerLayer.showsPlaybackControls = NO; + playerLayer.rctDelegate = self; + playerLayer.view.frame = self.bounds; + playerLayer.player = _player; + playerLayer.view.frame = self.bounds; + return playerLayer; } /* --------------------------------------------------------- @@ -104,35 +104,35 @@ static NSString *const timedMetadata = @"timedMetadata"; - (CMTime)playerItemDuration { - AVPlayerItem *playerItem = [_player currentItem]; - if (playerItem.status == AVPlayerItemStatusReadyToPlay) - { - return([playerItem duration]); - } - - return(kCMTimeInvalid); + AVPlayerItem *playerItem = [_player currentItem]; + if (playerItem.status == AVPlayerItemStatusReadyToPlay) + { + return([playerItem duration]); + } + + return(kCMTimeInvalid); } - (CMTimeRange)playerItemSeekableTimeRange { - AVPlayerItem *playerItem = [_player currentItem]; - if (playerItem.status == AVPlayerItemStatusReadyToPlay) - { - return [playerItem seekableTimeRanges].firstObject.CMTimeRangeValue; - } - - return (kCMTimeRangeZero); + AVPlayerItem *playerItem = [_player currentItem]; + if (playerItem.status == AVPlayerItemStatusReadyToPlay) + { + return [playerItem seekableTimeRanges].firstObject.CMTimeRangeValue; + } + + return (kCMTimeRangeZero); } /* Cancels the previously registered time observer. */ -(void)removePlayerTimeObserver { - if (_timeObserver) - { - [_player removeTimeObserver:_timeObserver]; - _timeObserver = nil; - } + if (_timeObserver) + { + [_player removeTimeObserver:_timeObserver]; + _timeObserver = nil; + } } #pragma mark - Progress @@ -150,7 +150,7 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)applicationWillResignActive:(NSNotification *)notification { if (_playInBackground || _playWhenInactive || _paused) return; - + [_player pause]; [_player setRate:0.0]; } @@ -175,32 +175,32 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)sendProgressUpdate { - AVPlayerItem *video = [_player currentItem]; - if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) { - return; - } - - CMTime playerDuration = [self playerItemDuration]; - if (CMTIME_IS_INVALID(playerDuration)) { - return; - } - - CMTime currentTime = _player.currentTime; - const Float64 duration = CMTimeGetSeconds(playerDuration); - const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime); - - [[NSNotificationCenter defaultCenter] postNotificationName:@"RCTVideo_progress" object:nil userInfo:@{@"progress": [NSNumber numberWithDouble: currentTimeSecs / duration]}]; - - if( currentTimeSecs >= 0 && self.onVideoProgress) { - self.onVideoProgress(@{ - @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)], - @"playableDuration": [self calculatePlayableDuration], - @"atValue": [NSNumber numberWithLongLong:currentTime.value], - @"atTimescale": [NSNumber numberWithInt:currentTime.timescale], - @"target": self.reactTag, - @"seekableDuration": [self calculateSeekableDuration], - }); - } + AVPlayerItem *video = [_player currentItem]; + if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) { + return; + } + + CMTime playerDuration = [self playerItemDuration]; + if (CMTIME_IS_INVALID(playerDuration)) { + return; + } + + CMTime currentTime = _player.currentTime; + const Float64 duration = CMTimeGetSeconds(playerDuration); + const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime); + + [[NSNotificationCenter defaultCenter] postNotificationName:@"RCTVideo_progress" object:nil userInfo:@{@"progress": [NSNumber numberWithDouble: currentTimeSecs / duration]}]; + + if( currentTimeSecs >= 0 && self.onVideoProgress) { + self.onVideoProgress(@{ + @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)], + @"playableDuration": [self calculatePlayableDuration], + @"atValue": [NSNumber numberWithLongLong:currentTime.value], + @"atTimescale": [NSNumber numberWithInt:currentTime.timescale], + @"target": self.reactTag, + @"seekableDuration": [self calculateSeekableDuration], + }); + } } /*! @@ -230,12 +230,12 @@ static NSString *const timedMetadata = @"timedMetadata"; - (NSNumber *)calculateSeekableDuration { - CMTimeRange timeRange = [self playerItemSeekableTimeRange]; - if (CMTIME_IS_NUMERIC(timeRange.duration)) - { - return [NSNumber numberWithFloat:CMTimeGetSeconds(timeRange.duration)]; - } - return [NSNumber numberWithInteger:0]; + CMTimeRange timeRange = [self playerItemSeekableTimeRange]; + if (CMTIME_IS_NUMERIC(timeRange.duration)) + { + return [NSNumber numberWithFloat:CMTimeGetSeconds(timeRange.duration)]; + } + return [NSNumber numberWithInteger:0]; } - (void)addPlayerItemObservers @@ -266,29 +266,41 @@ static NSString *const timedMetadata = @"timedMetadata"; #pragma mark - Player and source +- (void)setCache:(BOOL *)cache +{ + // @TODO: Implement +} + - (void)setSrc:(NSDictionary *)source { [self removePlayerTimeObserver]; [self removePlayerItemObservers]; - _playerItem = [self playerItemForSource:source]; - [self addPlayerItemObservers]; + [self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) { + [self didSetPlayerItemWithSource:source playerItem:playerItem]; + }]; +} +- (void) didSetPlayerItemWithSource:(NSDictionary *)source playerItem:(AVPlayerItem *) playerItem +{ + _playerItem = playerItem; + [self addPlayerItemObservers]; + [_player pause]; [self removePlayerLayer]; [_playerViewController.view removeFromSuperview]; _playerViewController = nil; - + if (_playbackRateObserverRegistered) { [_player removeObserver:self forKeyPath:playbackRate context:nil]; _playbackRateObserverRegistered = NO; } - + _player = [AVPlayer playerWithPlayerItem:_playerItem]; _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; - + [_player addObserver:self forKeyPath:playbackRate options:0 context:nil]; _playbackRateObserverRegistered = YES; - + const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; // @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html __weak RCTVideo *weakSelf = self; @@ -296,107 +308,110 @@ static NSString *const timedMetadata = @"timedMetadata"; queue:NULL usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } ]; - + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //Perform on next run loop, otherwise onVideoLoadStart is nil if(self.onVideoLoadStart) { id uri = [source objectForKey:@"uri"]; id type = [source objectForKey:@"type"]; self.onVideoLoadStart(@{@"src": @{ - @"uri": uri ? uri : [NSNull null], - @"type": type ? type : [NSNull null], - @"isNetwork": [NSNumber numberWithBool:(bool)[source objectForKey:@"isNetwork"]]}, - @"target": self.reactTag - }); + @"uri": uri ? uri : [NSNull null], + @"type": type ? type : [NSNull null], + @"isNetwork": [NSNumber numberWithBool:(bool)[source objectForKey:@"isNetwork"]]}, + @"target": self.reactTag + }); } }); } -- (AVPlayerItem*)playerItemForSource:(NSDictionary *)source +- (void)playerItemForSource:(NSDictionary *)source withCallback:(void(^)(AVPlayerItem *))handler { bool isNetwork = [RCTConvert BOOL:[source objectForKey:@"isNetwork"]]; bool isAsset = [RCTConvert BOOL:[source objectForKey:@"isAsset"]]; NSString *uri = [source objectForKey:@"uri"]; NSString *type = [source objectForKey:@"type"]; - + NSURL *url = (isNetwork || isAsset) ? - [NSURL URLWithString:uri] : - [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]]; - + [NSURL URLWithString:uri] : + [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]]; + if (isNetwork) { + // @TODO: Check if item is cached an if so use the cached asset NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:@{AVURLAssetHTTPCookiesKey : cookies}]; - return [AVPlayerItem playerItemWithAsset:asset]; + handler([AVPlayerItem playerItemWithAsset:asset]); + return; } else if (isAsset) { AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil]; - return [AVPlayerItem playerItemWithAsset:asset]; + handler([AVPlayerItem playerItemWithAsset:asset]); + return; } - - return [AVPlayerItem playerItemWithURL:url]; + handler([AVPlayerItem playerItemWithURL:url]); + return; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (object == _playerItem) { - + if (object == _playerItem) { + // When timeMetadata is read the event onTimedMetadata is triggered if ([keyPath isEqualToString: timedMetadata]) { - - - NSArray *items = [change objectForKey:@"new"]; - if (items && ![items isEqual:[NSNull null]] && items.count > 0) { - - NSMutableArray *array = [NSMutableArray new]; - for (AVMetadataItem *item in items) { - - NSString *value = item.value; - NSString *identifier = item.identifier; - - if (![value isEqual: [NSNull null]]) { - NSDictionary *dictionary = [[NSDictionary alloc] initWithObjects:@[value, identifier] forKeys:@[@"value", @"identifier"]]; - - [array addObject:dictionary]; - } - } - - self.onTimedMetadata(@{ - @"target": self.reactTag, - @"metadata": array - }); + + + NSArray *items = [change objectForKey:@"new"]; + if (items && ![items isEqual:[NSNull null]] && items.count > 0) { + + NSMutableArray *array = [NSMutableArray new]; + for (AVMetadataItem *item in items) { + + NSString *value = item.value; + NSString *identifier = item.identifier; + + if (![value isEqual: [NSNull null]]) { + NSDictionary *dictionary = [[NSDictionary alloc] initWithObjects:@[value, identifier] forKeys:@[@"value", @"identifier"]]; + + [array addObject:dictionary]; + } } + + self.onTimedMetadata(@{ + @"target": self.reactTag, + @"metadata": array + }); + } } - + if ([keyPath isEqualToString:statusKeyPath]) { // Handle player item status change. if (_playerItem.status == AVPlayerItemStatusReadyToPlay) { float duration = CMTimeGetSeconds(_playerItem.asset.duration); - + if (isnan(duration)) { duration = 0.0; } - + NSObject *width = @"undefined"; NSObject *height = @"undefined"; NSString *orientation = @"undefined"; - + if ([_playerItem.asset tracksWithMediaType:AVMediaTypeVideo].count > 0) { AVAssetTrack *videoTrack = [[_playerItem.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; width = [NSNumber numberWithFloat:videoTrack.naturalSize.width]; height = [NSNumber numberWithFloat:videoTrack.naturalSize.height]; CGAffineTransform preferredTransform = [videoTrack preferredTransform]; - + if ((videoTrack.naturalSize.width == preferredTransform.tx - && videoTrack.naturalSize.height == preferredTransform.ty) - || (preferredTransform.tx == 0 && preferredTransform.ty == 0)) + && videoTrack.naturalSize.height == preferredTransform.ty) + || (preferredTransform.tx == 0 && preferredTransform.ty == 0)) { orientation = @"landscape"; } else orientation = @"portrait"; } - - if(self.onVideoLoad) { + + if(self.onVideoLoad) { self.onVideoLoad(@{@"duration": [NSNumber numberWithFloat:duration], @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)], @"canPlayReverse": [NSNumber numberWithBool:_playerItem.canPlayReverse], @@ -406,20 +421,20 @@ static NSString *const timedMetadata = @"timedMetadata"; @"canStepBackward": [NSNumber numberWithBool:_playerItem.canStepBackward], @"canStepForward": [NSNumber numberWithBool:_playerItem.canStepForward], @"naturalSize": @{ - @"width": width, - @"height": height, - @"orientation": orientation - }, + @"width": width, + @"height": height, + @"orientation": orientation + }, @"target": self.reactTag}); - } - - + } + + [self attachListeners]; [self applyModifiers]; } else if(_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) { self.onVideoError(@{@"error": @{@"code": [NSNumber numberWithInteger: _playerItem.error.code], @"domain": _playerItem.error.domain}, - @"target": self.reactTag}); + @"target": self.reactTag}); } } else if ([keyPath isEqualToString:playbackBufferEmptyKeyPath]) { _playerBufferEmpty = YES; @@ -432,28 +447,28 @@ static NSString *const timedMetadata = @"timedMetadata"; _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 == _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) { - self.onPlaybackRateChange(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate], - @"target": self.reactTag}); - } - if(_playbackStalled && _player.rate > 0) { - if(self.onPlaybackResume) { - self.onPlaybackResume(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate], - @"target": self.reactTag}); - } - _playbackStalled = NO; - } + if([keyPath isEqualToString:playbackRate]) { + if(self.onPlaybackRateChange) { + self.onPlaybackRateChange(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate], + @"target": self.reactTag}); } + if(_playbackStalled && _player.rate > 0) { + if(self.onPlaybackResume) { + self.onPlaybackResume(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate], + @"target": self.reactTag}); + } + _playbackStalled = NO; + } + } } else { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } @@ -481,9 +496,9 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)playerItemDidReachEnd:(NSNotification *)notification { if(self.onVideoEnd) { - self.onVideoEnd(@{@"target": self.reactTag}); + self.onVideoEnd(@{@"target": self.reactTag}); } - + if (_repeat) { AVPlayerItem *item = [notification object]; [item seekToTime:kCMTimeZero]; @@ -536,7 +551,7 @@ static NSString *const timedMetadata = @"timedMetadata"; [_player play]; [_player setRate:_rate]; } - + _paused = paused; } @@ -553,31 +568,31 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)setSeek:(float)seekTime { int timeScale = 10000; - + AVPlayerItem *item = _player.currentItem; if (item && item.status == AVPlayerItemStatusReadyToPlay) { // TODO check loadedTimeRanges - + CMTime cmSeekTime = CMTimeMakeWithSeconds(seekTime, timeScale); CMTime current = item.currentTime; // TODO figure out a good tolerance level CMTime tolerance = CMTimeMake(1000, timeScale); BOOL wasPaused = _paused; - + if (CMTimeCompare(current, cmSeekTime) != 0) { if (!wasPaused) [_player pause]; [_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) { if (!wasPaused) [_player play]; if(self.onVideoSeek) { - self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)], - @"seekTime": [NSNumber numberWithFloat:seekTime], - @"target": self.reactTag}); + self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)], + @"seekTime": [NSNumber numberWithFloat:seekTime], + @"target": self.reactTag}); } }]; - + _pendingSeek = false; } - + } else { // TODO: See if this makes sense and if so, actually implement it _pendingSeek = true; @@ -612,7 +627,7 @@ static NSString *const timedMetadata = @"timedMetadata"; [_player setVolume:_volume]; [_player setMuted:NO]; } - + [self setResizeMode:_resizeMode]; [self setRepeat:_repeat]; [self setPaused:_paused]; @@ -625,103 +640,103 @@ static NSString *const timedMetadata = @"timedMetadata"; - (BOOL)getFullscreen { - return _fullscreenPlayerPresented; + return _fullscreenPlayerPresented; } - (void)setFullscreen:(BOOL)fullscreen { - if( fullscreen && !_fullscreenPlayerPresented ) + if( fullscreen && !_fullscreenPlayerPresented ) + { + // Ensure player view controller is not null + if( !_playerViewController ) { - // Ensure player view controller is not null - if( !_playerViewController ) - { - [self usePlayerViewController]; - } - // Set presentation style to fullscreen - [_playerViewController setModalPresentationStyle:UIModalPresentationFullScreen]; - - // Find the nearest view controller - UIViewController *viewController = [self firstAvailableUIViewController]; - if( !viewController ) - { - UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; - viewController = keyWindow.rootViewController; - if( viewController.childViewControllers.count > 0 ) - { - viewController = viewController.childViewControllers.lastObject; - } - } - if( viewController ) - { - _presentingViewController = viewController; - if(self.onVideoFullscreenPlayerWillPresent) { - self.onVideoFullscreenPlayerWillPresent(@{@"target": self.reactTag}); - } - [viewController presentViewController:_playerViewController animated:true completion:^{ - _playerViewController.showsPlaybackControls = YES; - _fullscreenPlayerPresented = fullscreen; - if(self.onVideoFullscreenPlayerDidPresent) { - self.onVideoFullscreenPlayerDidPresent(@{@"target": self.reactTag}); - } - }]; - } + [self usePlayerViewController]; } - else if ( !fullscreen && _fullscreenPlayerPresented ) + // Set presentation style to fullscreen + [_playerViewController setModalPresentationStyle:UIModalPresentationFullScreen]; + + // Find the nearest view controller + UIViewController *viewController = [self firstAvailableUIViewController]; + if( !viewController ) { - [self videoPlayerViewControllerWillDismiss:_playerViewController]; - [_presentingViewController dismissViewControllerAnimated:true completion:^{ - [self videoPlayerViewControllerDidDismiss:_playerViewController]; - }]; + UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; + viewController = keyWindow.rootViewController; + if( viewController.childViewControllers.count > 0 ) + { + viewController = viewController.childViewControllers.lastObject; + } } + if( viewController ) + { + _presentingViewController = viewController; + if(self.onVideoFullscreenPlayerWillPresent) { + self.onVideoFullscreenPlayerWillPresent(@{@"target": self.reactTag}); + } + [viewController presentViewController:_playerViewController animated:true completion:^{ + _playerViewController.showsPlaybackControls = YES; + _fullscreenPlayerPresented = fullscreen; + if(self.onVideoFullscreenPlayerDidPresent) { + self.onVideoFullscreenPlayerDidPresent(@{@"target": self.reactTag}); + } + }]; + } + } + else if ( !fullscreen && _fullscreenPlayerPresented ) + { + [self videoPlayerViewControllerWillDismiss:_playerViewController]; + [_presentingViewController dismissViewControllerAnimated:true completion:^{ + [self videoPlayerViewControllerDidDismiss:_playerViewController]; + }]; + } } - (void)usePlayerViewController { - if( _player ) - { - _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]; - [self addSubview:_playerViewController.view]; - } + if( _player ) + { + _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]; + [self addSubview:_playerViewController.view]; + } } - (void)usePlayerLayer { - if( _player ) - { - _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; - _playerLayer.frame = self.bounds; - _playerLayer.needsDisplayOnBoundsChange = YES; - - // to prevent video from being animated when resizeMode is 'cover' - // resize mode must be set before layer is added - [self setResizeMode:_resizeMode]; - [_playerLayer addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil]; - - [self.layer addSublayer:_playerLayer]; - self.layer.needsDisplayOnBoundsChange = YES; - } + if( _player ) + { + _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; + _playerLayer.frame = self.bounds; + _playerLayer.needsDisplayOnBoundsChange = YES; + + // to prevent video from being animated when resizeMode is 'cover' + // resize mode must be set before layer is added + [self setResizeMode:_resizeMode]; + [_playerLayer addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil]; + + [self.layer addSublayer:_playerLayer]; + self.layer.needsDisplayOnBoundsChange = YES; + } } - (void)setControls:(BOOL)controls { - if( _controls != controls || (!_playerLayer && !_playerViewController) ) + if( _controls != controls || (!_playerLayer && !_playerViewController) ) + { + _controls = controls; + if( _controls ) { - _controls = controls; - if( _controls ) - { - [self removePlayerLayer]; - [self usePlayerViewController]; - } - else - { - [_playerViewController.view removeFromSuperview]; - _playerViewController = nil; - [self usePlayerLayer]; - } + [self removePlayerLayer]; + [self usePlayerViewController]; } + else + { + [_playerViewController.view removeFromSuperview]; + _playerViewController = nil; + [self usePlayerLayer]; + } + } } - (void)setProgressUpdateInterval:(float)progressUpdateInterval @@ -731,33 +746,33 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)removePlayerLayer { - [_playerLayer removeFromSuperlayer]; - [_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath]; - _playerLayer = nil; + [_playerLayer removeFromSuperlayer]; + [_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath]; + _playerLayer = nil; } #pragma mark - RCTVideoPlayerViewControllerDelegate - (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController { - if (_playerViewController == playerViewController && _fullscreenPlayerPresented && self.onVideoFullscreenPlayerWillDismiss) - { - self.onVideoFullscreenPlayerWillDismiss(@{@"target": self.reactTag}); - } + if (_playerViewController == playerViewController && _fullscreenPlayerPresented && self.onVideoFullscreenPlayerWillDismiss) + { + self.onVideoFullscreenPlayerWillDismiss(@{@"target": self.reactTag}); + } } - (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController { - if (_playerViewController == playerViewController && _fullscreenPlayerPresented) - { - _fullscreenPlayerPresented = false; - _presentingViewController = nil; - _playerViewController = nil; - [self applyModifiers]; - if(self.onVideoFullscreenPlayerDidDismiss) { - self.onVideoFullscreenPlayerDidDismiss(@{@"target": self.reactTag}); - } + if (_playerViewController == playerViewController && _fullscreenPlayerPresented) + { + _fullscreenPlayerPresented = false; + _presentingViewController = nil; + _playerViewController = nil; + [self applyModifiers]; + if(self.onVideoFullscreenPlayerDidDismiss) { + self.onVideoFullscreenPlayerDidDismiss(@{@"target": self.reactTag}); } + } } #pragma mark - React View Management @@ -770,15 +785,15 @@ static NSString *const timedMetadata = @"timedMetadata"; { [self setControls:true]; } - + if( _controls ) { - view.frame = self.bounds; - [_playerViewController.contentOverlayView insertSubview:view atIndex:atIndex]; + view.frame = self.bounds; + [_playerViewController.contentOverlayView insertSubview:view atIndex:atIndex]; } else { - RCTLogError(@"video cannot have any subviews"); + RCTLogError(@"video cannot have any subviews"); } return; } @@ -787,7 +802,7 @@ static NSString *const timedMetadata = @"timedMetadata"; { if( _controls ) { - [subview removeFromSuperview]; + [subview removeFromSuperview]; } else { @@ -802,7 +817,7 @@ static NSString *const timedMetadata = @"timedMetadata"; if( _controls ) { _playerViewController.view.frame = self.bounds; - + // also adjust all subviews of contentOverlayView for (UIView* subview in _playerViewController.contentOverlayView.subviews) { subview.frame = self.bounds; @@ -810,10 +825,10 @@ static NSString *const timedMetadata = @"timedMetadata"; } else { - [CATransaction begin]; - [CATransaction setAnimationDuration:0]; - _playerLayer.frame = self.bounds; - [CATransaction commit]; + [CATransaction begin]; + [CATransaction setAnimationDuration:0]; + _playerLayer.frame = self.bounds; + [CATransaction commit]; } } @@ -827,18 +842,18 @@ static NSString *const timedMetadata = @"timedMetadata"; _playbackRateObserverRegistered = NO; } _player = nil; - + [self removePlayerLayer]; - + [_playerViewController.view removeFromSuperview]; _playerViewController = nil; - + [self removePlayerTimeObserver]; [self removePlayerItemObservers]; - + _eventDispatcher = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; - + [super removeFromSuperview]; } diff --git a/ios/RCTVideoManager.m b/ios/RCTVideoManager.m index a5cf85e0..7f95ab80 100644 --- a/ios/RCTVideoManager.m +++ b/ios/RCTVideoManager.m @@ -20,6 +20,7 @@ RCT_EXPORT_MODULE(); } RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary); +RCT_EXPORT_VIEW_PROPERTY(cache, BOOL); RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString); RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL); RCT_EXPORT_VIEW_PROPERTY(paused, BOOL);