├── .gitignore ├── Assets ├── airplay-display.png ├── airplay-display@2x.png ├── fullscreen-button.png ├── fullscreen-button@2x.png ├── minimize-button.png ├── minimize-button@2x.png ├── pause-button.png ├── pause-button@2x.png ├── play-button.png ├── play-button@2x.png ├── share-button.png ├── share-button@2x.png ├── transparentBar.png └── transparentBar@2x.png ├── Code ├── .DS_Store ├── AirplayActiveView.h ├── AirplayActiveView.m ├── FullScreenView.h ├── FullScreenView.m ├── FullScreenViewController.h ├── FullScreenViewController.m ├── VideoPlayer.h ├── VideoPlayerKit.h ├── VideoPlayerKit.m ├── VideoPlayerView.h └── VideoPlayerView.m ├── LICENSE ├── README.md ├── VideoPlayerKit.podspec └── VideoPlayerKitSampleProject ├── Gemfile ├── Gemfile.lock ├── Makefile ├── Podfile ├── Podfile.lock ├── VideoPlayerSample.xcodeproj └── project.pbxproj └── VideoPlayerSample ├── AppDelegate.h ├── AppDelegate.m ├── Default-568h@2x.png ├── Default.png ├── Default@2x.png ├── VideoPlayerSample-Info.plist ├── VideoPlayerSample-Prefix.pch ├── VideoPlayerSampleView.h ├── VideoPlayerSampleView.m ├── VideoPlayerSampleViewController.h ├── VideoPlayerSampleViewController.m ├── en.lproj └── InfoPlist.strings └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # build files # 2 | ############### 3 | build/ 4 | cache/ 5 | .bundle/ 6 | .idea/ 7 | *.zargo~ 8 | Pods/ 9 | Podfile.lock 10 | 11 | # Eclipse # 12 | ########### 13 | .metadata/ 14 | 15 | 16 | # Xcode files # 17 | ############### 18 | xcuserdata/ 19 | *.xcworkspace/ 20 | 21 | # Compiled source # 22 | ################### 23 | *.com 24 | *.class 25 | *.dll 26 | *.exe 27 | *.o 28 | *.so 29 | 30 | # Packages # 31 | ############ 32 | # it's better to unpack these files and commit the raw source 33 | # git has its own built in compression methods 34 | *.7z 35 | *.dmg 36 | *.gz 37 | *.iso 38 | *.jar 39 | *.rar 40 | *.tar 41 | *.zip 42 | 43 | # Logs and databases # 44 | ###################### 45 | *.log 46 | *.sql 47 | *.sqlite 48 | 49 | # OS generated files # 50 | ###################### 51 | .DS_Store* 52 | ehthumbs.db 53 | Icon? 54 | Thumbs.db 55 | -------------------------------------------------------------------------------- /Assets/airplay-display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/airplay-display.png -------------------------------------------------------------------------------- /Assets/airplay-display@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/airplay-display@2x.png -------------------------------------------------------------------------------- /Assets/fullscreen-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/fullscreen-button.png -------------------------------------------------------------------------------- /Assets/fullscreen-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/fullscreen-button@2x.png -------------------------------------------------------------------------------- /Assets/minimize-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/minimize-button.png -------------------------------------------------------------------------------- /Assets/minimize-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/minimize-button@2x.png -------------------------------------------------------------------------------- /Assets/pause-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/pause-button.png -------------------------------------------------------------------------------- /Assets/pause-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/pause-button@2x.png -------------------------------------------------------------------------------- /Assets/play-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/play-button.png -------------------------------------------------------------------------------- /Assets/play-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/play-button@2x.png -------------------------------------------------------------------------------- /Assets/share-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/share-button.png -------------------------------------------------------------------------------- /Assets/share-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/share-button@2x.png -------------------------------------------------------------------------------- /Assets/transparentBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/transparentBar.png -------------------------------------------------------------------------------- /Assets/transparentBar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Assets/transparentBar@2x.png -------------------------------------------------------------------------------- /Code/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/Code/.DS_Store -------------------------------------------------------------------------------- /Code/AirplayActiveView.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import 4 | 5 | @interface AirplayActiveView : UIView 6 | 7 | @end -------------------------------------------------------------------------------- /Code/AirplayActiveView.m: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import "AirplayActiveView.h" 4 | #import 5 | 6 | @interface AirplayActiveView () 7 | 8 | @property (readwrite, strong) CAGradientLayer *gradientLayer; 9 | @property (readwrite, strong) UIImageView *displayImageView; 10 | @property (readwrite, strong) UILabel *titleLabel; 11 | @property (readwrite, strong) UILabel *descriptionLabel; 12 | 13 | @end 14 | 15 | @implementation AirplayActiveView 16 | 17 | - (id)initWithFrame:(CGRect)frame 18 | { 19 | if ((self = [super initWithFrame:frame])) { 20 | _gradientLayer = [[CAGradientLayer alloc] init]; 21 | [_gradientLayer setColors:@[ 22 | (id)[[UIColor colorWithWhite:0.22f alpha:1.0f] CGColor], 23 | (id)[[UIColor colorWithWhite:0.09f alpha:1.0f] CGColor], 24 | ]]; 25 | [_gradientLayer setLocations:@[ @0.0, @1.0 ]]; 26 | [[self layer] addSublayer:_gradientLayer]; 27 | 28 | _displayImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"airplay-display.png"]]; 29 | [self addSubview:_displayImageView]; 30 | 31 | _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 32 | [_titleLabel setText:@"Airplay"]; 33 | [_titleLabel setFont:[UIFont fontWithName:@"DINRoundCompPro" size:20.0f]]; 34 | [_titleLabel setTextColor:[UIColor colorWithWhite:0.5f alpha:1.0f]]; 35 | [_titleLabel setBackgroundColor:[UIColor clearColor]]; 36 | [self addSubview:_titleLabel]; 37 | 38 | _descriptionLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 39 | [_descriptionLabel setText:@"This video is playing elsewhere"]; 40 | [_descriptionLabel setFont:[UIFont fontWithName:@"DINRoundCompPro" size:14.0f]]; 41 | [_descriptionLabel setTextColor:[UIColor colorWithWhite:0.36f alpha:1.0f]]; 42 | [_descriptionLabel setBackgroundColor:[UIColor clearColor]]; 43 | [self addSubview:_descriptionLabel]; 44 | } 45 | return self; 46 | } 47 | 48 | - (void)layoutSubviews 49 | { 50 | [super layoutSubviews]; 51 | 52 | CGRect bounds = [self bounds]; 53 | 54 | [_gradientLayer setFrame:bounds]; 55 | 56 | CGSize displayImageSize = [[_displayImageView image] size]; 57 | 58 | if (bounds.size.height < 300) { 59 | displayImageSize = CGSizeMake(displayImageSize.width / 2, displayImageSize.height / 2); 60 | } 61 | 62 | CGSize titleLabelSize = [[_titleLabel text] sizeWithFont:[_titleLabel font]]; 63 | CGSize descriptionLabelSize = [[_descriptionLabel text] sizeWithFont:[_descriptionLabel font]]; 64 | 65 | CGFloat contentHeight = displayImageSize.height + titleLabelSize.height + descriptionLabelSize.height; 66 | 67 | CGFloat y = (bounds.size.height / 2) - (contentHeight / 2); 68 | [_displayImageView setFrame:CGRectMake((bounds.size.width / 2) - (displayImageSize.width / 2), 69 | y, 70 | displayImageSize.width, 71 | displayImageSize.height)]; 72 | y += displayImageSize.height; 73 | 74 | [_titleLabel setFrame:CGRectMake((bounds.size.width / 2) - (titleLabelSize.width / 2), 75 | y, 76 | titleLabelSize.width, 77 | titleLabelSize.height)]; 78 | y += titleLabelSize.height - 8; 79 | 80 | [_descriptionLabel setFrame:CGRectMake((bounds.size.width / 2) - (descriptionLabelSize.width / 2), 81 | y, 82 | descriptionLabelSize.width, 83 | descriptionLabelSize.height)]; 84 | } 85 | 86 | @end -------------------------------------------------------------------------------- /Code/FullScreenView.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import 4 | 5 | @interface FullScreenView : UIView 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Code/FullScreenView.m: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import "FullScreenView.h" 4 | 5 | @implementation FullScreenView 6 | 7 | - (id)init 8 | { 9 | if ((self = [super init])) { 10 | self.autoresizesSubviews = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth; 11 | self.backgroundColor = [UIColor blackColor]; 12 | } 13 | 14 | return self; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Code/FullScreenViewController.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import 4 | 5 | @interface FullScreenViewController : UIViewController 6 | 7 | @property (nonatomic) BOOL allowPortraitFullscreen; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Code/FullScreenViewController.m: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import "FullScreenViewController.h" 4 | #import "FullScreenView.h" 5 | 6 | @interface FullScreenViewController () 7 | @property (nonatomic, strong) FullScreenView *fullScreenView; 8 | @end 9 | 10 | @implementation FullScreenViewController 11 | 12 | - (id)init 13 | { 14 | if (self = [super init]) { 15 | self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 16 | } 17 | 18 | return self; 19 | } 20 | 21 | - (void)loadView 22 | { 23 | self.fullScreenView = [[FullScreenView alloc] init]; 24 | [self setView:self.fullScreenView]; 25 | } 26 | 27 | - (NSUInteger)supportedInterfaceOrientations 28 | { 29 | if (!self.allowPortraitFullscreen) { 30 | return UIInterfaceOrientationMaskLandscape; 31 | } else { 32 | return UIInterfaceOrientationMaskAll; 33 | } 34 | } 35 | 36 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation 37 | { 38 | if (!self.allowPortraitFullscreen) { 39 | return UIInterfaceOrientationIsLandscape(toInterfaceOrientation); 40 | } else { 41 | return YES; 42 | } 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Code/VideoPlayer.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import 4 | 5 | extern NSString * const kVideoPlayerVideoChangedNotification; 6 | extern NSString * const kVideoPlayerWillHideControlsNotification; 7 | extern NSString * const kVideoPlayerWillShowControlsNotification; 8 | extern NSString * const kTrackEventVideoStart; 9 | extern NSString * const kTrackEventVideoLiveStart; 10 | extern NSString * const kTrackEventVideoComplete; 11 | 12 | @protocol VideoPlayerDelegate 13 | 14 | @optional 15 | @property (nonatomic) BOOL fullScreenToggled; 16 | - (void)trackEvent:(NSString *)event videoID:(NSString *)videoID title:(NSString *)title; 17 | 18 | @end 19 | 20 | @protocol VideoPlayer 21 | 22 | @property (readonly, strong) NSDictionary *currentVideoInfo; 23 | @property (nonatomic, weak) id delegate; 24 | @property (readonly) BOOL fullScreenModeToggled; 25 | @property (nonatomic) BOOL showStaticEndTime; 26 | @property (nonatomic) BOOL allowPortraitFullscreen; 27 | 28 | @property (nonatomic, readonly) BOOL isPlaying; 29 | 30 | - (void)playVideoWithTitle:(NSString *)title URL:(NSURL *)url videoID:(NSString *)videoID shareURL:(NSURL *)shareURL isStreaming:(BOOL)streaming playInFullScreen:(BOOL)playInFullScreen; 31 | - (void)showCannotFetchStreamError; 32 | 33 | - (void)launchFullScreen; 34 | - (void)minimizeVideo; 35 | - (void)playPauseHandler; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Code/VideoPlayerKit.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import 4 | #import "VideoPlayer.h" 5 | #import "VideoPlayerView.h" 6 | 7 | @interface VideoPlayerKit : UIViewController 8 | 9 | @property (nonatomic, weak) id delegate; 10 | @property (readonly, strong) NSDictionary *currentVideoInfo; 11 | @property (readonly, strong) VideoPlayerView *videoPlayerView; 12 | @property (readonly) BOOL fullScreenModeToggled; 13 | @property (nonatomic) BOOL showStaticEndTime; 14 | @property (nonatomic, readonly) BOOL isPlaying; 15 | @property (nonatomic) BOOL allowPortraitFullscreen; 16 | @property (nonatomic) UIEdgeInsets controlsEdgeInsets; 17 | @property (readwrite, strong) AVPlayer *videoPlayer; 18 | 19 | - (void)playVideoWithTitle:(NSString *)title URL:(NSURL *)url videoID:(NSString *)videoID shareURL:(NSURL *)shareURL isStreaming:(BOOL)streaming playInFullScreen:(BOOL)playInFullScreen; 20 | - (void)syncFullScreenButton:(UIInterfaceOrientation)toInterfaceOrientation; 21 | - (void)showCannotFetchStreamError; 22 | - (void)launchFullScreen; 23 | - (void)minimizeVideo; 24 | - (void)playPauseHandler; 25 | + (VideoPlayerKit *)videoPlayerWithContainingViewController:(UIViewController *)containingViewController 26 | optionalTopView:(UIView *)topView 27 | hideTopViewWithControls:(BOOL)hideTopViewWithControls 28 | __attribute__((deprecated("Replaced by videoPlayerWithContainingView:(UIView *)"))); 29 | + (VideoPlayerKit *)videoPlayerWithContainingView:(UIView *)containingView 30 | optionalTopView:(UIView *)topView 31 | hideTopViewWithControls:(BOOL)hideTopViewWithControls; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Code/VideoPlayerKit.m: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import "VideoPlayerKit.h" 4 | #import "FullScreenViewController.h" 5 | // #import "ShareThis.h" 6 | 7 | NSString * const kVideoPlayerVideoChangedNotification = @"VideoPlayerVideoChangedNotification"; 8 | NSString * const kVideoPlayerWillHideControlsNotification = @"VideoPlayerWillHideControlsNotitication"; 9 | NSString * const kVideoPlayerWillShowControlsNotification = @"VideoPlayerWillShowControlsNotification"; 10 | NSString * const kTrackEventVideoStart = @"Video Start"; 11 | NSString * const kTrackEventVideoLiveStart = @"Video Live Start"; 12 | NSString * const kTrackEventVideoComplete = @"Video Complete"; 13 | 14 | // Match the controls animation duration with status bar duration 15 | static const NSTimeInterval controlsAnimationDuration = 0.4; 16 | 17 | @interface VideoPlayerKit () 18 | 19 | @property (readwrite, strong) NSDictionary *currentVideoInfo; 20 | @property (readwrite, strong) VideoPlayerView *videoPlayerView; 21 | @property (readwrite) BOOL restoreVideoPlayStateAfterScrubbing; 22 | @property (readwrite, strong) id scrubberTimeObserver; 23 | @property (readwrite, strong) id playClockTimeObserver; 24 | @property (readwrite) BOOL seekToZeroBeforePlay; 25 | @property (readwrite) BOOL rotationIsLocked; 26 | @property (readwrite) BOOL playerIsBuffering; 27 | @property (nonatomic, weak) UIView *containingView; 28 | @property (nonatomic, weak) UIView *topView; 29 | @property (readwrite) BOOL fullScreenModeToggled; 30 | @property (nonatomic) BOOL isAlwaysFullscreen; 31 | @property (nonatomic, readwrite) BOOL isPlaying; 32 | @property (nonatomic, strong) FullScreenViewController *fullscreenViewController; 33 | @property (nonatomic) CGRect previousBounds; 34 | @property (nonatomic) BOOL hideTopViewWithControls; 35 | @property (nonatomic) UIStatusBarStyle previousStatusBarStyle; 36 | 37 | @end 38 | 39 | @implementation VideoPlayerKit { 40 | BOOL playWhenReady; 41 | BOOL scrubBuffering; 42 | BOOL showShareOptions; 43 | } 44 | 45 | - (void)setTopView:(UIView *)topView 46 | { 47 | _topView = topView; 48 | if (self.hideTopViewWithControls) { 49 | __weak UIView *weakTopView = _topView; 50 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 51 | 52 | [[NSNotificationCenter defaultCenter] addObserverForName:kVideoPlayerWillHideControlsNotification 53 | object:self 54 | queue:[NSOperationQueue mainQueue] 55 | usingBlock:^(NSNotification *note) { 56 | [UIView animateWithDuration:controlsAnimationDuration 57 | animations:^{ 58 | [weakTopView setAlpha:0.0f]; 59 | }]; 60 | }]; 61 | 62 | [[NSNotificationCenter defaultCenter] addObserverForName:kVideoPlayerWillShowControlsNotification 63 | object:self 64 | queue:[NSOperationQueue mainQueue] 65 | usingBlock:^(NSNotification *note) { 66 | [UIView animateWithDuration:controlsAnimationDuration 67 | animations:^{ 68 | [weakTopView setAlpha:1.0f]; 69 | }]; 70 | }]; 71 | } 72 | } 73 | 74 | - (id)initWithContainingView:(UIView *)containingView optionalTopView:(UIView *)topView hideTopViewWithControls:(BOOL)hideTopViewWithControls 75 | { 76 | if ((self = [super init])) { 77 | self.containingView = containingView; 78 | self.hideTopViewWithControls = hideTopViewWithControls; 79 | self.topView = topView; 80 | self.previousStatusBarStyle = [[UIApplication sharedApplication] statusBarStyle]; 81 | } 82 | 83 | return self; 84 | } 85 | 86 | + (VideoPlayerKit *)videoPlayerWithContainingViewController:(UIViewController *)containingViewController 87 | optionalTopView:(UIView *)topView 88 | hideTopViewWithControls:(BOOL)hideTopViewWithControls 89 | { 90 | VideoPlayerKit *videoPlayer = [[VideoPlayerKit alloc] initWithContainingView:containingViewController.view 91 | optionalTopView:topView 92 | hideTopViewWithControls:hideTopViewWithControls]; 93 | 94 | return videoPlayer; 95 | } 96 | 97 | + (VideoPlayerKit *)videoPlayerWithContainingView:(UIView *)containingView 98 | optionalTopView:(UIView *)topView 99 | hideTopViewWithControls:(BOOL)hideTopViewWithControls 100 | { 101 | VideoPlayerKit *videoPlayer = [[VideoPlayerKit alloc] initWithContainingView:containingView 102 | optionalTopView:topView 103 | hideTopViewWithControls:hideTopViewWithControls]; 104 | 105 | return videoPlayer; 106 | } 107 | 108 | - (void)setControlsEdgeInsets:(UIEdgeInsets)controlsEdgeInsets 109 | { 110 | if (!self.videoPlayerView) { 111 | self.videoPlayerView = [[VideoPlayerView alloc] initWithFrame:self.containingView.bounds]; 112 | } 113 | _controlsEdgeInsets = controlsEdgeInsets; 114 | self.videoPlayerView.controlsEdgeInsets = _controlsEdgeInsets; 115 | 116 | [self.view setNeedsLayout]; 117 | } 118 | 119 | - (void)dealloc 120 | { 121 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 122 | 123 | [self removeObserversFromVideoPlayerItem]; 124 | [self removePlayerTimeObservers]; 125 | } 126 | 127 | - (void)removeObserversFromVideoPlayerItem 128 | { 129 | [self.videoPlayer.currentItem removeObserver:self forKeyPath:@"status"]; 130 | [self.videoPlayer.currentItem removeObserver:self forKeyPath:@"playbackBufferEmpty"]; 131 | [self.videoPlayer.currentItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"]; 132 | [self.videoPlayer.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"]; 133 | [_videoPlayer removeObserver:self forKeyPath:@"externalPlaybackActive"]; 134 | [_videoPlayer removeObserver:self forKeyPath:@"airPlayVideoActive"]; 135 | } 136 | 137 | - (void)loadView 138 | { 139 | if (!self.videoPlayerView) { 140 | self.videoPlayerView = [[VideoPlayerView alloc] initWithFrame:self.containingView.bounds]; 141 | } 142 | 143 | if (self.topView) { 144 | self.topView.frame = CGRectMake(0, 0, self.videoPlayerView.frame.size.width, self.topView.frame.size.height); 145 | [self.videoPlayerView addSubview:self.topView]; 146 | } 147 | 148 | self.view = self.videoPlayerView; 149 | } 150 | 151 | - (void)viewDidLoad 152 | { 153 | [super viewDidLoad]; 154 | 155 | _currentVideoInfo = [[NSDictionary alloc] init]; 156 | 157 | [_videoPlayerView.playPauseButton addTarget:self action:@selector(playPauseHandler) forControlEvents:UIControlEventTouchUpInside]; 158 | 159 | [_videoPlayerView.fullScreenButton addTarget:self action:@selector(fullScreenButtonHandler) forControlEvents:UIControlEventTouchUpInside]; 160 | 161 | [self.videoPlayerView.shareButton addTarget:self action:@selector(shareButtonHandler) forControlEvents:UIControlEventTouchUpInside]; 162 | 163 | [_videoPlayerView.videoScrubber addTarget:self action:@selector(scrubbingDidBegin) forControlEvents:UIControlEventTouchDown]; 164 | [_videoPlayerView.videoScrubber addTarget:self action:@selector(scrubberIsScrolling) forControlEvents:UIControlEventValueChanged]; 165 | [_videoPlayerView.videoScrubber addTarget:self action:@selector(scrubbingDidEnd) forControlEvents:(UIControlEventTouchUpInside | UIControlEventTouchCancel)]; 166 | 167 | UITapGestureRecognizer *playerTouchedGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(videoTapHandler)]; 168 | playerTouchedGesture.delegate = self; 169 | [_videoPlayerView addGestureRecognizer:playerTouchedGesture]; 170 | 171 | UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchGesture:)]; 172 | [pinchRecognizer setDelegate:self]; 173 | [self.view addGestureRecognizer:pinchRecognizer]; 174 | } 175 | 176 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch 177 | { 178 | if ([touch.view isDescendantOfView:self.videoPlayerView.playerControlBar] || [touch.view isDescendantOfView:self.videoPlayerView.shareButton]) { 179 | return NO; 180 | } 181 | return YES; 182 | } 183 | 184 | 185 | - (void)viewWillAppear:(BOOL)animated 186 | { 187 | if (self.fullScreenModeToggled) { 188 | BOOL isHidingPlayerControls = self.videoPlayerView.playerControlBar.alpha == 0; 189 | [[UIApplication sharedApplication] setStatusBarHidden:isHidingPlayerControls withAnimation:UIStatusBarAnimationNone]; 190 | } else { 191 | [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone]; 192 | } 193 | } 194 | 195 | - (void)presentShareOptions 196 | { 197 | showShareOptions = NO; 198 | // [ShareThis showShareOptionsToShareUrl:[_currentVideoInfo objectForKey:@"shareURL"] title:[_currentVideoInfo objectForKey:@"title"] image:nil onViewController:[[[UIApplication sharedApplication] keyWindow] rootViewController] forTypeOfContent:STContentTypeVideo]; 199 | } 200 | 201 | - (void)shareButtonHandler 202 | { 203 | // Minimize the video if fullscreen so that ShareThis can work 204 | if (self.fullScreenModeToggled) { 205 | showShareOptions = YES; 206 | [self minimizeVideo]; 207 | } else { 208 | [self presentShareOptions]; 209 | } 210 | } 211 | 212 | - (void)playVideoWithTitle:(NSString *)title URL:(NSURL *)url videoID:(NSString *)videoID shareURL:(NSURL *)shareURL isStreaming:(BOOL)streaming playInFullScreen:(BOOL)playInFullScreen 213 | { 214 | [self.videoPlayer pause]; 215 | 216 | [[_videoPlayerView activityIndicator] startAnimating]; 217 | // Reset the buffer bar back to 0 218 | [self.videoPlayerView.progressView setProgress:0 animated:NO]; 219 | [self showControls]; 220 | 221 | NSString *vidID = videoID ?: @""; 222 | _currentVideoInfo = @{ @"title": title ?: @"", @"videoID": vidID, @"isStreaming": @(streaming), @"shareURL": shareURL ?: url}; 223 | 224 | [[NSNotificationCenter defaultCenter] postNotificationName:kVideoPlayerVideoChangedNotification 225 | object:self 226 | userInfo:_currentVideoInfo]; 227 | if ([self.delegate respondsToSelector:@selector(trackEvent:videoID:title:)]) { 228 | if (streaming) { 229 | [self.delegate trackEvent:kTrackEventVideoLiveStart videoID:vidID title:title]; 230 | } else { 231 | [self.delegate trackEvent:kTrackEventVideoStart videoID:vidID title:title]; 232 | } 233 | } 234 | 235 | [_videoPlayerView.currentPositionLabel setText:@""]; 236 | [_videoPlayerView.timeLeftLabel setText:@""]; 237 | _videoPlayerView.videoScrubber.value = 0; 238 | 239 | [_videoPlayerView setTitle:title]; 240 | 241 | [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:@{ 242 | MPMediaItemPropertyTitle: title, 243 | }]; 244 | 245 | [self setURL:url]; 246 | 247 | [self syncPlayPauseButtons]; 248 | 249 | if (playInFullScreen) { 250 | self.isAlwaysFullscreen = YES; 251 | [self launchFullScreen]; 252 | } else { 253 | [self.containingView addSubview:self.videoPlayerView]; 254 | } 255 | } 256 | 257 | - (void)showCannotFetchStreamError 258 | { 259 | UIAlertView *alertView = [[UIAlertView alloc] 260 | initWithTitle:@"Sad Panda says..." 261 | message:@"I can't seem to fetch that stream. Please try again later." 262 | delegate:nil 263 | cancelButtonTitle:@"Bummer!" 264 | otherButtonTitles:nil]; 265 | [alertView show]; 266 | } 267 | 268 | - (void)playPauseHandler 269 | { 270 | if (_seekToZeroBeforePlay) { 271 | _seekToZeroBeforePlay = NO; 272 | [_videoPlayer seekToTime:kCMTimeZero]; 273 | } 274 | 275 | if ([self isPlaying]) { 276 | [_videoPlayer pause]; 277 | } else { 278 | [self playVideo]; 279 | [[_videoPlayerView activityIndicator] stopAnimating]; 280 | } 281 | 282 | [self syncPlayPauseButtons]; 283 | [self showControls]; 284 | } 285 | 286 | - (void)launchFullScreen 287 | { 288 | if (!self.fullScreenModeToggled) { 289 | self.fullScreenModeToggled = YES; 290 | 291 | if (!self.isAlwaysFullscreen) { 292 | [self hideControlsAnimated:YES]; 293 | } 294 | 295 | [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent]; 296 | [self syncFullScreenButton:[[UIApplication sharedApplication] statusBarOrientation]]; 297 | 298 | if (!self.fullscreenViewController) { 299 | self.fullscreenViewController = [[FullScreenViewController alloc] init]; 300 | self.fullscreenViewController.allowPortraitFullscreen = self.allowPortraitFullscreen; 301 | } 302 | 303 | [self.videoPlayerView setFullscreen:YES]; 304 | [self.fullscreenViewController.view addSubview:self.videoPlayerView]; 305 | 306 | 307 | if (self.topView) { 308 | [self.topView removeFromSuperview]; 309 | [self.fullscreenViewController.view addSubview:self.topView]; 310 | } 311 | 312 | if (self.isAlwaysFullscreen) { 313 | self.videoPlayerView.alpha = 0.0; 314 | if (self.topView) { 315 | self.topView.alpha = 0.0; 316 | } 317 | } else { 318 | self.previousBounds = self.videoPlayerView.frame; 319 | [UIView animateWithDuration:0.45f 320 | delay:0.0f 321 | options:UIViewAnimationCurveLinear 322 | animations:^{ 323 | [self.videoPlayerView setCenter:CGPointMake( self.videoPlayerView.superview.bounds.size.width / 2, ( self.videoPlayerView.superview.bounds.size.height / 2))]; 324 | self.videoPlayerView.bounds = self.videoPlayerView.superview.bounds; 325 | } 326 | completion:nil]; 327 | } 328 | 329 | [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:self.fullscreenViewController animated:YES completion:^{ 330 | if (self.isAlwaysFullscreen) { 331 | self.videoPlayerView.frame = CGRectMake(self.videoPlayerView.superview.bounds.size.width / 2, self.videoPlayerView.superview.bounds.size.height / 2, 0, 0); 332 | self.previousBounds = CGRectMake(self.videoPlayerView.superview.bounds.size.width / 2, self.videoPlayerView.superview.bounds.size.height / 2, 0, 0); 333 | [self.videoPlayerView setCenter:CGPointMake( self.videoPlayerView.superview.bounds.size.width / 2, self.videoPlayerView.superview.bounds.size.height / 2)]; 334 | [UIView animateWithDuration:0.25f 335 | delay:0.0f 336 | options:UIViewAnimationCurveLinear 337 | animations:^{ 338 | self.videoPlayerView.alpha = 1.0; 339 | self.topView.alpha = 1.0; 340 | } 341 | completion:nil]; 342 | 343 | self.videoPlayerView.frame = self.videoPlayerView.superview.bounds; 344 | } 345 | 346 | if (self.topView) { 347 | self.topView.frame = CGRectMake(0, 0, self.videoPlayerView.frame.size.width, self.topView.frame.size.height); 348 | } 349 | 350 | if ([self.delegate respondsToSelector:@selector(setFullScreenToggled:)]) { 351 | [self.delegate setFullScreenToggled:self.fullScreenModeToggled]; 352 | } 353 | }]; 354 | } 355 | } 356 | 357 | - (void)minimizeVideo 358 | { 359 | if (self.fullScreenModeToggled) { 360 | self.fullScreenModeToggled = NO; 361 | [self.videoPlayerView setFullscreen:NO]; 362 | [self hideControlsAnimated:NO]; 363 | [self syncFullScreenButton:self.interfaceOrientation]; 364 | 365 | if (self.topView) { 366 | [self.topView removeFromSuperview]; 367 | [self.videoPlayerView addSubview:self.topView]; 368 | } 369 | 370 | if (self.isAlwaysFullscreen) { 371 | [self.videoPlayer pause]; 372 | [UIView animateWithDuration:0.45f 373 | delay:0.0f 374 | options:UIViewAnimationCurveLinear 375 | animations:^{ 376 | self.videoPlayerView.frame = self.previousBounds; 377 | } 378 | completion:^(BOOL success){ 379 | 380 | if (showShareOptions) { 381 | [self presentShareOptions]; 382 | } 383 | 384 | [self.videoPlayerView removeFromSuperview]; 385 | }]; 386 | } else { 387 | [UIView animateWithDuration:0.45f 388 | delay:0.0f 389 | options:UIViewAnimationCurveLinear 390 | animations:^{ 391 | self.videoPlayerView.frame = self.previousBounds; 392 | } 393 | completion:^(BOOL success){ 394 | if (showShareOptions) { 395 | [self presentShareOptions]; 396 | } 397 | }]; 398 | 399 | [self.videoPlayerView removeFromSuperview]; 400 | [self.containingView addSubview:self.videoPlayerView]; 401 | } 402 | 403 | [[UIApplication sharedApplication] setStatusBarStyle:self.previousStatusBarStyle]; 404 | 405 | [[UIApplication sharedApplication].keyWindow.rootViewController dismissViewControllerAnimated:self.isAlwaysFullscreen completion:^{ 406 | 407 | if (!self.isAlwaysFullscreen) { 408 | [self showControls]; 409 | } 410 | [[UIApplication sharedApplication] setStatusBarHidden:NO 411 | withAnimation:UIStatusBarAnimationFade]; 412 | 413 | if ([self.delegate respondsToSelector:@selector(setFullScreenToggled:)]) { 414 | [self.delegate setFullScreenToggled:self.fullScreenModeToggled]; 415 | } 416 | }]; 417 | } 418 | } 419 | 420 | - (void)fullScreenButtonHandler 421 | { 422 | [self showControls]; 423 | 424 | if (self.fullScreenModeToggled) { 425 | [self minimizeVideo]; 426 | } else { 427 | [self launchFullScreen]; 428 | } 429 | } 430 | 431 | - (void)pinchGesture:(id)sender 432 | { 433 | if([(UIPinchGestureRecognizer *)sender state] == UIGestureRecognizerStateEnded) { 434 | [self fullScreenButtonHandler]; 435 | } 436 | } 437 | 438 | - (void)forceOrientationChange 439 | { 440 | _rotationIsLocked = YES; 441 | [self performSelector:@selector(unlockRotationLock) withObject:nil afterDelay:0.5]; 442 | 443 | if ([[[UIDevice currentDevice] systemVersion] floatValue] < 6.0) { 444 | UIWindow *window = [[UIApplication sharedApplication] keyWindow]; 445 | UIView *view = [window.subviews objectAtIndex:0]; 446 | [view removeFromSuperview]; 447 | [window addSubview:view]; 448 | 449 | [_videoPlayerView.superview layoutSubviews]; 450 | } else { 451 | // Have the VideoPlayerVC's parent VC implement rotation trigger 452 | if ([self.delegate respondsToSelector:@selector(setFullScreenToggled:)]) { 453 | [self.delegate setFullScreenToggled:self.fullScreenModeToggled]; 454 | } 455 | } 456 | } 457 | 458 | - (void)unlockRotationLock 459 | { 460 | _rotationIsLocked = NO; 461 | } 462 | 463 | - (void)videoTapHandler 464 | { 465 | if (_videoPlayerView.playerControlBar.alpha) { 466 | [self hideControlsAnimated:YES]; 467 | } else { 468 | [self showControls]; 469 | } 470 | } 471 | 472 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 473 | { 474 | return YES; 475 | } 476 | 477 | - (void)setURL:(NSURL *)url 478 | { 479 | AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url]; 480 | 481 | [playerItem addObserver:self 482 | forKeyPath:@"status" 483 | options:NSKeyValueObservingOptionNew 484 | context:nil]; 485 | 486 | [playerItem addObserver:self 487 | forKeyPath:@"playbackBufferEmpty" 488 | options:NSKeyValueObservingOptionNew 489 | context:nil]; 490 | 491 | [playerItem addObserver:self 492 | forKeyPath:@"playbackLikelyToKeepUp" 493 | options:NSKeyValueObservingOptionNew 494 | context:nil]; 495 | 496 | [playerItem addObserver:self 497 | forKeyPath:@"loadedTimeRanges" 498 | options:NSKeyValueObservingOptionNew 499 | context:nil]; 500 | 501 | if (!self.videoPlayer) { 502 | _videoPlayer = [AVPlayer playerWithPlayerItem:playerItem]; 503 | [_videoPlayer setAllowsAirPlayVideo:YES]; 504 | [_videoPlayer setUsesAirPlayVideoWhileAirPlayScreenIsActive:YES]; 505 | 506 | if ([_videoPlayer respondsToSelector:@selector(setAllowsExternalPlayback:)]) { // iOS 6 API 507 | [_videoPlayer setAllowsExternalPlayback:YES]; 508 | } 509 | 510 | [_videoPlayerView setPlayer:_videoPlayer]; 511 | } else { 512 | [self removeObserversFromVideoPlayerItem]; 513 | [self.videoPlayer replaceCurrentItemWithPlayerItem:playerItem]; 514 | } 515 | 516 | // iOS 5 517 | [_videoPlayer addObserver:self forKeyPath:@"airPlayVideoActive" options:NSKeyValueObservingOptionNew context:nil]; 518 | 519 | // iOS 6 520 | [_videoPlayer addObserver:self 521 | forKeyPath:@"externalPlaybackActive" 522 | options:NSKeyValueObservingOptionNew 523 | context:nil]; 524 | 525 | [[NSNotificationCenter defaultCenter] addObserver:self 526 | selector:@selector(playerItemDidReachEnd:) 527 | name:AVPlayerItemDidPlayToEndTimeNotification 528 | object:self.videoPlayer.currentItem]; 529 | } 530 | 531 | // Wait for the video player status to change to ready before initializing video player controls 532 | - (void)observeValueForKeyPath:(NSString *)keyPath 533 | ofObject:(id)object 534 | change:(NSDictionary *)change 535 | context:(void *)context 536 | { 537 | if (object == _videoPlayer 538 | && ([keyPath isEqualToString:@"externalPlaybackActive"] || [keyPath isEqualToString:@"airPlayVideoActive"])) { 539 | BOOL externalPlaybackActive = [[change objectForKey:NSKeyValueChangeNewKey] boolValue]; 540 | [[_videoPlayerView airplayIsActiveView] setHidden:!externalPlaybackActive]; 541 | return; 542 | } 543 | 544 | if (object != [_videoPlayer currentItem]) { 545 | return; 546 | } 547 | 548 | if ([keyPath isEqualToString:@"status"]) { 549 | AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue]; 550 | switch (status) { 551 | case AVPlayerStatusReadyToPlay: 552 | playWhenReady = YES; 553 | break; 554 | case AVPlayerStatusFailed: 555 | // TODO: 556 | [self removeObserversFromVideoPlayerItem]; 557 | [self removePlayerTimeObservers]; 558 | self.videoPlayer = nil; 559 | break; 560 | } 561 | } else if ([keyPath isEqualToString:@"playbackBufferEmpty"] && _videoPlayer.currentItem.playbackBufferEmpty) { 562 | self.playerIsBuffering = YES; 563 | [[_videoPlayerView activityIndicator] startAnimating]; 564 | [self syncPlayPauseButtons]; 565 | } else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"] && _videoPlayer.currentItem.playbackLikelyToKeepUp) { 566 | if (![self isPlaying] && playWhenReady) 567 | { 568 | if (self.playerIsBuffering || scrubBuffering) { 569 | if (self.restoreVideoPlayStateAfterScrubbing) { 570 | self.restoreVideoPlayStateAfterScrubbing = NO; 571 | [self playVideo]; 572 | } 573 | } else { 574 | [self playVideo]; 575 | } 576 | } 577 | [[_videoPlayerView activityIndicator] stopAnimating]; 578 | 579 | } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) { 580 | float durationTime = CMTimeGetSeconds([[self.videoPlayer currentItem] duration]); 581 | float bufferTime = [self availableDuration]; 582 | [self.videoPlayerView.progressView setProgress:bufferTime/durationTime animated:YES]; 583 | } 584 | 585 | return; 586 | } 587 | 588 | - (float)availableDuration 589 | { 590 | NSArray *loadedTimeRanges = [[self.videoPlayer currentItem] loadedTimeRanges]; 591 | 592 | // Check to see if the timerange is not an empty array, fix for when video goes on airplay 593 | // and video doesn't include any time ranges 594 | if ([loadedTimeRanges count] > 0) { 595 | CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue]; 596 | float startSeconds = CMTimeGetSeconds(timeRange.start); 597 | float durationSeconds = CMTimeGetSeconds(timeRange.duration); 598 | return (startSeconds + durationSeconds); 599 | } else { 600 | return 0.0f; 601 | } 602 | } 603 | 604 | - (void)playVideo 605 | { 606 | if (self.view.superview) { 607 | self.playerIsBuffering = NO; 608 | scrubBuffering = NO; 609 | playWhenReady = NO; 610 | // Configuration is done, ready to start. 611 | [self.videoPlayer play]; 612 | [self updatePlaybackProgress]; 613 | } 614 | } 615 | 616 | - (void)showControls 617 | { 618 | [[NSNotificationCenter defaultCenter] postNotificationName:kVideoPlayerWillShowControlsNotification 619 | object:self 620 | userInfo:nil]; 621 | [UIView animateWithDuration:controlsAnimationDuration animations:^{ 622 | self.videoPlayerView.playerControlBar.alpha = 1.0; 623 | self.videoPlayerView.titleLabel.alpha = 1.0; 624 | _videoPlayerView.shareButton.alpha = 1.0; 625 | } completion:nil]; 626 | 627 | if (self.fullScreenModeToggled) { 628 | [[UIApplication sharedApplication] setStatusBarHidden:NO 629 | withAnimation:UIStatusBarAnimationFade]; 630 | } 631 | 632 | [NSObject cancelPreviousPerformRequestsWithTarget:self 633 | selector:@selector(hideControlsAnimated:) 634 | object:[NSString stringWithFormat:@"YES"]]; 635 | 636 | if ([self isPlaying]) { 637 | [self performSelector:@selector(hideControlsAnimated:) 638 | withObject:[NSString stringWithFormat:@"YES"] 639 | afterDelay:4.0]; 640 | } 641 | } 642 | 643 | - (void)hideControlsAnimated:(BOOL)animated 644 | { 645 | [[NSNotificationCenter defaultCenter] postNotificationName:kVideoPlayerWillHideControlsNotification 646 | object:self 647 | userInfo:nil]; 648 | if (animated) { 649 | [UIView animateWithDuration:controlsAnimationDuration animations:^{ 650 | self.videoPlayerView.playerControlBar.alpha = 0; 651 | self.videoPlayerView.titleLabel.alpha = 0; 652 | _videoPlayerView.shareButton.alpha = 0; 653 | } completion:nil]; 654 | 655 | if (self.fullScreenModeToggled) { 656 | [[UIApplication sharedApplication] setStatusBarHidden:YES 657 | withAnimation:UIStatusBarAnimationFade]; 658 | } 659 | 660 | } else { 661 | self.videoPlayerView.playerControlBar.alpha = 0; 662 | self.videoPlayerView.titleLabel.alpha = 0; 663 | _videoPlayerView.shareButton.alpha = 0; 664 | if (self.fullScreenModeToggled) { 665 | [[UIApplication sharedApplication] setStatusBarHidden:YES 666 | withAnimation:UIStatusBarAnimationNone]; 667 | } 668 | } 669 | } 670 | 671 | - (void)updatePlaybackProgress 672 | { 673 | [self syncPlayPauseButtons]; 674 | [self showControls]; 675 | 676 | double interval = .1f; 677 | 678 | CMTime playerDuration = [self playerItemDuration]; 679 | if (CMTIME_IS_INVALID(playerDuration)) { 680 | return; 681 | } 682 | 683 | double duration = CMTimeGetSeconds(playerDuration); 684 | if (CMTIME_IS_INDEFINITE(playerDuration) || duration <= 0) { 685 | [_videoPlayerView.videoScrubber setHidden:YES]; 686 | [_videoPlayerView.progressView setHidden:YES]; 687 | [self syncPlayClock]; 688 | return; 689 | } 690 | 691 | [_videoPlayerView.videoScrubber setHidden:NO]; 692 | [_videoPlayerView.progressView setHidden:NO]; 693 | 694 | CGFloat width = CGRectGetWidth([_videoPlayerView.videoScrubber bounds]); 695 | interval = 0.5f * duration / width; 696 | __weak VideoPlayerKit *vpvc = self; 697 | _scrubberTimeObserver = [_videoPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC) 698 | queue:NULL 699 | usingBlock:^(CMTime time) { 700 | [vpvc syncScrubber]; 701 | }]; 702 | 703 | // Update the play clock every second 704 | _playClockTimeObserver = [_videoPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1, NSEC_PER_SEC) 705 | queue:NULL 706 | usingBlock:^(CMTime time) { 707 | [vpvc syncPlayClock]; 708 | }]; 709 | 710 | } 711 | 712 | -(void)removePlayerTimeObservers 713 | { 714 | if (_scrubberTimeObserver) { 715 | [_videoPlayer removeTimeObserver:_scrubberTimeObserver]; 716 | _scrubberTimeObserver = nil; 717 | } 718 | 719 | if (_playClockTimeObserver) { 720 | [_videoPlayer removeTimeObserver:_playClockTimeObserver]; 721 | _playClockTimeObserver = nil; 722 | } 723 | } 724 | 725 | - (void)playerItemDidReachEnd:(NSNotification *)notification 726 | { 727 | [self syncPlayPauseButtons]; 728 | _seekToZeroBeforePlay = YES; 729 | if ([self.delegate respondsToSelector:@selector(trackEvent:videoID:title:)]) { 730 | [self.delegate trackEvent:kTrackEventVideoComplete videoID:[_currentVideoInfo objectForKey:@"videoID"] title:[_currentVideoInfo objectForKey:@"title"]]; 731 | } 732 | 733 | [self minimizeVideo]; 734 | } 735 | 736 | - (void)syncScrubber 737 | { 738 | CMTime playerDuration = [self playerItemDuration]; 739 | if (CMTIME_IS_INVALID(playerDuration)) { 740 | _videoPlayerView.videoScrubber.minimumValue = 0.0; 741 | return; 742 | } 743 | 744 | double duration = CMTimeGetSeconds(playerDuration); 745 | if (isfinite(duration)) { 746 | float minValue = [_videoPlayerView.videoScrubber minimumValue]; 747 | float maxValue = [_videoPlayerView.videoScrubber maximumValue]; 748 | double time = CMTimeGetSeconds([_videoPlayer currentTime]); 749 | 750 | [_videoPlayerView.videoScrubber setValue:(maxValue - minValue) * time / duration + minValue]; 751 | } 752 | } 753 | 754 | - (void)syncPlayClock 755 | { 756 | CMTime playerDuration = [self playerItemDuration]; 757 | if (CMTIME_IS_INVALID(playerDuration)) { 758 | return; 759 | } 760 | 761 | if (CMTIME_IS_INDEFINITE(playerDuration)) { 762 | [_videoPlayerView.currentPositionLabel setText:@"LIVE"]; 763 | [_videoPlayerView.timeLeftLabel setText:@""]; 764 | return; 765 | } 766 | 767 | double duration = CMTimeGetSeconds(playerDuration); 768 | if (isfinite(duration)) { 769 | double currentTime = floor(CMTimeGetSeconds([_videoPlayer currentTime])); 770 | double timeLeft = floor(duration - currentTime); 771 | 772 | if (currentTime <= 0) { 773 | currentTime = 0; 774 | timeLeft = floor(duration); 775 | } 776 | 777 | [_videoPlayerView.currentPositionLabel setText:[NSString stringWithFormat:@"%@ ", [self stringFormattedTimeFromSeconds:¤tTime]]]; 778 | if (!self.showStaticEndTime) { 779 | [_videoPlayerView.timeLeftLabel setText:[NSString stringWithFormat:@"-%@", [self stringFormattedTimeFromSeconds:&timeLeft]]]; 780 | } else { 781 | [_videoPlayerView.timeLeftLabel setText:[NSString stringWithFormat:@"%@", [self stringFormattedTimeFromSeconds:&duration]]]; 782 | } 783 | } 784 | } 785 | 786 | - (CMTime)playerItemDuration 787 | { 788 | if (_videoPlayer.status == AVPlayerItemStatusReadyToPlay) { 789 | return([_videoPlayer.currentItem duration]); 790 | } 791 | 792 | return(kCMTimeInvalid); 793 | } 794 | 795 | - (BOOL)isPlaying 796 | { 797 | return [_videoPlayer rate] != 0.0; 798 | } 799 | 800 | - (void)syncPlayPauseButtons 801 | { 802 | if ([self isPlaying]) { 803 | [_videoPlayerView.playPauseButton setImage:[UIImage imageNamed:@"pause-button"] forState:UIControlStateNormal]; 804 | } else { 805 | [_videoPlayerView.playPauseButton setImage:[UIImage imageNamed:@"play-button"] forState:UIControlStateNormal]; 806 | } 807 | } 808 | 809 | - (void)syncFullScreenButton:(UIInterfaceOrientation)toInterfaceOrientation 810 | { 811 | if (_fullScreenModeToggled) { 812 | [_videoPlayerView.fullScreenButton setImage:[UIImage imageNamed:@"minimize-button"] forState:UIControlStateNormal]; 813 | } else { 814 | [_videoPlayerView.fullScreenButton setImage:[UIImage imageNamed:@"fullscreen-button"] forState:UIControlStateNormal]; 815 | } 816 | } 817 | 818 | -(void)scrubbingDidBegin 819 | { 820 | if ([self isPlaying]) { 821 | [_videoPlayer pause]; 822 | [self syncPlayPauseButtons]; 823 | self.restoreVideoPlayStateAfterScrubbing = YES; 824 | [self showControls]; 825 | } 826 | } 827 | 828 | -(void)scrubberIsScrolling 829 | { 830 | CMTime playerDuration = [self playerItemDuration]; 831 | double duration = CMTimeGetSeconds(playerDuration); 832 | if (isfinite(duration)) { 833 | double currentTime = floor(duration * _videoPlayerView.videoScrubber.value); 834 | double timeLeft = floor(duration - currentTime); 835 | 836 | if (currentTime <= 0) { 837 | currentTime = 0; 838 | timeLeft = floor(duration); 839 | } 840 | 841 | [_videoPlayerView.currentPositionLabel setText:[NSString stringWithFormat:@"%@ ", [self stringFormattedTimeFromSeconds:¤tTime]]]; 842 | 843 | if (!self.showStaticEndTime) { 844 | [_videoPlayerView.timeLeftLabel setText:[NSString stringWithFormat:@"-%@", [self stringFormattedTimeFromSeconds:&timeLeft]]]; 845 | } else { 846 | [_videoPlayerView.timeLeftLabel setText:[NSString stringWithFormat:@"%@", [self stringFormattedTimeFromSeconds:&duration]]]; 847 | } 848 | [_videoPlayer seekToTime:CMTimeMakeWithSeconds((float) currentTime, NSEC_PER_SEC)]; 849 | } 850 | } 851 | 852 | -(void)scrubbingDidEnd 853 | { 854 | if (self.restoreVideoPlayStateAfterScrubbing) { 855 | scrubBuffering = YES; 856 | } 857 | [[_videoPlayerView activityIndicator] startAnimating]; 858 | 859 | [self showControls]; 860 | } 861 | 862 | - (NSString *)stringFormattedTimeFromSeconds:(double *)seconds 863 | { 864 | NSDate *date = [NSDate dateWithTimeIntervalSince1970:*seconds]; 865 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 866 | [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; 867 | if (*seconds >= 3600) { 868 | [formatter setDateFormat:@"HH:mm:ss"]; 869 | } else { 870 | [formatter setDateFormat:@"mm:ss"]; 871 | } 872 | 873 | return [formatter stringFromDate:date]; 874 | } 875 | 876 | @end 877 | -------------------------------------------------------------------------------- /Code/VideoPlayerView.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import 4 | #import 5 | #import 6 | #import 7 | #import "AirplayActiveView.h" 8 | 9 | @interface VideoPlayerView : UIView { 10 | BOOL _fullscreen; 11 | } 12 | 13 | @property (readwrite) CGFloat padding; 14 | @property (readonly, strong) UILabel *titleLabel; 15 | @property (readonly, strong) AirplayActiveView *airplayIsActiveView; 16 | @property (readonly, strong) UIView *playerControlBar; 17 | @property (readonly, strong) UIButton *playPauseButton; 18 | @property (readonly, strong) UIButton *fullScreenButton; 19 | @property (readonly, strong) UISlider *videoScrubber; 20 | @property (readonly, strong) UILabel *currentPositionLabel; 21 | @property (readonly, strong) UILabel *timeLeftLabel; 22 | @property (readonly, strong) UIProgressView *progressView; 23 | @property (readonly, strong) UIButton *shareButton; 24 | @property (readwrite) UIEdgeInsets controlsEdgeInsets; 25 | @property (nonatomic, readwrite, getter=isFullscreen) BOOL fullscreen; 26 | 27 | @property (readonly, strong) UIActivityIndicatorView *activityIndicator; 28 | 29 | - (void)setTitle:(NSString *)title; 30 | - (CGFloat)heightForWidth:(CGFloat)width; 31 | - (void)setPlayer:(AVPlayer *)player; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Code/VideoPlayerView.m: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import "VideoPlayerView.h" 4 | 5 | #define PLAYER_CONTROL_BAR_HEIGHT 40 6 | #define LABEL_PADDING 10 7 | #define CURRENT_POSITION_WIDTH 56 8 | #define TIME_LEFT_WIDTH 59 9 | #define ALIGNMENT_FUZZ 2 10 | #define ROUTE_BUTTON_ALIGNMENT_FUZZ 8 11 | 12 | @interface VideoPlayerView () 13 | 14 | @property (readwrite, strong) UILabel *titleLabel; 15 | @property (readwrite, strong) UIView *playerControlBar; 16 | @property (readwrite, strong) AirplayActiveView *airplayIsActiveView; 17 | @property (readwrite, strong) UIButton *airplayButton; 18 | @property (readwrite, strong) MPVolumeView *volumeView; 19 | @property (readwrite, strong) UIButton *fullScreenButton; 20 | @property (readwrite, strong) UIButton *playPauseButton; 21 | @property (readwrite, strong) UISlider *videoScrubber; 22 | @property (readwrite, strong) UILabel *currentPositionLabel; 23 | @property (readwrite, strong) UILabel *timeLeftLabel; 24 | @property (readwrite, strong) UIProgressView *progressView; 25 | @property (readwrite, strong) UIActivityIndicatorView *activityIndicator; 26 | @property (readwrite, strong) UIButton *shareButton; 27 | @end 28 | 29 | @implementation VideoPlayerView 30 | 31 | - (id)initWithFrame:(CGRect)frame 32 | { 33 | if (self = [super initWithFrame:frame]) { 34 | _airplayIsActiveView = [[AirplayActiveView alloc] initWithFrame:CGRectZero]; 35 | [_airplayIsActiveView setHidden:YES]; 36 | [self addSubview:_airplayIsActiveView]; 37 | 38 | _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 39 | [_titleLabel setFont:[UIFont fontWithName:@"Forza-Medium" size:16.0f]]; 40 | [_titleLabel setTextColor:[UIColor whiteColor]]; 41 | [_titleLabel setBackgroundColor:[UIColor clearColor]]; 42 | [_titleLabel setNumberOfLines:2]; 43 | [_titleLabel setLineBreakMode:NSLineBreakByWordWrapping]; 44 | [self addSubview:_titleLabel]; 45 | 46 | _playerControlBar = [[UIView alloc] init]; 47 | [_playerControlBar setOpaque:NO]; 48 | [_playerControlBar setBackgroundColor:[[UIColor blackColor] colorWithAlphaComponent:0.8]]; 49 | 50 | _playPauseButton = [[UIButton alloc] init]; 51 | [_playPauseButton setImage:[UIImage imageNamed:@"play-button"] forState:UIControlStateNormal]; 52 | [_playPauseButton setShowsTouchWhenHighlighted:YES]; 53 | [_playerControlBar addSubview:_playPauseButton]; 54 | 55 | _fullScreenButton = [[UIButton alloc] init]; 56 | [_fullScreenButton setImage:[UIImage imageNamed:@"fullscreen-button"] forState:UIControlStateNormal]; 57 | [_fullScreenButton setShowsTouchWhenHighlighted:YES]; 58 | [_playerControlBar addSubview:_fullScreenButton]; 59 | 60 | _progressView = [[UIProgressView alloc] init]; 61 | _progressView.progressTintColor = [UIColor colorWithRed:31.0/255.0 green:31.0/255.0 blue:31.0/255.0 alpha:1.0]; 62 | _progressView.trackTintColor = [UIColor darkGrayColor]; 63 | [_playerControlBar addSubview:_progressView]; 64 | 65 | _videoScrubber = [[UISlider alloc] init]; 66 | [_videoScrubber setMinimumTrackTintColor:[UIColor redColor]]; 67 | [_videoScrubber setMaximumTrackImage:[UIImage imageNamed:@"transparentBar"] forState:UIControlStateNormal]; 68 | [_videoScrubber setThumbTintColor:[UIColor whiteColor]]; 69 | [_playerControlBar addSubview:_videoScrubber]; 70 | 71 | _volumeView = [[MPVolumeView alloc] init]; 72 | [_volumeView setShowsRouteButton:YES]; 73 | [_volumeView setShowsVolumeSlider:NO]; 74 | [_playerControlBar addSubview:_volumeView]; 75 | 76 | // Listen to alpha changes to know when other routes are available 77 | for (UIButton *button in [_volumeView subviews]) { 78 | if (![button isKindOfClass:[UIButton class]]) { 79 | continue; 80 | } 81 | 82 | [button addObserver:self forKeyPath:@"alpha" options:NSKeyValueObservingOptionNew context:nil]; 83 | [self setAirplayButton:button]; 84 | } 85 | 86 | _currentPositionLabel = [[UILabel alloc] init]; 87 | [_currentPositionLabel setBackgroundColor:[UIColor clearColor]]; 88 | [_currentPositionLabel setTextColor:[UIColor whiteColor]]; 89 | [_currentPositionLabel setFont:[UIFont systemFontOfSize:14.0f]]; 90 | [_currentPositionLabel setTextAlignment:NSTextAlignmentCenter]; 91 | [_playerControlBar addSubview:_currentPositionLabel]; 92 | 93 | _timeLeftLabel = [[UILabel alloc] init]; 94 | [_timeLeftLabel setBackgroundColor:[UIColor clearColor]]; 95 | [_timeLeftLabel setTextColor:[UIColor whiteColor]]; 96 | [_timeLeftLabel setFont:[UIFont systemFontOfSize:14.0f]]; 97 | [_timeLeftLabel setTextAlignment:NSTextAlignmentCenter]; 98 | [_playerControlBar addSubview:_timeLeftLabel]; 99 | 100 | _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; 101 | [self addSubview:_activityIndicator]; 102 | 103 | _shareButton = [[UIButton alloc] init]; 104 | [_shareButton setImage:[UIImage imageNamed:@"share-button"] forState:UIControlStateNormal]; 105 | [_shareButton setShowsTouchWhenHighlighted:YES]; 106 | 107 | // Hide the Share Button by default after removing ShareThis 108 | _shareButton.hidden = YES; 109 | 110 | [self addSubview:_shareButton]; 111 | self.controlsEdgeInsets = UIEdgeInsetsZero; 112 | } 113 | return self; 114 | } 115 | 116 | - (void)dealloc 117 | { 118 | [_airplayButton removeObserver:self forKeyPath:@"alpha"]; 119 | } 120 | 121 | 122 | - (void)layoutSubviews 123 | { 124 | [super layoutSubviews]; 125 | 126 | CGRect bounds = [self bounds]; 127 | 128 | CGRect insetBounds = CGRectInset(UIEdgeInsetsInsetRect(bounds, self.controlsEdgeInsets), _padding, _padding); 129 | CGSize titleLabelSize = [[_titleLabel text] sizeWithFont:[_titleLabel font] 130 | constrainedToSize:CGSizeMake(insetBounds.size.width, CGFLOAT_MAX) 131 | lineBreakMode:NSLineBreakByCharWrapping]; 132 | 133 | UIImage *shareImage = [UIImage imageNamed:@"share-button"]; 134 | 135 | if (!_fullscreen) { 136 | CGSize twoLineSize = [@"M\nM" sizeWithFont:[_titleLabel font] 137 | constrainedToSize:CGSizeMake(insetBounds.size.width, CGFLOAT_MAX) 138 | lineBreakMode:UILineBreakModeWordWrap]; 139 | 140 | self.autoresizingMask = UIViewAutoresizingNone; 141 | 142 | [_titleLabel setFrame:CGRectMake(insetBounds.origin.x + LABEL_PADDING, 143 | insetBounds.origin.y, 144 | insetBounds.size.width, 145 | titleLabelSize.height)]; 146 | 147 | CGRect playerFrame = CGRectMake(0, 148 | 0, 149 | bounds.size.width, 150 | bounds.size.height - twoLineSize.height - _padding - _padding); 151 | [_airplayIsActiveView setFrame:playerFrame]; 152 | 153 | [_shareButton setFrame:CGRectMake(insetBounds.size.width - shareImage.size.width, insetBounds.origin.y, shareImage.size.width, shareImage.size.height)]; 154 | } else { 155 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 156 | 157 | [_titleLabel setFrame:CGRectMake(insetBounds.origin.x + LABEL_PADDING, 158 | insetBounds.origin.y, 159 | insetBounds.size.width, 160 | titleLabelSize.height)]; 161 | 162 | 163 | [_airplayIsActiveView setFrame:bounds]; 164 | 165 | [_shareButton setFrame:CGRectMake(insetBounds.size.width - shareImage.size.width, insetBounds.origin.y, shareImage.size.width, shareImage.size.height)]; 166 | } 167 | 168 | [_playerControlBar setFrame:CGRectMake(bounds.origin.x, 169 | bounds.size.height - PLAYER_CONTROL_BAR_HEIGHT, 170 | bounds.size.width, 171 | PLAYER_CONTROL_BAR_HEIGHT)]; 172 | 173 | [_activityIndicator setFrame:CGRectMake((bounds.size.width - _activityIndicator.frame.size.width)/2.0, 174 | (bounds.size.height - _activityIndicator.frame.size.width)/2.0, 175 | _activityIndicator.frame.size.width, 176 | _activityIndicator.frame.size.height)]; 177 | 178 | [_playPauseButton setFrame:CGRectMake(0, 179 | 0, 180 | PLAYER_CONTROL_BAR_HEIGHT, 181 | PLAYER_CONTROL_BAR_HEIGHT)]; 182 | 183 | CGRect fullScreenButtonFrame = CGRectMake(bounds.size.width - PLAYER_CONTROL_BAR_HEIGHT, 184 | 0, 185 | PLAYER_CONTROL_BAR_HEIGHT, 186 | PLAYER_CONTROL_BAR_HEIGHT); 187 | [_fullScreenButton setFrame:fullScreenButtonFrame]; 188 | 189 | CGRect routeButtonRect = CGRectZero; 190 | if ([_airplayButton alpha] > 0) { 191 | if ([_volumeView respondsToSelector:@selector(routeButtonRectForBounds:)]) { 192 | routeButtonRect = [_volumeView routeButtonRectForBounds:bounds]; 193 | } else { 194 | routeButtonRect = CGRectMake(0, 0, 24, 18); 195 | } 196 | [_volumeView setFrame:CGRectMake(CGRectGetMinX(fullScreenButtonFrame) - routeButtonRect.size.width 197 | - ROUTE_BUTTON_ALIGNMENT_FUZZ, 198 | PLAYER_CONTROL_BAR_HEIGHT / 2 - routeButtonRect.size.height / 2, 199 | routeButtonRect.size.width, 200 | routeButtonRect.size.height)]; 201 | } 202 | 203 | [_currentPositionLabel setFrame:CGRectMake(PLAYER_CONTROL_BAR_HEIGHT, 204 | ALIGNMENT_FUZZ, 205 | CURRENT_POSITION_WIDTH, 206 | PLAYER_CONTROL_BAR_HEIGHT)]; 207 | [_timeLeftLabel setFrame:CGRectMake(bounds.size.width - PLAYER_CONTROL_BAR_HEIGHT - TIME_LEFT_WIDTH 208 | - routeButtonRect.size.width, 209 | ALIGNMENT_FUZZ, 210 | TIME_LEFT_WIDTH, 211 | PLAYER_CONTROL_BAR_HEIGHT)]; 212 | 213 | CGRect scrubberRect = CGRectMake(PLAYER_CONTROL_BAR_HEIGHT + CURRENT_POSITION_WIDTH, 214 | 0, 215 | bounds.size.width - (PLAYER_CONTROL_BAR_HEIGHT * 2) - TIME_LEFT_WIDTH - 216 | CURRENT_POSITION_WIDTH - (TIME_LEFT_WIDTH - CURRENT_POSITION_WIDTH) 217 | - routeButtonRect.size.width, 218 | PLAYER_CONTROL_BAR_HEIGHT); 219 | [_videoScrubber setFrame:scrubberRect]; 220 | 221 | CGRect progressViewFrameWithOffset = [_videoScrubber trackRectForBounds:scrubberRect]; 222 | progressViewFrameWithOffset.origin.y += 3; 223 | [_progressView setFrame:progressViewFrameWithOffset]; 224 | } 225 | 226 | - (void)setTitle:(NSString *)title 227 | { 228 | [_titleLabel setText:title]; 229 | [self setNeedsLayout]; 230 | } 231 | 232 | - (void)setFullscreen:(BOOL)fullscreen 233 | { 234 | if (_fullscreen == fullscreen) { 235 | return; 236 | } 237 | 238 | _fullscreen = fullscreen; 239 | 240 | [self setNeedsLayout]; 241 | } 242 | 243 | - (CGFloat)heightForWidth:(CGFloat)width 244 | { 245 | CGSize titleLabelSize = [@"M\nM" sizeWithFont:[_titleLabel font] 246 | constrainedToSize:CGSizeMake(width - _padding - _padding, CGFLOAT_MAX)]; 247 | return (width / 16 * 9) + titleLabelSize.height; 248 | } 249 | 250 | - (AVPlayer *)player 251 | { 252 | return [(AVPlayerLayer *)[self layer] player]; 253 | } 254 | 255 | + (Class)layerClass 256 | { 257 | return [AVPlayerLayer class]; 258 | } 259 | 260 | - (void)setPlayer:(AVPlayer *)player 261 | { 262 | [(AVPlayerLayer *)self.layer setPlayer:player]; 263 | [_airplayIsActiveView setHidden:YES]; 264 | [self addSubview:self.playerControlBar]; 265 | } 266 | 267 | - (void)observeValueForKeyPath:(NSString *)keyPath 268 | ofObject:(id)object 269 | change:(NSDictionary *)change 270 | context:(void *)context 271 | { 272 | if (object == _airplayButton && [keyPath isEqualToString:@"alpha"]) { 273 | [self setNeedsLayout]; 274 | } 275 | } 276 | 277 | @end 278 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 IGN (http://www.ign.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VideoPlayerKit 2 | ============== 3 | 4 | We know how hard making a custom video player is. That's why we created VideoPlayerKit. Using VideoPlayerKit is easy to use. All controls, progress bar, and airplay are already set up to make everyone's life easier. 5 | 6 | References to IGN's ShareThis repo and share button to handle sharing are currently commented out. More information can be found [here](https://github.com/ign/ShareThis). 7 | 8 | Use the sample project to see how VideoPlayerKit works. 9 | 10 | An example from IGN Pro League's deprecated "IPL" app (with ShareThis) are shown below: 11 | 12 | [![](http://i.imgur.com/Ayxdp5V.png)](http://i.imgur.com/Ayxdp5V.png) 13 | [![](http://i.imgur.com/KNIxaWr.png)](http://i.imgur.com/KNIxaWr.png) 14 | 15 | [![](http://i.imgur.com/disgBRz.png)](http://i.imgur.com/disgBRz.png) 16 | [![](http://i.imgur.com/v4WswEi.png)](http://i.imgur.com/v4WswEi.png) 17 | 18 | # Using the sample project 19 | Download the github project and use the Makefile to gather all the pods require for VideoPlayerKit. Use the following command: 20 | ``` objective-c 21 | make clean setup 22 | ``` 23 | 24 | Note: If you get a build error due to architectural dependencies, update the following value in your 25 | Pods project's Build Settings for both Debug and Release modes: 26 | ``` objective-c 27 | Set Build Active Architectures Only —> “No” 28 | ``` 29 | 30 | # Installation Instructions: 31 | 32 | ## In the View Controller that will contain the video player: 33 | 34 | ### Import VideoPlayerKit.h 35 | ``` objective-c 36 | import "VideoPlayerKit.h" 37 | ``` 38 | 39 | ### Initializing video player 40 | ``` objective-c 41 | [VideoPlayerKit initWithContainingViewController:optionalTopView:hideTopViewWithControls:]; 42 | [VideoPlayerKit initWithContainingView:optionalTopView:hideTopViewWithControls:]; 43 | ``` 44 | Make sure that the view controller, or a specific inline view for inline players, containing the video player is passed into the first parameter. The optional top view is a view that will be at the top of the video player. This can be use to put any additional buttons or labels. The third parameter is a boolean that will be used to check if the top view should hide with the video player controls. If this is set to NO, it is still possible to hide the top view only on certain times given the situation using the two notifications: kVideoPlayerWillHideControlsNotification and kVideoPlayerWillShowControlsNotification. 45 | 46 | ### Top View Edge Inset 47 | ``` objective-c 48 | setControlsEdgeInsets 49 | ``` 50 | If a top view is set, use this to offset the controls so it accounts for the top view. Usually you'll only want to change the edge inset's top parameter but the option is left open to change it entirely. 51 | 52 | ### Playing the video 53 | ``` objective-c 54 | playVideoWithTitle:URL:videoID:isStreaming:playInFullScreen: 55 | ``` 56 | The method will automatically initialize the video player and start playing the video that is given in the url. The title parameter is used for the label that will appear near the top of the video player under the optional Top View. The videoID and isStreaming parameters are used mainly for analytics tracking. The last parameter, playInFullScreen, is a boolean that when set to YES, the video will play automatically in fullscreen without an inline player. 57 | 58 | ## VideoPlayerDelegate 59 | 60 | ### Event tracking 61 | ``` objective-c 62 | trackEvent:videoID:title: 63 | ``` 64 | This method is used for analytic event tracking. The first parameter will be one of three events: kTrackEventVideoStart, kTrackEventVideoLiveStart, kTrackEventVideoComplete. The videoID and title will be the same ones that was passed into the method playVideoWithTitle:URL:videoID:isStreaming:playInFullScreen: 65 | 66 | ### Fullscreen toggle switch 67 | ``` objective-c 68 | BOOL fullScreenToggled 69 | ``` 70 | This property will be set to YES whenever the video player is fullscreen modally. 71 | 72 | ## Optional Methods 73 | 74 | ### Launch video player in fullscreen 75 | ``` objective-c 76 | launchFullScreen 77 | ``` 78 | 79 | ### Minimize video player from fullscreen 80 | ``` objective-c 81 | minimizeVideo 82 | ``` 83 | 84 | ### Play and pause video 85 | ``` objective-c 86 | playPauseHandler 87 | ``` 88 | Plays the video if paused. Pauses the video if playing. 89 | 90 | ## Optional properties 91 | 92 | ### Check if fullscreen mode is toggled 93 | ``` objective-c 94 | fullScreenModeToggled 95 | ``` 96 | 97 | ### Set the end time to static or dynamic 98 | ``` objective-c 99 | showStaticEndTime 100 | ``` 101 | Set this boolean to YES if the end time should just be a static label. Default setting is NO which will make the end time decrease as the video plays. 102 | 103 | ### Get current video info 104 | ``` objective-c 105 | currentVideoInfo 106 | ``` 107 | A dictionary which uses these keys: @"title" for title of video, @"videoID" for id of video, @"isStreaming" to check if it is a live video. 108 | 109 | ### Check if video is playing 110 | ``` objective-c 111 | isPlaying 112 | ``` 113 | 114 | ### Allow fullscreen view to be in portrait 115 | ``` objective-c 116 | allowPortraitFullscreen 117 | ``` 118 | Default fullscreen is landscape. For portrait fullscreen, mark this property to YES. 119 | 120 | # License 121 | ShareThis is available under the MIT license. See the LICENSE file for more info. 122 | -------------------------------------------------------------------------------- /VideoPlayerKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'VideoPlayerKit' 3 | s.version = '1.0.1' 4 | s.platform = :ios, '9.0' 5 | s.license = { :type => 'MIT'} 6 | s.summary = 'A custom iOS video player with controls, progress bar, and airplay all set up.' 7 | s.homepage = 'https://github.com/ign/VideoPlayerKit' 8 | s.author = { 'Aung Thar' => 'samuraizack@gmail.com', 9 | 'Grant Bartone' => 'gbartone@gmail.com', 10 | 'Alex Ivlev' => 'aivlev@ign.com', 11 | 'Brad Taylor' => 'brtaylor@ign.com' } 12 | 13 | s.source = { :git => 'https://github.com/ign/VideoPlayerKit.git', :tag => '1.0.1' } 14 | 15 | s.description = 'We know how hard making a custom video player is. That is why ' \ 16 | 'we created VideoPlayerKit. Using VideoPlayerKit is easy. All ' \ 17 | 'controls, progress bar, and airplay are already set up to make ' \ 18 | 'everyones life easier.' 19 | s.source_files = 'Code/*.{h,m}' 20 | s.resources = 'Assets/*.png' 21 | s.frameworks = 'AudioToolbox', 'CoreMedia', 'AVFoundation', 'MediaPlayer' 22 | s.requires_arc = true 23 | 24 | # s.dependency 'ShareThis' 25 | end 26 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem "cocoapods", "1.3.1" 4 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (2.3.5) 5 | activesupport (4.2.10) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | claide (1.0.2) 11 | cocoapods (1.3.1) 12 | activesupport (>= 4.0.2, < 5) 13 | claide (>= 1.0.2, < 2.0) 14 | cocoapods-core (= 1.3.1) 15 | cocoapods-deintegrate (>= 1.0.1, < 2.0) 16 | cocoapods-downloader (>= 1.1.3, < 2.0) 17 | cocoapods-plugins (>= 1.0.0, < 2.0) 18 | cocoapods-search (>= 1.0.0, < 2.0) 19 | cocoapods-stats (>= 1.0.0, < 2.0) 20 | cocoapods-trunk (>= 1.2.0, < 2.0) 21 | cocoapods-try (>= 1.1.0, < 2.0) 22 | colored2 (~> 3.1) 23 | escape (~> 0.0.4) 24 | fourflusher (~> 2.0.1) 25 | gh_inspector (~> 1.0) 26 | molinillo (~> 0.5.7) 27 | nap (~> 1.0) 28 | ruby-macho (~> 1.1) 29 | xcodeproj (>= 1.5.1, < 2.0) 30 | cocoapods-core (1.3.1) 31 | activesupport (>= 4.0.2, < 6) 32 | fuzzy_match (~> 2.0.4) 33 | nap (~> 1.0) 34 | cocoapods-deintegrate (1.0.1) 35 | cocoapods-downloader (1.1.3) 36 | cocoapods-plugins (1.0.0) 37 | nap 38 | cocoapods-search (1.0.0) 39 | cocoapods-stats (1.0.0) 40 | cocoapods-trunk (1.3.0) 41 | nap (>= 0.8, < 2.0) 42 | netrc (~> 0.11) 43 | cocoapods-try (1.1.0) 44 | colored2 (3.1.2) 45 | escape (0.0.4) 46 | fourflusher (2.0.1) 47 | fuzzy_match (2.0.4) 48 | gh_inspector (1.0.3) 49 | i18n (0.8.6) 50 | minitest (5.10.3) 51 | molinillo (0.5.7) 52 | nanaimo (0.2.3) 53 | nap (1.1.0) 54 | netrc (0.11.0) 55 | ruby-macho (1.1.0) 56 | thread_safe (0.3.6) 57 | tzinfo (1.2.3) 58 | thread_safe (~> 0.1) 59 | xcodeproj (1.5.2) 60 | CFPropertyList (~> 2.3.3) 61 | claide (>= 1.0.2, < 2.0) 62 | colored2 (~> 3.1) 63 | nanaimo (~> 0.2.3) 64 | 65 | PLATFORMS 66 | ruby 67 | 68 | DEPENDENCIES 69 | cocoapods (= 1.3.1) 70 | 71 | BUNDLED WITH 72 | 1.15.4 73 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/Makefile: -------------------------------------------------------------------------------- 1 | # CURDIR preferable to PWD in Makefile's, as it handles gnumake's --directory 2 | # recursion correctly 3 | BUILDDIR=${CURDIR}/build 4 | GEM_CACHE=${CURDIR}/cache 5 | POD_CACHE=${CURDIR}/Pods 6 | BUNDLE_CACHE=${CURDIR}/.bundle 7 | DISTRIBUTION_IDENTITY=iPhone Distribution: FOX Interactive Media 8 | 9 | PLISTBUDDY=/usr/libexec/PlistBuddy 10 | INFO_PLIST=VideoPlayerSample/VideoPlayerSample-Info.plist 11 | VERSION_SHORT=$(shell git describe --abbrev=0) 12 | VERSION_LONG=$(shell git describe --long) 13 | 14 | ZIP=/usr/bin/zip 15 | CURL=/usr/bin/curl 16 | 17 | LAST_TAG=$(shell git for-each-ref refs/tags --sort=-authordate --format='%(refname)' --count=2 | tail -n1) 18 | NOTES=${BUILDDIR}/notes 19 | 20 | build-debug: setup xcodebuild-debug 21 | 22 | xcodebuild-debug: setup 23 | xcodebuild -workspace VideoPlayerSample.xcworkspace -sdk iphonesimulator \ 24 | -scheme VideoPlayerSample -configuration Debug \ 25 | clean build 26 | 27 | setup: 28 | bundle install --path=${GEM_CACHE} 29 | bundle exec pod install 30 | 31 | clean: 32 | ${RM} -r ${BUILDDIR} 33 | ${RM} -r ${GEM_CACHE} 34 | ${RM} -r ${POD_CACHE} 35 | ${RM} -r ${BUNDLE_CACHE} 36 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | 3 | inhibit_all_warnings! 4 | 5 | target 'VideoPlayerSample' do 6 | 7 | pod 'VideoPlayerKit', :path => '../' 8 | # pod 'ShareThis', :git => 'https://github.com/ign/ShareThis.git' 9 | 10 | end 11 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - VideoPlayerKit (1.0.1) 3 | 4 | DEPENDENCIES: 5 | - VideoPlayerKit (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | VideoPlayerKit: 9 | :path: ../ 10 | 11 | SPEC CHECKSUMS: 12 | VideoPlayerKit: 31272af730e5eb6fab1d5f2ddb326708455e63b3 13 | 14 | PODFILE CHECKSUM: b5198a1dc2efda4abc25b77e61cd3ecd8b425c9f 15 | 16 | COCOAPODS: 1.3.1 17 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 412E927A74CDA4869F97D5DD /* libPods-VideoPlayerSample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D34B7B7252E948478FBF49A /* libPods-VideoPlayerSample.a */; }; 11 | 76A54BB1165566510041450E /* VideoPlayerSampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 76A54BB0165566510041450E /* VideoPlayerSampleView.m */; }; 12 | 76D67AE1165419CA00CAC69A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76D67AE0165419CA00CAC69A /* UIKit.framework */; }; 13 | 76D67AE3165419CA00CAC69A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76D67AE2165419CA00CAC69A /* Foundation.framework */; }; 14 | 76D67AE5165419CA00CAC69A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76D67AE4165419CA00CAC69A /* CoreGraphics.framework */; }; 15 | 76D67AEB165419CA00CAC69A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 76D67AE9165419CA00CAC69A /* InfoPlist.strings */; }; 16 | 76D67AED165419CA00CAC69A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 76D67AEC165419CA00CAC69A /* main.m */; }; 17 | 76D67AF1165419CA00CAC69A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76D67AF0165419CA00CAC69A /* AppDelegate.m */; }; 18 | 76D67AF3165419CA00CAC69A /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 76D67AF2165419CA00CAC69A /* Default.png */; }; 19 | 76D67AF5165419CA00CAC69A /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 76D67AF4165419CA00CAC69A /* Default@2x.png */; }; 20 | 76D67AF7165419CA00CAC69A /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 76D67AF6165419CA00CAC69A /* Default-568h@2x.png */; }; 21 | 76D67B0216541A0200CAC69A /* VideoPlayerSampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 76D67B0116541A0200CAC69A /* VideoPlayerSampleViewController.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 0DC5F9946DCB23241D84AE16 /* Pods-VideoPlayerSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VideoPlayerSample.release.xcconfig"; path = "Pods/Target Support Files/Pods-VideoPlayerSample/Pods-VideoPlayerSample.release.xcconfig"; sourceTree = ""; }; 26 | 1D34B7B7252E948478FBF49A /* libPods-VideoPlayerSample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-VideoPlayerSample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 76A54BAF165566510041450E /* VideoPlayerSampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoPlayerSampleView.h; sourceTree = ""; }; 28 | 76A54BB0165566510041450E /* VideoPlayerSampleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoPlayerSampleView.m; sourceTree = ""; }; 29 | 76D67ADC165419CA00CAC69A /* VideoPlayerSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VideoPlayerSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 76D67AE0165419CA00CAC69A /* UIKit.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 31 | 76D67AE2165419CA00CAC69A /* Foundation.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 32 | 76D67AE4165419CA00CAC69A /* CoreGraphics.framework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 33 | 76D67AE8165419CA00CAC69A /* VideoPlayerSample-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "VideoPlayerSample-Info.plist"; sourceTree = ""; }; 34 | 76D67AEA165419CA00CAC69A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 35 | 76D67AEC165419CA00CAC69A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 36 | 76D67AEE165419CA00CAC69A /* VideoPlayerSample-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VideoPlayerSample-Prefix.pch"; sourceTree = ""; }; 37 | 76D67AEF165419CA00CAC69A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 38 | 76D67AF0165419CA00CAC69A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 39 | 76D67AF2165419CA00CAC69A /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 40 | 76D67AF4165419CA00CAC69A /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 41 | 76D67AF6165419CA00CAC69A /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 42 | 76D67B0016541A0200CAC69A /* VideoPlayerSampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoPlayerSampleViewController.h; sourceTree = ""; }; 43 | 76D67B0116541A0200CAC69A /* VideoPlayerSampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoPlayerSampleViewController.m; sourceTree = ""; }; 44 | FE2AD2BCD4FBA1FADF7DC98A /* Pods-VideoPlayerSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VideoPlayerSample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-VideoPlayerSample/Pods-VideoPlayerSample.debug.xcconfig"; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 76D67AD9165419CA00CAC69A /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | 76D67AE1165419CA00CAC69A /* UIKit.framework in Frameworks */, 53 | 76D67AE3165419CA00CAC69A /* Foundation.framework in Frameworks */, 54 | 76D67AE5165419CA00CAC69A /* CoreGraphics.framework in Frameworks */, 55 | 412E927A74CDA4869F97D5DD /* libPods-VideoPlayerSample.a in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 76D67AD1165419CA00CAC69A = { 63 | isa = PBXGroup; 64 | children = ( 65 | 76D67AE6165419CA00CAC69A /* VideoPlayerSample */, 66 | 76D67ADF165419CA00CAC69A /* Frameworks */, 67 | 76D67ADD165419CA00CAC69A /* Products */, 68 | 9C7DEEBD95C9CF2B856A8661 /* Pods */, 69 | ); 70 | sourceTree = ""; 71 | }; 72 | 76D67ADD165419CA00CAC69A /* Products */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 76D67ADC165419CA00CAC69A /* VideoPlayerSample.app */, 76 | ); 77 | name = Products; 78 | sourceTree = ""; 79 | }; 80 | 76D67ADF165419CA00CAC69A /* Frameworks */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 76D67AE0165419CA00CAC69A /* UIKit.framework */, 84 | 76D67AE2165419CA00CAC69A /* Foundation.framework */, 85 | 76D67AE4165419CA00CAC69A /* CoreGraphics.framework */, 86 | 1D34B7B7252E948478FBF49A /* libPods-VideoPlayerSample.a */, 87 | ); 88 | name = Frameworks; 89 | sourceTree = ""; 90 | }; 91 | 76D67AE6165419CA00CAC69A /* VideoPlayerSample */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 76D67AEF165419CA00CAC69A /* AppDelegate.h */, 95 | 76D67AF0165419CA00CAC69A /* AppDelegate.m */, 96 | 76D67B0016541A0200CAC69A /* VideoPlayerSampleViewController.h */, 97 | 76D67B0116541A0200CAC69A /* VideoPlayerSampleViewController.m */, 98 | 76A54BAF165566510041450E /* VideoPlayerSampleView.h */, 99 | 76A54BB0165566510041450E /* VideoPlayerSampleView.m */, 100 | 76D67AE7165419CA00CAC69A /* Supporting Files */, 101 | ); 102 | path = VideoPlayerSample; 103 | sourceTree = ""; 104 | }; 105 | 76D67AE7165419CA00CAC69A /* Supporting Files */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 76D67AE8165419CA00CAC69A /* VideoPlayerSample-Info.plist */, 109 | 76D67AE9165419CA00CAC69A /* InfoPlist.strings */, 110 | 76D67AEC165419CA00CAC69A /* main.m */, 111 | 76D67AEE165419CA00CAC69A /* VideoPlayerSample-Prefix.pch */, 112 | 76D67AF2165419CA00CAC69A /* Default.png */, 113 | 76D67AF4165419CA00CAC69A /* Default@2x.png */, 114 | 76D67AF6165419CA00CAC69A /* Default-568h@2x.png */, 115 | ); 116 | name = "Supporting Files"; 117 | sourceTree = ""; 118 | }; 119 | 9C7DEEBD95C9CF2B856A8661 /* Pods */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | FE2AD2BCD4FBA1FADF7DC98A /* Pods-VideoPlayerSample.debug.xcconfig */, 123 | 0DC5F9946DCB23241D84AE16 /* Pods-VideoPlayerSample.release.xcconfig */, 124 | ); 125 | name = Pods; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | 76D67ADB165419CA00CAC69A /* VideoPlayerSample */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 76D67AFA165419CA00CAC69A /* Build configuration list for PBXNativeTarget "VideoPlayerSample" */; 134 | buildPhases = ( 135 | 7A657C80F4F80104488D0F5E /* [CP] Check Pods Manifest.lock */, 136 | 76D67AD8165419CA00CAC69A /* Sources */, 137 | 76D67AD9165419CA00CAC69A /* Frameworks */, 138 | 76D67ADA165419CA00CAC69A /* Resources */, 139 | C16EB640F87143E29128E229 /* [CP] Copy Pods Resources */, 140 | C64E06C8A7288696193CCDBF /* [CP] Embed Pods Frameworks */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = VideoPlayerSample; 147 | productName = VideoPlayerSample; 148 | productReference = 76D67ADC165419CA00CAC69A /* VideoPlayerSample.app */; 149 | productType = "com.apple.product-type.application"; 150 | }; 151 | /* End PBXNativeTarget section */ 152 | 153 | /* Begin PBXProject section */ 154 | 76D67AD3165419CA00CAC69A /* Project object */ = { 155 | isa = PBXProject; 156 | attributes = { 157 | LastUpgradeCheck = 0900; 158 | ORGANIZATIONNAME = IGN; 159 | }; 160 | buildConfigurationList = 76D67AD6165419CA00CAC69A /* Build configuration list for PBXProject "VideoPlayerSample" */; 161 | compatibilityVersion = "Xcode 3.2"; 162 | developmentRegion = English; 163 | hasScannedForEncodings = 0; 164 | knownRegions = ( 165 | en, 166 | ); 167 | mainGroup = 76D67AD1165419CA00CAC69A; 168 | productRefGroup = 76D67ADD165419CA00CAC69A /* Products */; 169 | projectDirPath = ""; 170 | projectRoot = ""; 171 | targets = ( 172 | 76D67ADB165419CA00CAC69A /* VideoPlayerSample */, 173 | ); 174 | }; 175 | /* End PBXProject section */ 176 | 177 | /* Begin PBXResourcesBuildPhase section */ 178 | 76D67ADA165419CA00CAC69A /* Resources */ = { 179 | isa = PBXResourcesBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | 76D67AEB165419CA00CAC69A /* InfoPlist.strings in Resources */, 183 | 76D67AF3165419CA00CAC69A /* Default.png in Resources */, 184 | 76D67AF5165419CA00CAC69A /* Default@2x.png in Resources */, 185 | 76D67AF7165419CA00CAC69A /* Default-568h@2x.png in Resources */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | /* End PBXResourcesBuildPhase section */ 190 | 191 | /* Begin PBXShellScriptBuildPhase section */ 192 | 7A657C80F4F80104488D0F5E /* [CP] Check Pods Manifest.lock */ = { 193 | isa = PBXShellScriptBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | ); 197 | inputPaths = ( 198 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 199 | "${PODS_ROOT}/Manifest.lock", 200 | ); 201 | name = "[CP] Check Pods Manifest.lock"; 202 | outputPaths = ( 203 | "$(DERIVED_FILE_DIR)/Pods-VideoPlayerSample-checkManifestLockResult.txt", 204 | ); 205 | runOnlyForDeploymentPostprocessing = 0; 206 | shellPath = /bin/sh; 207 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 208 | showEnvVarsInLog = 0; 209 | }; 210 | C16EB640F87143E29128E229 /* [CP] Copy Pods Resources */ = { 211 | isa = PBXShellScriptBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | ); 215 | inputPaths = ( 216 | "${SRCROOT}/Pods/Target Support Files/Pods-VideoPlayerSample/Pods-VideoPlayerSample-resources.sh", 217 | "${PODS_ROOT}/../../Assets/airplay-display.png", 218 | "${PODS_ROOT}/../../Assets/airplay-display@2x.png", 219 | "${PODS_ROOT}/../../Assets/fullscreen-button.png", 220 | "${PODS_ROOT}/../../Assets/fullscreen-button@2x.png", 221 | "${PODS_ROOT}/../../Assets/minimize-button.png", 222 | "${PODS_ROOT}/../../Assets/minimize-button@2x.png", 223 | "${PODS_ROOT}/../../Assets/pause-button.png", 224 | "${PODS_ROOT}/../../Assets/pause-button@2x.png", 225 | "${PODS_ROOT}/../../Assets/play-button.png", 226 | "${PODS_ROOT}/../../Assets/play-button@2x.png", 227 | "${PODS_ROOT}/../../Assets/share-button.png", 228 | "${PODS_ROOT}/../../Assets/share-button@2x.png", 229 | "${PODS_ROOT}/../../Assets/transparentBar.png", 230 | "${PODS_ROOT}/../../Assets/transparentBar@2x.png", 231 | ); 232 | name = "[CP] Copy Pods Resources"; 233 | outputPaths = ( 234 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}", 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | shellPath = /bin/sh; 238 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-VideoPlayerSample/Pods-VideoPlayerSample-resources.sh\"\n"; 239 | }; 240 | C64E06C8A7288696193CCDBF /* [CP] Embed Pods Frameworks */ = { 241 | isa = PBXShellScriptBuildPhase; 242 | buildActionMask = 2147483647; 243 | files = ( 244 | ); 245 | inputPaths = ( 246 | ); 247 | name = "[CP] Embed Pods Frameworks"; 248 | outputPaths = ( 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | shellPath = /bin/sh; 252 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-VideoPlayerSample/Pods-VideoPlayerSample-frameworks.sh\"\n"; 253 | showEnvVarsInLog = 0; 254 | }; 255 | /* End PBXShellScriptBuildPhase section */ 256 | 257 | /* Begin PBXSourcesBuildPhase section */ 258 | 76D67AD8165419CA00CAC69A /* Sources */ = { 259 | isa = PBXSourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | 76D67AED165419CA00CAC69A /* main.m in Sources */, 263 | 76D67AF1165419CA00CAC69A /* AppDelegate.m in Sources */, 264 | 76D67B0216541A0200CAC69A /* VideoPlayerSampleViewController.m in Sources */, 265 | 76A54BB1165566510041450E /* VideoPlayerSampleView.m in Sources */, 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | /* End PBXSourcesBuildPhase section */ 270 | 271 | /* Begin PBXVariantGroup section */ 272 | 76D67AE9165419CA00CAC69A /* InfoPlist.strings */ = { 273 | isa = PBXVariantGroup; 274 | children = ( 275 | 76D67AEA165419CA00CAC69A /* en */, 276 | ); 277 | name = InfoPlist.strings; 278 | sourceTree = ""; 279 | }; 280 | /* End PBXVariantGroup section */ 281 | 282 | /* Begin XCBuildConfiguration section */ 283 | 76D67AF8165419CA00CAC69A /* Debug */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ALWAYS_SEARCH_USER_PATHS = NO; 287 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 288 | CLANG_CXX_LIBRARY = "libc++"; 289 | CLANG_ENABLE_OBJC_ARC = YES; 290 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 291 | CLANG_WARN_BOOL_CONVERSION = YES; 292 | CLANG_WARN_COMMA = YES; 293 | CLANG_WARN_CONSTANT_CONVERSION = YES; 294 | CLANG_WARN_EMPTY_BODY = YES; 295 | CLANG_WARN_ENUM_CONVERSION = YES; 296 | CLANG_WARN_INFINITE_RECURSION = YES; 297 | CLANG_WARN_INT_CONVERSION = YES; 298 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 299 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 300 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 301 | CLANG_WARN_STRICT_PROTOTYPES = YES; 302 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 303 | CLANG_WARN_UNREACHABLE_CODE = YES; 304 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 305 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 306 | COPY_PHASE_STRIP = NO; 307 | ENABLE_STRICT_OBJC_MSGSEND = YES; 308 | ENABLE_TESTABILITY = YES; 309 | GCC_C_LANGUAGE_STANDARD = gnu99; 310 | GCC_DYNAMIC_NO_PIC = NO; 311 | GCC_NO_COMMON_BLOCKS = YES; 312 | GCC_OPTIMIZATION_LEVEL = 0; 313 | GCC_PREPROCESSOR_DEFINITIONS = ( 314 | "DEBUG=1", 315 | "$(inherited)", 316 | ); 317 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 320 | GCC_WARN_UNDECLARED_SELECTOR = YES; 321 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 322 | GCC_WARN_UNUSED_FUNCTION = YES; 323 | GCC_WARN_UNUSED_VARIABLE = YES; 324 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 325 | SDKROOT = iphoneos; 326 | TARGETED_DEVICE_FAMILY = "1,2"; 327 | }; 328 | name = Debug; 329 | }; 330 | 76D67AF9165419CA00CAC69A /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 335 | CLANG_CXX_LIBRARY = "libc++"; 336 | CLANG_ENABLE_OBJC_ARC = YES; 337 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 338 | CLANG_WARN_BOOL_CONVERSION = YES; 339 | CLANG_WARN_COMMA = YES; 340 | CLANG_WARN_CONSTANT_CONVERSION = YES; 341 | CLANG_WARN_EMPTY_BODY = YES; 342 | CLANG_WARN_ENUM_CONVERSION = YES; 343 | CLANG_WARN_INFINITE_RECURSION = YES; 344 | CLANG_WARN_INT_CONVERSION = YES; 345 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 346 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 347 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 348 | CLANG_WARN_STRICT_PROTOTYPES = YES; 349 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 350 | CLANG_WARN_UNREACHABLE_CODE = YES; 351 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 352 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 353 | COPY_PHASE_STRIP = YES; 354 | ENABLE_STRICT_OBJC_MSGSEND = YES; 355 | GCC_C_LANGUAGE_STANDARD = gnu99; 356 | GCC_NO_COMMON_BLOCKS = YES; 357 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 358 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 359 | GCC_WARN_UNDECLARED_SELECTOR = YES; 360 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 361 | GCC_WARN_UNUSED_FUNCTION = YES; 362 | GCC_WARN_UNUSED_VARIABLE = YES; 363 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 364 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 365 | SDKROOT = iphoneos; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | VALIDATE_PRODUCT = YES; 368 | }; 369 | name = Release; 370 | }; 371 | 76D67AFB165419CA00CAC69A /* Debug */ = { 372 | isa = XCBuildConfiguration; 373 | baseConfigurationReference = FE2AD2BCD4FBA1FADF7DC98A /* Pods-VideoPlayerSample.debug.xcconfig */; 374 | buildSettings = { 375 | CODE_SIGN_IDENTITY = "iPhone Developer"; 376 | DEVELOPMENT_TEAM = ""; 377 | FRAMEWORK_SEARCH_PATHS = ( 378 | "$(inherited)", 379 | "\"$(SRCROOT)\"", 380 | ); 381 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 382 | GCC_PREFIX_HEADER = "VideoPlayerSample/VideoPlayerSample-Prefix.pch"; 383 | HEADER_SEARCH_PATHS = ( 384 | "$(inherited)", 385 | "${PODS_HEADERS_SEARCH_PATHS}", 386 | ); 387 | INFOPLIST_FILE = "VideoPlayerSample/VideoPlayerSample-Info.plist"; 388 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 389 | PRODUCT_BUNDLE_IDENTIFIER = "com.ign.${PRODUCT_NAME:rfc1034identifier}"; 390 | PRODUCT_NAME = "$(TARGET_NAME)"; 391 | PROVISIONING_PROFILE = ""; 392 | WRAPPER_EXTENSION = app; 393 | }; 394 | name = Debug; 395 | }; 396 | 76D67AFC165419CA00CAC69A /* Release */ = { 397 | isa = XCBuildConfiguration; 398 | baseConfigurationReference = 0DC5F9946DCB23241D84AE16 /* Pods-VideoPlayerSample.release.xcconfig */; 399 | buildSettings = { 400 | CODE_SIGN_IDENTITY = "iPhone Developer"; 401 | DEVELOPMENT_TEAM = ""; 402 | FRAMEWORK_SEARCH_PATHS = ( 403 | "$(inherited)", 404 | "\"$(SRCROOT)\"", 405 | ); 406 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 407 | GCC_PREFIX_HEADER = "VideoPlayerSample/VideoPlayerSample-Prefix.pch"; 408 | HEADER_SEARCH_PATHS = ( 409 | "$(inherited)", 410 | "${PODS_HEADERS_SEARCH_PATHS}", 411 | ); 412 | INFOPLIST_FILE = "VideoPlayerSample/VideoPlayerSample-Info.plist"; 413 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 414 | PRODUCT_BUNDLE_IDENTIFIER = "com.ign.${PRODUCT_NAME:rfc1034identifier}"; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | PROVISIONING_PROFILE = ""; 417 | WRAPPER_EXTENSION = app; 418 | }; 419 | name = Release; 420 | }; 421 | /* End XCBuildConfiguration section */ 422 | 423 | /* Begin XCConfigurationList section */ 424 | 76D67AD6165419CA00CAC69A /* Build configuration list for PBXProject "VideoPlayerSample" */ = { 425 | isa = XCConfigurationList; 426 | buildConfigurations = ( 427 | 76D67AF8165419CA00CAC69A /* Debug */, 428 | 76D67AF9165419CA00CAC69A /* Release */, 429 | ); 430 | defaultConfigurationIsVisible = 0; 431 | defaultConfigurationName = Release; 432 | }; 433 | 76D67AFA165419CA00CAC69A /* Build configuration list for PBXNativeTarget "VideoPlayerSample" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | 76D67AFB165419CA00CAC69A /* Debug */, 437 | 76D67AFC165419CA00CAC69A /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | /* End XCConfigurationList section */ 443 | }; 444 | rootObject = 76D67AD3165419CA00CAC69A /* Project object */; 445 | } 446 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // VideoPlayerSample 4 | // 5 | // Created by Zack Thar on 11/14/12. 6 | // Copyright (c) 2012 IGN. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // VideoPlayerSample 4 | // 5 | // Created by Zack Thar on 11/14/12. 6 | // Copyright (c) 2012 IGN. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "VideoPlayerSampleViewController.h" 11 | 12 | @implementation AppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | [self.window makeKeyAndVisible]; 18 | 19 | VideoPlayerSampleViewController *videoPlayerSample = [[VideoPlayerSampleViewController alloc] init]; 20 | [self.window setRootViewController:videoPlayerSample]; 21 | 22 | return YES; 23 | } 24 | 25 | - (void)applicationWillResignActive:(UIApplication *)application 26 | { 27 | // 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. 28 | // 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. 29 | } 30 | 31 | - (void)applicationDidEnterBackground:(UIApplication *)application 32 | { 33 | // 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. 34 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 35 | } 36 | 37 | - (void)applicationWillEnterForeground:(UIApplication *)application 38 | { 39 | // 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. 40 | } 41 | 42 | - (void)applicationDidBecomeActive:(UIApplication *)application 43 | { 44 | // 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. 45 | } 46 | 47 | - (void)applicationWillTerminate:(UIApplication *)application 48 | { 49 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/VideoPlayerKitSampleProject/VideoPlayerSample/Default-568h@2x.png -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/VideoPlayerKitSampleProject/VideoPlayerSample/Default.png -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blizzard-op/VideoPlayerKit/fe4a38b8cfe622629c5e62312a4e7c26ebf19f25/VideoPlayerKitSampleProject/VideoPlayerSample/Default@2x.png -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/VideoPlayerSample-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/VideoPlayerSample-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'VideoPlayerSample' target in the 'VideoPlayerSample' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iOS SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/VideoPlayerSampleView.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import 4 | 5 | @interface VideoPlayerSampleView : UIView 6 | 7 | @property (nonatomic, readonly, strong) UIView *videoPlayerView; 8 | @property (nonatomic, readonly, strong) UIButton *playFullScreenButton; 9 | @property (nonatomic, readonly, strong) UIButton *playInlineButton; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/VideoPlayerSampleView.m: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import "VideoPlayerSampleView.h" 4 | 5 | @interface VideoPlayerSampleView() 6 | 7 | @property (nonatomic, readwrite, strong) UIView *videoPlayerView; 8 | @property (nonatomic, readwrite, strong) UIButton *playFullScreenButton; 9 | @property (nonatomic, readwrite, strong) UIButton *playInlineButton; 10 | 11 | @end 12 | 13 | @implementation VideoPlayerSampleView 14 | 15 | - (id)init 16 | { 17 | if ((self = [super init])) { 18 | self.playInlineButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 19 | [self.playInlineButton setTitle:@"Play Inline" forState:UIControlStateNormal]; 20 | [self addSubview:self.playInlineButton]; 21 | 22 | self.playFullScreenButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 23 | [self.playFullScreenButton setTitle:@"Play Fullscreen" forState:UIControlStateNormal]; 24 | [self addSubview:self.playFullScreenButton]; 25 | 26 | self.videoPlayerView = [[UIView alloc] init]; 27 | self.videoPlayerView.autoresizesSubviews = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth; 28 | [self addSubview:self.videoPlayerView]; 29 | 30 | self.backgroundColor = [UIColor whiteColor]; 31 | } 32 | 33 | return self; 34 | } 35 | 36 | - (void)layoutSubviews 37 | { 38 | [super layoutSubviews]; 39 | CGRect bounds = self.bounds; 40 | self.playInlineButton.frame = CGRectMake((bounds.size.width - 150)/2.0, 41 | (bounds.size.height - 50)/2.0, 42 | 150, 43 | 50); 44 | 45 | self.playFullScreenButton.frame = CGRectMake((bounds.size.width - 150)/2.0, 46 | (bounds.size.height + 50)/2.0, 47 | 150, 48 | 50); 49 | 50 | CGFloat videoHeight = bounds.size.width * 9 / 16; 51 | self.videoPlayerView.frame = CGRectMake(0, [[UIApplication sharedApplication] statusBarFrame].size.height, bounds.size.width, videoHeight); 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/VideoPlayerSampleViewController.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import 4 | #import "VideoPlayerKit.h" 5 | 6 | @interface VideoPlayerSampleViewController : UIViewController 7 | 8 | @property (nonatomic) BOOL fullScreenToggled; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/VideoPlayerSampleViewController.m: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012 IGN Entertainment, Inc. */ 2 | 3 | #import "VideoPlayerSampleViewController.h" 4 | #import "VideoPlayerSampleView.h" 5 | 6 | #define LABEL_PADDING 10 7 | #define TOPVIEW_HEIGHT 40 8 | 9 | @interface VideoPlayerSampleViewController () 10 | 11 | @property (nonatomic, strong) VideoPlayerKit *videoPlayerViewController; 12 | @property (nonatomic, strong) UIView *topView; 13 | @property (nonatomic, strong) VideoPlayerSampleView *videoPlayerSampleView; 14 | @property (nonatomic) BOOL fullScreenOnOrientationChange; 15 | @property (nonatomic) BOOL isFullScreenPortraitOrientation; 16 | @end 17 | 18 | @implementation VideoPlayerSampleViewController 19 | 20 | - (id)init 21 | { 22 | if ((self = [super init])) { 23 | // Optional auto-fullscreen on orientation change 24 | self.fullScreenOnOrientationChange = YES; 25 | 26 | // Optional Top View 27 | _topView = [[UIView alloc] init]; 28 | UILabel *topViewLabel = [[UILabel alloc] initWithFrame:CGRectMake(LABEL_PADDING, 5, 200, 40.0)]; 29 | topViewLabel.text = @"Top View Label"; 30 | topViewLabel.textColor = [UIColor whiteColor]; 31 | [_topView addSubview:topViewLabel]; 32 | } 33 | return self; 34 | } 35 | 36 | - (void) handleOrientationChanged:(NSNotification *)note 37 | { 38 | UIDevice * device = note.object; 39 | switch(device.orientation) 40 | { 41 | case UIDeviceOrientationLandscapeLeft: 42 | case UIDeviceOrientationLandscapeRight: 43 | if (!self.videoPlayerViewController.fullScreenModeToggled) { 44 | [self.videoPlayerViewController launchFullScreen]; 45 | } else if (self.videoPlayerViewController.allowPortraitFullscreen) { 46 | // Preserve portrait fullscreen mode 47 | self.isFullScreenPortraitOrientation = YES; 48 | } 49 | break; 50 | case UIDeviceOrientationPortrait: 51 | if (self.videoPlayerViewController.fullScreenModeToggled) { 52 | if (self.videoPlayerViewController.allowPortraitFullscreen && 53 | self.isFullScreenPortraitOrientation) { 54 | // Reset the portrait mode flag 55 | self.isFullScreenPortraitOrientation = NO; 56 | } else { 57 | [self.videoPlayerViewController minimizeVideo]; 58 | } 59 | } 60 | break; 61 | default: 62 | break; 63 | } 64 | } 65 | 66 | // Fullscreen / minimize without need for user's input 67 | - (void)fullScreen 68 | { 69 | if (!self.videoPlayerViewController.fullScreenModeToggled) { 70 | [self.videoPlayerViewController launchFullScreen]; 71 | } else { 72 | [self.videoPlayerViewController minimizeVideo]; 73 | } 74 | } 75 | 76 | - (void)loadView 77 | { 78 | self.videoPlayerSampleView = [[VideoPlayerSampleView alloc] init]; 79 | [self.videoPlayerSampleView.playFullScreenButton addTarget:self action:@selector(playVideoFullScreen) forControlEvents:UIControlEventTouchUpInside]; 80 | [self.videoPlayerSampleView.playInlineButton addTarget:self action:@selector(playVideoInline) forControlEvents:UIControlEventTouchUpInside]; 81 | [self setView:self.videoPlayerSampleView]; 82 | } 83 | 84 | - (void)playVideoFullScreen 85 | { 86 | // Hide Play Inline button on FullScreen to avoid layout conflicts 87 | [self.videoPlayerSampleView.playInlineButton setHidden:YES]; 88 | 89 | [self playVideo:YES]; 90 | } 91 | 92 | - (void)playVideoInline 93 | { 94 | [self playVideo:NO]; 95 | } 96 | 97 | - (void)playVideo:(BOOL)playInFullScreen 98 | { 99 | NSURL *url = [NSURL URLWithString:@"https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8"]; 100 | 101 | if (!self.videoPlayerViewController) { 102 | self.videoPlayerViewController = [VideoPlayerKit videoPlayerWithContainingView:self.videoPlayerSampleView.videoPlayerView optionalTopView:_topView hideTopViewWithControls:YES]; 103 | // Need to set edge inset if top view is inserted 104 | [self.videoPlayerViewController setControlsEdgeInsets:UIEdgeInsetsMake(self.topView.frame.size.height, 0, 0, 0)]; 105 | self.videoPlayerViewController.delegate = self; 106 | self.videoPlayerViewController.allowPortraitFullscreen = YES; 107 | } else { 108 | [self.videoPlayerViewController.view removeFromSuperview]; 109 | } 110 | 111 | [self.view addSubview:self.videoPlayerViewController.view]; 112 | 113 | [self.videoPlayerViewController playVideoWithTitle:@"Video Title" URL:url videoID:nil shareURL:nil isStreaming:NO playInFullScreen:playInFullScreen]; 114 | } 115 | 116 | - (void)viewDidLoad 117 | { 118 | [super viewDidLoad]; 119 | 120 | self.topView.frame = CGRectMake(0, [[UIApplication sharedApplication] statusBarFrame].size.height, self.view.bounds.size.width, TOPVIEW_HEIGHT); 121 | 122 | if (self.fullScreenOnOrientationChange) { 123 | [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 124 | [[NSNotificationCenter defaultCenter] 125 | addObserver:self 126 | selector:@selector(handleOrientationChanged:) 127 | name:UIDeviceOrientationDidChangeNotification 128 | object:[UIDevice currentDevice]]; 129 | } 130 | } 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /VideoPlayerKitSampleProject/VideoPlayerSample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // VideoPlayerSample 4 | // 5 | // Created by Zack Thar on 11/14/12. 6 | // Copyright (c) 2012 IGN. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | --------------------------------------------------------------------------------