diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 7140d980b1..be2eb6169d 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -311,6 +311,14 @@ 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */; }; 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; + 8B0768B31CE752EC002E1453 /* ASDefaultPlaybackButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */; }; + 8B0768B41CE752EC002E1453 /* ASDefaultPlaybackButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */; }; + 8BBBAB8C1CEBAF1700107FC6 /* ASDefaultPlaybackButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */; }; + 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */; }; + 8BDA5FC51CDBDDE1007D13B2 /* ASVideoPlayerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; }; + 8BDA5FC61CDBDDE1007D13B2 /* ASVideoPlayerNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */; }; + 8BDA5FC71CDBDF91007D13B2 /* ASVideoPlayerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; }; + 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */; }; 92074A611CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */; }; 92074A621CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */; }; 92074A631CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */; }; @@ -820,6 +828,10 @@ 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = ""; }; 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = ""; }; + 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDefaultPlaybackButton.h; sourceTree = ""; }; + 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDefaultPlaybackButton.m; sourceTree = ""; }; + 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVideoPlayerNode.h; sourceTree = ""; }; + 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASVideoPlayerNode.mm; sourceTree = ""; }; 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+tvOS.h"; sourceTree = ""; }; 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASImageNode+tvOS.m"; sourceTree = ""; }; 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+tvOS.h"; sourceTree = ""; }; @@ -908,6 +920,7 @@ B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASLayoutManager.m; path = TextKit/ASLayoutManager.m; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B35061DD1B010EDF0018CF92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../AsyncDisplayKit-iOS/Info.plist"; sourceTree = ""; }; + BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = ""; }; CC3B20811C3F76D600798563 /* ASPendingStateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPendingStateController.h; sourceTree = ""; }; CC3B20821C3F76D600798563 /* ASPendingStateController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPendingStateController.mm; sourceTree = ""; }; CC3B20871C3F7A5400798563 /* ASWeakSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakSet.h; sourceTree = ""; }; @@ -1064,6 +1077,8 @@ 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */, AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */, AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */, + 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */, + 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */, 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */, AC6456071B0A335000CF11B8 /* ASCellNode.mm */, 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */, @@ -1332,6 +1347,8 @@ CC3B20881C3F7A5400798563 /* ASWeakSet.m */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, + 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */, + 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */, ); path = Private; sourceTree = ""; @@ -1476,6 +1493,7 @@ children = ( FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */, D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */, + BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */, ); name = Pods; sourceTree = ""; @@ -1570,6 +1588,7 @@ DBDB83941C6E879900D0098C /* ASPagerFlowLayout.h in Headers */, 058D0A4F195D05CB00B7D73C /* ASImageNode.h in Headers */, 05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */, + 8BDA5FC51CDBDDE1007D13B2 /* ASVideoPlayerNode.h in Headers */, 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */, ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */, ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */, @@ -1591,6 +1610,7 @@ B13CA1001C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, 0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */, 0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */, + 8B0768B31CE752EC002E1453 /* ASDefaultPlaybackButton.h in Headers */, 058D0A59195D05DC00B7D73C /* ASMutableAttributedStringBuilder.h in Headers */, 055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */, ACF6ED2B1B17843500DA7C62 /* ASOverlayLayoutSpec.h in Headers */, @@ -1747,6 +1767,7 @@ DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */, + 8BBBAB8C1CEBAF1700107FC6 /* ASDefaultPlaybackButton.h in Headers */, B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */, 34EFC76C1B701CED00AD841F /* ASOverlayLayoutSpec.h in Headers */, B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */, @@ -1790,6 +1811,7 @@ B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */, 044284FF1BAA3BD600D16268 /* UICollectionViewLayout+ASConvenience.h in Headers */, B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */, + 8BDA5FC71CDBDF91007D13B2 /* ASVideoPlayerNode.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1836,12 +1858,12 @@ isa = PBXNativeTarget; buildConfigurationList = 058D09D2195D04C000B7D73C /* Build configuration list for PBXNativeTarget "AsyncDisplayKitTests" */; buildPhases = ( - 2E61B6A0DB0F436A9DDBE86F /* Check Pods Manifest.lock */, + 2E61B6A0DB0F436A9DDBE86F /* 📦 Check Pods Manifest.lock */, 058D09B8195D04C000B7D73C /* Sources */, 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, - 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, - B130AB1AC0A1E5162E211C19 /* Embed Pods Frameworks */, + 3B9D88CDF51B429C8409E4B6 /* 📦 Copy Pods Resources */, + B130AB1AC0A1E5162E211C19 /* 📦 Embed Pods Frameworks */, ); buildRules = ( ); @@ -1942,14 +1964,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 2E61B6A0DB0F436A9DDBE86F /* Check Pods Manifest.lock */ = { + 2E61B6A0DB0F436A9DDBE86F /* 📦 Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Check Pods Manifest.lock"; + name = "📦 Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1957,14 +1979,14 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */ = { + 3B9D88CDF51B429C8409E4B6 /* 📦 Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "📦 Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1972,14 +1994,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - B130AB1AC0A1E5162E211C19 /* Embed Pods Frameworks */ = { + B130AB1AC0A1E5162E211C19 /* 📦 Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "📦 Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -2004,6 +2026,7 @@ buildActionMask = 2147483647; files = ( 058D0A22195D050800B7D73C /* _ASAsyncTransaction.mm in Sources */, + 8B0768B41CE752EC002E1453 /* ASDefaultPlaybackButton.m in Sources */, E55D86321CA8A14000A0C26F /* ASLayoutable.mm in Sources */, 68FC85E41CE29B7E00EDD713 /* ASTabBarController.m in Sources */, 058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */, @@ -2108,6 +2131,7 @@ 055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */, 058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */, 257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */, + 8BDA5FC61CDBDDE1007D13B2 /* ASVideoPlayerNode.mm in Sources */, 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */, 257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */, 9CFFC6BE1CCAC52B006A6476 /* ASEnvironment.mm in Sources */, @@ -2164,6 +2188,7 @@ buildActionMask = 2147483647; files = ( 9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */, + 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */, DE4843DB1C93EAB100A1F33B /* ASLayoutTransition.mm in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, @@ -2252,6 +2277,7 @@ B35062091B010EFD0018CF92 /* ASScrollNode.m in Sources */, B35062561B010EFD0018CF92 /* ASSentinel.m in Sources */, 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, + 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */, 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */, 34EFC7761B701D2A00AD841F /* ASStackPositionedLayout.mm in Sources */, 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */, @@ -2648,7 +2674,7 @@ }; DB1020821CBCA2AD00FA6FE1 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */; + baseConfigurationReference = BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_CODE_COVERAGE = YES; diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 0cff257224..5008ea3333 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -15,8 +15,10 @@ typedef enum { ASVideoNodePlayerStateUnknown, ASVideoNodePlayerStateInitialLoading, - ASVideoNodePlayerStateLoading, + ASVideoNodePlayerStateReadyToPlay, + ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying, ASVideoNodePlayerStatePlaying, + ASVideoNodePlayerStateLoading, ASVideoNodePlayerStatePaused, ASVideoNodePlayerStateFinished } ASVideoNodePlayerState; @@ -40,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, atomic, strong, readonly) AVPlayer *player; @property (nullable, atomic, strong, readonly) AVPlayerItem *currentItem; + /** * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. * If it leaves the visible interfaceState it will pause but will resume once it has returned. @@ -57,9 +60,6 @@ NS_ASSUME_NONNULL_BEGIN //! Defaults to AVLayerVideoGravityResizeAspect @property (atomic) NSString *gravity; -//! Defaults to an ASDefaultPlayButton instance. -@property (nullable, atomic) ASButtonNode *playButton; - @property (nullable, atomic, weak, readwrite) id delegate; @end diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 86ea8e6e19..feb5910ad3 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -72,10 +72,8 @@ static NSString * const kStatus = @"status"; CMTime _timeObserverInterval; ASImageNode *_placeholderImageNode; // TODO: Make ASVideoNode an ASImageNode subclass; remove this. - - ASButtonNode *_playButtonNode; + ASDisplayNode *_playerNode; - ASDisplayNode *_spinnerNode; NSString *_gravity; } @@ -92,8 +90,7 @@ static NSString * const kStatus = @"status"; if (!(self = [super init])) { return nil; } - - self.playButton = [[ASDefaultPlayButton alloc] init]; + self.gravity = AVLayerVideoGravityResizeAspect; _periodicTimeObserverTimescale = 10000; [self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; @@ -157,7 +154,7 @@ static NSString * const kStatus = @"status"; if (_placeholderImageNode.image == nil) { [self generatePlaceholderImage]; } - + __weak __typeof(self) weakSelf = self; _timeObserverInterval = CMTimeMake(1, _periodicTimeObserverTimescale); _timeObserver = [_player addPeriodicTimeObserverForInterval:_timeObserverInterval queue:NULL usingBlock:^(CMTime time){ @@ -212,10 +209,7 @@ static NSString * const kStatus = @"status"; // Stretch out play button, placeholder image player node to the max size NSMutableArray *children = [NSMutableArray array]; - if (_playButtonNode) { - _playButtonNode.preferredFrameSize = maxSize; - [children addObject:_playButtonNode]; - } + if (_placeholderImageNode) { _placeholderImageNode.preferredFrameSize = maxSize; [children addObject:_placeholderImageNode]; @@ -225,28 +219,9 @@ static NSString * const kStatus = @"status"; [children addObject:_playerNode]; } - // Center spinner node - if (_spinnerNode) { - ASCenterLayoutSpec *centerLayoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:_spinnerNode]; - centerLayoutSpec.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(maxSize); - [children addObject:centerLayoutSpec]; - } - return [ASStaticLayoutSpec staticLayoutSpecWithChildren:children]; } -- (void)layout -{ - [super layout]; - - CGRect bounds = self.bounds; - - ASDN::MutexLocker l(_videoLock); - CGFloat horizontalDiff = (CGRectGetWidth(bounds) - CGRectGetWidth(_playButtonNode.bounds))/2; - CGFloat verticalDiff = (CGRectGetHeight(bounds) - CGRectGetHeight(_playButtonNode.bounds))/2; - _playButtonNode.hitTestSlop = UIEdgeInsetsMake(-verticalDiff, -horizontalDiff, -verticalDiff, -horizontalDiff); -} - - (void)generatePlaceholderImage { ASVideoNode * __weak weakSelf = self; @@ -319,13 +294,14 @@ static NSString * const kStatus = @"status"; if ([keyPath isEqualToString:kStatus]) { if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { - [self removeSpinner]; + self.playerState = ASVideoNodePlayerStateReadyToPlay; // If we don't yet have a placeholder image update it now that we should have data available for it if (_placeholderImageNode.image == nil) { [self generatePlaceholderImage]; } } } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { + self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || [change[NSKeyValueChangeNewKey] boolValue]) && ASInterfaceStateIncludesVisible(self.interfaceState)) { if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { [_delegate videoNodeDidRecoverFromStall:self]; @@ -335,7 +311,6 @@ static NSString * const kStatus = @"status"; } else if ([keyPath isEqualToString:kplaybackBufferEmpty]) { if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) { self.playerState = ASVideoNodePlayerStateLoading; - [self showSpinner]; } } } @@ -455,26 +430,6 @@ static NSString * const kStatus = @"status"; _playerState = playerState; } -- (void)setPlayButton:(ASButtonNode *)playButton -{ - ASDN::MutexLocker l(_videoLock); - - [_playButtonNode removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - [_playButtonNode removeFromSupernode]; - - _playButtonNode = playButton; - [_playButtonNode addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - - [self addSubnode:playButton]; - [self setNeedsLayout]; -} - -- (ASButtonNode *)playButton -{ - ASDN::MutexLocker l(_videoLock); - return _playButtonNode; -} - - (void)setAsset:(AVAsset *)asset { ASDN::MutexLocker l(_videoLock); @@ -488,10 +443,6 @@ static NSString * const kStatus = @"status"; _asset = asset; [self setNeedsDataFetch]; - - if (_shouldAutoplay) { - [self play]; - } } - (AVAsset *)asset @@ -583,11 +534,8 @@ static NSString * const kStatus = @"status"; if (_playerNode == nil) { _playerNode = [self constructPlayerNode]; - if (_playButtonNode.supernode == self) { - [self insertSubnode:_playerNode belowSubnode:_playButtonNode]; - } else { - [self addSubnode:_playerNode]; - } + [self addSubnode:_playerNode]; + [self setNeedsLayout]; } @@ -595,14 +543,10 @@ static NSString * const kStatus = @"status"; [_player play]; _shouldBePlaying = YES; - - [UIView animateWithDuration:0.15 animations:^{ - _playButtonNode.alpha = 0.0; - }]; + if (![self ready]) { - [self showSpinner]; + self.playerState = ASVideoNodePlayerStateLoading; } else { - [self removeSpinner]; self.playerState = ASVideoNodePlayerStatePlaying; } } @@ -612,35 +556,6 @@ static NSString * const kStatus = @"status"; return _currentPlayerItem.status == AVPlayerItemStatusReadyToPlay; } -- (void)showSpinner -{ - ASDN::MutexLocker l(_videoLock); - - if (!_spinnerNode) { - _spinnerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ - UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; - spinnnerView.color = [UIColor whiteColor]; - return spinnnerView; - }]; - _spinnerNode.preferredFrameSize = CGSizeMake(44.0, 44.0); - - [self addSubnode:_spinnerNode]; - [self setNeedsLayout]; - } - [(UIActivityIndicatorView *)_spinnerNode.view startAnimating]; -} - -- (void)removeSpinner -{ - ASDN::MutexLocker l(_videoLock); - - if (!_spinnerNode) { - return; - } - [_spinnerNode removeFromSupernode]; - _spinnerNode = nil; -} - - (void)pause { ASDN::MutexLocker l(_videoLock); @@ -649,11 +564,7 @@ static NSString * const kStatus = @"status"; } self.playerState = ASVideoNodePlayerStatePaused; [_player pause]; - [self removeSpinner]; _shouldBePlaying = NO; - [UIView animateWithDuration:0.15 animations:^{ - _playButtonNode.alpha = 1.0; - }]; } - (BOOL)isPlaying @@ -700,7 +611,6 @@ static NSString * const kStatus = @"status"; - (void)videoNodeDidStall:(NSNotification *)notification { self.playerState = ASVideoNodePlayerStateLoading; - [self showSpinner]; if (_delegateFlags.delegateVideoNodeDidStallAtTimeInterval) { [_delegate videoNode:self didStallAtTimeInterval:CMTimeGetSeconds(_player.currentItem.currentTime)]; } @@ -724,13 +634,6 @@ static NSString * const kStatus = @"status"; } #pragma mark - Internal Properties - -- (ASDisplayNode *)spinner -{ - ASDN::MutexLocker l(_videoLock); - return _spinnerNode; -} - - (ASImageNode *)placeholderImageNode { ASDN::MutexLocker l(_videoLock); @@ -794,7 +697,6 @@ static NSString * const kStatus = @"status"; { [_player removeTimeObserver:_timeObserver]; _timeObserver = nil; - [_playButtonNode removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; [self removePlayerItemObservers:_currentPlayerItem]; } diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h new file mode 100644 index 0000000000..99532928c6 --- /dev/null +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -0,0 +1,160 @@ +// +// ASVideoPlayerNode.h +// AsyncDisplayKit +// +// Created by Erekle on 5/6/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#if TARGET_OS_IOS +#import +//#import +//#import +//#import + +@class AVAsset; +@protocol ASVideoPlayerNodeDelegate; + +typedef enum { + ASVideoPlayerNodeControlTypePlaybackButton, + ASVideoPlayerNodeControlTypeElapsedText, + ASVideoPlayerNodeControlTypeDurationText, + ASVideoPlayerNodeControlTypeScrubber, + ASVideoPlayerNodeControlTypeFlexGrowSpacer, +} ASVideoPlayerNodeControlType; + +NS_ASSUME_NONNULL_BEGIN + +@interface ASVideoPlayerNode : ASDisplayNode + +@property (nullable, atomic, weak) id delegate; + +@property (nonatomic, assign, readonly) CMTime duration; + +@property (nonatomic, assign) BOOL controlsDisabled; + +@property (nonatomic, assign, readonly) BOOL loadAssetWhenNodeBecomesVisible; + +#pragma mark - ASVideoNode property proxy +/** + * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. + * If it leaves the visible interfaceState it will pause but will resume once it has returned. + */ +@property (nonatomic, assign, readwrite) BOOL shouldAutoPlay; +@property (nonatomic, assign, readwrite) BOOL shouldAutoRepeat; +@property (nonatomic, assign, readwrite) BOOL muted; +@property (nonatomic, assign, readonly) ASVideoNodePlayerState playerState; +@property (nonatomic, assign, readwrite) BOOL shouldAggressivelyRecoverFromStall; + +//! Defaults to 100 +@property (nonatomic, assign) int32_t periodicTimeObserverTimescale; +//! Defaults to AVLayerVideoGravityResizeAspect +@property (atomic) NSString *gravity; + +- (instancetype)initWithUrl:(NSURL*)url; +- (instancetype)initWithAsset:(AVAsset*)asset; +- (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; +- (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible; + +#pragma mark - Public API +- (void)seekToTime:(CGFloat)percentComplete; +- (void)play; +- (void)pause; +- (BOOL)isPlaying; + +@end + +#pragma mark - ASVideoPlayerNodeDelegate - +@protocol ASVideoPlayerNodeDelegate +@optional +/** + * @abstract Delegate method invoked before creating controlbar controls + * @param videoPlayer + */ +- (NSArray *)videoPlayerNodeNeededDefaultControls:(ASVideoPlayerNode*)videoPlayer; + +/** + * @abstract Delegate method invoked before creating default controls, asks delegate for custom controls dictionary. + * This dictionary must constain only ASDisplayNode subclass objects. + * @param videoPlayer + * @discussion - This method is invoked only when developer implements videoPlayerNodeLayoutSpec:forControls:forMaximumSize: + * and gives ability to add custom constrols to ASVideoPlayerNode, for example mute button. + */ +- (NSDictionary *)videoPlayerNodeCustomControls:(ASVideoPlayerNode*)videoPlayer; + +/** + * @abstract Delegate method invoked in layoutSpecThatFits: + * @param videoPlayer + * @param controls - Dictionary of controls which are used in videoPlayer; Dictionary keys are ASVideoPlayerNodeControlType + * @param maxSize - Maximum size for ASVideoPlayerNode + * @discussion - Developer can layout whole ASVideoPlayerNode as he wants. ASVideoNode is locked and it can't be changed + */ +- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer + forControls:(NSDictionary *)controls + forMaximumSize:(CGSize)maxSize; + +#pragma mark Text delegate methods +/** + * @abstract Delegate method invoked before creating ASVideoPlayerNodeControlTypeElapsedText and ASVideoPlayerNodeControlTypeDurationText + * @param videoPlayer + * @param timeLabelType + */ +- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayerNode timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType; +- (NSString *)videoPlayerNode:(ASVideoPlayerNode *)videoPlayerNode + timeStringForTimeLabelType:(ASVideoPlayerNodeControlType)timeLabelType + forTime:(CMTime)time; + +#pragma mark Scrubber delegate methods +- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer; +- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer; +- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer; +- (UIImage *)videoPlayerNodeScrubberThumbImage:(ASVideoPlayerNode *)videoPlayer; + +#pragma mark - Spinner delegate methods +- (UIColor *)videoPlayerNodeSpinnerTint:(ASVideoPlayerNode *)videoPlayer; + +#pragma mark - Playback button delegate methods +- (UIColor *)videoPlayerNodePlaybackButtonTint:(ASVideoPlayerNode *)videoPlayer; + + +#pragma mark ASVideoNodeDelegate proxy methods +/** + * @abstract Delegate method invoked when ASVideoPlayerNode is taped. + * @param videoPlayerNode The ASVideoPlayerNode that was tapped. + */ +- (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer; +/** + * @abstract Delegate method invoked when ASVideoNode playback time is updated. + * @param videoPlayerNode The video player node + * @param second current playback time. + */ +- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didPlayToTime:(CMTime)time; + +/** + * @abstract Delegate method invoked when ASVideoNode changes state. + * @param videoPlayerNode The ASVideoPlayerNode whose ASVideoNode is changing state. + * @param state ASVideoNode state before this change. + * @param toSate ASVideoNode new state. + * @discussion This method is called after each state change + */ +- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer willChangeVideoNodeState:(ASVideoNodePlayerState)state toVideoNodeState:(ASVideoNodePlayerState)toState; + +/** + * @abstract Delegate method is invoked when ASVideoNode decides to change state. + * @param videoPlayerNode The ASVideoPlayerNode whose ASVideoNode is changing state. + * @param state ASVideoNode that is going to be set. + * @discussion Delegate method invoked when player changes it's state to + * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused + * and asks delegate if state change is valid + */ +- (BOOL)videoPlayerNode:(ASVideoPlayerNode*)videoPlayer shouldChangeVideoNodeStateTo:(ASVideoNodePlayerState)state; + +/** + * @abstract Delegate method invoked when the ASVideoNode has played to its end time. + * @param videoPlayerNode The video node has played to its end time. + */ +- (void)videoPlayerNodeDidPlayToEnd:(ASVideoPlayerNode *)videoPlayer; + +@end +NS_ASSUME_NONNULL_END +#endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm new file mode 100644 index 0000000000..ec19fda312 --- /dev/null +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -0,0 +1,801 @@ +// +// ASVideoPlayerNode.m +// AsyncDisplayKit +// +// Created by Erekle on 5/6/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASVideoPlayerNode.h" +#import "ASDefaultPlaybackButton.h" + +static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; + +@interface ASVideoPlayerNode() +{ + ASDN::RecursiveMutex _videoPlayerLock; + + __weak id _delegate; + + struct { + unsigned int delegateNeededDefaultControls:1; + unsigned int delegateCustomControls:1; + unsigned int delegateSpinnerTintColor:1; + unsigned int delegatePlaybackButtonTint:1; + unsigned int delegateScrubberMaximumTrackTintColor:1; + unsigned int delegateScrubberMinimumTrackTintColor:1; + unsigned int delegateScrubberThumbTintColor:1; + unsigned int delegateScrubberThumbImage:1; + unsigned int delegateTimeLabelAttributes:1; + unsigned int delegateTimeLabelAttributedString:1; + unsigned int delegateLayoutSpecForControls:1; + unsigned int delegateVideoNodeDidPlayToTime:1; + unsigned int delegateVideoNodeWillChangeState:1; + unsigned int delegateVideoNodeShouldChangeState:1; + unsigned int delegateVideoNodePlaybackDidFinish:1; + unsigned int delegateDidTapVideoPlayerNode:1; + } _delegateFlags; + + NSURL *_url; + AVAsset *_asset; + + ASVideoNode *_videoNode; + + NSArray *_neededDefaultControls; + + NSMutableDictionary *_cachedControls; + + ASDefaultPlaybackButton *_playbackButtonNode; + ASTextNode *_elapsedTextNode; + ASTextNode *_durationTextNode; + ASDisplayNode *_scrubberNode; + ASStackLayoutSpec *_controlFlexGrowSpacerSpec; + ASDisplayNode *_spinnerNode; + + BOOL _loadAssetWhenNodeBecomesVisible; + BOOL _isSeeking; + CMTime _duration; + + BOOL _controlsDisabled; + + BOOL _shouldAutoPlay; + BOOL _shouldAutoRepeat; + BOOL _muted; + int32_t _periodicTimeObserverTimescale; + NSString *_gravity; + + BOOL _shouldAggressivelyRecoverFromStall; + + UIColor *_defaultControlsColor; +} + +@end + +@implementation ASVideoPlayerNode +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + [self _init]; + + return self; +} + +- (instancetype)initWithUrl:(NSURL*)url +{ + if (!(self = [super init])) { + return nil; + } + + _url = url; + _asset = [AVAsset assetWithURL:_url]; + _loadAssetWhenNodeBecomesVisible = YES; + + [self _init]; + + return self; +} + +- (instancetype)initWithAsset:(AVAsset *)asset +{ + if (!(self = [super init])) { + return nil; + } + + _asset = asset; + _loadAssetWhenNodeBecomesVisible = YES; + + [self _init]; + + return self; +} + +- (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible +{ + if (!(self = [super init])) { + return nil; + } + + _url = url; + _asset = [AVAsset assetWithURL:_url]; + _loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible; + + [self _init]; + + return self; +} + +- (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible +{ + if (!(self = [super init])) { + return nil; + } + + _asset = asset; + _loadAssetWhenNodeBecomesVisible = loadAssetWhenNodeBecomesVisible; + + [self _init]; + + return self; +} + +- (void)_init +{ + _defaultControlsColor = [UIColor whiteColor]; + _cachedControls = [[NSMutableDictionary alloc] init]; + + _videoNode = [[ASVideoNode alloc] init]; + _videoNode.delegate = self; + if (_loadAssetWhenNodeBecomesVisible == NO) { + _videoNode.asset = _asset; + } + [self addSubnode:_videoNode]; +} + +- (void)didLoad +{ + [super didLoad]; + { + ASDN::MutexLocker l(_videoPlayerLock); + [self createControls]; + } +} + +- (void)visibilityDidChange:(BOOL)isVisible +{ + [super visibilityDidChange:isVisible]; + + ASDN::MutexLocker l(_videoPlayerLock); + + if (isVisible && _loadAssetWhenNodeBecomesVisible && _asset != _videoNode.asset) { + _videoNode.asset = _asset; + } +} + +- (NSArray *)createDefaultControlElementArray +{ + if (_delegateFlags.delegateNeededDefaultControls) { + return [_delegate videoPlayerNodeNeededDefaultControls:self]; + } + + return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), + @(ASVideoPlayerNodeControlTypeElapsedText), + @(ASVideoPlayerNodeControlTypeScrubber), + @(ASVideoPlayerNodeControlTypeDurationText) ]; +} + +#pragma mark - UI +- (void)createControls +{ + ASDN::MutexLocker l(_videoPlayerLock); + + if (_controlsDisabled) { + return; + } + + if (_neededDefaultControls == nil) { + _neededDefaultControls = [self createDefaultControlElementArray]; + } + + if (_cachedControls == nil) { + _cachedControls = [[NSMutableDictionary alloc] init]; + } + + for (id object in _neededDefaultControls) { + ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[object integerValue]; + switch (type) { + case ASVideoPlayerNodeControlTypePlaybackButton: + [self createPlaybackButton]; + break; + case ASVideoPlayerNodeControlTypeElapsedText: + [self createElapsedTextField]; + break; + case ASVideoPlayerNodeControlTypeDurationText: + [self createDurationTextField]; + break; + case ASVideoPlayerNodeControlTypeScrubber: + [self createScrubber]; + break; + case ASVideoPlayerNodeControlTypeFlexGrowSpacer: + [self createControlFlexGrowSpacer]; + break; + default: + break; + } + } + + if (_delegateFlags.delegateCustomControls && _delegateFlags.delegateLayoutSpecForControls) { + NSDictionary *customControls = [_delegate videoPlayerNodeCustomControls:self]; + for (id key in customControls) { + id node = customControls[key]; + if (![node isKindOfClass:[ASDisplayNode class]]) { + continue; + } + + [self addSubnode:node]; + [_cachedControls setObject:node forKey:key]; + } + } + + ASPerformBlockOnMainThread(^{ + ASDN::MutexLocker l(_videoPlayerLock); + [self setNeedsLayout]; + }); +} + +- (void)removeControls +{ + NSArray *controls = [_cachedControls allValues]; + [controls enumerateObjectsUsingBlock:^(ASDisplayNode *_Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { + [node removeFromSupernode]; + }]; + + [self cleanCachedControls]; +} + +- (void)cleanCachedControls +{ + [_cachedControls removeAllObjects]; + + _playbackButtonNode = nil; + _elapsedTextNode = nil; + _durationTextNode = nil; + _scrubberNode = nil; +} + +- (void)createPlaybackButton +{ + if (_playbackButtonNode == nil) { + _playbackButtonNode = [[ASDefaultPlaybackButton alloc] init]; + _playbackButtonNode.preferredFrameSize = CGSizeMake(16.0, 22.0); + if (_delegateFlags.delegatePlaybackButtonTint) { + _playbackButtonNode.tintColor = [_delegate videoPlayerNodePlaybackButtonTint:self]; + } else { + _playbackButtonNode.tintColor = _defaultControlsColor; + } + + if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { + _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; + } + + [_playbackButtonNode addTarget:self action:@selector(didTapPlaybackButton:) forControlEvents:ASControlNodeEventTouchUpInside]; + [_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)]; + } + + [self addSubnode:_playbackButtonNode]; +} + +- (void)createElapsedTextField +{ + if (_elapsedTextNode == nil) { + _elapsedTextNode = [[ASTextNode alloc] init]; + _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00" + forControlType:ASVideoPlayerNodeControlTypeElapsedText]; + _elapsedTextNode.truncationMode = NSLineBreakByClipping; + + [_cachedControls setObject:_elapsedTextNode forKey:@(ASVideoPlayerNodeControlTypeElapsedText)]; + } + [self addSubnode:_elapsedTextNode]; +} + +- (void)createDurationTextField +{ + if (_durationTextNode == nil) { + _durationTextNode = [[ASTextNode alloc] init]; + _durationTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00" + forControlType:ASVideoPlayerNodeControlTypeDurationText]; + _durationTextNode.truncationMode = NSLineBreakByClipping; + + [_cachedControls setObject:_durationTextNode forKey:@(ASVideoPlayerNodeControlTypeDurationText)]; + } + [self addSubnode:_durationTextNode]; +} + +- (void)createScrubber +{ + if (_scrubberNode == nil) { + _scrubberNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ + UISlider *slider = [[UISlider alloc] initWithFrame:CGRectZero]; + slider.minimumValue = 0.0; + slider.maximumValue = 1.0; + + if (_delegateFlags.delegateScrubberMinimumTrackTintColor) { + slider.minimumTrackTintColor = [_delegate videoPlayerNodeScrubberMinimumTrackTint:self]; + } + + if (_delegateFlags.delegateScrubberMaximumTrackTintColor) { + slider.maximumTrackTintColor = [_delegate videoPlayerNodeScrubberMaximumTrackTint:self]; + } + + if (_delegateFlags.delegateScrubberThumbTintColor) { + slider.thumbTintColor = [_delegate videoPlayerNodeScrubberThumbTint:self]; + } + + if (_delegateFlags.delegateScrubberThumbImage) { + UIImage *thumbImage = [_delegate videoPlayerNodeScrubberThumbImage:self]; + [slider setThumbImage:thumbImage forState:UIControlStateNormal]; + } + + + [slider addTarget:self action:@selector(beginSeek) forControlEvents:UIControlEventTouchDown]; + [slider addTarget:self action:@selector(endSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; + [slider addTarget:self action:@selector(seekTimeDidChange:) forControlEvents:UIControlEventValueChanged]; + + return slider; + }]; + + _scrubberNode.flexShrink = YES; + + [_cachedControls setObject:_scrubberNode forKey:@(ASVideoPlayerNodeControlTypeScrubber)]; + } + + [self addSubnode:_scrubberNode]; +} + +- (void)createControlFlexGrowSpacer +{ + if (_controlFlexGrowSpacerSpec == nil) { + _controlFlexGrowSpacerSpec = [[ASStackLayoutSpec alloc] init]; + _controlFlexGrowSpacerSpec.flexGrow = YES; + } + + [_cachedControls setObject:_controlFlexGrowSpacerSpec forKey:@(ASVideoPlayerNodeControlTypeFlexGrowSpacer)]; +} + +- (void)updateDurationTimeLabel +{ + if (!_durationTextNode) { + return; + } + NSString *formattedDuration = [self timeStringForCMTime:_duration forTimeLabelType:ASVideoPlayerNodeControlTypeDurationText]; + _durationTextNode.attributedString = [self timeLabelAttributedStringForString:formattedDuration forControlType:ASVideoPlayerNodeControlTypeDurationText]; +} + +- (void)updateElapsedTimeLabel:(NSTimeInterval)seconds +{ + if (!_elapsedTextNode) { + return; + } + NSString *formatteElapsed = [self timeStringForCMTime:CMTimeMakeWithSeconds( seconds, _videoNode.periodicTimeObserverTimescale ) forTimeLabelType:ASVideoPlayerNodeControlTypeElapsedText]; + _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:formatteElapsed forControlType:ASVideoPlayerNodeControlTypeElapsedText]; +} + +- (NSAttributedString*)timeLabelAttributedStringForString:(NSString*)string forControlType:(ASVideoPlayerNodeControlType)controlType +{ + NSDictionary *options; + if (_delegateFlags.delegateTimeLabelAttributes) { + options = [_delegate videoPlayerNodeTimeLabelAttributes:self timeLabelType:controlType]; + } else { + options = @{ + NSFontAttributeName : [UIFont systemFontOfSize:12.0], + NSForegroundColorAttributeName: _defaultControlsColor + }; + } + + + NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:options]; + + return attributedString; +} + +#pragma mark - ASVideoNodeDelegate +- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState +{ + if (_delegateFlags.delegateVideoNodeWillChangeState) { + [_delegate videoPlayerNode:self willChangeVideoNodeState:state toVideoNodeState:toState]; + } + + if (toState == ASVideoNodePlayerStateReadyToPlay) { + _duration = _videoNode.currentItem.duration; + [self updateDurationTimeLabel]; + } + + if (toState == ASVideoNodePlayerStatePlaying) { + _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; + [self removeSpinner]; + } else if (toState != ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying && toState != ASVideoNodePlayerStateReadyToPlay) { + _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePlay; + } + + if (toState == ASVideoNodePlayerStateLoading || toState == ASVideoNodePlayerStateInitialLoading) { + [self showSpinner]; + } + + if (toState == ASVideoNodePlayerStateReadyToPlay || toState == ASVideoNodePlayerStatePaused || toState == ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying) { + [self removeSpinner]; + } +} + +- (BOOL)videoNode:(ASVideoNode *)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state +{ + if (_delegateFlags.delegateVideoNodeShouldChangeState) { + return [_delegate videoPlayerNode:self shouldChangeVideoNodeStateTo:state]; + } + return YES; +} + +- (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval +{ + if (_delegateFlags.delegateVideoNodeDidPlayToTime) { + [_delegate videoPlayerNode:self didPlayToTime:_videoNode.player.currentTime]; + } + + if (_isSeeking) { + return; + } + + if (_elapsedTextNode) { + [self updateElapsedTimeLabel:timeInterval]; + } + + if (_scrubberNode) { + [(UISlider*)_scrubberNode.view setValue:( timeInterval / CMTimeGetSeconds(_duration) ) animated:NO]; + } +} + +- (void)videoDidPlayToEnd:(ASVideoNode *)videoNode +{ + if (_delegateFlags.delegateVideoNodePlaybackDidFinish) { + [_delegate videoPlayerNodeDidPlayToEnd:self]; + } +} + +- (void)didTapVideoNode:(ASVideoNode *)videoNode +{ + if (_delegateFlags.delegateDidTapVideoPlayerNode) { + [_delegate didTapVideoPlayerNode:self]; + } else { + [self togglePlayPause]; + } +} + +#pragma mark - Actions +- (void)togglePlayPause +{ + if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { + [_videoNode pause]; + } else { + [_videoNode play]; + } +} + +- (void)showSpinner +{ + ASDN::MutexLocker l(_videoPlayerLock); + + if (!_spinnerNode) { + _spinnerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ + UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; + spinnnerView.color = _defaultControlsColor; + if (_delegateFlags.delegateSpinnerTintColor) { + spinnnerView.color = [_delegate videoPlayerNodeSpinnerTint:self]; + } + return spinnnerView; + }]; + _spinnerNode.preferredFrameSize = CGSizeMake(44.0, 44.0); + + [self addSubnode:_spinnerNode]; + [self setNeedsLayout]; + } + [(UIActivityIndicatorView *)_spinnerNode.view startAnimating]; +} + +- (void)removeSpinner +{ + ASDN::MutexLocker l(_videoPlayerLock); + + if (!_spinnerNode) { + return; + } + [_spinnerNode removeFromSupernode]; + _spinnerNode = nil; +} + +- (void)didTapPlaybackButton:(ASControlNode*)node +{ + [self togglePlayPause]; +} + +- (void)beginSeek +{ + _isSeeking = YES; +} + +- (void)endSeek +{ + _isSeeking = NO; +} + +- (void)seekTimeDidChange:(UISlider*)slider +{ + CGFloat percentage = slider.value * 100; + [self seekToTime:percentage]; +} + +#pragma mark - Public API +- (void)seekToTime:(CGFloat)percentComplete +{ + CGFloat seconds = ( CMTimeGetSeconds(_duration) * percentComplete ) / 100; + + [self updateElapsedTimeLabel:seconds]; + [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; + + if (_videoNode.playerState != ASVideoNodePlayerStatePlaying) { + [self togglePlayPause]; + } +} + +- (void)play +{ + [_videoNode play]; +} + +- (void)pause +{ + [_videoNode pause]; +} + +- (BOOL)isPlaying +{ + return [_videoNode isPlaying]; +} + +- (NSArray *)controlsForLayoutSpec +{ + NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:_cachedControls.count]; + + if (_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]) { + [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]]; + } + + if (_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]) { + [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]]; + } + + if (_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]) { + [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]]; + } + + if (_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]) { + [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]]; + } + + return controls; +} + +#pragma mark - Layout +- (ASLayoutSpec*)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + CGSize maxSize = constrainedSize.max; + if (!CGSizeEqualToSize(self.preferredFrameSize, CGSizeZero)) { + maxSize = self.preferredFrameSize; + } + + // Prevent crashes through if infinite width or height + if (isinf(maxSize.width) || isinf(maxSize.height)) { + ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoPlayerNode"); + maxSize = CGSizeZero; + } + _videoNode.preferredFrameSize = maxSize; + + ASLayoutSpec *layoutSpec; + + if (_delegateFlags.delegateLayoutSpecForControls) { + layoutSpec = [_delegate videoPlayerNodeLayoutSpec:self forControls:_cachedControls forMaximumSize:maxSize]; + } else { + layoutSpec = [self defaultLayoutSpecThatFits:maxSize]; + } + + NSMutableArray *children = [[NSMutableArray alloc] init]; + + if (_spinnerNode) { + ASCenterLayoutSpec *centerLayoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:_spinnerNode]; + centerLayoutSpec.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(maxSize); + [children addObject:centerLayoutSpec]; + } + + ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec]; + overlaySpec.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(maxSize); + + [children addObject:overlaySpec]; + + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:children]; +} + +- (ASLayoutSpec*)defaultLayoutSpecThatFits:(CGSize)maxSize +{ + _scrubberNode.preferredFrameSize = CGSizeMake(maxSize.width, 44.0); + + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.flexGrow = YES; + + ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:10.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children: [self controlsForLayoutSpec] ]; + controlbarSpec.alignSelf = ASStackLayoutAlignSelfStretch; + + UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); + + ASInsetLayoutSpec *controlbarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlbarSpec]; + + controlbarInsetSpec.alignSelf = ASStackLayoutAlignSelfStretch; + + ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + children:@[spacer,controlbarInsetSpec]]; + + return mainVerticalStack; +} + +#pragma mark - Properties +- (id)delegate +{ + return _delegate; +} + +- (void)setDelegate:(id)delegate +{ + if (delegate == _delegate) { + return; + } + + _delegate = delegate; + + if (_delegate == nil) { + memset(&_delegateFlags, 0, sizeof(_delegateFlags)); + } else { + _delegateFlags.delegateNeededDefaultControls = [_delegate respondsToSelector:@selector(videoPlayerNodeNeededDefaultControls:)]; + _delegateFlags.delegateCustomControls = [_delegate respondsToSelector:@selector(videoPlayerNodeCustomControls:)]; + _delegateFlags.delegateSpinnerTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerTint:)]; + _delegateFlags.delegateScrubberMaximumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMaximumTrackTint:)]; + _delegateFlags.delegateScrubberMinimumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMinimumTrackTint:)]; + _delegateFlags.delegateScrubberThumbTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbTint:)]; + _delegateFlags.delegateScrubberThumbImage = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbImage:)]; + _delegateFlags.delegateTimeLabelAttributes = [_delegate respondsToSelector:@selector(videoPlayerNodeTimeLabelAttributes:timeLabelType:)]; + _delegateFlags.delegateLayoutSpecForControls = [_delegate respondsToSelector:@selector(videoPlayerNodeLayoutSpec:forControls:forMaximumSize:)]; + _delegateFlags.delegateVideoNodeDidPlayToTime = [_delegate respondsToSelector:@selector(videoPlayerNode:didPlayToTime:)]; + _delegateFlags.delegateVideoNodeWillChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:willChangeVideoNodeState:toVideoNodeState:)]; + _delegateFlags.delegateVideoNodePlaybackDidFinish = [_delegate respondsToSelector:@selector(videoPlayerNodeDidPlayToEnd:)]; + _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)]; + _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; + _delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)]; + _delegateFlags.delegateDidTapVideoPlayerNode = [_delegate respondsToSelector:@selector(didTapVideoPlayerNode:)]; + } +} + +- (void)setControlsDisabled:(BOOL)controlsDisabled +{ + if (_controlsDisabled == controlsDisabled) { + return; + } + + _controlsDisabled = controlsDisabled; + + if (_controlsDisabled && _cachedControls.count > 0) { + [self removeControls]; + } else if (!_controlsDisabled) { + [self createControls]; + } +} + +- (void)setShouldAutoPlay:(BOOL)shouldAutoPlay +{ + if (_shouldAutoPlay == shouldAutoPlay) { + return; + } + _shouldAutoPlay = shouldAutoPlay; + _videoNode.shouldAutoplay = _shouldAutoPlay; +} + +- (void)setShouldAutoRepeat:(BOOL)shouldAutoRepeat +{ + if (_shouldAutoRepeat == shouldAutoRepeat) { + return; + } + _shouldAutoRepeat = shouldAutoRepeat; + _videoNode.shouldAutorepeat = _shouldAutoRepeat; +} + +- (void)setMuted:(BOOL)muted +{ + if (_muted == muted) { + return; + } + _muted = muted; + _videoNode.muted = _muted; +} + +- (void)setPeriodicTimeObserverTimescale:(int32_t)periodicTimeObserverTimescale +{ + if (_periodicTimeObserverTimescale == periodicTimeObserverTimescale) { + return; + } + _periodicTimeObserverTimescale = periodicTimeObserverTimescale; + _videoNode.periodicTimeObserverTimescale = _periodicTimeObserverTimescale; +} + +- (NSString *)gravity +{ + if (_gravity == nil) { + _gravity = _videoNode.gravity; + } + return _gravity; +} + +- (void)setGravity:(NSString *)gravity +{ + if (_gravity == gravity) { + return; + } + _gravity = gravity; + _videoNode.gravity = _gravity; +} + +- (ASVideoNodePlayerState)playerState +{ + return _videoNode.playerState; +} + +- (BOOL)shouldAggressivelyRecoverFromStall +{ + return _videoNode.shouldAggressivelyRecoverFromStall; +} + +- (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFromStall +{ + if (_shouldAggressivelyRecoverFromStall == shouldAggressivelyRecoverFromStall) { + return; + } + _shouldAggressivelyRecoverFromStall = shouldAggressivelyRecoverFromStall; + _videoNode.shouldAggressivelyRecoverFromStall = _shouldAggressivelyRecoverFromStall; +} + +#pragma mark - Helpers +- (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type +{ + if (_delegateFlags.delegateTimeLabelAttributedString) { + return [_delegate videoPlayerNode:self timeStringForTimeLabelType:type forTime:time]; + } + + NSUInteger dTotalSeconds = CMTimeGetSeconds(time); + + NSUInteger dHours = floor(dTotalSeconds / 3600); + NSUInteger dMinutes = floor(dTotalSeconds % 3600 / 60); + NSUInteger dSeconds = floor(dTotalSeconds % 3600 % 60); + + NSString *videoDurationText; + if (dHours > 0) { + videoDurationText = [NSString stringWithFormat:@"%i:%02i:%02i", (int)dHours, (int)dMinutes, (int)dSeconds]; + } else { + videoDurationText = [NSString stringWithFormat:@"%02i:%02i", (int)dMinutes, (int)dSeconds]; + } + return videoDurationText; +} + +@end diff --git a/AsyncDisplayKit/Private/ASDefaultPlaybackButton.h b/AsyncDisplayKit/Private/ASDefaultPlaybackButton.h new file mode 100644 index 0000000000..9f502688e1 --- /dev/null +++ b/AsyncDisplayKit/Private/ASDefaultPlaybackButton.h @@ -0,0 +1,16 @@ +// +// ASDefaultPlaybackButton.h +// AsyncDisplayKit +// +// Created by Erekle on 5/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +typedef enum { + ASDefaultPlaybackButtonTypePlay, + ASDefaultPlaybackButtonTypePause +} ASDefaultPlaybackButtonType; +@interface ASDefaultPlaybackButton : ASControlNode +@property (nonatomic, assign) ASDefaultPlaybackButtonType buttonType; +@end diff --git a/AsyncDisplayKit/Private/ASDefaultPlaybackButton.m b/AsyncDisplayKit/Private/ASDefaultPlaybackButton.m new file mode 100644 index 0000000000..6e9455bde5 --- /dev/null +++ b/AsyncDisplayKit/Private/ASDefaultPlaybackButton.m @@ -0,0 +1,81 @@ +// +// ASDefaultPlaybackButton.m +// AsyncDisplayKit +// +// Created by Erekle on 5/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASDefaultPlaybackButton.h" +@interface ASDefaultPlaybackButton() +{ + ASDefaultPlaybackButtonType _buttonType; +} +@end + +@implementation ASDefaultPlaybackButton +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + self.opaque = NO; + + return self; +} + +- (void)setButtonType:(ASDefaultPlaybackButtonType)buttonType +{ + ASDefaultPlaybackButtonType oldType = _buttonType; + _buttonType = buttonType; + + if (oldType != _buttonType) { + [self setNeedsDisplay]; + } +} + +- (nullable id)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer +{ + return @{ + @"buttonType" : [NSNumber numberWithInt:_buttonType], + @"color" : self.tintColor + }; +} + ++ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing +{ + ASDefaultPlaybackButtonType buttonType = [parameters[@"buttonType"] intValue]; + UIColor *color = parameters[@"color"]; + + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSaveGState(context); + UIBezierPath* bezierPath = [UIBezierPath bezierPath]; + if (buttonType == ASDefaultPlaybackButtonTypePlay) { + [bezierPath moveToPoint: CGPointMake(0, 0)]; + [bezierPath addLineToPoint: CGPointMake(0, bounds.size.height)]; + [bezierPath addLineToPoint: CGPointMake(bounds.size.width, bounds.size.height/2)]; + [bezierPath addLineToPoint: CGPointMake(0, 0)]; + [bezierPath closePath]; + } else if (buttonType == ASDefaultPlaybackButtonTypePause) { + CGFloat pauseSingleLineWidth = bounds.size.width / 3.0; + [bezierPath moveToPoint: CGPointMake(0, bounds.size.height)]; + [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth, bounds.size.height)]; + [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth, 0)]; + [bezierPath addLineToPoint: CGPointMake(0, 0)]; + [bezierPath addLineToPoint: CGPointMake(0, bounds.size.height)]; + [bezierPath closePath]; + [bezierPath moveToPoint: CGPointMake(pauseSingleLineWidth * 2, 0)]; + [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth * 2, bounds.size.height)]; + [bezierPath addLineToPoint: CGPointMake(bounds.size.width, bounds.size.height)]; + [bezierPath addLineToPoint: CGPointMake(bounds.size.width, 0)]; + [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth * 2, 0)]; + [bezierPath closePath]; + } + + [color setFill]; + [bezierPath fill]; + + CGContextRestoreGState(context); +} +@end diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 6530edd4f6..72e78cbf27 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -50,13 +50,6 @@ _requestedKeys = @[ @"playable" ]; } - -- (void)testSpinnerDefaultsToNil -{ - XCTAssertNil(_videoNode.spinner); -} - - - (void)testOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnode { _videoNode.asset = _firstAsset; @@ -73,8 +66,6 @@ { _videoNode.interfaceState = ASInterfaceStateFetchData; [_videoNode play]; - - XCTAssertNotNil(_videoNode.spinner); } @@ -96,8 +87,7 @@ [_videoNode play]; [_videoNode pause]; - - XCTAssertFalse(((UIActivityIndicatorView *)_videoNode.spinner.view).isAnimating); + } @@ -119,8 +109,6 @@ [_videoNode play]; [_videoNode observeValueForKeyPath:@"status" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @(AVPlayerItemStatusReadyToPlay)} context:NULL]; - - XCTAssertFalse(((UIActivityIndicatorView *)_videoNode.spinner.view).isAnimating); } @@ -379,21 +367,6 @@ XCTAssertEqual(UIViewContentModeScaleAspectFill, _videoNode.placeholderImageNode.contentMode); } -- (void)testChangingPlayButtonPerformsProperCleanup -{ - ASButtonNode *firstButton = _videoNode.playButton; - XCTAssertTrue([firstButton.allTargets containsObject:_videoNode]); - - ASButtonNode *secondButton = [[ASButtonNode alloc] init]; - _videoNode.playButton = secondButton; - - XCTAssertTrue([secondButton.allTargets containsObject:_videoNode]); - XCTAssertEqual(_videoNode, secondButton.supernode); - - XCTAssertFalse([firstButton.allTargets containsObject:_videoNode]); - XCTAssertNotEqual(_videoNode, firstButton.supernode); -} - - (void)testChangingAssetsChangesPlaceholderImage { UIImage *firstImage = [[UIImage alloc] init]; diff --git a/examples/ASDKTube/Default-568h@2x.png b/examples/ASDKTube/Default-568h@2x.png new file mode 100644 index 0000000000..6ee80b9393 Binary files /dev/null and b/examples/ASDKTube/Default-568h@2x.png differ diff --git a/examples/ASDKTube/Default-667h@2x.png b/examples/ASDKTube/Default-667h@2x.png new file mode 100644 index 0000000000..e7b975e21b Binary files /dev/null and b/examples/ASDKTube/Default-667h@2x.png differ diff --git a/examples/ASDKTube/Default-736h@3x.png b/examples/ASDKTube/Default-736h@3x.png new file mode 100644 index 0000000000..c8949cae16 Binary files /dev/null and b/examples/ASDKTube/Default-736h@3x.png differ diff --git a/examples/ASDKTube/Podfile b/examples/ASDKTube/Podfile new file mode 100644 index 0000000000..919de4b311 --- /dev/null +++ b/examples/ASDKTube/Podfile @@ -0,0 +1,5 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '7.0' +target 'Sample' do + pod 'AsyncDisplayKit', :path => '../..' +end diff --git a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..1a8abd1257 --- /dev/null +++ b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,447 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 5791C5525B690FA54F26ACE8 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A2092CAF5607B3863A3700A2 /* libPods-Sample.a */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; + 8B0768B81CE7AD03002E1453 /* VideoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B71CE7AD03002E1453 /* VideoModel.m */; }; + 8B0768BC1CE7B091002E1453 /* VideoContentCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768BB1CE7B091002E1453 /* VideoContentCell.m */; }; + 8B0768BF1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768BE1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.m */; }; + 8B0768C51CE7C707002E1453 /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768C41CE7C707002E1453 /* Utilities.m */; }; + 8B0768C91CE7C889002E1453 /* VideoFeedNodeController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768C81CE7C889002E1453 /* VideoFeedNodeController.m */; }; + 8B9075851CF386A400F924C1 /* ico-unmute.png in Resources */ = {isa = PBXBuildFile; fileRef = 8B90757F1CF386A400F924C1 /* ico-unmute.png */; }; + 8B9075861CF386A400F924C1 /* ico-unmute@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8B9075801CF386A400F924C1 /* ico-unmute@2x.png */; }; + 8B9075871CF386A400F924C1 /* ico-unmute@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8B9075811CF386A400F924C1 /* ico-unmute@3x.png */; }; + 8B9075881CF386A400F924C1 /* ico-mute.png in Resources */ = {isa = PBXBuildFile; fileRef = 8B9075821CF386A400F924C1 /* ico-mute.png */; }; + 8B9075891CF386A400F924C1 /* ico-mute@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8B9075831CF386A400F924C1 /* ico-mute@2x.png */; }; + 8B90758A1CF386A400F924C1 /* ico-mute@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8B9075841CF386A400F924C1 /* ico-mute@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + 8B0768B61CE7AD03002E1453 /* VideoModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoModel.h; sourceTree = ""; }; + 8B0768B71CE7AD03002E1453 /* VideoModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoModel.m; sourceTree = ""; }; + 8B0768BA1CE7B091002E1453 /* VideoContentCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoContentCell.h; sourceTree = ""; }; + 8B0768BB1CE7B091002E1453 /* VideoContentCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoContentCell.m; sourceTree = ""; }; + 8B0768BD1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowWithStatusBarUnderlay.h; sourceTree = ""; }; + 8B0768BE1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WindowWithStatusBarUnderlay.m; sourceTree = ""; }; + 8B0768C31CE7C707002E1453 /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; + 8B0768C41CE7C707002E1453 /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = ""; }; + 8B0768C71CE7C889002E1453 /* VideoFeedNodeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoFeedNodeController.h; sourceTree = ""; }; + 8B0768C81CE7C889002E1453 /* VideoFeedNodeController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoFeedNodeController.m; sourceTree = ""; }; + 8B90757F1CF386A400F924C1 /* ico-unmute.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ico-unmute.png"; sourceTree = ""; }; + 8B9075801CF386A400F924C1 /* ico-unmute@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ico-unmute@2x.png"; sourceTree = ""; }; + 8B9075811CF386A400F924C1 /* ico-unmute@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ico-unmute@3x.png"; sourceTree = ""; }; + 8B9075821CF386A400F924C1 /* ico-mute.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ico-mute.png"; sourceTree = ""; }; + 8B9075831CF386A400F924C1 /* ico-mute@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ico-mute@2x.png"; sourceTree = ""; }; + 8B9075841CF386A400F924C1 /* ico-mute@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ico-mute@3x.png"; sourceTree = ""; }; + A2092CAF5607B3863A3700A2 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + CFD6AA1D30516C27DEE5602B /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + E51646FF8D3676A1D826A5AE /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5791C5525B690FA54F26ACE8 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 8B90757E1CF3869100F924C1 /* Icons */, + 8B0768C61CE7C85F002E1453 /* Controller */, + 8B0768B91CE7B07E002E1453 /* Nodes */, + 8B0768B51CE7ACE8002E1453 /* Models */, + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + 8B0768BD1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.h */, + 8B0768BE1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.m */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + A2092CAF5607B3863A3700A2 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + CFD6AA1D30516C27DEE5602B /* Pods-Sample.debug.xcconfig */, + E51646FF8D3676A1D826A5AE /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 8B0768B51CE7ACE8002E1453 /* Models */ = { + isa = PBXGroup; + children = ( + 8B0768C31CE7C707002E1453 /* Utilities.h */, + 8B0768C41CE7C707002E1453 /* Utilities.m */, + 8B0768B61CE7AD03002E1453 /* VideoModel.h */, + 8B0768B71CE7AD03002E1453 /* VideoModel.m */, + ); + path = Models; + sourceTree = ""; + }; + 8B0768B91CE7B07E002E1453 /* Nodes */ = { + isa = PBXGroup; + children = ( + 8B0768BA1CE7B091002E1453 /* VideoContentCell.h */, + 8B0768BB1CE7B091002E1453 /* VideoContentCell.m */, + ); + path = Nodes; + sourceTree = ""; + }; + 8B0768C61CE7C85F002E1453 /* Controller */ = { + isa = PBXGroup; + children = ( + 8B0768C71CE7C889002E1453 /* VideoFeedNodeController.h */, + 8B0768C81CE7C889002E1453 /* VideoFeedNodeController.m */, + ); + path = Controller; + sourceTree = ""; + }; + 8B90757E1CF3869100F924C1 /* Icons */ = { + isa = PBXGroup; + children = ( + 8B90757F1CF386A400F924C1 /* ico-unmute.png */, + 8B9075801CF386A400F924C1 /* ico-unmute@2x.png */, + 8B9075811CF386A400F924C1 /* ico-unmute@3x.png */, + 8B9075821CF386A400F924C1 /* ico-mute.png */, + 8B9075831CF386A400F924C1 /* ico-mute@2x.png */, + 8B9075841CF386A400F924C1 /* ico-mute@3x.png */, + ); + path = Icons; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */, + 93B7780A33739EF25F20366B /* 📦 Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8B9075861CF386A400F924C1 /* ico-unmute@2x.png in Resources */, + 8B9075881CF386A400F924C1 /* ico-mute.png in Resources */, + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + 8B90758A1CF386A400F924C1 /* ico-mute@3x.png in Resources */, + 8B9075851CF386A400F924C1 /* ico-unmute.png in Resources */, + 8B9075871CF386A400F924C1 /* ico-unmute@3x.png in Resources */, + 8B9075891CF386A400F924C1 /* ico-mute@2x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 93B7780A33739EF25F20366B /* 📦 Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "📦 Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "📦 Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "📦 Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 8B0768C91CE7C889002E1453 /* VideoFeedNodeController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 8B0768BC1CE7B091002E1453 /* VideoContentCell.m in Sources */, + 8B0768BF1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + 8B0768C51CE7C707002E1453 /* Utilities.m in Sources */, + 8B0768B81CE7AD03002E1453 /* VideoModel.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* 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; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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 = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* 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; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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 = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CFD6AA1D30516C27DEE5602B /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E51646FF8D3676A1D826A5AE /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples/ASDKTube/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/ASDKTube/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/ASDKTube/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ASDKTube/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/ASDKTube/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..1e14aa0329 --- /dev/null +++ b/examples/ASDKTube/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASDKTube/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASDKTube/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/ASDKTube/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/ASDKTube/Sample/AppDelegate.h b/examples/ASDKTube/Sample/AppDelegate.h new file mode 100644 index 0000000000..2aa29369b4 --- /dev/null +++ b/examples/ASDKTube/Sample/AppDelegate.h @@ -0,0 +1,18 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/ASDKTube/Sample/AppDelegate.m b/examples/ASDKTube/Sample/AppDelegate.m new file mode 100644 index 0000000000..947d95ca09 --- /dev/null +++ b/examples/ASDKTube/Sample/AppDelegate.m @@ -0,0 +1,45 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "AppDelegate.h" +#import "WindowWithStatusBarUnderlay.h" +#import "Utilities.h" +#import "VideoFeedNodeController.h" + + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + // this UIWindow subclass is neccessary to make the status bar opaque + _window = [[WindowWithStatusBarUnderlay alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + _window.backgroundColor = [UIColor whiteColor]; + + + VideoFeedNodeController *asdkHomeFeedVC = [[VideoFeedNodeController alloc] init]; + UINavigationController *asdkHomeFeedNavCtrl = [[UINavigationController alloc] initWithRootViewController:asdkHomeFeedVC]; + + + _window.rootViewController = asdkHomeFeedNavCtrl; + [_window makeKeyAndVisible]; + + // Nav Bar appearance + NSDictionary *attributes = @{NSForegroundColorAttributeName:[UIColor whiteColor]}; + [[UINavigationBar appearance] setTitleTextAttributes:attributes]; + [[UINavigationBar appearance] setBarTintColor:[UIColor lighOrangeColor]]; + [[UINavigationBar appearance] setTranslucent:NO]; + + [application setStatusBarStyle:UIStatusBarStyleLightContent]; + + + return YES; +} +@end diff --git a/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.h b/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.h new file mode 100644 index 0000000000..28d7758f6b --- /dev/null +++ b/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.h @@ -0,0 +1,13 @@ +// +// VideoFeedNodeController.h +// Sample +// +// Created by Erekle on 5/15/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface VideoFeedNodeController : ASViewController + +@end diff --git a/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.m b/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.m new file mode 100644 index 0000000000..fea764e18e --- /dev/null +++ b/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.m @@ -0,0 +1,71 @@ +// +// VideoFeedNodeController.m +// Sample +// +// Created by Erekle on 5/15/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "VideoFeedNodeController.h" +#import +#import "VideoModel.h" +#import "VideoContentCell.h" + +@interface VideoFeedNodeController () + +@end + +@implementation VideoFeedNodeController +{ + ASTableNode *_tableNode; + NSMutableArray *_videoFeedData; +} + +- (instancetype)init +{ + self.navigationItem.title = @"Home"; + _tableNode = [[ASTableNode alloc] init]; + _tableNode.delegate = self; + _tableNode.dataSource = self; + + if (!(self = [super initWithNode:_tableNode])) { + return nil; + } + + return self; +} + +- (void)loadView +{ + [super loadView]; + + [self generateFeedData]; + + [_tableNode.view reloadData]; +} + +- (void)generateFeedData +{ + _videoFeedData = [[NSMutableArray alloc] init]; + + for (int i = 0; i < 30; i++) { + [_videoFeedData addObject:[[VideoModel alloc] init]]; + } +} + +#pragma mark - ASCollectionDelegate - ASCollectionDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ + return _videoFeedData.count; +} + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + VideoModel *videoObject = [_videoFeedData objectAtIndex:indexPath.row]; + VideoContentCell *cellNode = [[VideoContentCell alloc] initWithVideoObject:videoObject]; + return cellNode; +} +@end diff --git a/examples/ASDKTube/Sample/Icons/ico-mute.png b/examples/ASDKTube/Sample/Icons/ico-mute.png new file mode 100644 index 0000000000..1f700e3a5d Binary files /dev/null and b/examples/ASDKTube/Sample/Icons/ico-mute.png differ diff --git a/examples/ASDKTube/Sample/Icons/ico-mute@2x.png b/examples/ASDKTube/Sample/Icons/ico-mute@2x.png new file mode 100644 index 0000000000..025f1386a4 Binary files /dev/null and b/examples/ASDKTube/Sample/Icons/ico-mute@2x.png differ diff --git a/examples/ASDKTube/Sample/Icons/ico-mute@3x.png b/examples/ASDKTube/Sample/Icons/ico-mute@3x.png new file mode 100644 index 0000000000..a4815608b9 Binary files /dev/null and b/examples/ASDKTube/Sample/Icons/ico-mute@3x.png differ diff --git a/examples/ASDKTube/Sample/Icons/ico-unmute.png b/examples/ASDKTube/Sample/Icons/ico-unmute.png new file mode 100644 index 0000000000..52d15943f7 Binary files /dev/null and b/examples/ASDKTube/Sample/Icons/ico-unmute.png differ diff --git a/examples/ASDKTube/Sample/Icons/ico-unmute@2x.png b/examples/ASDKTube/Sample/Icons/ico-unmute@2x.png new file mode 100644 index 0000000000..e671374d47 Binary files /dev/null and b/examples/ASDKTube/Sample/Icons/ico-unmute@2x.png differ diff --git a/examples/ASDKTube/Sample/Icons/ico-unmute@3x.png b/examples/ASDKTube/Sample/Icons/ico-unmute@3x.png new file mode 100644 index 0000000000..2fa2462d98 Binary files /dev/null and b/examples/ASDKTube/Sample/Icons/ico-unmute@3x.png differ diff --git a/examples/ASDKTube/Sample/Info.plist b/examples/ASDKTube/Sample/Info.plist new file mode 100644 index 0000000000..3b4c6c7839 --- /dev/null +++ b/examples/ASDKTube/Sample/Info.plist @@ -0,0 +1,38 @@ + + + + + UIViewControllerBasedStatusBarAppearance + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/examples/ASDKTube/Sample/Models/Utilities.h b/examples/ASDKTube/Sample/Models/Utilities.h new file mode 100644 index 0000000000..8928fd9747 --- /dev/null +++ b/examples/ASDKTube/Sample/Models/Utilities.h @@ -0,0 +1,40 @@ +// +// Utilities.h +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// +#include +@interface UIColor (Additions) + ++ (UIColor *)lighOrangeColor; ++ (UIColor *)darkBlueColor; ++ (UIColor *)lightBlueColor; + +@end + +@interface UIImage (Additions) + ++ (UIImage *)followingButtonStretchableImageForCornerRadius:(CGFloat)cornerRadius following:(BOOL)followingEnabled; ++ (void)downloadImageForURL:(NSURL *)url completion:(void (^)(UIImage *))block; + +- (UIImage *)makeCircularImageWithSize:(CGSize)size; + +@end + +@interface NSString (Additions) + +// returns a user friendly elapsed time such as '50s', '6m' or '3w' ++ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString; + +@end + +@interface NSAttributedString (Additions) + ++ (NSAttributedString *)attributedStringWithString:(NSString *)string + fontSize:(CGFloat)size + color:(UIColor *)color + firstWordColor:(UIColor *)firstWordColor; + +@end \ No newline at end of file diff --git a/examples/ASDKTube/Sample/Models/Utilities.m b/examples/ASDKTube/Sample/Models/Utilities.m new file mode 100644 index 0000000000..79b393c106 --- /dev/null +++ b/examples/ASDKTube/Sample/Models/Utilities.m @@ -0,0 +1,230 @@ +// +// Utilities.m +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "Utilities.h" + +#define StrokeRoundedImages 0 + +@implementation UIColor (Additions) + ++ (UIColor *)lighOrangeColor +{ + return [UIColor colorWithRed:1 green:0.506 blue:0.384 alpha:1]; +} + ++ (UIColor *)darkBlueColor +{ + return [UIColor colorWithRed:70.0/255.0 green:102.0/255.0 blue:118.0/255.0 alpha:1.0]; +} + ++ (UIColor *)lightBlueColor +{ + return [UIColor colorWithRed:70.0/255.0 green:165.0/255.0 blue:196.0/255.0 alpha:1.0]; +} + +@end + +@implementation UIImage (Additions) + ++ (UIImage *)followingButtonStretchableImageForCornerRadius:(CGFloat)cornerRadius following:(BOOL)followingEnabled +{ + CGSize unstretchedSize = CGSizeMake(2 * cornerRadius + 1, 2 * cornerRadius + 1); + CGRect rect = (CGRect) {CGPointZero, unstretchedSize}; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius]; + + // create a graphics context for the following status button + UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0); + + [path addClip]; + + if (followingEnabled) { + + [[UIColor whiteColor] setFill]; + [path fill]; + + path.lineWidth = 3; + [[UIColor lightBlueColor] setStroke]; + [path stroke]; + + } else { + + [[UIColor lightBlueColor] setFill]; + [path fill]; + } + + UIImage *followingBtnImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + UIImage *followingBtnImageStretchable = [followingBtnImage stretchableImageWithLeftCapWidth:cornerRadius + topCapHeight:cornerRadius]; + return followingBtnImageStretchable; +} + ++ (void)downloadImageForURL:(NSURL *)url completion:(void (^)(UIImage *))block +{ + static NSCache *simpleImageCache = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + simpleImageCache = [[NSCache alloc] init]; + simpleImageCache.countLimit = 10; + }); + + if (!block) { + return; + } + + // check if image is cached + UIImage *image = [simpleImageCache objectForKey:url]; + if (image) { + dispatch_async(dispatch_get_main_queue(), ^{ + block(image); + }); + } else { + // else download image + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (data) { + UIImage *image = [UIImage imageWithData:data]; + dispatch_async(dispatch_get_main_queue(), ^{ + block(image); + }); + } + }]; + [task resume]; + } +} + +- (UIImage *)makeCircularImageWithSize:(CGSize)size +{ + // make a CGRect with the image's size + CGRect circleRect = (CGRect) {CGPointZero, size}; + + // begin the image context since we're not in a drawRect: + UIGraphicsBeginImageContextWithOptions(circleRect.size, NO, 0); + + // create a UIBezierPath circle + UIBezierPath *circle = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:circleRect.size.width/2]; + + // clip to the circle + [circle addClip]; + + // draw the image in the circleRect *AFTER* the context is clipped + [self drawInRect:circleRect]; + + // create a border (for white background pictures) +#if StrokeRoundedImages + circle.lineWidth = 1; + [[UIColor darkGrayColor] set]; + [circle stroke]; +#endif + + // get an image from the image context + UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext(); + + // end the image context since we're not in a drawRect: + UIGraphicsEndImageContext(); + + return roundedImage; +} + +@end + +@implementation NSString (Additions) + +// Returns a user-visible date time string that corresponds to the +// specified RFC 3339 date time string. Note that this does not handle +// all possible RFC 3339 date time strings, just one of the most common +// styles. ++ (NSDate *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString +{ + NSDateFormatter * rfc3339DateFormatter; + NSLocale * enUSPOSIXLocale; + + // Convert the RFC 3339 date time string to an NSDate. + + rfc3339DateFormatter = [[NSDateFormatter alloc] init]; + + enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + + [rfc3339DateFormatter setLocale:enUSPOSIXLocale]; + [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ'"]; + [rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + return [rfc3339DateFormatter dateFromString:rfc3339DateTimeString]; +} + ++ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString +{ + // early return if no post date string + if (!uploadDateString) + { + return @"NO POST DATE"; + } + + NSDate *postDate = [self userVisibleDateTimeStringForRFC3339DateTimeString:uploadDateString]; + + if (!postDate) { + return @"DATE CONVERSION ERROR"; + } + + NSDate *currentDate = [NSDate date]; + + NSCalendar *calendar = [NSCalendar currentCalendar]; + + NSUInteger seconds = [[calendar components:NSCalendarUnitSecond fromDate:postDate toDate:currentDate options:0] second]; + NSUInteger minutes = [[calendar components:NSCalendarUnitMinute fromDate:postDate toDate:currentDate options:0] minute]; + NSUInteger hours = [[calendar components:NSCalendarUnitHour fromDate:postDate toDate:currentDate options:0] hour]; + NSUInteger days = [[calendar components:NSCalendarUnitDay fromDate:postDate toDate:currentDate options:0] day]; + + NSString *elapsedTime; + + if (days > 7) { + elapsedTime = [NSString stringWithFormat:@"%luw", (long)ceil(days/7.0)]; + } else if (days > 0) { + elapsedTime = [NSString stringWithFormat:@"%lud", (long)days]; + } else if (hours > 0) { + elapsedTime = [NSString stringWithFormat:@"%luh", (long)hours]; + } else if (minutes > 0) { + elapsedTime = [NSString stringWithFormat:@"%lum", (long)minutes]; + } else if (seconds > 0) { + elapsedTime = [NSString stringWithFormat:@"%lus", (long)seconds]; + } else if (seconds == 0) { + elapsedTime = @"1s"; + } else { + elapsedTime = @"ERROR"; + } + + return elapsedTime; +} + +@end + +@implementation NSAttributedString (Additions) + ++ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size + color:(nullable UIColor *)color firstWordColor:(nullable UIColor *)firstWordColor +{ + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; + + if (string) { + NSDictionary *attributes = @{NSForegroundColorAttributeName: color ? : [UIColor blackColor], + NSFontAttributeName: [UIFont systemFontOfSize:size]}; + attributedString = [[NSMutableAttributedString alloc] initWithString:string]; + [attributedString addAttributes:attributes range:NSMakeRange(0, string.length)]; + + if (firstWordColor) { + NSRange firstSpaceRange = [string rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + NSRange firstWordRange = NSMakeRange(0, firstSpaceRange.location); + [attributedString addAttribute:NSForegroundColorAttributeName value:firstWordColor range:firstWordRange]; + } + } + + return attributedString; +} + +@end diff --git a/examples/ASDKTube/Sample/Models/VideoModel.h b/examples/ASDKTube/Sample/Models/VideoModel.h new file mode 100644 index 0000000000..63e7f48236 --- /dev/null +++ b/examples/ASDKTube/Sample/Models/VideoModel.h @@ -0,0 +1,16 @@ +// +// VideoModel.h +// Sample +// +// Created by Erekle on 5/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface VideoModel : NSObject +@property (nonatomic, strong, readonly) NSString* title; +@property (nonatomic, strong, readonly) NSURL *url; +@property (nonatomic, strong, readonly) NSString *userName; +@property (nonatomic, strong, readonly) NSURL *avatarUrl; +@end diff --git a/examples/ASDKTube/Sample/Models/VideoModel.m b/examples/ASDKTube/Sample/Models/VideoModel.m new file mode 100644 index 0000000000..2f233d59ce --- /dev/null +++ b/examples/ASDKTube/Sample/Models/VideoModel.m @@ -0,0 +1,27 @@ +// +// VideoModel.m +// Sample +// +// Created by Erekle on 5/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "VideoModel.h" + +@implementation VideoModel +- (instancetype)init +{ + self = [super init]; + if (self) { + NSString *videoUrlString = @"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov"; + NSString *avatarUrlString = [NSString stringWithFormat:@"https://api.adorable.io/avatars/50/%@",[[NSProcessInfo processInfo] globallyUniqueString]]; + + _title = @"Demo title"; + _url = [NSURL URLWithString:videoUrlString]; + _userName = @"Random User"; + _avatarUrl = [NSURL URLWithString:avatarUrlString]; + } + + return self; +} +@end diff --git a/examples/ASDKTube/Sample/Nodes/VideoContentCell.h b/examples/ASDKTube/Sample/Nodes/VideoContentCell.h new file mode 100644 index 0000000000..ca6bb3256e --- /dev/null +++ b/examples/ASDKTube/Sample/Nodes/VideoContentCell.h @@ -0,0 +1,14 @@ +// +// VideoContentCell.h +// Sample +// +// Created by Erekle on 5/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import "VideoModel.h" + +@interface VideoContentCell : ASCellNode +- (instancetype)initWithVideoObject:(VideoModel *)video; +@end diff --git a/examples/ASDKTube/Sample/Nodes/VideoContentCell.m b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m new file mode 100644 index 0000000000..f38757bbd0 --- /dev/null +++ b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m @@ -0,0 +1,217 @@ +// +// VideoContentCell.m +// Sample +// +// Created by Erekle on 5/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "VideoContentCell.h" +#import "ASVideoPlayerNode.h" +#import "Utilities.h" + +#define AVATAR_IMAGE_HEIGHT 30 +#define HORIZONTAL_BUFFER 10 +#define VERTICAL_BUFFER 5 + +@interface VideoContentCell () + +@end + +@implementation VideoContentCell +{ + VideoModel *_videoModel; + ASTextNode *_titleNode; + ASNetworkImageNode *_avatarNode; + ASVideoPlayerNode *_videoPlayerNode; + ASControlNode *_likeButtonNode; + ASButtonNode *_muteButtonNode; +} + +- (instancetype)initWithVideoObject:(VideoModel *)video +{ + self = [super init]; + if (self) { + _videoModel = video; + + _titleNode = [[ASTextNode alloc] init]; + _titleNode.attributedText = [[NSAttributedString alloc] initWithString:_videoModel.title attributes:[self titleNodeStringOptions]]; + _titleNode.flexGrow = YES; + [self addSubnode:_titleNode]; + + _avatarNode = [[ASNetworkImageNode alloc] init]; + _avatarNode.URL = _videoModel.avatarUrl; + + [_avatarNode setImageModificationBlock:^UIImage *(UIImage *image) { + CGSize profileImageSize = CGSizeMake(AVATAR_IMAGE_HEIGHT, AVATAR_IMAGE_HEIGHT); + return [image makeCircularImageWithSize:profileImageSize]; + }]; + + [self addSubnode:_avatarNode]; + + _likeButtonNode = [[ASControlNode alloc] init]; + _likeButtonNode.backgroundColor = [UIColor redColor]; + [self addSubnode:_likeButtonNode]; + + _muteButtonNode = [[ASButtonNode alloc] init]; + _muteButtonNode.preferredFrameSize = CGSizeMake(16.0, 22.0); + [_muteButtonNode addTarget:self action:@selector(didTapMuteButton) forControlEvents:ASControlNodeEventTouchUpInside]; + + _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:_videoModel.url loadAssetWhenNodeBecomesVisible:YES]; + _videoPlayerNode.delegate = self; + _videoPlayerNode.backgroundColor = [UIColor blackColor]; + [self addSubnode:_videoPlayerNode]; + + [self setMuteButtonIcon]; + } + return self; +} + +- (NSDictionary*)titleNodeStringOptions +{ + return @{ + NSFontAttributeName : [UIFont systemFontOfSize:14.0], + NSForegroundColorAttributeName: [UIColor blackColor] + }; +} + +- (ASLayoutSpec*)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + CGFloat fullWidth = [UIScreen mainScreen].bounds.size.width; + _videoPlayerNode.preferredFrameSize = CGSizeMake(fullWidth, fullWidth * 9 / 16); + _avatarNode.preferredFrameSize = CGSizeMake(AVATAR_IMAGE_HEIGHT, AVATAR_IMAGE_HEIGHT); + _likeButtonNode.preferredFrameSize = CGSizeMake(50.0, 26.0); + + ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; + headerStack.spacing = HORIZONTAL_BUFFER; + headerStack.alignItems = ASStackLayoutAlignItemsCenter; + [headerStack setChildren:@[ _avatarNode, _titleNode]]; + + UIEdgeInsets headerInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *headerInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:headerInsets child:headerStack]; + + ASStackLayoutSpec *bottomControlsStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; + bottomControlsStack.spacing = HORIZONTAL_BUFFER; + bottomControlsStack.alignItems = ASStackLayoutAlignItemsCenter; + [bottomControlsStack setChildren:@[ _likeButtonNode]]; + + UIEdgeInsets bottomControlsInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *bottomControlsInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:bottomControlsInsets child:bottomControlsStack]; + + + ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStack.alignItems = ASStackLayoutAlignItemsStretch; + [verticalStack setChildren:@[ headerInset, _videoPlayerNode, bottomControlsInset ]]; + return verticalStack; +} + +- (void)setMuteButtonIcon +{ + if (_videoPlayerNode.muted) { + [_muteButtonNode setImage:[UIImage imageNamed:@"ico-mute"] forState:ASControlStateNormal]; + } else { + [_muteButtonNode setImage:[UIImage imageNamed:@"ico-unmute"] forState:ASControlStateNormal]; + } +} + +- (void)didTapMuteButton +{ + _videoPlayerNode.muted = !_videoPlayerNode.muted; + [self setMuteButtonIcon]; +} + +#pragma mark - ASVideoPlayerNodeDelegate +- (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer +{ + if (_videoPlayerNode.playerState == ASVideoNodePlayerStatePlaying) { + NSLog(@"TRANSITION"); + _videoPlayerNode.controlsDisabled = !_videoPlayerNode.controlsDisabled; + [_videoPlayerNode pause]; + } else { + [_videoPlayerNode play]; + } +} + +- (NSDictionary *)videoPlayerNodeCustomControls:(ASVideoPlayerNode *)videoPlayer +{ + return @{ + @"muteControl" : _muteButtonNode + }; +} + +- (NSArray *)controlsForControlBar:(NSDictionary *)availableControls +{ + NSMutableArray *controls = [[NSMutableArray alloc] init]; + + if (availableControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]) { + [controls addObject:availableControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]]; + } + + if (availableControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]) { + [controls addObject:availableControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]]; + } + + if (availableControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]) { + [controls addObject:availableControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]]; + } + + if (availableControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]) { + [controls addObject:availableControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]]; + } + + return controls; +} + +#pragma mark - Layout +- (ASLayoutSpec*)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer forControls:(NSDictionary *)controls forMaximumSize:(CGSize)maxSize +{ + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.flexGrow = YES; + + UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); + + if (controls[ @(ASVideoPlayerNodeControlTypeScrubber) ]) { + ASDisplayNode *scrubber = controls[ @(ASVideoPlayerNodeControlTypeScrubber) ]; + scrubber.preferredFrameSize = CGSizeMake(maxSize.width, 44.0); + } + + NSArray *controlBarControls = [self controlsForControlBar:controls]; + NSMutableArray *topBarControls = [[NSMutableArray alloc] init]; + + //Our custom control + if (controls[@"muteControl"]) { + [topBarControls addObject:controls[@"muteControl"]]; + } + + + ASStackLayoutSpec *topBarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:10.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children:topBarControls]; + + ASInsetLayoutSpec *topBarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:topBarSpec]; + + ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:10.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children: controlBarControls ]; + controlbarSpec.alignSelf = ASStackLayoutAlignSelfStretch; + + + + ASInsetLayoutSpec *controlbarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlbarSpec]; + + controlbarInsetSpec.alignSelf = ASStackLayoutAlignSelfStretch; + + ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + children:@[topBarInsetSpec, spacer, controlbarInsetSpec]]; + + return mainVerticalStack; + +} +@end diff --git a/examples/ASDKTube/Sample/ViewController.h b/examples/ASDKTube/Sample/ViewController.h new file mode 100644 index 0000000000..d4ec993c7b --- /dev/null +++ b/examples/ASDKTube/Sample/ViewController.h @@ -0,0 +1,16 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface ViewController : ASViewController + +@end diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m new file mode 100644 index 0000000000..adb64991c1 --- /dev/null +++ b/examples/ASDKTube/Sample/ViewController.m @@ -0,0 +1,213 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "ViewController.h" +#import +#import +#import "VideoModel.h" +#import "VideoContentCell.h" + +@interface ViewController() +@property (nonatomic, strong) ASVideoPlayerNode *videoPlayerNode; +@end + +@implementation ViewController +{ + ASTableNode *_tableNode; + NSMutableArray *_videoFeedData; +} + +- (instancetype)init +{ + _tableNode = [[ASTableNode alloc] init]; + _tableNode.delegate = self; + _tableNode.dataSource = self; + + if (!(self = [super initWithNode:_tableNode])) { + return nil; + } + + return self; +} + +- (void)loadView +{ + [super loadView]; + + _videoFeedData = [[NSMutableArray alloc] initWithObjects:[[VideoModel alloc] init], [[VideoModel alloc] init], nil]; + + [_tableNode.view reloadData]; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + //[self.view addSubnode:self.videoPlayerNode]; + + //[self.videoPlayerNode setNeedsLayout]; +} + +#pragma mark - ASCollectionDelegate - ASCollectionDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ + return _videoFeedData.count; +} + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + VideoModel *videoObject = [_videoFeedData objectAtIndex:indexPath.row]; + VideoContentCell *cellNode = [[VideoContentCell alloc] initWithVideoObject:videoObject]; + return cellNode; +} + +//- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath{ +// CGFloat fullWidth = [UIScreen mainScreen].bounds.size.width; +// return ASSizeRangeMake(CGSizeMake(fullWidth, 0.0), CGSizeMake(fullWidth, 400.0)); +//} + +- (ASVideoPlayerNode *)videoPlayerNode; +{ + if (_videoPlayerNode) { + return _videoPlayerNode; + } + + NSURL *fileUrl = [NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov"]; + + _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:fileUrl]; + _videoPlayerNode.delegate = self; +// _videoPlayerNode.disableControls = YES; +// +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +// _videoPlayerNode.disableControls = NO; +// }); + + _videoPlayerNode.backgroundColor = [UIColor blackColor]; + + return _videoPlayerNode; +} + +#pragma mark - ASVideoPlayerNodeDelegate +//- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer +//{ +// return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), +// @(ASVideoPlayerNodeControlTypeElapsedText), +// @(ASVideoPlayerNodeControlTypeScrubber), +// @(ASVideoPlayerNodeControlTypeDurationText) ]; +//} +// +//- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer +//{ +// return [UIColor colorWithRed:1 green:1 blue:1 alpha:0.3]; +//} +// +//- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer +//{ +// return [UIColor whiteColor]; +//} +// +//- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer +//{ +// return [UIColor whiteColor]; +//} +// +//- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayerNode timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType +//{ +// NSDictionary *options; +// +// if (timeLabelType == ASVideoPlayerNodeControlTypeElapsedText) { +// options = @{ +// NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:16.0], +// NSForegroundColorAttributeName: [UIColor orangeColor] +// }; +// } else if (timeLabelType == ASVideoPlayerNodeControlTypeDurationText) { +// options = @{ +// NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:16.0], +// NSForegroundColorAttributeName: [UIColor redColor] +// }; +// } +// +// return options; +//} + +/*- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer + forControls:(NSDictionary *)controls + forConstrainedSize:(ASSizeRange)constrainedSize +{ + + NSMutableArray *bottomControls = [[NSMutableArray alloc] init]; + NSMutableArray *topControls = [[NSMutableArray alloc] init]; + + ASDisplayNode *scrubberNode = controls[@(ASVideoPlayerNodeControlTypeScrubber)]; + ASDisplayNode *playbackButtonNode = controls[@(ASVideoPlayerNodeControlTypePlaybackButton)]; + ASTextNode *elapsedTexNode = controls[@(ASVideoPlayerNodeControlTypeElapsedText)]; + ASTextNode *durationTexNode = controls[@(ASVideoPlayerNodeControlTypeDurationText)]; + + if (playbackButtonNode) { + [bottomControls addObject:playbackButtonNode]; + } + + if (scrubberNode) { + scrubberNode.preferredFrameSize = CGSizeMake(constrainedSize.max.width, 44.0); + [bottomControls addObject:scrubberNode]; + } + + if (elapsedTexNode) { + [topControls addObject:elapsedTexNode]; + } + + if (durationTexNode) { + [topControls addObject:durationTexNode]; + } + + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.flexGrow = YES; + + ASStackLayoutSpec *topBarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:10.0 + justifyContent:ASStackLayoutJustifyContentCenter + alignItems:ASStackLayoutAlignItemsCenter + children:topControls]; + + + + UIEdgeInsets topBarSpecInsets = UIEdgeInsetsMake(20.0, 10.0, 0.0, 10.0); + + ASInsetLayoutSpec *topBarSpecInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:topBarSpecInsets child:topBarSpec]; + topBarSpecInsetSpec.alignSelf = ASStackLayoutAlignSelfStretch; + + ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:10.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children:bottomControls]; + controlbarSpec.alignSelf = ASStackLayoutAlignSelfStretch; + + UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); + + ASInsetLayoutSpec *controlbarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlbarSpec]; + + controlbarInsetSpec.alignSelf = ASStackLayoutAlignSelfStretch; + + ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + children:@[ topBarSpecInsetSpec, spacer, controlbarInsetSpec ]]; + + + return mainVerticalStack; +}*/ + +@end \ No newline at end of file diff --git a/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.h b/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.h new file mode 100644 index 0000000000..43ad266374 --- /dev/null +++ b/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.h @@ -0,0 +1,13 @@ +// +// WindowWithStatusBarUnderlay.h +// Sample +// +// Created by Hannah Troisi on 4/10/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface WindowWithStatusBarUnderlay : UIWindow + +@end diff --git a/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.m b/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.m new file mode 100644 index 0000000000..d50df6b2df --- /dev/null +++ b/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.m @@ -0,0 +1,39 @@ +// +// WindowWithStatusBarUnderlay.m +// Sample +// +// Created by Erekle on 5/15/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "WindowWithStatusBarUnderlay.h" +#import "Utilities.h" + +@implementation WindowWithStatusBarUnderlay +{ + UIView *_statusBarOpaqueUnderlayView; +} + +-(instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + _statusBarOpaqueUnderlayView = [[UIView alloc] init]; + _statusBarOpaqueUnderlayView.backgroundColor = [UIColor lighOrangeColor]; + [self addSubview:_statusBarOpaqueUnderlayView]; + } + return self; +} + +-(void)layoutSubviews +{ + [super layoutSubviews]; + + [self bringSubviewToFront:_statusBarOpaqueUnderlayView]; + + CGRect statusBarFrame = CGRectZero; + statusBarFrame.size.width = [[UIScreen mainScreen] bounds].size.width; + statusBarFrame.size.height = [[UIApplication sharedApplication] statusBarFrame].size.height; + _statusBarOpaqueUnderlayView.frame = statusBarFrame; +} +@end diff --git a/examples/ASDKTube/Sample/main.m b/examples/ASDKTube/Sample/main.m new file mode 100644 index 0000000000..ae9488711c --- /dev/null +++ b/examples/ASDKTube/Sample/main.m @@ -0,0 +1,20 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj index d6a580f89b..b736656a9c 100644 --- a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj @@ -254,12 +254,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 06770D39D4186D6446B1BDD5 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */, + 06770D39D4186D6446B1BDD5 /* 📦 Embed Pods Frameworks */, ); buildRules = ( ); @@ -318,14 +318,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 06770D39D4186D6446B1BDD5 /* Embed Pods Frameworks */ = { + 06770D39D4186D6446B1BDD5 /* 📦 Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "📦 Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -333,14 +333,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Check Pods Manifest.lock"; + name = "📦 Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -348,14 +348,14 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "📦 Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; diff --git a/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/Videos/Sample/ViewController.m b/examples/Videos/Sample/ViewController.m index d34984c76f..db1f18659d 100644 --- a/examples/Videos/Sample/ViewController.m +++ b/examples/Videos/Sample/ViewController.m @@ -38,7 +38,6 @@ // Video node with custom play button ASVideoNode *simonVideoNode = self.simonVideoNode; - simonVideoNode.playButton = self.playButton; [_rootNode addSubnode:simonVideoNode]; _rootNode.layoutSpecBlock = ^ASLayoutSpec *(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { @@ -129,7 +128,7 @@ #pragma mark - Actions -- (void)videoNodeWasTapped:(ASVideoNode *)videoNode +- (void)didTapVideoNode:(ASVideoNode *)videoNode { if (videoNode == self.guitarVideoNode) { if (videoNode.playerState == ASVideoNodePlayerStatePlaying) { @@ -150,29 +149,29 @@ #pragma mark - ASVideoNodeDelegate -- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toSate +- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState { //Ignore nicCageVideo if (videoNode != _guitarVideoNode) { return; } - if (toSate == ASVideoNodePlayerStatePlaying) { + if (toState == ASVideoNodePlayerStatePlaying) { NSLog(@"guitarVideoNode is playing"); - } else if (toSate == ASVideoNodePlayerStateFinished) { + } else if (toState == ASVideoNodePlayerStateFinished) { NSLog(@"guitarVideoNode finished"); - } else if (toSate == ASVideoNodePlayerStateLoading) { + } else if (toState == ASVideoNodePlayerStateLoading) { NSLog(@"guitarVideoNode is buffering"); } } -- (void)videoNode:(ASVideoNode *)videoNode didPlayToSecond:(NSTimeInterval)second +- (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval { if (videoNode != _guitarVideoNode) { return; } - NSLog(@"guitarVideoNode playback time is: %f",second); + NSLog(@"guitarVideoNode playback time is: %f",timeInterval); } @end \ No newline at end of file diff --git a/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj b/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj index f567ebb15c..f579b25bce 100644 --- a/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj @@ -1,806 +1,377 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 05561CF819D4E77700CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - BlurbNode.h - sourceTree - <group> - - 05561CF919D4E77700CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - BlurbNode.m - sourceTree - <group> - - 05561CFA19D4E77700CBA93C - - fileRef - 05561CF919D4E77700CBA93C - isa - PBXBuildFile - - 05561CFB19D4F94A00CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - NicCageNode.h - sourceTree - <group> - - 05561CFC19D4F94A00CBA93C - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.cpp.objcpp - path - NicCageNode.mm - sourceTree - <group> - - 05561CFD19D4F94A00CBA93C - - fileRef - 05561CFC19D4F94A00CBA93C - isa - PBXBuildFile - - 0585427F19D4DBE100606EA6 - - isa - PBXFileReference - lastKnownFileType - image.png - name - Default-568h@2x.png - path - ../Default-568h@2x.png - sourceTree - <group> - - 0585428019D4DBE100606EA6 - - fileRef - 0585427F19D4DBE100606EA6 - isa - PBXBuildFile - - 05E2127819D4DB510098F589 - - children - - 05E2128319D4DB510098F589 - 05E2128219D4DB510098F589 - 1A943BF0259746F18D6E423F - 1AE410B73DA5C3BD087ACDD7 - - indentWidth - 2 - isa - PBXGroup - sourceTree - <group> - tabWidth - 2 - usesTabs - 0 - - 05E2127919D4DB510098F589 - - attributes - - LastUpgradeCheck - 0600 - ORGANIZATIONNAME - Facebook - TargetAttributes - - 05E2128019D4DB510098F589 - - CreatedOnToolsVersion - 6.0.1 - - - - buildConfigurationList - 05E2127C19D4DB510098F589 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - 05E2127819D4DB510098F589 - productRefGroup - 05E2128219D4DB510098F589 - projectDirPath - - projectReferences - - projectRoot - - targets - - 05E2128019D4DB510098F589 - - - 05E2127C19D4DB510098F589 - - buildConfigurations - - 05E212A219D4DB510098F589 - 05E212A319D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E2127D19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 05561CFD19D4F94A00CBA93C - 05E2128D19D4DB510098F589 - 05E2128A19D4DB510098F589 - 05561CFA19D4E77700CBA93C - 05E2128719D4DB510098F589 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127E19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 67ADD7A0A11DF37B2D73094B - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2127F19D4DB510098F589 - - buildActionMask - 2147483647 - files - - 0585428019D4DBE100606EA6 - 6C2C82AC19EE274300767484 - 6C2C82AD19EE274300767484 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 05E2128019D4DB510098F589 - - buildConfigurationList - 05E212A419D4DB510098F589 - buildPhases - - E080B80F89C34A25B3488E26 - 05E2127D19D4DB510098F589 - 05E2127E19D4DB510098F589 - 05E2127F19D4DB510098F589 - F012A6F39E0149F18F564F50 - EBE12F047824F0A2C6353B54 - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - 05E2128119D4DB510098F589 - productType - com.apple.product-type.application - - 05E2128119D4DB510098F589 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - 05E2128219D4DB510098F589 - - children - - 05E2128119D4DB510098F589 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 05E2128319D4DB510098F589 - - children - - 05E2128819D4DB510098F589 - 05E2128919D4DB510098F589 - 05E2128B19D4DB510098F589 - 05E2128C19D4DB510098F589 - 05561CFB19D4F94A00CBA93C - 05561CFC19D4F94A00CBA93C - 05561CF819D4E77700CBA93C - 05561CF919D4E77700CBA93C - 05E2128419D4DB510098F589 - - isa - PBXGroup - path - Sample - sourceTree - <group> - - 05E2128419D4DB510098F589 - - children - - 0585427F19D4DBE100606EA6 - 6C2C82AA19EE274300767484 - 6C2C82AB19EE274300767484 - 05E2128519D4DB510098F589 - 05E2128619D4DB510098F589 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - 05E2128519D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - 05E2128619D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - 05E2128719D4DB510098F589 - - fileRef - 05E2128619D4DB510098F589 - isa - PBXBuildFile - - 05E2128819D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 05E2128919D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 05E2128A19D4DB510098F589 - - fileRef - 05E2128919D4DB510098F589 - isa - PBXBuildFile - - 05E2128B19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - 05E2128C19D4DB510098F589 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - 05E2128D19D4DB510098F589 - - fileRef - 05E2128C19D4DB510098F589 - isa - PBXBuildFile - - 05E212A219D4DB510098F589 - - 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 - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - 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 - 8.0 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - - isa - XCBuildConfiguration - name - Debug - - 05E212A319D4DB510098F589 - - 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 - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - 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 - 8.0 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - 05E212A419D4DB510098F589 - - buildConfigurations - - 05E212A519D4DB510098F589 - 05E212A619D4DB510098F589 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 05E212A519D4DB510098F589 - - baseConfigurationReference - 246853115611E4007B767EA5 - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Debug - - 05E212A619D4DB510098F589 - - baseConfigurationReference - C822FB25F4C6DBD8EA3CE6EC - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - IPHONEOS_DEPLOYMENT_TARGET - 7.1 - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_NAME - $(TARGET_NAME) - TARGETED_DEVICE_FAMILY - 1,2 - - isa - XCBuildConfiguration - name - Release - - 12CBA11A5870DDF5A5626B7B - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - 1A943BF0259746F18D6E423F - - children - - 12CBA11A5870DDF5A5626B7B - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - 1AE410B73DA5C3BD087ACDD7 - - children - - 246853115611E4007B767EA5 - C822FB25F4C6DBD8EA3CE6EC - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 246853115611E4007B767EA5 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - 67ADD7A0A11DF37B2D73094B - - fileRef - 12CBA11A5870DDF5A5626B7B - isa - PBXBuildFile - - 6C2C82AA19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-667h@2x.png - sourceTree - SOURCE_ROOT - - 6C2C82AB19EE274300767484 - - isa - PBXFileReference - lastKnownFileType - image.png - path - Default-736h@3x.png - sourceTree - SOURCE_ROOT - - 6C2C82AC19EE274300767484 - - fileRef - 6C2C82AA19EE274300767484 - isa - PBXBuildFile - - 6C2C82AD19EE274300767484 - - fileRef - 6C2C82AB19EE274300767484 - isa - PBXBuildFile - - C822FB25F4C6DBD8EA3CE6EC - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - E080B80F89C34A25B3488E26 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - EBE12F047824F0A2C6353B54 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - F012A6F39E0149F18F564F50 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - - rootObject - 05E2127919D4DB510098F589 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 05561CFA19D4E77700CBA93C /* BlurbNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 05561CF919D4E77700CBA93C /* BlurbNode.m */; }; + 05561CFD19D4F94A00CBA93C /* NicCageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05561CFC19D4F94A00CBA93C /* NicCageNode.mm */; }; + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 67ADD7A0A11DF37B2D73094B /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 12CBA11A5870DDF5A5626B7B /* libPods-Sample.a */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 05561CF819D4E77700CBA93C /* BlurbNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlurbNode.h; sourceTree = ""; }; + 05561CF919D4E77700CBA93C /* BlurbNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlurbNode.m; sourceTree = ""; }; + 05561CFB19D4F94A00CBA93C /* NicCageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NicCageNode.h; sourceTree = ""; }; + 05561CFC19D4F94A00CBA93C /* NicCageNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = NicCageNode.mm; sourceTree = ""; }; + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 12CBA11A5870DDF5A5626B7B /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 246853115611E4007B767EA5 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + C822FB25F4C6DBD8EA3CE6EC /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 67ADD7A0A11DF37B2D73094B /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05561CFB19D4F94A00CBA93C /* NicCageNode.h */, + 05561CFC19D4F94A00CBA93C /* NicCageNode.mm */, + 05561CF819D4E77700CBA93C /* BlurbNode.h */, + 05561CF919D4E77700CBA93C /* BlurbNode.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 12CBA11A5870DDF5A5626B7B /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + 246853115611E4007B767EA5 /* Pods-Sample.debug.xcconfig */, + C822FB25F4C6DBD8EA3CE6EC /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */, + EBE12F047824F0A2C6353B54 /* 📦 Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "📦 Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + EBE12F047824F0A2C6353B54 /* 📦 Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "📦 Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "📦 Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05561CFD19D4F94A00CBA93C /* NicCageNode.mm in Sources */, + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05561CFA19D4E77700CBA93C /* BlurbNode.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* 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; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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 = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* 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; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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 = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 246853115611E4007B767EA5 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C822FB25F4C6DBD8EA3CE6EC /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +}