From e87fd7a998be6434c8311e2017df8899da03fbae Mon Sep 17 00:00:00 2001 From: Erekle Date: Fri, 6 May 2016 02:42:17 +0400 Subject: [PATCH 01/26] first steps --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 + AsyncDisplayKit/ASVideoPlayerNode.h | 25 ++ AsyncDisplayKit/ASVideoPlayerNode.mm | 146 +++++++ examples/ASDKTube/Default-568h@2x.png | Bin 0 -> 17520 bytes examples/ASDKTube/Default-667h@2x.png | Bin 0 -> 18314 bytes examples/ASDKTube/Default-736h@3x.png | Bin 0 -> 23380 bytes examples/ASDKTube/Podfile | 5 + .../ASDKTube/Sample.xcodeproj/project.pbxproj | 361 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/xcschemes/Sample.xcscheme | 88 +++++ .../contents.xcworkspacedata | 10 + examples/ASDKTube/Sample/AppDelegate.h | 18 + examples/ASDKTube/Sample/AppDelegate.m | 27 ++ examples/ASDKTube/Sample/Info.plist | 36 ++ examples/ASDKTube/Sample/ViewController.h | 16 + examples/ASDKTube/Sample/ViewController.m | 71 ++++ examples/ASDKTube/Sample/main.m | 20 + 17 files changed, 842 insertions(+) create mode 100644 AsyncDisplayKit/ASVideoPlayerNode.h create mode 100644 AsyncDisplayKit/ASVideoPlayerNode.mm create mode 100644 examples/ASDKTube/Default-568h@2x.png create mode 100644 examples/ASDKTube/Default-667h@2x.png create mode 100644 examples/ASDKTube/Default-736h@3x.png create mode 100644 examples/ASDKTube/Podfile create mode 100644 examples/ASDKTube/Sample.xcodeproj/project.pbxproj create mode 100644 examples/ASDKTube/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/ASDKTube/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme create mode 100644 examples/ASDKTube/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples/ASDKTube/Sample/AppDelegate.h create mode 100644 examples/ASDKTube/Sample/AppDelegate.m create mode 100644 examples/ASDKTube/Sample/Info.plist create mode 100644 examples/ASDKTube/Sample/ViewController.h create mode 100644 examples/ASDKTube/Sample/ViewController.m create mode 100644 examples/ASDKTube/Sample/main.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 8d5e02ea5d..372cd296e1 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -302,6 +302,10 @@ 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 */; }; + 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 */; }; @@ -794,6 +798,8 @@ 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 = ""; }; + 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 = ""; }; @@ -1035,6 +1041,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 */, @@ -1529,6 +1537,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 */, @@ -1740,6 +1749,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; }; @@ -2053,6 +2063,7 @@ 055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */, 058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */, 257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */, + 8BDA5FC61CDBDDE1007D13B2 /* ASVideoPlayerNode.mm in Sources */, ACC945AB1BA9E7C1005E1FB8 /* ASViewController.m in Sources */, B0F8805B1BEAEC7500D17647 /* ASTableNode.m in Sources */, 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */, @@ -2195,6 +2206,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 */, diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h new file mode 100644 index 0000000000..eec4e350d3 --- /dev/null +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -0,0 +1,25 @@ +// +// 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; + +NS_ASSUME_NONNULL_BEGIN + +@interface ASVideoPlayerNode : ASDisplayNode + +- (instancetype)initWithUrl:(NSURL*)url; +- (instancetype)initWithAsset:(AVAsset*)asset; +@end +NS_ASSUME_NONNULL_END +#endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm new file mode 100644 index 0000000000..ea51682ba3 --- /dev/null +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -0,0 +1,146 @@ +// +// ASVideoPlayerNode.m +// AsyncDisplayKit +// +// Created by Erekle on 5/6/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASVideoPlayerNode.h" + +static NSString * const kASVideoPlayerNodePlayButton = @"playButton"; + +@interface ASVideoPlayerNode() +{ + ASDN::RecursiveMutex _videoPlayerLock; + + NSURL *_url; + AVAsset *_asset; + + ASVideoNode *_videoNode; + + ASDisplayNode *_controlsHolderNode; + + NSArray *_neededControls; + + NSMutableDictionary *_cachedControls; +} + +@end + +@implementation ASVideoPlayerNode +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + [self privateInit]; + + return self; +} + +- (instancetype)initWithUrl:(NSURL*)url +{ + if (!(self = [super init])) { + return nil; + } + + _url = url; + + [self privateInit]; + + return self; +} + +- (instancetype)initWithAsset:(AVAsset *)asset +{ + if (!(self = [super init])) { + return nil; + } + + _asset = asset; + + [self privateInit]; + + return self; +} + +- (void)privateInit +{ + + _neededControls = [self createNeededControlElementsArray]; + _cachedControls = [[NSMutableDictionary alloc] init]; + + _videoNode = [[ASVideoNode alloc] init]; + _videoNode.asset = [AVAsset assetWithURL:_url]; + [self addSubnode:_videoNode]; + + _controlsHolderNode = [[ASDisplayNode alloc] init]; + _controlsHolderNode.backgroundColor = [UIColor greenColor]; + [self addSubnode:_controlsHolderNode]; + + [self createControls]; +} + +- (NSArray*)createNeededControlElementsArray +{ + //TODO:: Maybe here we will ask delegate what he needs and we force delegate to use our static strings or something like that + return @[kASVideoPlayerNodePlayButton]; +} + +#pragma mark - UI +- (void)createControls +{ + for (NSString *controlType in _neededControls) { + if ([controlType isEqualToString:kASVideoPlayerNodePlayButton]) { + [self createPlayButton]; + } + } +} + +- (void)createPlayButton +{ + ASControlNode *playButton = [_cachedControls objectForKey:kASVideoPlayerNodePlayButton]; + if (!playButton) { + playButton = [[ASControlNode alloc] init]; + playButton.preferredFrameSize = CGSizeMake(20.0, 20.0); + playButton.backgroundColor = [UIColor redColor]; + + [_cachedControls setObject:playButton forKey:kASVideoPlayerNodePlayButton]; + } + + [self addSubnode:playButton]; +} + +#pragma mark - Layout +- (ASLayoutSpec*)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + _videoNode.preferredFrameSize = constrainedSize.max; + + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.flexGrow = YES; + + ASStackLayoutSpec *controlsSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + children:[_cachedControls allValues]]; + + UIEdgeInsets insets = UIEdgeInsetsMake(8.0, 8.0, 8.0, 8.0); + + ASInsetLayoutSpec *controlsInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlsSpec]; + + ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + children:@[spacer,controlsInsetSpec]]; + + + ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:mainVerticalStack]; + + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[overlaySpec]]; +} + +@end diff --git a/examples/ASDKTube/Default-568h@2x.png b/examples/ASDKTube/Default-568h@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ee80b93937cd9dd79502629b14fe09aa03cacc2 GIT binary patch literal 17520 zcmeI3&2QsG7{;dyhuxq(aR70$vO)rco)4~Hqb-mA2=CIb8^QK*gwP8wCVy+_i!WbB=&euO z!=w19^{!?6gA#W9HYtq<0qu=Ybz>Z0`-H?on{-{TR{Zmu?}~!!)QWeFmfQ*&q~~s* zhveXV_s~8+u`5n-qh6?vEov|zF&4&yz86{JS~2yt=yB346@|1*d{QfJCIbpbtv#XP zheR++hG@%*E|`^)Vkml9c~ekjMU!MrQZ!LfExBSThA{aQ>jipL4V{j)-@H8;jz+a& zFOCCCl18IZX{43>uq!E*N=1@YNmWJKLyXS67>`9Sx|NwseVQb)LpO+B-xCsF-1diY ztyoM3ntdkMH3(({dC`O&r6`SYASoqTS|)PrnI;&9{q)ovTOxfjAYL3%ow8IH^!(V5 zdj5(bXX%v#(>ZCiW@9fs-@#z%&{4c~N)b$uE>%W{X91D+N#qYhn{1uZOS!e|>SMPv zpPUO$NoM7_ld-!(mSi$nx)ib*s?uw<8X>{4A0GOCzn-nKy(vPW(GXs1VcYc*q_0;c z*nd9Rb1TxsF{#tVsEdj$D*B-+TUy1EO;I*2Sj^#R z=5cV0FXfW&oAYsOtK)|Q9M|0e?h+~Rx>af3nCm%PQdYz7`yo9oQrD`|vgVvBU1rvf z7sc4K$xgFQ8%nP0Sc)olh@)wuzTR$&x`VM;Hf>YXY_n{bs#dn!Y6`K{%F7q5o4!3v zw#vlXq1KM0n~q5oQE_zYZ)g>1Jsbid6i*sMS$nsnP**iK4W z-A;A`ajMdV*7<48loOe|IDwa=ocZVEtH&7ih{xJcnN`|rwMpc6;t>wXW|yvs$8Pk@ z@}dTMSEZ!x_uck_9fO)qQO@GMQ+b+k+QN;k1G;mdod%W(l9?2zMP^8s0o3jkq<92c7p$Z}i&2s`As z*nB{i;{rg~A;-n$1F{?!0KyJAE;b*K<+uP4cF1wD`G73P1%R+aj*HC)WH~MXgdK8R zY(5~%aRDIgkmF+W0a=a<0AYt57n={ra$EoiJ7nT2%-^yl9(}cTMBkzP^v2h3(D!cz zdwaiy(D|zfJ@^QrzyG1%zali05&G>uLe}R9z2tv(@5kE+U4MV4xp_GL`Sg5w7{{k8fuN`-gg~6EtdKy$@q3ealPsm_(n_S1wutt$JFzFN)xMF-1^Yl zKS&PRZ`w}KFH<+@u=1!M^4^5hZ;wLioUladup`fJl>Yeo+mhtDjncbTTWyEy?AY5p zkJ#S%_P%p|;?&&I?dEcQWOIW)OQbw>80kV~ynhxlWtYXlAadBoDZiAPi>^NL zy0gi-;FM-AJ$E+pE|H~~T$U|`e1_`$TJ80S(IklWgP_;USJ}=4p|rj(z1*gb=chz*y}FfH5CiynoZ z(1ULtmnQT|F2%kDAJ?(FLDZ*7)9ceCriA`cU70l&dQO*=y&m*}h@Tc~8g*q+b3v6Y zGkeRA6Y4u`tJUNUWzTbMv)ZYeIx}Tvs=93I-HKc_OiS*t8m(1Toz=z=+wG!!&bk#i zgLJEmtzB+S4XSl5!;n{9oyw*`b-6>UrmUK^il*vDw??bk{BY}ne9ro<$m3;>_6mK{ zv;U_`>IE_XGxBbybA%AHk*$y&Essi=Cl+F`4c zIsUhEU>dfjO$yTgGzYWw>l{=6h`CK=a#@px$7$NGR{I`p>s+{xJnqw$@4<_ua8kkN zOJ_ZOc(8fd2=@U+V2j1fk5@J z8HQPu7E)trK3jz+=d5(*t^B#1|0GbRzX|55>h#WYod>gPx=vT%g@XVf;t+9(`G73q z0zkwe;u7-#S;Pf^h(p9B<^!^b3jh&^h)c`|WDyqtA`TIkm=DMzE&xOvA}%o>kVRYo zh&V)CVm=^?xBw7wh`7XjKo)TUAmR{liTQvm;sQX#A>tDA0a?TafQUoHCFTRNhzkG_ zhloqe2V@Z!03r?%mzWR8A}#<#93n0;ACN^{0Ejq5Tw*>Ti?{#~afrCYd_Wd)0U+WK zaf$hWEaCz{#3AAm^8s1J1%QY{#3kkfvWN=+5r;xt%d@v^na^LX9rAZ*rRRS9n7@B3 zIh(s}Le5_zCFIw8gxH@D@_g{o-5>7o_jw0ft+oBp&%b@Yw8WM7 zrH5boo3EvZ_(1|l00|%gB!C2v01`j~NZ=X>eD`35{4^j-PY%DimD+7>Y`4C6{oeh* E06EB-U;qFB literal 0 HcmV?d00001 diff --git a/examples/ASDKTube/Default-736h@3x.png b/examples/ASDKTube/Default-736h@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8949cae16a4217842bd8ad813422c6b02e7cafa GIT binary patch literal 23380 zcmeI3&2QsG7{;dvp`~agBn}`rU}c2_5{^Co*u*CBswBI#5^1Zpi0)~3Y)@Ki6WiGC zChY|TCvFHXE5u>%NL;wV0fEGo^J@PC5E5KCaDuU&4|kFdg zdhMfNZ$I1by=i;VuulBQrS}<*{ybMEgw+Y z?`=z+D4~*BH)T)7hSad?*u+K?zba`e))iG(ur6cGRxKNw(&STfR@qT2@%#2p_u6DQ z7PV`KSr*%hG8&EQBfTCa2MV?4Y7lsEkRh;JT_T6Zzgu6CWjm;?#Ukp#wUkVU{u-UaE@^ zqby1fqcet_rOzCg%}K8}8++;b4u?yJPP41G8G;GYrOI^gIHt-DO{1g4qgQXUOS!b{ z>a(CfpPW-pdFIS>r{mxZS)M6n#Zo9|sKu_;?j)3CQL-0B1E*YN+f#&6rz5@GBVG{Z zNMC6weE<1m&#h>eWYl4c(U7q!V`EQKZH#RestsFJD<)-6&Z8IkLH~G(hhf@Aqv}!V z$$PNP%ca`4;^TXEKT3uqbAll`ph_Gbw3K;crRQu(*_~(*CG51Qqqmf0%@tL# z%OtV!#5ekuMW}3fo+cYjqbZZ7=E~GCvFmv%we)@gvDd507p%LH zca(3HiM7wHU9)NG4c*OMb=lB-OLlervW%Ms#tqVRUEP{mSL6%UTS>sm92r#lFw}aGvc-8^S+s2F7KLn=zH_>DnivE{L5fL|(tNwMYt#KUt6;MNm1~M^YZEUo zWsaBc2I{wzQ?2vUnkgr;U~vM^N4fN`$j=^QbVx(dhAOR!UT2%6Q9m1zgsvU1HSw1l zy|g^7;k{c*UiSyVzc33ax&2^sKn+c6P*;_G^K!n@JzsX48kQ}r_EkzOqv5;LIsT_} zU}&~ED@gy*9L(3RcSynm>O0ExvZf7>(zKng_C46vIdva-)Tgc7gQrX3w1O{|&Q|{L zV6(EzN&qR!9d0QLZSw_F_TSIT=isR5-_TU{QE>i$BCV!*>25)XkQ{H}i_^U`z-5-GJRH)BFa2HG_>+sQA=U>Gio()6`~F zT1ic$<#bgZor~I8wz3Cv_M1SN{U}%{tFv3r!#tQ@)5CP-ykHOxh&TjXVm@3JaB)Dy zA>b18;j(~>10oIqmzWQi1za2uaR|7?e7G#&;(&-lz$NCxWdRolL>vMxF&{1qxHur< z5O9h4a9O~`0TG9QOU#GM0xk}SI0Rf`K3o=XaX`c&;1cuUvVe;NA`StUm=Bi)TpSQ_ z2)M+2xGdn}fQUoDCFa9r0T%~E90D#eA1({HI3VH>aEbYFS-`~s5r=?F%!kVYE)Iw| z1YBZ1To!O~K*S;767%7*fQthn4gr^#50?d891w9R#I-tq&6bAj-P#d*iT2)B=M(k< zuH>!n^bk6E38D8sKf00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!= l00AHX1c1Q*gn;t`y7MJkdHVCOto({Mu5Na}c>U)4e*&NtopJyG literal 0 HcmV?d00001 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..e9965d6274 --- /dev/null +++ b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,361 @@ +// !$*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 */; }; +/* 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; }; + 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 = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.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 = ( + 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 = ""; + }; +/* 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 = ( + 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-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 */, + 05E2128A19D4DB510098F589 /* AppDelegate.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 = 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..a8e5594780 --- /dev/null +++ b/examples/ASDKTube/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/ASDKTube/Sample/Info.plist b/examples/ASDKTube/Sample/Info.plist new file mode 100644 index 0000000000..35d842827b --- /dev/null +++ b/examples/ASDKTube/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/ASDKTube/Sample/ViewController.h b/examples/ASDKTube/Sample/ViewController.h new file mode 100644 index 0000000000..8b16b1c332 --- /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 +#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..46bf1faa90 --- /dev/null +++ b/examples/ASDKTube/Sample/ViewController.m @@ -0,0 +1,71 @@ +/* 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, strong) ASVideoPlayerNode *videoPlayerNode; +@end + +@implementation ViewController + +- (instancetype)init +{ + if (!(self = [super initWithNode:self.videoPlayerNode])) { + return nil; + } + + return self; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + //[self.view addSubnode:self.videoPlayerNode]; + + //[self.videoPlayerNode setNeedsLayout]; +} + +- (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.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); + + _videoPlayerNode.backgroundColor = [UIColor blackColor]; + + return _videoPlayerNode; + +// _guitarVideoNode = [[ASVideoNode alloc] init]; +// +// _guitarVideoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov"]]; +// +// _guitarVideoNode.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height/3); +// +// _guitarVideoNode.gravity = AVLayerVideoGravityResizeAspectFill; +// +// _guitarVideoNode.backgroundColor = [UIColor lightGrayColor]; +// +// _guitarVideoNode.periodicTimeObserverTimescale = 1; //Default is 100 +// +// _guitarVideoNode.delegate = self; +// +// return _guitarVideoNode; +} + +@end \ No newline at end of file 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])); + } +} From c46547ac5f5884594890240b7f44048b5a8e539c Mon Sep 17 00:00:00 2001 From: Erekle Date: Fri, 6 May 2016 21:41:54 +0400 Subject: [PATCH 02/26] prototype Added duration detection in ASVideoNode. --- AsyncDisplayKit/ASVideoNode.h | 2 + AsyncDisplayKit/ASVideoNode.mm | 6 + AsyncDisplayKit/ASVideoPlayerNode.mm | 226 ++++++++++++++++++++++++--- 3 files changed, 212 insertions(+), 22 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index cb85c2de63..1d628e18fd 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -39,6 +39,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, atomic, strong, readonly) AVPlayer *player; @property (nullable, atomic, strong, readonly) AVPlayerItem *currentItem; +@property (nonatomic,assign,readonly) CGFloat duration; + /** * 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. diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 44ba2e88d7..fa4ee1e09b 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -57,6 +57,7 @@ static NSString * const kStatus = @"status"; AVPlayerItem *_currentPlayerItem; AVPlayer *_player; + CGFloat _duration; id _timeObserver; int32_t _periodicTimeObserverTimescale; @@ -148,6 +149,11 @@ static NSString * const kStatus = @"status"; if (_placeholderImageNode.image == nil) { [self generatePlaceholderImage]; } + + [self willChangeValueForKey:@"duration"]; + _duration = CMTimeGetSeconds(asset.duration); + [self didChangeValueForKey:@"duration"]; + __weak __typeof(self) weakSelf = self; _timeObserverInterval = CMTimeMake(1, _periodicTimeObserverTimescale); diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index ea51682ba3..0dcd1d8410 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -8,9 +8,14 @@ #import "ASVideoPlayerNode.h" -static NSString * const kASVideoPlayerNodePlayButton = @"playButton"; +static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; +static NSString * const kASVideoPlayerNodeDurationKeyPath = @"duration"; +static NSString * const kASVideoPlayerNodePlaybackButton = @"playbackButtonNode"; +static NSString * const kASVideoPlayerNodeElapsedLabel = @"elapsedTextNode"; +static NSString * const kASVideoPlayerNodeDurationLabel = @"durationTextNode"; +static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNode"; -@interface ASVideoPlayerNode() +@interface ASVideoPlayerNode() { ASDN::RecursiveMutex _videoPlayerLock; @@ -23,7 +28,15 @@ static NSString * const kASVideoPlayerNodePlayButton = @"playButton"; NSArray *_neededControls; - NSMutableDictionary *_cachedControls; + NSMutableArray *_cachedControls; + + ASControlNode *_playbackButtonNode; + ASTextNode *_elapsedTextNode; + ASTextNode *_durationTextNode; + ASDisplayNode *_scrubberNode; + + BOOL _scrubbing; + } @end @@ -47,6 +60,7 @@ static NSString * const kASVideoPlayerNodePlayButton = @"playButton"; } _url = url; + _asset = [AVAsset assetWithURL:_url]; [self privateInit]; @@ -58,7 +72,7 @@ static NSString * const kASVideoPlayerNodePlayButton = @"playButton"; if (!(self = [super init])) { return nil; } - + _asset = asset; [self privateInit]; @@ -70,10 +84,11 @@ static NSString * const kASVideoPlayerNodePlayButton = @"playButton"; { _neededControls = [self createNeededControlElementsArray]; - _cachedControls = [[NSMutableDictionary alloc] init]; + _cachedControls = [[NSMutableArray alloc] init]; _videoNode = [[ASVideoNode alloc] init]; - _videoNode.asset = [AVAsset assetWithURL:_url]; + _videoNode.asset = _asset; + _videoNode.delegate = self; [self addSubnode:_videoNode]; _controlsHolderNode = [[ASDisplayNode alloc] init]; @@ -81,53 +96,203 @@ static NSString * const kASVideoPlayerNodePlayButton = @"playButton"; [self addSubnode:_controlsHolderNode]; [self createControls]; + + [self addObservers]; } - (NSArray*)createNeededControlElementsArray { //TODO:: Maybe here we will ask delegate what he needs and we force delegate to use our static strings or something like that - return @[kASVideoPlayerNodePlayButton]; + return @[ kASVideoPlayerNodePlaybackButton, kASVideoPlayerNodeElapsedLabel, kASVideoPlayerNodeScrubber, kASVideoPlayerNodeDurationLabel ]; } +- (void)addObservers +{ + [_videoNode addObserver:self forKeyPath:kASVideoPlayerNodeDurationKeyPath options:NSKeyValueObservingOptionNew context:ASVideoPlayerNodeContext]; +} + +- (void)removeObservers +{ + [_videoNode removeObserver:self forKeyPath:kASVideoPlayerNodeDurationKeyPath]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + ASDN::MutexLocker l(_videoPlayerLock); + + if (object != _videoNode) { + return; + } + + if ([keyPath isEqualToString:kASVideoPlayerNodeDurationKeyPath]) { + [(UISlider*)_scrubberNode.view setMaximumValue:round(_videoNode.duration)]; + [self updateDurationTimeLabel]; + } + +} + + #pragma mark - UI - (void)createControls { + ASDN::MutexLocker l(_videoPlayerLock); + for (NSString *controlType in _neededControls) { - if ([controlType isEqualToString:kASVideoPlayerNodePlayButton]) { - [self createPlayButton]; + if ([controlType isEqualToString:kASVideoPlayerNodePlaybackButton]) { + [self createPlaybackButton]; + } else if ([controlType isEqualToString:kASVideoPlayerNodeElapsedLabel]) { + [self createElapsedTextField]; + } else if ([controlType isEqualToString:kASVideoPlayerNodeScrubber]) { + [self createScrubber]; + } else if ([controlType isEqualToString:kASVideoPlayerNodeDurationLabel]) { + [self createDurationTextField]; } } } -- (void)createPlayButton +- (void)createPlaybackButton { - ASControlNode *playButton = [_cachedControls objectForKey:kASVideoPlayerNodePlayButton]; - if (!playButton) { - playButton = [[ASControlNode alloc] init]; - playButton.preferredFrameSize = CGSizeMake(20.0, 20.0); - playButton.backgroundColor = [UIColor redColor]; - - [_cachedControls setObject:playButton forKey:kASVideoPlayerNodePlayButton]; + if (_playbackButtonNode == nil) { + _playbackButtonNode = [[ASControlNode alloc] init]; + _playbackButtonNode.preferredFrameSize = CGSizeMake(20.0, 20.0); + _playbackButtonNode.backgroundColor = [UIColor redColor]; + [_playbackButtonNode addTarget:self action:@selector(playbackButtonTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; + [_cachedControls addObject:_playbackButtonNode]; } - [self addSubnode:playButton]; + [self addSubnode:_playbackButtonNode]; } +- (void)createElapsedTextField +{ + if (_elapsedTextNode == nil) { + _elapsedTextNode = [[ASTextNode alloc] init]; + _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00"]; + _elapsedTextNode.flexGrow = YES; + + [_cachedControls addObject:_elapsedTextNode]; + } + [self addSubnode:_elapsedTextNode]; +} + +- (void)createDurationTextField +{ + if (_durationTextNode == nil) { + _durationTextNode = [[ASTextNode alloc] init]; + _durationTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00"]; + _durationTextNode.flexGrow = YES; + + [_cachedControls addObject:_durationTextNode]; + } + [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 addTarget:self action:@selector(scrubbingDidBegin) forControlEvents:UIControlEventTouchDown]; + [slider addTarget:self action:@selector(scrubbingDidEnd) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; + [slider addTarget:self action:@selector(scrubberValueChanged:) forControlEvents:UIControlEventValueChanged]; + + return slider; + }]; + + _scrubberNode.flexShrink = YES; + + [_cachedControls addObject:_scrubberNode]; + } + + [self addSubnode:_scrubberNode]; +} + +- (void)updateDurationTimeLabel +{ + NSString *formatedDuration = [self timeFormatted:round(_videoNode.duration)]; + _durationTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration]; +} + +- (void)updateElapsedTimeLabel:(NSTimeInterval)seconds +{ + NSString *formatedDuration = [self timeFormatted:round(seconds)]; + _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration]; +} + +- (NSAttributedString*)timeLabelAttributedStringForString:(NSString*)string +{ + //TODO:: maybe we can ask delegate for this options too + NSDictionary *options = @{ + NSFontAttributeName : [UIFont systemFontOfSize:12.0], + NSForegroundColorAttributeName: [UIColor whiteColor] + }; + + NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:options]; + + return attributedString; +} + +#pragma mark - ASVideoNodeDelegate +- (void)videoNode:(ASVideoNode *)videoNode didPlayToSecond:(NSTimeInterval)second +{ + if(_scrubbing){ + return; + } + [self updateElapsedTimeLabel:second]; + [(UISlider*)_scrubberNode.view setValue:second animated:YES]; +} + +#pragma mark - Actions +- (void)playbackButtonTapped:(ASControlNode*)node +{ + if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { + [_videoNode pause]; + _playbackButtonNode.backgroundColor = [UIColor greenColor]; + } else { + [_videoNode play]; + _playbackButtonNode.backgroundColor = [UIColor redColor]; + } +} + +- (void)scrubbingDidBegin +{ + NSLog(@"scrubbingDidBegin"); + _scrubbing = YES; +} + +- (void)scrubbingDidEnd +{ + NSLog(@"scrubbingDidEnd"); + _scrubbing = NO; +} + +- (void)scrubberValueChanged:(UISlider*)slider +{ + CGFloat seconds = slider.value; + NSLog(@"scrubberValueChanged, value is : %f",seconds); + [self updateElapsedTimeLabel:seconds]; + [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; +} + + #pragma mark - Layout - (ASLayoutSpec*)layoutSpecThatFits:(ASSizeRange)constrainedSize { _videoNode.preferredFrameSize = constrainedSize.max; + _scrubberNode.preferredFrameSize = CGSizeMake(constrainedSize.max.width, 44.0); ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; spacer.flexGrow = YES; ASStackLayoutSpec *controlsSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:0.0 + spacing:10.0 justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - children:[_cachedControls allValues]]; + alignItems:ASStackLayoutAlignItemsCenter + children:_cachedControls]; - UIEdgeInsets insets = UIEdgeInsetsMake(8.0, 8.0, 8.0, 8.0); + UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); ASInsetLayoutSpec *controlsInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlsSpec]; @@ -143,4 +308,21 @@ static NSString * const kASVideoPlayerNodePlayButton = @"playButton"; return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[overlaySpec]]; } +#pragma mark - Helpers +- (NSString *)timeFormatted:(int)totalSeconds +{ + + int seconds = totalSeconds % 60; + int minutes = (totalSeconds / 60) % 60; + + return [NSString stringWithFormat:@"%02d:%02d", minutes, seconds]; +} + +#pragma mark - Lifecycle + +- (void)dealloc +{ + [self removeObservers]; +} + @end From 3367b4fc49b36b75c8725d9ca061ebf37e90e03b Mon Sep 17 00:00:00 2001 From: Erekle Date: Tue, 10 May 2016 22:05:07 +0400 Subject: [PATCH 03/26] slider changes --- AsyncDisplayKit/ASVideoPlayerNode.mm | 64 +++++++++++++++++++--------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 0dcd1d8410..d1462d2087 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -35,7 +35,7 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod ASTextNode *_durationTextNode; ASDisplayNode *_scrubberNode; - BOOL _scrubbing; + BOOL _isSeeking; } @@ -125,7 +125,6 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod } if ([keyPath isEqualToString:kASVideoPlayerNodeDurationKeyPath]) { - [(UISlider*)_scrubberNode.view setMaximumValue:round(_videoNode.duration)]; [self updateDurationTimeLabel]; } @@ -150,6 +149,16 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod } } +- (void)removeControls +{ + [_cachedControls enumerateObjectsUsingBlock:^(ASDisplayNode *_Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { + [node.view removeFromSuperview]; + [node removeFromSupernode]; + node = nil; + NSLog(@"%@",_playbackButtonNode); + }]; +} + - (void)createPlaybackButton { if (_playbackButtonNode == nil) { @@ -193,10 +202,11 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod _scrubberNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ UISlider *slider = [[UISlider alloc] initWithFrame:CGRectZero]; slider.minimumValue = 0.0; + slider.maximumValue = 1.0; - [slider addTarget:self action:@selector(scrubbingDidBegin) forControlEvents:UIControlEventTouchDown]; - [slider addTarget:self action:@selector(scrubbingDidEnd) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; - [slider addTarget:self action:@selector(scrubberValueChanged:) forControlEvents:UIControlEventValueChanged]; + [slider addTarget:self action:@selector(beganSeek) forControlEvents:UIControlEventTouchDown]; + [slider addTarget:self action:@selector(endedSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; + [slider addTarget:self action:@selector(changedSeekValue:) forControlEvents:UIControlEventValueChanged]; return slider; }]; @@ -237,11 +247,18 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod #pragma mark - ASVideoNodeDelegate - (void)videoNode:(ASVideoNode *)videoNode didPlayToSecond:(NSTimeInterval)second { - if(_scrubbing){ + if(_isSeeking){ return; } + CGFloat duration = CMTimeGetSeconds(videoNode.currentItem.duration); + [self updateElapsedTimeLabel:second]; - [(UISlider*)_scrubberNode.view setValue:second animated:YES]; + [(UISlider*)_scrubberNode.view setValue:(second/duration) animated:NO]; +} + +- (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode +{ + //[self removeControls]; } #pragma mark - Actions @@ -256,24 +273,33 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod } } -- (void)scrubbingDidBegin +- (void)beganSeek { - NSLog(@"scrubbingDidBegin"); - _scrubbing = YES; + _isSeeking = YES; } -- (void)scrubbingDidEnd +- (void)endedSeek { - NSLog(@"scrubbingDidEnd"); - _scrubbing = NO; + _isSeeking = NO; } -- (void)scrubberValueChanged:(UISlider*)slider +- (void)changedSeekValue:(UISlider*)slider { - CGFloat seconds = slider.value; - NSLog(@"scrubberValueChanged, value is : %f",seconds); + CGFloat percentage = slider.value * 100; + [self seekToTime:percentage]; +} + +-(void)seekToTime:(CGFloat)percentComplete +{ + CGFloat duration = CMTimeGetSeconds(_videoNode.currentItem.duration); + CGFloat seconds = (duration * percentComplete) / 100; + [self updateElapsedTimeLabel:seconds]; [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; + + if (_videoNode.playerState != ASVideoNodePlayerStatePlaying) { + [_videoNode play]; + } } @@ -286,7 +312,7 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; spacer.flexGrow = YES; - ASStackLayoutSpec *controlsSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:10.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsCenter @@ -294,13 +320,13 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); - ASInsetLayoutSpec *controlsInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlsSpec]; + ASInsetLayoutSpec *controlbarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlbarSpec]; ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart - children:@[spacer,controlsInsetSpec]]; + children:@[spacer,controlbarInsetSpec]]; ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:mainVerticalStack]; From 716082d411d414d6fe56a295d9f22c1a5041dec2 Mon Sep 17 00:00:00 2001 From: Erekle Date: Tue, 10 May 2016 22:48:32 +0400 Subject: [PATCH 04/26] better duration handling. new ASVideoNode states --- AsyncDisplayKit/ASVideoNode.h | 3 ++- AsyncDisplayKit/ASVideoNode.mm | 8 ++----- AsyncDisplayKit/ASVideoPlayerNode.mm | 33 ++++++++++------------------ 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 1d628e18fd..aecc602913 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -14,6 +14,8 @@ typedef enum { ASVideoNodePlayerStateUnknown, + ASVideoNodePlayerStateReadyToPlay, + ASVideoNodePlayerStatePlaybackLikelyToKeepUp, ASVideoNodePlayerStatePlaying, ASVideoNodePlayerStateLoading, ASVideoNodePlayerStatePaused, @@ -39,7 +41,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, atomic, strong, readonly) AVPlayer *player; @property (nullable, atomic, strong, readonly) AVPlayerItem *currentItem; -@property (nonatomic,assign,readonly) CGFloat duration; /** * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index fa4ee1e09b..ed50e25190 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -57,7 +57,6 @@ static NSString * const kStatus = @"status"; AVPlayerItem *_currentPlayerItem; AVPlayer *_player; - CGFloat _duration; id _timeObserver; int32_t _periodicTimeObserverTimescale; @@ -150,11 +149,6 @@ static NSString * const kStatus = @"status"; [self generatePlaceholderImage]; } - [self willChangeValueForKey:@"duration"]; - _duration = CMTimeGetSeconds(asset.duration); - [self didChangeValueForKey:@"duration"]; - - __weak __typeof(self) weakSelf = self; _timeObserverInterval = CMTimeMake(1, _periodicTimeObserverTimescale); _timeObserver = [_player addPeriodicTimeObserverForInterval:_timeObserverInterval queue:NULL usingBlock:^(CMTime time){ @@ -283,6 +277,7 @@ static NSString * const kStatus = @"status"; if ([keyPath isEqualToString:kStatus]) { if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { + self.playerState = ASVideoNodePlayerStateReadyToPlay; [self removeSpinner]; // If we don't yet have a placeholder image update it now that we should have data available for it if (_placeholderImageNode.image == nil) { @@ -293,6 +288,7 @@ static NSString * const kStatus = @"status"; } } } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { + self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUp; if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) { [self play]; // autoresume after buffer catches up } diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index d1462d2087..542796e6b6 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -36,6 +36,7 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod ASDisplayNode *_scrubberNode; BOOL _isSeeking; + CGFloat _duration; } @@ -108,29 +109,14 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod - (void)addObservers { - [_videoNode addObserver:self forKeyPath:kASVideoPlayerNodeDurationKeyPath options:NSKeyValueObservingOptionNew context:ASVideoPlayerNodeContext]; + } - (void)removeObservers { - [_videoNode removeObserver:self forKeyPath:kASVideoPlayerNodeDurationKeyPath]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - ASDN::MutexLocker l(_videoPlayerLock); - - if (object != _videoNode) { - return; - } - - if ([keyPath isEqualToString:kASVideoPlayerNodeDurationKeyPath]) { - [self updateDurationTimeLabel]; - } } - #pragma mark - UI - (void)createControls { @@ -221,7 +207,7 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod - (void)updateDurationTimeLabel { - NSString *formatedDuration = [self timeFormatted:round(_videoNode.duration)]; + NSString *formatedDuration = [self timeFormatted:round(_duration)]; _durationTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration]; } @@ -245,15 +231,21 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod } #pragma mark - ASVideoNodeDelegate +- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toSate +{ + if(toSate == ASVideoNodePlayerStateReadyToPlay){ + _duration = CMTimeGetSeconds(_videoNode.currentItem.duration); + [self updateDurationTimeLabel]; + } +} - (void)videoNode:(ASVideoNode *)videoNode didPlayToSecond:(NSTimeInterval)second { if(_isSeeking){ return; } - CGFloat duration = CMTimeGetSeconds(videoNode.currentItem.duration); [self updateElapsedTimeLabel:second]; - [(UISlider*)_scrubberNode.view setValue:(second/duration) animated:NO]; + [(UISlider*)_scrubberNode.view setValue:(second/_duration) animated:NO]; } - (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode @@ -291,8 +283,7 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod -(void)seekToTime:(CGFloat)percentComplete { - CGFloat duration = CMTimeGetSeconds(_videoNode.currentItem.duration); - CGFloat seconds = (duration * percentComplete) / 100; + CGFloat seconds = (_duration * percentComplete) / 100; [self updateElapsedTimeLabel:seconds]; [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; From 52b40f80045942ecdf94dec0a772c11e279acd06 Mon Sep 17 00:00:00 2001 From: Erekle Date: Tue, 10 May 2016 23:57:47 +0400 Subject: [PATCH 05/26] ASVideoPlayerNodeControlType and fixes * changed static string to ASVideoPlayerNodeControlType * public property for duration * public method for seek * new _controlFlexGrowSpacerSpec; used to use flexible space in controlbar. for example if user does not want to have scrubber, he can replace it with flexible spacer and duration text node will be kept at the right edge. --- AsyncDisplayKit/ASVideoNode.h | 2 +- AsyncDisplayKit/ASVideoNode.mm | 2 +- AsyncDisplayKit/ASVideoPlayerNode.h | 14 +++++++ AsyncDisplayKit/ASVideoPlayerNode.mm | 63 +++++++++++++++++++--------- 4 files changed, 59 insertions(+), 22 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index aecc602913..14120a2eda 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -15,7 +15,7 @@ typedef enum { ASVideoNodePlayerStateUnknown, ASVideoNodePlayerStateReadyToPlay, - ASVideoNodePlayerStatePlaybackLikelyToKeepUp, + ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying, ASVideoNodePlayerStatePlaying, ASVideoNodePlayerStateLoading, ASVideoNodePlayerStatePaused, diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index ed50e25190..742a55026d 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -288,7 +288,7 @@ static NSString * const kStatus = @"status"; } } } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { - self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUp; + self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) { [self play]; // autoresume after buffer catches up } diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index eec4e350d3..c7fdb8f66b 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -14,12 +14,26 @@ @class AVAsset; +typedef enum { + ASVideoPlayerNodeControlTypePlaybackButton, + ASVideoPlayerNodeControlTypeElapsedText, + ASVideoPlayerNodeControlTypeDurationText, + ASVideoPlayerNodeControlTypeScrubber, + ASVideoPlayerNodeControlTypeFlexGrowSpacer, +} ASVideoPlayerNodeControlType; + NS_ASSUME_NONNULL_BEGIN @interface ASVideoPlayerNode : ASDisplayNode +@property (nonatomic,assign,readonly) CGFloat duration; + - (instancetype)initWithUrl:(NSURL*)url; - (instancetype)initWithAsset:(AVAsset*)asset; + +#pragma mark - Public API +-(void)seekToTime:(CGFloat)percentComplete; + @end NS_ASSUME_NONNULL_END #endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 542796e6b6..78fbaf9e07 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -9,11 +9,6 @@ #import "ASVideoPlayerNode.h" static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; -static NSString * const kASVideoPlayerNodeDurationKeyPath = @"duration"; -static NSString * const kASVideoPlayerNodePlaybackButton = @"playbackButtonNode"; -static NSString * const kASVideoPlayerNodeElapsedLabel = @"elapsedTextNode"; -static NSString * const kASVideoPlayerNodeDurationLabel = @"durationTextNode"; -static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNode"; @interface ASVideoPlayerNode() { @@ -34,6 +29,7 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod ASTextNode *_elapsedTextNode; ASTextNode *_durationTextNode; ASDisplayNode *_scrubberNode; + ASStackLayoutSpec *_controlFlexGrowSpacerSpec; BOOL _isSeeking; CGFloat _duration; @@ -104,7 +100,11 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod - (NSArray*)createNeededControlElementsArray { //TODO:: Maybe here we will ask delegate what he needs and we force delegate to use our static strings or something like that - return @[ kASVideoPlayerNodePlaybackButton, kASVideoPlayerNodeElapsedLabel, kASVideoPlayerNodeScrubber, kASVideoPlayerNodeDurationLabel ]; + return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), + @(ASVideoPlayerNodeControlTypeElapsedText), + @(ASVideoPlayerNodeControlTypeScrubber), + @(ASVideoPlayerNodeControlTypeFlexGrowSpacer), + @(ASVideoPlayerNodeControlTypeDurationText) ]; } - (void)addObservers @@ -122,15 +122,26 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod { ASDN::MutexLocker l(_videoPlayerLock); - for (NSString *controlType in _neededControls) { - if ([controlType isEqualToString:kASVideoPlayerNodePlaybackButton]) { - [self createPlaybackButton]; - } else if ([controlType isEqualToString:kASVideoPlayerNodeElapsedLabel]) { - [self createElapsedTextField]; - } else if ([controlType isEqualToString:kASVideoPlayerNodeScrubber]) { - [self createScrubber]; - } else if ([controlType isEqualToString:kASVideoPlayerNodeDurationLabel]) { - [self createDurationTextField]; + for (int i = 0; i < _neededControls.count; i++) { + ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[[_neededControls objectAtIndex:i] 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; } } } @@ -163,7 +174,6 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod if (_elapsedTextNode == nil) { _elapsedTextNode = [[ASTextNode alloc] init]; _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00"]; - _elapsedTextNode.flexGrow = YES; [_cachedControls addObject:_elapsedTextNode]; } @@ -175,7 +185,6 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod if (_durationTextNode == nil) { _durationTextNode = [[ASTextNode alloc] init]; _durationTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00"]; - _durationTextNode.flexGrow = YES; [_cachedControls addObject:_durationTextNode]; } @@ -205,6 +214,16 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod [self addSubnode:_scrubberNode]; } +- (void)createControlFlexGrowSpacer +{ + if (_controlFlexGrowSpacerSpec == nil) { + _controlFlexGrowSpacerSpec = [[ASStackLayoutSpec alloc] init]; + _controlFlexGrowSpacerSpec.flexGrow = YES; + } + + [_cachedControls addObject:_controlFlexGrowSpacerSpec]; +} + - (void)updateDurationTimeLabel { NSString *formatedDuration = [self timeFormatted:round(_duration)]; @@ -233,11 +252,12 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod #pragma mark - ASVideoNodeDelegate - (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toSate { - if(toSate == ASVideoNodePlayerStateReadyToPlay){ + if (toSate == ASVideoNodePlayerStateReadyToPlay) { _duration = CMTimeGetSeconds(_videoNode.currentItem.duration); [self updateDurationTimeLabel]; } } + - (void)videoNode:(ASVideoNode *)videoNode didPlayToSecond:(NSTimeInterval)second { if(_isSeeking){ @@ -277,13 +297,13 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod - (void)changedSeekValue:(UISlider*)slider { - CGFloat percentage = slider.value * 100; + CGFloat percentage = slider.value * 100; [self seekToTime:percentage]; } -(void)seekToTime:(CGFloat)percentComplete { - CGFloat seconds = (_duration * percentComplete) / 100; + CGFloat seconds = (_duration * percentComplete) / 100; [self updateElapsedTimeLabel:seconds]; [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; @@ -308,11 +328,14 @@ static NSString * const kASVideoPlayerNodeScrubber = @"elapsedScrubberNod justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsCenter children:_cachedControls]; + 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 From 03444a980772bdd148401f13272b0d0cf6c8cf51 Mon Sep 17 00:00:00 2001 From: Erekle Date: Wed, 11 May 2016 00:18:26 +0400 Subject: [PATCH 06/26] minor layout change --- AsyncDisplayKit/ASVideoPlayerNode.mm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 78fbaf9e07..ee63e566c0 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -320,6 +320,13 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _videoNode.preferredFrameSize = constrainedSize.max; _scrubberNode.preferredFrameSize = CGSizeMake(constrainedSize.max.width, 44.0); + //TODO:: here wi will ask delegate for it's own ASLayoutSpec + // something like layoutSpecForVideoPlayerNode:forConstrainedsize:forControls + return [self defaultLayoutSpecThatFits:constrainedSize]; +} + +- (ASLayoutSpec*)defaultLayoutSpecThatFits:(ASSizeRange)constrainedSize +{ ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; spacer.flexGrow = YES; From 9fe57ea5838c6e9c89bbdad7741c954a5442c5c9 Mon Sep 17 00:00:00 2001 From: Erekle Date: Wed, 11 May 2016 21:55:53 +0400 Subject: [PATCH 07/26] ASVideoPlayerDelegate first steps --- AsyncDisplayKit/ASVideoPlayerNode.h | 15 ++++++ AsyncDisplayKit/ASVideoPlayerNode.mm | 61 +++++++++++++++++++++-- examples/ASDKTube/Sample/ViewController.m | 46 ++++++++++------- 3 files changed, 99 insertions(+), 23 deletions(-) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index c7fdb8f66b..097348ea1e 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -13,6 +13,7 @@ //#import @class AVAsset; +@protocol ASVideoPlayerNodeDelegate; typedef enum { ASVideoPlayerNodeControlTypePlaybackButton, @@ -26,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN @interface ASVideoPlayerNode : ASDisplayNode +@property (nullable, atomic, weak, readwrite) id delegate; + @property (nonatomic,assign,readonly) CGFloat duration; - (instancetype)initWithUrl:(NSURL*)url; @@ -34,6 +37,18 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Public API -(void)seekToTime:(CGFloat)percentComplete; +@end + +@protocol ASVideoPlayerNodeDelegate +@optional +/** + * @abstract Delegate method invoked before creating controlbar controls + * @param videoPlayer + */ +- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode*)videoPlayer; +- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode*)videoPlayer; +- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode*)videoPlayer; +- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode*)videoPlayer; @end NS_ASSUME_NONNULL_END #endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index ee63e566c0..da4d4e0777 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -13,6 +13,15 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; @interface ASVideoPlayerNode() { ASDN::RecursiveMutex _videoPlayerLock; + + __weak id _delegate; + + struct { + unsigned int delegateNeededControls:1; + unsigned int delegateScrubberMaximumTrackTintColor:1; + unsigned int delegateScrubberMinimumTrackTintColor:1; + unsigned int delegateScrubberThumbTintColor:1; + } _delegateFlags; NSURL *_url; AVAsset *_asset; @@ -80,7 +89,6 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)privateInit { - _neededControls = [self createNeededControlElementsArray]; _cachedControls = [[NSMutableArray alloc] init]; _videoNode = [[ASVideoNode alloc] init]; @@ -92,14 +100,24 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _controlsHolderNode.backgroundColor = [UIColor greenColor]; [self addSubnode:_controlsHolderNode]; - [self createControls]; - [self addObservers]; } +- (void)didLoad +{ + [super didLoad]; + { + ASDN::MutexLocker l(_videoPlayerLock); + [self createControls]; + } +} + - (NSArray*)createNeededControlElementsArray { - //TODO:: Maybe here we will ask delegate what he needs and we force delegate to use our static strings or something like that + if (_delegateFlags.delegateNeededControls) { + return [_delegate videoPlayerNodeNeededControls:self]; + } + return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), @(ASVideoPlayerNodeControlTypeElapsedText), @(ASVideoPlayerNodeControlTypeScrubber), @@ -122,6 +140,10 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; { ASDN::MutexLocker l(_videoPlayerLock); + if (_neededControls == nil) { + _neededControls = [self createNeededControlElementsArray]; + } + for (int i = 0; i < _neededControls.count; i++) { ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[[_neededControls objectAtIndex:i] integerValue]; switch (type) { @@ -199,6 +221,18 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; 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]; + } + [slider addTarget:self action:@selector(beganSeek) forControlEvents:UIControlEventTouchDown]; [slider addTarget:self action:@selector(endedSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; [slider addTarget:self action:@selector(changedSeekValue:) forControlEvents:UIControlEventValueChanged]; @@ -355,6 +389,25 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[overlaySpec]]; } +#pragma mark - Properties +- (id)delegate{ + return _delegate; +} + +- (void)setDelegate:(id)delegate +{ + _delegate = delegate; + + if (_delegate == nil) { + memset(&_delegateFlags, 0, sizeof(_delegateFlags)); + } else { + _delegateFlags.delegateNeededControls = [_delegate respondsToSelector:@selector(videoPlayerNodeNeededControls:)]; + _delegateFlags.delegateScrubberMaximumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMaximumTrackTint:)]; + _delegateFlags.delegateScrubberMinimumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMinimumTrackTint:)]; + _delegateFlags.delegateScrubberThumbTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbTint:)]; + } +} + #pragma mark - Helpers - (NSString *)timeFormatted:(int)totalSeconds { diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index 46bf1faa90..8c09f47b2c 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -11,7 +11,7 @@ #import "ViewController.h" -@interface ViewController() +@interface ViewController() @property (nonatomic, strong) ASVideoPlayerNode *videoPlayerNode; @end @@ -44,28 +44,36 @@ 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.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); + _videoPlayerNode.delegate = self; _videoPlayerNode.backgroundColor = [UIColor blackColor]; return _videoPlayerNode; - -// _guitarVideoNode = [[ASVideoNode alloc] init]; -// -// _guitarVideoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov"]]; -// -// _guitarVideoNode.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height/3); -// -// _guitarVideoNode.gravity = AVLayerVideoGravityResizeAspectFill; -// -// _guitarVideoNode.backgroundColor = [UIColor lightGrayColor]; -// -// _guitarVideoNode.periodicTimeObserverTimescale = 1; //Default is 100 -// -// _guitarVideoNode.delegate = self; -// -// return _guitarVideoNode; +} + +#pragma mark - ASVideoPlayerNodeDelegate +- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer +{ + return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), + @(ASVideoPlayerNodeControlTypeElapsedText), + @(ASVideoPlayerNodeControlTypeScrubber), + @(ASVideoPlayerNodeControlTypeFlexGrowSpacer), + @(ASVideoPlayerNodeControlTypeDurationText) ]; +} + +- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer +{ + return [UIColor clearColor]; +} + +- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer +{ + return [UIColor orangeColor]; +} + +- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer +{ + return [UIColor orangeColor]; } @end \ No newline at end of file From 58101feeee2bea58e9ad60ab1b3749de13ddb7c4 Mon Sep 17 00:00:00 2001 From: Erekle Date: Wed, 11 May 2016 23:22:17 +0400 Subject: [PATCH 08/26] event better duration handling. time label attributes delegation --- AsyncDisplayKit/ASVideoPlayerNode.h | 14 +++-- AsyncDisplayKit/ASVideoPlayerNode.mm | 71 +++++++++++++++-------- examples/ASDKTube/Sample/ViewController.m | 17 ++++-- 3 files changed, 69 insertions(+), 33 deletions(-) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 097348ea1e..64998a154c 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -27,9 +27,9 @@ NS_ASSUME_NONNULL_BEGIN @interface ASVideoPlayerNode : ASDisplayNode -@property (nullable, atomic, weak, readwrite) id delegate; +@property (nullable, atomic, weak) id delegate; -@property (nonatomic,assign,readonly) CGFloat duration; +@property (nonatomic,assign,readonly) CMTime duration; - (instancetype)initWithUrl:(NSURL*)url; - (instancetype)initWithAsset:(AVAsset*)asset; @@ -46,9 +46,13 @@ NS_ASSUME_NONNULL_BEGIN * @param videoPlayer */ - (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode*)videoPlayer; -- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode*)videoPlayer; -- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode*)videoPlayer; -- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode*)videoPlayer; +- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayerNode timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType; + +#pragma mark - Scrubber delegate methods +- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer; +- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer; +- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer; +- (UIImage *)videoPlayerNodeScrubberThumbImage:(ASVideoPlayerNode *)videoPlayer; @end NS_ASSUME_NONNULL_END #endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index da4d4e0777..7173fed128 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -21,6 +21,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; unsigned int delegateScrubberMaximumTrackTintColor:1; unsigned int delegateScrubberMinimumTrackTintColor:1; unsigned int delegateScrubberThumbTintColor:1; + unsigned int delegateScrubberThumbImage:1; + unsigned int delegateTimeLabelAttributes:1; } _delegateFlags; NSURL *_url; @@ -41,7 +43,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; ASStackLayoutSpec *_controlFlexGrowSpacerSpec; BOOL _isSeeking; - CGFloat _duration; + CMTime _duration; } @@ -112,7 +114,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } -- (NSArray*)createNeededControlElementsArray +- (NSArray*)createControlElementArray { if (_delegateFlags.delegateNeededControls) { return [_delegate videoPlayerNodeNeededControls:self]; @@ -141,7 +143,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; ASDN::MutexLocker l(_videoPlayerLock); if (_neededControls == nil) { - _neededControls = [self createNeededControlElementsArray]; + _neededControls = [self createControlElementArray]; } for (int i = 0; i < _neededControls.count; i++) { @@ -195,7 +197,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; { if (_elapsedTextNode == nil) { _elapsedTextNode = [[ASTextNode alloc] init]; - _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00"]; + _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00" forControlType:ASVideoPlayerNodeControlTypeElapsedText]; [_cachedControls addObject:_elapsedTextNode]; } @@ -206,7 +208,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; { if (_durationTextNode == nil) { _durationTextNode = [[ASTextNode alloc] init]; - _durationTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00"]; + _durationTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00" forControlType:ASVideoPlayerNodeControlTypeDurationText]; [_cachedControls addObject:_durationTextNode]; } @@ -229,10 +231,16 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; slider.maximumTrackTintColor = [_delegate videoPlayerNodeScrubberMaximumTrackTint:self]; } - if (_delegateFlags.delegateScrubberThumbTintColor){ - slider.thumbTintColor = [_delegate videoPlayerNodeScrubberThumbTint: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(beganSeek) forControlEvents:UIControlEventTouchDown]; [slider addTarget:self action:@selector(endedSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; [slider addTarget:self action:@selector(changedSeekValue:) forControlEvents:UIControlEventValueChanged]; @@ -260,23 +268,28 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)updateDurationTimeLabel { - NSString *formatedDuration = [self timeFormatted:round(_duration)]; - _durationTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration]; + NSString *formatedDuration = [self timeStringForCMTime:_duration]; + _durationTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration forControlType:ASVideoPlayerNodeControlTypeDurationText]; } - (void)updateElapsedTimeLabel:(NSTimeInterval)seconds { - NSString *formatedDuration = [self timeFormatted:round(seconds)]; - _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration]; + NSString *formatedDuration = [self timeStringForCMTime:CMTimeMakeWithSeconds( seconds, _videoNode.periodicTimeObserverTimescale )]; + _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration forControlType:ASVideoPlayerNodeControlTypeElapsedText]; } -- (NSAttributedString*)timeLabelAttributedStringForString:(NSString*)string +- (NSAttributedString*)timeLabelAttributedStringForString:(NSString*)string forControlType:(ASVideoPlayerNodeControlType)controlType { - //TODO:: maybe we can ask delegate for this options too - NSDictionary *options = @{ - NSFontAttributeName : [UIFont systemFontOfSize:12.0], - NSForegroundColorAttributeName: [UIColor whiteColor] - }; + NSDictionary *options; + if (_delegateFlags.delegateTimeLabelAttributes) { + options = [_delegate videoPlayerNodeTimeLabelAttributes:self timeLabelType:controlType]; + } else { + options = @{ + NSFontAttributeName : [UIFont systemFontOfSize:12.0], + NSForegroundColorAttributeName: [UIColor whiteColor] + }; + } + NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:options]; @@ -287,7 +300,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toSate { if (toSate == ASVideoNodePlayerStateReadyToPlay) { - _duration = CMTimeGetSeconds(_videoNode.currentItem.duration); + _duration = _videoNode.currentItem.duration; [self updateDurationTimeLabel]; } } @@ -299,7 +312,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } [self updateElapsedTimeLabel:second]; - [(UISlider*)_scrubberNode.view setValue:(second/_duration) animated:NO]; + [(UISlider*)_scrubberNode.view setValue:(second/ CMTimeGetSeconds(_duration) ) animated:NO]; } - (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode @@ -337,7 +350,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; -(void)seekToTime:(CGFloat)percentComplete { - CGFloat seconds = (_duration * percentComplete) / 100; + CGFloat seconds = ( CMTimeGetSeconds(_duration) * percentComplete ) / 100; [self updateElapsedTimeLabel:seconds]; [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; @@ -405,17 +418,27 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _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:)]; } } #pragma mark - Helpers -- (NSString *)timeFormatted:(int)totalSeconds +- (NSString *)timeStringForCMTime:(CMTime)time { + NSUInteger dTotalSeconds = CMTimeGetSeconds(time); - int seconds = totalSeconds % 60; - int minutes = (totalSeconds / 60) % 60; + NSUInteger dHours = floor(dTotalSeconds / 3600); + NSUInteger dMinutes = floor(dTotalSeconds % 3600 / 60); + NSUInteger dSeconds = floor(dTotalSeconds % 3600 % 60); - return [NSString stringWithFormat:@"%02d:%02d", minutes, seconds]; + NSString *videoDurationText; + if (dHours > 0) { + videoDurationText = [NSString stringWithFormat:@"%i:%01i:%02i", (int)dHours, (int)dMinutes, (int)dSeconds]; + } else { + videoDurationText = [NSString stringWithFormat:@"%01i:%02i", (int)dMinutes, (int)dSeconds]; + } + return videoDurationText; } #pragma mark - Lifecycle diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index 8c09f47b2c..723e0a5af7 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -57,23 +57,32 @@ return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), @(ASVideoPlayerNodeControlTypeElapsedText), @(ASVideoPlayerNodeControlTypeScrubber), - @(ASVideoPlayerNodeControlTypeFlexGrowSpacer), @(ASVideoPlayerNodeControlTypeDurationText) ]; } - (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer { - return [UIColor clearColor]; + return [UIColor colorWithRed:1 green:1 blue:1 alpha:0.3]; } - (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer { - return [UIColor orangeColor]; + return [UIColor whiteColor]; } - (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer { - return [UIColor orangeColor]; + return [UIColor whiteColor]; +} + +- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayerNode timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType +{ + NSDictionary *options = @{ + NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:13.0], + NSForegroundColorAttributeName: [UIColor orangeColor] + }; + + return options; } @end \ No newline at end of file From 441f9632cdf9258c421eb81aa7de237d9f5bb4f9 Mon Sep 17 00:00:00 2001 From: Erekle Date: Thu, 12 May 2016 01:29:54 +0400 Subject: [PATCH 09/26] ASVideoPlayer layout delegation --- AsyncDisplayKit/ASVideoPlayerNode.h | 3 + AsyncDisplayKit/ASVideoPlayerNode.mm | 75 ++++++++++++++++------- examples/ASDKTube/Sample/ViewController.m | 53 ++++++++++++++-- 3 files changed, 104 insertions(+), 27 deletions(-) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 64998a154c..7373fb9f49 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -53,6 +53,9 @@ NS_ASSUME_NONNULL_BEGIN - (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer; - (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer; - (UIImage *)videoPlayerNodeScrubberThumbImage:(ASVideoPlayerNode *)videoPlayer; +- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer + forControls:(NSDictionary *)controls + forConstrainedSize:(ASSizeRange)constrainedSize; @end NS_ASSUME_NONNULL_END #endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 7173fed128..5bbbf05373 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -23,6 +23,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; unsigned int delegateScrubberThumbTintColor:1; unsigned int delegateScrubberThumbImage:1; unsigned int delegateTimeLabelAttributes:1; + unsigned int delegateLayoutSpecForControls:1; } _delegateFlags; NSURL *_url; @@ -34,7 +35,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; NSArray *_neededControls; - NSMutableArray *_cachedControls; + NSMutableDictionary *_cachedControls; ASControlNode *_playbackButtonNode; ASTextNode *_elapsedTextNode; @@ -91,7 +92,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)privateInit { - _cachedControls = [[NSMutableArray alloc] init]; + _cachedControls = [[NSMutableDictionary alloc] init]; _videoNode = [[ASVideoNode alloc] init]; _videoNode.asset = _asset; @@ -123,7 +124,6 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), @(ASVideoPlayerNodeControlTypeElapsedText), @(ASVideoPlayerNodeControlTypeScrubber), - @(ASVideoPlayerNodeControlTypeFlexGrowSpacer), @(ASVideoPlayerNodeControlTypeDurationText) ]; } @@ -172,12 +172,12 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)removeControls { - [_cachedControls enumerateObjectsUsingBlock:^(ASDisplayNode *_Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { - [node.view removeFromSuperview]; - [node removeFromSupernode]; - node = nil; - NSLog(@"%@",_playbackButtonNode); - }]; +// [_cachedControls enumerateObjectsUsingBlock:^(ASDisplayNode *_Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { +// [node.view removeFromSuperview]; +// [node removeFromSupernode]; +// node = nil; +// NSLog(@"%@",_playbackButtonNode); +// }]; } - (void)createPlaybackButton @@ -187,7 +187,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _playbackButtonNode.preferredFrameSize = CGSizeMake(20.0, 20.0); _playbackButtonNode.backgroundColor = [UIColor redColor]; [_playbackButtonNode addTarget:self action:@selector(playbackButtonTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; - [_cachedControls addObject:_playbackButtonNode]; + [_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)]; } [self addSubnode:_playbackButtonNode]; @@ -199,7 +199,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _elapsedTextNode = [[ASTextNode alloc] init]; _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00" forControlType:ASVideoPlayerNodeControlTypeElapsedText]; - [_cachedControls addObject:_elapsedTextNode]; + [_cachedControls setObject:_elapsedTextNode forKey:@(ASVideoPlayerNodeControlTypeElapsedText)]; } [self addSubnode:_elapsedTextNode]; } @@ -210,7 +210,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _durationTextNode = [[ASTextNode alloc] init]; _durationTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00" forControlType:ASVideoPlayerNodeControlTypeDurationText]; - [_cachedControls addObject:_durationTextNode]; + [_cachedControls setObject:_durationTextNode forKey:@(ASVideoPlayerNodeControlTypeDurationText)]; } [self addSubnode:_durationTextNode]; } @@ -250,7 +250,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _scrubberNode.flexShrink = YES; - [_cachedControls addObject:_scrubberNode]; + [_cachedControls setObject:_scrubberNode forKey:@(ASVideoPlayerNodeControlTypeScrubber)]; } [self addSubnode:_scrubberNode]; @@ -263,7 +263,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _controlFlexGrowSpacerSpec.flexGrow = YES; } - [_cachedControls addObject:_controlFlexGrowSpacerSpec]; + [_cachedControls setObject:_controlFlexGrowSpacerSpec forKey:@(ASVideoPlayerNodeControlTypeFlexGrowSpacer)]; } - (void)updateDurationTimeLabel @@ -360,20 +360,51 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } +- (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 { _videoNode.preferredFrameSize = constrainedSize.max; - _scrubberNode.preferredFrameSize = CGSizeMake(constrainedSize.max.width, 44.0); - //TODO:: here wi will ask delegate for it's own ASLayoutSpec - // something like layoutSpecForVideoPlayerNode:forConstrainedsize:forControls - return [self defaultLayoutSpecThatFits:constrainedSize]; + ASLayoutSpec *layoutSpec; + + if (_delegateFlags.delegateLayoutSpecForControls) { + layoutSpec = [_delegate videoPlayerNodeLayoutSpec:self forControls:_cachedControls forConstrainedSize:constrainedSize]; + } else { + layoutSpec = [self defaultLayoutSpecThatFits:constrainedSize]; + } + + ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec]; + + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[overlaySpec]]; } - (ASLayoutSpec*)defaultLayoutSpecThatFits:(ASSizeRange)constrainedSize { + _scrubberNode.preferredFrameSize = CGSizeMake(constrainedSize.max.width, 44.0); + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; spacer.flexGrow = YES; @@ -381,7 +412,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; spacing:10.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsCenter - children:_cachedControls]; + children: [self controlsForLayoutSpec] ]; controlbarSpec.alignSelf = ASStackLayoutAlignSelfStretch; UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); @@ -396,10 +427,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; alignItems:ASStackLayoutAlignItemsStart children:@[spacer,controlbarInsetSpec]]; - - ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:mainVerticalStack]; - - return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[overlaySpec]]; + return mainVerticalStack; } #pragma mark - Properties @@ -420,6 +448,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _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:forConstrainedSize:)]; } } diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index 723e0a5af7..0f831e12d5 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -77,12 +77,57 @@ - (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayerNode timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType { - NSDictionary *options = @{ - NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:13.0], - NSForegroundColorAttributeName: [UIColor orangeColor] - }; + NSDictionary *options; + + if (timeLabelType == ASVideoPlayerNodeControlTypeElapsedText) { + options = @{ + NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:13.0], + NSForegroundColorAttributeName: [UIColor orangeColor] + }; + } else if (timeLabelType == ASVideoPlayerNodeControlTypeDurationText) { + options = @{ + NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:13.0], + NSForegroundColorAttributeName: [UIColor redColor] + }; + } return options; } +- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer + forControls:(NSDictionary *)controls + forConstrainedSize:(ASSizeRange)constrainedSize +{ + ASDisplayNode *scrubber = controls[@(ASVideoPlayerNodeControlTypeScrubber)]; + if (scrubber) { + scrubber.preferredFrameSize = CGSizeMake(constrainedSize.max.width, 44.0); + } + + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.flexGrow = YES; + + ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:10.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children: @[scrubber] ]; + 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; +} + + @end \ No newline at end of file From e0074524c3cf503a26ce11bbbec65e53f897f991 Mon Sep 17 00:00:00 2001 From: Erekle Date: Thu, 12 May 2016 12:59:32 +0400 Subject: [PATCH 10/26] new ASVideoPlayerNodeDelegate methods. comments in header --- AsyncDisplayKit/ASVideoNode.h | 2 +- AsyncDisplayKit/ASVideoPlayerNode.h | 59 +++++++++++++++++++++-- AsyncDisplayKit/ASVideoPlayerNode.mm | 36 ++++++++++++-- examples/ASDKTube/Sample/ViewController.m | 1 - 4 files changed, 89 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 14120a2eda..e998c04bdf 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -85,7 +85,7 @@ NS_ASSUME_NONNULL_BEGIN * @param toSate player new state. * @discussion This method is called after each state change */ -- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toSate; +- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState; /** * @abstract Ssks delegate if state change is allowed * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused. diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 7373fb9f49..f3a9502d49 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -39,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN @end +#pragma mark - ASVideoPlayerNodeDelegate - @protocol ASVideoPlayerNodeDelegate @optional /** @@ -46,16 +47,66 @@ NS_ASSUME_NONNULL_BEGIN * @param videoPlayer */ - (NSArray *)videoPlayerNodeNeededControls:(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 constrainedSize - ASSizeRange 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 + forConstrainedSize:(ASSizeRange)constrainedSize; + +#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; -#pragma mark - Scrubber delegate methods +#pragma mark Scrubber delegate methods - (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer; - (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer; - (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer; - (UIImage *)videoPlayerNodeScrubberThumbImage:(ASVideoPlayerNode *)videoPlayer; -- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer - forControls:(NSDictionary *)controls - forConstrainedSize:(ASSizeRange)constrainedSize; + + +#pragma mark ASVideoNodeDelegate proxy methods +/** + * @abstract Delegate method invoked when ASVideoNode playback time is updated. + * @param videoPlayerNode The video node that was tapped. + * @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)toSate; + +/** + * @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)videoPlayerNodePlaybackDidFinish:(ASVideoPlayerNode *)videoPlayer; + @end NS_ASSUME_NONNULL_END #endif diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 5bbbf05373..ff2d825f87 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -24,6 +24,10 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; unsigned int delegateScrubberThumbImage:1; unsigned int delegateTimeLabelAttributes:1; unsigned int delegateLayoutSpecForControls:1; + unsigned int delegateVideoNodeDidPlayToTime:1; + unsigned int delegateVideoNodeWillChangeState:1; + unsigned int delegateVideoNodeShouldChangeState:1; + unsigned int delegateVideoNodePlaybackDidFinish:1; } _delegateFlags; NSURL *_url; @@ -297,17 +301,36 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } #pragma mark - ASVideoNodeDelegate -- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toSate +- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState { - if (toSate == ASVideoNodePlayerStateReadyToPlay) { + if (_delegateFlags.delegateVideoNodeWillChangeState) { + [_delegate videoPlayerNode:self willChangeVideoNodeState:state toVideoNodeState:toState]; + } + + if (toState == ASVideoNodePlayerStateReadyToPlay) { _duration = _videoNode.currentItem.duration; [self updateDurationTimeLabel]; } } +- (BOOL)videoNode:(ASVideoNode *)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state +{ + if (_delegateFlags.delegateVideoNodeShouldChangeState) { + return [_delegate videoPlayerNode:self shouldChangeVideoNodeStateTo:state]; + } + return YES; +} + - (void)videoNode:(ASVideoNode *)videoNode didPlayToSecond:(NSTimeInterval)second { - if(_isSeeking){ + //TODO: ask Max about CMTime problem in ASVideoNode Header file + //as we said yesterday, we must use CMTime in ASVideoNode instead of NSTimeInterval + //when this will be done, must just proxy value to delegate + if (_delegateFlags.delegateVideoNodeDidPlayToTime) { + [_delegate videoPlayerNode:self didPlayToTime:_videoNode.player.currentTime]; + } + + if (_isSeeking) { return; } @@ -317,6 +340,9 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode { + if (_delegateFlags.delegateVideoNodePlaybackDidFinish) { + [_delegate videoPlayerNodePlaybackDidFinish:self]; + } //[self removeControls]; } @@ -449,6 +475,10 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _delegateFlags.delegateScrubberThumbImage = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbImage:)]; _delegateFlags.delegateTimeLabelAttributes = [_delegate respondsToSelector:@selector(videoPlayerNodeTimeLabelAttributes:timeLabelType:)]; _delegateFlags.delegateLayoutSpecForControls = [_delegate respondsToSelector:@selector(videoPlayerNodeLayoutSpec:forControls:forConstrainedSize:)]; + _delegateFlags.delegateVideoNodeDidPlayToTime = [_delegate respondsToSelector:@selector(videoPlayerNode:didPlayToTime:)]; + _delegateFlags.delegateVideoNodeWillChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:willChangeVideoNodeState:toVideoNodeState:)]; + _delegateFlags.delegateVideoNodePlaybackDidFinish = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackDidFinish:)]; + _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)]; } } diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index 0f831e12d5..6087e9d95f 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -129,5 +129,4 @@ return mainVerticalStack; } - @end \ No newline at end of file From ae5440ad1163aaa36ff97b1505f6a8dd9d600e51 Mon Sep 17 00:00:00 2001 From: Erekle Date: Thu, 12 May 2016 13:26:39 +0400 Subject: [PATCH 11/26] updated example --- AsyncDisplayKit/ASVideoPlayerNode.mm | 5 ++- examples/ASDKTube/Sample/ViewController.m | 48 +++++++++++++++++++---- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index ff2d825f87..d01fc6d914 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -485,6 +485,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; #pragma mark - Helpers - (NSString *)timeStringForCMTime:(CMTime)time { + //TODO: ask Max if delegate for this thing will be useful; NSUInteger dTotalSeconds = CMTimeGetSeconds(time); NSUInteger dHours = floor(dTotalSeconds / 3600); @@ -493,9 +494,9 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; NSString *videoDurationText; if (dHours > 0) { - videoDurationText = [NSString stringWithFormat:@"%i:%01i:%02i", (int)dHours, (int)dMinutes, (int)dSeconds]; + videoDurationText = [NSString stringWithFormat:@"%i:%02i:%02i", (int)dHours, (int)dMinutes, (int)dSeconds]; } else { - videoDurationText = [NSString stringWithFormat:@"%01i:%02i", (int)dMinutes, (int)dSeconds]; + videoDurationText = [NSString stringWithFormat:@"%02i:%02i", (int)dMinutes, (int)dSeconds]; } return videoDurationText; } diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index 6087e9d95f..9b11e80f59 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -81,12 +81,12 @@ if (timeLabelType == ASVideoPlayerNodeControlTypeElapsedText) { options = @{ - NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:13.0], + NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:16.0], NSForegroundColorAttributeName: [UIColor orangeColor] }; } else if (timeLabelType == ASVideoPlayerNodeControlTypeDurationText) { options = @{ - NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:13.0], + NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:16.0], NSForegroundColorAttributeName: [UIColor redColor] }; } @@ -98,19 +98,53 @@ forControls:(NSDictionary *)controls forConstrainedSize:(ASSizeRange)constrainedSize { - ASDisplayNode *scrubber = controls[@(ASVideoPlayerNodeControlTypeScrubber)]; - if (scrubber) { - scrubber.preferredFrameSize = CGSizeMake(constrainedSize.max.width, 44.0); + + 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: @[scrubber] ]; + children:bottomControls]; controlbarSpec.alignSelf = ASStackLayoutAlignSelfStretch; UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); @@ -123,7 +157,7 @@ spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart - children:@[spacer,controlbarInsetSpec]]; + children:@[ topBarSpecInsetSpec, spacer, controlbarInsetSpec ]]; return mainVerticalStack; From 98c451118b157f8e5f2ed8f1f2723f496839438c Mon Sep 17 00:00:00 2001 From: Erekle Date: Fri, 13 May 2016 00:46:55 +0400 Subject: [PATCH 12/26] removed play button * Removed play button from ASVideoNode * Removed play button tests * added some checks in ASVideoPlayerNode * added new delegate method for time formats --- AsyncDisplayKit/ASVideoNode.h | 3 -- AsyncDisplayKit/ASVideoNode.mm | 59 +++---------------------- AsyncDisplayKit/ASVideoPlayerNode.h | 3 ++ AsyncDisplayKit/ASVideoPlayerNode.mm | 39 ++++++++++------ AsyncDisplayKitTests/ASVideoNodeTests.m | 15 ------- 5 files changed, 34 insertions(+), 85 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index e998c04bdf..a4ce9cdaf0 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -58,9 +58,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 e58ced6ca4..8c305559cc 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -63,8 +63,7 @@ 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; @@ -83,8 +82,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]; @@ -201,10 +199,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]; @@ -224,18 +219,6 @@ static NSString * const kStatus = @"status"; 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; @@ -421,26 +404,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); @@ -537,11 +500,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]; } @@ -549,10 +509,7 @@ static NSString * const kStatus = @"status"; [_player play]; _shouldBePlaying = YES; - - [UIView animateWithDuration:0.15 animations:^{ - _playButtonNode.alpha = 0.0; - }]; + if (![self ready]) { [self showSpinner]; } else { @@ -605,9 +562,6 @@ static NSString * const kStatus = @"status"; [_player pause]; [self removeSpinner]; _shouldBePlaying = NO; - [UIView animateWithDuration:0.15 animations:^{ - _playButtonNode.alpha = 1.0; - }]; } - (BOOL)isPlaying @@ -733,7 +687,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 index f3a9502d49..1183a53467 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -66,6 +66,9 @@ NS_ASSUME_NONNULL_BEGIN * @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; diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index d01fc6d914..a4d17bde9a 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -23,6 +23,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; 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; @@ -176,12 +177,13 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)removeControls { -// [_cachedControls enumerateObjectsUsingBlock:^(ASDisplayNode *_Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { -// [node.view removeFromSuperview]; -// [node removeFromSupernode]; -// node = nil; -// NSLog(@"%@",_playbackButtonNode); -// }]; + NSArray *controls = [_cachedControls allValues]; + [controls enumerateObjectsUsingBlock:^(ASDisplayNode *_Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { + [node.view removeFromSuperview]; + [node removeFromSupernode]; + //node = nil; + NSLog(@"%@",_playbackButtonNode); + }]; } - (void)createPlaybackButton @@ -272,13 +274,13 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)updateDurationTimeLabel { - NSString *formatedDuration = [self timeStringForCMTime:_duration]; + NSString *formatedDuration = [self timeStringForCMTime:_duration forTimeLabelType:ASVideoPlayerNodeControlTypeDurationText]; _durationTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration forControlType:ASVideoPlayerNodeControlTypeDurationText]; } - (void)updateElapsedTimeLabel:(NSTimeInterval)seconds { - NSString *formatedDuration = [self timeStringForCMTime:CMTimeMakeWithSeconds( seconds, _videoNode.periodicTimeObserverTimescale )]; + NSString *formatedDuration = [self timeStringForCMTime:CMTimeMakeWithSeconds( seconds, _videoNode.periodicTimeObserverTimescale ) forTimeLabelType:ASVideoPlayerNodeControlTypeElapsedText]; _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration forControlType:ASVideoPlayerNodeControlTypeElapsedText]; } @@ -307,7 +309,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; [_delegate videoPlayerNode:self willChangeVideoNodeState:state toVideoNodeState:toState]; } - if (toState == ASVideoNodePlayerStateReadyToPlay) { + if (toState == ASVideoNodePlayerStateReadyToPlay && _durationTextNode) { _duration = _videoNode.currentItem.duration; [self updateDurationTimeLabel]; } @@ -334,8 +336,13 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return; } - [self updateElapsedTimeLabel:second]; - [(UISlider*)_scrubberNode.view setValue:(second/ CMTimeGetSeconds(_duration) ) animated:NO]; + if (_elapsedTextNode) { + [self updateElapsedTimeLabel:second]; + } + + if (_scrubberNode) { + [(UISlider*)_scrubberNode.view setValue:(second/ CMTimeGetSeconds(_duration) ) animated:NO]; + } } - (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode @@ -343,7 +350,6 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; if (_delegateFlags.delegateVideoNodePlaybackDidFinish) { [_delegate videoPlayerNodePlaybackDidFinish:self]; } - //[self removeControls]; } #pragma mark - Actions @@ -423,6 +429,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec]; + overlaySpec.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(constrainedSize.max); return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[overlaySpec]]; } @@ -479,13 +486,17 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _delegateFlags.delegateVideoNodeWillChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:willChangeVideoNodeState:toVideoNodeState:)]; _delegateFlags.delegateVideoNodePlaybackDidFinish = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackDidFinish:)]; _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)]; + _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; } } #pragma mark - Helpers -- (NSString *)timeStringForCMTime:(CMTime)time +- (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type { - //TODO: ask Max if delegate for this thing will be useful; + if (_delegateFlags.delegateTimeLabelAttributedString) { + return [_delegate videoPlayerNode:self timeStringForTimeLabelType:type forTime:time]; + } + NSUInteger dTotalSeconds = CMTimeGetSeconds(time); NSUInteger dHours = floor(dTotalSeconds / 3600); diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 6530edd4f6..eaa7e63a5b 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -379,21 +379,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]; From db95771b330a9265ce92bb938945ade5481948cc Mon Sep 17 00:00:00 2001 From: Erekle Date: Fri, 13 May 2016 01:49:46 +0400 Subject: [PATCH 13/26] enable/disable controls --- AsyncDisplayKit/ASVideoPlayerNode.h | 4 +- AsyncDisplayKit/ASVideoPlayerNode.mm | 51 ++++++++++++++++++----- examples/ASDKTube/Sample/ViewController.m | 5 +++ 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 1183a53467..0220f82912 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -31,6 +31,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic,assign,readonly) CMTime duration; +@property (nonatomic, assign) BOOL disableControls; + - (instancetype)initWithUrl:(NSURL*)url; - (instancetype)initWithAsset:(AVAsset*)asset; @@ -108,7 +110,7 @@ NS_ASSUME_NONNULL_BEGIN * @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)videoPlayerNodePlaybackDidFinish:(ASVideoPlayerNode *)videoPlayer; +- (void)videoPlayerNodeDidPlayToEnd:(ASVideoPlayerNode *)videoPlayer; @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index a4d17bde9a..7b77824253 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -35,8 +35,6 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; AVAsset *_asset; ASVideoNode *_videoNode; - - ASDisplayNode *_controlsHolderNode; NSArray *_neededControls; @@ -51,6 +49,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; BOOL _isSeeking; CMTime _duration; + BOOL _disableControls; } @end @@ -88,6 +87,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } _asset = asset; + _disableControls = NO; [self privateInit]; @@ -104,10 +104,6 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _videoNode.delegate = self; [self addSubnode:_videoNode]; - _controlsHolderNode = [[ASDisplayNode alloc] init]; - _controlsHolderNode.backgroundColor = [UIColor greenColor]; - [self addSubnode:_controlsHolderNode]; - [self addObservers]; } @@ -147,10 +143,18 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; { ASDN::MutexLocker l(_videoPlayerLock); + if (_disableControls) { + return; + } + if (_neededControls == nil) { _neededControls = [self createControlElementArray]; } + if (_cachedControls == nil) { + _cachedControls = [[NSMutableDictionary alloc] init]; + } + for (int i = 0; i < _neededControls.count; i++) { ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[[_neededControls objectAtIndex:i] integerValue]; switch (type) { @@ -173,17 +177,31 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; break; } } + + ASPerformBlockOnMainThread(^{ + ASDN::MutexLocker l(_videoPlayerLock); + [self setNeedsLayout]; + }); } - (void)removeControls { NSArray *controls = [_cachedControls allValues]; [controls enumerateObjectsUsingBlock:^(ASDisplayNode *_Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { - [node.view removeFromSuperview]; [node removeFromSupernode]; - //node = nil; - NSLog(@"%@",_playbackButtonNode); }]; + + [self cleanCachedControls]; +} + +- (void)cleanCachedControls +{ + [_cachedControls removeAllObjects]; + + _playbackButtonNode = nil; + _elapsedTextNode = nil; + _durationTextNode = nil; + _scrubberNode = nil; } - (void)createPlaybackButton @@ -348,7 +366,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode { if (_delegateFlags.delegateVideoNodePlaybackDidFinish) { - [_delegate videoPlayerNodePlaybackDidFinish:self]; + [_delegate videoPlayerNodeDidPlayToEnd:self]; } } @@ -484,12 +502,23 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _delegateFlags.delegateLayoutSpecForControls = [_delegate respondsToSelector:@selector(videoPlayerNodeLayoutSpec:forControls:forConstrainedSize:)]; _delegateFlags.delegateVideoNodeDidPlayToTime = [_delegate respondsToSelector:@selector(videoPlayerNode:didPlayToTime:)]; _delegateFlags.delegateVideoNodeWillChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:willChangeVideoNodeState:toVideoNodeState:)]; - _delegateFlags.delegateVideoNodePlaybackDidFinish = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackDidFinish:)]; + _delegateFlags.delegateVideoNodePlaybackDidFinish = [_delegate respondsToSelector:@selector(videoPlayerNodeDidPlayToEnd:)]; _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)]; _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; } } +- (void)setDisableControls:(BOOL)disableControls +{ + _disableControls = disableControls; + + if (_disableControls && _cachedControls.count > 0) { + [self removeControls]; + } else if (!_disableControls) { + [self createControls]; + } +} + #pragma mark - Helpers - (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type { diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index 9b11e80f59..0e0e038fa6 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -45,6 +45,11 @@ _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]; From 658d376d7b25f800df4c5d22f974224f592a66a1 Mon Sep 17 00:00:00 2001 From: Erekle Date: Fri, 13 May 2016 02:14:12 +0400 Subject: [PATCH 14/26] ASVideoNode public methods proxy --- AsyncDisplayKit/ASVideoPlayerNode.h | 5 ++++- AsyncDisplayKit/ASVideoPlayerNode.mm | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 0220f82912..fcf3c6a2c8 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -37,7 +37,10 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithAsset:(AVAsset*)asset; #pragma mark - Public API --(void)seekToTime:(CGFloat)percentComplete; +- (void)seekToTime:(CGFloat)percentComplete; +- (void)play; +- (void)pause; +- (BOOL)isPlaying; @end diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 7b77824253..c2bc038bb2 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -398,7 +398,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; [self seekToTime:percentage]; } --(void)seekToTime:(CGFloat)percentComplete +#pragma mark - Public API +- (void)seekToTime:(CGFloat)percentComplete { CGFloat seconds = ( CMTimeGetSeconds(_duration) * percentComplete ) / 100; @@ -410,6 +411,21 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } +- (void)play +{ + [_videoNode play]; +} + +- (void)pause +{ + [_videoNode pause]; +} + +- (BOOL)isPlaying +{ + return [_videoNode isPlaying]; +} + - (NSArray *)controlsForLayoutSpec { NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:_cachedControls.count]; From 3e45f41710fe943563a0a51bd3dd3bfb5041b489 Mon Sep 17 00:00:00 2001 From: Erekle Date: Fri, 13 May 2016 18:43:24 +0400 Subject: [PATCH 15/26] ASVideoNode property proxy --- AsyncDisplayKit/ASVideoPlayerNode.h | 14 ++++++++ AsyncDisplayKit/ASVideoPlayerNode.mm | 49 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index fcf3c6a2c8..010e31bcb2 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -33,6 +33,20 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL disableControls; +#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; +//! Defaults to 100 +@property (nonatomic, assign) int32_t periodicTimeObserverTimescale; +//! Defaults to AVLayerVideoGravityResizeAspect +@property (atomic) NSString *gravity; + - (instancetype)initWithUrl:(NSURL*)url; - (instancetype)initWithAsset:(AVAsset*)asset; diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index c2bc038bb2..e84665c7bc 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -50,6 +50,12 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; CMTime _duration; BOOL _disableControls; + + BOOL _shouldAutoplay; + BOOL _shouldAutorepeat; + BOOL _muted; + int32_t _periodicTimeObserverTimescale; + NSString *_gravity; } @end @@ -535,6 +541,49 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } +- (void)setShouldAutoplay:(BOOL)shouldAutoplay +{ + _shouldAutoplay = shouldAutoplay; + _videoNode.shouldAutoplay = _shouldAutoplay; +} + +- (void)setShouldAutorepeat:(BOOL)shouldAutorepeat +{ + _shouldAutorepeat = shouldAutorepeat; + _videoNode.shouldAutorepeat = YES; +} + +- (void)setMuted:(BOOL)muted +{ + _muted = muted; + _videoNode.muted = _muted; +} + +- (void)setPeriodicTimeObserverTimescale:(int32_t)periodicTimeObserverTimescale +{ + _periodicTimeObserverTimescale = periodicTimeObserverTimescale; + _videoNode.periodicTimeObserverTimescale = _periodicTimeObserverTimescale; +} + +- (NSString*)gravity +{ + if (_gravity == nil) { + _gravity = _videoNode.gravity; + } + return _gravity; +} + +- (void)setGravity:(NSString *)gravity +{ + _gravity = gravity; + _videoNode.gravity = _gravity; +} + +- (ASVideoNodePlayerState)playerState +{ + return _videoNode.playerState; +} + #pragma mark - Helpers - (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type { From e30584d21533a016bce3dc6e660ac6987349a216 Mon Sep 17 00:00:00 2001 From: Erekle Date: Sat, 14 May 2016 16:41:32 +0400 Subject: [PATCH 16/26] ASDefaultPlaybackButton --- AsyncDisplayKit.xcodeproj/project.pbxproj | 10 ++- AsyncDisplayKit/ASVideoPlayerNode.h | 3 + AsyncDisplayKit/ASVideoPlayerNode.mm | 23 ++++-- .../Private/ASDefaultPlaybackButton.h | 16 ++++ .../Private/ASDefaultPlaybackButton.m | 81 +++++++++++++++++++ examples/ASDKTube/Sample/ViewController.m | 4 +- 6 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 AsyncDisplayKit/Private/ASDefaultPlaybackButton.h create mode 100644 AsyncDisplayKit/Private/ASDefaultPlaybackButton.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index f9e68c87cf..54eb504046 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -311,6 +311,8 @@ 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 */; }; 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 */; }; @@ -824,6 +826,8 @@ 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 = ""; }; @@ -1340,6 +1344,8 @@ CC3B20881C3F7A5400798563 /* ASWeakSet.m */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, + 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */, + 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */, ); path = Private; sourceTree = ""; @@ -1600,6 +1606,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 */, @@ -2014,6 +2021,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 */, @@ -2119,8 +2127,6 @@ 058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */, 257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */, 8BDA5FC61CDBDDE1007D13B2 /* ASVideoPlayerNode.mm in Sources */, - ACC945AB1BA9E7C1005E1FB8 /* ASViewController.m in Sources */, - B0F8805B1BEAEC7500D17647 /* ASTableNode.m in Sources */, 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */, 257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */, 9CFFC6BE1CCAC52B006A6476 /* ASEnvironment.mm in Sources */, diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 010e31bcb2..32d771510f 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -95,6 +95,9 @@ NS_ASSUME_NONNULL_BEGIN - (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer; - (UIImage *)videoPlayerNodeScrubberThumbImage:(ASVideoPlayerNode *)videoPlayer; +#pragma mark - Playback button delegate methods +- (UIColor *)videoPlayerNodePlaybackButtonTint:(ASVideoPlayerNode *)videoPlayer; + #pragma mark ASVideoNodeDelegate proxy methods /** diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index e84665c7bc..c974a5e18f 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -7,6 +7,7 @@ // #import "ASVideoPlayerNode.h" +#import "ASDefaultPlaybackButton.h" static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; @@ -18,6 +19,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; struct { unsigned int delegateNeededControls:1; + unsigned int delegatePlaybackButtonTint:1; unsigned int delegateScrubberMaximumTrackTintColor:1; unsigned int delegateScrubberMinimumTrackTintColor:1; unsigned int delegateScrubberThumbTintColor:1; @@ -40,7 +42,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; NSMutableDictionary *_cachedControls; - ASControlNode *_playbackButtonNode; + ASDefaultPlaybackButton *_playbackButtonNode; ASTextNode *_elapsedTextNode; ASTextNode *_durationTextNode; ASDisplayNode *_scrubberNode; @@ -213,9 +215,13 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)createPlaybackButton { if (_playbackButtonNode == nil) { - _playbackButtonNode = [[ASControlNode alloc] init]; - _playbackButtonNode.preferredFrameSize = CGSizeMake(20.0, 20.0); - _playbackButtonNode.backgroundColor = [UIColor redColor]; + _playbackButtonNode = [[ASDefaultPlaybackButton alloc] init]; + _playbackButtonNode.preferredFrameSize = CGSizeMake(16.0, 22.0); + if (_delegateFlags.delegatePlaybackButtonTint) { + _playbackButtonNode.tintColor = [_delegate videoPlayerNodePlaybackButtonTint:self]; + } else { + _playbackButtonNode.tintColor = [UIColor whiteColor]; + } [_playbackButtonNode addTarget:self action:@selector(playbackButtonTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; [_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)]; } @@ -337,6 +343,12 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _duration = _videoNode.currentItem.duration; [self updateDurationTimeLabel]; } + + if (toState == ASVideoNodePlayerStatePlaying) { + _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; + } else { + _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePlay; + } } - (BOOL)videoNode:(ASVideoNode *)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state @@ -381,10 +393,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; { if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { [_videoNode pause]; - _playbackButtonNode.backgroundColor = [UIColor greenColor]; } else { [_videoNode play]; - _playbackButtonNode.backgroundColor = [UIColor redColor]; } } @@ -527,6 +537,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _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:)]; } } 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..a3b006e1d5 --- /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/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index 0e0e038fa6..b1d44e2510 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -99,7 +99,7 @@ return options; } -- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer +/*- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer forControls:(NSDictionary *)controls forConstrainedSize:(ASSizeRange)constrainedSize { @@ -166,6 +166,6 @@ return mainVerticalStack; -} +}*/ @end \ No newline at end of file From f30bf32a472ec4aba6f073d7ca3991faab79b5d3 Mon Sep 17 00:00:00 2001 From: Erekle Date: Sat, 14 May 2016 17:01:06 +0400 Subject: [PATCH 17/26] minor fixis --- AsyncDisplayKit/ASVideoPlayerNode.mm | 7 +++++-- AsyncDisplayKit/Private/ASDefaultPlaybackButton.m | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index c974a5e18f..8a30f2b1a4 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -58,6 +58,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; BOOL _muted; int32_t _periodicTimeObserverTimescale; NSString *_gravity; + + UIColor *_defaultControlsColor; } @end @@ -105,6 +107,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)privateInit { + _defaultControlsColor = [UIColor whiteColor]; _cachedControls = [[NSMutableDictionary alloc] init]; _videoNode = [[ASVideoNode alloc] init]; @@ -220,7 +223,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; if (_delegateFlags.delegatePlaybackButtonTint) { _playbackButtonNode.tintColor = [_delegate videoPlayerNodePlaybackButtonTint:self]; } else { - _playbackButtonNode.tintColor = [UIColor whiteColor]; + _playbackButtonNode.tintColor = _defaultControlsColor; } [_playbackButtonNode addTarget:self action:@selector(playbackButtonTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; [_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)]; @@ -322,7 +325,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } else { options = @{ NSFontAttributeName : [UIFont systemFontOfSize:12.0], - NSForegroundColorAttributeName: [UIColor whiteColor] + NSForegroundColorAttributeName: _defaultControlsColor }; } diff --git a/AsyncDisplayKit/Private/ASDefaultPlaybackButton.m b/AsyncDisplayKit/Private/ASDefaultPlaybackButton.m index a3b006e1d5..6e9455bde5 100644 --- a/AsyncDisplayKit/Private/ASDefaultPlaybackButton.m +++ b/AsyncDisplayKit/Private/ASDefaultPlaybackButton.m @@ -38,9 +38,9 @@ - (nullable id)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { return @{ - @"buttonType" : [NSNumber numberWithInt:_buttonType], - @"color" : self.tintColor - }; + @"buttonType" : [NSNumber numberWithInt:_buttonType], + @"color" : self.tintColor + }; } + (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing From e1544f0f59566a7b753371b3a08553bbf4f74fd1 Mon Sep 17 00:00:00 2001 From: Erekle Date: Mon, 16 May 2016 11:33:46 +0400 Subject: [PATCH 18/26] minor fixes. ensure size in layoutSpecThatFits: --- AsyncDisplayKit/ASVideoPlayerNode.h | 4 ++-- AsyncDisplayKit/ASVideoPlayerNode.mm | 33 ++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 32d771510f..ec01c206a0 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -71,12 +71,12 @@ NS_ASSUME_NONNULL_BEGIN * @abstract Delegate method invoked in layoutSpecThatFits: * @param videoPlayer * @param controls - Dictionary of controls which are used in videoPlayer; Dictionary keys are ASVideoPlayerNodeControlType - * @param constrainedSize - ASSizeRange for ASVideoPlayerNode + * @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 - forConstrainedSize:(ASSizeRange)constrainedSize; + forMaximumSize:(CGSize)maxSize; #pragma mark Text delegate methods /** diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 8a30f2b1a4..7787e4d470 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -391,6 +391,15 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } +- (void)videoNodeWasTapped:(ASVideoNode *)videoNode +{ + if (videoNode.playerState == ASVideoNodePlayerStatePlaying) { + [videoNode pause]; + } else { + [videoNode play]; + } +} + #pragma mark - Actions - (void)playbackButtonTapped:(ASControlNode*)node { @@ -471,25 +480,35 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; #pragma mark - Layout - (ASLayoutSpec*)layoutSpecThatFits:(ASSizeRange)constrainedSize { - _videoNode.preferredFrameSize = constrainedSize.max; + 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 forConstrainedSize:constrainedSize]; + layoutSpec = [_delegate videoPlayerNodeLayoutSpec:self forControls:_cachedControls forMaximumSize:maxSize]; } else { - layoutSpec = [self defaultLayoutSpecThatFits:constrainedSize]; + layoutSpec = [self defaultLayoutSpecThatFits:maxSize]; } ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec]; - overlaySpec.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(constrainedSize.max); + overlaySpec.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(maxSize); return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[overlaySpec]]; } -- (ASLayoutSpec*)defaultLayoutSpecThatFits:(ASSizeRange)constrainedSize +- (ASLayoutSpec*)defaultLayoutSpecThatFits:(CGSize)maxSize { - _scrubberNode.preferredFrameSize = CGSizeMake(constrainedSize.max.width, 44.0); + _scrubberNode.preferredFrameSize = CGSizeMake(maxSize.width, 44.0); ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; spacer.flexGrow = YES; @@ -534,7 +553,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _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:forConstrainedSize:)]; + _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:)]; From 7872d85ab0aabd7f797fb885b7f159ab1db4973e Mon Sep 17 00:00:00 2001 From: Erekle Date: Tue, 17 May 2016 00:01:14 +0400 Subject: [PATCH 19/26] tap proxy --- AsyncDisplayKit/ASVideoPlayerNode.h | 5 +++++ AsyncDisplayKit/ASVideoPlayerNode.mm | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index ec01c206a0..41c22eceea 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -100,6 +100,11 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark ASVideoNodeDelegate proxy methods +/** + * @abstract Delegate method invoked when ASVideoPlayerNode playback time is taped. + * @param videoPlayerNode The ASVideoPlayerNode that was tapped. + */ +- (void)videoPlayerNodeWasTapped:(ASVideoPlayerNode *)videoPlayer; /** * @abstract Delegate method invoked when ASVideoNode playback time is updated. * @param videoPlayerNode The video node that was tapped. diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 7787e4d470..e4d3482a40 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -31,6 +31,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; unsigned int delegateVideoNodeWillChangeState:1; unsigned int delegateVideoNodeShouldChangeState:1; unsigned int delegateVideoNodePlaybackDidFinish:1; + unsigned int delegateVideoNodeTapped:1; } _delegateFlags; NSURL *_url; @@ -393,10 +394,14 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)videoNodeWasTapped:(ASVideoNode *)videoNode { - if (videoNode.playerState == ASVideoNodePlayerStatePlaying) { - [videoNode pause]; + if (_delegateFlags.delegateVideoNodeTapped) { + [_delegate videoPlayerNodeWasTapped:self]; } else { - [videoNode play]; + if (videoNode.playerState == ASVideoNodePlayerStatePlaying) { + [videoNode pause]; + } else { + [videoNode play]; + } } } @@ -560,6 +565,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)]; _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; _delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)]; + _delegateFlags.delegateVideoNodeTapped = [_delegate respondsToSelector:@selector(videoPlayerNodeWasTapped:)]; } } From d9e1e7cc0a4e8f76bcc586a1b49b058574c2e4b3 Mon Sep 17 00:00:00 2001 From: Erekle Date: Tue, 17 May 2016 01:26:40 +0400 Subject: [PATCH 20/26] example --- .../ASDKTube/Sample.xcodeproj/project.pbxproj | 54 ++++ examples/ASDKTube/Sample/AppDelegate.m | 34 ++- .../Controller/VideoFeedNodeController.h | 13 + .../Controller/VideoFeedNodeController.m | 71 ++++++ examples/ASDKTube/Sample/Info.plist | 6 +- examples/ASDKTube/Sample/Models/Utilities.h | 40 +++ examples/ASDKTube/Sample/Models/Utilities.m | 230 ++++++++++++++++++ examples/ASDKTube/Sample/Models/VideoModel.h | 16 ++ examples/ASDKTube/Sample/Models/VideoModel.m | 27 ++ .../ASDKTube/Sample/Nodes/VideoContentCell.h | 14 ++ .../ASDKTube/Sample/Nodes/VideoContentCell.m | 151 ++++++++++++ examples/ASDKTube/Sample/ViewController.h | 2 +- examples/ASDKTube/Sample/ViewController.m | 130 ++++++---- .../Sample/WindowWithStatusBarUnderlay.h | 13 + .../Sample/WindowWithStatusBarUnderlay.m | 39 +++ .../ASDKgram/Sample.xcodeproj/project.pbxproj | 18 +- .../contents.xcworkspacedata | 10 + 17 files changed, 804 insertions(+), 64 deletions(-) create mode 100644 examples/ASDKTube/Sample/Controller/VideoFeedNodeController.h create mode 100644 examples/ASDKTube/Sample/Controller/VideoFeedNodeController.m create mode 100644 examples/ASDKTube/Sample/Models/Utilities.h create mode 100644 examples/ASDKTube/Sample/Models/Utilities.m create mode 100644 examples/ASDKTube/Sample/Models/VideoModel.h create mode 100644 examples/ASDKTube/Sample/Models/VideoModel.m create mode 100644 examples/ASDKTube/Sample/Nodes/VideoContentCell.h create mode 100644 examples/ASDKTube/Sample/Nodes/VideoContentCell.m create mode 100644 examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.h create mode 100644 examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.m create mode 100644 examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata diff --git a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj index e9965d6274..f808ae18f9 100644 --- a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj @@ -14,6 +14,11 @@ 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 */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -27,6 +32,16 @@ 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 = ""; }; 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 = ""; }; @@ -68,11 +83,16 @@ 05E2128319D4DB510098F589 /* Sample */ = { isa = PBXGroup; children = ( + 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 = ""; @@ -106,6 +126,35 @@ 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 = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -228,8 +277,13 @@ 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; }; diff --git a/examples/ASDKTube/Sample/AppDelegate.m b/examples/ASDKTube/Sample/AppDelegate.m index a8e5594780..947d95ca09 100644 --- a/examples/ASDKTube/Sample/AppDelegate.m +++ b/examples/ASDKTube/Sample/AppDelegate.m @@ -10,18 +10,36 @@ */ #import "AppDelegate.h" +#import "WindowWithStatusBarUnderlay.h" +#import "Utilities.h" +#import "VideoFeedNodeController.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]; +- (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/Info.plist b/examples/ASDKTube/Sample/Info.plist index 35d842827b..3b4c6c7839 100644 --- a/examples/ASDKTube/Sample/Info.plist +++ b/examples/ASDKTube/Sample/Info.plist @@ -2,6 +2,8 @@ + UIViewControllerBasedStatusBarAppearance + CFBundleDevelopmentRegion en CFBundleExecutable @@ -26,11 +28,11 @@ armv7 + UIStatusBarStyle + UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight 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..d5db4a84e4 --- /dev/null +++ b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m @@ -0,0 +1,151 @@ +// +// 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; +} + +- (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]; + + _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:_videoModel.url]; + _videoPlayerNode.delegate = self; + [self addSubnode:_videoPlayerNode]; + } + 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; +} + +#pragma mark - ASVideoPlayerNodeDelegate +- (void)videoPlayerNodeWasTapped:(ASVideoPlayerNode *)videoPlayer +{ + if (_videoPlayerNode.playerState == ASVideoNodePlayerStatePlaying) { + NSLog(@"TRANSITION"); + } else { + [_videoPlayerNode play]; + } +} + +- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer +{ + return @[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]; +} + +- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer forControls:(NSDictionary *)controls forMaximumSize:(CGSize)maxSize +{ + NSMutableArray *bottomControls = [[NSMutableArray alloc] init]; + + ASDisplayNode *playbackButtonNode = controls[@(ASVideoPlayerNodeControlTypePlaybackButton)]; + + if (playbackButtonNode) { + [bottomControls addObject:playbackButtonNode]; + } + + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.flexGrow = YES; + + 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:@[ spacer, controlbarInsetSpec ]]; + + + return mainVerticalStack; +} +@end diff --git a/examples/ASDKTube/Sample/ViewController.h b/examples/ASDKTube/Sample/ViewController.h index 8b16b1c332..d4ec993c7b 100644 --- a/examples/ASDKTube/Sample/ViewController.h +++ b/examples/ASDKTube/Sample/ViewController.h @@ -8,8 +8,8 @@ * 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 : ASViewController diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index b1d44e2510..adb64991c1 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -10,31 +10,73 @@ */ #import "ViewController.h" +#import +#import +#import "VideoModel.h" +#import "VideoContentCell.h" -@interface ViewController() +@interface ViewController() @property (nonatomic, strong) ASVideoPlayerNode *videoPlayerNode; @end @implementation ViewController +{ + ASTableNode *_tableNode; + NSMutableArray *_videoFeedData; +} - (instancetype)init { - if (!(self = [super initWithNode:self.videoPlayerNode])) { + _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) { @@ -57,47 +99,47 @@ } #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; -} +//- (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 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/ASDKgram/Sample.xcodeproj/project.pbxproj b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj index 9307b60a63..4aba4f49ff 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 = ( ); @@ -319,14 +319,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; @@ -334,14 +334,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; @@ -349,14 +349,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 @@ + + + + + + + From 8c8fc3dba0eb692fb8676b0c332343149b90fb15 Mon Sep 17 00:00:00 2001 From: Erekle Date: Sat, 14 May 2016 16:41:32 +0400 Subject: [PATCH 21/26] Adding ASVideoPlayerNode --- AsyncDisplayKit.xcodeproj/project.pbxproj | 10 +- AsyncDisplayKit/ASVideoPlayerNode.h | 12 +- AsyncDisplayKit/ASVideoPlayerNode.mm | 67 +++-- .../Private/ASDefaultPlaybackButton.h | 16 ++ .../Private/ASDefaultPlaybackButton.m | 81 ++++++ .../ASDKTube/Sample.xcodeproj/project.pbxproj | 54 ++++ examples/ASDKTube/Sample/AppDelegate.m | 34 ++- .../Controller/VideoFeedNodeController.h | 13 + .../Controller/VideoFeedNodeController.m | 71 ++++++ examples/ASDKTube/Sample/Info.plist | 6 +- examples/ASDKTube/Sample/Models/Utilities.h | 40 +++ examples/ASDKTube/Sample/Models/Utilities.m | 230 ++++++++++++++++++ examples/ASDKTube/Sample/Models/VideoModel.h | 16 ++ examples/ASDKTube/Sample/Models/VideoModel.m | 27 ++ .../ASDKTube/Sample/Nodes/VideoContentCell.h | 14 ++ .../ASDKTube/Sample/Nodes/VideoContentCell.m | 151 ++++++++++++ examples/ASDKTube/Sample/ViewController.h | 2 +- examples/ASDKTube/Sample/ViewController.m | 134 ++++++---- .../Sample/WindowWithStatusBarUnderlay.h | 13 + .../Sample/WindowWithStatusBarUnderlay.m | 39 +++ .../ASDKgram/Sample.xcodeproj/project.pbxproj | 18 +- .../contents.xcworkspacedata | 10 + 22 files changed, 974 insertions(+), 84 deletions(-) create mode 100644 AsyncDisplayKit/Private/ASDefaultPlaybackButton.h create mode 100644 AsyncDisplayKit/Private/ASDefaultPlaybackButton.m create mode 100644 examples/ASDKTube/Sample/Controller/VideoFeedNodeController.h create mode 100644 examples/ASDKTube/Sample/Controller/VideoFeedNodeController.m create mode 100644 examples/ASDKTube/Sample/Models/Utilities.h create mode 100644 examples/ASDKTube/Sample/Models/Utilities.m create mode 100644 examples/ASDKTube/Sample/Models/VideoModel.h create mode 100644 examples/ASDKTube/Sample/Models/VideoModel.m create mode 100644 examples/ASDKTube/Sample/Nodes/VideoContentCell.h create mode 100644 examples/ASDKTube/Sample/Nodes/VideoContentCell.m create mode 100644 examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.h create mode 100644 examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.m create mode 100644 examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index f9e68c87cf..54eb504046 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -311,6 +311,8 @@ 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 */; }; 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 */; }; @@ -824,6 +826,8 @@ 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 = ""; }; @@ -1340,6 +1344,8 @@ CC3B20881C3F7A5400798563 /* ASWeakSet.m */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, + 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */, + 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */, ); path = Private; sourceTree = ""; @@ -1600,6 +1606,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 */, @@ -2014,6 +2021,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 */, @@ -2119,8 +2127,6 @@ 058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */, 257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */, 8BDA5FC61CDBDDE1007D13B2 /* ASVideoPlayerNode.mm in Sources */, - ACC945AB1BA9E7C1005E1FB8 /* ASViewController.m in Sources */, - B0F8805B1BEAEC7500D17647 /* ASTableNode.m in Sources */, 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */, 257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */, 9CFFC6BE1CCAC52B006A6476 /* ASEnvironment.mm in Sources */, diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 010e31bcb2..41c22eceea 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -71,12 +71,12 @@ NS_ASSUME_NONNULL_BEGIN * @abstract Delegate method invoked in layoutSpecThatFits: * @param videoPlayer * @param controls - Dictionary of controls which are used in videoPlayer; Dictionary keys are ASVideoPlayerNodeControlType - * @param constrainedSize - ASSizeRange for ASVideoPlayerNode + * @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 - forConstrainedSize:(ASSizeRange)constrainedSize; + forMaximumSize:(CGSize)maxSize; #pragma mark Text delegate methods /** @@ -95,8 +95,16 @@ NS_ASSUME_NONNULL_BEGIN - (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer; - (UIImage *)videoPlayerNodeScrubberThumbImage:(ASVideoPlayerNode *)videoPlayer; +#pragma mark - Playback button delegate methods +- (UIColor *)videoPlayerNodePlaybackButtonTint:(ASVideoPlayerNode *)videoPlayer; + #pragma mark ASVideoNodeDelegate proxy methods +/** + * @abstract Delegate method invoked when ASVideoPlayerNode playback time is taped. + * @param videoPlayerNode The ASVideoPlayerNode that was tapped. + */ +- (void)videoPlayerNodeWasTapped:(ASVideoPlayerNode *)videoPlayer; /** * @abstract Delegate method invoked when ASVideoNode playback time is updated. * @param videoPlayerNode The video node that was tapped. diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index e84665c7bc..e4d3482a40 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -7,6 +7,7 @@ // #import "ASVideoPlayerNode.h" +#import "ASDefaultPlaybackButton.h" static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; @@ -18,6 +19,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; struct { unsigned int delegateNeededControls:1; + unsigned int delegatePlaybackButtonTint:1; unsigned int delegateScrubberMaximumTrackTintColor:1; unsigned int delegateScrubberMinimumTrackTintColor:1; unsigned int delegateScrubberThumbTintColor:1; @@ -29,6 +31,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; unsigned int delegateVideoNodeWillChangeState:1; unsigned int delegateVideoNodeShouldChangeState:1; unsigned int delegateVideoNodePlaybackDidFinish:1; + unsigned int delegateVideoNodeTapped:1; } _delegateFlags; NSURL *_url; @@ -40,7 +43,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; NSMutableDictionary *_cachedControls; - ASControlNode *_playbackButtonNode; + ASDefaultPlaybackButton *_playbackButtonNode; ASTextNode *_elapsedTextNode; ASTextNode *_durationTextNode; ASDisplayNode *_scrubberNode; @@ -56,6 +59,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; BOOL _muted; int32_t _periodicTimeObserverTimescale; NSString *_gravity; + + UIColor *_defaultControlsColor; } @end @@ -103,6 +108,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)privateInit { + _defaultControlsColor = [UIColor whiteColor]; _cachedControls = [[NSMutableDictionary alloc] init]; _videoNode = [[ASVideoNode alloc] init]; @@ -213,9 +219,13 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)createPlaybackButton { if (_playbackButtonNode == nil) { - _playbackButtonNode = [[ASControlNode alloc] init]; - _playbackButtonNode.preferredFrameSize = CGSizeMake(20.0, 20.0); - _playbackButtonNode.backgroundColor = [UIColor redColor]; + _playbackButtonNode = [[ASDefaultPlaybackButton alloc] init]; + _playbackButtonNode.preferredFrameSize = CGSizeMake(16.0, 22.0); + if (_delegateFlags.delegatePlaybackButtonTint) { + _playbackButtonNode.tintColor = [_delegate videoPlayerNodePlaybackButtonTint:self]; + } else { + _playbackButtonNode.tintColor = _defaultControlsColor; + } [_playbackButtonNode addTarget:self action:@selector(playbackButtonTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; [_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)]; } @@ -316,7 +326,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } else { options = @{ NSFontAttributeName : [UIFont systemFontOfSize:12.0], - NSForegroundColorAttributeName: [UIColor whiteColor] + NSForegroundColorAttributeName: _defaultControlsColor }; } @@ -337,6 +347,12 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _duration = _videoNode.currentItem.duration; [self updateDurationTimeLabel]; } + + if (toState == ASVideoNodePlayerStatePlaying) { + _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; + } else { + _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePlay; + } } - (BOOL)videoNode:(ASVideoNode *)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state @@ -376,15 +392,26 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } +- (void)videoNodeWasTapped:(ASVideoNode *)videoNode +{ + if (_delegateFlags.delegateVideoNodeTapped) { + [_delegate videoPlayerNodeWasTapped:self]; + } else { + if (videoNode.playerState == ASVideoNodePlayerStatePlaying) { + [videoNode pause]; + } else { + [videoNode play]; + } + } +} + #pragma mark - Actions - (void)playbackButtonTapped:(ASControlNode*)node { if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { [_videoNode pause]; - _playbackButtonNode.backgroundColor = [UIColor greenColor]; } else { [_videoNode play]; - _playbackButtonNode.backgroundColor = [UIColor redColor]; } } @@ -458,25 +485,35 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; #pragma mark - Layout - (ASLayoutSpec*)layoutSpecThatFits:(ASSizeRange)constrainedSize { - _videoNode.preferredFrameSize = constrainedSize.max; + 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 forConstrainedSize:constrainedSize]; + layoutSpec = [_delegate videoPlayerNodeLayoutSpec:self forControls:_cachedControls forMaximumSize:maxSize]; } else { - layoutSpec = [self defaultLayoutSpecThatFits:constrainedSize]; + layoutSpec = [self defaultLayoutSpecThatFits:maxSize]; } ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec]; - overlaySpec.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(constrainedSize.max); + overlaySpec.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(maxSize); return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[overlaySpec]]; } -- (ASLayoutSpec*)defaultLayoutSpecThatFits:(ASSizeRange)constrainedSize +- (ASLayoutSpec*)defaultLayoutSpecThatFits:(CGSize)maxSize { - _scrubberNode.preferredFrameSize = CGSizeMake(constrainedSize.max.width, 44.0); + _scrubberNode.preferredFrameSize = CGSizeMake(maxSize.width, 44.0); ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; spacer.flexGrow = YES; @@ -521,12 +558,14 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _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:forConstrainedSize:)]; + _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.delegateVideoNodeTapped = [_delegate respondsToSelector:@selector(videoPlayerNodeWasTapped:)]; } } 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/examples/ASDKTube/Sample.xcodeproj/project.pbxproj b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj index e9965d6274..f808ae18f9 100644 --- a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj @@ -14,6 +14,11 @@ 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 */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -27,6 +32,16 @@ 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 = ""; }; 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 = ""; }; @@ -68,11 +83,16 @@ 05E2128319D4DB510098F589 /* Sample */ = { isa = PBXGroup; children = ( + 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 = ""; @@ -106,6 +126,35 @@ 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 = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -228,8 +277,13 @@ 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; }; diff --git a/examples/ASDKTube/Sample/AppDelegate.m b/examples/ASDKTube/Sample/AppDelegate.m index a8e5594780..947d95ca09 100644 --- a/examples/ASDKTube/Sample/AppDelegate.m +++ b/examples/ASDKTube/Sample/AppDelegate.m @@ -10,18 +10,36 @@ */ #import "AppDelegate.h" +#import "WindowWithStatusBarUnderlay.h" +#import "Utilities.h" +#import "VideoFeedNodeController.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]; +- (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/Info.plist b/examples/ASDKTube/Sample/Info.plist index 35d842827b..3b4c6c7839 100644 --- a/examples/ASDKTube/Sample/Info.plist +++ b/examples/ASDKTube/Sample/Info.plist @@ -2,6 +2,8 @@ + UIViewControllerBasedStatusBarAppearance + CFBundleDevelopmentRegion en CFBundleExecutable @@ -26,11 +28,11 @@ armv7 + UIStatusBarStyle + UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight 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..d5db4a84e4 --- /dev/null +++ b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m @@ -0,0 +1,151 @@ +// +// 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; +} + +- (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]; + + _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:_videoModel.url]; + _videoPlayerNode.delegate = self; + [self addSubnode:_videoPlayerNode]; + } + 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; +} + +#pragma mark - ASVideoPlayerNodeDelegate +- (void)videoPlayerNodeWasTapped:(ASVideoPlayerNode *)videoPlayer +{ + if (_videoPlayerNode.playerState == ASVideoNodePlayerStatePlaying) { + NSLog(@"TRANSITION"); + } else { + [_videoPlayerNode play]; + } +} + +- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer +{ + return @[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]; +} + +- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer forControls:(NSDictionary *)controls forMaximumSize:(CGSize)maxSize +{ + NSMutableArray *bottomControls = [[NSMutableArray alloc] init]; + + ASDisplayNode *playbackButtonNode = controls[@(ASVideoPlayerNodeControlTypePlaybackButton)]; + + if (playbackButtonNode) { + [bottomControls addObject:playbackButtonNode]; + } + + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.flexGrow = YES; + + 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:@[ spacer, controlbarInsetSpec ]]; + + + return mainVerticalStack; +} +@end diff --git a/examples/ASDKTube/Sample/ViewController.h b/examples/ASDKTube/Sample/ViewController.h index 8b16b1c332..d4ec993c7b 100644 --- a/examples/ASDKTube/Sample/ViewController.h +++ b/examples/ASDKTube/Sample/ViewController.h @@ -8,8 +8,8 @@ * 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 : ASViewController diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index 0e0e038fa6..adb64991c1 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -10,31 +10,73 @@ */ #import "ViewController.h" +#import +#import +#import "VideoModel.h" +#import "VideoContentCell.h" -@interface ViewController() +@interface ViewController() @property (nonatomic, strong) ASVideoPlayerNode *videoPlayerNode; @end @implementation ViewController +{ + ASTableNode *_tableNode; + NSMutableArray *_videoFeedData; +} - (instancetype)init { - if (!(self = [super initWithNode:self.videoPlayerNode])) { + _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) { @@ -57,49 +99,49 @@ } #pragma mark - ASVideoPlayerNodeDelegate -- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer -{ - return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), - @(ASVideoPlayerNodeControlTypeElapsedText), - @(ASVideoPlayerNodeControlTypeScrubber), - @(ASVideoPlayerNodeControlTypeDurationText) ]; -} +//- (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; +//} -- (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 +/*- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer forControls:(NSDictionary *)controls forConstrainedSize:(ASSizeRange)constrainedSize { @@ -166,6 +208,6 @@ 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/ASDKgram/Sample.xcodeproj/project.pbxproj b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj index 9307b60a63..4aba4f49ff 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 = ( ); @@ -319,14 +319,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; @@ -334,14 +334,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; @@ -349,14 +349,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 @@ + + + + + + + From 39e287f0d6b5cb23615acf9c6047c63f0fa72696 Mon Sep 17 00:00:00 2001 From: Erekle Date: Wed, 18 May 2016 00:45:40 +0400 Subject: [PATCH 22/26] fixes suggested by levi --- AsyncDisplayKit.xcodeproj/project.pbxproj | 26 +++++++----- AsyncDisplayKit/ASVideoPlayerNode.h | 2 +- AsyncDisplayKit/ASVideoPlayerNode.mm | 42 +++++++++++-------- .../ASDKTube/Sample/Nodes/VideoContentCell.m | 2 +- 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 54eb504046..9a6fdcc673 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -313,6 +313,8 @@ 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 */; }; @@ -918,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 = ""; }; @@ -1490,6 +1493,7 @@ children = ( FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */, D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */, + BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */, ); name = Pods; sourceTree = ""; @@ -1763,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 */, @@ -1853,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 = ( ); @@ -1959,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; @@ -1974,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; @@ -1989,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; @@ -2183,6 +2188,7 @@ buildActionMask = 2147483647; files = ( 9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */, + 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */, DE4843DB1C93EAB100A1F33B /* ASDisplayNodeLayoutContext.mm in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, @@ -2668,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/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 41c22eceea..bdbff43157 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -104,7 +104,7 @@ NS_ASSUME_NONNULL_BEGIN * @abstract Delegate method invoked when ASVideoPlayerNode playback time is taped. * @param videoPlayerNode The ASVideoPlayerNode that was tapped. */ -- (void)videoPlayerNodeWasTapped:(ASVideoPlayerNode *)videoPlayer; +- (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer; /** * @abstract Delegate method invoked when ASVideoNode playback time is updated. * @param videoPlayerNode The video node that was tapped. diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index e4d3482a40..291824010e 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -31,7 +31,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; unsigned int delegateVideoNodeWillChangeState:1; unsigned int delegateVideoNodeShouldChangeState:1; unsigned int delegateVideoNodePlaybackDidFinish:1; - unsigned int delegateVideoNodeTapped:1; + unsigned int delegateDidTapVideoPlayerNode:1; } _delegateFlags; NSURL *_url; @@ -72,7 +72,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return nil; } - [self privateInit]; + [self _init]; return self; } @@ -86,7 +86,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _url = url; _asset = [AVAsset assetWithURL:_url]; - [self privateInit]; + [self _init]; return self; } @@ -100,12 +100,12 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _asset = asset; _disableControls = NO; - [self privateInit]; + [self _init]; return self; } -- (void)privateInit +- (void)_init { _defaultControlsColor = [UIColor whiteColor]; @@ -226,7 +226,12 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } else { _playbackButtonNode.tintColor = _defaultControlsColor; } - [_playbackButtonNode addTarget:self action:@selector(playbackButtonTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; + + if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { + _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; + } + + [_playbackButtonNode addTarget:self action:@selector(didTapPlaybackButton:) forControlEvents:ASControlNodeEventTouchUpInside]; [_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)]; } @@ -281,9 +286,9 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } - [slider addTarget:self action:@selector(beganSeek) forControlEvents:UIControlEventTouchDown]; - [slider addTarget:self action:@selector(endedSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; - [slider addTarget:self action:@selector(changedSeekValue:) forControlEvents:UIControlEventValueChanged]; + [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; }]; @@ -394,8 +399,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)videoNodeWasTapped:(ASVideoNode *)videoNode { - if (_delegateFlags.delegateVideoNodeTapped) { - [_delegate videoPlayerNodeWasTapped:self]; + if (_delegateFlags.delegateDidTapVideoPlayerNode) { + [_delegate didTapVideoPlayerNode:self]; } else { if (videoNode.playerState == ASVideoNodePlayerStatePlaying) { [videoNode pause]; @@ -406,7 +411,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } #pragma mark - Actions -- (void)playbackButtonTapped:(ASControlNode*)node +- (void)didTapPlaybackButton:(ASControlNode*)node { if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { [_videoNode pause]; @@ -415,17 +420,17 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } -- (void)beganSeek +- (void)beginSeek { _isSeeking = YES; } -- (void)endedSeek +- (void)endSeek { _isSeeking = NO; } -- (void)changedSeekValue:(UISlider*)slider +- (void)seekTimeDidChange:(UISlider*)slider { CGFloat percentage = slider.value * 100; [self seekToTime:percentage]; @@ -541,7 +546,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } #pragma mark - Properties -- (id)delegate{ +- (id)delegate +{ return _delegate; } @@ -565,7 +571,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)]; _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; _delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)]; - _delegateFlags.delegateVideoNodeTapped = [_delegate respondsToSelector:@selector(videoPlayerNodeWasTapped:)]; + _delegateFlags.delegateDidTapVideoPlayerNode = [_delegate respondsToSelector:@selector(didTapVideoPlayerNode:)]; } } @@ -604,7 +610,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _videoNode.periodicTimeObserverTimescale = _periodicTimeObserverTimescale; } -- (NSString*)gravity +- (NSString *)gravity { if (_gravity == nil) { _gravity = _videoNode.gravity; diff --git a/examples/ASDKTube/Sample/Nodes/VideoContentCell.m b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m index d5db4a84e4..2a163f9c44 100644 --- a/examples/ASDKTube/Sample/Nodes/VideoContentCell.m +++ b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m @@ -99,7 +99,7 @@ } #pragma mark - ASVideoPlayerNodeDelegate -- (void)videoPlayerNodeWasTapped:(ASVideoPlayerNode *)videoPlayer +- (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer { if (_videoPlayerNode.playerState == ASVideoNodePlayerStatePlaying) { NSLog(@"TRANSITION"); From 3d5385dad3e5bbea39faf87d48182c36d114d378 Mon Sep 17 00:00:00 2001 From: Erekle Date: Thu, 19 May 2016 13:40:09 +0400 Subject: [PATCH 23/26] replaced deprecated methods --- AsyncDisplayKit/ASVideoNode.h | 1 - AsyncDisplayKit/ASVideoPlayerNode.h | 4 ++-- AsyncDisplayKit/ASVideoPlayerNode.mm | 10 +++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index bca3b3a22c..5008ea3333 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -19,7 +19,6 @@ typedef enum { ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying, ASVideoNodePlayerStatePlaying, ASVideoNodePlayerStateLoading, - ASVideoNodePlayerStatePlaying, ASVideoNodePlayerStatePaused, ASVideoNodePlayerStateFinished } ASVideoNodePlayerState; diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index bdbff43157..4a7338e6f9 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -107,7 +107,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer; /** * @abstract Delegate method invoked when ASVideoNode playback time is updated. - * @param videoPlayerNode The video node that was tapped. + * @param videoPlayerNode The video player node * @param second current playback time. */ - (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didPlayToTime:(CMTime)time; @@ -119,7 +119,7 @@ NS_ASSUME_NONNULL_BEGIN * @param toSate ASVideoNode new state. * @discussion This method is called after each state change */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer willChangeVideoNodeState:(ASVideoNodePlayerState)state toVideoNodeState:(ASVideoNodePlayerState)toSate; +- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer willChangeVideoNodeState:(ASVideoNodePlayerState)state toVideoNodeState:(ASVideoNodePlayerState)toState; /** * @abstract Delegate method is invoked when ASVideoNode decides to change state. diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 291824010e..021ce66cac 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -368,7 +368,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return YES; } -- (void)videoNode:(ASVideoNode *)videoNode didPlayToSecond:(NSTimeInterval)second +- (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval { //TODO: ask Max about CMTime problem in ASVideoNode Header file //as we said yesterday, we must use CMTime in ASVideoNode instead of NSTimeInterval @@ -382,22 +382,22 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } if (_elapsedTextNode) { - [self updateElapsedTimeLabel:second]; + [self updateElapsedTimeLabel:timeInterval]; } if (_scrubberNode) { - [(UISlider*)_scrubberNode.view setValue:(second/ CMTimeGetSeconds(_duration) ) animated:NO]; + [(UISlider*)_scrubberNode.view setValue:( timeInterval / CMTimeGetSeconds(_duration) ) animated:NO]; } } -- (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode +- (void)videoDidPlayToEnd:(ASVideoNode *)videoNode { if (_delegateFlags.delegateVideoNodePlaybackDidFinish) { [_delegate videoPlayerNodeDidPlayToEnd:self]; } } -- (void)videoNodeWasTapped:(ASVideoNode *)videoNode +- (void)didTapVideoNode:(ASVideoNode *)videoNode { if (_delegateFlags.delegateDidTapVideoPlayerNode) { [_delegate didTapVideoPlayerNode:self]; From 100d2b4f2667cf1cf6f9f0d5d82c09276dbff8ba Mon Sep 17 00:00:00 2001 From: Erekle Date: Thu, 19 May 2016 15:07:39 +0400 Subject: [PATCH 24/26] moved spinner to ASVIdeoPlayerNode --- AsyncDisplayKit/ASVideoNode.mm | 51 +---------- AsyncDisplayKit/ASVideoPlayerNode.h | 5 ++ AsyncDisplayKit/ASVideoPlayerNode.mm | 86 +++++++++++++++++-- AsyncDisplayKitTests/ASVideoNodeTests.m | 14 +-- .../ASDKTube/Sample/Nodes/VideoContentCell.m | 13 +-- 5 files changed, 91 insertions(+), 78 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 39de422ba9..8e15cb9e14 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -74,7 +74,6 @@ static NSString * const kStatus = @"status"; ASImageNode *_placeholderImageNode; // TODO: Make ASVideoNode an ASImageNode subclass; remove this. ASDisplayNode *_playerNode; - ASDisplayNode *_spinnerNode; NSString *_gravity; } @@ -220,13 +219,6 @@ 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]; } @@ -303,7 +295,6 @@ static NSString * const kStatus = @"status"; if ([keyPath isEqualToString:kStatus]) { if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { self.playerState = ASVideoNodePlayerStateReadyToPlay; - [self removeSpinner]; // 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]; @@ -320,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]; } } } @@ -559,9 +549,8 @@ static NSString * const kStatus = @"status"; _shouldBePlaying = YES; if (![self ready]) { - [self showSpinner]; + self.playerState = ASVideoNodePlayerStateLoading; } else { - [self removeSpinner]; self.playerState = ASVideoNodePlayerStatePlaying; } } @@ -571,35 +560,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); @@ -608,7 +568,6 @@ static NSString * const kStatus = @"status"; } self.playerState = ASVideoNodePlayerStatePaused; [_player pause]; - [self removeSpinner]; _shouldBePlaying = NO; } @@ -656,7 +615,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)]; } @@ -680,13 +638,6 @@ static NSString * const kStatus = @"status"; } #pragma mark - Internal Properties - -- (ASDisplayNode *)spinner -{ - ASDN::MutexLocker l(_videoLock); - return _spinnerNode; -} - - (ASImageNode *)placeholderImageNode { ASDN::MutexLocker l(_videoLock); diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 4a7338e6f9..d5fb937d8a 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -42,6 +42,8 @@ NS_ASSUME_NONNULL_BEGIN @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 @@ -95,6 +97,9 @@ NS_ASSUME_NONNULL_BEGIN - (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; diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 021ce66cac..b62093c3c6 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -19,6 +19,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; struct { unsigned int delegateNeededControls:1; + unsigned int delegateSpinnerTintColor:1; unsigned int delegatePlaybackButtonTint:1; unsigned int delegateScrubberMaximumTrackTintColor:1; unsigned int delegateScrubberMinimumTrackTintColor:1; @@ -48,6 +49,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; ASTextNode *_durationTextNode; ASDisplayNode *_scrubberNode; ASStackLayoutSpec *_controlFlexGrowSpacerSpec; + ASDisplayNode *_spinnerNode; BOOL _isSeeking; CMTime _duration; @@ -60,6 +62,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; int32_t _periodicTimeObserverTimescale; NSString *_gravity; + BOOL _shouldAggressivelyRecoverFromStall; + UIColor *_defaultControlsColor; } @@ -355,9 +359,18 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; if (toState == ASVideoNodePlayerStatePlaying) { _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; - } else { + [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 @@ -402,16 +415,12 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; if (_delegateFlags.delegateDidTapVideoPlayerNode) { [_delegate didTapVideoPlayerNode:self]; } else { - if (videoNode.playerState == ASVideoNodePlayerStatePlaying) { - [videoNode pause]; - } else { - [videoNode play]; - } + [self manageVideoNodePlayback]; } } #pragma mark - Actions -- (void)didTapPlaybackButton:(ASControlNode*)node +- (void)manageVideoNodePlayback { if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { [_videoNode pause]; @@ -420,6 +429,43 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } +- (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 manageVideoNodePlayback]; +} + - (void)beginSeek { _isSeeking = YES; @@ -445,7 +491,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; if (_videoNode.playerState != ASVideoNodePlayerStatePlaying) { - [_videoNode play]; + [self manageVideoNodePlayback]; } } @@ -510,10 +556,20 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; 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); - return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[overlaySpec]]; + [children addObject:overlaySpec]; + + return [ASStaticLayoutSpec staticLayoutSpecWithChildren:children]; } - (ASLayoutSpec*)defaultLayoutSpecThatFits:(CGSize)maxSize @@ -559,6 +615,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; memset(&_delegateFlags, 0, sizeof(_delegateFlags)); } else { _delegateFlags.delegateNeededControls = [_delegate respondsToSelector:@selector(videoPlayerNodeNeededControls:)]; + _delegateFlags.delegateSpinnerTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerTint:)]; _delegateFlags.delegateScrubberMaximumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMaximumTrackTint:)]; _delegateFlags.delegateScrubberMinimumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMinimumTrackTint:)]; _delegateFlags.delegateScrubberThumbTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbTint:)]; @@ -629,6 +686,17 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return _videoNode.playerState; } +- (BOOL)shouldAggressivelyRecoverFromStall +{ + return _videoNode.shouldAggressivelyRecoverFromStall; +} + +- (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFromStall +{ + _shouldAggressivelyRecoverFromStall = shouldAggressivelyRecoverFromStall; + _videoNode.shouldAggressivelyRecoverFromStall = _shouldAggressivelyRecoverFromStall; +} + #pragma mark - Helpers - (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type { diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index eaa7e63a5b..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); } diff --git a/examples/ASDKTube/Sample/Nodes/VideoContentCell.m b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m index 2a163f9c44..f5fdd4b5c5 100644 --- a/examples/ASDKTube/Sample/Nodes/VideoContentCell.m +++ b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m @@ -31,7 +31,6 @@ { self = [super init]; if (self) { - _videoModel = video; _titleNode = [[ASTextNode alloc] init]; @@ -55,6 +54,7 @@ _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:_videoModel.url]; _videoPlayerNode.delegate = self; + _videoPlayerNode.backgroundColor = [UIColor blackColor]; [self addSubnode:_videoPlayerNode]; } return self; @@ -101,19 +101,20 @@ #pragma mark - ASVideoPlayerNodeDelegate - (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer { - if (_videoPlayerNode.playerState == ASVideoNodePlayerStatePlaying) { + if (_videoPlayerNode.isPlaying) { NSLog(@"TRANSITION"); + [_videoPlayerNode pause]; } else { [_videoPlayerNode play]; } } -- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer +/*- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer { return @[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]; -} +}*/ -- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer forControls:(NSDictionary *)controls forMaximumSize:(CGSize)maxSize +/*- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer forControls:(NSDictionary *)controls forMaximumSize:(CGSize)maxSize { NSMutableArray *bottomControls = [[NSMutableArray alloc] init]; @@ -147,5 +148,5 @@ return mainVerticalStack; -} +}*/ @end From 49b272663250b68605d56da476f086cb8280bc43 Mon Sep 17 00:00:00 2001 From: Erekle Date: Fri, 20 May 2016 12:21:57 +0400 Subject: [PATCH 25/26] fixed old examples --- examples/Videos/Sample/ViewController.m | 15 +- .../Sample.xcodeproj/project.pbxproj | 1183 ++++++----------- 2 files changed, 384 insertions(+), 814 deletions(-) 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 */; +} From 4ab0f1ec60ddf5c73c89016b33302c356819aaf8 Mon Sep 17 00:00:00 2001 From: Erekle Date: Mon, 23 May 2016 23:23:33 +0400 Subject: [PATCH 26/26] [ASVideoPlayerNode] fixes; Ability to add custom controls --- AsyncDisplayKit/ASVideoNode.mm | 4 - AsyncDisplayKit/ASVideoPlayerNode.h | 25 ++- AsyncDisplayKit/ASVideoPlayerNode.mm | 192 ++++++++++++------ .../ASDKTube/Sample.xcodeproj/project.pbxproj | 32 +++ examples/ASDKTube/Sample/Icons/ico-mute.png | Bin 0 -> 483 bytes .../ASDKTube/Sample/Icons/ico-mute@2x.png | Bin 0 -> 801 bytes .../ASDKTube/Sample/Icons/ico-mute@3x.png | Bin 0 -> 1236 bytes examples/ASDKTube/Sample/Icons/ico-unmute.png | Bin 0 -> 578 bytes .../ASDKTube/Sample/Icons/ico-unmute@2x.png | Bin 0 -> 1147 bytes .../ASDKTube/Sample/Icons/ico-unmute@3x.png | Bin 0 -> 1939 bytes .../ASDKTube/Sample/Nodes/VideoContentCell.m | 109 ++++++++-- 11 files changed, 270 insertions(+), 92 deletions(-) create mode 100644 examples/ASDKTube/Sample/Icons/ico-mute.png create mode 100644 examples/ASDKTube/Sample/Icons/ico-mute@2x.png create mode 100644 examples/ASDKTube/Sample/Icons/ico-mute@3x.png create mode 100644 examples/ASDKTube/Sample/Icons/ico-unmute.png create mode 100644 examples/ASDKTube/Sample/Icons/ico-unmute@2x.png create mode 100644 examples/ASDKTube/Sample/Icons/ico-unmute@3x.png diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 8e15cb9e14..feb5910ad3 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -443,10 +443,6 @@ static NSString * const kStatus = @"status"; _asset = asset; [self setNeedsDataFetch]; - - if (_shouldAutoplay) { - [self play]; - } } - (AVAsset *)asset diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index d5fb937d8a..99532928c6 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -29,17 +29,19 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, atomic, weak) id delegate; -@property (nonatomic,assign,readonly) CMTime duration; +@property (nonatomic, assign, readonly) CMTime duration; -@property (nonatomic, assign) BOOL disableControls; +@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 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; @@ -51,6 +53,8 @@ NS_ASSUME_NONNULL_BEGIN - (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; @@ -67,7 +71,16 @@ NS_ASSUME_NONNULL_BEGIN * @abstract Delegate method invoked before creating controlbar controls * @param videoPlayer */ -- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode*)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: @@ -106,7 +119,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark ASVideoNodeDelegate proxy methods /** - * @abstract Delegate method invoked when ASVideoPlayerNode playback time is taped. + * @abstract Delegate method invoked when ASVideoPlayerNode is taped. * @param videoPlayerNode The ASVideoPlayerNode that was tapped. */ - (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer; diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index b62093c3c6..ec19fda312 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -18,7 +18,8 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; __weak id _delegate; struct { - unsigned int delegateNeededControls:1; + unsigned int delegateNeededDefaultControls:1; + unsigned int delegateCustomControls:1; unsigned int delegateSpinnerTintColor:1; unsigned int delegatePlaybackButtonTint:1; unsigned int delegateScrubberMaximumTrackTintColor:1; @@ -40,7 +41,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; ASVideoNode *_videoNode; - NSArray *_neededControls; + NSArray *_neededDefaultControls; NSMutableDictionary *_cachedControls; @@ -51,13 +52,14 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; ASStackLayoutSpec *_controlFlexGrowSpacerSpec; ASDisplayNode *_spinnerNode; + BOOL _loadAssetWhenNodeBecomesVisible; BOOL _isSeeking; CMTime _duration; - BOOL _disableControls; + BOOL _controlsDisabled; - BOOL _shouldAutoplay; - BOOL _shouldAutorepeat; + BOOL _shouldAutoPlay; + BOOL _shouldAutoRepeat; BOOL _muted; int32_t _periodicTimeObserverTimescale; NSString *_gravity; @@ -89,6 +91,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; _url = url; _asset = [AVAsset assetWithURL:_url]; + _loadAssetWhenNodeBecomesVisible = YES; [self _init]; @@ -102,7 +105,36 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } _asset = asset; - _disableControls = NO; + _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]; @@ -111,16 +143,15 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)_init { - _defaultControlsColor = [UIColor whiteColor]; _cachedControls = [[NSMutableDictionary alloc] init]; _videoNode = [[ASVideoNode alloc] init]; - _videoNode.asset = _asset; _videoNode.delegate = self; + if (_loadAssetWhenNodeBecomesVisible == NO) { + _videoNode.asset = _asset; + } [self addSubnode:_videoNode]; - - [self addObservers]; } - (void)didLoad @@ -132,10 +163,21 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } -- (NSArray*)createControlElementArray +- (void)visibilityDidChange:(BOOL)isVisible { - if (_delegateFlags.delegateNeededControls) { - return [_delegate videoPlayerNodeNeededControls:self]; + [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), @@ -144,35 +186,25 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; @(ASVideoPlayerNodeControlTypeDurationText) ]; } -- (void)addObservers -{ - -} - -- (void)removeObservers -{ - -} - #pragma mark - UI - (void)createControls { ASDN::MutexLocker l(_videoPlayerLock); - if (_disableControls) { + if (_controlsDisabled) { return; } - if (_neededControls == nil) { - _neededControls = [self createControlElementArray]; + if (_neededDefaultControls == nil) { + _neededDefaultControls = [self createDefaultControlElementArray]; } if (_cachedControls == nil) { _cachedControls = [[NSMutableDictionary alloc] init]; } - for (int i = 0; i < _neededControls.count; i++) { - ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[[_neededControls objectAtIndex:i] integerValue]; + for (id object in _neededDefaultControls) { + ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[object integerValue]; switch (type) { case ASVideoPlayerNodeControlTypePlaybackButton: [self createPlaybackButton]; @@ -194,6 +226,19 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } + 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]; @@ -246,7 +291,9 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; { if (_elapsedTextNode == nil) { _elapsedTextNode = [[ASTextNode alloc] init]; - _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00" forControlType:ASVideoPlayerNodeControlTypeElapsedText]; + _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00" + forControlType:ASVideoPlayerNodeControlTypeElapsedText]; + _elapsedTextNode.truncationMode = NSLineBreakByClipping; [_cachedControls setObject:_elapsedTextNode forKey:@(ASVideoPlayerNodeControlTypeElapsedText)]; } @@ -257,7 +304,9 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; { if (_durationTextNode == nil) { _durationTextNode = [[ASTextNode alloc] init]; - _durationTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00" forControlType:ASVideoPlayerNodeControlTypeDurationText]; + _durationTextNode.attributedString = [self timeLabelAttributedStringForString:@"00:00" + forControlType:ASVideoPlayerNodeControlTypeDurationText]; + _durationTextNode.truncationMode = NSLineBreakByClipping; [_cachedControls setObject:_durationTextNode forKey:@(ASVideoPlayerNodeControlTypeDurationText)]; } @@ -317,14 +366,20 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)updateDurationTimeLabel { - NSString *formatedDuration = [self timeStringForCMTime:_duration forTimeLabelType:ASVideoPlayerNodeControlTypeDurationText]; - _durationTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration forControlType:ASVideoPlayerNodeControlTypeDurationText]; + if (!_durationTextNode) { + return; + } + NSString *formattedDuration = [self timeStringForCMTime:_duration forTimeLabelType:ASVideoPlayerNodeControlTypeDurationText]; + _durationTextNode.attributedString = [self timeLabelAttributedStringForString:formattedDuration forControlType:ASVideoPlayerNodeControlTypeDurationText]; } - (void)updateElapsedTimeLabel:(NSTimeInterval)seconds { - NSString *formatedDuration = [self timeStringForCMTime:CMTimeMakeWithSeconds( seconds, _videoNode.periodicTimeObserverTimescale ) forTimeLabelType:ASVideoPlayerNodeControlTypeElapsedText]; - _elapsedTextNode.attributedString = [self timeLabelAttributedStringForString:formatedDuration forControlType:ASVideoPlayerNodeControlTypeElapsedText]; + 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 @@ -352,7 +407,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; [_delegate videoPlayerNode:self willChangeVideoNodeState:state toVideoNodeState:toState]; } - if (toState == ASVideoNodePlayerStateReadyToPlay && _durationTextNode) { + if (toState == ASVideoNodePlayerStateReadyToPlay) { _duration = _videoNode.currentItem.duration; [self updateDurationTimeLabel]; } @@ -383,9 +438,6 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval { - //TODO: ask Max about CMTime problem in ASVideoNode Header file - //as we said yesterday, we must use CMTime in ASVideoNode instead of NSTimeInterval - //when this will be done, must just proxy value to delegate if (_delegateFlags.delegateVideoNodeDidPlayToTime) { [_delegate videoPlayerNode:self didPlayToTime:_videoNode.player.currentTime]; } @@ -415,12 +467,12 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; if (_delegateFlags.delegateDidTapVideoPlayerNode) { [_delegate didTapVideoPlayerNode:self]; } else { - [self manageVideoNodePlayback]; + [self togglePlayPause]; } } #pragma mark - Actions -- (void)manageVideoNodePlayback +- (void)togglePlayPause { if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { [_videoNode pause]; @@ -463,7 +515,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)didTapPlaybackButton:(ASControlNode*)node { - [self manageVideoNodePlayback]; + [self togglePlayPause]; } - (void)beginSeek @@ -491,7 +543,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; if (_videoNode.playerState != ASVideoNodePlayerStatePlaying) { - [self manageVideoNodePlayback]; + [self togglePlayPause]; } } @@ -609,12 +661,17 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)setDelegate:(id)delegate { + if (delegate == _delegate) { + return; + } + _delegate = delegate; if (_delegate == nil) { memset(&_delegateFlags, 0, sizeof(_delegateFlags)); } else { - _delegateFlags.delegateNeededControls = [_delegate respondsToSelector:@selector(videoPlayerNodeNeededControls:)]; + _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:)]; @@ -632,37 +689,53 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; } } -- (void)setDisableControls:(BOOL)disableControls +- (void)setControlsDisabled:(BOOL)controlsDisabled { - _disableControls = disableControls; + if (_controlsDisabled == controlsDisabled) { + return; + } + + _controlsDisabled = controlsDisabled; - if (_disableControls && _cachedControls.count > 0) { + if (_controlsDisabled && _cachedControls.count > 0) { [self removeControls]; - } else if (!_disableControls) { + } else if (!_controlsDisabled) { [self createControls]; } } -- (void)setShouldAutoplay:(BOOL)shouldAutoplay +- (void)setShouldAutoPlay:(BOOL)shouldAutoPlay { - _shouldAutoplay = shouldAutoplay; - _videoNode.shouldAutoplay = _shouldAutoplay; + if (_shouldAutoPlay == shouldAutoPlay) { + return; + } + _shouldAutoPlay = shouldAutoPlay; + _videoNode.shouldAutoplay = _shouldAutoPlay; } -- (void)setShouldAutorepeat:(BOOL)shouldAutorepeat +- (void)setShouldAutoRepeat:(BOOL)shouldAutoRepeat { - _shouldAutorepeat = shouldAutorepeat; - _videoNode.shouldAutorepeat = YES; + 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; } @@ -677,6 +750,9 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)setGravity:(NSString *)gravity { + if (_gravity == gravity) { + return; + } _gravity = gravity; _videoNode.gravity = _gravity; } @@ -693,6 +769,9 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFromStall { + if (_shouldAggressivelyRecoverFromStall == shouldAggressivelyRecoverFromStall) { + return; + } _shouldAggressivelyRecoverFromStall = shouldAggressivelyRecoverFromStall; _videoNode.shouldAggressivelyRecoverFromStall = _shouldAggressivelyRecoverFromStall; } @@ -719,11 +798,4 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; return videoDurationText; } -#pragma mark - Lifecycle - -- (void)dealloc -{ - [self removeObservers]; -} - @end diff --git a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj index f808ae18f9..1a8abd1257 100644 --- a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj @@ -19,6 +19,12 @@ 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 */ @@ -42,6 +48,12 @@ 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 = ""; }; @@ -83,6 +95,7 @@ 05E2128319D4DB510098F589 /* Sample */ = { isa = PBXGroup; children = ( + 8B90757E1CF3869100F924C1 /* Icons */, 8B0768C61CE7C85F002E1453 /* Controller */, 8B0768B91CE7B07E002E1453 /* Nodes */, 8B0768B51CE7ACE8002E1453 /* Models */, @@ -155,6 +168,19 @@ 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 */ @@ -215,9 +241,15 @@ 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; }; diff --git a/examples/ASDKTube/Sample/Icons/ico-mute.png b/examples/ASDKTube/Sample/Icons/ico-mute.png new file mode 100644 index 0000000000000000000000000000000000000000..1f700e3a5d31793a91ca0a3d36b7bea1c13b97e4 GIT binary patch literal 483 zcmV<90UZ8`P)Px$ok>JNR7efAl)p;EKoG_gy(f^s6?V3Y_yPyQHDc{Es9-CCS9rNrU%>tfA3>YL z!WU4VK@J3KD<%FW?8dqL+l^TVK6YkycD|j-W}>KrWLXx2OYj9mjyoJ*2_39hEe0eB z+jDTx66U?-MGWbPjCWwuJF_Ejt!*E|ytTwUf?%Y=*;sMt)(V-H+KNqUp9$>zJvqB= zF0FZT9sj;{4lv3ubBoP$ux^8VS90wTE1zYFK91w~!=A-X(Wk64{Aq;JM~X|6WCOgb zWH0FQCkRKX{Gy%)BQ^vVB?2M8uJI*7i4FOrM5N843&DvE`ITlf`Y$O#wXa$Z_Y`;D znwxcT6_3kal*8qRr1KS3yKiPQdaOc9*i?nSqTeWc7kxoo(m1(Pu$M}*A%SNqSV;4K znY~YRdm$k|65Px%+et)0RA>e5n!k?JFc62ca7W8KTt$Zj9X$^KhqOEcLKHL<6c-u_3L1C@#PR2N z3#x-h;2EeniRJr2yfRK=JKiLA7Dk$L65HeXJhR@}-K(k@WV6{Equ)_m&SzM9-X);B zg1)jTt?T^)2J8quw*cMCEWnqhh&g1(*!_y$oWg!FHUagea1jwc7;CqnIl|UEwCJdg zlF~~`;hlg@3KP{2=vB0MNlxjdq_7)-qzEr5g?9p8VhSe%H!1uX0}ovzQ{J=g0s0ob zJ7gWk*U}+#wqu#tRSWZYCka+sSQqU3W|bebceYM>E*K|RBegwL215($V7qQln&TS3 zwC)@9UbEgI?J3XiuU4zG=2@$uzvkhI)p5eJiaB~-Rovu~-i^kg%c#=0OL`;CU^}wz z+@gVRRb-ENFAYC7g_gTM{+;~gCP8y15}mDcZ_n&}Ux>r0k;lCazbbJooNWX~-gSkB_~ z3!104fZ@3#-A40dB}*{atnK#G-&XxN01ZiLfd%o!p&e zXzY$@bF#?*SmUE9LtD_r11G?Gp^FBNfYkwYY6v^pcBCv8^c2qmoSf3`PW?~u_WxzM?@?JI>;`JI`-l|ZFJSE)(7r-t|A0-p f0Nz!oWC8vKNIdPx(j!8s8RCodHoV`vJK@@(YK%tI1WOZ3PzwtSV^|ua78aUl zOr#ePc?&CTh=q;5gPlUc^*gZRU55RcnVsFcb7xO-nA!O`bLQK_ad+;Osi}_2a5$Vp zf1*V)*HPEQaSO7`=$|6OX?qXa^_Bwg6uMafSak;ldd3xs?|IM@a39e#zHN8G-gFn7 z0P-?^d_gA?vM?TNL<9H~KD)rgKMRg4XEY^g#A z-Ad|+F;(cG8%-T)s#s^yoBG1BT{i|g_Eez7vh;XK9U-O)d|p6Ll;bh)#nwgh(Uv1IF2|am&~b?KF)7YoM|aUJv^H#PB&bMY ziLiP&MyoynIas;DA5Nh0=Nc-ImDGZb5d~S<%Y{8duZzRyxfRdRlhwDRJ0sZZRlM0m zvr&A5>kstZ9t;M1Hr_HPVf?B#XO;FTY)RipXbe@X0)$OoLI0L))+(~GqerrCIvG)> zruPPPZ48<{T}G=ugOsfs!Dd_)WU(bOf_-=$a5?TS(zcNYo3?~LJtyawIMOuByy;j{#~n7ikJ`wQ$Y!E#B+FVO z8#XI_-V&HV*8U6py6tK}sQu{b)hU=7If!;<40gNF`*k9W`hH0PLp}wl5(vRm2Fg42&MM zKtNnM3=gMQ|8!lT+HxQxQlC_#IMYy>+x z&<#PZgdG)VN60@EMZ;2Z_ObKqXh1tcmgnSIbj=X58g~4{0~`odjL)OlVD0nd+BQO^ zjNe9}8TZ6tkE2gURE$z$$MuFy$B;U{!)PzVtEyV~TFvWv!erYtvctT6xl5snQA7(> zHn@mpznxUH=iEpZ8(CqpX=Eq==M}9OK)W$$*)031f^7s@fir-esIgMYXK3@;2E+OV z#yvxW`AQhSzT3cA!siWCKbt5_7mUpnNh8>kzPxjiHR;ctXxqpl^Jv+hd7!&dv5IK5 zKRnVS+bWWk9aUmaPa}!y^H#D*s9)Pvuxmk9ATzN2LspAO$wK$gvM&(Q?OmKATKSDP z;d~KYIi138JC)P|o%Owqy0_`c#reSYx15*nKK#19ZBEM0fP5q|u00pwsA%I+e5r zI)&Y*l5F&~QRmI9?tci4^M%Fs=JZb`w!}P)C%RqEN`i`L519Oza7ZOVfo}C5!?Ih_ yr;;){4fPS-D`?fRYfus05UToS4N^%^p#K3@;l`JC14)Px${7FPXR7efImb*&>K@i68q88!#Lb>hJ1H@i18-|l2@Psgdl^7*_AR=^h! zOV^Pegf<6W6$gPhA-iCzN@(zyoD73E*L8P-$yyzfQ{VxF&AL+p+rhUcEY_;!kPvRC z)(FTdDk4N}XyhlHswzxS92{wqjY>oboClr&+yah)*fnsi!aTIKQ<4jBOyQVNv-xPBcT#K&uLG>)fdk)p0{43yy(l(5(Uq{2UNl z(z#K8=pC$h%u|l`XM*$tAirH3ug4Sqx|a0Y={OgRpMmI2{)OljgO&G_V8(*9Q#Cc7NcxqG>)qF6((jC^GlbOna`aF#)nnW)5i zG7lw&)yt0dKqq(vTR?2iC+D1aIq9WFa@1?^gBtd0uvF5YA?bfFe6tW~7byHsfnkvq z-MuEyFp>3r+DxJ_$+mV|MagOGBOVei*KVt*E;fUB#h5~kvOWdE?QW>xFAmjM9YubU Qs{jB107*qoM6N<$f}>&t@Bjb+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e671374d475c27a740178713ec388fe8f8c9c9b5 GIT binary patch literal 1147 zcmV->1cdvEP)Px(HAzH4RA>d=nq7!ZQ547LiWx>sNcqYG36ZZ%N`&Oi1LlD@nN@MWazZ9u%WNGP!>1cF%3ie~vll-gEA~XY5*?{a3TaCLZf;v1|BL^9j`()iO^Q<41BD5%3RM36qTyVEs4;%+F23r za+K1qGDw_MRc;jvH3f8bPCEquz!s#gkWR}#PLtYdbDG3dsuwgK02x-Rh3VqP#V~(bzpz6w^*x^#kU0ej`{KpFhyfyV;Uz( zRZqm~afEJ{MxVellrX--5@+zF#>K)kOq#07C63TN+6Zi$kO8R-9*=#-eCY+W#1a<- z(IAaf^(3qwN9aW2-fKhfpbY}P_K>*Ufd<=QL=8BS;Kf0M6pvlGTyA+(fACvD%ss5h z#r9L5Py084kGRp)yRoT(kK+SkRuyo_EY!*sHNhpm5ZVz!>S`&VSRv-D#Mw=3x}6@O z!S%q9X~t{=@Dw|RHF_Sb$`$ZK@1wqrxY^>nB{&COSMU*BuTol32pka-mxQqGgw{xm z!F@gs+=TVn30E#!OeR(o9t%BT>boG?=6$THU8gU+kNQTBiTMS-<9*bZ8qSK)YeLT8 z(GZ`e=4vKnti-((JjZY*gt8JQ^tuq7hlpk3yDixEv@^x`QE&_WOvr@LBC)+f(BN9} z^$0eoTfI*qF)f)9%BsGD;0GaD_-SyyFcJQD(?WGc%{MMHLXAxoOFlC~xsL2wn5a!uYdY&SYcai<5Xx$9h0JDS)oE4$Zh1_KXba)@Dz6Pu>z6NX{ zh_NS}^}ZeTCx-12EDPlXBqm|@NM)OX1DKZM;>d$s)5A(^D)gc*`*15c2lmeO}J?gAaKharWT7#rvo~BMteiO_lM21S`L_&ixGId07SSr_4=wJ92d(#+7IT8Q> N002ovPDHLkV1kFfB{={9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2fa2462d98aa117d2f15f3dd23f5ac78dc3e6b97 GIT binary patch literal 1939 zcmV;E2WP)Px+O-V#SRCodHoLz`jRTRf()SAl5Y@jTy%m`5fAt zLV79C9x^ne2Oo-1N;Am5A2j;}gB}bkrOYz>5>kgE{L1O~Ki)I$yYF86&iy!Z&ppk8 zHD~X&_Bwm5|5)~-(&NGnb9x{-VS#tsGLUfo00a_Togd%YYYG^3aiKY)se<@AW5k`aQHqSx#h<)=Ze&)d0SA!4JZ#;YZ=& z7C4=qoqwXc6aLp2b1w2_hOW6{9L=%}aQZT;xh!Bk9Nu1bmfqg)>0cH@0{v3Y#_ekL zFfC@^u#O3`E(2~rYS#nqlm1CceLdoKTOgnt2Ja}14ISRJY#Wv{+d{b6tAS7uuB;w# z-*r#mwRQ6{H?q*p^v1hgW$#2E$cAbzCrru!CtU@f?w>q|{e2Oiu0{8hrKkRGLATM+ zYtLb^p{rdCumfEN+)Posp3I$qU2GFx8F-_G2D-_xMf&89bZt8k)a)cDxDH+pw=!F& zl1*Q_!Stt1y!SG0O=Rr1=q7XSW1%tFzmI%&NQdlx>`tAV!R<=*Bt?P=0k5nQwE1v= z-K>c1W*2m2Zm%n7qjp`HtM~|6H?9xhnd%4V4`gG>!o$7}Th-USaA^1J1oF|?{VuzP zZfH6|o=Abi6XEIb*_Lkve;nOi@IS_w3z6Rw(qXG)d^My)COl`Qy25}9m=_R~?&x2` zpTa*OyM<$r0O>arD}&0D!iOTd*CM(b`gAzZu2fcY2_m#ld=&YNzF@rux{>ff*rG|E zNz!HT5cq&$v@^9SMCKBtRxNYWpq%4rv^Sz3?a2C?WbZ*b&-Do^8+TNSIy$gZZPlkE zMB|ySTZ%$}8;a18sa!nm^!?@rU*Po3H7{d6amM-0@e&jQTr%k>v25}qbozIbO%;!) z(qsD#hds_%w>b^FGhBhw-r=}35%4u`iQFz>fs&oR9u~%;6Tl(*S;E4AODwBlD+9IB@*-@}97AygxnBE($a=WY z*yMr~0$j4z+7(Mdd4-aoJ%kFvR@;hWD-{VyRD$XdM$e=MXc$<~K-*aSZ<`}P=-b{iaadb~3Vb^< z^8tXddT@9Iw&(>*fqY&>_fe`V47dsiF!g3Oc;iX~))8P8_geTY^#i2s%a;Rk+3JyZ zen^K*tCG#Bt`Idh4W@?U3dSnSEn3cnvQ`PQ8)F!5#jbA?NLO&GF)${+9_2DvOQ5zQ zGVeYdUKnv&>)Pid`o$65x`?j-Dx4hrA|4<(q%Y!?-5l#K3#7lk1gE>^#{OTDmXBP7 z2VD^_o*}N-+IDHgb{)2dBKrDNT(O7Y!7o31@YS8U?s>RktLRUMgF8s>*cP|98T0Fj zYA$i;y;ud2IJ!5jtWca1yoGJmRm!R1b+m9|@CPS#;8v5#0F{X;H91upjBow^sgz6@ zKLb7vTl7-t)B|_GldM-0b%9$80>sb_*Zm{ETIQ;GERJPaCNc#V-}C$PRQF2YXcnu9 z%)rIp{Qt1p1Gw2`B1>>r0C0@*^DhBXkny&eWg-)B@v&+D%%Uml+!VZON7jNpuy+2n z)u#>aJkm8drJXN}9w&zPEM+d_WJsfBBCE`8+CLC#DbNj;iLAhF|9^euUR6M@=ZD#u zfGfZ7r~UtFn+I^MWg;_hUB4P>59kdo<|sFbcHF{@g_KWSKg&e!GdG{;;uQc_D-*c~ Z_dhAqjPKM=KSTfk002ovPDHLkV1lgwsDc0h literal 0 HcmV?d00001 diff --git a/examples/ASDKTube/Sample/Nodes/VideoContentCell.m b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m index f5fdd4b5c5..f38757bbd0 100644 --- a/examples/ASDKTube/Sample/Nodes/VideoContentCell.m +++ b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m @@ -25,6 +25,7 @@ ASNetworkImageNode *_avatarNode; ASVideoPlayerNode *_videoPlayerNode; ASControlNode *_likeButtonNode; + ASButtonNode *_muteButtonNode; } - (instancetype)initWithVideoObject:(VideoModel *)video @@ -52,10 +53,16 @@ _likeButtonNode.backgroundColor = [UIColor redColor]; [self addSubnode:_likeButtonNode]; - _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:_videoModel.url]; + _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; } @@ -98,55 +105,113 @@ 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.isPlaying) { + if (_videoPlayerNode.playerState == ASVideoNodePlayerStatePlaying) { NSLog(@"TRANSITION"); + _videoPlayerNode.controlsDisabled = !_videoPlayerNode.controlsDisabled; [_videoPlayerNode pause]; } else { [_videoPlayerNode play]; } } -/*- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer +- (NSDictionary *)videoPlayerNodeCustomControls:(ASVideoPlayerNode *)videoPlayer { - return @[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]; -}*/ + return @{ + @"muteControl" : _muteButtonNode + }; +} -/*- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer forControls:(NSDictionary *)controls forMaximumSize:(CGSize)maxSize +- (NSArray *)controlsForControlBar:(NSDictionary *)availableControls { - NSMutableArray *bottomControls = [[NSMutableArray alloc] init]; + NSMutableArray *controls = [[NSMutableArray alloc] init]; - ASDisplayNode *playbackButtonNode = controls[@(ASVideoPlayerNodeControlTypePlaybackButton)]; - - if (playbackButtonNode) { - [bottomControls addObject:playbackButtonNode]; + 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:bottomControls]; + spacing:10.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children: controlBarControls ]; 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 ]]; - + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + children:@[topBarInsetSpec, spacer, controlbarInsetSpec]]; return mainVerticalStack; -}*/ + +} @end