diff --git a/Examples/VideoPlayer/android/app/build.gradle b/Examples/VideoPlayer/android/app/build.gradle index 2afbba5c..888b3be6 100644 --- a/Examples/VideoPlayer/android/app/build.gradle +++ b/Examples/VideoPlayer/android/app/build.gradle @@ -114,5 +114,5 @@ dependencies { compile project(':react-native-video') compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" - compile "com.facebook.react:react-native:0.19.+" + compile "com.facebook.react:react-native:0.20.+" } diff --git a/Examples/VideoPlayer/index.android.js b/Examples/VideoPlayer/index.android.js index 21ee336a..c5a000a5 100644 --- a/Examples/VideoPlayer/index.android.js +++ b/Examples/VideoPlayer/index.android.js @@ -1,8 +1,11 @@ 'use strict'; -import React, { +import { + Component +} from 'react'; + +import { AppRegistry, - Component, StyleSheet, Text, TouchableOpacity, diff --git a/Examples/VideoPlayer/index.ios.js b/Examples/VideoPlayer/index.ios.js index e6e5f838..6fed5dd7 100644 --- a/Examples/VideoPlayer/index.ios.js +++ b/Examples/VideoPlayer/index.ios.js @@ -1,9 +1,11 @@ 'use strict'; - import React, { + Component +} from 'react'; + +import { AlertIOS, AppRegistry, - Component, StyleSheet, Text, TouchableOpacity, diff --git a/RCTVideo.m b/RCTVideo.m index 6664532b..daa9d16d 100644 --- a/RCTVideo.m +++ b/RCTVideo.m @@ -7,6 +7,8 @@ static NSString *const statusKeyPath = @"status"; static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp"; static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; +static NSString *const readyForDisplayKeyPath = @"readyForDisplay"; +static NSString *const playbackRate = @"rate"; @implementation RCTVideo { @@ -36,6 +38,7 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; BOOL _muted; BOOL _paused; BOOL _repeat; + BOOL _playbackStalled; BOOL _playInBackground; BOOL _playWhenInactive; NSString * _resizeMode; @@ -48,6 +51,7 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; if ((self = [super init])) { _eventDispatcher = eventDispatcher; + _playbackStalled = NO; _rate = 1.0; _volume = 1.0; _resizeMode = @"AVLayerVideoGravityResizeAspectFill"; @@ -233,13 +237,13 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; [self addPlayerItemObservers]; [_player pause]; - [_playerLayer removeFromSuperlayer]; - _playerLayer = nil; + [self removePlayerLayer]; [_playerViewController.view removeFromSuperview]; _playerViewController = nil; _player = [AVPlayer playerWithPlayerItem:_playerItem]; _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; + [_player addObserver:self forKeyPath:playbackRate options:0 context:nil]; const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; // @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html @@ -290,9 +294,21 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; NSObject *width = @"undefined"; NSObject *height = @"undefined"; + NSString *orientation = @"undefined"; + if ([_playerItem.asset tracksWithMediaType:AVMediaTypeVideo].count > 0) { - width = [NSNumber numberWithFloat:[_playerItem.asset tracksWithMediaType:AVMediaTypeVideo][0].naturalSize.width]; - height = [NSNumber numberWithFloat:[_playerItem.asset tracksWithMediaType:AVMediaTypeVideo][0].naturalSize.height]; + AVAssetTrack *videoTrack = [[_playerItem.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; + width = [NSNumber numberWithFloat:videoTrack.naturalSize.width]; + height = [NSNumber numberWithFloat:videoTrack.naturalSize.height]; + CGAffineTransform preferredTransform = [videoTrack preferredTransform]; + + if ((videoTrack.naturalSize.width == preferredTransform.tx + && videoTrack.naturalSize.height == preferredTransform.ty) + || (preferredTransform.tx == 0 && preferredTransform.ty == 0)) + { + orientation = @"landscape"; + } else + orientation = @"portrait"; } [_eventDispatcher sendInputEventWithName:@"onVideoLoad" @@ -306,7 +322,8 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; @"canStepForward": [NSNumber numberWithBool:_playerItem.canStepForward], @"naturalSize": @{ @"width": width, - @"height": height + @"height": height, + @"orientation": orientation }, @"target": self.reactTag}]; @@ -328,6 +345,25 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; } _playerBufferEmpty = NO; } + } else if (object == _playerLayer) { + if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey]) { + if([change objectForKey:NSKeyValueChangeNewKey]) { + [_eventDispatcher sendInputEventWithName:@"onReadyForDisplay" + body:@{@"target": self.reactTag}]; + } + } + } else if (object == _player) { + if([keyPath isEqualToString:playbackRate]) { + [_eventDispatcher sendInputEventWithName:@"onPlaybackRateChange" + body:@{@"playbackRate": [NSNumber numberWithFloat:_player.rate], + @"target": self.reactTag}]; + if(_playbackStalled && _player.rate > 0) { + [_eventDispatcher sendInputEventWithName:@"onPlaybackResume" + body:@{@"playbackRate": [NSNumber numberWithFloat:_player.rate], + @"target": self.reactTag}]; + _playbackStalled = NO; + } + } } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } @@ -340,6 +376,16 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:[_player currentItem]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playbackStalled:) + name:AVPlayerItemPlaybackStalledNotification + object:nil]; +} + +- (void)playbackStalled:(NSNotification *)notification +{ + [_eventDispatcher sendInputEventWithName:@"onPlaybackStalled" body:@{@"target": self.reactTag}]; + _playbackStalled = YES; } - (void)playerItemDidReachEnd:(NSNotification *)notification @@ -534,6 +580,8 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; _playerLayer.frame = self.bounds; _playerLayer.needsDisplayOnBoundsChange = YES; + + [_playerLayer addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil]; [self.layer addSublayer:_playerLayer]; self.layer.needsDisplayOnBoundsChange = YES; @@ -547,8 +595,7 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; _controls = controls; if( _controls ) { - [_playerLayer removeFromSuperlayer]; - _playerLayer = nil; + [self removePlayerLayer]; [self usePlayerViewController]; } else @@ -560,6 +607,13 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; } } +- (void)removePlayerLayer +{ + [_playerLayer removeFromSuperlayer]; + [_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath]; + _playerLayer = nil; +} + #pragma mark - RCTVideoPlayerViewControllerDelegate - (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController @@ -643,10 +697,10 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; - (void)removeFromSuperview { [_player pause]; + [_player removeObserver:self forKeyPath:playbackRate]; _player = nil; - [_playerLayer removeFromSuperlayer]; - _playerLayer = nil; + [self removePlayerLayer]; [_playerViewController.view removeFromSuperview]; _playerViewController = nil; diff --git a/RCTVideoManager.m b/RCTVideoManager.m index 3f55b599..3e11d412 100644 --- a/RCTVideoManager.m +++ b/RCTVideoManager.m @@ -28,7 +28,11 @@ RCT_EXPORT_MODULE(); @"onVideoFullscreenPlayerWillPresent", @"onVideoFullscreenPlayerDidPresent", @"onVideoFullscreenPlayerWillDismiss", - @"onVideoFullscreenPlayerDidDismiss" + @"onVideoFullscreenPlayerDidDismiss", + @"onReadyForDisplay", + @"onPlaybackStalled", + @"onPlaybackResume", + @"onPlaybackRateChange" ]; } diff --git a/README.md b/README.md index d1d4df1f..48cfbb3e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## react-native-video -A