diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 4269a60b2e..f9e20a5d2c 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -351,6 +351,9 @@ ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */; }; ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */; }; ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */; }; + AEEC47E11C20C2DD00EC1693 /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; }; + AEEC47E21C20C2DD00EC1693 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; + AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */; }; B0F8805A1BEAEC7500D17647 /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; B0F8805B1BEAEC7500D17647 /* ASTableNode.m in Sources */ = {isa = PBXBuildFile; fileRef = B0F880591BEAEC7500D17647 /* ASTableNode.m */; }; B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -736,6 +739,9 @@ ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASOverlayLayoutSpecSnapshotTests.mm; sourceTree = ""; }; ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRatioLayoutSpecSnapshotTests.mm; sourceTree = ""; }; ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackLayoutSpecSnapshotTests.mm; sourceTree = ""; }; + AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVideoNode.h; sourceTree = ""; }; + AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASVideoNode.mm; sourceTree = ""; }; + AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASVideoNodeTests.m; sourceTree = ""; }; B0F880581BEAEC7500D17647 /* ASTableNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableNode.h; sourceTree = ""; }; B0F880591BEAEC7500D17647 /* ASTableNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableNode.m; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -867,6 +873,8 @@ children = ( 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */, 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */, + AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */, + AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */, 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */, AC6456071B0A335000CF11B8 /* ASCellNode.m */, 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */, @@ -960,6 +968,7 @@ 254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */, 058D0A36195D057000B7D73C /* ASTextNodeTests.m */, 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */, + AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */, 058D09C6195D04C000B7D73C /* Supporting Files */, 052EE06A1A15A0D8002C6279 /* TestResources */, 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */, @@ -1254,6 +1263,7 @@ 18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */, 257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */, AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */, + AEEC47E11C20C2DD00EC1693 /* ASVideoNode.h in Headers */, 205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */, AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */, 058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */, @@ -1513,6 +1523,7 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, + 527A806066E1F4E2795090DF /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1642,6 +1653,21 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 527A806066E1F4E2795090DF /* 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-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1689,6 +1715,7 @@ 058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */, 058D0A14195D050800B7D73C /* ASDisplayNode.mm in Sources */, 058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */, + AEEC47E21C20C2DD00EC1693 /* ASVideoNode.mm in Sources */, 0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */, 464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */, 257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */, @@ -1775,6 +1802,7 @@ 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */, + AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */, 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */, 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */, 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */, diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 1801a03527..2f2bc9500c 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -212,6 +212,11 @@ return stack; } +- (void)layoutDidFinish +{ + +} + - (void)layout { [super layout]; diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h new file mode 100644 index 0000000000..9fc9db45b6 --- /dev/null +++ b/AsyncDisplayKit/ASVideoNode.h @@ -0,0 +1,34 @@ + +#import + +typedef NS_ENUM(NSUInteger, ASVideoGravity) { + ASVideoGravityResizeAspect, + ASVideoGravityResizeAspectFill, + ASVideoGravityResize +}; + +// set up boolean to repeat video +// set up delegate methods to provide play button +// tapping should play and pause + +@interface ASVideoNode : ASDisplayNode +@property (atomic, strong, readwrite) AVAsset *asset; +@property (nonatomic, assign, readwrite) BOOL shouldRepeat; +@property (atomic) ASVideoGravity gravity; +@property (atomic) BOOL autorepeat; +@property (atomic) ASButtonNode *playButton; + +- (void)play; +- (void)pause; + +@end + +@protocol ASVideoNodeDelegate +@end + +@protocol ASVideoNodeDatasource +@optional +- (ASDisplayNode *)playButtonForVideoNode:(ASVideoNode *) videoNode; +- (UIImage *)thumbnailForVideoNode:(ASVideoNode *) videoNode; +- (NSURL *)thumbnailURLForVideoNode:(ASVideoNode *)videoNode; +@end diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm new file mode 100644 index 0000000000..253943360a --- /dev/null +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -0,0 +1,161 @@ + + +#import "ASVideoNode.h" + +@interface ASVideoNode () { + ASDN::RecursiveMutex _lock; + + __weak id _datasource; + + AVPlayer *_player; + BOOL _shouldBePlaying; + AVAsset *_asset; + ASButtonNode *_playButton; + ASDisplayNode *_playerNode; +} + +@end + +@implementation ASVideoNode + +- (instancetype)init { + if (!(self = [super init])) { return nil; } + + _playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ return [[AVPlayerLayer alloc] init]; }]; + [self addSubnode:_playerNode]; + + self.gravity = ASVideoGravityResizeAspect; + + return self; +} + +- (void)layoutDidFinish +{ + _playerNode.frame = self.bounds; +} + +- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock +{ + ASDisplayNodeAssertNotSupported(); + return nil; +} + +- (void)fetchData +{ + [super fetchData]; + + { + ASDN::MutexLocker l(_lock); + AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:_asset]; + ((AVPlayerLayer *)_playerNode.layer).player = [[AVPlayer alloc] initWithPlayerItem:item]; + if (_shouldBePlaying) { + [[((AVPlayerLayer *)_playerNode.layer) player] play]; + } + } +} + +- (void)clearFetchedData +{ + [super clearFetchedData]; + + { + ASDN::MutexLocker l(_lock); + ((AVPlayerLayer *)_playerNode.layer).player = nil; + _shouldBePlaying = NO; + } +} + +- (void)setPlayButton:(ASButtonNode *)playButton +{ + ASDN::MutexLocker l(_lock); + + _playButton = playButton; + + [self addSubnode:playButton]; + [self.view bringSubviewToFront:playButton.view]; + + [_playButton addTarget:self action:@selector(playButtonWasTouchedUpInside) forControlEvents:ASControlNodeEventTouchUpInside]; +} + +- (void)playButtonWasTouchedUpInside +{ + [self play]; +} + +- (ASButtonNode *)playButton +{ + ASDN::MutexLocker l(_lock); + + return _playButton; +} + +- (void)setAsset:(AVAsset *)asset +{ + ASDN::MutexLocker l(_lock); + + if (ASObjectIsEqual(((AVURLAsset *)asset).URL, ((AVURLAsset *)_asset).URL)) { + return; + } + + _asset = asset; + + if (self.interfaceState & ASInterfaceStateFetchData) { + [self fetchData]; + } +} + +- (AVAsset *)asset +{ + return _asset; +} + +- (void)setGravity:(ASVideoGravity)gravity { + ASDN::MutexLocker l(_lock); + + switch (gravity) { + case ASVideoGravityResize: + ((AVPlayerLayer *)_playerNode.layer).videoGravity = AVLayerVideoGravityResize; + break; + case ASVideoGravityResizeAspect: + ((AVPlayerLayer *)_playerNode.layer).videoGravity = AVLayerVideoGravityResizeAspect; + break; + case ASVideoGravityResizeAspectFill: + ((AVPlayerLayer *)_playerNode.layer).videoGravity = AVLayerVideoGravityResizeAspectFill; + break; + default: + ((AVPlayerLayer *)_playerNode.layer).videoGravity = AVLayerVideoGravityResizeAspect; + break; + } +} + +- (ASVideoGravity)gravity; +{ + ASDN::MutexLocker l(_lock); + + if ([((AVPlayerLayer *)_playerNode.layer).contentsGravity isEqualToString:AVLayerVideoGravityResize]) { + return ASVideoGravityResize; + } + if ([((AVPlayerLayer *)_playerNode.layer).contentsGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) { + return ASVideoGravityResizeAspectFill; + } + + return ASVideoGravityResizeAspect; +} + +- (void)play; +{ + ASDN::MutexLocker l(_lock); + + [[((AVPlayerLayer *)_playerNode.layer) player] play]; + _shouldBePlaying = YES; +} + +- (void)pause; +{ + ASDN::MutexLocker l(_lock); + + [[((AVPlayerLayer *)_playerNode.layer) player] pause]; + _shouldBePlaying = NO; +} + +@end diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m new file mode 100644 index 0000000000..15a1aee7c5 --- /dev/null +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -0,0 +1,45 @@ +/* Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import +#import "ASVideoNode.h" + +@interface ASVideoNodeTests : XCTestCase + +@end + +@implementation ASVideoNodeTests + +- (void)testVideoNodeReplacesAVPlayerWhenNewURLIsSet { + ASVideoNode *videoNode = [[ASVideoNode alloc] init]; + videoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; + [videoNode fetchData]; + AVPlayer *player = ((AVPlayerLayer *)videoNode.layer).player; + + videoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"secondURL"]]; + [videoNode fetchData]; + AVPlayer *secondPlayer = ((AVPlayerLayer *)videoNode.layer).player; + + XCTAssertNotEqualObjects(player, secondPlayer); +} + +- (void)testVideoNodeDoesNotMakeNewPlayerWhenURLIsTheSame { + ASVideoNode *videoNode = [[ASVideoNode alloc] init]; + videoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; + [videoNode fetchData]; + + AVPlayer *firstPlayer = ((AVPlayerLayer *)videoNode.layer).player; + videoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; + + AVPlayer *secondPlayer = ((AVPlayerLayer *)videoNode.layer).player; + + XCTAssertEqualObjects(firstPlayer, secondPlayer); +} + +@end diff --git a/examples/Videos/Default-568h@2x.png b/examples/Videos/Default-568h@2x.png new file mode 100644 index 0000000000..6ee80b9393 Binary files /dev/null and b/examples/Videos/Default-568h@2x.png differ diff --git a/examples/Videos/Default-667h@2x.png b/examples/Videos/Default-667h@2x.png new file mode 100644 index 0000000000..e7b975e21b Binary files /dev/null and b/examples/Videos/Default-667h@2x.png differ diff --git a/examples/Videos/Default-736h@3x.png b/examples/Videos/Default-736h@3x.png new file mode 100644 index 0000000000..c8949cae16 Binary files /dev/null and b/examples/Videos/Default-736h@3x.png differ diff --git a/examples/Videos/Podfile b/examples/Videos/Podfile new file mode 100644 index 0000000000..6c012e3c04 --- /dev/null +++ b/examples/Videos/Podfile @@ -0,0 +1,3 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/Videos/Sample.xcodeproj/project.pbxproj b/examples/Videos/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..ca20fd27de --- /dev/null +++ b/examples/Videos/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,371 @@ +// !$*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 */; }; + 3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D24B17D1E4A4E7A9566C5E9 /* libPods.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 */; }; + ACC945AE1BA9EFBA005E1FB8 /* ScreenNode.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AD1BA9EFBA005E1FB8 /* ScreenNode.m */; }; + AED850671C22679200183ED3 /* playButton@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AED850661C22679200183ED3 /* playButton@2x.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 = ""; }; + 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 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; }; + ACC945AC1BA9EFB3005E1FB8 /* ScreenNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScreenNode.h; sourceTree = ""; }; + ACC945AD1BA9EFBA005E1FB8 /* ScreenNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScreenNode.m; sourceTree = ""; }; + AED850661C22679200183ED3 /* playButton@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playButton@2x.png"; sourceTree = ""; }; + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3EC0CDCBA10D483D9F386E5E /* libPods.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 = ( + AED850661C22679200183ED3 /* playButton@2x.png */, + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + ACC945AC1BA9EFB3005E1FB8 /* ScreenNode.h */, + ACC945AD1BA9EFBA005E1FB8 /* ScreenNode.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 = ( + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */, + 088AA6578212BE9BFBB07B70 /* Pods.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 */, + 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 = ( + AED850671C22679200183ED3 /* playButton@2x.png in Resources */, + 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 */ + 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/Pods-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/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ACC945AE1BA9EFBA005E1FB8 /* ScreenNode.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 = C068F1D3F0CC317E895FCDAB /* Pods.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 = 088AA6578212BE9BFBB07B70 /* Pods.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/Videos/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/Videos/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/Videos/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/Videos/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/Videos/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..1e14aa0329 --- /dev/null +++ b/examples/Videos/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Videos/Sample.xcworkspace/contents.xcworkspacedata b/examples/Videos/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/Videos/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/Videos/Sample/ASVideoNode.h b/examples/Videos/Sample/ASVideoNode.h new file mode 100644 index 0000000000..3e2cb7ecc9 --- /dev/null +++ b/examples/Videos/Sample/ASVideoNode.h @@ -0,0 +1,29 @@ + +#import + +typedef NS_ENUM(NSUInteger, ASVideoGravity) { + ASVideoGravityResizeAspect, + ASVideoGravityResizeAspectFill, + ASVideoGravityResize +}; + +// set up boolean to repeat video +// set up delegate methods to provide play button +// tapping should play and pause + +@interface ASVideoNode : ASDisplayNode +@property (nonatomic) NSURL *URL; +@property (nonatomic) BOOL shouldRepeat; +@property (nonatomic) ASVideoGravity gravity; + +- (instancetype)initWithURL:(NSURL *)URL; +- (instancetype)initWithURL:(NSURL *)URL videoGravity:(ASVideoGravity)gravity; + +- (void)play; +- (void)pause; + +@end + +@protocol ASVideoNodeDelegate + +@end diff --git a/examples/Videos/Sample/ASVideoNode.m b/examples/Videos/Sample/ASVideoNode.m new file mode 100644 index 0000000000..aec4dbf1dc --- /dev/null +++ b/examples/Videos/Sample/ASVideoNode.m @@ -0,0 +1,72 @@ + + +#import "ASVideoNode.h" + +@interface ASVideoNode () +@property (nonatomic) AVPlayer *player; +@end + +@implementation ASVideoNode + +- (instancetype)initWithURL:(NSURL *)URL; +{ + return [self initWithURL:URL videoGravity:ASVideoGravityResizeAspect]; +} + +- (instancetype)initWithURL:(NSURL *)URL videoGravity:(ASVideoGravity)gravity; +{ + if (!(self = [super initWithLayerBlock:^CALayer *{ + AVPlayerLayer *layer = [[AVPlayerLayer alloc] init]; + AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:URL]; + + layer.player = [[AVPlayer alloc] initWithPlayerItem:item]; + + return layer; + }])) { return nil; } + + self.gravity = gravity; + + return self; +} + +- (void)setGravity:(ASVideoGravity)gravity; +{ + switch (gravity) { + case ASVideoGravityResize: + ((AVPlayerLayer *)self.layer).videoGravity = AVLayerVideoGravityResize; + break; + case ASVideoGravityResizeAspect: + ((AVPlayerLayer *)self.layer).videoGravity = AVLayerVideoGravityResizeAspect; + break; + case ASVideoGravityResizeAspectFill: + ((AVPlayerLayer *)self.layer).videoGravity = AVLayerVideoGravityResizeAspectFill; + break; + default: + ((AVPlayerLayer *)self.layer).videoGravity = AVLayerVideoGravityResizeAspect; + break; + } +} + +- (ASVideoGravity)gravity; +{ + if ([((AVPlayerLayer *)self.layer).contentsGravity isEqualToString:AVLayerVideoGravityResize]) { + return ASVideoGravityResize; + } + if ([((AVPlayerLayer *)self.layer).contentsGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) { + return ASVideoGravityResizeAspectFill; + } + + return ASVideoGravityResizeAspect; +} + +- (void)play; +{ + [[((AVPlayerLayer *)self.layer) player] play]; +} + +- (void)pause; +{ + [[((AVPlayerLayer *)self.layer) player] pause]; +} + +@end diff --git a/examples/Videos/Sample/AppDelegate.h b/examples/Videos/Sample/AppDelegate.h new file mode 100644 index 0000000000..2aa29369b4 --- /dev/null +++ b/examples/Videos/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/Videos/Sample/AppDelegate.m b/examples/Videos/Sample/AppDelegate.m new file mode 100644 index 0000000000..a8e5594780 --- /dev/null +++ b/examples/Videos/Sample/AppDelegate.m @@ -0,0 +1,27 @@ +/* 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 "ViewController.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = [[ViewController alloc] init]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/Videos/Sample/Info.plist b/examples/Videos/Sample/Info.plist new file mode 100644 index 0000000000..35d842827b --- /dev/null +++ b/examples/Videos/Sample/Info.plist @@ -0,0 +1,36 @@ + + + + + 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 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/Videos/Sample/ScreenNode.h b/examples/Videos/Sample/ScreenNode.h new file mode 100644 index 0000000000..9a818befa0 --- /dev/null +++ b/examples/Videos/Sample/ScreenNode.h @@ -0,0 +1,19 @@ +// +// ScreenNode.h +// Sample +// +// Created by Huy Nguyen on 16/09/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import + +@interface ScreenNode : ASDisplayNode + +@property (nonatomic, strong) ASMultiplexImageNode *imageNode; +@property (nonatomic, strong) ASButtonNode *buttonNode; + +- (void)start; +- (void)reload; + +@end diff --git a/examples/Videos/Sample/ScreenNode.m b/examples/Videos/Sample/ScreenNode.m new file mode 100644 index 0000000000..b3e661079a --- /dev/null +++ b/examples/Videos/Sample/ScreenNode.m @@ -0,0 +1,158 @@ +// +// ScreenNode.m +// Sample +// +// Created by Huy Nguyen on 16/09/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import "ScreenNode.h" + +@interface ScreenNode() +@end + +@implementation ScreenNode + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + // multiplex image node! + // NB: we're using a custom downloader with an artificial delay for this demo, but ASBasicImageDownloader works too! + _imageNode = [[ASMultiplexImageNode alloc] initWithCache:nil downloader:self]; + _imageNode.dataSource = self; + _imageNode.delegate = self; + + // placeholder colour + _imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); + + // load low-quality images before high-quality images + _imageNode.downloadsIntermediateImages = YES; + + // simple status label. Synchronous to avoid flicker / placeholder state when updating. + _buttonNode = [[ASButtonNode alloc] init]; + [_buttonNode addTarget:self action:@selector(reload) forControlEvents:ASControlNodeEventTouchUpInside]; + _buttonNode.titleNode.displaysAsynchronously = NO; + + [self addSubnode:_imageNode]; + [self addSubnode:_buttonNode]; + + return self; +} + +- (void)start +{ + [self setText:@"loading…"]; + _buttonNode.userInteractionEnabled = NO; + _imageNode.imageIdentifiers = @[ @"best", @"medium", @"worst" ]; // go! +} + +- (void)reload +{ + [self start]; + [_imageNode reloadImageIdentifierSources]; +} + +- (void)setText:(NSString *)text +{ + NSDictionary *attributes = @{NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:22.0f]}; + NSAttributedString *string = [[NSAttributedString alloc] initWithString:text + attributes:attributes]; + [_buttonNode setAttributedTitle:string forState:ASButtonStateNormal]; + [self setNeedsLayout]; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASRatioLayoutSpec *imagePlaceholder = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1 child:_imageNode]; + + ASStackLayoutSpec *verticalStack = [[ASStackLayoutSpec alloc] init]; + verticalStack.direction = ASStackLayoutDirectionVertical; + verticalStack.spacing = 10; + verticalStack.justifyContent = ASStackLayoutJustifyContentCenter; + verticalStack.alignItems = ASStackLayoutAlignItemsCenter; + verticalStack.children = @[imagePlaceholder, _buttonNode]; + + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) child:verticalStack]; +} + +#pragma mark - +#pragma mark ASMultiplexImageNode data source & delegate. + +- (NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(id)imageIdentifier +{ + if ([imageIdentifier isEqualToString:@"worst"]) { + return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples/Multiplex/worst.png"]; + } + + if ([imageIdentifier isEqualToString:@"medium"]) { + return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples/Multiplex/medium.png"]; + } + + if ([imageIdentifier isEqualToString:@"best"]) { + return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples/Multiplex/best.png"]; + } + + // unexpected identifier + return nil; +} + +- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode didFinishDownloadingImageWithIdentifier:(id)imageIdentifier error:(NSError *)error +{ + [self setText:[NSString stringWithFormat:@"loaded '%@'", imageIdentifier]]; + + if ([imageIdentifier isEqualToString:@"best"]) { + [self setText:[_buttonNode.titleNode.attributedString.string stringByAppendingString:@". tap to reload"]]; + _buttonNode.userInteractionEnabled = YES; + } +} + + +#pragma mark - +#pragma mark ASImageDownloaderProtocol. + +- (id)downloadImageWithURL:(NSURL *)URL + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgressBlock:(void (^)(CGFloat progress))downloadProgressBlock + completion:(void (^)(CGImageRef image, NSError *error))completion +{ + // if no callback queue is supplied, run on the main thread + if (callbackQueue == nil) { + callbackQueue = dispatch_get_main_queue(); + } + + // call completion blocks + void (^handler)(NSURLResponse *, NSData *, NSError *) = ^(NSURLResponse *response, NSData *data, NSError *connectionError) { + // add an artificial delay + usleep(1.0 * USEC_PER_SEC); + + // ASMultiplexImageNode callbacks + dispatch_async(callbackQueue, ^{ + if (downloadProgressBlock) { + downloadProgressBlock(1.0f); + } + + if (completion) { + completion([[UIImage imageWithData:data] CGImage], connectionError); + } + }); + }; + + // let NSURLConnection do the heavy lifting + NSURLRequest *request = [NSURLRequest requestWithURL:URL]; + [NSURLConnection sendAsynchronousRequest:request + queue:[[NSOperationQueue alloc] init] + completionHandler:handler]; + + // return nil, don't support cancellation + return nil; +} + +- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier +{ + // no-op, don't support cancellation +} + +@end diff --git a/examples/Videos/Sample/ViewController.h b/examples/Videos/Sample/ViewController.h new file mode 100644 index 0000000000..1664a00082 --- /dev/null +++ b/examples/Videos/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 +#import + +@interface ViewController : UIViewController + +@end diff --git a/examples/Videos/Sample/ViewController.m b/examples/Videos/Sample/ViewController.m new file mode 100644 index 0000000000..c96f506f17 --- /dev/null +++ b/examples/Videos/Sample/ViewController.m @@ -0,0 +1,80 @@ +/* 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" + +@interface ViewController() +@property (nonatomic) ASVideoNode *videoNode; +@end + +@implementation ViewController + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + _videoNode = [[ASVideoNode alloc] init]; + + _videoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov"]]; + + _videoNode.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height/3); + + _videoNode.backgroundColor = [UIColor lightGrayColor]; + +// _videoNode.autorepeat = YES; //need to implement +// _videoNode.autoPlay = YES; //need to implement + + ASButtonNode *playButton = [[ASButtonNode alloc] init]; + playButton.bounds = CGRectMake(0, 0, 150, 150); + playButton.position = CGPointMake(_videoNode.bounds.size.width/2, _videoNode.bounds.size.height/2); + + UIImage *image = [UIImage imageNamed:@"playButton@2x.png"]; + [playButton setImage:image forState:ASButtonStateNormal]; + [playButton measure:CGSizeMake(100, 100)]; + + _videoNode.playButton = playButton; + + _videoNode.gravity = ASVideoGravityResizeAspectFill; + + [self.view addSubnode:_videoNode]; + +// NSLog(@"%@", _videoNode.asset); + +// ASButtonNode *playButton2 = [[ASButtonNode alloc] init]; +//// playButton2.bounds = CGRectMake(0, 0, 50, 50); +// +// UIImage *image2 = [UIImage imageNamed:@"playButton@2x.png"]; +// [playButton2 setImage:image2 forState:ASButtonStateNormal]; +// +//// playButton2.contentMode = UIViewContentModeScaleAspectFit; +// playButton2.clipsToBounds = YES; +// playButton2.layer.borderColor = [UIColor orangeColor].CGColor; +// playButton2.layer.borderWidth = 1.0; +// +// [playButton2 measure:CGSizeMake(100, 100)]; +//// playButton2.preferredFrameSize = CGSizeMake(100, 100); +// playButton2.position = CGPointMake(150, 300); +// playButton2.bounds = (CGRect){{0, 0}, playButton2.calculatedSize}; +// +// [self.view addSubview:playButton2.view]; +} + +- (void)viewDidAppear:(BOOL)animated +{ + +} + +- (BOOL)prefersStatusBarHidden +{ + return YES; +} + +@end diff --git a/examples/Videos/Sample/main.m b/examples/Videos/Sample/main.m new file mode 100644 index 0000000000..ae9488711c --- /dev/null +++ b/examples/Videos/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/Videos/Sample/playButton@2x.png b/examples/Videos/Sample/playButton@2x.png new file mode 100644 index 0000000000..ac7b8f0cd5 Binary files /dev/null and b/examples/Videos/Sample/playButton@2x.png differ diff --git a/examples/Videos/best.png b/examples/Videos/best.png new file mode 100644 index 0000000000..d50d8103a6 Binary files /dev/null and b/examples/Videos/best.png differ diff --git a/examples/Videos/medium.png b/examples/Videos/medium.png new file mode 100644 index 0000000000..7c08e0adc0 Binary files /dev/null and b/examples/Videos/medium.png differ diff --git a/examples/Videos/worst.png b/examples/Videos/worst.png new file mode 100644 index 0000000000..78727fa98f Binary files /dev/null and b/examples/Videos/worst.png differ