├── HSPlayerDemo ├── HSPlayerDemo │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── HSPlayerViewController.h │ ├── HSAppDelegate.h │ ├── HSPlayerDemo-Prefix.pch │ ├── main.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── HSPlayerDemo-Info.plist │ ├── HSPlayerViewController.m │ ├── HSAppDelegate.m │ └── HSMain.xib ├── HSPlayerDemoTests │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── HSPlayerDemoTests-Info.plist │ └── HSPlayerDemoTests.m └── HSPlayerDemo.xcodeproj │ └── project.pbxproj ├── .gitignore ├── HSSlider.h ├── README.markdown ├── HSPlayerView.h ├── LICENSE ├── HSSlider.m └── HSPlayerView.m /HSPlayerDemo/HSPlayerDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemoTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo/HSPlayerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSPlayerViewController.h 3 | // HSPlayer 4 | // 5 | // Created by Simon Blommegård on 2011-11-26. 6 | // Copyright (c) 2011 Doubleint. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSPlayerViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | 20 | # Cocoapods 21 | Podfile.lock 22 | Pods 23 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo/HSAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSAppDelegate.h 3 | // HSPlayerDemo 4 | // 5 | // Created by Simon Blommegård on 2013-10-11. 6 | // Copyright (c) 2013 Simon Blommegård. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) IBOutlet UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo/HSPlayerDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // HSPlayerDemo 4 | // 5 | // Created by Simon Blommegård on 2013-10-11. 6 | // Copyright (c) 2013 Simon Blommegård. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "HSAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([HSAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /HSSlider.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSSlider.h 3 | // SBSlider 4 | // 5 | // Created by Simon Blommegård on 2011-11-29. 6 | // Copyright (c) 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSSlider : UIControl 12 | @property (nonatomic, assign) float value; // observable 13 | @property (nonatomic, assign) float minimumValue; 14 | @property (nonatomic, assign) float maximumValue; 15 | 16 | @property (nonatomic, assign, getter=isContinuous) BOOL continuous; // defaults to YES 17 | 18 | @property (nonatomic, strong) UIColor *strokeColor; // defaults to white 19 | @property (nonatomic, strong) UIColor *fillColor; // defaults to white 20 | @end 21 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # HSPlayer 2 | This is a simple video player built using AVFoundation with a deoplyment target of 4.0 3 | Built just for fun and as a good reference of a clean player implementation. 4 | 5 | Interface: 6 | ```objective-c 7 | @property (nonatomic, strong, readonly) AVPlayer *player; 8 | 9 | // Start player by setting the URL 10 | @property (nonatomic, copy) NSURL *URL; 11 | 12 | @property (nonatomic, assign) BOOL playing; 13 | 14 | @property (nonatomic, assign) BOOL controlsVisible; 15 | - (void)setControlsVisible:(BOOL)controlsVisible animated:(BOOL)animated; 16 | 17 | // Hides statusBar if true, defaults to YES 18 | @property (nonatomic, assign) BOOL fullScreen; 19 | ``` 20 | 21 | ## License 22 | HSPlayer is released under the MIT-license (see the LICENSE file) 23 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemoTests/HSPlayerDemoTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.doubleint.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemoTests/HSPlayerDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSPlayerDemoTests.m 3 | // HSPlayerDemoTests 4 | // 5 | // Created by Simon Blommegård on 2013-10-11. 6 | // Copyright (c) 2013 Simon Blommegård. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HSPlayerDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation HSPlayerDemoTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /HSPlayerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // HSPLayerView.h 3 | // HSPlayer 4 | // 5 | // Created by Simon Blommegård on 2011-11-26. 6 | // Copyright (c) 2011 Doubleint. All rights reserved. 7 | // 8 | // ARC, 4.0 minimum. 9 | // All properties are observable 10 | 11 | #import 12 | #import 13 | 14 | @class AVPlayer, AVPlayerLayer; 15 | 16 | @interface HSPlayerView : UIView 17 | @property (nonatomic, strong, readonly) AVPlayer *player; 18 | 19 | // Start player by setting the URL 20 | @property (nonatomic, copy) NSURL *URL; 21 | 22 | @property (nonatomic, assign) BOOL playing; 23 | 24 | @property (nonatomic, assign) BOOL controlsVisible; 25 | - (void)setControlsVisible:(BOOL)controlsVisible animated:(BOOL)animated; 26 | 27 | // Hides statusBar if true, defaults to YES 28 | @property (nonatomic, assign) BOOL fullScreen; 29 | @end 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-2013 by Simon Blommegård 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo/HSPlayerDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.doubleint.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | NSMainNibFile 28 | HSMain 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo/HSPlayerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSPlayerViewController.m 3 | // HSPlayer 4 | // 5 | // Created by Simon Blommegård on 2011-11-26. 6 | // Copyright (c) 2011 Doubleint. All rights reserved. 7 | // 8 | 9 | #import "HSPlayerViewController.h" 10 | #import "HSPlayerView.h" 11 | 12 | @interface HSPlayerViewController () 13 | @property (nonatomic, strong) HSPlayerView *playerView; 14 | @end 15 | 16 | @implementation HSPlayerViewController 17 | 18 | - (id)initWithCoder:(NSCoder *)aDecoder { 19 | if (self = [super initWithCoder:aDecoder]){ 20 | [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackTranslucent]; 21 | } 22 | return self; 23 | } 24 | 25 | - (BOOL)wantsFullScreenLayout { 26 | return YES; 27 | } 28 | 29 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { 30 | return YES; 31 | } 32 | 33 | #pragma mark - View lifecycle 34 | 35 | - (void)viewDidLoad { 36 | [super viewDidLoad]; 37 | 38 | [self setPlayerView:[[HSPlayerView alloc] initWithFrame:self.view.frame]]; 39 | [self.playerView setAutoresizingMask:(UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth)]; 40 | 41 | [self.view addSubview:self.playerView]; 42 | } 43 | 44 | - (void)viewDidAppear:(BOOL)animated { 45 | [super viewDidAppear:animated]; 46 | 47 | [self.playerView setURL:[NSURL URLWithString:@"http://devimages.apple.com/iphone/samples/bipbop/gear4/prog_index.m3u8"]]; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo/HSAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSAppDelegate.m 3 | // HSPlayerDemo 4 | // 5 | // Created by Simon Blommegård on 2013-10-11. 6 | // Copyright (c) 2013 Simon Blommegård. All rights reserved. 7 | // 8 | 9 | #import "HSAppDelegate.h" 10 | 11 | @implementation HSAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo/HSMain.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /HSSlider.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSSlider.m 3 | // SBSlider 4 | // 5 | // Created by Simon Blommegård on 2011-11-29. 6 | // Copyright (c) 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "HSSlider.h" 10 | 11 | @interface HSSlider () 12 | - (void)setup; 13 | - (float)valueFromTouch:(UITouch *)touch; 14 | @end 15 | 16 | @implementation HSSlider 17 | @synthesize value = _value; 18 | @synthesize minimumValue = _minimumValue; 19 | @synthesize maximumValue = _maximumValue; 20 | @synthesize continuous = _continuous; 21 | 22 | @synthesize strokeColor = _strokeColor; 23 | @synthesize fillColor = _fillColor; 24 | 25 | - (id)init { 26 | if ((self = [super init])) 27 | [self setup]; 28 | 29 | return self; 30 | } 31 | 32 | - (id)initWithFrame:(CGRect)frame { 33 | if ((self = [super initWithFrame:frame])) 34 | [self setup]; 35 | 36 | return self; 37 | } 38 | 39 | - (id)initWithCoder:(NSCoder *)aDecoder { 40 | if ((self = [super initWithCoder:aDecoder])) 41 | [self setup]; 42 | 43 | return self; 44 | } 45 | 46 | - (void)drawRect:(CGRect)rect { 47 | UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectInset(self.bounds, .5, .5)]; 48 | 49 | [self.strokeColor setStroke]; 50 | [path stroke]; 51 | 52 | CGRect barRect = CGRectInset(self.bounds, 2., 2.); 53 | CGFloat barWidth = barRect.size.width; 54 | 55 | float length = self.maximumValue - self.minimumValue; 56 | float lenghtValue = self.value - self.minimumValue; 57 | float percent = lenghtValue / length; 58 | 59 | CGFloat width = percent*barWidth; 60 | 61 | path = [UIBezierPath bezierPathWithRect:CGRectMake(barRect.origin.x, barRect.origin.y, width, barRect.size.height)]; 62 | 63 | [self.fillColor setFill]; 64 | [path fill]; 65 | } 66 | 67 | - (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { 68 | BOOL returnValue = [super beginTrackingWithTouch:touch withEvent:event]; 69 | 70 | [self setValue:[self valueFromTouch:touch]]; 71 | 72 | if (self.continuous) 73 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 74 | 75 | return returnValue; 76 | } 77 | 78 | - (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { 79 | BOOL returnValue = [super continueTrackingWithTouch:touch withEvent:event]; 80 | 81 | [self setValue:[self valueFromTouch:touch]]; 82 | 83 | if (self.continuous) 84 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 85 | 86 | return returnValue; 87 | } 88 | 89 | - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { 90 | [super endTrackingWithTouch:touch withEvent:event]; 91 | 92 | [self setValue:[self valueFromTouch:touch]]; 93 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 94 | [self setNeedsDisplay]; 95 | } 96 | 97 | #pragma mark - Properties 98 | 99 | - (void)setValue:(float)value { 100 | if (value > self.maximumValue) 101 | value = self.maximumValue; 102 | 103 | if (value < self.minimumValue) 104 | value = self.minimumValue; 105 | 106 | _value = value; 107 | 108 | [self setNeedsDisplay]; 109 | } 110 | 111 | - (UIColor *)strokeColor { 112 | if (!_strokeColor) 113 | _strokeColor = [UIColor whiteColor]; 114 | return _strokeColor; 115 | } 116 | 117 | - (UIColor *)fillColor { 118 | if (!_fillColor) 119 | _fillColor = [UIColor whiteColor]; 120 | return _fillColor; 121 | } 122 | 123 | #pragma mark - Private 124 | 125 | - (void)setup { 126 | [self setMinimumValue:0.]; 127 | [self setMaximumValue:1.]; 128 | [self setValue:.5]; 129 | 130 | [self setContinuous:YES]; 131 | 132 | [self setBackgroundColor:[UIColor clearColor]]; 133 | } 134 | 135 | - (float)valueFromTouch:(UITouch *)touch { 136 | // Border inset 137 | CGFloat x = [touch locationInView:self].x-2; 138 | CGFloat width = self.bounds.size.width-4.; 139 | CGFloat percent = x / width; 140 | 141 | float length = self.maximumValue - self.minimumValue; 142 | 143 | return percent*length; 144 | } 145 | 146 | @end 147 | -------------------------------------------------------------------------------- /HSPlayerDemo/HSPlayerDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F40AA263180884470081F837 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F40AA262180884470081F837 /* Foundation.framework */; }; 11 | F40AA265180884470081F837 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F40AA264180884470081F837 /* CoreGraphics.framework */; }; 12 | F40AA267180884470081F837 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F40AA266180884470081F837 /* UIKit.framework */; }; 13 | F40AA26D180884470081F837 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F40AA26B180884470081F837 /* InfoPlist.strings */; }; 14 | F40AA26F180884470081F837 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F40AA26E180884470081F837 /* main.m */; }; 15 | F40AA273180884470081F837 /* HSAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F40AA272180884470081F837 /* HSAppDelegate.m */; }; 16 | F40AA27B180884470081F837 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F40AA27A180884470081F837 /* Images.xcassets */; }; 17 | F40AA282180884470081F837 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F40AA281180884470081F837 /* XCTest.framework */; }; 18 | F40AA283180884470081F837 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F40AA262180884470081F837 /* Foundation.framework */; }; 19 | F40AA284180884470081F837 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F40AA266180884470081F837 /* UIKit.framework */; }; 20 | F40AA28C180884470081F837 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F40AA28A180884470081F837 /* InfoPlist.strings */; }; 21 | F40AA28E180884470081F837 /* HSPlayerDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F40AA28D180884470081F837 /* HSPlayerDemoTests.m */; }; 22 | F40AA299180884740081F837 /* HSPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F40AA298180884740081F837 /* HSPlayerViewController.m */; }; 23 | F40AA29B180884B40081F837 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F40AA29A180884B40081F837 /* AVFoundation.framework */; }; 24 | F40AA2A1180884E30081F837 /* HSPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = F40AA29E180884E30081F837 /* HSPlayerView.m */; }; 25 | F40AA2A2180884E30081F837 /* HSSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = F40AA2A0180884E30081F837 /* HSSlider.m */; }; 26 | F40AA2A4180885300081F837 /* HSMain.xib in Resources */ = {isa = PBXBuildFile; fileRef = F40AA2A3180885300081F837 /* HSMain.xib */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | F40AA285180884470081F837 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = F40AA257180884470081F837 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = F40AA25E180884470081F837; 35 | remoteInfo = HSPlayerDemo; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | F40AA25F180884470081F837 /* HSPlayerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HSPlayerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | F40AA262180884470081F837 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 42 | F40AA264180884470081F837 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 43 | F40AA266180884470081F837 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 44 | F40AA26A180884470081F837 /* HSPlayerDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "HSPlayerDemo-Info.plist"; sourceTree = ""; }; 45 | F40AA26C180884470081F837 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 46 | F40AA26E180884470081F837 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 47 | F40AA270180884470081F837 /* HSPlayerDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "HSPlayerDemo-Prefix.pch"; sourceTree = ""; }; 48 | F40AA271180884470081F837 /* HSAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HSAppDelegate.h; sourceTree = ""; }; 49 | F40AA272180884470081F837 /* HSAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HSAppDelegate.m; sourceTree = ""; }; 50 | F40AA27A180884470081F837 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 51 | F40AA280180884470081F837 /* HSPlayerDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HSPlayerDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | F40AA281180884470081F837 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 53 | F40AA289180884470081F837 /* HSPlayerDemoTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "HSPlayerDemoTests-Info.plist"; sourceTree = ""; }; 54 | F40AA28B180884470081F837 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 55 | F40AA28D180884470081F837 /* HSPlayerDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HSPlayerDemoTests.m; sourceTree = ""; }; 56 | F40AA297180884740081F837 /* HSPlayerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSPlayerViewController.h; sourceTree = ""; }; 57 | F40AA298180884740081F837 /* HSPlayerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSPlayerViewController.m; sourceTree = ""; }; 58 | F40AA29A180884B40081F837 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 59 | F40AA29D180884E30081F837 /* HSPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSPlayerView.h; path = ../HSPlayerView.h; sourceTree = ""; }; 60 | F40AA29E180884E30081F837 /* HSPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSPlayerView.m; path = ../HSPlayerView.m; sourceTree = ""; }; 61 | F40AA29F180884E30081F837 /* HSSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSSlider.h; path = ../HSSlider.h; sourceTree = ""; }; 62 | F40AA2A0180884E30081F837 /* HSSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSSlider.m; path = ../HSSlider.m; sourceTree = ""; }; 63 | F40AA2A3180885300081F837 /* HSMain.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HSMain.xib; sourceTree = ""; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | F40AA25C180884470081F837 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | F40AA29B180884B40081F837 /* AVFoundation.framework in Frameworks */, 72 | F40AA265180884470081F837 /* CoreGraphics.framework in Frameworks */, 73 | F40AA267180884470081F837 /* UIKit.framework in Frameworks */, 74 | F40AA263180884470081F837 /* Foundation.framework in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | F40AA27D180884470081F837 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | F40AA282180884470081F837 /* XCTest.framework in Frameworks */, 83 | F40AA284180884470081F837 /* UIKit.framework in Frameworks */, 84 | F40AA283180884470081F837 /* Foundation.framework in Frameworks */, 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | F40AA256180884470081F837 = { 92 | isa = PBXGroup; 93 | children = ( 94 | F40AA29C180884C00081F837 /* HSPlayer */, 95 | F40AA268180884470081F837 /* HSPlayerDemo */, 96 | F40AA287180884470081F837 /* HSPlayerDemoTests */, 97 | F40AA261180884470081F837 /* Frameworks */, 98 | F40AA260180884470081F837 /* Products */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | F40AA260180884470081F837 /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | F40AA25F180884470081F837 /* HSPlayerDemo.app */, 106 | F40AA280180884470081F837 /* HSPlayerDemoTests.xctest */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | F40AA261180884470081F837 /* Frameworks */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | F40AA29A180884B40081F837 /* AVFoundation.framework */, 115 | F40AA262180884470081F837 /* Foundation.framework */, 116 | F40AA264180884470081F837 /* CoreGraphics.framework */, 117 | F40AA266180884470081F837 /* UIKit.framework */, 118 | F40AA281180884470081F837 /* XCTest.framework */, 119 | ); 120 | name = Frameworks; 121 | sourceTree = ""; 122 | }; 123 | F40AA268180884470081F837 /* HSPlayerDemo */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | F40AA297180884740081F837 /* HSPlayerViewController.h */, 127 | F40AA298180884740081F837 /* HSPlayerViewController.m */, 128 | F40AA271180884470081F837 /* HSAppDelegate.h */, 129 | F40AA272180884470081F837 /* HSAppDelegate.m */, 130 | F40AA27A180884470081F837 /* Images.xcassets */, 131 | F40AA269180884470081F837 /* Supporting Files */, 132 | F40AA2A3180885300081F837 /* HSMain.xib */, 133 | ); 134 | path = HSPlayerDemo; 135 | sourceTree = ""; 136 | }; 137 | F40AA269180884470081F837 /* Supporting Files */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | F40AA26A180884470081F837 /* HSPlayerDemo-Info.plist */, 141 | F40AA26B180884470081F837 /* InfoPlist.strings */, 142 | F40AA26E180884470081F837 /* main.m */, 143 | F40AA270180884470081F837 /* HSPlayerDemo-Prefix.pch */, 144 | ); 145 | name = "Supporting Files"; 146 | sourceTree = ""; 147 | }; 148 | F40AA287180884470081F837 /* HSPlayerDemoTests */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | F40AA28D180884470081F837 /* HSPlayerDemoTests.m */, 152 | F40AA288180884470081F837 /* Supporting Files */, 153 | ); 154 | path = HSPlayerDemoTests; 155 | sourceTree = ""; 156 | }; 157 | F40AA288180884470081F837 /* Supporting Files */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | F40AA289180884470081F837 /* HSPlayerDemoTests-Info.plist */, 161 | F40AA28A180884470081F837 /* InfoPlist.strings */, 162 | ); 163 | name = "Supporting Files"; 164 | sourceTree = ""; 165 | }; 166 | F40AA29C180884C00081F837 /* HSPlayer */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | F40AA29D180884E30081F837 /* HSPlayerView.h */, 170 | F40AA29E180884E30081F837 /* HSPlayerView.m */, 171 | F40AA29F180884E30081F837 /* HSSlider.h */, 172 | F40AA2A0180884E30081F837 /* HSSlider.m */, 173 | ); 174 | name = HSPlayer; 175 | sourceTree = ""; 176 | }; 177 | /* End PBXGroup section */ 178 | 179 | /* Begin PBXNativeTarget section */ 180 | F40AA25E180884470081F837 /* HSPlayerDemo */ = { 181 | isa = PBXNativeTarget; 182 | buildConfigurationList = F40AA291180884470081F837 /* Build configuration list for PBXNativeTarget "HSPlayerDemo" */; 183 | buildPhases = ( 184 | F40AA25B180884470081F837 /* Sources */, 185 | F40AA25C180884470081F837 /* Frameworks */, 186 | F40AA25D180884470081F837 /* Resources */, 187 | ); 188 | buildRules = ( 189 | ); 190 | dependencies = ( 191 | ); 192 | name = HSPlayerDemo; 193 | productName = HSPlayerDemo; 194 | productReference = F40AA25F180884470081F837 /* HSPlayerDemo.app */; 195 | productType = "com.apple.product-type.application"; 196 | }; 197 | F40AA27F180884470081F837 /* HSPlayerDemoTests */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = F40AA294180884470081F837 /* Build configuration list for PBXNativeTarget "HSPlayerDemoTests" */; 200 | buildPhases = ( 201 | F40AA27C180884470081F837 /* Sources */, 202 | F40AA27D180884470081F837 /* Frameworks */, 203 | F40AA27E180884470081F837 /* Resources */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | F40AA286180884470081F837 /* PBXTargetDependency */, 209 | ); 210 | name = HSPlayerDemoTests; 211 | productName = HSPlayerDemoTests; 212 | productReference = F40AA280180884470081F837 /* HSPlayerDemoTests.xctest */; 213 | productType = "com.apple.product-type.bundle.unit-test"; 214 | }; 215 | /* End PBXNativeTarget section */ 216 | 217 | /* Begin PBXProject section */ 218 | F40AA257180884470081F837 /* Project object */ = { 219 | isa = PBXProject; 220 | attributes = { 221 | CLASSPREFIX = HS; 222 | LastUpgradeCheck = 0500; 223 | ORGANIZATIONNAME = "Simon Blommegård"; 224 | TargetAttributes = { 225 | F40AA27F180884470081F837 = { 226 | TestTargetID = F40AA25E180884470081F837; 227 | }; 228 | }; 229 | }; 230 | buildConfigurationList = F40AA25A180884470081F837 /* Build configuration list for PBXProject "HSPlayerDemo" */; 231 | compatibilityVersion = "Xcode 3.2"; 232 | developmentRegion = English; 233 | hasScannedForEncodings = 0; 234 | knownRegions = ( 235 | en, 236 | Base, 237 | ); 238 | mainGroup = F40AA256180884470081F837; 239 | productRefGroup = F40AA260180884470081F837 /* Products */; 240 | projectDirPath = ""; 241 | projectRoot = ""; 242 | targets = ( 243 | F40AA25E180884470081F837 /* HSPlayerDemo */, 244 | F40AA27F180884470081F837 /* HSPlayerDemoTests */, 245 | ); 246 | }; 247 | /* End PBXProject section */ 248 | 249 | /* Begin PBXResourcesBuildPhase section */ 250 | F40AA25D180884470081F837 /* Resources */ = { 251 | isa = PBXResourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | F40AA27B180884470081F837 /* Images.xcassets in Resources */, 255 | F40AA26D180884470081F837 /* InfoPlist.strings in Resources */, 256 | F40AA2A4180885300081F837 /* HSMain.xib in Resources */, 257 | ); 258 | runOnlyForDeploymentPostprocessing = 0; 259 | }; 260 | F40AA27E180884470081F837 /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | F40AA28C180884470081F837 /* InfoPlist.strings in Resources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXResourcesBuildPhase section */ 269 | 270 | /* Begin PBXSourcesBuildPhase section */ 271 | F40AA25B180884470081F837 /* Sources */ = { 272 | isa = PBXSourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | F40AA2A1180884E30081F837 /* HSPlayerView.m in Sources */, 276 | F40AA2A2180884E30081F837 /* HSSlider.m in Sources */, 277 | F40AA26F180884470081F837 /* main.m in Sources */, 278 | F40AA299180884740081F837 /* HSPlayerViewController.m in Sources */, 279 | F40AA273180884470081F837 /* HSAppDelegate.m in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | F40AA27C180884470081F837 /* Sources */ = { 284 | isa = PBXSourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | F40AA28E180884470081F837 /* HSPlayerDemoTests.m in Sources */, 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | /* End PBXSourcesBuildPhase section */ 292 | 293 | /* Begin PBXTargetDependency section */ 294 | F40AA286180884470081F837 /* PBXTargetDependency */ = { 295 | isa = PBXTargetDependency; 296 | target = F40AA25E180884470081F837 /* HSPlayerDemo */; 297 | targetProxy = F40AA285180884470081F837 /* PBXContainerItemProxy */; 298 | }; 299 | /* End PBXTargetDependency section */ 300 | 301 | /* Begin PBXVariantGroup section */ 302 | F40AA26B180884470081F837 /* InfoPlist.strings */ = { 303 | isa = PBXVariantGroup; 304 | children = ( 305 | F40AA26C180884470081F837 /* en */, 306 | ); 307 | name = InfoPlist.strings; 308 | sourceTree = ""; 309 | }; 310 | F40AA28A180884470081F837 /* InfoPlist.strings */ = { 311 | isa = PBXVariantGroup; 312 | children = ( 313 | F40AA28B180884470081F837 /* en */, 314 | ); 315 | name = InfoPlist.strings; 316 | sourceTree = ""; 317 | }; 318 | /* End PBXVariantGroup section */ 319 | 320 | /* Begin XCBuildConfiguration section */ 321 | F40AA28F180884470081F837 /* Debug */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ALWAYS_SEARCH_USER_PATHS = NO; 325 | ARCHS = "$(ARCHS_STANDARD)"; 326 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 327 | CLANG_CXX_LIBRARY = "libc++"; 328 | CLANG_ENABLE_MODULES = YES; 329 | CLANG_ENABLE_OBJC_ARC = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_CONSTANT_CONVERSION = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_EMPTY_BODY = YES; 334 | CLANG_WARN_ENUM_CONVERSION = YES; 335 | CLANG_WARN_INT_CONVERSION = YES; 336 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 337 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 338 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 339 | COPY_PHASE_STRIP = NO; 340 | GCC_C_LANGUAGE_STANDARD = gnu99; 341 | GCC_DYNAMIC_NO_PIC = NO; 342 | GCC_OPTIMIZATION_LEVEL = 0; 343 | GCC_PREPROCESSOR_DEFINITIONS = ( 344 | "DEBUG=1", 345 | "$(inherited)", 346 | ); 347 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 350 | GCC_WARN_UNDECLARED_SELECTOR = YES; 351 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 352 | GCC_WARN_UNUSED_FUNCTION = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 355 | ONLY_ACTIVE_ARCH = YES; 356 | SDKROOT = iphoneos; 357 | }; 358 | name = Debug; 359 | }; 360 | F40AA290180884470081F837 /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ALWAYS_SEARCH_USER_PATHS = NO; 364 | ARCHS = "$(ARCHS_STANDARD)"; 365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 366 | CLANG_CXX_LIBRARY = "libc++"; 367 | CLANG_ENABLE_MODULES = YES; 368 | CLANG_ENABLE_OBJC_ARC = YES; 369 | CLANG_WARN_BOOL_CONVERSION = YES; 370 | CLANG_WARN_CONSTANT_CONVERSION = YES; 371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 372 | CLANG_WARN_EMPTY_BODY = YES; 373 | CLANG_WARN_ENUM_CONVERSION = YES; 374 | CLANG_WARN_INT_CONVERSION = YES; 375 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 376 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 377 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 378 | COPY_PHASE_STRIP = YES; 379 | ENABLE_NS_ASSERTIONS = NO; 380 | GCC_C_LANGUAGE_STANDARD = gnu99; 381 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 382 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 383 | GCC_WARN_UNDECLARED_SELECTOR = YES; 384 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 385 | GCC_WARN_UNUSED_FUNCTION = YES; 386 | GCC_WARN_UNUSED_VARIABLE = YES; 387 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 388 | SDKROOT = iphoneos; 389 | VALIDATE_PRODUCT = YES; 390 | }; 391 | name = Release; 392 | }; 393 | F40AA292180884470081F837 /* Debug */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 397 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 398 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 399 | GCC_PREFIX_HEADER = "HSPlayerDemo/HSPlayerDemo-Prefix.pch"; 400 | INFOPLIST_FILE = "HSPlayerDemo/HSPlayerDemo-Info.plist"; 401 | IPHONEOS_DEPLOYMENT_TARGET = 4.0; 402 | PRODUCT_NAME = "$(TARGET_NAME)"; 403 | WRAPPER_EXTENSION = app; 404 | }; 405 | name = Debug; 406 | }; 407 | F40AA293180884470081F837 /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 411 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 412 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 413 | GCC_PREFIX_HEADER = "HSPlayerDemo/HSPlayerDemo-Prefix.pch"; 414 | INFOPLIST_FILE = "HSPlayerDemo/HSPlayerDemo-Info.plist"; 415 | IPHONEOS_DEPLOYMENT_TARGET = 4.0; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | WRAPPER_EXTENSION = app; 418 | }; 419 | name = Release; 420 | }; 421 | F40AA295180884470081F837 /* Debug */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 425 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/HSPlayerDemo.app/HSPlayerDemo"; 426 | FRAMEWORK_SEARCH_PATHS = ( 427 | "$(SDKROOT)/Developer/Library/Frameworks", 428 | "$(inherited)", 429 | "$(DEVELOPER_FRAMEWORKS_DIR)", 430 | ); 431 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 432 | GCC_PREFIX_HEADER = "HSPlayerDemo/HSPlayerDemo-Prefix.pch"; 433 | GCC_PREPROCESSOR_DEFINITIONS = ( 434 | "DEBUG=1", 435 | "$(inherited)", 436 | ); 437 | INFOPLIST_FILE = "HSPlayerDemoTests/HSPlayerDemoTests-Info.plist"; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | TEST_HOST = "$(BUNDLE_LOADER)"; 440 | WRAPPER_EXTENSION = xctest; 441 | }; 442 | name = Debug; 443 | }; 444 | F40AA296180884470081F837 /* Release */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 448 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/HSPlayerDemo.app/HSPlayerDemo"; 449 | FRAMEWORK_SEARCH_PATHS = ( 450 | "$(SDKROOT)/Developer/Library/Frameworks", 451 | "$(inherited)", 452 | "$(DEVELOPER_FRAMEWORKS_DIR)", 453 | ); 454 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 455 | GCC_PREFIX_HEADER = "HSPlayerDemo/HSPlayerDemo-Prefix.pch"; 456 | INFOPLIST_FILE = "HSPlayerDemoTests/HSPlayerDemoTests-Info.plist"; 457 | PRODUCT_NAME = "$(TARGET_NAME)"; 458 | TEST_HOST = "$(BUNDLE_LOADER)"; 459 | WRAPPER_EXTENSION = xctest; 460 | }; 461 | name = Release; 462 | }; 463 | /* End XCBuildConfiguration section */ 464 | 465 | /* Begin XCConfigurationList section */ 466 | F40AA25A180884470081F837 /* Build configuration list for PBXProject "HSPlayerDemo" */ = { 467 | isa = XCConfigurationList; 468 | buildConfigurations = ( 469 | F40AA28F180884470081F837 /* Debug */, 470 | F40AA290180884470081F837 /* Release */, 471 | ); 472 | defaultConfigurationIsVisible = 0; 473 | defaultConfigurationName = Release; 474 | }; 475 | F40AA291180884470081F837 /* Build configuration list for PBXNativeTarget "HSPlayerDemo" */ = { 476 | isa = XCConfigurationList; 477 | buildConfigurations = ( 478 | F40AA292180884470081F837 /* Debug */, 479 | F40AA293180884470081F837 /* Release */, 480 | ); 481 | defaultConfigurationIsVisible = 0; 482 | }; 483 | F40AA294180884470081F837 /* Build configuration list for PBXNativeTarget "HSPlayerDemoTests" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | F40AA295180884470081F837 /* Debug */, 487 | F40AA296180884470081F837 /* Release */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | }; 491 | /* End XCConfigurationList section */ 492 | }; 493 | rootObject = F40AA257180884470081F837 /* Project object */; 494 | } 495 | -------------------------------------------------------------------------------- /HSPlayerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // HSPLayerView.m 3 | // HSPlayer 4 | // 5 | // Created by Simon Blommegård on 2011-11-26. 6 | // Copyright (c) 2011 Doubleint. All rights reserved. 7 | // 8 | 9 | #import "HSPlayerView.h" 10 | #import "HSSlider.h" 11 | #import 12 | #import 13 | #import 14 | 15 | // Constants 16 | CGFloat const HSPlayerViewControlsAnimationDelay = .4f; // ~ statusbar fade duration 17 | CGFloat const HSPlayerViewAutoHideControlsDelay = 4.f; 18 | 19 | // Contexts for KVO 20 | static void *HSPlayerViewPlayerRateObservationContext = &HSPlayerViewPlayerRateObservationContext; 21 | static void *HSPlayerViewPlayerCurrentItemObservationContext = &HSPlayerViewPlayerCurrentItemObservationContext; 22 | static void *HSPlayerViewPlayerAirPlayVideoActiveObservationContext = &HSPlayerViewPlayerAirPlayVideoActiveObservationContext; 23 | static void *HSPlayerViewPlayerItemStatusObservationContext = &HSPlayerViewPlayerItemStatusObservationContext; 24 | static void *HSPlayerViewPlaterItemDurationObservationContext = &HSPlayerViewPlaterItemDurationObservationContext; 25 | static void *HSPlayerViewPlayerLayerReadyForDisplayObservationContext = &HSPlayerViewPlayerLayerReadyForDisplayObservationContext; 26 | 27 | @interface HSPlayerView () 28 | @property (nonatomic, strong, readwrite) AVPlayer *player; 29 | @property (nonatomic, readonly) AVPlayerLayer *playerLayer; 30 | 31 | @property (nonatomic, strong) AVAsset *asset; 32 | @property (nonatomic, strong) AVPlayerItem *playerItem; 33 | @property (nonatomic, assign) CMTime duration; 34 | 35 | @property (nonatomic, strong) id playerTimeObserver; 36 | 37 | @property (nonatomic, assign) BOOL seekToZeroBeforePlay; 38 | @property (nonatomic, assign) BOOL readyForDisplayTriggered; 39 | 40 | // Array of UIView-subclasses 41 | @property (nonatomic, strong) NSArray *controls; 42 | 43 | // Controls 44 | @property (nonatomic, strong) UIView *topControlView; 45 | @property (nonatomic, strong) HSSlider *scrubberControlSlider; 46 | @property (nonatomic, strong) UILabel *currentPlayerTimeLabel; 47 | @property (nonatomic, strong) UILabel *remainingPlayerTimeLabel; 48 | 49 | @property (nonatomic, strong) UIView *bottomControlView; 50 | @property (nonatomic, strong) UIButton *playPauseControlButton; 51 | 52 | @property (nonatomic, strong) NSTimer *autoHideControlsTimer; 53 | 54 | // Gesture Recognizers 55 | @property (nonatomic, strong) UITapGestureRecognizer *singleTapRecognizer; 56 | @property (nonatomic, strong) UITapGestureRecognizer *doubleTapRecognizer; 57 | 58 | // Scrubbing 59 | @property (nonatomic, assign, getter = isScrubbing) BOOL scrubbing; 60 | @property (nonatomic, assign) float restoreAfterScrubbingRate; 61 | 62 | // Custom images for controls 63 | @property (nonatomic, strong) UIImage *playImage; 64 | @property (nonatomic, strong) UIImage *pauseImage; 65 | @end 66 | 67 | @implementation HSPlayerView 68 | 69 | + (Class)layerClass { 70 | return [AVPlayerLayer class]; 71 | } 72 | 73 | + (void)initialize { 74 | if (self == [HSPlayerView class]) { 75 | NSError *error; 76 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error]; 77 | } 78 | } 79 | 80 | - (id)initWithFrame:(CGRect)frame { 81 | if ((self = [super initWithFrame:frame])) { 82 | [self.playerLayer setOpacity:0]; 83 | [self.playerLayer addObserver:self 84 | forKeyPath:@"readyForDisplay" 85 | options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) 86 | context:HSPlayerViewPlayerLayerReadyForDisplayObservationContext]; 87 | 88 | [self addGestureRecognizer:self.singleTapRecognizer]; 89 | [self addGestureRecognizer:self.doubleTapRecognizer]; 90 | 91 | // Add controls 92 | for (UIView *view in self.controls) 93 | [self addSubview:view]; 94 | 95 | [self setControlsVisible:NO]; 96 | [self setFullScreen:YES]; 97 | 98 | [self setRestoreAfterScrubbingRate:1.]; 99 | } 100 | 101 | return self; 102 | } 103 | 104 | #pragma mark KVO 105 | 106 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 107 | 108 | if (context == HSPlayerViewPlayerItemStatusObservationContext) { 109 | [self syncPlayPauseButton]; 110 | 111 | AVPlayerStatus status = [change[NSKeyValueChangeNewKey] integerValue]; 112 | switch (status) { 113 | case AVPlayerStatusUnknown: { 114 | [self removePlayerTimeObserver]; 115 | [self syncScrobber]; 116 | 117 | // Disable buttons & scrubber 118 | } 119 | break; 120 | 121 | case AVPlayerStatusReadyToPlay: { 122 | 123 | // Enable buttons & scrubber 124 | 125 | if (!self.isScrubbing) 126 | [self setPlaying:YES]; 127 | } 128 | break; 129 | 130 | case AVPlayerStatusFailed: { 131 | [self removePlayerTimeObserver]; 132 | [self syncScrobber]; 133 | 134 | // Disable buttons & scrubber 135 | } 136 | break; 137 | } 138 | } 139 | 140 | else if (context == HSPlayerViewPlayerRateObservationContext) { 141 | [self syncPlayPauseButton]; 142 | } 143 | 144 | // -replaceCurrentItemWithPlayerItem: && new 145 | else if (context == HSPlayerViewPlayerCurrentItemObservationContext) { 146 | AVPlayerItem *newPlayerItem = change[NSKeyValueChangeNewKey]; 147 | 148 | // Null? 149 | if (newPlayerItem == (id)[NSNull null]) { 150 | [self removePlayerTimeObserver]; 151 | 152 | // Disable buttons & scrubber 153 | } 154 | else { 155 | // New title 156 | [self syncPlayPauseButton]; 157 | [self addPlayerTimeObserver]; 158 | } 159 | } 160 | 161 | else if (context == HSPlayerViewPlaterItemDurationObservationContext) { 162 | [self syncScrobber]; 163 | } 164 | 165 | // Animate in the player layer 166 | else if (context == HSPlayerViewPlayerLayerReadyForDisplayObservationContext) { 167 | BOOL ready = [change[NSKeyValueChangeNewKey] boolValue]; 168 | if (ready && !self.readyForDisplayTriggered) { 169 | [self setReadyForDisplayTriggered:YES]; 170 | 171 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 172 | [animation setFromValue:@0.f]; 173 | [animation setToValue:@1.f]; 174 | [animation setDuration:1.]; 175 | [self.playerLayer addAnimation:animation forKey:nil]; 176 | [self.playerLayer setOpacity:1.]; 177 | } 178 | } 179 | 180 | else if (context == HSPlayerViewPlayerAirPlayVideoActiveObservationContext) { 181 | // Show/hide airplay-image 182 | } 183 | 184 | else 185 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 186 | } 187 | 188 | - (void)dealloc { 189 | [self removePlayerTimeObserver]; 190 | 191 | [self.player removeObserver:self forKeyPath:@"rate"]; 192 | [self.player removeObserver:self forKeyPath:@"currentItem"]; 193 | [self.playerItem removeObserver:self forKeyPath:@"status"]; 194 | [self.playerItem removeObserver:self forKeyPath:@"duration"]; 195 | [self.playerLayer removeObserver:self forKeyPath:@"readyForDisplay"]; 196 | 197 | if ([self.player respondsToSelector:@selector(allowsAirPlayVideo)]) 198 | [self.player removeObserver:self forKeyPath:@"airPlayVideoActive"]; 199 | 200 | [self.player pause]; 201 | } 202 | 203 | #pragma mark - Properties 204 | 205 | - (AVPlayer *)player { 206 | return [(AVPlayerLayer *)[self layer] player]; 207 | } 208 | 209 | - (void)setPlayer:(AVPlayer *)player { 210 | [(AVPlayerLayer *) [self layer] setPlayer:player]; 211 | 212 | // Optimize for airplay if possible 213 | if ([player respondsToSelector:@selector(allowsAirPlayVideo)]) { 214 | [player setAllowsAirPlayVideo:YES]; 215 | [player setUsesAirPlayVideoWhileAirPlayScreenIsActive:YES]; 216 | 217 | [player addObserver:self 218 | forKeyPath:@"airPlayVideoActive" 219 | options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) 220 | context:HSPlayerViewPlayerAirPlayVideoActiveObservationContext]; 221 | } 222 | } 223 | 224 | - (AVPlayerLayer *)playerLayer { 225 | return (AVPlayerLayer *)[self layer]; 226 | } 227 | 228 | - (void)setURL:(NSURL *)URL { 229 | _URL = [URL copy]; 230 | 231 | // Create Asset, and load 232 | 233 | [self setAsset:[AVURLAsset URLAssetWithURL:URL options:nil]]; 234 | NSArray *keys = @[@"tracks", @"playable"]; 235 | 236 | [self.asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{ 237 | dispatch_async(dispatch_get_main_queue(), ^{ 238 | 239 | // Displatch to main queue! 240 | [self doneLoadingAsset:self.asset withKeys:keys]; 241 | }); 242 | }]; 243 | } 244 | 245 | - (void)setFullScreen:(BOOL)fullScreen { 246 | _fullScreen = fullScreen; 247 | 248 | [[UIApplication sharedApplication] setStatusBarHidden:fullScreen withAnimation:UIStatusBarAnimationFade]; 249 | } 250 | 251 | - (CMTime)duration { 252 | // Pefered in HTTP Live Streaming. 253 | if ([self.playerItem respondsToSelector:@selector(duration)] && // 4.3 254 | self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) { 255 | if (CMTIME_IS_VALID(self.playerItem.duration)) 256 | return self.playerItem.duration; 257 | } 258 | 259 | else if (CMTIME_IS_VALID(self.player.currentItem.asset.duration)) 260 | return self.player.currentItem.asset.duration; 261 | 262 | return kCMTimeInvalid; 263 | } 264 | 265 | - (void)setControlsVisible:(BOOL)controlsVisible { 266 | [self setControlsVisible:controlsVisible animated:NO]; 267 | } 268 | 269 | - (void)setPlaying:(BOOL)playing { 270 | if (playing) { 271 | if (self.seekToZeroBeforePlay) { 272 | [self setSeekToZeroBeforePlay:NO]; 273 | [self.player seekToTime:kCMTimeZero]; 274 | } 275 | 276 | [self.player play]; 277 | 278 | if (self.controlsVisible) 279 | [self triggerAutoHideControlsTimer]; 280 | } 281 | else { 282 | [self.player pause]; 283 | 284 | if (self.controlsVisible) 285 | [self cancelAutoHideControlsTimer]; 286 | } 287 | } 288 | 289 | - (BOOL)playing { 290 | // return mRestoreAfterScrubbingRate != 0.f || 291 | return (self.player.rate > 0.); 292 | } 293 | 294 | #pragma mark - Controls 295 | 296 | - (NSArray *)controls { 297 | if (!_controls) { 298 | _controls = @[self.topControlView, 299 | self.bottomControlView]; 300 | } 301 | 302 | return _controls; 303 | } 304 | 305 | - (UIView *)topControlView { 306 | if (!_topControlView) { 307 | _topControlView = [[UIView alloc] initWithFrame:CGRectMake(0., 20., self.bounds.size.width, 20.)]; 308 | [_topControlView setBackgroundColor:[UIColor colorWithWhite:0. alpha:.5]]; 309 | [_topControlView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth)]; 310 | 311 | [self.currentPlayerTimeLabel setFrame:CGRectMake(10., 3., 55., 15.)]; 312 | [_topControlView addSubview:self.currentPlayerTimeLabel]; 313 | 314 | [self.remainingPlayerTimeLabel setFrame:CGRectMake(_topControlView.bounds.size.width-65., 3., 55., 15.)]; 315 | [_topControlView addSubview:self.remainingPlayerTimeLabel]; 316 | 317 | [self.scrubberControlSlider setFrame:CGRectMake(70., 3., self.bounds.size.width-140., 14.)]; 318 | [_topControlView addSubview:self.scrubberControlSlider]; 319 | } 320 | 321 | return _topControlView; 322 | } 323 | 324 | - (UILabel *)currentPlayerTimeLabel { 325 | if (!_currentPlayerTimeLabel) { 326 | _currentPlayerTimeLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 327 | [_currentPlayerTimeLabel setBackgroundColor:[UIColor clearColor]]; 328 | [_currentPlayerTimeLabel setTextColor:[UIColor whiteColor]]; 329 | [_currentPlayerTimeLabel setFont:[UIFont systemFontOfSize:12.]]; 330 | [_currentPlayerTimeLabel setTextAlignment:UITextAlignmentCenter]; 331 | } 332 | 333 | return _currentPlayerTimeLabel; 334 | } 335 | 336 | - (UILabel *)remainingPlayerTimeLabel { 337 | if (!_remainingPlayerTimeLabel) { 338 | _remainingPlayerTimeLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 339 | [_remainingPlayerTimeLabel setBackgroundColor:[UIColor clearColor]]; 340 | [_remainingPlayerTimeLabel setTextColor:[UIColor whiteColor]]; 341 | [_remainingPlayerTimeLabel setFont:[UIFont systemFontOfSize:12.]]; 342 | [_remainingPlayerTimeLabel setTextAlignment:UITextAlignmentCenter]; 343 | [_remainingPlayerTimeLabel setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin)]; 344 | } 345 | 346 | return _remainingPlayerTimeLabel; 347 | } 348 | 349 | - (HSSlider *)scrubberControlSlider { 350 | if (!_scrubberControlSlider) { 351 | _scrubberControlSlider = [[HSSlider alloc] initWithFrame:CGRectZero]; 352 | [_scrubberControlSlider setAutoresizingMask:(UIViewAutoresizingFlexibleWidth)]; 353 | 354 | [_scrubberControlSlider addTarget:self action:@selector(beginScrubbing:) forControlEvents:UIControlEventTouchDown]; 355 | [_scrubberControlSlider addTarget:self action:@selector(scrub:) forControlEvents:UIControlEventValueChanged]; 356 | [_scrubberControlSlider addTarget:self action:@selector(endScrubbing:) forControlEvents:UIControlEventTouchUpInside]; 357 | [_scrubberControlSlider addTarget:self action:@selector(endScrubbing:) forControlEvents:UIControlEventTouchUpOutside]; 358 | } 359 | 360 | return _scrubberControlSlider; 361 | } 362 | 363 | - (UIView *)bottomControlView { 364 | if (!_bottomControlView) { 365 | _bottomControlView = [[UIView alloc] initWithFrame:CGRectMake(0., self.bounds.size.height-40., self.bounds.size.width, 40.)]; 366 | [_bottomControlView setBackgroundColor:[UIColor colorWithWhite:0. alpha:.5]]; 367 | [_bottomControlView setAutoresizingMask:(UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth)]; 368 | 369 | MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(40., 11., _bottomControlView.bounds.size.width-50., 18.)]; 370 | [volumeView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth)]; 371 | [_bottomControlView addSubview:volumeView]; 372 | 373 | [self.playPauseControlButton setFrame:CGRectMake(10., 10., 20., 20.)]; 374 | [_bottomControlView addSubview:self.playPauseControlButton]; 375 | } 376 | 377 | return _bottomControlView; 378 | } 379 | 380 | - (UIButton *)playPauseControlButton { 381 | if (!_playPauseControlButton) { 382 | _playPauseControlButton = [UIButton buttonWithType:UIButtonTypeCustom]; 383 | [_playPauseControlButton setShowsTouchWhenHighlighted:YES]; 384 | [_playPauseControlButton setImage:self.playImage forState:UIControlStateNormal]; 385 | [_playPauseControlButton addTarget:self action:@selector(playPause:) forControlEvents:UIControlEventTouchUpInside]; 386 | } 387 | return _playPauseControlButton; 388 | } 389 | 390 | #pragma mark - 391 | 392 | - (UITapGestureRecognizer *)singleTapRecognizer { 393 | if (!_singleTapRecognizer) { 394 | _singleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsWithRecognizer:)]; 395 | // We can handle both single and double 396 | [_singleTapRecognizer requireGestureRecognizerToFail:self.doubleTapRecognizer]; 397 | [_singleTapRecognizer setDelegate:self]; 398 | } 399 | 400 | return _singleTapRecognizer; 401 | } 402 | 403 | - (UITapGestureRecognizer *)doubleTapRecognizer { 404 | if (!_doubleTapRecognizer) { 405 | _doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleVideoGravityWithRecognizer:)]; 406 | [_doubleTapRecognizer setNumberOfTapsRequired:2]; 407 | [_doubleTapRecognizer setDelegate:self]; 408 | } 409 | 410 | return _doubleTapRecognizer; 411 | } 412 | 413 | #pragma mark - Public 414 | 415 | - (void)setControlsVisible:(BOOL)controlsVisible animated:(BOOL)animated { 416 | [self willChangeValueForKey:@"controlsVisible"]; 417 | _controlsVisible = controlsVisible; 418 | [self didChangeValueForKey:@"controlsVisible"]; 419 | 420 | if (controlsVisible) 421 | for (UIView *view in self.controls) 422 | [view setHidden:NO]; 423 | 424 | [UIView animateWithDuration:(animated ? HSPlayerViewControlsAnimationDelay:0.) 425 | delay:0. 426 | options:(UIViewAnimationOptionCurveEaseInOut) 427 | animations:^{ 428 | for (UIView *view in self.controls) 429 | [view setAlpha:(controlsVisible ? 1.:0.)]; 430 | } completion:^(BOOL finished) { 431 | if (!controlsVisible) 432 | for (UIView *view in self.controls) 433 | [view setHidden:YES]; 434 | }]; 435 | 436 | if (self.fullScreen) 437 | [[UIApplication sharedApplication] setStatusBarHidden:(!controlsVisible) withAnimation:UIStatusBarAnimationFade]; 438 | } 439 | 440 | #pragma mark - Private 441 | 442 | - (void)autoHideControlsTimerFire:(NSTimer *)timer { 443 | [self setControlsVisible:NO animated:YES]; 444 | [self setAutoHideControlsTimer:nil]; 445 | } 446 | 447 | - (void)triggerAutoHideControlsTimer { 448 | [self setAutoHideControlsTimer:[NSTimer scheduledTimerWithTimeInterval:HSPlayerViewAutoHideControlsDelay 449 | target:self 450 | selector:@selector(autoHideControlsTimerFire:) 451 | userInfo:nil 452 | repeats:NO]]; 453 | } 454 | 455 | - (void)cancelAutoHideControlsTimer { 456 | [self.autoHideControlsTimer invalidate]; 457 | [self setAutoHideControlsTimer:nil]; 458 | } 459 | 460 | - (void)toggleControlsWithRecognizer:(UIGestureRecognizer *)recognizer { 461 | [self setControlsVisible:(!self.controlsVisible) animated:YES]; 462 | 463 | if (self.controlsVisible && self.playing) 464 | [self triggerAutoHideControlsTimer]; 465 | } 466 | 467 | - (void)toggleVideoGravityWithRecognizer:(UIGestureRecognizer *)recognizer { 468 | if (self.playerLayer.videoGravity == AVLayerVideoGravityResizeAspect) 469 | [self.playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; 470 | else 471 | [self.playerLayer setVideoGravity:AVLayerVideoGravityResizeAspect]; 472 | 473 | // Bug in iOS5, this works, but will not animate 474 | [self.playerLayer setFrame:self.playerLayer.frame]; 475 | } 476 | 477 | - (void)doneLoadingAsset:(AVAsset *)asset withKeys:(NSArray *)keys { 478 | 479 | // Check if all keys are OK 480 | for (NSString *key in keys) { 481 | NSError *error = nil; 482 | AVKeyValueStatus status = [asset statusOfValueForKey:key error:&error]; 483 | if (status == AVKeyValueStatusFailed || status == AVKeyValueStatusCancelled) { 484 | // Error, error 485 | return; 486 | } 487 | } 488 | 489 | if (!asset.playable) { 490 | // Error 491 | } 492 | 493 | // Remove observer from old playerItem and create new one 494 | if (self.playerItem) { 495 | [self.playerItem removeObserver:self forKeyPath:@"status"]; 496 | 497 | [[NSNotificationCenter defaultCenter] removeObserver:self 498 | name:AVPlayerItemDidPlayToEndTimeNotification 499 | object:self.playerItem]; 500 | } 501 | 502 | [self setPlayerItem:[AVPlayerItem playerItemWithAsset:asset]]; 503 | 504 | // Observe status, ok -> play 505 | [self.playerItem addObserver:self 506 | forKeyPath:@"status" 507 | options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) 508 | context:HSPlayerViewPlayerItemStatusObservationContext]; 509 | 510 | // Durationchange 511 | [self.playerItem addObserver:self 512 | forKeyPath:@"duration" 513 | options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) 514 | context:HSPlayerViewPlaterItemDurationObservationContext]; 515 | 516 | [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification 517 | object:self.playerItem 518 | queue:nil usingBlock:^(NSNotification *note) { 519 | [self setSeekToZeroBeforePlay:YES]; 520 | }]; 521 | 522 | [self setSeekToZeroBeforePlay:YES]; 523 | 524 | // Create the player 525 | if (!self.player) { 526 | [self setPlayer:[AVPlayer playerWithPlayerItem:self.playerItem]]; 527 | 528 | // Observe currentItem, catch the -replaceCurrentItemWithPlayerItem: 529 | [self.player addObserver:self 530 | forKeyPath:@"currentItem" 531 | options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) 532 | context:HSPlayerViewPlayerCurrentItemObservationContext]; 533 | 534 | // Observe rate, play/pause-button? 535 | [self.player addObserver:self 536 | forKeyPath:@"rate" 537 | options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) 538 | context:HSPlayerViewPlayerRateObservationContext]; 539 | 540 | } 541 | 542 | // New playerItem? 543 | if (self.player.currentItem != self.playerItem) { 544 | [self.player replaceCurrentItemWithPlayerItem:self.playerItem]; 545 | [self syncPlayPauseButton]; 546 | } 547 | 548 | // Scrub to start 549 | } 550 | 551 | - (void)addPlayerTimeObserver { 552 | if (!_playerTimeObserver) { 553 | __unsafe_unretained HSPlayerView *weakSelf = self; 554 | id observer = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(.5, NSEC_PER_SEC) 555 | queue:dispatch_get_main_queue() 556 | usingBlock:^(CMTime time) { 557 | 558 | HSPlayerView *strongSelf = weakSelf; 559 | if (CMTIME_IS_VALID(strongSelf.player.currentTime) && CMTIME_IS_VALID(strongSelf.duration)) 560 | [strongSelf syncScrobber]; 561 | }]; 562 | 563 | [self setPlayerTimeObserver:observer]; 564 | } 565 | } 566 | 567 | - (void)removePlayerTimeObserver { 568 | if (_playerTimeObserver) { 569 | [self.player removeTimeObserver:self.playerTimeObserver]; 570 | [self setPlayerTimeObserver:nil]; 571 | } 572 | } 573 | 574 | - (void)playPause:(id)sender { 575 | [self setPlaying:!self.playing]; 576 | } 577 | 578 | - (void)syncPlayPauseButton { 579 | [self.playPauseControlButton setImage:(self.playing ? self.pauseImage : self.playImage) forState:UIControlStateNormal]; 580 | } 581 | 582 | - (void)beginScrubbing:(id)sender { 583 | [self removePlayerTimeObserver]; 584 | [self setScrubbing:YES]; 585 | [self setRestoreAfterScrubbingRate:self.player.rate]; 586 | [self.player setRate:0.]; 587 | } 588 | 589 | - (void)scrub:(id)sender { 590 | [self.player seekToTime:CMTimeMakeWithSeconds(self.scrubberControlSlider.value, NSEC_PER_SEC)]; 591 | } 592 | 593 | - (void)endScrubbing:(id)sender { 594 | [self.player setRate:self.restoreAfterScrubbingRate]; 595 | [self setScrubbing:NO]; 596 | [self addPlayerTimeObserver]; 597 | } 598 | 599 | - (void)syncScrobber { 600 | NSInteger currentSeconds = ceilf(CMTimeGetSeconds(self.player.currentTime)); 601 | NSInteger seconds = currentSeconds % 60; 602 | NSInteger minutes = currentSeconds / 60; 603 | NSInteger hours = minutes / 60; 604 | 605 | NSInteger duration = ceilf(CMTimeGetSeconds(self.duration)); 606 | NSInteger currentDurationSeconds = duration-currentSeconds; 607 | NSInteger durationSeconds = currentDurationSeconds % 60; 608 | NSInteger durationMinutes = currentDurationSeconds / 60; 609 | NSInteger durationHours = durationMinutes / 60; 610 | 611 | [self.currentPlayerTimeLabel setText:[NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds]]; 612 | [self.remainingPlayerTimeLabel setText:[NSString stringWithFormat:@"-%02d:%02d:%02d", durationHours, durationMinutes, durationSeconds]]; 613 | 614 | [self.scrubberControlSlider setMinimumValue:0.]; 615 | [self.scrubberControlSlider setMaximumValue:duration]; 616 | [self.scrubberControlSlider setValue:currentSeconds]; 617 | 618 | NSLog(@"%@", self.player.currentItem.seekableTimeRanges); 619 | } 620 | 621 | #pragma mark - UIGestureRecognizerDelegate 622 | 623 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { 624 | // We dont want to to hide the controls when we tap em 625 | for (UIView *view in self.controls) 626 | if (CGRectContainsPoint(view.frame, [touch locationInView:self]) && self.controlsVisible) 627 | return NO; 628 | 629 | return YES; 630 | } 631 | 632 | #pragma mark - Custom Images 633 | 634 | - (UIImage *)playImage { 635 | if (!_playImage) { 636 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(20., 20.), NO, [[UIScreen mainScreen] scale]); 637 | 638 | UIBezierPath *path = [UIBezierPath bezierPath]; 639 | 640 | // |> 641 | [path moveToPoint:CGPointMake(0., 0.)]; 642 | [path addLineToPoint:CGPointMake(20., 10.)]; 643 | [path addLineToPoint:CGPointMake(0., 20.)]; 644 | [path closePath]; 645 | 646 | [[UIColor whiteColor] setFill]; 647 | [path fill]; 648 | 649 | _playImage = UIGraphicsGetImageFromCurrentImageContext(); 650 | UIGraphicsEndImageContext(); 651 | } 652 | return _playImage; 653 | } 654 | 655 | - (UIImage *)pauseImage { 656 | if (!_pauseImage) { 657 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(20., 20.), NO, [[UIScreen mainScreen] scale]); 658 | 659 | // || 660 | UIBezierPath *path1 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0., 0., 7., 20.) cornerRadius:1.]; 661 | UIBezierPath *path2 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20.-7., 0., 7., 20.) cornerRadius:1.]; 662 | 663 | [[UIColor whiteColor] setFill]; 664 | [path1 fill]; 665 | [path2 fill]; 666 | 667 | _pauseImage = UIGraphicsGetImageFromCurrentImageContext(); 668 | UIGraphicsEndImageContext(); 669 | } 670 | return _pauseImage; 671 | } 672 | 673 | @end 674 | --------------------------------------------------------------------------------