├── landscape.png ├── portrait.png ├── GUIPlayerView ├── Resources │ ├── gui_play@1x.png │ ├── gui_play@2x.png │ ├── gui_play@3x.png │ ├── gui_expand@1x.png │ ├── gui_expand@2x.png │ ├── gui_expand@3x.png │ ├── gui_pause@1x.png │ ├── gui_pause@2x.png │ ├── gui_pause@3x.png │ ├── gui_shrink@1x.png │ ├── gui_shrink@2x.png │ └── gui_shrink@3x.png ├── ViewController.h ├── AppDelegate.h ├── Classes │ ├── GUISlider.h │ ├── UIView+UpdateAutoLayoutConstraints.h │ ├── GUIPlayerView.h │ ├── GUISlider.m │ ├── UIView+UpdateAutoLayoutConstraints.m │ └── GUIPlayerView.m ├── main.m ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── AppDelegate.m ├── ViewController.m └── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── GUIPlayerView.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── .gitignore ├── LICENSE ├── GUIPlayerView.podspec └── README.md /landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/landscape.png -------------------------------------------------------------------------------- /portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/portrait.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_play@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_play@1x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_play@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_play@2x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_play@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_play@3x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_expand@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_expand@1x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_expand@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_expand@2x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_expand@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_expand@3x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_pause@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_pause@1x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_pause@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_pause@2x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_pause@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_pause@3x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_shrink@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_shrink@1x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_shrink@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_shrink@2x.png -------------------------------------------------------------------------------- /GUIPlayerView/Resources/gui_shrink@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhermearaujo/GUIPlayerView/HEAD/GUIPlayerView/Resources/gui_shrink@3x.png -------------------------------------------------------------------------------- /GUIPlayerView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GUIPlayerView/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // GUIPlayerView 4 | // 5 | // Created by Guilherme Araújo on 09/12/14. 6 | // Copyright (c) 2014 Guilherme Araújo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /GUIPlayerView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // GUIPlayerView 4 | // 5 | // Created by Guilherme Araújo on 09/12/14. 6 | // Copyright (c) 2014 Guilherme Araújo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /GUIPlayerView/Classes/GUISlider.h: -------------------------------------------------------------------------------- 1 | // 2 | // GUISlider.h 3 | // GUIPlayerView 4 | // 5 | // Created by Guilherme Araújo on 08/12/14. 6 | // Copyright (c) 2014 Guilherme Araújo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GUISlider : UISlider 12 | 13 | - (void)setSecondaryValue:(float)value; 14 | - (void)setSecondaryTintColor:(UIColor *)tintColor; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /GUIPlayerView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // GUIPlayerView 4 | // 5 | // Created by Guilherme Araújo on 09/12/14. 6 | // Copyright (c) 2014 Guilherme Araújo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | 4 | .DS_Store 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | *.moved-aside 17 | DerivedData 18 | *.hmap 19 | *.ipa 20 | *.xcuserstate 21 | 22 | # CocoaPods 23 | # 24 | # We recommend against adding the Pods directory to your .gitignore. However 25 | # you should judge for yourself, the pros and cons are mentioned at: 26 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 27 | # 28 | Pods/ 29 | Podfile.lock 30 | -------------------------------------------------------------------------------- /GUIPlayerView/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" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /GUIPlayerView/Classes/UIView+UpdateAutoLayoutConstraints.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+UpdateAutoLayoutConstant.h 3 | // ConstraintsCodeDemo 4 | // 5 | // Created by Damien Romito on 13/03/2014. 6 | // Copyright (c) 2014 Damien Romito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIView (UpdateAutoLayoutConstraints) 12 | 13 | - (BOOL) setConstraintConstant:(CGFloat)constant forAttribute:(NSLayoutAttribute)attribute; 14 | - (CGFloat) constraintConstantforAttribute:(NSLayoutAttribute)attribute; 15 | - (NSLayoutConstraint*) constraintForAttribute:(NSLayoutAttribute)attribute; 16 | - (void)hideView:(BOOL)hidden byAttribute:(NSLayoutAttribute)attribute; 17 | - (void)hideByHeight:(BOOL)hidden; 18 | - (void)hideByWidth:(BOOL)hidden; 19 | - (CGSize) getSize; 20 | - (void)sizeToSubviews; 21 | 22 | @end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Guilherme Araújo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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 THE AUTHORS OR COPYRIGHT HOLDERS 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. 10 | -------------------------------------------------------------------------------- /GUIPlayerView/Classes/GUIPlayerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // GUIPlayerView.h 3 | // GUIPlayerView 4 | // 5 | // Created by Guilherme Araújo on 08/12/14. 6 | // Copyright (c) 2014 Guilherme Araújo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class GUIPlayerView; 12 | 13 | @protocol GUIPlayerViewDelegate 14 | 15 | @optional 16 | - (void)playerDidPause; 17 | - (void)playerDidResume; 18 | - (void)playerDidEndPlaying; 19 | - (void)playerWillEnterFullscreen; 20 | - (void)playerDidEnterFullscreen; 21 | - (void)playerWillLeaveFullscreen; 22 | - (void)playerDidLeaveFullscreen; 23 | 24 | - (void)playerFailedToPlayToEnd; 25 | - (void)playerStalled; 26 | 27 | @end 28 | 29 | @interface GUIPlayerView : UIView 30 | 31 | @property (strong, nonatomic) NSURL *videoURL; 32 | @property (assign, nonatomic) NSInteger controllersTimeoutPeriod; 33 | @property (weak, nonatomic) id delegate; 34 | 35 | - (void)prepareAndPlayAutomatically:(BOOL)playAutomatically; 36 | - (void)clean; 37 | - (void)play; 38 | - (void)pause; 39 | - (void)stop; 40 | 41 | - (BOOL)isPlaying; 42 | 43 | - (void)setBufferTintColor:(UIColor *)tintColor; 44 | 45 | - (void)setLiveStreamText:(NSString *)text; 46 | 47 | - (void)setAirPlayText:(NSString *)text; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /GUIPlayerView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | guilherme.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /GUIPlayerView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "GUIPlayerView" 4 | s.version = "0.0.4" 5 | s.summary = "GUIPlayerView is a simple video player embedded into a UIView." 6 | s.homepage = "https://github.com/guilhermearaujo/GUIPlayerView" 7 | s.license = { :type => "MIT", :file => "LICENSE" } 8 | s.author = { "Guilherme Araújo" => "guilhermeama@gmail.com" } 9 | s.platform = :ios, "7.0" 10 | s.source = { :git => "https://github.com/guilhermearaujo/GUIPlayerView.git", :tag => "0.0.4" } 11 | s.source_files = "GUIPlayerView/Classes", "GUIPlayerView/Classes/**/*.{h,m}" 12 | s.exclude_files = "GUIPlayerView/Classes/Exclude" 13 | s.resources = "GUIPlayerView/Resources/*.png" 14 | s.framework = "AVFoundation" 15 | s.requires_arc = true 16 | s.description = <<-DESC 17 | GUIPlayerView implements a simple video player using AVPlayer. 18 | 19 | To use it, you must create a GUIPlayerView object and add it as a subview to your desired view. 20 | Then set the property `videoURL` and call `prepareAndPlayAutomatically:`. 21 | 22 | If you decide not to play automatically, you can leave for the user to press Play, or you can do it programmatically by calling `play`. 23 | 24 | Once you're done playing the video, you may want to remove it from your view. To do so, just call the method `clean` and everything will be released, and the player view will be removed from its superview. 25 | 26 | There are several optional delegate methods you can use: 27 | 28 | * `- (void)playerDidPause;` 29 | * `- (void)playerDidResume;` 30 | * `- (void)playerDidEndPlaying;` 31 | * `- (void)playerWillEnterFullscreen;` 32 | * `- (void)playerDidEnterFullscreen;` 33 | * `- (void)playerWillLeaveFullscreen;` 34 | * `- (void)playerDidLeaveFullscreen;` 35 | * `- (void)playerFailedToPlayToEnd;` 36 | * `- (void)playerStalled;` 37 | DESC 38 | 39 | end 40 | -------------------------------------------------------------------------------- /GUIPlayerView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // GUIPlayerView 4 | // 5 | // Created by Guilherme Araújo on 09/12/14. 6 | // Copyright (c) 2014 Guilherme Araújo. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 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 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /GUIPlayerView/Classes/GUISlider.m: -------------------------------------------------------------------------------- 1 | // 2 | // GUISlider.m 3 | // GUIPlayerView 4 | // 5 | // Created by Guilherme Araújo on 08/12/14. 6 | // Copyright (c) 2014 Guilherme Araújo. All rights reserved. 7 | // 8 | 9 | #import "GUISlider.h" 10 | 11 | @interface GUISlider () 12 | 13 | @property (strong, nonatomic) UIProgressView *progressView; 14 | 15 | @end 16 | 17 | @implementation GUISlider 18 | 19 | @synthesize progressView; 20 | 21 | - (instancetype)initWithFrame:(CGRect)frame { 22 | self = [super initWithFrame:frame]; 23 | [self setup]; 24 | return self; 25 | } 26 | 27 | - (void)setup { 28 | [self setMaximumTrackTintColor:[UIColor clearColor]]; 29 | 30 | progressView = [UIProgressView new]; 31 | [progressView setTranslatesAutoresizingMaskIntoConstraints:NO]; 32 | [progressView setClipsToBounds:YES]; 33 | [[progressView layer] setCornerRadius:1.0f]; 34 | 35 | CGFloat hue, sat, bri; 36 | [[self tintColor] getHue:&hue saturation:&sat brightness:&bri alpha:nil]; 37 | [progressView setTintColor:[UIColor colorWithHue:hue saturation:(sat * 0.6f) brightness:bri alpha:1]]; 38 | 39 | [self addSubview:progressView]; 40 | 41 | NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[PV]|" 42 | options:0 43 | metrics:nil 44 | views:@{@"PV" : progressView}]; 45 | 46 | [self addConstraints:constraints]; 47 | 48 | constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(20)-[PV]" 49 | options:0 50 | metrics:nil 51 | views:@{@"PV" : progressView}]; 52 | 53 | [self addConstraints:constraints]; 54 | } 55 | 56 | - (void)setSecondaryValue:(float)value { 57 | [progressView setProgress:value]; 58 | } 59 | 60 | - (void)setTintColor:(UIColor *)tintColor { 61 | [super setTintColor:tintColor]; 62 | 63 | CGFloat hue, sat, bri; 64 | [[self tintColor] getHue:&hue saturation:&sat brightness:&bri alpha:nil]; 65 | [progressView setTintColor:[UIColor colorWithHue:hue saturation:(sat * 0.6f) brightness:bri alpha:1]]; 66 | } 67 | 68 | - (void)setSecondaryTintColor:(UIColor *)tintColor { 69 | [progressView setTintColor:tintColor]; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /GUIPlayerView/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // GUIPlayerView 4 | // 5 | // Created by Guilherme Araújo on 09/12/14. 6 | // Copyright (c) 2014 Guilherme Araújo. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #import "GUIPlayerView.h" 12 | 13 | @interface ViewController () 14 | 15 | @property (weak, nonatomic) IBOutlet UIButton *addPlayerButton; 16 | @property (weak, nonatomic) IBOutlet UIButton *removePlayerButton; 17 | @property (weak, nonatomic) IBOutlet UILabel *copyrightLabel; 18 | 19 | - (IBAction)addPlayer:(UIButton *)sender; 20 | - (IBAction)removePlayer:(UIButton *)sender; 21 | 22 | @property (strong, nonatomic) GUIPlayerView *playerView; 23 | 24 | @end 25 | 26 | @implementation ViewController 27 | 28 | @synthesize addPlayerButton, removePlayerButton, copyrightLabel, playerView; 29 | 30 | #pragma mark - Interface Builder Actions 31 | 32 | - (IBAction)addPlayer:(UIButton *)sender { 33 | [copyrightLabel setHidden:NO]; 34 | CGFloat width = [UIScreen mainScreen].bounds.size.width; 35 | playerView = [[GUIPlayerView alloc] initWithFrame:CGRectMake(0, 64, width, width * 9.0f / 16.0f)]; 36 | [playerView setDelegate:self]; 37 | 38 | [[self view] addSubview:playerView]; 39 | 40 | NSURL *URL = [NSURL URLWithString:@"http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4"]; 41 | [playerView setVideoURL:URL]; 42 | [playerView prepareAndPlayAutomatically:YES]; 43 | 44 | [addPlayerButton setEnabled:NO]; 45 | [removePlayerButton setEnabled:YES]; 46 | } 47 | 48 | - (IBAction)removePlayer:(UIButton *)sender { 49 | [copyrightLabel setHidden:YES]; 50 | 51 | [playerView clean]; 52 | playerView = nil; 53 | 54 | [addPlayerButton setEnabled:YES]; 55 | [removePlayerButton setEnabled:NO]; 56 | } 57 | 58 | #pragma mark - GUI Player View Delegate Methods 59 | 60 | - (void)playerWillEnterFullscreen { 61 | [[self navigationController] setNavigationBarHidden:YES]; 62 | [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade]; 63 | } 64 | 65 | - (void)playerWillLeaveFullscreen { 66 | [[self navigationController] setNavigationBarHidden:NO]; 67 | [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade]; 68 | } 69 | 70 | - (void)playerDidEndPlaying { 71 | [copyrightLabel setHidden:YES]; 72 | 73 | [playerView clean]; 74 | 75 | [addPlayerButton setEnabled:YES]; 76 | [removePlayerButton setEnabled:NO]; 77 | } 78 | 79 | - (void)playerFailedToPlayToEnd { 80 | NSLog(@"Error: could not play video"); 81 | [playerView clean]; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GUIPlayerView 2 | `GUIPlayerView` implements a simple video player using `AVPlayer`. 3 | 4 | Portrait screenshot 5 | 6 | Landscape screenshot 7 | 8 | ## Features 9 | * Stream online and local files 10 | * Progress bar also shows the buffer load progress 11 | * Customizable progress bar tint colors 12 | * Automatically detect whether the stream is a fixed-length or undefined (as in a live stream) and adjusts the UI accordingly 13 | * AirPlay integration 14 | 15 | ## Installation 16 | **CocoaPods** (recommended) 17 | Add the following line to your `Podfile`: 18 | `pod 'GUIPlayerView', '~> 0.0.4'` 19 | And then add `#import ` to your view controller. 20 | 21 | **Manual** 22 | Copy the folders `Classes` and `Resources` to your project, then add `#import "GUIPlayerView.h"` to your view controller. 23 | ## Usage 24 | To use it, you must create a `GUIPlayerView` object and add it as a subview to your desired view. 25 | Then set the property `videoURL` and call `prepareAndPlayAutomatically:`. 26 | 27 | If you decide not to play automatically, you can leave for the user to press Play, or you can do it programmatically by calling `play`. 28 | 29 | Once you're done playing the video, you may want to remove it from your view. To do so, just call the method `clean` and everything will be released, and the player view will be removed from its superview. 30 | 31 | ### Playback Control 32 | There are a few methods to control the video playback: 33 | ```obj-c 34 | - (void)play; 35 | - (void)pause; 36 | - (void)stop; 37 | - (BOOL)isPlaying; 38 | ``` 39 | 40 | ### UI Customization 41 | You can change the tint color of the progress indicator. 42 | When the tint color is set, the buffer tint color will automatically be set to a desaturated version of the same color. If you desire a different color for it, remember to set the buffer tint color **after** setting the main tint color. 43 | ```obj-c 44 | - (void)setTintColor:(UIColor *)tintColor; 45 | - (void)setBufferTintColor:(UIColor *)tintColor; 46 | - (void)setLiveStreamText:(NSString *)text; 47 | - (void)setAirPlayText:(NSString *)text; 48 | ``` 49 | ### Delegate Methods 50 | There are several optional delegate methods you can use: 51 | ```obj-c 52 | - (void)playerDidPause; 53 | - (void)playerDidResume; 54 | - (void)playerDidEndPlaying; 55 | - (void)playerWillEnterFullscreen; 56 | - (void)playerDidEnterFullscreen; 57 | - (void)playerWillLeaveFullscreen; 58 | - (void)playerDidLeaveFullscreen; 59 | - (void)playerFailedToPlayToEnd; 60 | - (void)playerStalled; 61 | ``` 62 | 63 | ## Issues 64 | As of this release, there are some issues that need to be worked on: 65 | 66 | * **It only behaves nicely on fixed-orientation apps** 67 | It currently does not handle the orientation change events. 68 | 69 | * **Playlist or multiple streams not supported** 70 | Only one video can be played and there are no interface buttons to skip/go back. You can still use the `playerDidEndPlaying` delegate method, reset `videoURL` and call `prepareAndPlayAutomatically`again to play another stream. 71 | -------------------------------------------------------------------------------- /GUIPlayerView/Classes/UIView+UpdateAutoLayoutConstraints.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+UpdateAutoLayoutConstant.m 3 | // ConstraintsCodeDemo 4 | // 5 | // Created by Damien Romito on 13/03/2014. 6 | // Copyright (c) 2014 Damien Romito. All rights reserved. 7 | // 8 | 9 | #import "UIView+UpdateAutoLayoutConstraints.h" 10 | 11 | @implementation UIView (UpdateAutoLayoutConstraints) 12 | 13 | 14 | - (BOOL) setConstraintConstant:(CGFloat)constant forAttribute:(NSLayoutAttribute)attribute 15 | { 16 | NSLayoutConstraint * constraint = [self constraintForAttribute:attribute]; 17 | if(constraint) 18 | { 19 | [constraint setConstant:constant]; 20 | return YES; 21 | }else 22 | { 23 | [self.superview addConstraint: [NSLayoutConstraint constraintWithItem:self attribute:attribute relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:constant]]; 24 | return NO; 25 | } 26 | } 27 | 28 | 29 | - (CGFloat) constraintConstantforAttribute:(NSLayoutAttribute)attribute 30 | { 31 | NSLayoutConstraint * constraint = [self constraintForAttribute:attribute]; 32 | 33 | if (constraint) { 34 | return constraint.constant; 35 | }else 36 | { 37 | return NAN; 38 | } 39 | 40 | } 41 | 42 | 43 | - (NSLayoutConstraint*) constraintForAttribute:(NSLayoutAttribute)attribute 44 | { 45 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"firstAttribute = %d && firstItem = %@", attribute, self]; 46 | NSArray *fillteredArray = [[self.superview constraints] filteredArrayUsingPredicate:predicate]; 47 | if(fillteredArray.count == 0) 48 | { 49 | return nil; 50 | }else 51 | { 52 | return fillteredArray.firstObject; 53 | } 54 | } 55 | 56 | 57 | - (void)hideByHeight:(BOOL)hidden 58 | { 59 | [self hideView:hidden byAttribute:NSLayoutAttributeHeight]; 60 | } 61 | 62 | 63 | - (void)hideByWidth:(BOOL)hidden 64 | { 65 | [self hideView:hidden byAttribute:NSLayoutAttributeWidth]; 66 | } 67 | 68 | 69 | 70 | - (void)hideView:(BOOL)hidden byAttribute:(NSLayoutAttribute)attribute 71 | { 72 | if (self.hidden != hidden) { 73 | CGFloat constraintConstant = [self constraintConstantforAttribute:attribute]; 74 | 75 | if (hidden) 76 | { 77 | 78 | if (!isnan(constraintConstant)) { 79 | self.alpha = constraintConstant; 80 | }else 81 | { 82 | CGSize size = [self getSize]; 83 | self.alpha = (attribute == NSLayoutAttributeHeight)?size.height:size.width; 84 | } 85 | 86 | [self setConstraintConstant:0 forAttribute:attribute]; 87 | self.hidden = YES; 88 | 89 | }else 90 | { 91 | if (!isnan(constraintConstant) ) { 92 | self.hidden = NO; 93 | [self setConstraintConstant:self.alpha forAttribute:attribute]; 94 | self.alpha = 1; 95 | } 96 | } 97 | } 98 | } 99 | 100 | 101 | - (CGSize) getSize 102 | { 103 | [self updateSizes]; 104 | return CGSizeMake(self.bounds.size.width, self.bounds.size.height); 105 | } 106 | 107 | - (void)updateSizes 108 | { 109 | [self setNeedsLayout]; 110 | [self layoutIfNeeded]; 111 | } 112 | 113 | - (void)sizeToSubviews 114 | { 115 | [self updateSizes]; 116 | CGSize fittingSize = [self systemLayoutSizeFittingSize: UILayoutFittingCompressedSize]; 117 | self.frame = CGRectMake(0, 0, 320, fittingSize.height); 118 | } 119 | 120 | 121 | @end -------------------------------------------------------------------------------- /GUIPlayerView/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /GUIPlayerView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 32 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /GUIPlayerView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 792DFFCE1A6BDD0900AA0606 /* UIView+UpdateAutoLayoutConstraints.m in Sources */ = {isa = PBXBuildFile; fileRef = 792DFFCD1A6BDD0900AA0606 /* UIView+UpdateAutoLayoutConstraints.m */; }; 11 | 798A74631A3B269D00C611E8 /* GUIPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 798A74601A3B269D00C611E8 /* GUIPlayerView.m */; }; 12 | 798A74641A3B269D00C611E8 /* GUISlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 798A74621A3B269D00C611E8 /* GUISlider.m */; }; 13 | 798A74721A3B57CD00C611E8 /* gui_expand@1x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A74661A3B57CD00C611E8 /* gui_expand@1x.png */; }; 14 | 798A74731A3B57CD00C611E8 /* gui_expand@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A74671A3B57CD00C611E8 /* gui_expand@2x.png */; }; 15 | 798A74741A3B57CD00C611E8 /* gui_expand@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A74681A3B57CD00C611E8 /* gui_expand@3x.png */; }; 16 | 798A74751A3B57CD00C611E8 /* gui_pause@1x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A74691A3B57CD00C611E8 /* gui_pause@1x.png */; }; 17 | 798A74761A3B57CD00C611E8 /* gui_pause@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A746A1A3B57CD00C611E8 /* gui_pause@2x.png */; }; 18 | 798A74771A3B57CD00C611E8 /* gui_pause@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A746B1A3B57CD00C611E8 /* gui_pause@3x.png */; }; 19 | 798A74781A3B57CD00C611E8 /* gui_play@1x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A746C1A3B57CD00C611E8 /* gui_play@1x.png */; }; 20 | 798A74791A3B57CD00C611E8 /* gui_play@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A746D1A3B57CD00C611E8 /* gui_play@2x.png */; }; 21 | 798A747A1A3B57CD00C611E8 /* gui_play@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A746E1A3B57CD00C611E8 /* gui_play@3x.png */; }; 22 | 798A747B1A3B57CD00C611E8 /* gui_shrink@1x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A746F1A3B57CD00C611E8 /* gui_shrink@1x.png */; }; 23 | 798A747C1A3B57CD00C611E8 /* gui_shrink@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A74701A3B57CD00C611E8 /* gui_shrink@2x.png */; }; 24 | 798A747D1A3B57CD00C611E8 /* gui_shrink@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 798A74711A3B57CD00C611E8 /* gui_shrink@3x.png */; }; 25 | 79B9DF061A37642100EA2C0F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 79B9DF051A37642100EA2C0F /* main.m */; }; 26 | 79B9DF091A37642100EA2C0F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 79B9DF081A37642100EA2C0F /* AppDelegate.m */; }; 27 | 79B9DF0C1A37642100EA2C0F /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 79B9DF0B1A37642100EA2C0F /* ViewController.m */; }; 28 | 79B9DF0F1A37642100EA2C0F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 79B9DF0D1A37642100EA2C0F /* Main.storyboard */; }; 29 | 79B9DF111A37642100EA2C0F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 79B9DF101A37642100EA2C0F /* Images.xcassets */; }; 30 | 79B9DF141A37642100EA2C0F /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 79B9DF121A37642100EA2C0F /* LaunchScreen.xib */; }; 31 | 79B9DF2F1A3764F400EA2C0F /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79B9DF2E1A3764F400EA2C0F /* AVFoundation.framework */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 792DFFCC1A6BDD0900AA0606 /* UIView+UpdateAutoLayoutConstraints.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+UpdateAutoLayoutConstraints.h"; sourceTree = ""; }; 36 | 792DFFCD1A6BDD0900AA0606 /* UIView+UpdateAutoLayoutConstraints.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+UpdateAutoLayoutConstraints.m"; sourceTree = ""; }; 37 | 798A745F1A3B269D00C611E8 /* GUIPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIPlayerView.h; sourceTree = ""; }; 38 | 798A74601A3B269D00C611E8 /* GUIPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GUIPlayerView.m; sourceTree = ""; }; 39 | 798A74611A3B269D00C611E8 /* GUISlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUISlider.h; sourceTree = ""; }; 40 | 798A74621A3B269D00C611E8 /* GUISlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GUISlider.m; sourceTree = ""; }; 41 | 798A74661A3B57CD00C611E8 /* gui_expand@1x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_expand@1x.png"; sourceTree = ""; }; 42 | 798A74671A3B57CD00C611E8 /* gui_expand@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_expand@2x.png"; sourceTree = ""; }; 43 | 798A74681A3B57CD00C611E8 /* gui_expand@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_expand@3x.png"; sourceTree = ""; }; 44 | 798A74691A3B57CD00C611E8 /* gui_pause@1x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_pause@1x.png"; sourceTree = ""; }; 45 | 798A746A1A3B57CD00C611E8 /* gui_pause@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_pause@2x.png"; sourceTree = ""; }; 46 | 798A746B1A3B57CD00C611E8 /* gui_pause@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_pause@3x.png"; sourceTree = ""; }; 47 | 798A746C1A3B57CD00C611E8 /* gui_play@1x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_play@1x.png"; sourceTree = ""; }; 48 | 798A746D1A3B57CD00C611E8 /* gui_play@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_play@2x.png"; sourceTree = ""; }; 49 | 798A746E1A3B57CD00C611E8 /* gui_play@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_play@3x.png"; sourceTree = ""; }; 50 | 798A746F1A3B57CD00C611E8 /* gui_shrink@1x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_shrink@1x.png"; sourceTree = ""; }; 51 | 798A74701A3B57CD00C611E8 /* gui_shrink@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_shrink@2x.png"; sourceTree = ""; }; 52 | 798A74711A3B57CD00C611E8 /* gui_shrink@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gui_shrink@3x.png"; sourceTree = ""; }; 53 | 79B9DF001A37642100EA2C0F /* GUIPlayerView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GUIPlayerView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 79B9DF041A37642100EA2C0F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 79B9DF051A37642100EA2C0F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 56 | 79B9DF071A37642100EA2C0F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 57 | 79B9DF081A37642100EA2C0F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 58 | 79B9DF0A1A37642100EA2C0F /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 59 | 79B9DF0B1A37642100EA2C0F /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 60 | 79B9DF0E1A37642100EA2C0F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 61 | 79B9DF101A37642100EA2C0F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 62 | 79B9DF131A37642100EA2C0F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 63 | 79B9DF2E1A3764F400EA2C0F /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | 79B9DEFD1A37642100EA2C0F /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | 79B9DF2F1A3764F400EA2C0F /* AVFoundation.framework in Frameworks */, 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | /* End PBXFrameworksBuildPhase section */ 76 | 77 | /* Begin PBXGroup section */ 78 | 798A745E1A3B269D00C611E8 /* Classes */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 798A745F1A3B269D00C611E8 /* GUIPlayerView.h */, 82 | 798A74601A3B269D00C611E8 /* GUIPlayerView.m */, 83 | 798A74611A3B269D00C611E8 /* GUISlider.h */, 84 | 798A74621A3B269D00C611E8 /* GUISlider.m */, 85 | 792DFFCC1A6BDD0900AA0606 /* UIView+UpdateAutoLayoutConstraints.h */, 86 | 792DFFCD1A6BDD0900AA0606 /* UIView+UpdateAutoLayoutConstraints.m */, 87 | ); 88 | path = Classes; 89 | sourceTree = ""; 90 | }; 91 | 798A74651A3B57CD00C611E8 /* Resources */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 798A74661A3B57CD00C611E8 /* gui_expand@1x.png */, 95 | 798A74671A3B57CD00C611E8 /* gui_expand@2x.png */, 96 | 798A74681A3B57CD00C611E8 /* gui_expand@3x.png */, 97 | 798A74691A3B57CD00C611E8 /* gui_pause@1x.png */, 98 | 798A746A1A3B57CD00C611E8 /* gui_pause@2x.png */, 99 | 798A746B1A3B57CD00C611E8 /* gui_pause@3x.png */, 100 | 798A746C1A3B57CD00C611E8 /* gui_play@1x.png */, 101 | 798A746D1A3B57CD00C611E8 /* gui_play@2x.png */, 102 | 798A746E1A3B57CD00C611E8 /* gui_play@3x.png */, 103 | 798A746F1A3B57CD00C611E8 /* gui_shrink@1x.png */, 104 | 798A74701A3B57CD00C611E8 /* gui_shrink@2x.png */, 105 | 798A74711A3B57CD00C611E8 /* gui_shrink@3x.png */, 106 | ); 107 | path = Resources; 108 | sourceTree = ""; 109 | }; 110 | 79B9DEF71A37642100EA2C0F = { 111 | isa = PBXGroup; 112 | children = ( 113 | 79B9DF2E1A3764F400EA2C0F /* AVFoundation.framework */, 114 | 79B9DF021A37642100EA2C0F /* GUIPlayerView */, 115 | 79B9DF011A37642100EA2C0F /* Products */, 116 | ); 117 | sourceTree = ""; 118 | }; 119 | 79B9DF011A37642100EA2C0F /* Products */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 79B9DF001A37642100EA2C0F /* GUIPlayerView.app */, 123 | ); 124 | name = Products; 125 | sourceTree = ""; 126 | }; 127 | 79B9DF021A37642100EA2C0F /* GUIPlayerView */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 79B9DF071A37642100EA2C0F /* AppDelegate.h */, 131 | 79B9DF081A37642100EA2C0F /* AppDelegate.m */, 132 | 79B9DF0A1A37642100EA2C0F /* ViewController.h */, 133 | 79B9DF0B1A37642100EA2C0F /* ViewController.m */, 134 | 798A745E1A3B269D00C611E8 /* Classes */, 135 | 798A74651A3B57CD00C611E8 /* Resources */, 136 | 79B9DF0D1A37642100EA2C0F /* Main.storyboard */, 137 | 79B9DF121A37642100EA2C0F /* LaunchScreen.xib */, 138 | 79B9DF101A37642100EA2C0F /* Images.xcassets */, 139 | 79B9DF031A37642100EA2C0F /* Supporting Files */, 140 | ); 141 | path = GUIPlayerView; 142 | sourceTree = ""; 143 | }; 144 | 79B9DF031A37642100EA2C0F /* Supporting Files */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 79B9DF041A37642100EA2C0F /* Info.plist */, 148 | 79B9DF051A37642100EA2C0F /* main.m */, 149 | ); 150 | name = "Supporting Files"; 151 | sourceTree = ""; 152 | }; 153 | /* End PBXGroup section */ 154 | 155 | /* Begin PBXNativeTarget section */ 156 | 79B9DEFF1A37642100EA2C0F /* GUIPlayerView */ = { 157 | isa = PBXNativeTarget; 158 | buildConfigurationList = 79B9DF231A37642100EA2C0F /* Build configuration list for PBXNativeTarget "GUIPlayerView" */; 159 | buildPhases = ( 160 | 79B9DEFC1A37642100EA2C0F /* Sources */, 161 | 79B9DEFD1A37642100EA2C0F /* Frameworks */, 162 | 79B9DEFE1A37642100EA2C0F /* Resources */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | ); 168 | name = GUIPlayerView; 169 | productName = GUIPlayerView; 170 | productReference = 79B9DF001A37642100EA2C0F /* GUIPlayerView.app */; 171 | productType = "com.apple.product-type.application"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | 79B9DEF81A37642100EA2C0F /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastUpgradeCheck = 0610; 180 | ORGANIZATIONNAME = "Guilherme Araújo"; 181 | TargetAttributes = { 182 | 79B9DEFF1A37642100EA2C0F = { 183 | CreatedOnToolsVersion = 6.1.1; 184 | }; 185 | }; 186 | }; 187 | buildConfigurationList = 79B9DEFB1A37642100EA2C0F /* Build configuration list for PBXProject "GUIPlayerView" */; 188 | compatibilityVersion = "Xcode 3.2"; 189 | developmentRegion = English; 190 | hasScannedForEncodings = 0; 191 | knownRegions = ( 192 | en, 193 | Base, 194 | ); 195 | mainGroup = 79B9DEF71A37642100EA2C0F; 196 | productRefGroup = 79B9DF011A37642100EA2C0F /* Products */; 197 | projectDirPath = ""; 198 | projectRoot = ""; 199 | targets = ( 200 | 79B9DEFF1A37642100EA2C0F /* GUIPlayerView */, 201 | ); 202 | }; 203 | /* End PBXProject section */ 204 | 205 | /* Begin PBXResourcesBuildPhase section */ 206 | 79B9DEFE1A37642100EA2C0F /* Resources */ = { 207 | isa = PBXResourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 798A747B1A3B57CD00C611E8 /* gui_shrink@1x.png in Resources */, 211 | 79B9DF0F1A37642100EA2C0F /* Main.storyboard in Resources */, 212 | 798A74791A3B57CD00C611E8 /* gui_play@2x.png in Resources */, 213 | 798A74771A3B57CD00C611E8 /* gui_pause@3x.png in Resources */, 214 | 79B9DF141A37642100EA2C0F /* LaunchScreen.xib in Resources */, 215 | 79B9DF111A37642100EA2C0F /* Images.xcassets in Resources */, 216 | 798A74781A3B57CD00C611E8 /* gui_play@1x.png in Resources */, 217 | 798A74761A3B57CD00C611E8 /* gui_pause@2x.png in Resources */, 218 | 798A747C1A3B57CD00C611E8 /* gui_shrink@2x.png in Resources */, 219 | 798A747D1A3B57CD00C611E8 /* gui_shrink@3x.png in Resources */, 220 | 798A747A1A3B57CD00C611E8 /* gui_play@3x.png in Resources */, 221 | 798A74731A3B57CD00C611E8 /* gui_expand@2x.png in Resources */, 222 | 798A74721A3B57CD00C611E8 /* gui_expand@1x.png in Resources */, 223 | 798A74751A3B57CD00C611E8 /* gui_pause@1x.png in Resources */, 224 | 798A74741A3B57CD00C611E8 /* gui_expand@3x.png in Resources */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | /* End PBXResourcesBuildPhase section */ 229 | 230 | /* Begin PBXSourcesBuildPhase section */ 231 | 79B9DEFC1A37642100EA2C0F /* Sources */ = { 232 | isa = PBXSourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | 79B9DF0C1A37642100EA2C0F /* ViewController.m in Sources */, 236 | 79B9DF091A37642100EA2C0F /* AppDelegate.m in Sources */, 237 | 798A74641A3B269D00C611E8 /* GUISlider.m in Sources */, 238 | 792DFFCE1A6BDD0900AA0606 /* UIView+UpdateAutoLayoutConstraints.m in Sources */, 239 | 79B9DF061A37642100EA2C0F /* main.m in Sources */, 240 | 798A74631A3B269D00C611E8 /* GUIPlayerView.m in Sources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | /* End PBXSourcesBuildPhase section */ 245 | 246 | /* Begin PBXVariantGroup section */ 247 | 79B9DF0D1A37642100EA2C0F /* Main.storyboard */ = { 248 | isa = PBXVariantGroup; 249 | children = ( 250 | 79B9DF0E1A37642100EA2C0F /* Base */, 251 | ); 252 | name = Main.storyboard; 253 | sourceTree = ""; 254 | }; 255 | 79B9DF121A37642100EA2C0F /* LaunchScreen.xib */ = { 256 | isa = PBXVariantGroup; 257 | children = ( 258 | 79B9DF131A37642100EA2C0F /* Base */, 259 | ); 260 | name = LaunchScreen.xib; 261 | sourceTree = ""; 262 | }; 263 | /* End PBXVariantGroup section */ 264 | 265 | /* Begin XCBuildConfiguration section */ 266 | 79B9DF211A37642100EA2C0F /* Debug */ = { 267 | isa = XCBuildConfiguration; 268 | buildSettings = { 269 | ALWAYS_SEARCH_USER_PATHS = NO; 270 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 271 | CLANG_CXX_LIBRARY = "libc++"; 272 | CLANG_ENABLE_MODULES = YES; 273 | CLANG_ENABLE_OBJC_ARC = YES; 274 | CLANG_WARN_BOOL_CONVERSION = YES; 275 | CLANG_WARN_CONSTANT_CONVERSION = YES; 276 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 277 | CLANG_WARN_EMPTY_BODY = YES; 278 | CLANG_WARN_ENUM_CONVERSION = YES; 279 | CLANG_WARN_INT_CONVERSION = YES; 280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 284 | COPY_PHASE_STRIP = NO; 285 | ENABLE_STRICT_OBJC_MSGSEND = YES; 286 | GCC_C_LANGUAGE_STANDARD = gnu99; 287 | GCC_DYNAMIC_NO_PIC = NO; 288 | GCC_OPTIMIZATION_LEVEL = 0; 289 | GCC_PREPROCESSOR_DEFINITIONS = ( 290 | "DEBUG=1", 291 | "$(inherited)", 292 | ); 293 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 294 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 295 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 296 | GCC_WARN_UNDECLARED_SELECTOR = YES; 297 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 298 | GCC_WARN_UNUSED_FUNCTION = YES; 299 | GCC_WARN_UNUSED_VARIABLE = YES; 300 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 301 | MTL_ENABLE_DEBUG_INFO = YES; 302 | ONLY_ACTIVE_ARCH = YES; 303 | SDKROOT = iphoneos; 304 | }; 305 | name = Debug; 306 | }; 307 | 79B9DF221A37642100EA2C0F /* Release */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ALWAYS_SEARCH_USER_PATHS = NO; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_MODULES = YES; 314 | CLANG_ENABLE_OBJC_ARC = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_CONSTANT_CONVERSION = YES; 317 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 318 | CLANG_WARN_EMPTY_BODY = YES; 319 | CLANG_WARN_ENUM_CONVERSION = YES; 320 | CLANG_WARN_INT_CONVERSION = YES; 321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 322 | CLANG_WARN_UNREACHABLE_CODE = YES; 323 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 324 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 325 | COPY_PHASE_STRIP = YES; 326 | ENABLE_NS_ASSERTIONS = NO; 327 | ENABLE_STRICT_OBJC_MSGSEND = YES; 328 | GCC_C_LANGUAGE_STANDARD = gnu99; 329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 331 | GCC_WARN_UNDECLARED_SELECTOR = YES; 332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 333 | GCC_WARN_UNUSED_FUNCTION = YES; 334 | GCC_WARN_UNUSED_VARIABLE = YES; 335 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 336 | MTL_ENABLE_DEBUG_INFO = NO; 337 | SDKROOT = iphoneos; 338 | VALIDATE_PRODUCT = YES; 339 | }; 340 | name = Release; 341 | }; 342 | 79B9DF241A37642100EA2C0F /* Debug */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 346 | INFOPLIST_FILE = GUIPlayerView/Info.plist; 347 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 348 | PRODUCT_NAME = "$(TARGET_NAME)"; 349 | }; 350 | name = Debug; 351 | }; 352 | 79B9DF251A37642100EA2C0F /* Release */ = { 353 | isa = XCBuildConfiguration; 354 | buildSettings = { 355 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 356 | INFOPLIST_FILE = GUIPlayerView/Info.plist; 357 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 358 | PRODUCT_NAME = "$(TARGET_NAME)"; 359 | }; 360 | name = Release; 361 | }; 362 | /* End XCBuildConfiguration section */ 363 | 364 | /* Begin XCConfigurationList section */ 365 | 79B9DEFB1A37642100EA2C0F /* Build configuration list for PBXProject "GUIPlayerView" */ = { 366 | isa = XCConfigurationList; 367 | buildConfigurations = ( 368 | 79B9DF211A37642100EA2C0F /* Debug */, 369 | 79B9DF221A37642100EA2C0F /* Release */, 370 | ); 371 | defaultConfigurationIsVisible = 0; 372 | defaultConfigurationName = Release; 373 | }; 374 | 79B9DF231A37642100EA2C0F /* Build configuration list for PBXNativeTarget "GUIPlayerView" */ = { 375 | isa = XCConfigurationList; 376 | buildConfigurations = ( 377 | 79B9DF241A37642100EA2C0F /* Debug */, 378 | 79B9DF251A37642100EA2C0F /* Release */, 379 | ); 380 | defaultConfigurationIsVisible = 0; 381 | defaultConfigurationName = Release; 382 | }; 383 | /* End XCConfigurationList section */ 384 | }; 385 | rootObject = 79B9DEF81A37642100EA2C0F /* Project object */; 386 | } 387 | -------------------------------------------------------------------------------- /GUIPlayerView/Classes/GUIPlayerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // GUIPlayerView.m 3 | // GUIPlayerView 4 | // 5 | // Created by Guilherme Araújo on 08/12/14. 6 | // Copyright (c) 2014 Guilherme Araújo. All rights reserved. 7 | // 8 | 9 | #import "GUIPlayerView.h" 10 | #import "GUISlider.h" 11 | 12 | #import 13 | #import 14 | 15 | #import "UIView+UpdateAutoLayoutConstraints.h" 16 | 17 | @interface GUIPlayerView () 18 | 19 | @property (strong, nonatomic) AVPlayer *player; 20 | @property (strong, nonatomic) AVPlayerLayer *playerLayer; 21 | @property (strong, nonatomic) AVPlayerItem *currentItem; 22 | 23 | @property (strong, nonatomic) UIView *controllersView; 24 | @property (strong, nonatomic) UILabel *airPlayLabel; 25 | 26 | @property (strong, nonatomic) UIButton *playButton; 27 | @property (strong, nonatomic) UIButton *fullscreenButton; 28 | @property (strong, nonatomic) MPVolumeView *volumeView; 29 | @property (strong, nonatomic) GUISlider *progressIndicator; 30 | @property (strong, nonatomic) UILabel *currentTimeLabel; 31 | @property (strong, nonatomic) UILabel *remainingTimeLabel; 32 | @property (strong, nonatomic) UILabel *liveLabel; 33 | 34 | @property (strong, nonatomic) UIView *spacerView; 35 | 36 | @property (strong, nonatomic) UIActivityIndicatorView *activityIndicator; 37 | @property (strong, nonatomic) NSTimer *progressTimer; 38 | @property (strong, nonatomic) NSTimer *controllersTimer; 39 | @property (assign, nonatomic) BOOL seeking; 40 | @property (assign, nonatomic) BOOL fullscreen; 41 | @property (assign, nonatomic) CGRect defaultFrame; 42 | 43 | @end 44 | 45 | @implementation GUIPlayerView 46 | 47 | @synthesize player, playerLayer, currentItem; 48 | @synthesize controllersView, airPlayLabel; 49 | @synthesize playButton, fullscreenButton, volumeView, progressIndicator, currentTimeLabel, remainingTimeLabel, liveLabel, spacerView; 50 | @synthesize activityIndicator, progressTimer, controllersTimer, seeking, fullscreen, defaultFrame; 51 | 52 | @synthesize videoURL, controllersTimeoutPeriod, delegate; 53 | 54 | #pragma mark - View Life Cycle 55 | 56 | - (instancetype)initWithFrame:(CGRect)frame { 57 | self = [super initWithFrame:frame]; 58 | defaultFrame = frame; 59 | [self setup]; 60 | return self; 61 | } 62 | 63 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 64 | self = [super initWithCoder:aDecoder]; 65 | [self setup]; 66 | return self; 67 | } 68 | 69 | - (void)setup { 70 | // Set up notification observers 71 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerDidFinishPlaying:) 72 | name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; 73 | 74 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerFailedToPlayToEnd:) 75 | name:AVPlayerItemFailedToPlayToEndTimeNotification object:nil]; 76 | 77 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerStalled:) 78 | name:AVPlayerItemPlaybackStalledNotification object:nil]; 79 | 80 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(airPlayAvailabilityChanged:) 81 | name:MPVolumeViewWirelessRoutesAvailableDidChangeNotification object:nil]; 82 | 83 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(airPlayActivityChanged:) 84 | name:MPVolumeViewWirelessRouteActiveDidChangeNotification object:nil]; 85 | 86 | [self setBackgroundColor:[UIColor blackColor]]; 87 | 88 | NSArray *horizontalConstraints; 89 | NSArray *verticalConstraints; 90 | 91 | 92 | /** Container View **************************************************************************************************/ 93 | controllersView = [UIView new]; 94 | [controllersView setTranslatesAutoresizingMaskIntoConstraints:NO]; 95 | [controllersView setBackgroundColor:[UIColor colorWithWhite:0.0f alpha:0.45f]]; 96 | 97 | [self addSubview:controllersView]; 98 | 99 | horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[CV]|" 100 | options:0 101 | metrics:nil 102 | views:@{@"CV" : controllersView}]; 103 | 104 | verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[CV(40)]|" 105 | options:0 106 | metrics:nil 107 | views:@{@"CV" : controllersView}]; 108 | [self addConstraints:horizontalConstraints]; 109 | [self addConstraints:verticalConstraints]; 110 | 111 | 112 | /** AirPlay View ****************************************************************************************************/ 113 | 114 | airPlayLabel = [UILabel new]; 115 | [airPlayLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; 116 | [airPlayLabel setText:@"AirPlay is enabled"]; 117 | [airPlayLabel setTextColor:[UIColor lightGrayColor]]; 118 | [airPlayLabel setFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:13.0f]]; 119 | [airPlayLabel setTextAlignment:NSTextAlignmentCenter]; 120 | [airPlayLabel setNumberOfLines:0]; 121 | [airPlayLabel setHidden:YES]; 122 | 123 | [self addSubview:airPlayLabel]; 124 | 125 | horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[AP]|" 126 | options:0 127 | metrics:nil 128 | views:@{@"AP" : airPlayLabel}]; 129 | 130 | verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[AP]-40-|" 131 | options:0 132 | metrics:nil 133 | views:@{@"AP" : airPlayLabel}]; 134 | [self addConstraints:horizontalConstraints]; 135 | [self addConstraints:verticalConstraints]; 136 | 137 | /** UI Controllers **************************************************************************************************/ 138 | playButton = [UIButton buttonWithType:UIButtonTypeCustom]; 139 | [playButton setTranslatesAutoresizingMaskIntoConstraints:NO]; 140 | [playButton setImage:[UIImage imageNamed:@"gui_play"] forState:UIControlStateNormal]; 141 | [playButton setImage:[UIImage imageNamed:@"gui_pause"] forState:UIControlStateSelected]; 142 | 143 | volumeView = [MPVolumeView new]; 144 | [volumeView setTranslatesAutoresizingMaskIntoConstraints:NO]; 145 | [volumeView setShowsRouteButton:YES]; 146 | [volumeView setShowsVolumeSlider:NO]; 147 | [volumeView setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; 148 | 149 | fullscreenButton = [UIButton buttonWithType:UIButtonTypeCustom]; 150 | [fullscreenButton setTranslatesAutoresizingMaskIntoConstraints:NO]; 151 | [fullscreenButton setImage:[UIImage imageNamed:@"gui_expand"] forState:UIControlStateNormal]; 152 | [fullscreenButton setImage:[UIImage imageNamed:@"gui_shrink"] forState:UIControlStateSelected]; 153 | 154 | currentTimeLabel = [UILabel new]; 155 | [currentTimeLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; 156 | [currentTimeLabel setFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:13.0f]]; 157 | [currentTimeLabel setTextAlignment:NSTextAlignmentCenter]; 158 | [currentTimeLabel setTextColor:[UIColor whiteColor]]; 159 | 160 | remainingTimeLabel = [UILabel new]; 161 | [remainingTimeLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; 162 | [remainingTimeLabel setFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:13.0f]]; 163 | [remainingTimeLabel setTextAlignment:NSTextAlignmentCenter]; 164 | [remainingTimeLabel setTextColor:[UIColor whiteColor]]; 165 | 166 | progressIndicator = [GUISlider new]; 167 | [progressIndicator setTranslatesAutoresizingMaskIntoConstraints:NO]; 168 | [progressIndicator setContinuous:YES]; 169 | 170 | liveLabel = [UILabel new]; 171 | [liveLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; 172 | [liveLabel setFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:13.0f]]; 173 | [liveLabel setTextAlignment:NSTextAlignmentCenter]; 174 | [liveLabel setTextColor:[UIColor whiteColor]]; 175 | [liveLabel setText:@"Live"]; 176 | [liveLabel setHidden:YES]; 177 | 178 | spacerView = [UIView new]; 179 | [spacerView setTranslatesAutoresizingMaskIntoConstraints:NO]; 180 | 181 | [controllersView addSubview:playButton]; 182 | [controllersView addSubview:fullscreenButton]; 183 | [controllersView addSubview:volumeView]; 184 | [controllersView addSubview:currentTimeLabel]; 185 | [controllersView addSubview:progressIndicator]; 186 | [controllersView addSubview:remainingTimeLabel]; 187 | [controllersView addSubview:liveLabel]; 188 | [controllersView addSubview:spacerView]; 189 | 190 | horizontalConstraints = [NSLayoutConstraint 191 | constraintsWithVisualFormat:@"H:|[P(40)][S(10)][C]-5-[I]-5-[R][F(40)][V(40)]|" 192 | options:0 193 | metrics:nil 194 | views:@{@"P" : playButton, 195 | @"S" : spacerView, 196 | @"C" : currentTimeLabel, 197 | @"I" : progressIndicator, 198 | @"R" : remainingTimeLabel, 199 | @"V" : volumeView, 200 | @"F" : fullscreenButton}]; 201 | 202 | [controllersView addConstraints:horizontalConstraints]; 203 | 204 | [volumeView hideByWidth:YES]; 205 | [spacerView hideByWidth:YES]; 206 | 207 | horizontalConstraints = [NSLayoutConstraint 208 | constraintsWithVisualFormat:@"H:|-5-[L]-5-|" 209 | options:0 210 | metrics:nil 211 | views:@{@"L" : liveLabel}]; 212 | 213 | [controllersView addConstraints:horizontalConstraints]; 214 | 215 | for (UIView *view in [controllersView subviews]) { 216 | verticalConstraints = [NSLayoutConstraint 217 | constraintsWithVisualFormat:@"V:|-0-[V(40)]" 218 | options:NSLayoutFormatAlignAllCenterY 219 | metrics:nil 220 | views:@{@"V" : view}]; 221 | [controllersView addConstraints:verticalConstraints]; 222 | } 223 | 224 | 225 | /** Loading Indicator ***********************************************************************************************/ 226 | activityIndicator = [UIActivityIndicatorView new]; 227 | [activityIndicator stopAnimating]; 228 | 229 | CGRect frame = self.frame; 230 | frame.origin = CGPointZero; 231 | [activityIndicator setFrame:frame]; 232 | 233 | [self addSubview:activityIndicator]; 234 | 235 | 236 | /** Actions Setup ***************************************************************************************************/ 237 | 238 | [playButton addTarget:self action:@selector(togglePlay:) forControlEvents:UIControlEventTouchUpInside]; 239 | [fullscreenButton addTarget:self action:@selector(toggleFullscreen:) forControlEvents:UIControlEventTouchUpInside]; 240 | 241 | [progressIndicator addTarget:self action:@selector(seek:) forControlEvents:UIControlEventValueChanged]; 242 | [progressIndicator addTarget:self action:@selector(pauseRefreshing) forControlEvents:UIControlEventTouchDown]; 243 | [progressIndicator addTarget:self action:@selector(resumeRefreshing) forControlEvents:UIControlEventTouchUpInside| 244 | UIControlEventTouchUpOutside]; 245 | 246 | [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showControllers)]]; 247 | [self showControllers]; 248 | 249 | controllersTimeoutPeriod = 3; 250 | } 251 | 252 | #pragma mark - UI Customization 253 | 254 | - (void)setTintColor:(UIColor *)tintColor { 255 | [super setTintColor:tintColor]; 256 | 257 | [progressIndicator setTintColor:tintColor]; 258 | } 259 | 260 | - (void)setBufferTintColor:(UIColor *)tintColor { 261 | [progressIndicator setSecondaryTintColor:tintColor]; 262 | } 263 | 264 | - (void)setLiveStreamText:(NSString *)text { 265 | [liveLabel setText:text]; 266 | } 267 | 268 | - (void)setAirPlayText:(NSString *)text { 269 | [airPlayLabel setText:text]; 270 | } 271 | 272 | #pragma mark - Actions 273 | 274 | - (void)togglePlay:(UIButton *)button { 275 | if ([button isSelected]) { 276 | [button setSelected:NO]; 277 | [player pause]; 278 | 279 | if ([delegate respondsToSelector:@selector(playerDidPause)]) { 280 | [delegate playerDidPause]; 281 | } 282 | } else { 283 | [button setSelected:YES]; 284 | [self play]; 285 | 286 | if ([delegate respondsToSelector:@selector(playerDidResume)]) { 287 | [delegate playerDidResume]; 288 | } 289 | } 290 | 291 | [self showControllers]; 292 | } 293 | 294 | - (void)toggleFullscreen:(UIButton *)button { 295 | if (fullscreen) { 296 | if ([delegate respondsToSelector:@selector(playerWillLeaveFullscreen)]) { 297 | [delegate playerWillLeaveFullscreen]; 298 | } 299 | 300 | [UIView animateWithDuration:0.2f animations:^{ 301 | [self setTransform:CGAffineTransformMakeRotation(0)]; 302 | [self setFrame:defaultFrame]; 303 | 304 | CGRect frame = defaultFrame; 305 | frame.origin = CGPointZero; 306 | [playerLayer setFrame:frame]; 307 | [activityIndicator setFrame:frame]; 308 | } completion:^(BOOL finished) { 309 | fullscreen = NO; 310 | 311 | if ([delegate respondsToSelector:@selector(playerDidLeaveFullscreen)]) { 312 | [delegate playerDidLeaveFullscreen]; 313 | } 314 | }]; 315 | 316 | [button setSelected:NO]; 317 | } else { 318 | UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; 319 | 320 | CGFloat width = [[UIScreen mainScreen] bounds].size.width; 321 | CGFloat height = [[UIScreen mainScreen] bounds].size.height; 322 | CGRect frame; 323 | 324 | if (UIInterfaceOrientationIsPortrait(orientation)) { 325 | CGFloat aux = width; 326 | width = height; 327 | height = aux; 328 | frame = CGRectMake((height - width) / 2, (width - height) / 2, width, height); 329 | } else { 330 | frame = CGRectMake(0, 0, width, height); 331 | } 332 | 333 | if ([delegate respondsToSelector:@selector(playerWillEnterFullscreen)]) { 334 | [delegate playerWillEnterFullscreen]; 335 | } 336 | 337 | [UIView animateWithDuration:0.2f animations:^{ 338 | [self setFrame:frame]; 339 | [playerLayer setFrame:CGRectMake(0, 0, width, height)]; 340 | 341 | [activityIndicator setFrame:CGRectMake(0, 0, width, height)]; 342 | if (UIInterfaceOrientationIsPortrait(orientation)) { 343 | [self setTransform:CGAffineTransformMakeRotation(M_PI_2)]; 344 | [activityIndicator setTransform:CGAffineTransformMakeRotation(M_PI_2)]; 345 | } 346 | 347 | } completion:^(BOOL finished) { 348 | fullscreen = YES; 349 | 350 | if ([delegate respondsToSelector:@selector(playerDidEnterFullscreen)]) { 351 | [delegate playerDidEnterFullscreen]; 352 | } 353 | }]; 354 | 355 | [button setSelected:YES]; 356 | } 357 | 358 | [self showControllers]; 359 | } 360 | 361 | - (void)seek:(UISlider *)slider { 362 | int timescale = currentItem.asset.duration.timescale; 363 | float time = slider.value * (currentItem.asset.duration.value / timescale); 364 | [player seekToTime:CMTimeMakeWithSeconds(time, timescale)]; 365 | 366 | [self showControllers]; 367 | } 368 | 369 | - (void)pauseRefreshing { 370 | seeking = YES; 371 | } 372 | 373 | - (void)resumeRefreshing { 374 | seeking = NO; 375 | } 376 | 377 | - (NSTimeInterval)availableDuration { 378 | NSTimeInterval result = 0; 379 | NSArray *loadedTimeRanges = player.currentItem.loadedTimeRanges; 380 | 381 | if ([loadedTimeRanges count] > 0) { 382 | CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue]; 383 | Float64 startSeconds = CMTimeGetSeconds(timeRange.start); 384 | Float64 durationSeconds = CMTimeGetSeconds(timeRange.duration); 385 | result = startSeconds + durationSeconds; 386 | } 387 | 388 | return result; 389 | } 390 | 391 | - (void)refreshProgressIndicator { 392 | CGFloat duration = CMTimeGetSeconds(currentItem.asset.duration); 393 | 394 | if (duration == 0 || isnan(duration)) { 395 | // Video is a live stream 396 | [currentTimeLabel setText:nil]; 397 | [remainingTimeLabel setText:nil]; 398 | [progressIndicator setHidden:YES]; 399 | [liveLabel setHidden:NO]; 400 | } 401 | 402 | else { 403 | CGFloat current = seeking ? 404 | progressIndicator.value * duration : // If seeking, reflects the position of the slider 405 | CMTimeGetSeconds(player.currentTime); // Otherwise, use the actual video position 406 | 407 | [progressIndicator setValue:(current / duration)]; 408 | [progressIndicator setSecondaryValue:([self availableDuration] / duration)]; 409 | 410 | // Set time labels 411 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 412 | [formatter setDateFormat:(duration >= 3600 ? @"hh:mm:ss": @"mm:ss")]; 413 | [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]]; 414 | 415 | NSDate *currentTime = [NSDate dateWithTimeIntervalSince1970:current]; 416 | NSDate *remainingTime = [NSDate dateWithTimeIntervalSince1970:(duration - current)]; 417 | 418 | [currentTimeLabel setText:[formatter stringFromDate:currentTime]]; 419 | [remainingTimeLabel setText:[NSString stringWithFormat:@"-%@", [formatter stringFromDate:remainingTime]]]; 420 | 421 | [progressIndicator setHidden:NO]; 422 | [liveLabel setHidden:YES]; 423 | } 424 | } 425 | 426 | - (void)showControllers { 427 | [UIView animateWithDuration:0.2f animations:^{ 428 | [controllersView setAlpha:1.0f]; 429 | } completion:^(BOOL finished) { 430 | [controllersTimer invalidate]; 431 | 432 | if (controllersTimeoutPeriod > 0) { 433 | controllersTimer = [NSTimer scheduledTimerWithTimeInterval:controllersTimeoutPeriod 434 | target:self 435 | selector:@selector(hideControllers) 436 | userInfo:nil 437 | repeats:NO]; 438 | } 439 | }]; 440 | } 441 | 442 | - (void)hideControllers { 443 | [UIView animateWithDuration:0.5f animations:^{ 444 | [controllersView setAlpha:0.0f]; 445 | }]; 446 | } 447 | 448 | #pragma mark - Public Methods 449 | 450 | - (void)prepareAndPlayAutomatically:(BOOL)playAutomatically { 451 | if (player) { 452 | [self stop]; 453 | } 454 | 455 | player = [[AVPlayer alloc] initWithPlayerItem:nil]; 456 | 457 | AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil]; 458 | NSArray *keys = [NSArray arrayWithObject:@"playable"]; 459 | 460 | __weak typeof(self) weakSelf = self; 461 | [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{ 462 | weakSelf.currentItem = [AVPlayerItem playerItemWithAsset:asset]; 463 | [weakSelf.player replaceCurrentItemWithPlayerItem:weakSelf.currentItem]; 464 | 465 | if (playAutomatically) { 466 | dispatch_sync(dispatch_get_main_queue(), ^{ 467 | [weakSelf play]; 468 | }); 469 | } 470 | }]; 471 | 472 | [player setAllowsExternalPlayback:YES]; 473 | playerLayer = [AVPlayerLayer playerLayerWithPlayer:player]; 474 | [self.layer addSublayer:playerLayer]; 475 | 476 | defaultFrame = self.frame; 477 | 478 | CGRect frame = self.frame; 479 | frame.origin = CGPointZero; 480 | [playerLayer setFrame:frame]; 481 | 482 | [self bringSubviewToFront:controllersView]; 483 | 484 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; 485 | 486 | [player addObserver:self forKeyPath:@"rate" options:0 context:nil]; 487 | [currentItem addObserver:self forKeyPath:@"status" options:0 context:nil]; 488 | 489 | [player seekToTime:kCMTimeZero]; 490 | [player setRate:0.0f]; 491 | [playButton setSelected:YES]; 492 | 493 | if (playAutomatically) { 494 | [activityIndicator startAnimating]; 495 | } 496 | } 497 | 498 | - (void)clean { 499 | 500 | [progressTimer invalidate]; 501 | progressTimer = nil; 502 | [controllersTimer invalidate]; 503 | controllersTimer = nil; 504 | 505 | [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; 506 | [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:nil]; 507 | [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemPlaybackStalledNotification object:nil]; 508 | [[NSNotificationCenter defaultCenter] removeObserver:self name:MPVolumeViewWirelessRoutesAvailableDidChangeNotification object:nil]; 509 | [[NSNotificationCenter defaultCenter] removeObserver:self name:MPVolumeViewWirelessRouteActiveDidChangeNotification object:nil]; 510 | 511 | [player setAllowsExternalPlayback:NO]; 512 | [self stop]; 513 | [player removeObserver:self forKeyPath:@"rate"]; 514 | [self setPlayer:nil]; 515 | [self.playerLayer removeFromSuperlayer]; 516 | [self setPlayerLayer:nil]; 517 | [self removeFromSuperview]; 518 | } 519 | 520 | - (void)play { 521 | [player play]; 522 | 523 | [playButton setSelected:YES]; 524 | 525 | progressTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f 526 | target:self 527 | selector:@selector(refreshProgressIndicator) 528 | userInfo:nil 529 | repeats:YES]; 530 | } 531 | 532 | - (void)pause { 533 | [player pause]; 534 | [playButton setSelected:NO]; 535 | 536 | if ([delegate respondsToSelector:@selector(playerDidPause)]) { 537 | [delegate playerDidPause]; 538 | } 539 | } 540 | 541 | - (void)stop { 542 | if (player) { 543 | [player pause]; 544 | [player seekToTime:kCMTimeZero]; 545 | 546 | [playButton setSelected:NO]; 547 | } 548 | } 549 | 550 | - (BOOL)isPlaying { 551 | return [player rate] > 0.0f; 552 | } 553 | 554 | #pragma mark - AV Player Notifications and Observers 555 | 556 | - (void)playerDidFinishPlaying:(NSNotification *)notification { 557 | [self stop]; 558 | 559 | if (fullscreen) { 560 | [self toggleFullscreen:fullscreenButton]; 561 | } 562 | 563 | if ([delegate respondsToSelector:@selector(playerDidEndPlaying)]) { 564 | [delegate playerDidEndPlaying]; 565 | } 566 | } 567 | 568 | - (void)playerFailedToPlayToEnd:(NSNotification *)notification { 569 | [self stop]; 570 | 571 | if ([delegate respondsToSelector:@selector(playerFailedToPlayToEnd)]) { 572 | [delegate playerFailedToPlayToEnd]; 573 | } 574 | } 575 | 576 | - (void)playerStalled:(NSNotification *)notification { 577 | [self togglePlay:playButton]; 578 | 579 | if ([delegate respondsToSelector:@selector(playerStalled)]) { 580 | [delegate playerStalled]; 581 | } 582 | } 583 | 584 | 585 | - (void)airPlayAvailabilityChanged:(NSNotification *)notification { 586 | [UIView animateWithDuration:0.4f 587 | animations:^{ 588 | if ([volumeView areWirelessRoutesAvailable]) { 589 | [volumeView hideByWidth:NO]; 590 | } else if (! [volumeView isWirelessRouteActive]) { 591 | [volumeView hideByWidth:YES]; 592 | } 593 | [self layoutIfNeeded]; 594 | }]; 595 | } 596 | 597 | 598 | - (void)airPlayActivityChanged:(NSNotification *)notification { 599 | [UIView animateWithDuration:0.4f 600 | animations:^{ 601 | if ([volumeView isWirelessRouteActive]) { 602 | if (fullscreen) 603 | [self toggleFullscreen:fullscreenButton]; 604 | 605 | [playButton hideByWidth:YES]; 606 | [fullscreenButton hideByWidth:YES]; 607 | [spacerView hideByWidth:NO]; 608 | 609 | [airPlayLabel setHidden:NO]; 610 | 611 | controllersTimeoutPeriod = 0; 612 | [self showControllers]; 613 | } else { 614 | [playButton hideByWidth:NO]; 615 | [fullscreenButton hideByWidth:NO]; 616 | [spacerView hideByWidth:YES]; 617 | 618 | [airPlayLabel setHidden:YES]; 619 | 620 | controllersTimeoutPeriod = 3; 621 | [self showControllers]; 622 | } 623 | [self layoutIfNeeded]; 624 | }]; 625 | } 626 | 627 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 628 | if ([keyPath isEqualToString:@"status"]) { 629 | if (currentItem.status == AVPlayerItemStatusFailed) { 630 | if ([delegate respondsToSelector:@selector(playerFailedToPlayToEnd)]) { 631 | [delegate playerFailedToPlayToEnd]; 632 | } 633 | } 634 | } 635 | 636 | if ([keyPath isEqualToString:@"rate"]) { 637 | CGFloat rate = [player rate]; 638 | if (rate > 0) { 639 | [activityIndicator stopAnimating]; 640 | } 641 | } 642 | } 643 | 644 | - (void)dealloc { 645 | NSLog(@"dealloc"); 646 | } 647 | 648 | @end 649 | --------------------------------------------------------------------------------