├── 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 |
5 |
6 |
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 |
--------------------------------------------------------------------------------