VideoPlayerKit.m 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  1. /* Copyright (C) 2012 IGN Entertainment, Inc. */
  2. #import "VideoPlayerKit.h"
  3. #import "FullScreenViewController.h"
  4. // #import "ShareThis.h"
  5. NSString * const kVideoPlayerVideoChangedNotification = @"VideoPlayerVideoChangedNotification";
  6. NSString * const kVideoPlayerWillHideControlsNotification = @"VideoPlayerWillHideControlsNotitication";
  7. NSString * const kVideoPlayerWillShowControlsNotification = @"VideoPlayerWillShowControlsNotification";
  8. NSString * const kTrackEventVideoStart = @"Video Start";
  9. NSString * const kTrackEventVideoLiveStart = @"Video Live Start";
  10. NSString * const kTrackEventVideoComplete = @"Video Complete";
  11. // Match the controls animation duration with status bar duration
  12. static const NSTimeInterval controlsAnimationDuration = 0.4;
  13. @interface VideoPlayerKit () <UIGestureRecognizerDelegate>
  14. @property (readwrite, strong) NSDictionary *currentVideoInfo;
  15. @property (readwrite, strong) VideoPlayerView *videoPlayerView;
  16. @property (readwrite) BOOL restoreVideoPlayStateAfterScrubbing;
  17. @property (readwrite, strong) id scrubberTimeObserver;
  18. @property (readwrite, strong) id playClockTimeObserver;
  19. @property (readwrite) BOOL seekToZeroBeforePlay;
  20. @property (readwrite) BOOL rotationIsLocked;
  21. @property (readwrite) BOOL playerIsBuffering;
  22. @property (nonatomic, weak) UIView *containingView;
  23. @property (nonatomic, weak) UIView *topView;
  24. @property (readwrite) BOOL fullScreenModeToggled;
  25. @property (nonatomic) BOOL isAlwaysFullscreen;
  26. @property (nonatomic, readwrite) BOOL isPlaying;
  27. @property (nonatomic, strong) FullScreenViewController *fullscreenViewController;
  28. @property (nonatomic) CGRect previousBounds;
  29. @property (nonatomic) BOOL hideTopViewWithControls;
  30. @property (nonatomic) UIStatusBarStyle previousStatusBarStyle;
  31. @end
  32. @implementation VideoPlayerKit {
  33. BOOL playWhenReady;
  34. BOOL scrubBuffering;
  35. BOOL showShareOptions;
  36. }
  37. - (void)setTopView:(UIView *)topView
  38. {
  39. _topView = topView;
  40. if (self.hideTopViewWithControls) {
  41. __weak UIView *weakTopView = _topView;
  42. [[NSNotificationCenter defaultCenter] removeObserver:self];
  43. [[NSNotificationCenter defaultCenter] addObserverForName:kVideoPlayerWillHideControlsNotification
  44. object:self
  45. queue:[NSOperationQueue mainQueue]
  46. usingBlock:^(NSNotification *note) {
  47. [UIView animateWithDuration:controlsAnimationDuration
  48. animations:^{
  49. [weakTopView setAlpha:0.0f];
  50. }];
  51. }];
  52. [[NSNotificationCenter defaultCenter] addObserverForName:kVideoPlayerWillShowControlsNotification
  53. object:self
  54. queue:[NSOperationQueue mainQueue]
  55. usingBlock:^(NSNotification *note) {
  56. [UIView animateWithDuration:controlsAnimationDuration
  57. animations:^{
  58. [weakTopView setAlpha:1.0f];
  59. }];
  60. }];
  61. }
  62. }
  63. - (id)initWithContainingView:(UIView *)containingView optionalTopView:(UIView *)topView hideTopViewWithControls:(BOOL)hideTopViewWithControls
  64. {
  65. if ((self = [super init])) {
  66. self.containingView = containingView;
  67. self.hideTopViewWithControls = hideTopViewWithControls;
  68. self.topView = topView;
  69. self.previousStatusBarStyle = [[UIApplication sharedApplication] statusBarStyle];
  70. }
  71. return self;
  72. }
  73. + (VideoPlayerKit *)videoPlayerWithContainingViewController:(UIViewController *)containingViewController
  74. optionalTopView:(UIView *)topView
  75. hideTopViewWithControls:(BOOL)hideTopViewWithControls
  76. {
  77. VideoPlayerKit *videoPlayer = [[VideoPlayerKit alloc] initWithContainingView:containingViewController.view
  78. optionalTopView:topView
  79. hideTopViewWithControls:hideTopViewWithControls];
  80. return videoPlayer;
  81. }
  82. + (VideoPlayerKit *)videoPlayerWithContainingView:(UIView *)containingView
  83. optionalTopView:(UIView *)topView
  84. hideTopViewWithControls:(BOOL)hideTopViewWithControls
  85. {
  86. VideoPlayerKit *videoPlayer = [[VideoPlayerKit alloc] initWithContainingView:containingView
  87. optionalTopView:topView
  88. hideTopViewWithControls:hideTopViewWithControls];
  89. return videoPlayer;
  90. }
  91. - (void)setControlsEdgeInsets:(UIEdgeInsets)controlsEdgeInsets
  92. {
  93. if (!self.videoPlayerView) {
  94. self.videoPlayerView = [[VideoPlayerView alloc] initWithFrame:self.containingView.bounds];
  95. }
  96. _controlsEdgeInsets = controlsEdgeInsets;
  97. self.videoPlayerView.controlsEdgeInsets = _controlsEdgeInsets;
  98. [self.view setNeedsLayout];
  99. }
  100. - (void)dealloc
  101. {
  102. [[NSNotificationCenter defaultCenter] removeObserver:self];
  103. [self removeObserversFromVideoPlayerItem];
  104. [self removePlayerTimeObservers];
  105. }
  106. - (void)removeObserversFromVideoPlayerItem
  107. {
  108. [self.videoPlayer.currentItem removeObserver:self forKeyPath:@"status"];
  109. [self.videoPlayer.currentItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
  110. [self.videoPlayer.currentItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
  111. [self.videoPlayer.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
  112. [_videoPlayer removeObserver:self forKeyPath:@"externalPlaybackActive"];
  113. [_videoPlayer removeObserver:self forKeyPath:@"airPlayVideoActive"];
  114. }
  115. - (void)loadView
  116. {
  117. if (!self.videoPlayerView) {
  118. self.videoPlayerView = [[VideoPlayerView alloc] initWithFrame:self.containingView.bounds];
  119. }
  120. if (self.topView) {
  121. self.topView.frame = CGRectMake(0, 0, self.videoPlayerView.frame.size.width, self.topView.frame.size.height);
  122. [self.videoPlayerView addSubview:self.topView];
  123. }
  124. self.view = self.videoPlayerView;
  125. }
  126. - (void)viewDidLoad
  127. {
  128. [super viewDidLoad];
  129. _currentVideoInfo = [[NSDictionary alloc] init];
  130. [_videoPlayerView.playPauseButton addTarget:self action:@selector(playPauseHandler) forControlEvents:UIControlEventTouchUpInside];
  131. [_videoPlayerView.fullScreenButton addTarget:self action:@selector(fullScreenButtonHandler) forControlEvents:UIControlEventTouchUpInside];
  132. [self.videoPlayerView.shareButton addTarget:self action:@selector(shareButtonHandler) forControlEvents:UIControlEventTouchUpInside];
  133. [_videoPlayerView.videoScrubber addTarget:self action:@selector(scrubbingDidBegin) forControlEvents:UIControlEventTouchDown];
  134. [_videoPlayerView.videoScrubber addTarget:self action:@selector(scrubberIsScrolling) forControlEvents:UIControlEventValueChanged];
  135. [_videoPlayerView.videoScrubber addTarget:self action:@selector(scrubbingDidEnd) forControlEvents:(UIControlEventTouchUpInside | UIControlEventTouchCancel)];
  136. UITapGestureRecognizer *playerTouchedGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(videoTapHandler)];
  137. playerTouchedGesture.delegate = self;
  138. [_videoPlayerView addGestureRecognizer:playerTouchedGesture];
  139. UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchGesture:)];
  140. [pinchRecognizer setDelegate:self];
  141. [self.view addGestureRecognizer:pinchRecognizer];
  142. }
  143. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
  144. {
  145. if ([touch.view isDescendantOfView:self.videoPlayerView.playerControlBar] || [touch.view isDescendantOfView:self.videoPlayerView.shareButton]) {
  146. return NO;
  147. }
  148. return YES;
  149. }
  150. - (void)viewWillAppear:(BOOL)animated
  151. {
  152. if (self.fullScreenModeToggled) {
  153. BOOL isHidingPlayerControls = self.videoPlayerView.playerControlBar.alpha == 0;
  154. [[UIApplication sharedApplication] setStatusBarHidden:isHidingPlayerControls withAnimation:UIStatusBarAnimationNone];
  155. } else {
  156. [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
  157. }
  158. }
  159. - (void)presentShareOptions
  160. {
  161. showShareOptions = NO;
  162. // [ShareThis showShareOptionsToShareUrl:[_currentVideoInfo objectForKey:@"shareURL"] title:[_currentVideoInfo objectForKey:@"title"] image:nil onViewController:[[[UIApplication sharedApplication] keyWindow] rootViewController] forTypeOfContent:STContentTypeVideo];
  163. }
  164. - (void)shareButtonHandler
  165. {
  166. // Minimize the video if fullscreen so that ShareThis can work
  167. if (self.fullScreenModeToggled) {
  168. showShareOptions = YES;
  169. [self minimizeVideo];
  170. } else {
  171. [self presentShareOptions];
  172. }
  173. }
  174. - (void)playVideoWithTitle:(NSString *)title URL:(NSURL *)url videoID:(NSString *)videoID shareURL:(NSURL *)shareURL isStreaming:(BOOL)streaming playInFullScreen:(BOOL)playInFullScreen
  175. {
  176. [self.videoPlayer pause];
  177. [[_videoPlayerView activityIndicator] startAnimating];
  178. // Reset the buffer bar back to 0
  179. [self.videoPlayerView.progressView setProgress:0 animated:NO];
  180. [self showControls];
  181. NSString *vidID = videoID ?: @"";
  182. _currentVideoInfo = @{ @"title": title ?: @"", @"videoID": vidID, @"isStreaming": @(streaming), @"shareURL": shareURL ?: url};
  183. [[NSNotificationCenter defaultCenter] postNotificationName:kVideoPlayerVideoChangedNotification
  184. object:self
  185. userInfo:_currentVideoInfo];
  186. if ([self.delegate respondsToSelector:@selector(trackEvent:videoID:title:)]) {
  187. if (streaming) {
  188. [self.delegate trackEvent:kTrackEventVideoLiveStart videoID:vidID title:title];
  189. } else {
  190. [self.delegate trackEvent:kTrackEventVideoStart videoID:vidID title:title];
  191. }
  192. }
  193. [_videoPlayerView.currentPositionLabel setText:@""];
  194. [_videoPlayerView.timeLeftLabel setText:@""];
  195. _videoPlayerView.videoScrubber.value = 0;
  196. [_videoPlayerView setTitle:title];
  197. [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:@{
  198. MPMediaItemPropertyTitle: title,
  199. }];
  200. [self setURL:url];
  201. [self syncPlayPauseButtons];
  202. if (playInFullScreen) {
  203. self.isAlwaysFullscreen = YES;
  204. [self launchFullScreen];
  205. } else {
  206. [self.containingView addSubview:self.videoPlayerView];
  207. }
  208. }
  209. - (void)showCannotFetchStreamError
  210. {
  211. UIAlertView *alertView = [[UIAlertView alloc]
  212. initWithTitle:@"Sad Panda says..."
  213. message:@"I can't seem to fetch that stream. Please try again later."
  214. delegate:nil
  215. cancelButtonTitle:@"Bummer!"
  216. otherButtonTitles:nil];
  217. [alertView show];
  218. }
  219. - (void)playPauseHandler
  220. {
  221. if (_seekToZeroBeforePlay) {
  222. _seekToZeroBeforePlay = NO;
  223. [_videoPlayer seekToTime:kCMTimeZero];
  224. }
  225. if ([self isPlaying]) {
  226. [_videoPlayer pause];
  227. } else {
  228. [self playVideo];
  229. [[_videoPlayerView activityIndicator] stopAnimating];
  230. }
  231. [self syncPlayPauseButtons];
  232. [self showControls];
  233. }
  234. - (void)launchFullScreen
  235. {
  236. if (!self.fullScreenModeToggled) {
  237. self.fullScreenModeToggled = YES;
  238. if (!self.isAlwaysFullscreen) {
  239. [self hideControlsAnimated:YES];
  240. }
  241. [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
  242. [self syncFullScreenButton:[[UIApplication sharedApplication] statusBarOrientation]];
  243. if (!self.fullscreenViewController) {
  244. self.fullscreenViewController = [[FullScreenViewController alloc] init];
  245. self.fullscreenViewController.allowPortraitFullscreen = self.allowPortraitFullscreen;
  246. }
  247. [self.videoPlayerView setFullscreen:YES];
  248. [self.fullscreenViewController.view addSubview:self.videoPlayerView];
  249. if (self.topView) {
  250. [self.topView removeFromSuperview];
  251. [self.fullscreenViewController.view addSubview:self.topView];
  252. }
  253. if (self.isAlwaysFullscreen) {
  254. self.videoPlayerView.alpha = 0.0;
  255. if (self.topView) {
  256. self.topView.alpha = 0.0;
  257. }
  258. } else {
  259. self.previousBounds = self.videoPlayerView.frame;
  260. [UIView animateWithDuration:0.45f
  261. delay:0.0f
  262. options:UIViewAnimationCurveLinear
  263. animations:^{
  264. [self.videoPlayerView setCenter:CGPointMake( self.videoPlayerView.superview.bounds.size.width / 2, ( self.videoPlayerView.superview.bounds.size.height / 2))];
  265. self.videoPlayerView.bounds = self.videoPlayerView.superview.bounds;
  266. }
  267. completion:nil];
  268. }
  269. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:self.fullscreenViewController animated:YES completion:^{
  270. if (self.isAlwaysFullscreen) {
  271. self.videoPlayerView.frame = CGRectMake(self.videoPlayerView.superview.bounds.size.width / 2, self.videoPlayerView.superview.bounds.size.height / 2, 0, 0);
  272. self.previousBounds = CGRectMake(self.videoPlayerView.superview.bounds.size.width / 2, self.videoPlayerView.superview.bounds.size.height / 2, 0, 0);
  273. [self.videoPlayerView setCenter:CGPointMake( self.videoPlayerView.superview.bounds.size.width / 2, self.videoPlayerView.superview.bounds.size.height / 2)];
  274. [UIView animateWithDuration:0.25f
  275. delay:0.0f
  276. options:UIViewAnimationCurveLinear
  277. animations:^{
  278. self.videoPlayerView.alpha = 1.0;
  279. self.topView.alpha = 1.0;
  280. }
  281. completion:nil];
  282. self.videoPlayerView.frame = self.videoPlayerView.superview.bounds;
  283. }
  284. if (self.topView) {
  285. self.topView.frame = CGRectMake(0, 0, self.videoPlayerView.frame.size.width, self.topView.frame.size.height);
  286. }
  287. if ([self.delegate respondsToSelector:@selector(setFullScreenToggled:)]) {
  288. [self.delegate setFullScreenToggled:self.fullScreenModeToggled];
  289. }
  290. }];
  291. }
  292. }
  293. - (void)minimizeVideo
  294. {
  295. if (self.fullScreenModeToggled) {
  296. self.fullScreenModeToggled = NO;
  297. [self.videoPlayerView setFullscreen:NO];
  298. [self hideControlsAnimated:NO];
  299. [self syncFullScreenButton:self.interfaceOrientation];
  300. if (self.topView) {
  301. [self.topView removeFromSuperview];
  302. [self.videoPlayerView addSubview:self.topView];
  303. }
  304. if (self.isAlwaysFullscreen) {
  305. [self.videoPlayer pause];
  306. [UIView animateWithDuration:0.45f
  307. delay:0.0f
  308. options:UIViewAnimationCurveLinear
  309. animations:^{
  310. self.videoPlayerView.frame = self.previousBounds;
  311. }
  312. completion:^(BOOL success){
  313. if (showShareOptions) {
  314. [self presentShareOptions];
  315. }
  316. [self.videoPlayerView removeFromSuperview];
  317. }];
  318. } else {
  319. [UIView animateWithDuration:0.45f
  320. delay:0.0f
  321. options:UIViewAnimationCurveLinear
  322. animations:^{
  323. self.videoPlayerView.frame = self.previousBounds;
  324. }
  325. completion:^(BOOL success){
  326. if (showShareOptions) {
  327. [self presentShareOptions];
  328. }
  329. }];
  330. [self.videoPlayerView removeFromSuperview];
  331. [self.containingView addSubview:self.videoPlayerView];
  332. }
  333. [[UIApplication sharedApplication] setStatusBarStyle:self.previousStatusBarStyle];
  334. [[UIApplication sharedApplication].keyWindow.rootViewController dismissViewControllerAnimated:self.isAlwaysFullscreen completion:^{
  335. if (!self.isAlwaysFullscreen) {
  336. [self showControls];
  337. }
  338. [[UIApplication sharedApplication] setStatusBarHidden:NO
  339. withAnimation:UIStatusBarAnimationFade];
  340. if ([self.delegate respondsToSelector:@selector(setFullScreenToggled:)]) {
  341. [self.delegate setFullScreenToggled:self.fullScreenModeToggled];
  342. }
  343. }];
  344. }
  345. }
  346. - (void)fullScreenButtonHandler
  347. {
  348. [self showControls];
  349. if (self.fullScreenModeToggled) {
  350. [self minimizeVideo];
  351. } else {
  352. [self launchFullScreen];
  353. }
  354. }
  355. - (void)pinchGesture:(id)sender
  356. {
  357. if([(UIPinchGestureRecognizer *)sender state] == UIGestureRecognizerStateEnded) {
  358. [self fullScreenButtonHandler];
  359. }
  360. }
  361. - (void)forceOrientationChange
  362. {
  363. _rotationIsLocked = YES;
  364. [self performSelector:@selector(unlockRotationLock) withObject:nil afterDelay:0.5];
  365. if ([[[UIDevice currentDevice] systemVersion] floatValue] < 6.0) {
  366. UIWindow *window = [[UIApplication sharedApplication] keyWindow];
  367. UIView *view = [window.subviews objectAtIndex:0];
  368. [view removeFromSuperview];
  369. [window addSubview:view];
  370. [_videoPlayerView.superview layoutSubviews];
  371. } else {
  372. // Have the VideoPlayerVC's parent VC implement rotation trigger
  373. if ([self.delegate respondsToSelector:@selector(setFullScreenToggled:)]) {
  374. [self.delegate setFullScreenToggled:self.fullScreenModeToggled];
  375. }
  376. }
  377. }
  378. - (void)unlockRotationLock
  379. {
  380. _rotationIsLocked = NO;
  381. }
  382. - (void)videoTapHandler
  383. {
  384. if (_videoPlayerView.playerControlBar.alpha) {
  385. [self hideControlsAnimated:YES];
  386. } else {
  387. [self showControls];
  388. }
  389. }
  390. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
  391. {
  392. return YES;
  393. }
  394. - (void)setURL:(NSURL *)url
  395. {
  396. AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url];
  397. [playerItem addObserver:self
  398. forKeyPath:@"status"
  399. options:NSKeyValueObservingOptionNew
  400. context:nil];
  401. [playerItem addObserver:self
  402. forKeyPath:@"playbackBufferEmpty"
  403. options:NSKeyValueObservingOptionNew
  404. context:nil];
  405. [playerItem addObserver:self
  406. forKeyPath:@"playbackLikelyToKeepUp"
  407. options:NSKeyValueObservingOptionNew
  408. context:nil];
  409. [playerItem addObserver:self
  410. forKeyPath:@"loadedTimeRanges"
  411. options:NSKeyValueObservingOptionNew
  412. context:nil];
  413. if (!self.videoPlayer) {
  414. _videoPlayer = [AVPlayer playerWithPlayerItem:playerItem];
  415. [_videoPlayer setAllowsAirPlayVideo:YES];
  416. [_videoPlayer setUsesAirPlayVideoWhileAirPlayScreenIsActive:YES];
  417. if ([_videoPlayer respondsToSelector:@selector(setAllowsExternalPlayback:)]) { // iOS 6 API
  418. [_videoPlayer setAllowsExternalPlayback:YES];
  419. }
  420. [_videoPlayerView setPlayer:_videoPlayer];
  421. } else {
  422. [self removeObserversFromVideoPlayerItem];
  423. [self.videoPlayer replaceCurrentItemWithPlayerItem:playerItem];
  424. }
  425. // iOS 5
  426. [_videoPlayer addObserver:self forKeyPath:@"airPlayVideoActive" options:NSKeyValueObservingOptionNew context:nil];
  427. // iOS 6
  428. [_videoPlayer addObserver:self
  429. forKeyPath:@"externalPlaybackActive"
  430. options:NSKeyValueObservingOptionNew
  431. context:nil];
  432. [[NSNotificationCenter defaultCenter] addObserver:self
  433. selector:@selector(playerItemDidReachEnd:)
  434. name:AVPlayerItemDidPlayToEndTimeNotification
  435. object:self.videoPlayer.currentItem];
  436. }
  437. // Wait for the video player status to change to ready before initializing video player controls
  438. - (void)observeValueForKeyPath:(NSString *)keyPath
  439. ofObject:(id)object
  440. change:(NSDictionary *)change
  441. context:(void *)context
  442. {
  443. if (object == _videoPlayer
  444. && ([keyPath isEqualToString:@"externalPlaybackActive"] || [keyPath isEqualToString:@"airPlayVideoActive"])) {
  445. BOOL externalPlaybackActive = [[change objectForKey:NSKeyValueChangeNewKey] boolValue];
  446. [[_videoPlayerView airplayIsActiveView] setHidden:!externalPlaybackActive];
  447. return;
  448. }
  449. if (object != [_videoPlayer currentItem]) {
  450. return;
  451. }
  452. if ([keyPath isEqualToString:@"status"]) {
  453. AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
  454. switch (status) {
  455. case AVPlayerStatusReadyToPlay:
  456. playWhenReady = YES;
  457. if (![self isPlaying]) {
  458. [self playVideo];
  459. }
  460. break;
  461. case AVPlayerStatusFailed:
  462. // TODO:
  463. [self removeObserversFromVideoPlayerItem];
  464. [self removePlayerTimeObservers];
  465. self.videoPlayer = nil;
  466. break;
  467. }
  468. } else if ([keyPath isEqualToString:@"playbackBufferEmpty"] && _videoPlayer.currentItem.playbackBufferEmpty) {
  469. self.playerIsBuffering = YES;
  470. [[_videoPlayerView activityIndicator] startAnimating];
  471. [self syncPlayPauseButtons];
  472. } else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"] && _videoPlayer.currentItem.playbackLikelyToKeepUp) {
  473. if (![self isPlaying] && playWhenReady)
  474. {
  475. if (self.playerIsBuffering || scrubBuffering) {
  476. if (self.restoreVideoPlayStateAfterScrubbing) {
  477. self.restoreVideoPlayStateAfterScrubbing = NO;
  478. [self playVideo];
  479. }
  480. } else {
  481. [self playVideo];
  482. }
  483. }
  484. [[_videoPlayerView activityIndicator] stopAnimating];
  485. } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
  486. float durationTime = CMTimeGetSeconds([[self.videoPlayer currentItem] duration]);
  487. float bufferTime = [self availableDuration];
  488. [self.videoPlayerView.progressView setProgress:bufferTime/durationTime animated:YES];
  489. }
  490. return;
  491. }
  492. - (float)availableDuration
  493. {
  494. NSArray *loadedTimeRanges = [[self.videoPlayer currentItem] loadedTimeRanges];
  495. // Check to see if the timerange is not an empty array, fix for when video goes on airplay
  496. // and video doesn't include any time ranges
  497. if ([loadedTimeRanges count] > 0) {
  498. CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue];
  499. float startSeconds = CMTimeGetSeconds(timeRange.start);
  500. float durationSeconds = CMTimeGetSeconds(timeRange.duration);
  501. return (startSeconds + durationSeconds);
  502. } else {
  503. return 0.0f;
  504. }
  505. }
  506. - (void)playVideo
  507. {
  508. if (self.view.superview) {
  509. self.playerIsBuffering = NO;
  510. scrubBuffering = NO;
  511. playWhenReady = NO;
  512. // Configuration is done, ready to start.
  513. [self.videoPlayer play];
  514. [self updatePlaybackProgress];
  515. }
  516. }
  517. - (void)showControls
  518. {
  519. [[NSNotificationCenter defaultCenter] postNotificationName:kVideoPlayerWillShowControlsNotification
  520. object:self
  521. userInfo:nil];
  522. [UIView animateWithDuration:controlsAnimationDuration animations:^{
  523. self.videoPlayerView.playerControlBar.alpha = 1.0;
  524. self.videoPlayerView.titleLabel.alpha = 1.0;
  525. self.videoPlayerView.shareButton.alpha = 1.0;
  526. } completion:nil];
  527. if (self.fullScreenModeToggled) {
  528. [[UIApplication sharedApplication] setStatusBarHidden:NO
  529. withAnimation:UIStatusBarAnimationFade];
  530. }
  531. [NSObject cancelPreviousPerformRequestsWithTarget:self
  532. selector:@selector(hideControlsAnimated:)
  533. object:[NSString stringWithFormat:@"YES"]];
  534. if ([self isPlaying]) {
  535. [self performSelector:@selector(hideControlsAnimated:)
  536. withObject:[NSString stringWithFormat:@"YES"]
  537. afterDelay:4.0];
  538. }
  539. }
  540. - (void)hideControlsAnimated:(BOOL)animated
  541. {
  542. [[NSNotificationCenter defaultCenter] postNotificationName:kVideoPlayerWillHideControlsNotification
  543. object:self
  544. userInfo:nil];
  545. if (animated) {
  546. [UIView animateWithDuration:controlsAnimationDuration animations:^{
  547. self.videoPlayerView.playerControlBar.alpha = 0;
  548. self.videoPlayerView.titleLabel.alpha = 0;
  549. _videoPlayerView.shareButton.alpha = 0;
  550. } completion:nil];
  551. if (self.fullScreenModeToggled) {
  552. [[UIApplication sharedApplication] setStatusBarHidden:YES
  553. withAnimation:UIStatusBarAnimationFade];
  554. }
  555. } else {
  556. self.videoPlayerView.playerControlBar.alpha = 0;
  557. self.videoPlayerView.titleLabel.alpha = 0;
  558. _videoPlayerView.shareButton.alpha = 0;
  559. if (self.fullScreenModeToggled) {
  560. [[UIApplication sharedApplication] setStatusBarHidden:YES
  561. withAnimation:UIStatusBarAnimationNone];
  562. }
  563. }
  564. }
  565. - (void)updatePlaybackProgress
  566. {
  567. [self syncPlayPauseButtons];
  568. [self showControls];
  569. double interval = .1f;
  570. CMTime playerDuration = [self playerItemDuration];
  571. if (CMTIME_IS_INVALID(playerDuration)) {
  572. return;
  573. }
  574. double duration = CMTimeGetSeconds(playerDuration);
  575. if (CMTIME_IS_INDEFINITE(playerDuration) || duration <= 0) {
  576. [_videoPlayerView.videoScrubber setHidden:YES];
  577. [_videoPlayerView.progressView setHidden:YES];
  578. [self syncPlayClock];
  579. return;
  580. }
  581. [_videoPlayerView.videoScrubber setHidden:NO];
  582. [_videoPlayerView.progressView setHidden:NO];
  583. CGFloat width = CGRectGetWidth([_videoPlayerView.videoScrubber bounds]);
  584. interval = 0.5f * duration / width;
  585. __weak VideoPlayerKit *vpvc = self;
  586. _scrubberTimeObserver = [_videoPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC)
  587. queue:NULL
  588. usingBlock:^(CMTime time) {
  589. [vpvc syncScrubber];
  590. }];
  591. // Update the play clock every second
  592. _playClockTimeObserver = [_videoPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1, NSEC_PER_SEC)
  593. queue:NULL
  594. usingBlock:^(CMTime time) {
  595. [vpvc syncPlayClock];
  596. }];
  597. }
  598. -(void)removePlayerTimeObservers
  599. {
  600. if (_scrubberTimeObserver) {
  601. [_videoPlayer removeTimeObserver:_scrubberTimeObserver];
  602. _scrubberTimeObserver = nil;
  603. }
  604. if (_playClockTimeObserver) {
  605. [_videoPlayer removeTimeObserver:_playClockTimeObserver];
  606. _playClockTimeObserver = nil;
  607. }
  608. }
  609. - (void)playerItemDidReachEnd:(NSNotification *)notification
  610. {
  611. [self syncPlayPauseButtons];
  612. _seekToZeroBeforePlay = YES;
  613. if ([self.delegate respondsToSelector:@selector(trackEvent:videoID:title:)]) {
  614. [self.delegate trackEvent:kTrackEventVideoComplete videoID:[_currentVideoInfo objectForKey:@"videoID"] title:[_currentVideoInfo objectForKey:@"title"]];
  615. }
  616. [self minimizeVideo];
  617. }
  618. - (void)syncScrubber
  619. {
  620. CMTime playerDuration = [self playerItemDuration];
  621. if (CMTIME_IS_INVALID(playerDuration)) {
  622. _videoPlayerView.videoScrubber.minimumValue = 0.0;
  623. return;
  624. }
  625. double duration = CMTimeGetSeconds(playerDuration);
  626. if (isfinite(duration)) {
  627. float minValue = [_videoPlayerView.videoScrubber minimumValue];
  628. float maxValue = [_videoPlayerView.videoScrubber maximumValue];
  629. double time = CMTimeGetSeconds([_videoPlayer currentTime]);
  630. [_videoPlayerView.videoScrubber setValue:(maxValue - minValue) * time / duration + minValue];
  631. }
  632. }
  633. - (void)syncPlayClock
  634. {
  635. CMTime playerDuration = [self playerItemDuration];
  636. if (CMTIME_IS_INVALID(playerDuration)) {
  637. return;
  638. }
  639. if (CMTIME_IS_INDEFINITE(playerDuration)) {
  640. [_videoPlayerView.currentPositionLabel setText:@"LIVE"];
  641. [_videoPlayerView.timeLeftLabel setText:@""];
  642. return;
  643. }
  644. double duration = CMTimeGetSeconds(playerDuration);
  645. if (isfinite(duration)) {
  646. double currentTime = floor(CMTimeGetSeconds([_videoPlayer currentTime]));
  647. double timeLeft = floor(duration - currentTime);
  648. if (currentTime <= 0) {
  649. currentTime = 0;
  650. timeLeft = floor(duration);
  651. }
  652. [_videoPlayerView.currentPositionLabel setText:[NSString stringWithFormat:@"%@ ", [self stringFormattedTimeFromSeconds:&currentTime]]];
  653. if (!self.showStaticEndTime) {
  654. [_videoPlayerView.timeLeftLabel setText:[NSString stringWithFormat:@"-%@", [self stringFormattedTimeFromSeconds:&timeLeft]]];
  655. } else {
  656. [_videoPlayerView.timeLeftLabel setText:[NSString stringWithFormat:@"%@", [self stringFormattedTimeFromSeconds:&duration]]];
  657. }
  658. }
  659. }
  660. - (CMTime)playerItemDuration
  661. {
  662. if (_videoPlayer.status == AVPlayerItemStatusReadyToPlay) {
  663. return([_videoPlayer.currentItem duration]);
  664. }
  665. return(kCMTimeInvalid);
  666. }
  667. - (BOOL)isPlaying
  668. {
  669. return [_videoPlayer rate] != 0.0;
  670. }
  671. - (void)syncPlayPauseButtons
  672. {
  673. if ([self isPlaying]) {
  674. [_videoPlayerView.playPauseButton setImage:[UIImage imageNamed:@"pause-button"] forState:UIControlStateNormal];
  675. } else {
  676. [_videoPlayerView.playPauseButton setImage:[UIImage imageNamed:@"play-button"] forState:UIControlStateNormal];
  677. }
  678. }
  679. - (void)syncFullScreenButton:(UIInterfaceOrientation)toInterfaceOrientation
  680. {
  681. if (_fullScreenModeToggled) {
  682. [_videoPlayerView.fullScreenButton setImage:[UIImage imageNamed:@"minimize-button"] forState:UIControlStateNormal];
  683. } else {
  684. [_videoPlayerView.fullScreenButton setImage:[UIImage imageNamed:@"fullscreen-button"] forState:UIControlStateNormal];
  685. }
  686. }
  687. -(void)scrubbingDidBegin
  688. {
  689. if ([self isPlaying]) {
  690. [_videoPlayer pause];
  691. [self syncPlayPauseButtons];
  692. self.restoreVideoPlayStateAfterScrubbing = YES;
  693. [self showControls];
  694. }
  695. }
  696. -(void)scrubberIsScrolling
  697. {
  698. CMTime playerDuration = [self playerItemDuration];
  699. double duration = CMTimeGetSeconds(playerDuration);
  700. if (isfinite(duration)) {
  701. double currentTime = floor(duration * _videoPlayerView.videoScrubber.value);
  702. double timeLeft = floor(duration - currentTime);
  703. if (currentTime <= 0) {
  704. currentTime = 0;
  705. timeLeft = floor(duration);
  706. }
  707. [_videoPlayerView.currentPositionLabel setText:[NSString stringWithFormat:@"%@ ", [self stringFormattedTimeFromSeconds:&currentTime]]];
  708. if (!self.showStaticEndTime) {
  709. [_videoPlayerView.timeLeftLabel setText:[NSString stringWithFormat:@"-%@", [self stringFormattedTimeFromSeconds:&timeLeft]]];
  710. } else {
  711. [_videoPlayerView.timeLeftLabel setText:[NSString stringWithFormat:@"%@", [self stringFormattedTimeFromSeconds:&duration]]];
  712. }
  713. [_videoPlayer seekToTime:CMTimeMakeWithSeconds((float) currentTime, NSEC_PER_SEC)];
  714. }
  715. }
  716. -(void)scrubbingDidEnd
  717. {
  718. if (self.restoreVideoPlayStateAfterScrubbing) {
  719. scrubBuffering = YES;
  720. }
  721. [[_videoPlayerView activityIndicator] startAnimating];
  722. [self showControls];
  723. }
  724. - (NSString *)stringFormattedTimeFromSeconds:(double *)seconds
  725. {
  726. NSDate *date = [NSDate dateWithTimeIntervalSince1970:*seconds];
  727. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  728. [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
  729. if (*seconds >= 3600) {
  730. [formatter setDateFormat:@"HH:mm:ss"];
  731. } else {
  732. [formatter setDateFormat:@"mm:ss"];
  733. }
  734. return [formatter stringFromDate:date];
  735. }
  736. @end