diff --git a/Examples/VideoPlayer/.gitignore b/Examples/VideoPlayer/.gitignore index 07e4fe72..52594ec7 100644 --- a/Examples/VideoPlayer/.gitignore +++ b/Examples/VideoPlayer/.gitignore @@ -1 +1,3 @@ node_modules/**/* +VideoPlayer.xcodeproj/project.xcworkspace/**/* +VideoPlayer.xcodeproj/xcuserdata/**/* \ No newline at end of file diff --git a/Examples/VideoPlayer/VideoPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/VideoPlayer/VideoPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 4ac4c91f..00000000 --- a/Examples/VideoPlayer/VideoPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Examples/VideoPlayer/VideoPlayer.xcodeproj/project.xcworkspace/xcshareddata/VideoPlayer.xccheckout b/Examples/VideoPlayer/VideoPlayer.xcodeproj/project.xcworkspace/xcshareddata/VideoPlayer.xccheckout deleted file mode 100644 index 6ce4fd2f..00000000 --- a/Examples/VideoPlayer/VideoPlayer.xcodeproj/project.xcworkspace/xcshareddata/VideoPlayer.xccheckout +++ /dev/null @@ -1,41 +0,0 @@ - - - - - IDESourceControlProjectFavoriteDictionaryKey - - IDESourceControlProjectIdentifier - 9B1B824B-5918-47F5-B27A-FA559B781ACC - IDESourceControlProjectName - VideoPlayer - IDESourceControlProjectOriginsDictionary - - D05F265D0E65FFE673B2E9F5A4680A7F46E27F31 - github.com:johanneslumpe/react-native-video.git - - IDESourceControlProjectPath - Examples/VideoPlayer/VideoPlayer.xcodeproj - IDESourceControlProjectRelativeInstallPathDictionary - - D05F265D0E65FFE673B2E9F5A4680A7F46E27F31 - ../../../.. - - IDESourceControlProjectURL - github.com:johanneslumpe/react-native-video.git - IDESourceControlProjectVersion - 111 - IDESourceControlProjectWCCIdentifier - D05F265D0E65FFE673B2E9F5A4680A7F46E27F31 - IDESourceControlProjectWCConfigurations - - - IDESourceControlRepositoryExtensionIdentifierKey - public.vcs.git - IDESourceControlWCCIdentifierKey - D05F265D0E65FFE673B2E9F5A4680A7F46E27F31 - IDESourceControlWCCName - react-native-video - - - - diff --git a/Examples/VideoPlayer/VideoPlayer.xcodeproj/project.xcworkspace/xcuserdata/brentvatne.xcuserdatad/UserInterfaceState.xcuserstate b/Examples/VideoPlayer/VideoPlayer.xcodeproj/project.xcworkspace/xcuserdata/brentvatne.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 01791fee..00000000 Binary files a/Examples/VideoPlayer/VideoPlayer.xcodeproj/project.xcworkspace/xcuserdata/brentvatne.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/Examples/VideoPlayer/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer.xcscheme b/Examples/VideoPlayer/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer.xcscheme deleted file mode 100644 index b089e027..00000000 --- a/Examples/VideoPlayer/VideoPlayer.xcodeproj/xcshareddata/xcschemes/VideoPlayer.xcscheme +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Examples/VideoPlayer/VideoPlayer.xcodeproj/xcuserdata/brentvatne.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Examples/VideoPlayer/VideoPlayer.xcodeproj/xcuserdata/brentvatne.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist deleted file mode 100644 index b1a4c519..00000000 --- a/Examples/VideoPlayer/VideoPlayer.xcodeproj/xcuserdata/brentvatne.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - diff --git a/Examples/VideoPlayer/VideoPlayer.xcodeproj/xcuserdata/brentvatne.xcuserdatad/xcschemes/xcschememanagement.plist b/Examples/VideoPlayer/VideoPlayer.xcodeproj/xcuserdata/brentvatne.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index bc98ef59..00000000 --- a/Examples/VideoPlayer/VideoPlayer.xcodeproj/xcuserdata/brentvatne.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - SchemeUserState - - VideoPlayer.xcscheme_^#shared#^_ - - orderHint - 0 - - - SuppressBuildableAutocreation - - 13B07F861A680F5B00A75B9A - - primary - - - - - diff --git a/Examples/VideoPlayer/index.ios.js b/Examples/VideoPlayer/index.ios.js index c77f53d5..3e97d984 100644 --- a/Examples/VideoPlayer/index.ios.js +++ b/Examples/VideoPlayer/index.ios.js @@ -21,6 +21,9 @@ var VideoPlayer = React.createClass({ resizeMode: 'contain', duration: 0.0, currentTime: 0.0, + controls: false, + paused: true, + skin: 'custom' } }, @@ -40,6 +43,21 @@ var VideoPlayer = React.createClass({ } }, + renderSkinControl(skin) { + var isSelected = this.state.skin == skin; + var selectControls = skin == 'native' || skin == 'embed'; + return ( + { this.setState({ + controls: selectControls, + skin: skin + }) }}> + + {skin} + + + ); + }, + renderRateControl(rate) { var isSelected = (this.state.rate == rate); @@ -76,7 +94,7 @@ var VideoPlayer = React.createClass({ ) }, - render() { + renderCustomSkin() { var flexCompleted = this.getCurrentTimePercentage() * 100; var flexRemaining = (1 - this.getCurrentTimePercentage()) * 100; @@ -97,12 +115,17 @@ var VideoPlayer = React.createClass({ + + + {this.renderSkinControl('custom')} + {this.renderSkinControl('native')} + {this.renderSkinControl('embed')} + + - {this.renderRateControl(0.25)} {this.renderRateControl(0.5)} {this.renderRateControl(1.0)} - {this.renderRateControl(1.5)} {this.renderRateControl(2.0)} @@ -128,7 +151,63 @@ var VideoPlayer = React.createClass({ ); + }, + + renderNativeSkin() { + var videoStyle = this.state.skin == 'embed' ? styles.nativeVideoControls : styles.fullScreen; + return ( + + + + + + + {this.renderSkinControl('custom')} + {this.renderSkinControl('native')} + {this.renderSkinControl('embed')} + + + + + {this.renderRateControl(0.5)} + {this.renderRateControl(1.0)} + {this.renderRateControl(2.0)} + + + + {this.renderVolumeControl(0.5)} + {this.renderVolumeControl(1)} + {this.renderVolumeControl(1.5)} + + + + {this.renderResizeModeControl('cover')} + {this.renderResizeModeControl('contain')} + {this.renderResizeModeControl('stretch')} + + + + + + ); + }, + + render() { + return this.state.controls ? this.renderNativeSkin() : this.renderCustomSkin(); } + }); @@ -150,9 +229,9 @@ var styles = StyleSheet.create({ backgroundColor: "transparent", borderRadius: 5, position: 'absolute', - bottom: 20, - left: 20, - right: 20, + bottom: 44, + left: 4, + right: 4, }, progress: { flex: 1, @@ -171,10 +250,14 @@ var styles = StyleSheet.create({ generalControls: { flex: 1, flexDirection: 'row', - borderRadius: 4, overflow: 'hidden', paddingBottom: 10, }, + skinControl: { + flex: 1, + flexDirection: 'row', + justifyContent: 'center', + }, rateControl: { flex: 1, flexDirection: 'row', @@ -189,7 +272,7 @@ var styles = StyleSheet.create({ flex: 1, flexDirection: 'row', alignItems: 'center', - justifyContent: 'center', + justifyContent: 'center' }, controlOption: { alignSelf: 'center', @@ -199,6 +282,10 @@ var styles = StyleSheet.create({ paddingRight: 2, lineHeight: 12, }, + nativeVideoControls: { + top: 184, + height: 300 + } }); AppRegistry.registerComponent('VideoPlayer', () => VideoPlayer); diff --git a/Examples/VideoPlayer/node_modules/react-native-video/RCTVideo.xcodeproj/project.pbxproj b/Examples/VideoPlayer/node_modules/react-native-video/RCTVideo.xcodeproj/project.pbxproj deleted file mode 100644 index e6ddaaf1..00000000 --- a/Examples/VideoPlayer/node_modules/react-native-video/RCTVideo.xcodeproj/project.pbxproj +++ /dev/null @@ -1,260 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - BBD49E3F1AC8DEF000610F8E /* RCTVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = BBD49E3A1AC8DEF000610F8E /* RCTVideo.m */; }; - BBD49E401AC8DEF000610F8E /* RCTVideoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BBD49E3C1AC8DEF000610F8E /* RCTVideoManager.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 58B511D91A9E6C8500147676 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; - dstSubfolderSpec = 16; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 134814201AA4EA6300B7C361 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; }; - BBD49E391AC8DEF000610F8E /* RCTVideo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVideo.h; sourceTree = ""; }; - BBD49E3A1AC8DEF000610F8E /* RCTVideo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVideo.m; sourceTree = ""; }; - BBD49E3B1AC8DEF000610F8E /* RCTVideoManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVideoManager.h; sourceTree = ""; }; - BBD49E3C1AC8DEF000610F8E /* RCTVideoManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVideoManager.m; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 58B511D81A9E6C8500147676 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 134814211AA4EA7D00B7C361 /* Products */ = { - isa = PBXGroup; - children = ( - 134814201AA4EA6300B7C361 /* libRCTVideo.a */, - ); - name = Products; - sourceTree = ""; - }; - 58B511D21A9E6C8500147676 = { - isa = PBXGroup; - children = ( - BBD49E391AC8DEF000610F8E /* RCTVideo.h */, - BBD49E3A1AC8DEF000610F8E /* RCTVideo.m */, - BBD49E3B1AC8DEF000610F8E /* RCTVideoManager.h */, - BBD49E3C1AC8DEF000610F8E /* RCTVideoManager.m */, - 134814211AA4EA7D00B7C361 /* Products */, - ); - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 58B511DA1A9E6C8500147676 /* RCTVideo */ = { - isa = PBXNativeTarget; - buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTVideo" */; - buildPhases = ( - 58B511D71A9E6C8500147676 /* Sources */, - 58B511D81A9E6C8500147676 /* Frameworks */, - 58B511D91A9E6C8500147676 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = RCTVideo; - productName = RCTDataManager; - productReference = 134814201AA4EA6300B7C361 /* libRCTVideo.a */; - productType = "com.apple.product-type.library.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 58B511D31A9E6C8500147676 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0610; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 58B511DA1A9E6C8500147676 = { - CreatedOnToolsVersion = 6.1.1; - }; - }; - }; - buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTVideo" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 58B511D21A9E6C8500147676; - productRefGroup = 58B511D21A9E6C8500147676; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 58B511DA1A9E6C8500147676 /* RCTVideo */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 58B511D71A9E6C8500147676 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BBD49E3F1AC8DEF000610F8E /* RCTVideo.m in Sources */, - BBD49E401AC8DEF000610F8E /* RCTVideoManager.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 58B511ED1A9E6C8500147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 58B511EE1A9E6C8500147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 58B511F01A9E6C8500147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../React/**", - "$(SRCROOT)/../react-native/React/**", - "$(SRCROOT)/node_modules/react-native/React/**", - ); - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = RCTVideo; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 58B511F11A9E6C8500147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../React/**", - "$(SRCROOT)/../react-native/React/**", - "$(SRCROOT)/node_modules/react-native/React/**", - ); - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = RCTVideo; - SKIP_INSTALL = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTVideo" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511ED1A9E6C8500147676 /* Debug */, - 58B511EE1A9E6C8500147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTVideo" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511F01A9E6C8500147676 /* Debug */, - 58B511F11A9E6C8500147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 58B511D31A9E6C8500147676 /* Project object */; -} diff --git a/Examples/VideoPlayer/package.json b/Examples/VideoPlayer/package.json index 2d097498..6be7c953 100644 --- a/Examples/VideoPlayer/package.json +++ b/Examples/VideoPlayer/package.json @@ -5,8 +5,11 @@ "scripts": { "start": "node_modules/react-native/packager/packager.sh" }, + "scripts": { + "postinstall" : "cp ../../*.js ../../*.m ../../*.h ../../README.md ../../package.json ./node_modules/react-native-video && cp ../../RCTVideo.xcodeproj/project.pbxproj ./node_modules/react-native-video/RCTVideo.xcodeproj/project.pbxproj" + }, "dependencies": { "react-native": "^0.16.0", - "react-native-video": "brentvatne/react-native-video#feature/android-support" + "react-native-video": "brentvatne/react-native-video" } } diff --git a/RCTVideo.h b/RCTVideo.h index 1fc82e43..757ed542 100644 --- a/RCTVideo.h +++ b/RCTVideo.h @@ -1,4 +1,6 @@ #import "RCTView.h" +#import +#import "AVKit/AVKit.h" @class RCTEventDispatcher; @@ -6,4 +8,6 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; +- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem; + @end diff --git a/RCTVideo.m b/RCTVideo.m index 37e09e8b..68199f72 100644 --- a/RCTVideo.m +++ b/RCTVideo.m @@ -3,7 +3,6 @@ #import "RCTBridgeModule.h" #import "RCTEventDispatcher.h" #import "UIView+React.h" -#import static NSString *const statusKeyPath = @"status"; static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp"; @@ -14,6 +13,7 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" AVPlayerItem *_playerItem; BOOL _playerItemObserversSet; AVPlayerLayer *_playerLayer; + AVPlayerViewController *_playerViewController; NSURL *_videoURL; /* Required to publish events */ @@ -24,9 +24,9 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" float _lastSeekTime; /* For sending videoProgress events */ - id _progressUpdateTimer; - int _progressUpdateInterval; - NSDate *_prevProgressUpdateTime; + Float64 _progressUpdateInterval; + BOOL _controls; + id _timeObserver; /* Keep track of any modifiers, need to be applied after each play */ float _volume; @@ -48,6 +48,8 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" _pendingSeek = false; _pendingSeekTime = 0.0f; _lastSeekTime = 0.0f; + _progressUpdateInterval = 250; + _controls = NO; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) @@ -63,6 +65,42 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" return self; } +- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem { + AVPlayerViewController* playerLayer= [[AVPlayerViewController alloc] init]; + playerLayer.view.frame = self.bounds; + playerLayer.player = _player; + playerLayer.view.frame = self.bounds; + return playerLayer; +} + +/* --------------------------------------------------------- + ** Get the duration for a AVPlayerItem. + ** ------------------------------------------------------- */ + +- (CMTime)playerItemDuration +{ + AVPlayerItem *playerItem = [_player currentItem]; + if (playerItem.status == AVPlayerItemStatusReadyToPlay) + { + return([playerItem duration]); + } + + return(kCMTimeInvalid); +} + + +/* Cancels the previously registered time observer. */ +-(void)removePlayerTimeObserver +{ + if (_timeObserver) + { + [_player removeTimeObserver:_timeObserver]; + _timeObserver = nil; + } +} + +#pragma mark - Progress + - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -73,14 +111,13 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" - (void)applicationWillResignActive:(NSNotification *)notification { if (!_paused) { - [self stopProgressTimer]; + [_player pause]; [_player setRate:0.0]; } } - (void)applicationWillEnterForeground:(NSNotification *)notification { - [self startProgressTimer]; [self applyModifiers]; } @@ -88,18 +125,29 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" - (void)sendProgressUpdate { - AVPlayerItem *video = [_player currentItem]; - if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) { - return; - } + AVPlayerItem *video = [_player currentItem]; + if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) { + return; + } + + CMTime playerDuration = [self playerItemDuration]; + if (CMTIME_IS_INVALID(playerDuration)) { + return; + } - if (_prevProgressUpdateTime == nil || (([_prevProgressUpdateTime timeIntervalSinceNow] * -1000.0) >= _progressUpdateInterval)) { - [_eventDispatcher sendInputEventWithName:@"onVideoProgress" - body:@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(video.currentTime)], - @"playableDuration": [self calculatePlayableDuration], - @"target": self.reactTag}]; - _prevProgressUpdateTime = [NSDate date]; - } + CMTime currentTime = _player.currentTime; + const Float64 duration = CMTimeGetSeconds(playerDuration); + const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime); + if( currentTimeSecs >= 0 && currentTimeSecs <= duration) { + [_eventDispatcher sendInputEventWithName:@"onVideoProgress" + body:@{ + @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)], + @"playableDuration": [self calculatePlayableDuration], + @"atValue": [NSNumber numberWithLongLong:currentTime.value], + @"atTimescale": [NSNumber numberWithInt:currentTime.timescale], + @"target": self.reactTag + }]; + } } /*! @@ -127,22 +175,6 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" return [NSNumber numberWithInteger:0]; } -- (void)stopProgressTimer -{ - [_progressUpdateTimer invalidate]; -} - -- (void)startProgressTimer -{ - _progressUpdateInterval = 250; - _prevProgressUpdateTime = nil; - - [self stopProgressTimer]; - - _progressUpdateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(sendProgressUpdate)]; - [_progressUpdateTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; -} - - (void)addPlayerItemObservers { [_playerItem addObserver:self forKeyPath:statusKeyPath options:0 context:nil]; @@ -166,25 +198,27 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" - (void)setSrc:(NSDictionary *)source { + [self removePlayerTimeObserver]; [self removePlayerItemObservers]; _playerItem = [self playerItemForSource:source]; [self addPlayerItemObservers]; [_player pause]; [_playerLayer removeFromSuperlayer]; + _playerLayer = nil; + [_playerViewController.view removeFromSuperview]; + _playerViewController = nil; _player = [AVPlayer playerWithPlayerItem:_playerItem]; _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; - _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; - _playerLayer.frame = self.bounds; - _playerLayer.needsDisplayOnBoundsChange = YES; - - [self applyModifiers]; - - [self.layer addSublayer:_playerLayer]; - self.layer.needsDisplayOnBoundsChange = YES; - + const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; + // @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html + __weak RCTVideo *weakSelf = self; + _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC) + queue:NULL + usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } + ]; [_eventDispatcher sendInputEventWithName:@"onVideoLoadStart" body:@{@"src": @{ @"uri": [source objectForKey:@"uri"], @@ -214,7 +248,7 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (object == _playerItem) { + if (object == _playerItem) { if ([keyPath isEqualToString:statusKeyPath]) { // Handle player item status change. @@ -236,7 +270,6 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" @"canStepForward": [NSNumber numberWithBool:_playerItem.canStepForward], @"target": self.reactTag}]; - [self startProgressTimer]; [self attachListeners]; [self applyModifiers]; } else if(_playerItem.status == AVPlayerItemStatusFailed) { @@ -253,7 +286,7 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" } } } else { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } @@ -281,23 +314,40 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" - (void)setResizeMode:(NSString*)mode { + if( _controls ) + { + _playerViewController.videoGravity = mode; + } + else + { + _playerLayer.videoGravity = mode; + } _resizeMode = mode; - _playerLayer.videoGravity = mode; } - (void)setPaused:(BOOL)paused { if (paused) { - [self stopProgressTimer]; + [_player pause]; [_player setRate:0.0]; } else { - [self startProgressTimer]; + [_player play]; [_player setRate:_rate]; } - + _paused = paused; } +- (float)getCurrentTime +{ + return _playerItem != NULL ? CMTimeGetSeconds(_playerItem.currentTime) : 0; +} + +- (void)setCurrentTime:(float)currentTime +{ + [self setSeek: currentTime]; +} + - (void)setSeek:(float)seekTime { int timeScale = 10000; @@ -310,7 +360,7 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" CMTime current = item.currentTime; // TODO figure out a good tolerance level CMTime tolerance = CMTimeMake(1000, timeScale); - + if (CMTimeCompare(current, cmSeekTime) != 0) { [_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) { [_eventDispatcher sendInputEventWithName:@"onVideoSeek" @@ -360,48 +410,126 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" [self setResizeMode:_resizeMode]; [self setRepeat:_repeat]; [self setPaused:_paused]; + [self setControls:_controls]; } - (void)setRepeat:(BOOL)repeat { _repeat = repeat; } +- (void)usePlayerViewController +{ + if( _player ) + { + _playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem]; + [self addSubview:_playerViewController.view]; + } +} + +- (void)usePlayerLayer +{ + if( _player ) + { + _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; + _playerLayer.frame = self.bounds; + _playerLayer.needsDisplayOnBoundsChange = YES; + + [self.layer addSublayer:_playerLayer]; + self.layer.needsDisplayOnBoundsChange = YES; + } +} + +- (void)setControls:(BOOL)controls +{ + if( _controls != controls || (!_playerLayer && !_playerViewController) ) + { + _controls = controls; + if( _controls ) + { + [_playerLayer removeFromSuperlayer]; + _playerLayer = nil; + [self usePlayerViewController]; + } + else + { + [_playerViewController.view removeFromSuperview]; + _playerViewController = nil; + [self usePlayerLayer]; + } + } +} + #pragma mark - React View Management - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex { - RCTLogError(@"video cannot have any subviews"); + // 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 ) + { + [self setControls:true]; + } + + if( _controls ) + { + view.frame = self.bounds; + [_playerViewController.contentOverlayView insertSubview:view atIndex:atIndex]; + } + else + { + RCTLogError(@"video cannot have any subviews"); + } return; } - (void)removeReactSubview:(UIView *)subview { - RCTLogError(@"video cannot have any subviews"); + if( _controls ) + { + [subview removeFromSuperview]; + } + else + { + RCTLogError(@"video cannot have any subviews"); + } return; } - (void)layoutSubviews { [super layoutSubviews]; - [CATransaction begin]; - [CATransaction setAnimationDuration:0]; - _playerLayer.frame = self.bounds; - [CATransaction commit]; + 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 + { + [CATransaction begin]; + [CATransaction setAnimationDuration:0]; + _playerLayer.frame = self.bounds; + [CATransaction commit]; + } } #pragma mark - Lifecycle - (void)removeFromSuperview { - [_progressUpdateTimer invalidate]; - _prevProgressUpdateTime = nil; - [_player pause]; _player = nil; [_playerLayer removeFromSuperlayer]; _playerLayer = nil; + + [_playerViewController.view removeFromSuperview]; + _playerViewController = nil; + [self removePlayerTimeObserver]; [self removePlayerItemObservers]; _eventDispatcher = nil; diff --git a/RCTVideo.xcodeproj/project.pbxproj b/RCTVideo.xcodeproj/project.pbxproj index e6ddaaf1..8eda4458 100644 --- a/RCTVideo.xcodeproj/project.pbxproj +++ b/RCTVideo.xcodeproj/project.pbxproj @@ -205,7 +205,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../React/**", "$(SRCROOT)/../react-native/React/**", "$(SRCROOT)/node_modules/react-native/React/**", ); @@ -222,7 +221,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../React/**", "$(SRCROOT)/../react-native/React/**", "$(SRCROOT)/node_modules/react-native/React/**", ); diff --git a/RCTVideoManager.m b/RCTVideoManager.m index 3f931999..7c0dfbc2 100644 --- a/RCTVideoManager.m +++ b/RCTVideoManager.m @@ -38,9 +38,11 @@ RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString); RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL); RCT_EXPORT_VIEW_PROPERTY(paused, BOOL); RCT_EXPORT_VIEW_PROPERTY(muted, BOOL); +RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); RCT_EXPORT_VIEW_PROPERTY(volume, float); RCT_EXPORT_VIEW_PROPERTY(rate, float); RCT_EXPORT_VIEW_PROPERTY(seek, float); +RCT_EXPORT_VIEW_PROPERTY(currentTime, float); - (NSDictionary *)constantsToExport { diff --git a/Video.js b/Video.js index 77323549..67978988 100644 --- a/Video.js +++ b/Video.js @@ -127,13 +127,15 @@ Video.propTypes = { muted: PropTypes.bool, volume: PropTypes.number, rate: PropTypes.number, + controls: PropTypes.bool, + currentTime: PropTypes.number, onLoadStart: PropTypes.func, onLoad: PropTypes.func, onError: PropTypes.func, onProgress: PropTypes.func, onSeek: PropTypes.func, onEnd: PropTypes.func, - + /* Required by react-native */ scaleX: React.PropTypes.number, scaleY: React.PropTypes.number, @@ -146,7 +148,7 @@ Video.propTypes = { const RCTVideo = requireNativeComponent('RCTVideo', Video, { nativeOnly: { src: true, - seek: true, + seek: true }, }); diff --git a/package.json b/package.json index 8dd18d0a..c9592151 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,7 @@ "RCTVideoManager.m", "README.md", "Video.js", - "VideoResizeMode.js", - "VideoStylePropTypes.js" + "VideoResizeMode.js" ], "contributors": [ {