Loading ios/Video/RCTVideo.m +215 −131 Changes for ios/Video/RCTVideo.m: 215 added lines, 131 removed lines. Original line number Diff line number Diff line Loading @@ -13,7 +13,6 @@ static NSString *const readyForDisplayKeyPath = @"readyForDisplay"; static NSString *const playbackRate = @"rate"; static NSString *const timedMetadata = @"timedMetadata"; static NSString *const externalPlaybackActive = @"externalPlaybackActive"; static NSDictionary *filters = nil; static int const RCTVideoUnset = -1; Loading @@ -23,7 +22,8 @@ static int const RCTVideoUnset = -1; #define DebugLog(...) (void)0 #endif @implementation RCTVideo { @implementation RCTVideo { AVPlayer *_player; AVPlayerItem *_playerItem; BOOL _playerItemObserversSet; Loading Loading @@ -73,18 +73,9 @@ static int const RCTVideoUnset = -1; #endif } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super init])) { filters = @{ @"Normal": @"", @"Country": @"CISepiaTone", @"Winter": @"CIPhotoEffectProcess", @"Black N White": @"CIPhotoEffectNoir", @"Sunrise": @"CIPhotoEffectTransfer", @"Artistic": @"CIColorPosterize", }; _eventDispatcher = eventDispatcher; _playbackRateObserverRegistered = NO; Loading Loading @@ -148,40 +139,45 @@ static int const RCTVideoUnset = -1; ** Get the duration for a AVPlayerItem. ** ------------------------------------------------------- */ - (CMTime)playerItemDuration { - (CMTime)playerItemDuration { AVPlayerItem *playerItem = [_player currentItem]; if (playerItem.status == AVPlayerItemStatusReadyToPlay) { if (playerItem.status == AVPlayerItemStatusReadyToPlay) { return([playerItem duration]); } return(kCMTimeInvalid); } - (CMTimeRange)playerItemSeekableTimeRange { - (CMTimeRange)playerItemSeekableTimeRange { AVPlayerItem *playerItem = [_player currentItem]; if (playerItem.status == AVPlayerItemStatusReadyToPlay) { if (playerItem.status == AVPlayerItemStatusReadyToPlay) { return [playerItem seekableTimeRanges].firstObject.CMTimeRangeValue; } return (kCMTimeRangeZero); } - (void)addPlayerTimeObserver { -(void)addPlayerTimeObserver { const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; // @see endScrubbing in AVPlayerDemoPlaybackViewController.m // of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html __weak RCTVideo *weakSelf = self; _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC) queue:NULL usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } ]; } /* Cancels the previously registered time observer. */ - (void)removePlayerTimeObserver { if (_timeObserver) { -(void)removePlayerTimeObserver { if (_timeObserver) { [_player removeTimeObserver:_timeObserver]; _timeObserver = nil; } Loading @@ -189,7 +185,8 @@ static int const RCTVideoUnset = -1; #pragma mark - Progress - (void)dealloc { - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [self removePlayerLayer]; [self removePlayerItemObservers]; Loading @@ -198,21 +195,24 @@ static int const RCTVideoUnset = -1; #pragma mark - App lifecycle handlers - (void)applicationWillResignActive:(NSNotification *)notification { - (void)applicationWillResignActive:(NSNotification *)notification { if (_playInBackground || _playWhenInactive || _paused) return; [_player pause]; [_player setRate:0.0]; } - (void)applicationDidEnterBackground:(NSNotification *)notification { - (void)applicationDidEnterBackground:(NSNotification *)notification { if (_playInBackground) { // Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html [_playerLayer setPlayer:nil]; } } - (void)applicationWillEnterForeground:(NSNotification *)notification { - (void)applicationWillEnterForeground:(NSNotification *)notification { [self applyModifiers]; if (_playInBackground) { [_playerLayer setPlayer:_player]; Loading @@ -221,7 +221,8 @@ static int const RCTVideoUnset = -1; #pragma mark - Audio events - (void)audioRouteChanged:(NSNotification *)notification { - (void)audioRouteChanged:(NSNotification *)notification { NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { Loading @@ -231,7 +232,8 @@ static int const RCTVideoUnset = -1; #pragma mark - Progress - (void)sendProgressUpdate { - (void)sendProgressUpdate { AVPlayerItem *video = [_player currentItem]; if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) { return; Loading Loading @@ -265,7 +267,8 @@ static int const RCTVideoUnset = -1; * * \returns The playable duration of the current player item in seconds. */ - (NSNumber *)calculatePlayableDuration { - (NSNumber *)calculatePlayableDuration { AVPlayerItem *video = _player.currentItem; if (video.status == AVPlayerItemStatusReadyToPlay) { __block CMTimeRange effectiveTimeRange; Loading @@ -284,15 +287,18 @@ static int const RCTVideoUnset = -1; return [NSNumber numberWithInteger:0]; } - (NSNumber *)calculateSeekableDuration { - (NSNumber *)calculateSeekableDuration { CMTimeRange timeRange = [self playerItemSeekableTimeRange]; if (CMTIME_IS_NUMERIC(timeRange.duration)) { if (CMTIME_IS_NUMERIC(timeRange.duration)) { return [NSNumber numberWithFloat:CMTimeGetSeconds(timeRange.duration)]; } return [NSNumber numberWithInteger:0]; } - (void)addPlayerItemObservers { - (void)addPlayerItemObservers { [_playerItem addObserver:self forKeyPath:statusKeyPath options:0 context:nil]; [_playerItem addObserver:self forKeyPath:playbackBufferEmptyKeyPath options:0 context:nil]; [_playerItem addObserver:self forKeyPath:playbackLikelyToKeepUpKeyPath options:0 context:nil]; Loading @@ -303,7 +309,8 @@ static int const RCTVideoUnset = -1; /* Fixes https://github.com/brentvatne/react-native-video/issues/43 * Crashes caused when trying to remove the observer when there is no * observer set */ - (void)removePlayerItemObservers { - (void)removePlayerItemObservers { if (_playerItemObserversSet) { [_playerItem removeObserver:self forKeyPath:statusKeyPath]; [_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath]; Loading @@ -315,7 +322,8 @@ static int const RCTVideoUnset = -1; #pragma mark - Player and source - (void)setSrc:(NSDictionary *)source { - (void)setSrc:(NSDictionary *)source { [self removePlayerLayer]; [self removePlayerTimeObserver]; [self removePlayerItemObservers]; Loading Loading @@ -388,7 +396,8 @@ static int const RCTVideoUnset = -1; return nil; } - (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSDictionary *__nullable)assetOptions withCallback:(void (^)(AVPlayerItem *))handler { - (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSDictionary * __nullable)assetOptions withCallback:(void(^)(AVPlayerItem *))handler { if (!_textTracks) { handler([AVPlayerItem playerItemWithAsset:asset]); return; Loading Loading @@ -438,7 +447,8 @@ static int const RCTVideoUnset = -1; handler([AVPlayerItem playerItemWithAsset:mixComposition]); } - (void)playerItemForSource:(NSDictionary *)source withCallback:(void (^)(AVPlayerItem *))handler { - (void)playerItemForSource:(NSDictionary *)source withCallback:(void(^)(AVPlayerItem *))handler { bool isNetwork = [RCTConvert BOOL:[source objectForKey:@"isNetwork"]]; bool isAsset = [RCTConvert BOOL:[source objectForKey:@"isAsset"]]; NSString *uri = [source objectForKey:@"uri"]; Loading Loading @@ -540,7 +550,8 @@ static int const RCTVideoUnset = -1; #endif - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == _playerItem) { // When timeMetadata is read the event onTimedMetadata is triggered if ([keyPath isEqualToString:timedMetadata]) { Loading Loading @@ -586,7 +597,8 @@ static int const RCTVideoUnset = -1; if ((videoTrack.naturalSize.width == preferredTransform.tx && videoTrack.naturalSize.height == preferredTransform.ty) || (preferredTransform.tx == 0 && preferredTransform.ty == 0)) { || (preferredTransform.tx == 0 && preferredTransform.ty == 0)) { orientation = @"landscape"; } else { orientation = @"portrait"; Loading Loading @@ -650,7 +662,8 @@ static int const RCTVideoUnset = -1; } _playbackStalled = NO; } } else if ([keyPath isEqualToString:externalPlaybackActive]) { } else if([keyPath isEqualToString:externalPlaybackActive]) { if(self.onVideoExternalPlaybackChange) { self.onVideoExternalPlaybackChange(@{@"isExternalPlaybackActive": [NSNumber numberWithBool:_player.isExternalPlaybackActive], @"target": self.reactTag}); Loading @@ -661,7 +674,8 @@ static int const RCTVideoUnset = -1; } } - (void)attachListeners { - (void)attachListeners { // listen for end of file [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification Loading @@ -680,14 +694,16 @@ static int const RCTVideoUnset = -1; object:nil]; } - (void)playbackStalled:(NSNotification *)notification { - (void)playbackStalled:(NSNotification *)notification { if(self.onPlaybackStalled) { self.onPlaybackStalled(@{@"target": self.reactTag}); } _playbackStalled = YES; } - (void)playerItemDidReachEnd:(NSNotification *)notification { - (void)playerItemDidReachEnd:(NSNotification *)notification { if(self.onVideoEnd) { self.onVideoEnd(@{@"target": self.reactTag}); } Loading @@ -703,34 +719,43 @@ static int const RCTVideoUnset = -1; #pragma mark - Prop setters - (void)setResizeMode:(NSString *)mode { if (_controls) { - (void)setResizeMode:(NSString*)mode { if( _controls ) { _playerViewController.videoGravity = mode; } else { } else { _playerLayer.videoGravity = mode; } _resizeMode = mode; } - (void)setPlayInBackground:(BOOL)playInBackground { - (void)setPlayInBackground:(BOOL)playInBackground { _playInBackground = playInBackground; } - (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback { - (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback { _allowsExternalPlayback = allowsExternalPlayback; _player.allowsExternalPlayback = _allowsExternalPlayback; } - (void)setPlayWhenInactive:(BOOL)playWhenInactive { - (void)setPlayWhenInactive:(BOOL)playWhenInactive { _playWhenInactive = playWhenInactive; } - (void)setIgnoreSilentSwitch:(NSString *)ignoreSilentSwitch { - (void)setIgnoreSilentSwitch:(NSString *)ignoreSilentSwitch { _ignoreSilentSwitch = ignoreSilentSwitch; [self applyModifiers]; } - (void)setPaused:(BOOL)paused { - (void)setPaused:(BOOL)paused { if (paused) { [_player pause]; [_player setRate:0.0]; Loading @@ -747,11 +772,13 @@ static int const RCTVideoUnset = -1; _paused = paused; } - (float)getCurrentTime { - (float)getCurrentTime { return _playerItem != NULL ? CMTimeGetSeconds(_playerItem.currentTime) : 0; } - (void)setCurrentTime:(float)currentTime { - (void)setCurrentTime:(float)currentTime { NSDictionary *info = @{ @"time": [NSNumber numberWithFloat:currentTime], @"tolerance": [NSNumber numberWithInt:100] Loading @@ -759,7 +786,8 @@ static int const RCTVideoUnset = -1; [self setSeek:info]; } - (void)setSeek:(NSDictionary *)info { - (void)setSeek:(NSDictionary *)info { NSNumber *seekTime = info[@"time"]; NSNumber *seekTolerance = info[@"tolerance"]; Loading Loading @@ -801,22 +829,26 @@ static int const RCTVideoUnset = -1; } } - (void)setRate:(float)rate { - (void)setRate:(float)rate { _rate = rate; [self applyModifiers]; } - (void)setMuted:(BOOL)muted { - (void)setMuted:(BOOL)muted { _muted = muted; [self applyModifiers]; } - (void)setVolume:(float)volume { - (void)setVolume:(float)volume { _volume = volume; [self applyModifiers]; } - (void)applyModifiers { - (void)applyModifiers { if (_muted) { [_player setVolume:0]; [_player setMuted:YES]; Loading @@ -840,7 +872,8 @@ static int const RCTVideoUnset = -1; } - (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)characteristic withCriteria:(NSDictionary *)criteria { withCriteria:(NSDictionary *)criteria { NSString *type = criteria[@"type"]; AVMediaSelectionGroup *group = [_player.currentItem.asset mediaSelectionGroupForMediaCharacteristic:characteristic]; Loading Loading @@ -1011,14 +1044,16 @@ static int const RCTVideoUnset = -1; [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; } - (void)setTextTracks:(NSArray *)textTracks; { - (void)setTextTracks:(NSArray*) textTracks; { _textTracks = textTracks; // in case textTracks was set after selectedTextTrack if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack]; } - (NSArray *)getAudioTrackInfo { - (NSArray *)getAudioTrackInfo { NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; AVMediaSelectionGroup *group = [_player.currentItem.asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; Loading @@ -1040,7 +1075,8 @@ static int const RCTVideoUnset = -1; return audioTracks; } - (NSArray *)getTextTrackInfo { - (NSArray *)getTextTrackInfo { // if sideloaded, textTracks will already be set if (_textTracks) return _textTracks; Loading @@ -1066,14 +1102,17 @@ static int const RCTVideoUnset = -1; return textTracks; } - (BOOL)getFullscreen { - (BOOL)getFullscreen { return _fullscreenPlayerPresented; } - (void)setFullscreen:(BOOL) fullscreen { if (fullscreen && !_fullscreenPlayerPresented && _player) { if( fullscreen && !_fullscreenPlayerPresented && _player ) { // Ensure player view controller is not null if (!_playerViewController) { if( !_playerViewController ) { [self usePlayerViewController]; } // Set presentation style to fullscreen Loading @@ -1081,14 +1120,17 @@ static int const RCTVideoUnset = -1; // Find the nearest view controller UIViewController *viewController = [self firstAvailableUIViewController]; if (!viewController) { if( !viewController ) { UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; viewController = keyWindow.rootViewController; if (viewController.childViewControllers.count > 0) { if( viewController.childViewControllers.count > 0 ) { viewController = viewController.childViewControllers.lastObject; } } if (viewController) { if( viewController ) { _presentingViewController = viewController; if(self.onVideoFullscreenPlayerWillPresent) { self.onVideoFullscreenPlayerWillPresent(@{@"target": self.reactTag}); Loading @@ -1101,7 +1143,9 @@ static int const RCTVideoUnset = -1; } }]; } } else if (!fullscreen && _fullscreenPlayerPresented) { } else if ( !fullscreen && _fullscreenPlayerPresented ) { [self videoPlayerViewControllerWillDismiss:_playerViewController]; [_presentingViewController dismissViewControllerAnimated:true completion:^{ [self videoPlayerViewControllerDidDismiss:_playerViewController]; Loading @@ -1109,43 +1153,6 @@ static int const RCTVideoUnset = -1; } } - (void)setFilter:(NSString *)filter { _filter = filter; AVAsset *asset = _playerItem.asset; if (asset != nil) { NSString *filterName = filters[filter]; CIFilter *filter = [CIFilter filterWithName:filterName]; _playerItem.videoComposition = [AVVideoComposition videoCompositionWithAsset:asset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest *_Nonnull request) { if (filter == nil) { [request finishWithImage:request.sourceImage context:nil]; } else { CIImage *image = request.sourceImage.imageByClampingToExtent; [filter setValue:image forKey:kCIInputImageKey]; CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent]; [request finishWithImage:output context:nil]; } }]; } } - (void)setFullscreenOrientation:(NSString *)orientation { _fullscreenOrientation = orientation; if (_fullscreenPlayerPresented) { Loading @@ -1153,8 +1160,10 @@ static int const RCTVideoUnset = -1; } } - (void)usePlayerViewController { if (_player) { - (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 Loading @@ -1163,8 +1172,10 @@ static int const RCTVideoUnset = -1; } } - (void)usePlayerLayer { if (_player) { - (void)usePlayerLayer { if( _player ) { _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; _playerLayer.frame = self.bounds; _playerLayer.needsDisplayOnBoundsChange = YES; Loading @@ -1180,13 +1191,18 @@ static int const RCTVideoUnset = -1; } } - (void)setControls:(BOOL)controls { if (_controls != controls || (!_playerLayer && !_playerViewController)) { - (void)setControls:(BOOL)controls { if( _controls != controls || (!_playerLayer && !_playerViewController) ) { _controls = controls; if (_controls) { if( _controls ) { [self removePlayerLayer]; [self usePlayerViewController]; } else { } else { [_playerViewController.view removeFromSuperview]; _playerViewController = nil; [self usePlayerLayer]; Loading @@ -1194,7 +1210,8 @@ static int const RCTVideoUnset = -1; } } - (void)setProgressUpdateInterval:(float)progressUpdateInterval { - (void)setProgressUpdateInterval:(float)progressUpdateInterval { _progressUpdateInterval = progressUpdateInterval; if (_timeObserver) { Loading @@ -1203,7 +1220,8 @@ static int const RCTVideoUnset = -1; } } - (void)removePlayerLayer { - (void)removePlayerLayer { [_playerLayer removeFromSuperlayer]; if (_playerLayerObserverSet) { [_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath]; Loading @@ -1212,16 +1230,66 @@ static int const RCTVideoUnset = -1; _playerLayer = nil; } - (void)setFilter:(NSString *)filter { _filter = filter; AVAsset *asset = _playerItem.asset; if (asset != nil) { NSDictionary *filters = @{ @"Normal": @"", @"Country": @"CISepiaTone", @"Winter": @"CIPhotoEffectProcess", @"Black N White": @"CIPhotoEffectNoir", @"Sunrise": @"CIPhotoEffectTransfer", @"Artistic": @"CIColorPosterize", }; NSString *filterName = filters[filter]; CIFilter *filter = [CIFilter filterWithName:filterName]; _playerItem.videoComposition = [AVVideoComposition videoCompositionWithAsset:asset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest *_Nonnull request) { if (filter == nil) { [request finishWithImage:request.sourceImage context:nil]; } else { CIImage *image = request.sourceImage.imageByClampingToExtent; [filter setValue:image forKey:kCIInputImageKey]; CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent]; [request finishWithImage:output context:nil]; } }]; } } #pragma mark - RCTVideoPlayerViewControllerDelegate - (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController { if (_playerViewController == playerViewController && _fullscreenPlayerPresented && self.onVideoFullscreenPlayerWillDismiss) { - (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController { if (_playerViewController == playerViewController && _fullscreenPlayerPresented && self.onVideoFullscreenPlayerWillDismiss) { self.onVideoFullscreenPlayerWillDismiss(@{@"target": self.reactTag}); } } - (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController { if (_playerViewController == playerViewController && _fullscreenPlayerPresented) { - (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController { if (_playerViewController == playerViewController && _fullscreenPlayerPresented) { _fullscreenPlayerPresented = false; _presentingViewController = nil; _playerViewController = nil; Loading @@ -1234,41 +1302,54 @@ static int const RCTVideoUnset = -1; #pragma mark - React View Management - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex { - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex { // We are early in the game and somebody wants to set a subview. // That can only be in the context of playerViewController. if (!_controls && !_playerLayer && !_playerViewController) { if( !_controls && !_playerLayer && !_playerViewController ) { [self setControls:true]; } if (_controls) { if( _controls ) { view.frame = self.bounds; [_playerViewController.contentOverlayView insertSubview:view atIndex:atIndex]; } else { } else { RCTLogError(@"video cannot have any subviews"); } return; } - (void)removeReactSubview:(UIView *)subview { if (_controls) { - (void)removeReactSubview:(UIView *)subview { if( _controls ) { [subview removeFromSuperview]; } else { } else { RCTLogError(@"video cannot have any subviews"); } return; } - (void)layoutSubviews { - (void)layoutSubviews { [super layoutSubviews]; if (_controls) { if( _controls ) { _playerViewController.view.frame = self.bounds; // also adjust all subviews of contentOverlayView for (UIView* subview in _playerViewController.contentOverlayView.subviews) { subview.frame = self.bounds; } } else { } else { [CATransaction begin]; [CATransaction setAnimationDuration:0]; _playerLayer.frame = self.bounds; Loading @@ -1278,7 +1359,8 @@ static int const RCTVideoUnset = -1; #pragma mark - Lifecycle - (void)removeFromSuperview { - (void)removeFromSuperview { [_player pause]; if (_playbackRateObserverRegistered) { [_player removeObserver:self forKeyPath:playbackRate context:nil]; Loading @@ -1304,6 +1386,8 @@ static int const RCTVideoUnset = -1; [super removeFromSuperview]; } #pragma mark - Export - (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { AVAsset *asset = _playerItem.asset; Loading Loading
ios/Video/RCTVideo.m +215 −131 Changes for ios/Video/RCTVideo.m: 215 added lines, 131 removed lines. Original line number Diff line number Diff line Loading @@ -13,7 +13,6 @@ static NSString *const readyForDisplayKeyPath = @"readyForDisplay"; static NSString *const playbackRate = @"rate"; static NSString *const timedMetadata = @"timedMetadata"; static NSString *const externalPlaybackActive = @"externalPlaybackActive"; static NSDictionary *filters = nil; static int const RCTVideoUnset = -1; Loading @@ -23,7 +22,8 @@ static int const RCTVideoUnset = -1; #define DebugLog(...) (void)0 #endif @implementation RCTVideo { @implementation RCTVideo { AVPlayer *_player; AVPlayerItem *_playerItem; BOOL _playerItemObserversSet; Loading Loading @@ -73,18 +73,9 @@ static int const RCTVideoUnset = -1; #endif } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super init])) { filters = @{ @"Normal": @"", @"Country": @"CISepiaTone", @"Winter": @"CIPhotoEffectProcess", @"Black N White": @"CIPhotoEffectNoir", @"Sunrise": @"CIPhotoEffectTransfer", @"Artistic": @"CIColorPosterize", }; _eventDispatcher = eventDispatcher; _playbackRateObserverRegistered = NO; Loading Loading @@ -148,40 +139,45 @@ static int const RCTVideoUnset = -1; ** Get the duration for a AVPlayerItem. ** ------------------------------------------------------- */ - (CMTime)playerItemDuration { - (CMTime)playerItemDuration { AVPlayerItem *playerItem = [_player currentItem]; if (playerItem.status == AVPlayerItemStatusReadyToPlay) { if (playerItem.status == AVPlayerItemStatusReadyToPlay) { return([playerItem duration]); } return(kCMTimeInvalid); } - (CMTimeRange)playerItemSeekableTimeRange { - (CMTimeRange)playerItemSeekableTimeRange { AVPlayerItem *playerItem = [_player currentItem]; if (playerItem.status == AVPlayerItemStatusReadyToPlay) { if (playerItem.status == AVPlayerItemStatusReadyToPlay) { return [playerItem seekableTimeRanges].firstObject.CMTimeRangeValue; } return (kCMTimeRangeZero); } - (void)addPlayerTimeObserver { -(void)addPlayerTimeObserver { const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; // @see endScrubbing in AVPlayerDemoPlaybackViewController.m // of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html __weak RCTVideo *weakSelf = self; _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC) queue:NULL usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } ]; } /* Cancels the previously registered time observer. */ - (void)removePlayerTimeObserver { if (_timeObserver) { -(void)removePlayerTimeObserver { if (_timeObserver) { [_player removeTimeObserver:_timeObserver]; _timeObserver = nil; } Loading @@ -189,7 +185,8 @@ static int const RCTVideoUnset = -1; #pragma mark - Progress - (void)dealloc { - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [self removePlayerLayer]; [self removePlayerItemObservers]; Loading @@ -198,21 +195,24 @@ static int const RCTVideoUnset = -1; #pragma mark - App lifecycle handlers - (void)applicationWillResignActive:(NSNotification *)notification { - (void)applicationWillResignActive:(NSNotification *)notification { if (_playInBackground || _playWhenInactive || _paused) return; [_player pause]; [_player setRate:0.0]; } - (void)applicationDidEnterBackground:(NSNotification *)notification { - (void)applicationDidEnterBackground:(NSNotification *)notification { if (_playInBackground) { // Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html [_playerLayer setPlayer:nil]; } } - (void)applicationWillEnterForeground:(NSNotification *)notification { - (void)applicationWillEnterForeground:(NSNotification *)notification { [self applyModifiers]; if (_playInBackground) { [_playerLayer setPlayer:_player]; Loading @@ -221,7 +221,8 @@ static int const RCTVideoUnset = -1; #pragma mark - Audio events - (void)audioRouteChanged:(NSNotification *)notification { - (void)audioRouteChanged:(NSNotification *)notification { NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey]; NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey]; if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { Loading @@ -231,7 +232,8 @@ static int const RCTVideoUnset = -1; #pragma mark - Progress - (void)sendProgressUpdate { - (void)sendProgressUpdate { AVPlayerItem *video = [_player currentItem]; if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) { return; Loading Loading @@ -265,7 +267,8 @@ static int const RCTVideoUnset = -1; * * \returns The playable duration of the current player item in seconds. */ - (NSNumber *)calculatePlayableDuration { - (NSNumber *)calculatePlayableDuration { AVPlayerItem *video = _player.currentItem; if (video.status == AVPlayerItemStatusReadyToPlay) { __block CMTimeRange effectiveTimeRange; Loading @@ -284,15 +287,18 @@ static int const RCTVideoUnset = -1; return [NSNumber numberWithInteger:0]; } - (NSNumber *)calculateSeekableDuration { - (NSNumber *)calculateSeekableDuration { CMTimeRange timeRange = [self playerItemSeekableTimeRange]; if (CMTIME_IS_NUMERIC(timeRange.duration)) { if (CMTIME_IS_NUMERIC(timeRange.duration)) { return [NSNumber numberWithFloat:CMTimeGetSeconds(timeRange.duration)]; } return [NSNumber numberWithInteger:0]; } - (void)addPlayerItemObservers { - (void)addPlayerItemObservers { [_playerItem addObserver:self forKeyPath:statusKeyPath options:0 context:nil]; [_playerItem addObserver:self forKeyPath:playbackBufferEmptyKeyPath options:0 context:nil]; [_playerItem addObserver:self forKeyPath:playbackLikelyToKeepUpKeyPath options:0 context:nil]; Loading @@ -303,7 +309,8 @@ static int const RCTVideoUnset = -1; /* Fixes https://github.com/brentvatne/react-native-video/issues/43 * Crashes caused when trying to remove the observer when there is no * observer set */ - (void)removePlayerItemObservers { - (void)removePlayerItemObservers { if (_playerItemObserversSet) { [_playerItem removeObserver:self forKeyPath:statusKeyPath]; [_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath]; Loading @@ -315,7 +322,8 @@ static int const RCTVideoUnset = -1; #pragma mark - Player and source - (void)setSrc:(NSDictionary *)source { - (void)setSrc:(NSDictionary *)source { [self removePlayerLayer]; [self removePlayerTimeObserver]; [self removePlayerItemObservers]; Loading Loading @@ -388,7 +396,8 @@ static int const RCTVideoUnset = -1; return nil; } - (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSDictionary *__nullable)assetOptions withCallback:(void (^)(AVPlayerItem *))handler { - (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSDictionary * __nullable)assetOptions withCallback:(void(^)(AVPlayerItem *))handler { if (!_textTracks) { handler([AVPlayerItem playerItemWithAsset:asset]); return; Loading Loading @@ -438,7 +447,8 @@ static int const RCTVideoUnset = -1; handler([AVPlayerItem playerItemWithAsset:mixComposition]); } - (void)playerItemForSource:(NSDictionary *)source withCallback:(void (^)(AVPlayerItem *))handler { - (void)playerItemForSource:(NSDictionary *)source withCallback:(void(^)(AVPlayerItem *))handler { bool isNetwork = [RCTConvert BOOL:[source objectForKey:@"isNetwork"]]; bool isAsset = [RCTConvert BOOL:[source objectForKey:@"isAsset"]]; NSString *uri = [source objectForKey:@"uri"]; Loading Loading @@ -540,7 +550,8 @@ static int const RCTVideoUnset = -1; #endif - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == _playerItem) { // When timeMetadata is read the event onTimedMetadata is triggered if ([keyPath isEqualToString:timedMetadata]) { Loading Loading @@ -586,7 +597,8 @@ static int const RCTVideoUnset = -1; if ((videoTrack.naturalSize.width == preferredTransform.tx && videoTrack.naturalSize.height == preferredTransform.ty) || (preferredTransform.tx == 0 && preferredTransform.ty == 0)) { || (preferredTransform.tx == 0 && preferredTransform.ty == 0)) { orientation = @"landscape"; } else { orientation = @"portrait"; Loading Loading @@ -650,7 +662,8 @@ static int const RCTVideoUnset = -1; } _playbackStalled = NO; } } else if ([keyPath isEqualToString:externalPlaybackActive]) { } else if([keyPath isEqualToString:externalPlaybackActive]) { if(self.onVideoExternalPlaybackChange) { self.onVideoExternalPlaybackChange(@{@"isExternalPlaybackActive": [NSNumber numberWithBool:_player.isExternalPlaybackActive], @"target": self.reactTag}); Loading @@ -661,7 +674,8 @@ static int const RCTVideoUnset = -1; } } - (void)attachListeners { - (void)attachListeners { // listen for end of file [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification Loading @@ -680,14 +694,16 @@ static int const RCTVideoUnset = -1; object:nil]; } - (void)playbackStalled:(NSNotification *)notification { - (void)playbackStalled:(NSNotification *)notification { if(self.onPlaybackStalled) { self.onPlaybackStalled(@{@"target": self.reactTag}); } _playbackStalled = YES; } - (void)playerItemDidReachEnd:(NSNotification *)notification { - (void)playerItemDidReachEnd:(NSNotification *)notification { if(self.onVideoEnd) { self.onVideoEnd(@{@"target": self.reactTag}); } Loading @@ -703,34 +719,43 @@ static int const RCTVideoUnset = -1; #pragma mark - Prop setters - (void)setResizeMode:(NSString *)mode { if (_controls) { - (void)setResizeMode:(NSString*)mode { if( _controls ) { _playerViewController.videoGravity = mode; } else { } else { _playerLayer.videoGravity = mode; } _resizeMode = mode; } - (void)setPlayInBackground:(BOOL)playInBackground { - (void)setPlayInBackground:(BOOL)playInBackground { _playInBackground = playInBackground; } - (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback { - (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback { _allowsExternalPlayback = allowsExternalPlayback; _player.allowsExternalPlayback = _allowsExternalPlayback; } - (void)setPlayWhenInactive:(BOOL)playWhenInactive { - (void)setPlayWhenInactive:(BOOL)playWhenInactive { _playWhenInactive = playWhenInactive; } - (void)setIgnoreSilentSwitch:(NSString *)ignoreSilentSwitch { - (void)setIgnoreSilentSwitch:(NSString *)ignoreSilentSwitch { _ignoreSilentSwitch = ignoreSilentSwitch; [self applyModifiers]; } - (void)setPaused:(BOOL)paused { - (void)setPaused:(BOOL)paused { if (paused) { [_player pause]; [_player setRate:0.0]; Loading @@ -747,11 +772,13 @@ static int const RCTVideoUnset = -1; _paused = paused; } - (float)getCurrentTime { - (float)getCurrentTime { return _playerItem != NULL ? CMTimeGetSeconds(_playerItem.currentTime) : 0; } - (void)setCurrentTime:(float)currentTime { - (void)setCurrentTime:(float)currentTime { NSDictionary *info = @{ @"time": [NSNumber numberWithFloat:currentTime], @"tolerance": [NSNumber numberWithInt:100] Loading @@ -759,7 +786,8 @@ static int const RCTVideoUnset = -1; [self setSeek:info]; } - (void)setSeek:(NSDictionary *)info { - (void)setSeek:(NSDictionary *)info { NSNumber *seekTime = info[@"time"]; NSNumber *seekTolerance = info[@"tolerance"]; Loading Loading @@ -801,22 +829,26 @@ static int const RCTVideoUnset = -1; } } - (void)setRate:(float)rate { - (void)setRate:(float)rate { _rate = rate; [self applyModifiers]; } - (void)setMuted:(BOOL)muted { - (void)setMuted:(BOOL)muted { _muted = muted; [self applyModifiers]; } - (void)setVolume:(float)volume { - (void)setVolume:(float)volume { _volume = volume; [self applyModifiers]; } - (void)applyModifiers { - (void)applyModifiers { if (_muted) { [_player setVolume:0]; [_player setMuted:YES]; Loading @@ -840,7 +872,8 @@ static int const RCTVideoUnset = -1; } - (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)characteristic withCriteria:(NSDictionary *)criteria { withCriteria:(NSDictionary *)criteria { NSString *type = criteria[@"type"]; AVMediaSelectionGroup *group = [_player.currentItem.asset mediaSelectionGroupForMediaCharacteristic:characteristic]; Loading Loading @@ -1011,14 +1044,16 @@ static int const RCTVideoUnset = -1; [_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group]; } - (void)setTextTracks:(NSArray *)textTracks; { - (void)setTextTracks:(NSArray*) textTracks; { _textTracks = textTracks; // in case textTracks was set after selectedTextTrack if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack]; } - (NSArray *)getAudioTrackInfo { - (NSArray *)getAudioTrackInfo { NSMutableArray *audioTracks = [[NSMutableArray alloc] init]; AVMediaSelectionGroup *group = [_player.currentItem.asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; Loading @@ -1040,7 +1075,8 @@ static int const RCTVideoUnset = -1; return audioTracks; } - (NSArray *)getTextTrackInfo { - (NSArray *)getTextTrackInfo { // if sideloaded, textTracks will already be set if (_textTracks) return _textTracks; Loading @@ -1066,14 +1102,17 @@ static int const RCTVideoUnset = -1; return textTracks; } - (BOOL)getFullscreen { - (BOOL)getFullscreen { return _fullscreenPlayerPresented; } - (void)setFullscreen:(BOOL) fullscreen { if (fullscreen && !_fullscreenPlayerPresented && _player) { if( fullscreen && !_fullscreenPlayerPresented && _player ) { // Ensure player view controller is not null if (!_playerViewController) { if( !_playerViewController ) { [self usePlayerViewController]; } // Set presentation style to fullscreen Loading @@ -1081,14 +1120,17 @@ static int const RCTVideoUnset = -1; // Find the nearest view controller UIViewController *viewController = [self firstAvailableUIViewController]; if (!viewController) { if( !viewController ) { UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; viewController = keyWindow.rootViewController; if (viewController.childViewControllers.count > 0) { if( viewController.childViewControllers.count > 0 ) { viewController = viewController.childViewControllers.lastObject; } } if (viewController) { if( viewController ) { _presentingViewController = viewController; if(self.onVideoFullscreenPlayerWillPresent) { self.onVideoFullscreenPlayerWillPresent(@{@"target": self.reactTag}); Loading @@ -1101,7 +1143,9 @@ static int const RCTVideoUnset = -1; } }]; } } else if (!fullscreen && _fullscreenPlayerPresented) { } else if ( !fullscreen && _fullscreenPlayerPresented ) { [self videoPlayerViewControllerWillDismiss:_playerViewController]; [_presentingViewController dismissViewControllerAnimated:true completion:^{ [self videoPlayerViewControllerDidDismiss:_playerViewController]; Loading @@ -1109,43 +1153,6 @@ static int const RCTVideoUnset = -1; } } - (void)setFilter:(NSString *)filter { _filter = filter; AVAsset *asset = _playerItem.asset; if (asset != nil) { NSString *filterName = filters[filter]; CIFilter *filter = [CIFilter filterWithName:filterName]; _playerItem.videoComposition = [AVVideoComposition videoCompositionWithAsset:asset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest *_Nonnull request) { if (filter == nil) { [request finishWithImage:request.sourceImage context:nil]; } else { CIImage *image = request.sourceImage.imageByClampingToExtent; [filter setValue:image forKey:kCIInputImageKey]; CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent]; [request finishWithImage:output context:nil]; } }]; } } - (void)setFullscreenOrientation:(NSString *)orientation { _fullscreenOrientation = orientation; if (_fullscreenPlayerPresented) { Loading @@ -1153,8 +1160,10 @@ static int const RCTVideoUnset = -1; } } - (void)usePlayerViewController { if (_player) { - (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 Loading @@ -1163,8 +1172,10 @@ static int const RCTVideoUnset = -1; } } - (void)usePlayerLayer { if (_player) { - (void)usePlayerLayer { if( _player ) { _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; _playerLayer.frame = self.bounds; _playerLayer.needsDisplayOnBoundsChange = YES; Loading @@ -1180,13 +1191,18 @@ static int const RCTVideoUnset = -1; } } - (void)setControls:(BOOL)controls { if (_controls != controls || (!_playerLayer && !_playerViewController)) { - (void)setControls:(BOOL)controls { if( _controls != controls || (!_playerLayer && !_playerViewController) ) { _controls = controls; if (_controls) { if( _controls ) { [self removePlayerLayer]; [self usePlayerViewController]; } else { } else { [_playerViewController.view removeFromSuperview]; _playerViewController = nil; [self usePlayerLayer]; Loading @@ -1194,7 +1210,8 @@ static int const RCTVideoUnset = -1; } } - (void)setProgressUpdateInterval:(float)progressUpdateInterval { - (void)setProgressUpdateInterval:(float)progressUpdateInterval { _progressUpdateInterval = progressUpdateInterval; if (_timeObserver) { Loading @@ -1203,7 +1220,8 @@ static int const RCTVideoUnset = -1; } } - (void)removePlayerLayer { - (void)removePlayerLayer { [_playerLayer removeFromSuperlayer]; if (_playerLayerObserverSet) { [_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath]; Loading @@ -1212,16 +1230,66 @@ static int const RCTVideoUnset = -1; _playerLayer = nil; } - (void)setFilter:(NSString *)filter { _filter = filter; AVAsset *asset = _playerItem.asset; if (asset != nil) { NSDictionary *filters = @{ @"Normal": @"", @"Country": @"CISepiaTone", @"Winter": @"CIPhotoEffectProcess", @"Black N White": @"CIPhotoEffectNoir", @"Sunrise": @"CIPhotoEffectTransfer", @"Artistic": @"CIColorPosterize", }; NSString *filterName = filters[filter]; CIFilter *filter = [CIFilter filterWithName:filterName]; _playerItem.videoComposition = [AVVideoComposition videoCompositionWithAsset:asset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest *_Nonnull request) { if (filter == nil) { [request finishWithImage:request.sourceImage context:nil]; } else { CIImage *image = request.sourceImage.imageByClampingToExtent; [filter setValue:image forKey:kCIInputImageKey]; CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent]; [request finishWithImage:output context:nil]; } }]; } } #pragma mark - RCTVideoPlayerViewControllerDelegate - (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController { if (_playerViewController == playerViewController && _fullscreenPlayerPresented && self.onVideoFullscreenPlayerWillDismiss) { - (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController { if (_playerViewController == playerViewController && _fullscreenPlayerPresented && self.onVideoFullscreenPlayerWillDismiss) { self.onVideoFullscreenPlayerWillDismiss(@{@"target": self.reactTag}); } } - (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController { if (_playerViewController == playerViewController && _fullscreenPlayerPresented) { - (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController { if (_playerViewController == playerViewController && _fullscreenPlayerPresented) { _fullscreenPlayerPresented = false; _presentingViewController = nil; _playerViewController = nil; Loading @@ -1234,41 +1302,54 @@ static int const RCTVideoUnset = -1; #pragma mark - React View Management - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex { - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex { // We are early in the game and somebody wants to set a subview. // That can only be in the context of playerViewController. if (!_controls && !_playerLayer && !_playerViewController) { if( !_controls && !_playerLayer && !_playerViewController ) { [self setControls:true]; } if (_controls) { if( _controls ) { view.frame = self.bounds; [_playerViewController.contentOverlayView insertSubview:view atIndex:atIndex]; } else { } else { RCTLogError(@"video cannot have any subviews"); } return; } - (void)removeReactSubview:(UIView *)subview { if (_controls) { - (void)removeReactSubview:(UIView *)subview { if( _controls ) { [subview removeFromSuperview]; } else { } else { RCTLogError(@"video cannot have any subviews"); } return; } - (void)layoutSubviews { - (void)layoutSubviews { [super layoutSubviews]; if (_controls) { if( _controls ) { _playerViewController.view.frame = self.bounds; // also adjust all subviews of contentOverlayView for (UIView* subview in _playerViewController.contentOverlayView.subviews) { subview.frame = self.bounds; } } else { } else { [CATransaction begin]; [CATransaction setAnimationDuration:0]; _playerLayer.frame = self.bounds; Loading @@ -1278,7 +1359,8 @@ static int const RCTVideoUnset = -1; #pragma mark - Lifecycle - (void)removeFromSuperview { - (void)removeFromSuperview { [_player pause]; if (_playbackRateObserverRegistered) { [_player removeObserver:self forKeyPath:playbackRate context:nil]; Loading @@ -1304,6 +1386,8 @@ static int const RCTVideoUnset = -1; [super removeFromSuperview]; } #pragma mark - Export - (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { AVAsset *asset = _playerItem.asset; Loading