From 0c03932adad3c800a062bd101fa5bf62ff651e48 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Thu, 2 Aug 2018 10:32:50 -0700 Subject: [PATCH 01/14] Working on autorotation of video player --- ios/RCTVideo.m | 6 +++++- ios/RCTVideoPlayerViewController.m | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index f5fc6125..a696987c 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -55,6 +55,7 @@ static int const RCTVideoUnset = -1; BOOL _playWhenInactive; NSString * _ignoreSilentSwitch; NSString * _resizeMode; + BOOL _fullscreen; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; } @@ -329,6 +330,8 @@ static int const RCTVideoUnset = -1; _playbackRateObserverRegistered = YES; [self addPlayerTimeObserver]; + + [self setFullscreen:_fullscreen]; //Perform on next run loop, otherwise onVideoLoadStart is nil if(self.onVideoLoadStart) { @@ -994,7 +997,8 @@ static int const RCTVideoUnset = -1; - (void)setFullscreen:(BOOL)fullscreen { - if( fullscreen && !_fullscreenPlayerPresented ) + _fullscreen = fullscreen; + if( fullscreen && !_fullscreenPlayerPresented && _player ) { // Ensure player view controller is not null if( !_playerViewController ) diff --git a/ios/RCTVideoPlayerViewController.m b/ios/RCTVideoPlayerViewController.m index 7809221a..12480d54 100644 --- a/ios/RCTVideoPlayerViewController.m +++ b/ios/RCTVideoPlayerViewController.m @@ -13,4 +13,18 @@ [_rctDelegate videoPlayerViewControllerDidDismiss:self]; } +- (BOOL)shouldAutorotate { + return YES; +} + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + + return UIInterfaceOrientationMaskLandscape; +} + +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { + + return UIInterfaceOrientationLandscapeLeft; +} + @end From f41831ceacb1377c90649be3aaaaf3e6c9e36078 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Thu, 9 Aug 2018 09:58:03 -0700 Subject: [PATCH 02/14] Added fullscreen options for iOS Player --- Video.js | 5 + ios/RCTVideo.m | 560 +++++++++++++++-------------- ios/RCTVideoManager.m | 3 +- ios/RCTVideoPlayerViewController.h | 5 + ios/RCTVideoPlayerViewController.m | 24 +- 5 files changed, 328 insertions(+), 269 deletions(-) diff --git a/Video.js b/Video.js index f32725f3..3d3a638a 100644 --- a/Video.js +++ b/Video.js @@ -289,6 +289,11 @@ Video.propTypes = { PropTypes.object ]), fullscreen: PropTypes.bool, + fullscreenOptions: PropTypes.shape({ + enabled: PropTypes.bool, + preferredOrientation: PropTypes.string, + autorotate: PropTypes.bool + }), onVideoLoadStart: PropTypes.func, onVideoLoad: PropTypes.func, onVideoBuffer: PropTypes.func, diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index a696987c..3adab702 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -25,21 +25,21 @@ static int const RCTVideoUnset = -1; BOOL _playerLayerObserverSet; AVPlayerViewController *_playerViewController; NSURL *_videoURL; - + /* Required to publish events */ RCTEventDispatcher *_eventDispatcher; BOOL _playbackRateObserverRegistered; BOOL _videoLoadStarted; - + 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; @@ -56,6 +56,7 @@ static int const RCTVideoUnset = -1; NSString * _ignoreSilentSwitch; NSString * _resizeMode; BOOL _fullscreen; + NSDictionary* _fullscreenOptions; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; } @@ -64,7 +65,7 @@ static int const RCTVideoUnset = -1; { if ((self = [super init])) { _eventDispatcher = eventDispatcher; - + _playbackRateObserverRegistered = NO; _playbackStalled = NO; _rate = 1.0; @@ -80,39 +81,46 @@ static int const RCTVideoUnset = -1; _allowsExternalPlayback = YES; _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]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChanged:) name:AVAudioSessionRouteChangeNotification object:nil]; } - + return self; } - (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem { - RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init]; - playerLayer.showsPlaybackControls = YES; - 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 = YES; + playerLayer.rctDelegate = self; + + if (_fullscreenOptions) { + playerLayer.preferredOrientation = [RCTConvert NSString:[_fullscreenOptions objectForKey:@"preferredOrientation"]]; + playerLayer.autorotate = [RCTConvert BOOL:[_fullscreenOptions objectForKey:@"autorotate"]]; + } + + playerLayer.view.frame = self.bounds; + playerLayer.player = player; + playerLayer.view.frame = self.bounds; + return playerLayer; } /* --------------------------------------------------------- @@ -121,24 +129,24 @@ static int const RCTVideoUnset = -1; - (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); } -(void)addPlayerTimeObserver @@ -156,11 +164,11 @@ static int const RCTVideoUnset = -1; /* 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 @@ -178,7 +186,7 @@ static int const RCTVideoUnset = -1; - (void)applicationWillResignActive:(NSNotification *)notification { if (_playInBackground || _playWhenInactive || _paused) return; - + [_player pause]; [_player setRate:0.0]; } @@ -203,43 +211,43 @@ static int const RCTVideoUnset = -1; - (void)audioRouteChanged:(NSNotification *)notification { - NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; - NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; - if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { - self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag}); - } + NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; + NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; + if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { + self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag}); + } } #pragma mark - Progress - (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], + }); + } } /*! @@ -269,12 +277,12 @@ static int const RCTVideoUnset = -1; - (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 @@ -309,42 +317,42 @@ static int const RCTVideoUnset = -1; [self removePlayerItemObservers]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - + // perform on next run loop, otherwise other passed react-props may not be set _playerItem = [self playerItemForSource:source]; [self addPlayerItemObservers]; - + [_player pause]; [_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; - + [self addPlayerTimeObserver]; - [self setFullscreen:_fullscreen]; - + [self setFullscreenOptions:_fullscreenOptions]; + //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 + }); } - + }); _videoLoadStarted = YES; } @@ -379,15 +387,15 @@ static int const RCTVideoUnset = -1; AVURLAsset *asset; NSMutableDictionary *assetOptions = [[NSMutableDictionary alloc] init]; - + if (isNetwork) { /* Per #1091, this is not a public API. We need to either get approval from Apple to use this * or use a different approach. - NSDictionary *headers = [source objectForKey:@"requestHeaders"]; - if ([headers count] > 0) { - [assetOptions setObject:headers forKey:@"AVURLAssetHTTPHeaderFieldsKey"]; - } - */ + NSDictionary *headers = [source objectForKey:@"requestHeaders"]; + if ([headers count] > 0) { + [assetOptions setObject:headers forKey:@"AVURLAssetHTTPHeaderFieldsKey"]; + } + */ NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; [assetOptions setObject:cookies forKey:AVURLAssetHTTPCookiesKey]; asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:uri] options:assetOptions]; @@ -410,14 +418,14 @@ static int const RCTVideoUnset = -1; ofTrack:videoAsset atTime:kCMTimeZero error:nil]; - + AVAssetTrack *audioAsset = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject; AVMutableCompositionTrack *audioCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; [audioCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) ofTrack:audioAsset atTime:kCMTimeZero error:nil]; - + NSMutableArray* validTextTracks = [NSMutableArray array]; for (int i = 0; i < _textTracks.count; ++i) { AVURLAsset *textURLAsset; @@ -434,14 +442,14 @@ static int const RCTVideoUnset = -1; addMutableTrackWithMediaType:AVMediaTypeText preferredTrackID:kCMPersistentTrackID_Invalid]; [textCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) - ofTrack:textTrackAsset - atTime:kCMTimeZero - error:nil]; + ofTrack:textTrackAsset + atTime:kCMTimeZero + error:nil]; } if (validTextTracks.count != _textTracks.count) { [self setTextTracks:validTextTracks]; } - + return [AVPlayerItem playerItemWithAsset:mixComposition]; } @@ -573,7 +581,7 @@ static int const RCTVideoUnset = -1; selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:[_player currentItem]]; - + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemPlaybackStalledNotification object:nil]; @@ -594,9 +602,9 @@ static int const RCTVideoUnset = -1; - (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]; @@ -628,8 +636,8 @@ static int const RCTVideoUnset = -1; - (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback { - _allowsExternalPlayback = allowsExternalPlayback; - _player.allowsExternalPlayback = _allowsExternalPlayback; + _allowsExternalPlayback = allowsExternalPlayback; + _player.allowsExternalPlayback = _allowsExternalPlayback; } - (void)setPlayWhenInactive:(BOOL)playWhenInactive @@ -657,7 +665,7 @@ static int const RCTVideoUnset = -1; [_player play]; [_player setRate:_rate]; } - + _paused = paused; } @@ -745,7 +753,7 @@ static int const RCTVideoUnset = -1; [_player setVolume:_volume]; [_player setMuted:NO]; } - + [self setSelectedAudioTrack:_selectedAudioTrack]; [self setSelectedTextTrack:_selectedTextTrack]; [self setResizeMode:_resizeMode]; @@ -762,52 +770,52 @@ static int const RCTVideoUnset = -1; - (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)characteristic withCriteria:(NSDictionary *)criteria { - NSString *type = criteria[@"type"]; - AVMediaSelectionGroup *group = [_player.currentItem.asset - mediaSelectionGroupForMediaCharacteristic:characteristic]; - AVMediaSelectionOption *mediaOption; - - if ([type isEqualToString:@"disabled"]) { - // Do nothing. We want to ensure option is nil - } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { - NSString *value = criteria[@"value"]; - for (int i = 0; i < group.options.count; ++i) { - AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; - NSString *optionValue; - if ([type isEqualToString:@"language"]) { - optionValue = [currentOption extendedLanguageTag]; - } else { - optionValue = [[[currentOption commonMetadata] - valueForKey:@"value"] - objectAtIndex:0]; - } - if ([value isEqualToString:optionValue]) { - mediaOption = currentOption; - break; - } - } - //} else if ([type isEqualToString:@"default"]) { - // option = group.defaultOption; */ - } else if ([type isEqualToString:@"index"]) { - if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) { - int index = [criteria[@"value"] intValue]; - if (group.options.count > index) { - mediaOption = [group.options objectAtIndex:index]; - } - } - } else { // default. invalid type or "system" - [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; - return; + NSString *type = criteria[@"type"]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:characteristic]; + AVMediaSelectionOption *mediaOption; + + if ([type isEqualToString:@"disabled"]) { + // Do nothing. We want to ensure option is nil + } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { + NSString *value = criteria[@"value"]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *optionValue; + if ([type isEqualToString:@"language"]) { + optionValue = [currentOption extendedLanguageTag]; + } else { + optionValue = [[[currentOption commonMetadata] + valueForKey:@"value"] + objectAtIndex:0]; + } + if ([value isEqualToString:optionValue]) { + mediaOption = currentOption; + break; + } } - - // If a match isn't found, option will be nil and text tracks will be disabled - [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; + //} else if ([type isEqualToString:@"default"]) { + // option = group.defaultOption; */ + } else if ([type isEqualToString:@"index"]) { + if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) { + int index = [criteria[@"value"] intValue]; + if (group.options.count > index) { + mediaOption = [group.options objectAtIndex:index]; + } + } + } else { // default. invalid type or "system" + [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; + return; + } + + // If a match isn't found, option will be nil and text tracks will be disabled + [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; } - (void)setSelectedAudioTrack:(NSDictionary *)selectedAudioTrack { - _selectedAudioTrack = selectedAudioTrack; - [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible - withCriteria:_selectedAudioTrack]; + _selectedAudioTrack = selectedAudioTrack; + [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible + withCriteria:_selectedAudioTrack]; } - (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack { @@ -879,7 +887,7 @@ static int const RCTVideoUnset = -1; } } } - + for (int i = firstTextIndex; i < _player.currentItem.tracks.count; ++i) { BOOL isEnabled = NO; if (selectedTrackIndex != RCTVideoUnset) { @@ -935,32 +943,32 @@ static int const RCTVideoUnset = -1; - (void)setTextTracks:(NSArray*) textTracks; { _textTracks = textTracks; - + // in case textTracks was set after selectedTextTrack if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack]; } - (NSArray *)getAudioTrackInfo { - NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; - AVMediaSelectionGroup *group = [_player.currentItem.asset - mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - for (int i = 0; i < group.options.count; ++i) { - AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; - NSString *title = @""; - NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"]; - if (values.count > 0) { - title = [values objectAtIndex:0]; - } - NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @""; - NSDictionary *audioTrack = @{ - @"index": [NSNumber numberWithInt:i], - @"title": title, - @"language": language - }; - [audioTracks addObject:audioTrack]; + NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *title = @""; + NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"]; + if (values.count > 0) { + title = [values objectAtIndex:0]; } - return audioTracks; + NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @""; + NSDictionary *audioTrack = @{ + @"index": [NSNumber numberWithInt:i], + @"title": title, + @"language": language + }; + [audioTracks addObject:audioTrack]; + } + return audioTracks; } - (NSArray *)getTextTrackInfo @@ -992,67 +1000,91 @@ static int const RCTVideoUnset = -1; - (BOOL)getFullscreen { - return _fullscreenPlayerPresented; + return _fullscreenPlayerPresented; } -- (void)setFullscreen:(BOOL)fullscreen -{ - _fullscreen = fullscreen; - if( fullscreen && !_fullscreenPlayerPresented && _player ) - { - // 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}); - } - }]; - } - } - else if ( !fullscreen && _fullscreenPlayerPresented ) - { - [self videoPlayerViewControllerWillDismiss:_playerViewController]; - [_presentingViewController dismissViewControllerAnimated:true completion:^{ - [self videoPlayerViewControllerDidDismiss:_playerViewController]; +- (void)setFullscreen:(BOOL) fullscreen { + _fullscreen = fullscreen; + + NSString* enabled = _fullscreen ? @"1" : @"0"; + + if (!_fullscreenOptions) { + [self setFullscreenOptions: + @{ + @"enabled": enabled, + @"autorotate": @"0", + @"preferredOrientation": @"default" }]; + } + else { + [_fullscreenOptions setValue:enabled forKey:@"enabled"]; + [self setFullscreenOptions:_fullscreenOptions]; + } +} + +- (void)setFullscreenOptions:(NSDictionary*) fullscreenOptions +{ + _fullscreenOptions = fullscreenOptions; + + if (!_fullscreenOptions) return; + + BOOL fullscreenEnabled = [RCTConvert BOOL:[_fullscreenOptions objectForKey:@"enabled"]]; + + if( fullscreenEnabled && !_fullscreenPlayerPresented && _player ) + { + // 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 = fullscreenEnabled; + if(self.onVideoFullscreenPlayerDidPresent) { + self.onVideoFullscreenPlayerDidPresent(@{@"target": self.reactTag}); + } + }]; + } + } + else if ( !fullscreenEnabled && _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 @@ -1076,27 +1108,27 @@ static int const RCTVideoUnset = -1; - (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 { _progressUpdateInterval = progressUpdateInterval; - + if (_timeObserver) { [self removePlayerTimeObserver]; [self addPlayerTimeObserver]; @@ -1117,24 +1149,24 @@ static int const RCTVideoUnset = -1; - (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 @@ -1147,15 +1179,15 @@ static int const RCTVideoUnset = -1; { [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; } @@ -1164,7 +1196,7 @@ static int const RCTVideoUnset = -1; { if( _controls ) { - [subview removeFromSuperview]; + [subview removeFromSuperview]; } else { @@ -1179,7 +1211,7 @@ static int const RCTVideoUnset = -1; if( _controls ) { _playerViewController.view.frame = self.bounds; - + // also adjust all subviews of contentOverlayView for (UIView* subview in _playerViewController.contentOverlayView.subviews) { subview.frame = self.bounds; @@ -1187,10 +1219,10 @@ static int const RCTVideoUnset = -1; } else { - [CATransaction begin]; - [CATransaction setAnimationDuration:0]; - _playerLayer.frame = self.bounds; - [CATransaction commit]; + [CATransaction begin]; + [CATransaction setAnimationDuration:0]; + _playerLayer.frame = self.bounds; + [CATransaction commit]; } } @@ -1204,18 +1236,18 @@ static int const RCTVideoUnset = -1; _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 e0e0162e..9d902392 100644 --- a/ios/RCTVideoManager.m +++ b/ios/RCTVideoManager.m @@ -36,7 +36,8 @@ RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString); RCT_EXPORT_VIEW_PROPERTY(rate, float); RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(currentTime, float); -RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL); +RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL); // deprecated property +RCT_EXPORT_VIEW_PROPERTY(fullscreenOptions, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float); /* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */ RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock); diff --git a/ios/RCTVideoPlayerViewController.h b/ios/RCTVideoPlayerViewController.h index d427b63d..ed9ebdde 100644 --- a/ios/RCTVideoPlayerViewController.h +++ b/ios/RCTVideoPlayerViewController.h @@ -12,4 +12,9 @@ @interface RCTVideoPlayerViewController : AVPlayerViewController @property (nonatomic, weak) id rctDelegate; + +// Optional paramters +@property (nonatomic, weak) NSString* preferredOrientation; +@property (nonatomic) BOOL autorotate; + @end diff --git a/ios/RCTVideoPlayerViewController.m b/ios/RCTVideoPlayerViewController.m index 12480d54..b79a2de2 100644 --- a/ios/RCTVideoPlayerViewController.m +++ b/ios/RCTVideoPlayerViewController.m @@ -6,6 +6,14 @@ @implementation RCTVideoPlayerViewController +- (id)init { + self = [super init]; + if (self) { + self.autorotate = false; + } + return self; +} + - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; @@ -14,17 +22,25 @@ } - (BOOL)shouldAutorotate { - return YES; + return self.autorotate; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { - return UIInterfaceOrientationMaskLandscape; + return UIInterfaceOrientationMaskAll; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { - - return UIInterfaceOrientationLandscapeLeft; + if ([self.preferredOrientation.lowercaseString isEqualToString:@"landscape"]) { + return UIInterfaceOrientationLandscapeLeft; + } + else if ([self.preferredOrientation.lowercaseString isEqualToString:@"portrait"]) { + return UIInterfaceOrientationPortrait; + } + else { // default case + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + return orientation; + } } @end From 887010f291266d9e8f53ec3a525ee4a602d6ed27 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Tue, 14 Aug 2018 10:29:01 -0700 Subject: [PATCH 03/14] merge RCTVideo.m --- ios/RCTVideo.m | 352 ++++++++++++++++++++++++------------------------- 1 file changed, 176 insertions(+), 176 deletions(-) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 3adab702..c78fcf74 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -129,24 +129,24 @@ static int const RCTVideoUnset = -1; - (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); } -(void)addPlayerTimeObserver @@ -164,11 +164,11 @@ static int const RCTVideoUnset = -1; /* 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 @@ -211,43 +211,43 @@ static int const RCTVideoUnset = -1; - (void)audioRouteChanged:(NSNotification *)notification { - NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; - NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; - if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { - self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag}); - } + NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; + NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; + if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { + self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag}); + } } #pragma mark - Progress - (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], + }); + } } /*! @@ -277,12 +277,12 @@ static int const RCTVideoUnset = -1; - (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 @@ -346,13 +346,13 @@ static int const RCTVideoUnset = -1; 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 + }); } - + }); _videoLoadStarted = YES; } @@ -391,11 +391,11 @@ static int const RCTVideoUnset = -1; if (isNetwork) { /* Per #1091, this is not a public API. We need to either get approval from Apple to use this * or use a different approach. - NSDictionary *headers = [source objectForKey:@"requestHeaders"]; - if ([headers count] > 0) { - [assetOptions setObject:headers forKey:@"AVURLAssetHTTPHeaderFieldsKey"]; - } - */ + NSDictionary *headers = [source objectForKey:@"requestHeaders"]; + if ([headers count] > 0) { + [assetOptions setObject:headers forKey:@"AVURLAssetHTTPHeaderFieldsKey"]; + } + */ NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; [assetOptions setObject:cookies forKey:AVURLAssetHTTPCookiesKey]; asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:uri] options:assetOptions]; @@ -442,9 +442,9 @@ static int const RCTVideoUnset = -1; addMutableTrackWithMediaType:AVMediaTypeText preferredTrackID:kCMPersistentTrackID_Invalid]; [textCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) - ofTrack:textTrackAsset - atTime:kCMTimeZero - error:nil]; + ofTrack:textTrackAsset + atTime:kCMTimeZero + error:nil]; } if (validTextTracks.count != _textTracks.count) { [self setTextTracks:validTextTracks]; @@ -602,7 +602,7 @@ static int const RCTVideoUnset = -1; - (void)playerItemDidReachEnd:(NSNotification *)notification { if(self.onVideoEnd) { - self.onVideoEnd(@{@"target": self.reactTag}); + self.onVideoEnd(@{@"target": self.reactTag}); } if (_repeat) { @@ -636,8 +636,8 @@ static int const RCTVideoUnset = -1; - (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback { - _allowsExternalPlayback = allowsExternalPlayback; - _player.allowsExternalPlayback = _allowsExternalPlayback; + _allowsExternalPlayback = allowsExternalPlayback; + _player.allowsExternalPlayback = _allowsExternalPlayback; } - (void)setPlayWhenInactive:(BOOL)playWhenInactive @@ -770,52 +770,52 @@ static int const RCTVideoUnset = -1; - (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)characteristic withCriteria:(NSDictionary *)criteria { - NSString *type = criteria[@"type"]; - AVMediaSelectionGroup *group = [_player.currentItem.asset - mediaSelectionGroupForMediaCharacteristic:characteristic]; - AVMediaSelectionOption *mediaOption; - - if ([type isEqualToString:@"disabled"]) { - // Do nothing. We want to ensure option is nil - } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { - NSString *value = criteria[@"value"]; - for (int i = 0; i < group.options.count; ++i) { - AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; - NSString *optionValue; - if ([type isEqualToString:@"language"]) { - optionValue = [currentOption extendedLanguageTag]; - } else { - optionValue = [[[currentOption commonMetadata] - valueForKey:@"value"] - objectAtIndex:0]; - } - if ([value isEqualToString:optionValue]) { - mediaOption = currentOption; - break; - } + NSString *type = criteria[@"type"]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:characteristic]; + AVMediaSelectionOption *mediaOption; + + if ([type isEqualToString:@"disabled"]) { + // Do nothing. We want to ensure option is nil + } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { + NSString *value = criteria[@"value"]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *optionValue; + if ([type isEqualToString:@"language"]) { + optionValue = [currentOption extendedLanguageTag]; + } else { + optionValue = [[[currentOption commonMetadata] + valueForKey:@"value"] + objectAtIndex:0]; + } + if ([value isEqualToString:optionValue]) { + mediaOption = currentOption; + break; + } + } + //} else if ([type isEqualToString:@"default"]) { + // option = group.defaultOption; */ + } else if ([type isEqualToString:@"index"]) { + if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) { + int index = [criteria[@"value"] intValue]; + if (group.options.count > index) { + mediaOption = [group.options objectAtIndex:index]; + } + } + } else { // default. invalid type or "system" + [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; + return; } - //} else if ([type isEqualToString:@"default"]) { - // option = group.defaultOption; */ - } else if ([type isEqualToString:@"index"]) { - if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) { - int index = [criteria[@"value"] intValue]; - if (group.options.count > index) { - mediaOption = [group.options objectAtIndex:index]; - } - } - } else { // default. invalid type or "system" - [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; - return; - } - - // If a match isn't found, option will be nil and text tracks will be disabled - [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; + + // If a match isn't found, option will be nil and text tracks will be disabled + [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; } - (void)setSelectedAudioTrack:(NSDictionary *)selectedAudioTrack { - _selectedAudioTrack = selectedAudioTrack; - [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible - withCriteria:_selectedAudioTrack]; + _selectedAudioTrack = selectedAudioTrack; + [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible + withCriteria:_selectedAudioTrack]; } - (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack { @@ -950,25 +950,25 @@ static int const RCTVideoUnset = -1; - (NSArray *)getAudioTrackInfo { - NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; - AVMediaSelectionGroup *group = [_player.currentItem.asset - mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - for (int i = 0; i < group.options.count; ++i) { - AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; - NSString *title = @""; - NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"]; - if (values.count > 0) { - title = [values objectAtIndex:0]; + NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *title = @""; + NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"]; + if (values.count > 0) { + title = [values objectAtIndex:0]; + } + NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @""; + NSDictionary *audioTrack = @{ + @"index": [NSNumber numberWithInt:i], + @"title": title, + @"language": language + }; + [audioTracks addObject:audioTrack]; } - NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @""; - NSDictionary *audioTrack = @{ - @"index": [NSNumber numberWithInt:i], - @"title": title, - @"language": language - }; - [audioTracks addObject:audioTrack]; - } - return audioTracks; + return audioTracks; } - (NSArray *)getTextTrackInfo @@ -1000,7 +1000,7 @@ static int const RCTVideoUnset = -1; - (BOOL)getFullscreen { - return _fullscreenPlayerPresented; + return _fullscreenPlayerPresented; } - (void)setFullscreen:(BOOL) fullscreen { @@ -1077,14 +1077,14 @@ static int const RCTVideoUnset = -1; - (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 @@ -1108,21 +1108,21 @@ static int const RCTVideoUnset = -1; - (void)setControls:(BOOL)controls { - if( _controls != controls || (!_playerLayer && !_playerViewController) ) - { - _controls = controls; - if( _controls ) + if( _controls != controls || (!_playerLayer && !_playerViewController) ) { - [self removePlayerLayer]; - [self usePlayerViewController]; + _controls = controls; + if( _controls ) + { + [self removePlayerLayer]; + [self usePlayerViewController]; + } + else + { + [_playerViewController.view removeFromSuperview]; + _playerViewController = nil; + [self usePlayerLayer]; + } } - else - { - [_playerViewController.view removeFromSuperview]; - _playerViewController = nil; - [self usePlayerLayer]; - } - } } - (void)setProgressUpdateInterval:(float)progressUpdateInterval @@ -1149,24 +1149,24 @@ static int const RCTVideoUnset = -1; - (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 @@ -1182,12 +1182,12 @@ static int const RCTVideoUnset = -1; 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; } @@ -1196,7 +1196,7 @@ static int const RCTVideoUnset = -1; { if( _controls ) { - [subview removeFromSuperview]; + [subview removeFromSuperview]; } else { @@ -1219,10 +1219,10 @@ static int const RCTVideoUnset = -1; } else { - [CATransaction begin]; - [CATransaction setAnimationDuration:0]; - _playerLayer.frame = self.bounds; - [CATransaction commit]; + [CATransaction begin]; + [CATransaction setAnimationDuration:0]; + _playerLayer.frame = self.bounds; + [CATransaction commit]; } } From f7fd5dc5b70c5394cd1ab826c29083d26f9555eb Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Thu, 16 Aug 2018 15:32:36 -0700 Subject: [PATCH 04/14] Fix for files that are stored in the Documents folder on iOS (and not as resources in the JS app) --- ios/RCTVideo.m | 364 ++++++++++++++++++++++++------------------------- 1 file changed, 182 insertions(+), 182 deletions(-) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index c78fcf74..455856c5 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -129,24 +129,24 @@ static int const RCTVideoUnset = -1; - (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); } -(void)addPlayerTimeObserver @@ -164,11 +164,11 @@ static int const RCTVideoUnset = -1; /* 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 @@ -211,43 +211,43 @@ static int const RCTVideoUnset = -1; - (void)audioRouteChanged:(NSNotification *)notification { - NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; - NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; - if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { - self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag}); - } + NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; + NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; + if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { + self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag}); + } } #pragma mark - Progress - (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], + }); + } } /*! @@ -277,12 +277,12 @@ static int const RCTVideoUnset = -1; - (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 @@ -346,19 +346,19 @@ static int const RCTVideoUnset = -1; 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 + }); } - + }); _videoLoadStarted = YES; } - (NSURL*) urlFilePath:(NSString*) filepath { - if ([filepath containsString:@"file://"]) { + if ([filepath containsString:@"file://"] && ![filepath containsString:@"/Documents/"]) { return [NSURL URLWithString:filepath]; } @@ -391,11 +391,11 @@ static int const RCTVideoUnset = -1; if (isNetwork) { /* Per #1091, this is not a public API. We need to either get approval from Apple to use this * or use a different approach. - NSDictionary *headers = [source objectForKey:@"requestHeaders"]; - if ([headers count] > 0) { - [assetOptions setObject:headers forKey:@"AVURLAssetHTTPHeaderFieldsKey"]; - } - */ + NSDictionary *headers = [source objectForKey:@"requestHeaders"]; + if ([headers count] > 0) { + [assetOptions setObject:headers forKey:@"AVURLAssetHTTPHeaderFieldsKey"]; + } + */ NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; [assetOptions setObject:cookies forKey:AVURLAssetHTTPCookiesKey]; asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:uri] options:assetOptions]; @@ -442,9 +442,9 @@ static int const RCTVideoUnset = -1; addMutableTrackWithMediaType:AVMediaTypeText preferredTrackID:kCMPersistentTrackID_Invalid]; [textCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) - ofTrack:textTrackAsset - atTime:kCMTimeZero - error:nil]; + ofTrack:textTrackAsset + atTime:kCMTimeZero + error:nil]; } if (validTextTracks.count != _textTracks.count) { [self setTextTracks:validTextTracks]; @@ -602,7 +602,7 @@ static int const RCTVideoUnset = -1; - (void)playerItemDidReachEnd:(NSNotification *)notification { if(self.onVideoEnd) { - self.onVideoEnd(@{@"target": self.reactTag}); + self.onVideoEnd(@{@"target": self.reactTag}); } if (_repeat) { @@ -636,8 +636,8 @@ static int const RCTVideoUnset = -1; - (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback { - _allowsExternalPlayback = allowsExternalPlayback; - _player.allowsExternalPlayback = _allowsExternalPlayback; + _allowsExternalPlayback = allowsExternalPlayback; + _player.allowsExternalPlayback = _allowsExternalPlayback; } - (void)setPlayWhenInactive:(BOOL)playWhenInactive @@ -770,52 +770,52 @@ static int const RCTVideoUnset = -1; - (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)characteristic withCriteria:(NSDictionary *)criteria { - NSString *type = criteria[@"type"]; - AVMediaSelectionGroup *group = [_player.currentItem.asset - mediaSelectionGroupForMediaCharacteristic:characteristic]; - AVMediaSelectionOption *mediaOption; - - if ([type isEqualToString:@"disabled"]) { - // Do nothing. We want to ensure option is nil - } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { - NSString *value = criteria[@"value"]; - for (int i = 0; i < group.options.count; ++i) { - AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; - NSString *optionValue; - if ([type isEqualToString:@"language"]) { - optionValue = [currentOption extendedLanguageTag]; - } else { - optionValue = [[[currentOption commonMetadata] - valueForKey:@"value"] - objectAtIndex:0]; - } - if ([value isEqualToString:optionValue]) { - mediaOption = currentOption; - break; - } - } - //} else if ([type isEqualToString:@"default"]) { - // option = group.defaultOption; */ - } else if ([type isEqualToString:@"index"]) { - if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) { - int index = [criteria[@"value"] intValue]; - if (group.options.count > index) { - mediaOption = [group.options objectAtIndex:index]; - } - } - } else { // default. invalid type or "system" - [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; - return; + NSString *type = criteria[@"type"]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:characteristic]; + AVMediaSelectionOption *mediaOption; + + if ([type isEqualToString:@"disabled"]) { + // Do nothing. We want to ensure option is nil + } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { + NSString *value = criteria[@"value"]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *optionValue; + if ([type isEqualToString:@"language"]) { + optionValue = [currentOption extendedLanguageTag]; + } else { + optionValue = [[[currentOption commonMetadata] + valueForKey:@"value"] + objectAtIndex:0]; + } + if ([value isEqualToString:optionValue]) { + mediaOption = currentOption; + break; + } } - - // If a match isn't found, option will be nil and text tracks will be disabled - [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; + //} else if ([type isEqualToString:@"default"]) { + // option = group.defaultOption; */ + } else if ([type isEqualToString:@"index"]) { + if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) { + int index = [criteria[@"value"] intValue]; + if (group.options.count > index) { + mediaOption = [group.options objectAtIndex:index]; + } + } + } else { // default. invalid type or "system" + [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; + return; + } + + // If a match isn't found, option will be nil and text tracks will be disabled + [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; } - (void)setSelectedAudioTrack:(NSDictionary *)selectedAudioTrack { - _selectedAudioTrack = selectedAudioTrack; - [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible - withCriteria:_selectedAudioTrack]; + _selectedAudioTrack = selectedAudioTrack; + [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible + withCriteria:_selectedAudioTrack]; } - (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack { @@ -950,25 +950,25 @@ static int const RCTVideoUnset = -1; - (NSArray *)getAudioTrackInfo { - NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; - AVMediaSelectionGroup *group = [_player.currentItem.asset - mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - for (int i = 0; i < group.options.count; ++i) { - AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; - NSString *title = @""; - NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"]; - if (values.count > 0) { - title = [values objectAtIndex:0]; - } - NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @""; - NSDictionary *audioTrack = @{ - @"index": [NSNumber numberWithInt:i], - @"title": title, - @"language": language - }; - [audioTracks addObject:audioTrack]; + NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *title = @""; + NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"]; + if (values.count > 0) { + title = [values objectAtIndex:0]; } - return audioTracks; + NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @""; + NSDictionary *audioTrack = @{ + @"index": [NSNumber numberWithInt:i], + @"title": title, + @"language": language + }; + [audioTracks addObject:audioTrack]; + } + return audioTracks; } - (NSArray *)getTextTrackInfo @@ -1000,7 +1000,7 @@ static int const RCTVideoUnset = -1; - (BOOL)getFullscreen { - return _fullscreenPlayerPresented; + return _fullscreenPlayerPresented; } - (void)setFullscreen:(BOOL) fullscreen { @@ -1010,11 +1010,11 @@ static int const RCTVideoUnset = -1; if (!_fullscreenOptions) { [self setFullscreenOptions: - @{ - @"enabled": enabled, - @"autorotate": @"0", - @"preferredOrientation": @"default" - }]; + @{ + @"enabled": enabled, + @"autorotate": @"0", + @"preferredOrientation": @"default" + }]; } else { [_fullscreenOptions setValue:enabled forKey:@"enabled"]; @@ -1077,14 +1077,14 @@ static int const RCTVideoUnset = -1; - (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 @@ -1108,21 +1108,21 @@ static int const RCTVideoUnset = -1; - (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 @@ -1149,24 +1149,24 @@ static int const RCTVideoUnset = -1; - (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 @@ -1182,12 +1182,12 @@ static int const RCTVideoUnset = -1; 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; } @@ -1196,7 +1196,7 @@ static int const RCTVideoUnset = -1; { if( _controls ) { - [subview removeFromSuperview]; + [subview removeFromSuperview]; } else { @@ -1219,10 +1219,10 @@ static int const RCTVideoUnset = -1; } else { - [CATransaction begin]; - [CATransaction setAnimationDuration:0]; - _playerLayer.frame = self.bounds; - [CATransaction commit]; + [CATransaction begin]; + [CATransaction setAnimationDuration:0]; + _playerLayer.frame = self.bounds; + [CATransaction commit]; } } From 7d805f13633f5a93d343ba6930ee99994423f57a Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Mon, 20 Aug 2018 11:52:06 -0700 Subject: [PATCH 05/14] Improvements to urlFilePath so that it only does a /Documents/ folder check if the file does not exist; improvements to full-screen options for iOS to default autorotate, and to respect landscape / portrait masks --- ios/RCTVideo.m | 10 ++++++---- ios/RCTVideoPlayerViewController.m | 10 ++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 455856c5..8e810b6b 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -358,15 +358,17 @@ static int const RCTVideoUnset = -1; } - (NSURL*) urlFilePath:(NSString*) filepath { - if ([filepath containsString:@"file://"] && ![filepath containsString:@"/Documents/"]) { - return [NSURL URLWithString:filepath]; + + // check if the file exists at the specified location + if ([[NSFileManager defaultManager] fileExistsAtPath:filepath]) { + return [NSURL fileURLWithPath:filepath]; } - // code to support local caching + // if no file found, check if the file exists in the Document directory NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString* relativeFilePath = [filepath lastPathComponent]; // the file may be multiple levels below the documents directory - NSArray* fileComponents = [filepath componentsSeparatedByString:@"Documents/"]; + NSArray* fileComponents = [filepath componentsSeparatedByString:@"/Documents/"]; if (fileComponents.count>1) { relativeFilePath = [fileComponents objectAtIndex:1]; } diff --git a/ios/RCTVideoPlayerViewController.m b/ios/RCTVideoPlayerViewController.m index b79a2de2..fe8cb171 100644 --- a/ios/RCTVideoPlayerViewController.m +++ b/ios/RCTVideoPlayerViewController.m @@ -9,7 +9,7 @@ - (id)init { self = [super init]; if (self) { - self.autorotate = false; + self.autorotate = true; // autorotate should be true by default } return self; } @@ -27,12 +27,18 @@ - (UIInterfaceOrientationMask)supportedInterfaceOrientations { + if ([self.preferredOrientation.lowercaseString isEqualToString:@"landscape"]) { + return UIInterfaceOrientationMaskLandscape; + } + else if ([self.preferredOrientation.lowercaseString isEqualToString:@"portrait"]) { + return UIInterfaceOrientationMaskPortrait; + } return UIInterfaceOrientationMaskAll; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { if ([self.preferredOrientation.lowercaseString isEqualToString:@"landscape"]) { - return UIInterfaceOrientationLandscapeLeft; + return UIInterfaceOrientationLandscapeRight; } else if ([self.preferredOrientation.lowercaseString isEqualToString:@"portrait"]) { return UIInterfaceOrientationPortrait; From 7b8f79b36a6eda1452b3cab1f35165f2ebcf9769 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Mon, 27 Aug 2018 10:42:49 -0700 Subject: [PATCH 06/14] added an onCaptionsDeviceSettings event --- Video.js | 8 ++++++++ ios/RCTVideo.h | 1 + ios/RCTVideo.m | 15 ++++++++++++--- ios/RCTVideoManager.m | 1 + 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Video.js b/Video.js index bf64e9d5..58f57922 100644 --- a/Video.js +++ b/Video.js @@ -151,6 +151,12 @@ export default class Video extends Component { } }; + _onCaptionsDeviceSetting = (event) => { + if (this.props.onCaptionsDeviceSetting) { + this.props.onCaptionsDeviceSetting(event.nativeEvent); + } + }; + _onPlaybackStalled = (event) => { if (this.props.onPlaybackStalled) { this.props.onPlaybackStalled(event.nativeEvent); @@ -241,6 +247,7 @@ export default class Video extends Component { onVideoFullscreenPlayerWillDismiss: this._onFullscreenPlayerWillDismiss, onVideoFullscreenPlayerDidDismiss: this._onFullscreenPlayerDidDismiss, onReadyForDisplay: this._onReadyForDisplay, + onCaptionsDeviceSetting: this._onCaptionsDeviceSetting, onPlaybackStalled: this._onPlaybackStalled, onPlaybackResume: this._onPlaybackResume, onPlaybackRateChange: this._onPlaybackRateChange, @@ -379,6 +386,7 @@ Video.propTypes = { onFullscreenPlayerWillDismiss: PropTypes.func, onFullscreenPlayerDidDismiss: PropTypes.func, onReadyForDisplay: PropTypes.func, + onCaptionsDeviceSetting: PropTypes.func, onPlaybackStalled: PropTypes.func, onPlaybackResume: PropTypes.func, onPlaybackRateChange: PropTypes.func, diff --git a/ios/RCTVideo.h b/ios/RCTVideo.h index 1c471236..a556b3c3 100644 --- a/ios/RCTVideo.h +++ b/ios/RCTVideo.h @@ -23,6 +23,7 @@ @property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillDismiss; @property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidDismiss; @property (nonatomic, copy) RCTBubblingEventBlock onReadyForDisplay; +@property (nonatomic, copy) RCTBubblingEventBlock onCaptionsDeviceSetting; @property (nonatomic, copy) RCTBubblingEventBlock onPlaybackStalled; @property (nonatomic, copy) RCTBubblingEventBlock onPlaybackResume; @property (nonatomic, copy) RCTBubblingEventBlock onPlaybackRateChange; diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 8e810b6b..f5e7f4e8 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -59,6 +59,7 @@ static int const RCTVideoUnset = -1; NSDictionary* _fullscreenOptions; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; + BOOL _deviceCaptionsEnabled; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -316,6 +317,10 @@ static int const RCTVideoUnset = -1; [self removePlayerTimeObserver]; [self removePlayerItemObservers]; + CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser); + NSArray *captionSettings = (__bridge NSArray*)captioningMediaCharacteristics; + _deviceCaptionsEnabled = [captionSettings containsObject:AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // perform on next run loop, otherwise other passed react-props may not be set @@ -353,6 +358,12 @@ static int const RCTVideoUnset = -1; }); } + if (self.onCaptionsDeviceSetting) { + self.onCaptionsDeviceSetting(@{@"deviceCaptionsEnabled": [NSNumber numberWithBool:_deviceCaptionsEnabled], + @"target": self.reactTag + }); + } + }); _videoLoadStarted = YES; } @@ -875,9 +886,7 @@ static int const RCTVideoUnset = -1; // in the situation that a selected text track is not available (eg. specifies a textTrack not available) if (![type isEqualToString:@"disabled"] && selectedTrackIndex == RCTVideoUnset) { - CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser); - NSArray *captionSettings = (__bridge NSArray*)captioningMediaCharacteristics; - if ([captionSettings containsObject:AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]) { + if (_deviceCaptionsEnabled) { selectedTrackIndex = 0; // If we can't find a match, use the first available track NSString *systemLanguage = [[NSLocale preferredLanguages] firstObject]; for (int i = 0; i < textTracks.count; ++i) { diff --git a/ios/RCTVideoManager.m b/ios/RCTVideoManager.m index 9d902392..683238a1 100644 --- a/ios/RCTVideoManager.m +++ b/ios/RCTVideoManager.m @@ -54,6 +54,7 @@ RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTBubblingEventBloc RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidDismiss, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onCaptionsDeviceSetting, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTBubblingEventBlock); From 3ba26eb45a7b6602c5051e2a20e272afcad25852 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Thu, 2 Aug 2018 10:32:50 -0700 Subject: [PATCH 07/14] Working on autorotation of video player --- ios/Video/RCTVideo.m | 9 +++++---- ios/Video/RCTVideoPlayerViewController.m | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index 9fe30b63..3b04e4e5 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -61,6 +61,7 @@ static int const RCTVideoUnset = -1; BOOL _playWhenInactive; NSString * _ignoreSilentSwitch; NSString * _resizeMode; + BOOL _fullscreen; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; #if __has_include() @@ -341,6 +342,8 @@ static int const RCTVideoUnset = -1; _playbackRateObserverRegistered = YES; [self addPlayerTimeObserver]; + + [self setFullscreen:_fullscreen]; //Perform on next run loop, otherwise onVideoLoadStart is nil if (self.onVideoLoadStart) { @@ -1085,10 +1088,8 @@ static int const RCTVideoUnset = -1; - (void)setFullscreen:(BOOL)fullscreen { - if( fullscreen && !_fullscreenPlayerPresented ) - { - // Ensure player view controller is not null - if( !_playerViewController ) + _fullscreen = fullscreen; + if( fullscreen && !_fullscreenPlayerPresented && _player ) { [self usePlayerViewController]; } diff --git a/ios/Video/RCTVideoPlayerViewController.m b/ios/Video/RCTVideoPlayerViewController.m index 7809221a..12480d54 100644 --- a/ios/Video/RCTVideoPlayerViewController.m +++ b/ios/Video/RCTVideoPlayerViewController.m @@ -13,4 +13,18 @@ [_rctDelegate videoPlayerViewControllerDidDismiss:self]; } +- (BOOL)shouldAutorotate { + return YES; +} + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + + return UIInterfaceOrientationMaskLandscape; +} + +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { + + return UIInterfaceOrientationLandscapeLeft; +} + @end From f45d6a2c3e63e9904f9472325ba2dcd960504a88 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Thu, 9 Aug 2018 09:58:03 -0700 Subject: [PATCH 08/14] Added fullscreen options for iOS Player --- Video.js | 5 + ios/Video/RCTVideo.m | 219 +++++++++++++---------- ios/Video/RCTVideoManager.m | 3 +- ios/Video/RCTVideoPlayerViewController.h | 5 + ios/Video/RCTVideoPlayerViewController.m | 24 ++- 5 files changed, 159 insertions(+), 97 deletions(-) diff --git a/Video.js b/Video.js index e8203b8b..58a7b9c1 100644 --- a/Video.js +++ b/Video.js @@ -289,6 +289,11 @@ Video.propTypes = { PropTypes.object ]), fullscreen: PropTypes.bool, + fullscreenOptions: PropTypes.shape({ + enabled: PropTypes.bool, + preferredOrientation: PropTypes.string, + autorotate: PropTypes.bool + }), onVideoLoadStart: PropTypes.func, onVideoLoad: PropTypes.func, onVideoBuffer: PropTypes.func, diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index 3b04e4e5..a4f11492 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -36,7 +36,7 @@ static int const RCTVideoUnset = -1; RCTEventDispatcher *_eventDispatcher; BOOL _playbackRateObserverRegistered; BOOL _videoLoadStarted; - + bool _pendingSeek; float _pendingSeekTime; float _lastSeekTime; @@ -62,6 +62,7 @@ static int const RCTVideoUnset = -1; NSString * _ignoreSilentSwitch; NSString * _resizeMode; BOOL _fullscreen; + NSDictionary* _fullscreenOptions; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; #if __has_include() @@ -106,7 +107,7 @@ static int const RCTVideoUnset = -1; selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChanged:) name:AVAudioSessionRouteChangeNotification @@ -117,13 +118,20 @@ static int const RCTVideoUnset = -1; } - (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem { - RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init]; - playerLayer.showsPlaybackControls = YES; - 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 = YES; + playerLayer.rctDelegate = self; + + if (_fullscreenOptions) { + playerLayer.preferredOrientation = [RCTConvert NSString:[_fullscreenOptions objectForKey:@"preferredOrientation"]]; + playerLayer.autorotate = [RCTConvert BOOL:[_fullscreenOptions objectForKey:@"autorotate"]]; + } + + playerLayer.view.frame = self.bounds; + playerLayer.player = player; + playerLayer.view.frame = self.bounds; + return playerLayer; } /* --------------------------------------------------------- @@ -214,11 +222,11 @@ static int const RCTVideoUnset = -1; - (void)audioRouteChanged:(NSNotification *)notification { - NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; - NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; - if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { - self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag}); - } + NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; + NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; + if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { + self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag}); + } } #pragma mark - Progress @@ -343,7 +351,7 @@ static int const RCTVideoUnset = -1; [self addPlayerTimeObserver]; - [self setFullscreen:_fullscreen]; + [self setFullscreenOptions:_fullscreenOptions]; //Perform on next run loop, otherwise onVideoLoadStart is nil if (self.onVideoLoadStart) { @@ -398,14 +406,14 @@ static int const RCTVideoUnset = -1; ofTrack:videoAsset atTime:kCMTimeZero error:nil]; - + AVAssetTrack *audioAsset = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject; AVMutableCompositionTrack *audioCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; [audioCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) ofTrack:audioAsset atTime:kCMTimeZero error:nil]; - + NSMutableArray* validTextTracks = [NSMutableArray array]; for (int i = 0; i < _textTracks.count; ++i) { AVURLAsset *textURLAsset; @@ -422,9 +430,9 @@ static int const RCTVideoUnset = -1; addMutableTrackWithMediaType:AVMediaTypeText preferredTrackID:kCMPersistentTrackID_Invalid]; [textCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration) - ofTrack:textTrackAsset - atTime:kCMTimeZero - error:nil]; + ofTrack:textTrackAsset + atTime:kCMTimeZero + error:nil]; } if (validTextTracks.count != _textTracks.count) { [self setTextTracks:validTextTracks]; @@ -664,7 +672,7 @@ static int const RCTVideoUnset = -1; selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:[_player currentItem]]; - + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemPlaybackStalledNotification object:nil]; @@ -719,8 +727,8 @@ static int const RCTVideoUnset = -1; - (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback { - _allowsExternalPlayback = allowsExternalPlayback; - _player.allowsExternalPlayback = _allowsExternalPlayback; + _allowsExternalPlayback = allowsExternalPlayback; + _player.allowsExternalPlayback = _allowsExternalPlayback; } - (void)setPlayWhenInactive:(BOOL)playWhenInactive @@ -836,7 +844,7 @@ static int const RCTVideoUnset = -1; [_player setVolume:_volume]; [_player setMuted:NO]; } - + [self setSelectedAudioTrack:_selectedAudioTrack]; [self setSelectedTextTrack:_selectedTextTrack]; [self setResizeMode:_resizeMode]; @@ -853,52 +861,52 @@ static int const RCTVideoUnset = -1; - (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)characteristic withCriteria:(NSDictionary *)criteria { - NSString *type = criteria[@"type"]; - AVMediaSelectionGroup *group = [_player.currentItem.asset - mediaSelectionGroupForMediaCharacteristic:characteristic]; - AVMediaSelectionOption *mediaOption; - - if ([type isEqualToString:@"disabled"]) { - // Do nothing. We want to ensure option is nil - } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { - NSString *value = criteria[@"value"]; - for (int i = 0; i < group.options.count; ++i) { - AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; - NSString *optionValue; - if ([type isEqualToString:@"language"]) { - optionValue = [currentOption extendedLanguageTag]; - } else { - optionValue = [[[currentOption commonMetadata] - valueForKey:@"value"] - objectAtIndex:0]; - } - if ([value isEqualToString:optionValue]) { - mediaOption = currentOption; - break; - } - } - //} else if ([type isEqualToString:@"default"]) { - // option = group.defaultOption; */ - } else if ([type isEqualToString:@"index"]) { - if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) { - int index = [criteria[@"value"] intValue]; - if (group.options.count > index) { - mediaOption = [group.options objectAtIndex:index]; - } - } - } else { // default. invalid type or "system" - [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; - return; + NSString *type = criteria[@"type"]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:characteristic]; + AVMediaSelectionOption *mediaOption; + + if ([type isEqualToString:@"disabled"]) { + // Do nothing. We want to ensure option is nil + } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { + NSString *value = criteria[@"value"]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *optionValue; + if ([type isEqualToString:@"language"]) { + optionValue = [currentOption extendedLanguageTag]; + } else { + optionValue = [[[currentOption commonMetadata] + valueForKey:@"value"] + objectAtIndex:0]; + } + if ([value isEqualToString:optionValue]) { + mediaOption = currentOption; + break; + } } - - // If a match isn't found, option will be nil and text tracks will be disabled - [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; + //} else if ([type isEqualToString:@"default"]) { + // option = group.defaultOption; */ + } else if ([type isEqualToString:@"index"]) { + if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) { + int index = [criteria[@"value"] intValue]; + if (group.options.count > index) { + mediaOption = [group.options objectAtIndex:index]; + } + } + } else { // default. invalid type or "system" + [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; + return; + } + + // If a match isn't found, option will be nil and text tracks will be disabled + [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; } - (void)setSelectedAudioTrack:(NSDictionary *)selectedAudioTrack { - _selectedAudioTrack = selectedAudioTrack; - [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible - withCriteria:_selectedAudioTrack]; + _selectedAudioTrack = selectedAudioTrack; + [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible + withCriteria:_selectedAudioTrack]; } - (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack { @@ -970,7 +978,7 @@ static int const RCTVideoUnset = -1; } } } - + for (int i = firstTextIndex; i < _player.currentItem.tracks.count; ++i) { BOOL isEnabled = NO; if (selectedTrackIndex != RCTVideoUnset) { @@ -1026,32 +1034,32 @@ static int const RCTVideoUnset = -1; - (void)setTextTracks:(NSArray*) textTracks; { _textTracks = textTracks; - + // in case textTracks was set after selectedTextTrack if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack]; } - (NSArray *)getAudioTrackInfo { - NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; - AVMediaSelectionGroup *group = [_player.currentItem.asset - mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - for (int i = 0; i < group.options.count; ++i) { - AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; - NSString *title = @""; - NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"]; - if (values.count > 0) { - title = [values objectAtIndex:0]; - } - NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @""; - NSDictionary *audioTrack = @{ - @"index": [NSNumber numberWithInt:i], - @"title": title, - @"language": language - }; - [audioTracks addObject:audioTrack]; + NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *title = @""; + NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"]; + if (values.count > 0) { + title = [values objectAtIndex:0]; } - return audioTracks; + NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @""; + NSDictionary *audioTrack = @{ + @"index": [NSNumber numberWithInt:i], + @"title": title, + @"language": language + }; + [audioTracks addObject:audioTrack]; + } + return audioTracks; } - (NSArray *)getTextTrackInfo @@ -1086,10 +1094,37 @@ static int const RCTVideoUnset = -1; return _fullscreenPlayerPresented; } -- (void)setFullscreen:(BOOL)fullscreen -{ +- (void)setFullscreen:(BOOL) fullscreen { _fullscreen = fullscreen; - if( fullscreen && !_fullscreenPlayerPresented && _player ) + + NSString* enabled = _fullscreen ? @"1" : @"0"; + + if (!_fullscreenOptions) { + [self setFullscreenOptions: + @{ + @"enabled": enabled, + @"autorotate": @"0", + @"preferredOrientation": @"default" + }]; + } + else { + [_fullscreenOptions setValue:enabled forKey:@"enabled"]; + [self setFullscreenOptions:_fullscreenOptions]; + } +} + +- (void)setFullscreenOptions:(NSDictionary*) fullscreenOptions +{ + _fullscreenOptions = fullscreenOptions; + + if (!_fullscreenOptions) return; + + BOOL fullscreenEnabled = [RCTConvert BOOL:[_fullscreenOptions objectForKey:@"enabled"]]; + + if( fullscreenEnabled && !_fullscreenPlayerPresented && _player ) + { + // Ensure player view controller is not null + if( !_playerViewController ) { [self usePlayerViewController]; } @@ -1115,14 +1150,14 @@ static int const RCTVideoUnset = -1; } [viewController presentViewController:_playerViewController animated:true completion:^{ _playerViewController.showsPlaybackControls = YES; - _fullscreenPlayerPresented = fullscreen; + _fullscreenPlayerPresented = fullscreenEnabled; if(self.onVideoFullscreenPlayerDidPresent) { self.onVideoFullscreenPlayerDidPresent(@{@"target": self.reactTag}); } }]; } } - else if ( !fullscreen && _fullscreenPlayerPresented ) + else if ( !fullscreenEnabled && _fullscreenPlayerPresented ) { [self videoPlayerViewControllerWillDismiss:_playerViewController]; [_presentingViewController dismissViewControllerAnimated:true completion:^{ @@ -1184,7 +1219,7 @@ static int const RCTVideoUnset = -1; - (void)setProgressUpdateInterval:(float)progressUpdateInterval { _progressUpdateInterval = progressUpdateInterval; - + if (_timeObserver) { [self removePlayerTimeObserver]; [self addPlayerTimeObserver]; diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index e0e0162e..9d902392 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -36,7 +36,8 @@ RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString); RCT_EXPORT_VIEW_PROPERTY(rate, float); RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(currentTime, float); -RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL); +RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL); // deprecated property +RCT_EXPORT_VIEW_PROPERTY(fullscreenOptions, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float); /* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */ RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock); diff --git a/ios/Video/RCTVideoPlayerViewController.h b/ios/Video/RCTVideoPlayerViewController.h index d427b63d..ed9ebdde 100644 --- a/ios/Video/RCTVideoPlayerViewController.h +++ b/ios/Video/RCTVideoPlayerViewController.h @@ -12,4 +12,9 @@ @interface RCTVideoPlayerViewController : AVPlayerViewController @property (nonatomic, weak) id rctDelegate; + +// Optional paramters +@property (nonatomic, weak) NSString* preferredOrientation; +@property (nonatomic) BOOL autorotate; + @end diff --git a/ios/Video/RCTVideoPlayerViewController.m b/ios/Video/RCTVideoPlayerViewController.m index 12480d54..b79a2de2 100644 --- a/ios/Video/RCTVideoPlayerViewController.m +++ b/ios/Video/RCTVideoPlayerViewController.m @@ -6,6 +6,14 @@ @implementation RCTVideoPlayerViewController +- (id)init { + self = [super init]; + if (self) { + self.autorotate = false; + } + return self; +} + - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; @@ -14,17 +22,25 @@ } - (BOOL)shouldAutorotate { - return YES; + return self.autorotate; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { - return UIInterfaceOrientationMaskLandscape; + return UIInterfaceOrientationMaskAll; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { - - return UIInterfaceOrientationLandscapeLeft; + if ([self.preferredOrientation.lowercaseString isEqualToString:@"landscape"]) { + return UIInterfaceOrientationLandscapeLeft; + } + else if ([self.preferredOrientation.lowercaseString isEqualToString:@"portrait"]) { + return UIInterfaceOrientationPortrait; + } + else { // default case + UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; + return orientation; + } } @end From 3b0d4592303e41cf4ba0ed69c450970755e91f90 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Thu, 16 Aug 2018 15:32:36 -0700 Subject: [PATCH 09/14] Fix for files that are stored in the Documents folder on iOS (and not as resources in the JS app) --- ios/Video/RCTVideo.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index a4f11492..8732f768 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -370,7 +370,7 @@ static int const RCTVideoUnset = -1; } - (NSURL*) urlFilePath:(NSString*) filepath { - if ([filepath containsString:@"file://"]) { + if ([filepath containsString:@"file://"] && ![filepath containsString:@"/Documents/"]) { return [NSURL URLWithString:filepath]; } @@ -1101,11 +1101,11 @@ static int const RCTVideoUnset = -1; if (!_fullscreenOptions) { [self setFullscreenOptions: - @{ - @"enabled": enabled, - @"autorotate": @"0", - @"preferredOrientation": @"default" - }]; + @{ + @"enabled": enabled, + @"autorotate": @"0", + @"preferredOrientation": @"default" + }]; } else { [_fullscreenOptions setValue:enabled forKey:@"enabled"]; From 37c31a3c92a6d0e884e0cf5732faeea56d115cc4 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Mon, 20 Aug 2018 11:52:06 -0700 Subject: [PATCH 10/14] Improvements to urlFilePath so that it only does a /Documents/ folder check if the file does not exist; improvements to full-screen options for iOS to default autorotate, and to respect landscape / portrait masks --- ios/Video/RCTVideo.m | 12 +++++++----- ios/Video/RCTVideoPlayerViewController.m | 10 ++++++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index 8732f768..da1cbe31 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -370,16 +370,18 @@ static int const RCTVideoUnset = -1; } - (NSURL*) urlFilePath:(NSString*) filepath { - if ([filepath containsString:@"file://"] && ![filepath containsString:@"/Documents/"]) { - return [NSURL URLWithString:filepath]; + + // check if the file exists at the specified location + if ([[NSFileManager defaultManager] fileExistsAtPath:filepath]) { + return [NSURL fileURLWithPath:filepath]; } - // code to support local caching + // if no file found, check if the file exists in the Document directory NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString* relativeFilePath = [filepath lastPathComponent]; // the file may be multiple levels below the documents directory - NSArray* fileComponents = [filepath componentsSeparatedByString:@"Documents/"]; - if (fileComponents.count > 1) { + NSArray* fileComponents = [filepath componentsSeparatedByString:@"/Documents/"]; + if (fileComponents.count>1) { relativeFilePath = [fileComponents objectAtIndex:1]; } diff --git a/ios/Video/RCTVideoPlayerViewController.m b/ios/Video/RCTVideoPlayerViewController.m index b79a2de2..fe8cb171 100644 --- a/ios/Video/RCTVideoPlayerViewController.m +++ b/ios/Video/RCTVideoPlayerViewController.m @@ -9,7 +9,7 @@ - (id)init { self = [super init]; if (self) { - self.autorotate = false; + self.autorotate = true; // autorotate should be true by default } return self; } @@ -27,12 +27,18 @@ - (UIInterfaceOrientationMask)supportedInterfaceOrientations { + if ([self.preferredOrientation.lowercaseString isEqualToString:@"landscape"]) { + return UIInterfaceOrientationMaskLandscape; + } + else if ([self.preferredOrientation.lowercaseString isEqualToString:@"portrait"]) { + return UIInterfaceOrientationMaskPortrait; + } return UIInterfaceOrientationMaskAll; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { if ([self.preferredOrientation.lowercaseString isEqualToString:@"landscape"]) { - return UIInterfaceOrientationLandscapeLeft; + return UIInterfaceOrientationLandscapeRight; } else if ([self.preferredOrientation.lowercaseString isEqualToString:@"portrait"]) { return UIInterfaceOrientationPortrait; From 77c48c9dfc3d3718f9319705c51bdebf86948af9 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Mon, 27 Aug 2018 10:42:49 -0700 Subject: [PATCH 11/14] added an onCaptionsDeviceSettings event --- Video.js | 8 ++++++++ ios/Video/RCTVideo.h | 1 + ios/Video/RCTVideo.m | 11 ++++++++--- ios/Video/RCTVideoManager.m | 1 + 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Video.js b/Video.js index 58a7b9c1..2f2f3379 100644 --- a/Video.js +++ b/Video.js @@ -151,6 +151,12 @@ export default class Video extends Component { } }; + _onCaptionsDeviceSetting = (event) => { + if (this.props.onCaptionsDeviceSetting) { + this.props.onCaptionsDeviceSetting(event.nativeEvent); + } + }; + _onPlaybackStalled = (event) => { if (this.props.onPlaybackStalled) { this.props.onPlaybackStalled(event.nativeEvent); @@ -241,6 +247,7 @@ export default class Video extends Component { onVideoFullscreenPlayerWillDismiss: this._onFullscreenPlayerWillDismiss, onVideoFullscreenPlayerDidDismiss: this._onFullscreenPlayerDidDismiss, onReadyForDisplay: this._onReadyForDisplay, + onCaptionsDeviceSetting: this._onCaptionsDeviceSetting, onPlaybackStalled: this._onPlaybackStalled, onPlaybackResume: this._onPlaybackResume, onPlaybackRateChange: this._onPlaybackRateChange, @@ -379,6 +386,7 @@ Video.propTypes = { onFullscreenPlayerWillDismiss: PropTypes.func, onFullscreenPlayerDidDismiss: PropTypes.func, onReadyForDisplay: PropTypes.func, + onCaptionsDeviceSetting: PropTypes.func, onPlaybackStalled: PropTypes.func, onPlaybackResume: PropTypes.func, onPlaybackRateChange: PropTypes.func, diff --git a/ios/Video/RCTVideo.h b/ios/Video/RCTVideo.h index b2296c5d..23d420d8 100644 --- a/ios/Video/RCTVideo.h +++ b/ios/Video/RCTVideo.h @@ -32,6 +32,7 @@ @property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillDismiss; @property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidDismiss; @property (nonatomic, copy) RCTBubblingEventBlock onReadyForDisplay; +@property (nonatomic, copy) RCTBubblingEventBlock onCaptionsDeviceSetting; @property (nonatomic, copy) RCTBubblingEventBlock onPlaybackStalled; @property (nonatomic, copy) RCTBubblingEventBlock onPlaybackResume; @property (nonatomic, copy) RCTBubblingEventBlock onPlaybackRateChange; diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index da1cbe31..f894d911 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -68,6 +68,7 @@ static int const RCTVideoUnset = -1; #if __has_include() RCTVideoCache * _videoCache; #endif + BOOL _deviceCaptionsEnabled; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -364,6 +365,12 @@ static int const RCTVideoUnset = -1; @"target": self.reactTag }); } + + if (self.onCaptionsDeviceSetting) { + self.onCaptionsDeviceSetting(@{@"deviceCaptionsEnabled": [NSNumber numberWithBool:_deviceCaptionsEnabled], + @"target": self.reactTag + }); + } }]; }); _videoLoadStarted = YES; @@ -966,9 +973,7 @@ static int const RCTVideoUnset = -1; // in the situation that a selected text track is not available (eg. specifies a textTrack not available) if (![type isEqualToString:@"disabled"] && selectedTrackIndex == RCTVideoUnset) { - CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser); - NSArray *captionSettings = (__bridge NSArray*)captioningMediaCharacteristics; - if ([captionSettings containsObject:AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]) { + if (_deviceCaptionsEnabled) { selectedTrackIndex = 0; // If we can't find a match, use the first available track NSString *systemLanguage = [[NSLocale preferredLanguages] firstObject]; for (int i = 0; i < textTracks.count; ++i) { diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 9d902392..683238a1 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -54,6 +54,7 @@ RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTBubblingEventBloc RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidDismiss, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onCaptionsDeviceSetting, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTBubblingEventBlock); From 443bf88c63d315bbe82501b8c662f64a4d40c439 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Tue, 4 Sep 2018 15:44:19 -0700 Subject: [PATCH 12/14] Adds fullscreenOptions to iOS --- README.md | 29 ++++++++ ios/Video/RCTVideo.m | 156 +++++++++++++++++++++---------------------- 2 files changed, 107 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index c1dd0f0f..a1bfd118 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,8 @@ var styles = StyleSheet.create({ * [allowsExternalPlayback](#allowsexternalplayback) * [audioOnly](#audioonly) * [bufferConfig](#bufferconfig) +* [fullscreen (deprecated)](#fullscreen) +* [fullscreenOptions](#fullscreenOptions) * [ignoreSilentSwitch](#ignoresilentswitch) * [muted](#muted) * [paused](#paused) @@ -305,6 +307,33 @@ bufferConfig={{ Platforms: Android ExoPlayer +#### fullscreen (deprecated) + +Controls whether the player enters fullscreen on play. Use fullscreenOptions for extended behaviour. + +Platforms: iOS + +#### fullscreenOptions + +Controls behaviour of the player entering fullscreen, such as forcing landscape playback on portrait devices + +Property | Type | Description +--- | --- | --- +enabled | boolean | determines whether to enter fullscreen on video play +preferredOrientation | landscape, portrait, default | Defaults to the current device orientation; otherwise will force fullscreen video playback into landscape or portrait +autorotate | boolean | determines whether the video player will rotate to the preferredOrientation automatically + +Example with default values +``` +fullscreenOptions={{ + enabled: false, + preferredOrientation: 'default' + autorotate: true +}} +``` + +Platforms: iOS + #### ignoreSilentSwitch Controls the iOS silent switch behavior * **"inherit" (default)** - Use the default AVPlayer behavior diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index da1cbe31..9d68ad8f 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -119,19 +119,19 @@ static int const RCTVideoUnset = -1; - (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem { - RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init]; - playerLayer.showsPlaybackControls = YES; - playerLayer.rctDelegate = self; - - if (_fullscreenOptions) { - playerLayer.preferredOrientation = [RCTConvert NSString:[_fullscreenOptions objectForKey:@"preferredOrientation"]]; - playerLayer.autorotate = [RCTConvert BOOL:[_fullscreenOptions objectForKey:@"autorotate"]]; - } - - playerLayer.view.frame = self.bounds; - playerLayer.player = player; - playerLayer.view.frame = self.bounds; - return playerLayer; + RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init]; + playerLayer.showsPlaybackControls = YES; + playerLayer.rctDelegate = self; + + if (_fullscreenOptions) { + playerLayer.preferredOrientation = [RCTConvert NSString:[_fullscreenOptions objectForKey:@"preferredOrientation"]]; + playerLayer.autorotate = [RCTConvert BOOL:[_fullscreenOptions objectForKey:@"autorotate"]]; + } + + playerLayer.view.frame = self.bounds; + playerLayer.player = player; + playerLayer.view.frame = self.bounds; + return playerLayer; } /* --------------------------------------------------------- @@ -222,11 +222,11 @@ static int const RCTVideoUnset = -1; - (void)audioRouteChanged:(NSNotification *)notification { - NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; - NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; - if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { - self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag}); - } + NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; + NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; + if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { + self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag}); + } } #pragma mark - Progress @@ -381,7 +381,7 @@ static int const RCTVideoUnset = -1; NSString* relativeFilePath = [filepath lastPathComponent]; // the file may be multiple levels below the documents directory NSArray* fileComponents = [filepath componentsSeparatedByString:@"/Documents/"]; - if (fileComponents.count>1) { + if (fileComponents.count > 1) { relativeFilePath = [fileComponents objectAtIndex:1]; } @@ -729,8 +729,8 @@ static int const RCTVideoUnset = -1; - (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback { - _allowsExternalPlayback = allowsExternalPlayback; - _player.allowsExternalPlayback = _allowsExternalPlayback; + _allowsExternalPlayback = allowsExternalPlayback; + _player.allowsExternalPlayback = _allowsExternalPlayback; } - (void)setPlayWhenInactive:(BOOL)playWhenInactive @@ -863,52 +863,52 @@ static int const RCTVideoUnset = -1; - (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)characteristic withCriteria:(NSDictionary *)criteria { - NSString *type = criteria[@"type"]; - AVMediaSelectionGroup *group = [_player.currentItem.asset - mediaSelectionGroupForMediaCharacteristic:characteristic]; - AVMediaSelectionOption *mediaOption; + NSString *type = criteria[@"type"]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:characteristic]; + AVMediaSelectionOption *mediaOption; - if ([type isEqualToString:@"disabled"]) { - // Do nothing. We want to ensure option is nil - } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { - NSString *value = criteria[@"value"]; - for (int i = 0; i < group.options.count; ++i) { - AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; - NSString *optionValue; - if ([type isEqualToString:@"language"]) { - optionValue = [currentOption extendedLanguageTag]; - } else { - optionValue = [[[currentOption commonMetadata] - valueForKey:@"value"] - objectAtIndex:0]; - } - if ([value isEqualToString:optionValue]) { - mediaOption = currentOption; - break; + if ([type isEqualToString:@"disabled"]) { + // Do nothing. We want to ensure option is nil + } else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) { + NSString *value = criteria[@"value"]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *optionValue; + if ([type isEqualToString:@"language"]) { + optionValue = [currentOption extendedLanguageTag]; + } else { + optionValue = [[[currentOption commonMetadata] + valueForKey:@"value"] + objectAtIndex:0]; + } + if ([value isEqualToString:optionValue]) { + mediaOption = currentOption; + break; + } } + //} else if ([type isEqualToString:@"default"]) { + // option = group.defaultOption; */ + } else if ([type isEqualToString:@"index"]) { + if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) { + int index = [criteria[@"value"] intValue]; + if (group.options.count > index) { + mediaOption = [group.options objectAtIndex:index]; + } + } + } else { // default. invalid type or "system" + [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; + return; } - //} else if ([type isEqualToString:@"default"]) { - // option = group.defaultOption; */ - } else if ([type isEqualToString:@"index"]) { - if ([criteria[@"value"] isKindOfClass:[NSNumber class]]) { - int index = [criteria[@"value"] intValue]; - if (group.options.count > index) { - mediaOption = [group.options objectAtIndex:index]; - } - } - } else { // default. invalid type or "system" - [_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group]; - return; - } - // If a match isn't found, option will be nil and text tracks will be disabled - [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; + // If a match isn't found, option will be nil and text tracks will be disabled + [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; } - (void)setSelectedAudioTrack:(NSDictionary *)selectedAudioTrack { - _selectedAudioTrack = selectedAudioTrack; - [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible - withCriteria:_selectedAudioTrack]; + _selectedAudioTrack = selectedAudioTrack; + [self setMediaSelectionTrackForCharacteristic:AVMediaCharacteristicAudible + withCriteria:_selectedAudioTrack]; } - (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack { @@ -1043,25 +1043,25 @@ static int const RCTVideoUnset = -1; - (NSArray *)getAudioTrackInfo { - NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; - AVMediaSelectionGroup *group = [_player.currentItem.asset - mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - for (int i = 0; i < group.options.count; ++i) { - AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; - NSString *title = @""; - NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"]; - if (values.count > 0) { - title = [values objectAtIndex:0]; + NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; + AVMediaSelectionGroup *group = [_player.currentItem.asset + mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + for (int i = 0; i < group.options.count; ++i) { + AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i]; + NSString *title = @""; + NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"]; + if (values.count > 0) { + title = [values objectAtIndex:0]; + } + NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @""; + NSDictionary *audioTrack = @{ + @"index": [NSNumber numberWithInt:i], + @"title": title, + @"language": language + }; + [audioTracks addObject:audioTrack]; } - NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @""; - NSDictionary *audioTrack = @{ - @"index": [NSNumber numberWithInt:i], - @"title": title, - @"language": language - }; - [audioTracks addObject:audioTrack]; - } - return audioTracks; + return audioTracks; } - (NSArray *)getTextTrackInfo From 0646dca0713d8256a7ac3138173b901e0b337903 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Sun, 7 Oct 2018 20:24:50 -0700 Subject: [PATCH 13/14] Split fullscreen options into separate props --- README.md | 14 +++++- Video.js | 7 +-- ios/Video/RCTVideo.m | 88 +++++++++++++++---------------------- ios/Video/RCTVideoManager.m | 5 ++- 4 files changed, 54 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index eed2959e..0b428961 100644 --- a/README.md +++ b/README.md @@ -254,7 +254,8 @@ var styles = StyleSheet.create({ * [bufferConfig](#bufferconfig) * [controls](#controls) * [fullscreen](#fullscreen) -* [fullscreenOptions](#fullscreenOptions) +* [fullscreenAutorotate](#fullscreenautorotate) +* [fullscreenOrientation](#fullscreenorientation) * [headers](#headers) * [ignoreSilentSwitch](#ignoresilentswitch) * [muted](#muted) @@ -346,9 +347,20 @@ Platforms: DOM, iOS #### fullscreen Controls whether the player enters fullscreen on play. Use fullscreenOptions for extended behaviour. +* **false (default)** - Don't display the video in fullscreen +* **true** - Display the video in fullscreen Platforms: iOS +#### fullscreenAutorotate +If a preferred [fullscreenOrientation](#fullscreenorientation) is set, causes the video to rotate to that orientation when the video enters fullscreen. + +#### fullscreenOrientation + +* **all (default)** - +* **landscape** +* **portrait** + #### fullscreenOptions Controls behaviour of the player entering fullscreen, such as forcing landscape playback on portrait devices diff --git a/Video.js b/Video.js index 01f15b45..16cca44f 100644 --- a/Video.js +++ b/Video.js @@ -282,11 +282,6 @@ Video.propTypes = { PropTypes.object ]), fullscreen: PropTypes.bool, - fullscreenOptions: PropTypes.shape({ - enabled: PropTypes.bool, - preferredOrientation: PropTypes.string, - autorotate: PropTypes.bool - }), onVideoLoadStart: PropTypes.func, onVideoLoad: PropTypes.func, onVideoBuffer: PropTypes.func, @@ -359,6 +354,8 @@ Video.propTypes = { controls: PropTypes.bool, audioOnly: PropTypes.bool, currentTime: PropTypes.number, + fullscreenAutorotate: PropTypes.bool, + fullscreenOrientation: PropTypes.string, progressUpdateInterval: PropTypes.number, useTextureView: PropTypes.bool, onLoadStart: PropTypes.func, diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index 93f4e3d3..e837fefe 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -30,7 +30,7 @@ static int const RCTVideoUnset = -1; BOOL _playerBufferEmpty; AVPlayerLayer *_playerLayer; BOOL _playerLayerObserverSet; - AVPlayerViewController *_playerViewController; + RCTVideoPlayerViewController *_playerViewController; NSURL *_videoURL; /* Required to publish events */ @@ -64,7 +64,8 @@ static int const RCTVideoUnset = -1; NSString * _ignoreSilentSwitch; NSString * _resizeMode; BOOL _fullscreen; - NSDictionary* _fullscreenOptions; + BOOL _fullscreenAutorotate; + NSString * _fullscreenOrientation; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; #if __has_include() @@ -83,6 +84,8 @@ static int const RCTVideoUnset = -1; _rate = 1.0; _volume = 1.0; _resizeMode = @"AVLayerVideoGravityResizeAspectFill"; + _fullscreenAutorotate = YES; + _fullscreenOrientation = @"default"; _pendingSeek = false; _pendingSeekTime = 0.0f; _lastSeekTime = 0.0f; @@ -120,21 +123,18 @@ static int const RCTVideoUnset = -1; return self; } -- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem { - - RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init]; - playerLayer.showsPlaybackControls = YES; - playerLayer.rctDelegate = self; +- (RCTVideoPlayerViewController*)createPlayerViewController:(AVPlayer*)player + withPlayerItem:(AVPlayerItem*)playerItem { + RCTVideoPlayerViewController* viewController = [[RCTVideoPlayerViewController alloc] init]; + viewController.showsPlaybackControls = YES; + viewController.rctDelegate = self; + viewController.autorotate = _fullscreenAutorotate; + viewController.preferredOrientation = _fullscreenOrientation; - if (_fullscreenOptions) { - playerLayer.preferredOrientation = [RCTConvert NSString:[_fullscreenOptions objectForKey:@"preferredOrientation"]]; - playerLayer.autorotate = [RCTConvert BOOL:[_fullscreenOptions objectForKey:@"autorotate"]]; - } - - playerLayer.view.frame = self.bounds; - playerLayer.player = player; - playerLayer.view.frame = self.bounds; - return playerLayer; + viewController.view.frame = self.bounds; + viewController.player = player; + viewController.view.frame = self.bounds; + return viewController; } /* --------------------------------------------------------- @@ -361,8 +361,6 @@ static int const RCTVideoUnset = -1; [self addPlayerTimeObserver]; - [self setFullscreenOptions:_fullscreenOptions]; - //Perform on next run loop, otherwise onVideoLoadStart is nil if (self.onVideoLoadStart) { id uri = [source objectForKey:@"uri"]; @@ -380,17 +378,15 @@ static int const RCTVideoUnset = -1; } - (NSURL*) urlFilePath:(NSString*) filepath { - - // check if the file exists at the specified location - if ([[NSFileManager defaultManager] fileExistsAtPath:filepath]) { - return [NSURL fileURLWithPath:filepath]; + if ([filepath containsString:@"file://"]) { + return [NSURL URLWithString:filepath]; } // if no file found, check if the file exists in the Document directory NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString* relativeFilePath = [filepath lastPathComponent]; // the file may be multiple levels below the documents directory - NSArray* fileComponents = [filepath componentsSeparatedByString:@"/Documents/"]; + NSArray* fileComponents = [filepath componentsSeparatedByString:@"Documents/"]; if (fileComponents.count > 1) { relativeFilePath = [fileComponents objectAtIndex:1]; } @@ -1113,33 +1109,7 @@ static int const RCTVideoUnset = -1; } - (void)setFullscreen:(BOOL) fullscreen { - _fullscreen = fullscreen; - - NSString* enabled = _fullscreen ? @"1" : @"0"; - - if (!_fullscreenOptions) { - [self setFullscreenOptions: - @{ - @"enabled": enabled, - @"autorotate": @"0", - @"preferredOrientation": @"default" - }]; - } - else { - [_fullscreenOptions setValue:enabled forKey:@"enabled"]; - [self setFullscreenOptions:_fullscreenOptions]; - } -} - -- (void)setFullscreenOptions:(NSDictionary*) fullscreenOptions -{ - _fullscreenOptions = fullscreenOptions; - - if (!_fullscreenOptions) return; - - BOOL fullscreenEnabled = [RCTConvert BOOL:[_fullscreenOptions objectForKey:@"enabled"]]; - - if( fullscreenEnabled && !_fullscreenPlayerPresented && _player ) + if( fullscreen && !_fullscreenPlayerPresented ) { // Ensure player view controller is not null if( !_playerViewController ) @@ -1168,14 +1138,14 @@ static int const RCTVideoUnset = -1; } [viewController presentViewController:_playerViewController animated:true completion:^{ _playerViewController.showsPlaybackControls = YES; - _fullscreenPlayerPresented = fullscreenEnabled; + _fullscreenPlayerPresented = fullscreen; if(self.onVideoFullscreenPlayerDidPresent) { self.onVideoFullscreenPlayerDidPresent(@{@"target": self.reactTag}); } }]; } } - else if ( !fullscreenEnabled && _fullscreenPlayerPresented ) + else if ( !fullscreen && _fullscreenPlayerPresented ) { [self videoPlayerViewControllerWillDismiss:_playerViewController]; [_presentingViewController dismissViewControllerAnimated:true completion:^{ @@ -1184,6 +1154,20 @@ static int const RCTVideoUnset = -1; } } +- (void)setFullscreenAutorotate:(BOOL)autorotate { + _fullscreenAutorotate = autorotate; + if (_fullscreenPlayerPresented) { + _playerViewController.autorotate = autorotate; + } +} + +- (void)setFullscreenOrientation:(NSString *)orientation { + _fullscreenOrientation = orientation; + if (_fullscreenPlayerPresented) { + _playerViewController.preferredOrientation = orientation; + } +} + - (void)usePlayerViewController { if( _player ) diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 10e6f6f8..ce699a18 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -36,8 +36,9 @@ RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString); RCT_EXPORT_VIEW_PROPERTY(rate, float); RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(currentTime, float); -RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL); // deprecated property -RCT_EXPORT_VIEW_PROPERTY(fullscreenOptions, NSDictionary); +RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL); +RCT_EXPORT_VIEW_PROPERTY(fullscreenAutorotate, BOOL); +RCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString); RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float); /* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */ RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock); From bbf37ed5cb69e8960d274b0a0d7b19f4deb01a77 Mon Sep 17 00:00:00 2001 From: Ash Mishra Date: Tue, 9 Oct 2018 16:01:41 -0700 Subject: [PATCH 14/14] Removed autoRotate from codebase; added TV_OS target check for orientation; added tvOS target to examples/basic/ios project --- README.md | 24 +- Video.js | 3 +- examples/basic/ios/VideoPlayer-tvOS.plist | 53 ++++ .../ios/VideoPlayer.xcodeproj/project.pbxproj | 291 ++++++++++-------- .../xcschemes/VideoPlayer-tvOS.xcscheme | 105 +++++++ .../xcschemes/VideoPlayer.xcscheme | 24 -- .../basic/ios/VideoPlayerTests/Info.plist | 24 -- .../ios/VideoPlayerTests/VideoPlayerTests.m | 70 ----- ios/Video/RCTVideo.h | 2 +- ios/Video/RCTVideo.m | 14 +- ios/Video/RCTVideoManager.m | 1 - ios/Video/RCTVideoPlayerViewController.h | 1 - ios/Video/RCTVideoPlayerViewController.m | 24 +- 13 files changed, 331 insertions(+), 305 deletions(-) create mode 100644 examples/basic/ios/VideoPlayer-tvOS.plist create mode 100644 examples/basic/ios/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer-tvOS.xcscheme delete mode 100644 examples/basic/ios/VideoPlayerTests/Info.plist delete mode 100644 examples/basic/ios/VideoPlayerTests/VideoPlayerTests.m diff --git a/README.md b/README.md index 0b428961..74438362 100644 --- a/README.md +++ b/README.md @@ -254,7 +254,6 @@ var styles = StyleSheet.create({ * [bufferConfig](#bufferconfig) * [controls](#controls) * [fullscreen](#fullscreen) -* [fullscreenAutorotate](#fullscreenautorotate) * [fullscreenOrientation](#fullscreenorientation) * [headers](#headers) * [ignoreSilentSwitch](#ignoresilentswitch) @@ -346,39 +345,18 @@ Note on iOS, controls are always shown when in fullscreen mode. Platforms: DOM, iOS #### fullscreen -Controls whether the player enters fullscreen on play. Use fullscreenOptions for extended behaviour. +Controls whether the player enters fullscreen on play. * **false (default)** - Don't display the video in fullscreen * **true** - Display the video in fullscreen Platforms: iOS -#### fullscreenAutorotate -If a preferred [fullscreenOrientation](#fullscreenorientation) is set, causes the video to rotate to that orientation when the video enters fullscreen. - #### fullscreenOrientation * **all (default)** - * **landscape** * **portrait** -#### fullscreenOptions -Controls behaviour of the player entering fullscreen, such as forcing landscape playback on portrait devices - -Property | Type | Description ---- | --- | --- -enabled | boolean | determines whether to enter fullscreen on video play -preferredOrientation | landscape, portrait, default | Defaults to the current device orientation; otherwise will force fullscreen video playback into landscape or portrait -autorotate | boolean | determines whether the video player will rotate to the preferredOrientation automatically - -Example with default values -``` -fullscreenOptions={{ - enabled: false, - preferredOrientation: 'default' - autorotate: true -}} -``` - Platforms: iOS #### headers diff --git a/Video.js b/Video.js index 16cca44f..e430e419 100644 --- a/Video.js +++ b/Video.js @@ -354,8 +354,7 @@ Video.propTypes = { controls: PropTypes.bool, audioOnly: PropTypes.bool, currentTime: PropTypes.number, - fullscreenAutorotate: PropTypes.bool, - fullscreenOrientation: PropTypes.string, + fullscreenOrientation: PropTypes.oneOf(['all','landscape','portrait']), progressUpdateInterval: PropTypes.number, useTextureView: PropTypes.bool, onLoadStart: PropTypes.func, diff --git a/examples/basic/ios/VideoPlayer-tvOS.plist b/examples/basic/ios/VideoPlayer-tvOS.plist new file mode 100644 index 00000000..ecbd496b --- /dev/null +++ b/examples/basic/ios/VideoPlayer-tvOS.plist @@ -0,0 +1,53 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + NSLocationWhenInUseUsageDescription + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/examples/basic/ios/VideoPlayer.xcodeproj/project.pbxproj b/examples/basic/ios/VideoPlayer.xcodeproj/project.pbxproj index 4c3fa6f2..db5f9a39 100644 --- a/examples/basic/ios/VideoPlayer.xcodeproj/project.pbxproj +++ b/examples/basic/ios/VideoPlayer.xcodeproj/project.pbxproj @@ -12,7 +12,6 @@ 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; - 00E356F31AD99517003FC87E /* VideoPlayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* VideoPlayerTests.m */; }; 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; @@ -20,11 +19,21 @@ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; 8C2A0F841E2560A100E31596 /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C2A0F791E25608300E31596 /* libRCTVideo.a */; }; + FA3566AB216D5D7000E01ABD /* libRCTImage-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E841DF850E9000B6D8A /* libRCTImage-tvOS.a */; }; + FA3566AC216D5D7000E01ABD /* libRCTLinking-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E881DF850E9000B6D8A /* libRCTLinking-tvOS.a */; }; + FA3566AD216D5D7000E01ABD /* libRCTNetwork-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E8C1DF850E9000B6D8A /* libRCTNetwork-tvOS.a */; }; + FA3566AE216D5D7000E01ABD /* libRCTSettings-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E901DF850E9000B6D8A /* libRCTSettings-tvOS.a */; }; + FA3566AF216D5D7000E01ABD /* libRCTText-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E941DF850E9000B6D8A /* libRCTText-tvOS.a */; }; + FA3566B0216D5D7000E01ABD /* libRCTWebSocket-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E991DF850E9000B6D8A /* libRCTWebSocket-tvOS.a */; }; + FA3566C8216D5DA900E01ABD /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D1107C542111145500073188 /* libRCTVideo.a */; }; + FA8681B8216D5C6D0010C92A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + FA8681B9216D5C6D0010C92A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + FA8681C8216D5C6D0010C92A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + FA8B47A5216D777200AB07CF /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3EA31DF850E9000B6D8A /* libReact.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,13 +72,6 @@ remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; remoteInfo = RCTVibration; }; - 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 13B07F861A680F5B00A75B9A; - remoteInfo = VideoPlayer; - }; 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; @@ -303,9 +305,6 @@ 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; - 00E356EE1AD99517003FC87E /* VideoPlayerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VideoPlayerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 00E356F21AD99517003FC87E /* VideoPlayerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VideoPlayerTests.m; sourceTree = ""; }; 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* VideoPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VideoPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -316,28 +315,24 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = VideoPlayer/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = VideoPlayer/main.m; sourceTree = ""; }; 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 39CBB10045CEBFA9BBB9645E /* libPods-VideoPlayer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VideoPlayer.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = ""; }; + 627363E07276C06249D7CEBF /* libPods-VideoPlayer-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VideoPlayer-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; 8C2A0F651E25608300E31596 /* RCTVideo.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVideo.xcodeproj; path = "../node_modules/react-native-video/ios/RCTVideo.xcodeproj"; sourceTree = ""; }; + FA8681CE216D5C6D0010C92A /* VideoPlayer-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "VideoPlayer-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + FA8681D0216D5C6E0010C92A /* VideoPlayer-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "VideoPlayer-tvOS.plist"; path = "/Users/amishra/Development/react-native-video-nfb/examples/basic/ios/VideoPlayer-tvOS.plist"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 00E356EB1AD99517003FC87E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8C2A0F841E2560A100E31596 /* libRCTVideo.a in Frameworks */, - 5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */, + 5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */, + 8C2A0F841E2560A100E31596 /* libRCTVideo.a in Frameworks */, 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, @@ -350,6 +345,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FA8681BA216D5C6D0010C92A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FA8B47A5216D777200AB07CF /* libReact.a in Frameworks */, + FA3566AB216D5D7000E01ABD /* libRCTImage-tvOS.a in Frameworks */, + FA3566AC216D5D7000E01ABD /* libRCTLinking-tvOS.a in Frameworks */, + FA3566AD216D5D7000E01ABD /* libRCTNetwork-tvOS.a in Frameworks */, + FA3566AE216D5D7000E01ABD /* libRCTSettings-tvOS.a in Frameworks */, + FA3566AF216D5D7000E01ABD /* libRCTText-tvOS.a in Frameworks */, + FA3566B0216D5D7000E01ABD /* libRCTWebSocket-tvOS.a in Frameworks */, + FA3566C8216D5DA900E01ABD /* libRCTVideo.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -395,23 +405,6 @@ name = Products; sourceTree = ""; }; - 00E356EF1AD99517003FC87E /* VideoPlayerTests */ = { - isa = PBXGroup; - children = ( - 00E356F21AD99517003FC87E /* VideoPlayerTests.m */, - 00E356F01AD99517003FC87E /* Supporting Files */, - ); - path = VideoPlayerTests; - sourceTree = ""; - }; - 00E356F01AD99517003FC87E /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 00E356F11AD99517003FC87E /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; 139105B71AF99BAD00B5F7CC /* Products */ = { isa = PBXGroup; children = ( @@ -473,7 +466,7 @@ isa = PBXGroup; children = ( 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */, - 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */, + 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */, ); name = Products; sourceTree = ""; @@ -518,10 +511,11 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + FA8681CF216D5C6D0010C92A /* Resources-iPad */, 13B07FAE1A68108700A75B9A /* VideoPlayer */, 832341AE1AAA6A7D00B99B32 /* Libraries */, - 00E356EF1AD99517003FC87E /* VideoPlayerTests */, 83CBBA001A601CBA00E9B192 /* Products */, + FA35669C216D5D7000E01ABD /* Frameworks */, ); indentWidth = 2; sourceTree = ""; @@ -531,7 +525,7 @@ isa = PBXGroup; children = ( 13B07F961A680F5B00A75B9A /* VideoPlayer.app */, - 00E356EE1AD99517003FC87E /* VideoPlayerTests.xctest */, + FA8681CE216D5C6D0010C92A /* VideoPlayer-tvOS.app */, ); name = Products; sourceTree = ""; @@ -545,27 +539,26 @@ name = Products; sourceTree = ""; }; + FA35669C216D5D7000E01ABD /* Frameworks */ = { + isa = PBXGroup; + children = ( + 39CBB10045CEBFA9BBB9645E /* libPods-VideoPlayer.a */, + 627363E07276C06249D7CEBF /* libPods-VideoPlayer-tvOS.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + FA8681CF216D5C6D0010C92A /* Resources-iPad */ = { + isa = PBXGroup; + children = ( + FA8681D0216D5C6E0010C92A /* VideoPlayer-tvOS.plist */, + ); + name = "Resources-iPad"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 00E356ED1AD99517003FC87E /* VideoPlayerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "VideoPlayerTests" */; - buildPhases = ( - 00E356EA1AD99517003FC87E /* Sources */, - 00E356EB1AD99517003FC87E /* Frameworks */, - 00E356EC1AD99517003FC87E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 00E356F51AD99517003FC87E /* PBXTargetDependency */, - ); - name = VideoPlayerTests; - productName = VideoPlayerTests; - productReference = 00E356EE1AD99517003FC87E /* VideoPlayerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; 13B07F861A680F5B00A75B9A /* VideoPlayer */ = { isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "VideoPlayer" */; @@ -584,6 +577,24 @@ productReference = 13B07F961A680F5B00A75B9A /* VideoPlayer.app */; productType = "com.apple.product-type.application"; }; + FA8681B6216D5C6D0010C92A /* VideoPlayer-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = FA8681CB216D5C6D0010C92A /* Build configuration list for PBXNativeTarget "VideoPlayer-tvOS" */; + buildPhases = ( + FA8681B7216D5C6D0010C92A /* Sources */, + FA8681BA216D5C6D0010C92A /* Frameworks */, + FA8681C7216D5C6D0010C92A /* Resources */, + FA8681CA216D5C6D0010C92A /* Bundle React Native code and images */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "VideoPlayer-tvOS"; + productName = "Hello World"; + productReference = FA8681CE216D5C6D0010C92A /* VideoPlayer-tvOS.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -592,12 +603,6 @@ attributes = { LastUpgradeCheck = 0610; ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 00E356ED1AD99517003FC87E = { - CreatedOnToolsVersion = 6.2; - TestTargetID = 13B07F861A680F5B00A75B9A; - }; - }; }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "VideoPlayer" */; compatibilityVersion = "Xcode 3.2"; @@ -663,7 +668,7 @@ projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* VideoPlayer */, - 00E356ED1AD99517003FC87E /* VideoPlayerTests */, + FA8681B6216D5C6D0010C92A /* VideoPlayer-tvOS */, ); }; /* End PBXProject section */ @@ -823,7 +828,7 @@ remoteRef = 5E9157321DD0AC6500FF2AA8 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */ = { + 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libRCTAnimation.a; @@ -931,13 +936,6 @@ /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ - 00E356EC1AD99517003FC87E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 13B07F8E1A680F5B00A75B9A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -947,6 +945,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FA8681C7216D5C6D0010C92A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FA8681C8216D5C6D0010C92A /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -962,19 +968,25 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; + shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; + }; + FA8681CA216D5C6D0010C92A /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 00E356EA1AD99517003FC87E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 00E356F31AD99517003FC87E /* VideoPlayerTests.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 13B07F871A680F5B00A75B9A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -984,15 +996,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 13B07F861A680F5B00A75B9A /* VideoPlayer */; - targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + FA8681B7216D5C6D0010C92A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FA8681B8216D5C6D0010C92A /* AppDelegate.m in Sources */, + FA8681B9216D5C6D0010C92A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; }; -/* End PBXTargetDependency section */ +/* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { @@ -1007,41 +1020,13 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 00E356F61AD99517003FC87E /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = VideoPlayerTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VideoPlayer.app/VideoPlayer"; - }; - name = Debug; - }; - 00E356F71AD99517003FC87E /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - COPY_PHASE_STRIP = NO; - INFOPLIST_FILE = VideoPlayerTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VideoPlayer.app/VideoPlayer"; - }; - name = Release; - }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; + HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = VideoPlayer/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( @@ -1059,6 +1044,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 1; + HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = VideoPlayer/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( @@ -1147,18 +1133,54 @@ }; name = Release; }; + FA8681CC216D5C6D0010C92A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = NO; + INFOPLIST_FILE = "VideoPlayer-tvOS.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.VideoPlayer-tvOS"; + PRODUCT_NAME = "VideoPlayer-tvOS"; + SDKROOT = appletvos; + SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; + TARGETED_DEVICE_FAMILY = 3; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + FA8681CD216D5C6D0010C92A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = 1; + INFOPLIST_FILE = "VideoPlayer-tvOS.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.VideoPlayer-tvOS"; + PRODUCT_NAME = "VideoPlayer-tvOS"; + SDKROOT = appletvos; + SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; + TARGETED_DEVICE_FAMILY = 3; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "VideoPlayerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 00E356F61AD99517003FC87E /* Debug */, - 00E356F71AD99517003FC87E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "VideoPlayer" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1177,6 +1199,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + FA8681CB216D5C6D0010C92A /* Build configuration list for PBXNativeTarget "VideoPlayer-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FA8681CC216D5C6D0010C92A /* Debug */, + FA8681CD216D5C6D0010C92A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; diff --git a/examples/basic/ios/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer-tvOS.xcscheme b/examples/basic/ios/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer-tvOS.xcscheme new file mode 100644 index 00000000..75b3d2eb --- /dev/null +++ b/examples/basic/ios/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer-tvOS.xcscheme @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/basic/ios/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer.xcscheme b/examples/basic/ios/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer.xcscheme index f27978d8..48a64467 100644 --- a/examples/basic/ios/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer.xcscheme +++ b/examples/basic/ios/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer.xcscheme @@ -34,20 +34,6 @@ ReferencedContainer = "container:VideoPlayer.xcodeproj"> - - - - - - - - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/examples/basic/ios/VideoPlayerTests/VideoPlayerTests.m b/examples/basic/ios/VideoPlayerTests/VideoPlayerTests.m deleted file mode 100644 index 9b7b412d..00000000 --- a/examples/basic/ios/VideoPlayerTests/VideoPlayerTests.m +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import -#import - -#import -#import - -#define TIMEOUT_SECONDS 600 -#define TEXT_TO_LOOK_FOR @"Welcome to React Native!" - -@interface VideoPlayerTests : XCTestCase - -@end - -@implementation VideoPlayerTests - -- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test -{ - if (test(view)) { - return YES; - } - for (UIView *subview in [view subviews]) { - if ([self findSubviewInView:subview matching:test]) { - return YES; - } - } - return NO; -} - -- (void)testRendersWelcomeScreen -{ - UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - BOOL foundElement = NO; - - __block NSString *redboxError = nil; - RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { - if (level >= RCTLogLevelError) { - redboxError = message; - } - }); - - while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - - foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { - if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { - return YES; - } - return NO; - }]; - } - - RCTSetLogFunction(RCTDefaultLogFunction); - - XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); -} - - -@end diff --git a/ios/Video/RCTVideo.h b/ios/Video/RCTVideo.h index f857c841..e43fbe50 100644 --- a/ios/Video/RCTVideo.h +++ b/ios/Video/RCTVideo.h @@ -1,9 +1,9 @@ -#import #import #import "AVKit/AVKit.h" #import "UIView+FindUIViewController.h" #import "RCTVideoPlayerViewController.h" #import "RCTVideoPlayerViewControllerDelegate.h" +#import #if __has_include() #import diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index e837fefe..61b87578 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -64,7 +64,6 @@ static int const RCTVideoUnset = -1; NSString * _ignoreSilentSwitch; NSString * _resizeMode; BOOL _fullscreen; - BOOL _fullscreenAutorotate; NSString * _fullscreenOrientation; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; @@ -84,8 +83,7 @@ static int const RCTVideoUnset = -1; _rate = 1.0; _volume = 1.0; _resizeMode = @"AVLayerVideoGravityResizeAspectFill"; - _fullscreenAutorotate = YES; - _fullscreenOrientation = @"default"; + _fullscreenOrientation = @"all"; _pendingSeek = false; _pendingSeekTime = 0.0f; _lastSeekTime = 0.0f; @@ -128,7 +126,6 @@ static int const RCTVideoUnset = -1; RCTVideoPlayerViewController* viewController = [[RCTVideoPlayerViewController alloc] init]; viewController.showsPlaybackControls = YES; viewController.rctDelegate = self; - viewController.autorotate = _fullscreenAutorotate; viewController.preferredOrientation = _fullscreenOrientation; viewController.view.frame = self.bounds; @@ -1109,7 +1106,7 @@ static int const RCTVideoUnset = -1; } - (void)setFullscreen:(BOOL) fullscreen { - if( fullscreen && !_fullscreenPlayerPresented ) + if( fullscreen && !_fullscreenPlayerPresented && _player ) { // Ensure player view controller is not null if( !_playerViewController ) @@ -1154,13 +1151,6 @@ static int const RCTVideoUnset = -1; } } -- (void)setFullscreenAutorotate:(BOOL)autorotate { - _fullscreenAutorotate = autorotate; - if (_fullscreenPlayerPresented) { - _playerViewController.autorotate = autorotate; - } -} - - (void)setFullscreenOrientation:(NSString *)orientation { _fullscreenOrientation = orientation; if (_fullscreenPlayerPresented) { diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index ce699a18..aa3c4670 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -37,7 +37,6 @@ RCT_EXPORT_VIEW_PROPERTY(rate, float); RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(currentTime, float); RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL); -RCT_EXPORT_VIEW_PROPERTY(fullscreenAutorotate, BOOL); RCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString); RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float); /* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */ diff --git a/ios/Video/RCTVideoPlayerViewController.h b/ios/Video/RCTVideoPlayerViewController.h index ed9ebdde..99b1349b 100644 --- a/ios/Video/RCTVideoPlayerViewController.h +++ b/ios/Video/RCTVideoPlayerViewController.h @@ -15,6 +15,5 @@ // Optional paramters @property (nonatomic, weak) NSString* preferredOrientation; -@property (nonatomic) BOOL autorotate; @end diff --git a/ios/Video/RCTVideoPlayerViewController.m b/ios/Video/RCTVideoPlayerViewController.m index fe8cb171..e9550db5 100644 --- a/ios/Video/RCTVideoPlayerViewController.m +++ b/ios/Video/RCTVideoPlayerViewController.m @@ -6,12 +6,11 @@ @implementation RCTVideoPlayerViewController -- (id)init { - self = [super init]; - if (self) { - self.autorotate = true; // autorotate should be true by default - } - return self; +- (BOOL)shouldAutorotate { + if (self.preferredOrientation.lowercaseString == nil || [self.preferredOrientation.lowercaseString isEqualToString:@"all"]) + return YES; + + return NO; } - (void)viewDidDisappear:(BOOL)animated @@ -21,18 +20,8 @@ [_rctDelegate videoPlayerViewControllerDidDismiss:self]; } -- (BOOL)shouldAutorotate { - return self.autorotate; -} - +#if !TARGET_OS_TV - (UIInterfaceOrientationMask)supportedInterfaceOrientations { - - if ([self.preferredOrientation.lowercaseString isEqualToString:@"landscape"]) { - return UIInterfaceOrientationMaskLandscape; - } - else if ([self.preferredOrientation.lowercaseString isEqualToString:@"portrait"]) { - return UIInterfaceOrientationMaskPortrait; - } return UIInterfaceOrientationMaskAll; } @@ -48,5 +37,6 @@ return orientation; } } +#endif @end