VLCFullscreenMovieTVViewController.m 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. /*****************************************************************************
  2. * VLC for iOS
  3. *****************************************************************************
  4. * Copyright (c) 2015 VideoLAN. All rights reserved.
  5. * $Id$
  6. *
  7. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  8. *
  9. * Refer to the COPYING file of the official project for license.
  10. *****************************************************************************/
  11. #import "VLCFullscreenMovieTVViewController.h"
  12. #import "VLCPlaybackInfoTVViewController.h"
  13. #import "VLCPlaybackInfoTVAnimators.h"
  14. #import "VLCIRTVTapGestureRecognizer.h"
  15. #import "VLCHTTPUploaderController.h"
  16. #import "VLCSiriRemoteGestureRecognizer.h"
  17. #import "VLCNetworkImageView.h"
  18. typedef NS_ENUM(NSInteger, VLCPlayerScanState)
  19. {
  20. VLCPlayerScanStateNone,
  21. VLCPlayerScanStateForward2,
  22. VLCPlayerScanStateForward4,
  23. };
  24. @interface VLCFullscreenMovieTVViewController (UIViewControllerTransitioningDelegate) <UIViewControllerTransitioningDelegate, UIGestureRecognizerDelegate>
  25. @end
  26. @interface VLCFullscreenMovieTVViewController ()
  27. @property (nonatomic) CADisplayLink *displayLink;
  28. @property (nonatomic) NSTimer *audioDescriptionScrollTimer;
  29. @property (nonatomic) NSTimer *hidePlaybackControlsViewAfterDeleayTimer;
  30. @property (nonatomic) VLCPlaybackInfoTVViewController *infoViewController;
  31. @property (nonatomic) NSNumber *scanSavedPlaybackRate;
  32. @property (nonatomic) VLCPlayerScanState scanState;
  33. @property (nonatomic) NSString *lastArtist;
  34. @property (nonatomic, readonly, getter=isSeekable) BOOL seekable;
  35. @property (nonatomic) NSSet<UIGestureRecognizer *> *simultaneousGestureRecognizers;
  36. @end
  37. @implementation VLCFullscreenMovieTVViewController
  38. + (instancetype)fullscreenMovieTVViewController
  39. {
  40. return [[self alloc] initWithNibName:nil bundle:nil];
  41. }
  42. - (void)viewDidLoad
  43. {
  44. self.extendedLayoutIncludesOpaqueBars = YES;
  45. self.edgesForExtendedLayout = UIRectEdgeAll;
  46. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  47. [center addObserver:self
  48. selector:@selector(playbackDidStop)
  49. name:VLCPlaybackControllerPlaybackDidStop
  50. object:nil];
  51. [center addObserver:self
  52. selector:@selector(playbackDidStop)
  53. name:VLCPlaybackControllerPlaybackDidFail
  54. object:nil];
  55. _movieView.userInteractionEnabled = NO;
  56. self.titleLabel.text = @"";
  57. self.transportBar.bufferStartFraction = 0.0;
  58. self.transportBar.bufferEndFraction = 1.0;
  59. self.transportBar.playbackFraction = 0.0;
  60. self.transportBar.scrubbingFraction = 0.0;
  61. self.dimmingView.alpha = 0.0;
  62. self.bottomOverlayView.alpha = 0.0;
  63. self.bufferingLabel.text = NSLocalizedString(@"PLEASE_WAIT", nil);
  64. NSMutableSet<UIGestureRecognizer *> *simultaneousGestureRecognizers = [NSMutableSet set];
  65. // Panning and Swiping
  66. UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
  67. panGestureRecognizer.delegate = self;
  68. [self.view addGestureRecognizer:panGestureRecognizer];
  69. [simultaneousGestureRecognizers addObject:panGestureRecognizer];
  70. // Button presses
  71. UITapGestureRecognizer *playpauseGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playPausePressed)];
  72. playpauseGesture.allowedPressTypes = @[@(UIPressTypePlayPause)];
  73. [self.view addGestureRecognizer:playpauseGesture];
  74. UITapGestureRecognizer *menuTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuButtonPressed:)];
  75. menuTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
  76. menuTapGestureRecognizer.delegate = self;
  77. [self.view addGestureRecognizer:menuTapGestureRecognizer];
  78. // IR only recognizer
  79. UITapGestureRecognizer *upArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIRPressUp)];
  80. upArrowRecognizer.allowedPressTypes = @[@(UIPressTypeUpArrow)];
  81. [self.view addGestureRecognizer:upArrowRecognizer];
  82. UITapGestureRecognizer *downArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(showInfoVCIfNotScrubbing)];
  83. downArrowRecognizer.allowedPressTypes = @[@(UIPressTypeDownArrow)];
  84. [self.view addGestureRecognizer:downArrowRecognizer];
  85. UITapGestureRecognizer *leftArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIRPressLeft)];
  86. leftArrowRecognizer.allowedPressTypes = @[@(UIPressTypeLeftArrow)];
  87. [self.view addGestureRecognizer:leftArrowRecognizer];
  88. UITapGestureRecognizer *rightArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIRPressRight)];
  89. rightArrowRecognizer.allowedPressTypes = @[@(UIPressTypeRightArrow)];
  90. [self.view addGestureRecognizer:rightArrowRecognizer];
  91. // Siri remote arrow presses
  92. VLCSiriRemoteGestureRecognizer *siriArrowRecognizer = [[VLCSiriRemoteGestureRecognizer alloc] initWithTarget:self action:@selector(handleSiriRemote:)];
  93. siriArrowRecognizer.delegate = self;
  94. [self.view addGestureRecognizer:siriArrowRecognizer];
  95. [simultaneousGestureRecognizers addObject:siriArrowRecognizer];
  96. self.simultaneousGestureRecognizers = simultaneousGestureRecognizers;
  97. [super viewDidLoad];
  98. }
  99. - (void)didReceiveMemoryWarning
  100. {
  101. [super didReceiveMemoryWarning];
  102. self.infoViewController = nil;
  103. }
  104. #pragma mark - view events
  105. - (void)viewWillAppear:(BOOL)animated
  106. {
  107. [super viewWillAppear:animated];
  108. self.audioView.hidden = YES;
  109. self.audioDescriptionTextView.hidden = YES;
  110. self.audioTitleLabel.hidden = YES;
  111. self.audioArtistLabel.hidden = YES;
  112. self.audioAlbumNameLabel.hidden = YES;
  113. self.audioArtworkImageView.image = [UIImage imageNamed:@"about-app-icon"];
  114. self.audioLargeBackgroundImageView.image = [UIImage imageNamed:@"about-app-icon"];
  115. self.audioArtworkImageView.animateImageSetting = YES;
  116. self.audioLargeBackgroundImageView.animateImageSetting = YES;
  117. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  118. vpc.delegate = self;
  119. [vpc recoverPlaybackState];
  120. }
  121. - (void)viewDidAppear:(BOOL)animated
  122. {
  123. [super viewDidAppear:animated];
  124. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  125. [vpc recoverDisplayedMetadata];
  126. vpc.videoOutputView = nil;
  127. vpc.videoOutputView = self.movieView;
  128. }
  129. - (void)viewWillDisappear:(BOOL)animated
  130. {
  131. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  132. if (vpc.videoOutputView == self.movieView) {
  133. vpc.videoOutputView = nil;
  134. }
  135. [vpc stopPlayback];
  136. [self stopAudioDescriptionAnimation];
  137. /* delete potentially downloaded subs */
  138. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  139. NSString* tempSubsDirPath = [searchPaths[0] stringByAppendingPathComponent:@"tempsubs"];
  140. NSFileManager *fileManager = [NSFileManager defaultManager];
  141. if ([fileManager fileExistsAtPath:tempSubsDirPath])
  142. [fileManager removeItemAtPath:tempSubsDirPath error:nil];
  143. [super viewWillDisappear:animated];
  144. [[NSNotificationCenter defaultCenter] removeObserver:self];
  145. }
  146. - (BOOL)canBecomeFirstResponder
  147. {
  148. return YES;
  149. }
  150. #pragma mark - UIActions
  151. - (void)playPausePressed
  152. {
  153. [self showPlaybackControlsIfNeededForUserInteraction];
  154. [self setScanState:VLCPlayerScanStateNone];
  155. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  156. if (self.transportBar.scrubbing) {
  157. [self selectButtonPressed];
  158. } else {
  159. [vpc playPause];
  160. }
  161. }
  162. - (void)panGesture:(UIPanGestureRecognizer *)panGestureRecognizer
  163. {
  164. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  165. NSInteger currentTitle = [vpc indexOfCurrentTitle];
  166. if (currentTitle < [vpc numberOfTitles]) {
  167. NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
  168. if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
  169. return;
  170. }
  171. }
  172. if (!self.canScrub) {
  173. return;
  174. }
  175. switch (panGestureRecognizer.state) {
  176. case UIGestureRecognizerStateCancelled:
  177. case UIGestureRecognizerStateFailed:
  178. return;
  179. default:
  180. break;
  181. }
  182. VLCTransportBar *bar = self.transportBar;
  183. UIView *view = self.view;
  184. CGPoint translation = [panGestureRecognizer translationInView:view];
  185. if (!bar.scrubbing) {
  186. if (ABS(translation.x) > 150.0) {
  187. if (self.isSeekable) {
  188. [self startScrubbing];
  189. } else {
  190. return;
  191. }
  192. } else if (translation.y > 200.0) {
  193. panGestureRecognizer.enabled = NO;
  194. panGestureRecognizer.enabled = YES;
  195. [self showInfoVCIfNotScrubbing];
  196. return;
  197. } else {
  198. return;
  199. }
  200. }
  201. [self showPlaybackControlsIfNeededForUserInteraction];
  202. [self setScanState:VLCPlayerScanStateNone];
  203. const CGFloat scaleFactor = 8.0;
  204. CGFloat fractionInView = translation.x/CGRectGetWidth(view.bounds)/scaleFactor;
  205. CGFloat scrubbingFraction = MAX(0.0, MIN(bar.scrubbingFraction + fractionInView,1.0));
  206. if (ABS(scrubbingFraction - bar.playbackFraction)<0.005) {
  207. scrubbingFraction = bar.playbackFraction;
  208. } else {
  209. translation.x = 0.0;
  210. [panGestureRecognizer setTranslation:translation inView:view];
  211. }
  212. [UIView animateWithDuration:0.3
  213. delay:0.0
  214. options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState
  215. animations:^{
  216. bar.scrubbingFraction = scrubbingFraction;
  217. }
  218. completion:nil];
  219. [self updateTimeLabelsForScrubbingFraction:scrubbingFraction];
  220. }
  221. - (void)selectButtonPressed
  222. {
  223. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  224. NSInteger currentTitle = [vpc indexOfCurrentTitle];
  225. if (currentTitle < [vpc numberOfTitles]) {
  226. NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
  227. if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
  228. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionActivate];
  229. return;
  230. }
  231. }
  232. [self showPlaybackControlsIfNeededForUserInteraction];
  233. [self setScanState:VLCPlayerScanStateNone];
  234. VLCTransportBar *bar = self.transportBar;
  235. if (bar.scrubbing) {
  236. bar.playbackFraction = bar.scrubbingFraction;
  237. [self stopScrubbing];
  238. [vpc setPlaybackPosition:bar.scrubbingFraction];
  239. } else if(vpc.isPlaying) {
  240. [vpc playPause];
  241. }
  242. }
  243. - (void)menuButtonPressed:(UITapGestureRecognizer *)recognizer
  244. {
  245. VLCTransportBar *bar = self.transportBar;
  246. if (bar.scrubbing) {
  247. [UIView animateWithDuration:0.3 animations:^{
  248. bar.scrubbingFraction = bar.playbackFraction;
  249. [bar layoutIfNeeded];
  250. }];
  251. [self updateTimeLabelsForScrubbingFraction:bar.playbackFraction];
  252. [self stopScrubbing];
  253. [self hidePlaybackControlsIfNeededAfterDelay];
  254. }
  255. }
  256. - (void)showInfoVCIfNotScrubbing
  257. {
  258. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  259. NSInteger currentTitle = [vpc indexOfCurrentTitle];
  260. if (currentTitle < [vpc numberOfTitles]) {
  261. NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
  262. if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
  263. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionDown];
  264. return;
  265. }
  266. }
  267. if (self.transportBar.scrubbing) {
  268. return;
  269. }
  270. // TODO: configure with player info
  271. VLCPlaybackInfoTVViewController *infoViewController = self.infoViewController;
  272. // prevent repeated presentation when users repeatedly and quickly press the arrow button
  273. if (infoViewController.isBeingPresented) {
  274. return;
  275. }
  276. infoViewController.transitioningDelegate = self;
  277. [self presentViewController:infoViewController animated:YES completion:nil];
  278. [self animatePlaybackControlsToVisibility:NO];
  279. }
  280. - (void)handleIRPressUp
  281. {
  282. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  283. NSInteger currentTitle = [vpc indexOfCurrentTitle];
  284. if (currentTitle < [vpc numberOfTitles]) {
  285. NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
  286. if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
  287. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionUp];
  288. }
  289. }
  290. }
  291. - (void)handleIRPressLeft
  292. {
  293. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  294. NSInteger currentTitle = [vpc indexOfCurrentTitle];
  295. if (currentTitle < [vpc numberOfTitles]) {
  296. NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
  297. if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
  298. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionLeft];
  299. return;
  300. }
  301. }
  302. [self showPlaybackControlsIfNeededForUserInteraction];
  303. if (!self.isSeekable) {
  304. return;
  305. }
  306. BOOL paused = ![VLCPlaybackController sharedInstance].isPlaying;
  307. if (paused) {
  308. [self jumpBackward];
  309. } else
  310. {
  311. [self scanForwardPrevious];
  312. }
  313. }
  314. - (void)handleIRPressRight
  315. {
  316. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  317. NSInteger currentTitle = [vpc indexOfCurrentTitle];
  318. if (currentTitle < [vpc numberOfTitles]) {
  319. NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
  320. if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
  321. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionRight];
  322. return;
  323. }
  324. }
  325. [self showPlaybackControlsIfNeededForUserInteraction];
  326. if (!self.isSeekable) {
  327. return;
  328. }
  329. BOOL paused = ![VLCPlaybackController sharedInstance].isPlaying;
  330. if (paused) {
  331. [self jumpForward];
  332. } else {
  333. [self scanForwardNext];
  334. }
  335. }
  336. - (void)handleSiriRemote:(VLCSiriRemoteGestureRecognizer *)recognizer
  337. {
  338. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  339. NSInteger currentTitle = [vpc indexOfCurrentTitle];
  340. if (currentTitle < [vpc numberOfTitles]) {
  341. NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
  342. if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
  343. switch (recognizer.state) {
  344. case UIGestureRecognizerStateBegan:
  345. case UIGestureRecognizerStateChanged:
  346. if (recognizer.isLongPress) {
  347. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionActivate];
  348. break;
  349. }
  350. break;
  351. case UIGestureRecognizerStateEnded:
  352. if (recognizer.isClick && !recognizer.isLongPress) {
  353. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionActivate];
  354. } else {
  355. switch (recognizer.touchLocation) {
  356. case VLCSiriRemoteTouchLocationLeft:
  357. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionLeft];
  358. break;
  359. case VLCSiriRemoteTouchLocationRight:
  360. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionRight];
  361. break;
  362. case VLCSiriRemoteTouchLocationUp:
  363. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionUp];
  364. break;
  365. case VLCSiriRemoteTouchLocationDown:
  366. [vpc performNavigationAction:VLCMediaPlaybackNavigationActionDown];
  367. break;
  368. case VLCSiriRemoteTouchLocationUnknown:
  369. break;
  370. }
  371. }
  372. break;
  373. default:
  374. break;
  375. }
  376. return;
  377. }
  378. }
  379. [self showPlaybackControlsIfNeededForUserInteraction];
  380. VLCTransportBarHint hint = self.transportBar.hint;
  381. switch (recognizer.state) {
  382. case UIGestureRecognizerStateBegan:
  383. case UIGestureRecognizerStateChanged:
  384. if (recognizer.isLongPress) {
  385. if (!self.isSeekable && recognizer.touchLocation == VLCSiriRemoteTouchLocationRight) {
  386. [self setScanState:VLCPlayerScanStateForward2];
  387. return;
  388. }
  389. } else {
  390. if (self.canJump) {
  391. switch (recognizer.touchLocation) {
  392. case VLCSiriRemoteTouchLocationLeft:
  393. hint = VLCTransportBarHintJumpBackward10;
  394. break;
  395. case VLCSiriRemoteTouchLocationRight:
  396. hint = VLCTransportBarHintJumpForward10;
  397. break;
  398. default:
  399. hint = VLCTransportBarHintNone;
  400. break;
  401. }
  402. } else {
  403. hint = VLCTransportBarHintNone;
  404. }
  405. }
  406. break;
  407. case UIGestureRecognizerStateEnded:
  408. if (recognizer.isClick && !recognizer.isLongPress) {
  409. [self handleSiriPressUpAtLocation:recognizer.touchLocation];
  410. }
  411. [self setScanState:VLCPlayerScanStateNone];
  412. break;
  413. case UIGestureRecognizerStateCancelled:
  414. hint = VLCTransportBarHintNone;
  415. [self setScanState:VLCPlayerScanStateNone];
  416. break;
  417. default:
  418. break;
  419. }
  420. self.transportBar.hint = self.isSeekable ? hint : VLCPlayerScanStateNone;
  421. }
  422. - (void)handleSiriPressUpAtLocation:(VLCSiriRemoteTouchLocation)location
  423. {
  424. BOOL canJump = [self canJump];
  425. switch (location) {
  426. case VLCSiriRemoteTouchLocationLeft:
  427. if (canJump && self.isSeekable) {
  428. [self jumpBackward];
  429. }
  430. break;
  431. case VLCSiriRemoteTouchLocationRight:
  432. if (canJump && self.isSeekable) {
  433. [self jumpForward];
  434. }
  435. break;
  436. default:
  437. [self selectButtonPressed];
  438. break;
  439. }
  440. }
  441. #pragma mark -
  442. static const NSInteger VLCJumpInterval = 10000; // 10 seconds
  443. - (void)jumpForward
  444. {
  445. NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
  446. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  447. if (vpc.isPlaying) {
  448. [self jumpInterval:VLCJumpInterval];
  449. } else {
  450. [self scrubbingJumpInterval:VLCJumpInterval];
  451. }
  452. }
  453. - (void)jumpBackward
  454. {
  455. NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
  456. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  457. if (vpc.isPlaying) {
  458. [self jumpInterval:-VLCJumpInterval];
  459. } else {
  460. [self scrubbingJumpInterval:-VLCJumpInterval];
  461. }
  462. }
  463. - (void)jumpInterval:(NSInteger)interval
  464. {
  465. NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
  466. NSInteger duration = [VLCPlaybackController sharedInstance].mediaDuration;
  467. if (duration==0) {
  468. return;
  469. }
  470. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  471. CGFloat intervalFraction = ((CGFloat)interval)/((CGFloat)duration);
  472. CGFloat currentFraction = vpc.playbackPosition;
  473. currentFraction += intervalFraction;
  474. vpc.playbackPosition = currentFraction;
  475. }
  476. - (void)scrubbingJumpInterval:(NSInteger)interval
  477. {
  478. NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
  479. NSInteger duration = [VLCPlaybackController sharedInstance].mediaDuration;
  480. if (duration==0) {
  481. return;
  482. }
  483. CGFloat intervalFraction = ((CGFloat)interval)/((CGFloat)duration);
  484. VLCTransportBar *bar = self.transportBar;
  485. bar.scrubbing = YES;
  486. CGFloat currentFraction = bar.scrubbingFraction;
  487. currentFraction += intervalFraction;
  488. bar.scrubbingFraction = currentFraction;
  489. [self updateTimeLabelsForScrubbingFraction:currentFraction];
  490. }
  491. - (void)scanForwardNext
  492. {
  493. NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
  494. VLCPlayerScanState nextState = self.scanState;
  495. switch (self.scanState) {
  496. case VLCPlayerScanStateNone:
  497. nextState = VLCPlayerScanStateForward2;
  498. break;
  499. case VLCPlayerScanStateForward2:
  500. nextState = VLCPlayerScanStateForward4;
  501. break;
  502. case VLCPlayerScanStateForward4:
  503. return;
  504. default:
  505. return;
  506. }
  507. [self setScanState:nextState];
  508. }
  509. - (void)scanForwardPrevious
  510. {
  511. NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
  512. VLCPlayerScanState nextState = self.scanState;
  513. switch (self.scanState) {
  514. case VLCPlayerScanStateNone:
  515. return;
  516. case VLCPlayerScanStateForward2:
  517. nextState = VLCPlayerScanStateNone;
  518. break;
  519. case VLCPlayerScanStateForward4:
  520. nextState = VLCPlayerScanStateForward2;
  521. break;
  522. default:
  523. return;
  524. }
  525. [self setScanState:nextState];
  526. }
  527. - (void)setScanState:(VLCPlayerScanState)scanState
  528. {
  529. if (_scanState == scanState) {
  530. return;
  531. }
  532. NSAssert(self.isSeekable || scanState == VLCPlayerScanStateNone, @"Tried to seek while media not seekable.");
  533. if (_scanState == VLCPlayerScanStateNone) {
  534. self.scanSavedPlaybackRate = @([VLCPlaybackController sharedInstance].playbackRate);
  535. }
  536. _scanState = scanState;
  537. float rate = 1.0;
  538. VLCTransportBarHint hint = VLCTransportBarHintNone;
  539. switch (scanState) {
  540. case VLCPlayerScanStateForward2:
  541. rate = 2.0;
  542. hint = VLCTransportBarHintScanForward;
  543. break;
  544. case VLCPlayerScanStateForward4:
  545. rate = 4.0;
  546. hint = VLCTransportBarHintScanForward;
  547. break;
  548. case VLCPlayerScanStateNone:
  549. default:
  550. rate = self.scanSavedPlaybackRate.floatValue ?: 1.0;
  551. hint = VLCTransportBarHintNone;
  552. self.scanSavedPlaybackRate = nil;
  553. break;
  554. }
  555. [VLCPlaybackController sharedInstance].playbackRate = rate;
  556. [self.transportBar setHint:hint];
  557. }
  558. - (BOOL)isSeekable
  559. {
  560. return [[VLCPlaybackController sharedInstance] isSeekable];
  561. }
  562. - (BOOL)canJump
  563. {
  564. // to match the AVPlayerViewController behavior only allow jumping when playing.
  565. return [VLCPlaybackController sharedInstance].isPlaying;
  566. }
  567. - (BOOL)canScrub
  568. {
  569. // to match the AVPlayerViewController behavior only allow scrubbing when paused.
  570. return ![VLCPlaybackController sharedInstance].isPlaying;
  571. }
  572. #pragma mark -
  573. - (void)updateTimeLabelsForScrubbingFraction:(CGFloat)scrubbingFraction
  574. {
  575. VLCTransportBar *bar = self.transportBar;
  576. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  577. // MAX 1, _ is ugly hack to prevent --:-- instead of 00:00
  578. int scrubbingTimeInt = MAX(1,vpc.mediaDuration*scrubbingFraction);
  579. VLCTime *scrubbingTime = [VLCTime timeWithInt:scrubbingTimeInt];
  580. bar.markerTimeLabel.text = [scrubbingTime stringValue];
  581. VLCTime *remainingTime = [VLCTime timeWithInt:-(int)(vpc.mediaDuration-scrubbingTime.intValue)];
  582. bar.remainingTimeLabel.text = [remainingTime stringValue];
  583. }
  584. - (void)startScrubbing
  585. {
  586. NSAssert(self.isSeekable, @"Tried to seek while not media is not seekable.");
  587. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  588. self.transportBar.scrubbing = YES;
  589. [self updateDimmingView];
  590. if (vpc.isPlaying) {
  591. [vpc playPause];
  592. }
  593. }
  594. - (void)stopScrubbing
  595. {
  596. self.transportBar.scrubbing = NO;
  597. [self updateDimmingView];
  598. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  599. [vpc playPause];
  600. }
  601. - (void)updateDimmingView
  602. {
  603. BOOL shouldBeVisible = self.transportBar.scrubbing;
  604. BOOL isVisible = self.dimmingView.alpha == 1.0;
  605. if (shouldBeVisible != isVisible) {
  606. [UIView animateWithDuration:0.3 animations:^{
  607. self.dimmingView.alpha = shouldBeVisible ? 1.0 : 0.0;
  608. }];
  609. }
  610. }
  611. - (void)updateActivityIndicatorForState:(VLCMediaPlayerState)state {
  612. UIActivityIndicatorView *indicator = self.activityIndicator;
  613. switch (state) {
  614. case VLCMediaPlayerStateBuffering:
  615. if (!indicator.isAnimating) {
  616. self.activityIndicator.alpha = 1.0;
  617. [self.activityIndicator startAnimating];
  618. }
  619. break;
  620. default:
  621. if (indicator.isAnimating) {
  622. [self.activityIndicator stopAnimating];
  623. self.activityIndicator.alpha = 0.0;
  624. }
  625. break;
  626. }
  627. }
  628. #pragma mark - PlaybackControls
  629. - (void)fireHidePlaybackControlsIfNotPlayingTimer:(NSTimer *)timer
  630. {
  631. BOOL playing = [[VLCPlaybackController sharedInstance] isPlaying];
  632. if (playing) {
  633. [self animatePlaybackControlsToVisibility:NO];
  634. }
  635. }
  636. - (void)showPlaybackControlsIfNeededForUserInteraction
  637. {
  638. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  639. NSInteger currentTitle = [vpc indexOfCurrentTitle];
  640. if (currentTitle < [vpc numberOfTitles]) {
  641. NSDictionary *title = [vpc titleDescriptionsDictAtIndex:currentTitle];
  642. if ([[title objectForKey:VLCTitleDescriptionIsMenu] boolValue]) {
  643. return;
  644. }
  645. }
  646. if (self.bottomOverlayView.alpha == 0.0) {
  647. [self animatePlaybackControlsToVisibility:YES];
  648. // We need an additional update here because in some cases (e.g. when the playback was
  649. // paused or started buffering), the transport bar is only updated when it is visible
  650. // and if the playback is interrupted, no updates of the transport bar are triggered.
  651. [self updateTransportBarPosition];
  652. }
  653. [self hidePlaybackControlsIfNeededAfterDelay];
  654. }
  655. - (void)hidePlaybackControlsIfNeededAfterDelay
  656. {
  657. self.hidePlaybackControlsViewAfterDeleayTimer = [NSTimer scheduledTimerWithTimeInterval:3.0
  658. target:self
  659. selector:@selector(fireHidePlaybackControlsIfNotPlayingTimer:)
  660. userInfo:nil repeats:NO];
  661. }
  662. - (void)animatePlaybackControlsToVisibility:(BOOL)visible
  663. {
  664. NSTimeInterval duration = visible ? 0.3 : 1.0;
  665. CGFloat alpha = visible ? 1.0 : 0.0;
  666. [UIView animateWithDuration:duration
  667. animations:^{
  668. self.bottomOverlayView.alpha = alpha;
  669. }];
  670. }
  671. #pragma mark - Properties
  672. - (void)setHidePlaybackControlsViewAfterDeleayTimer:(NSTimer *)hidePlaybackControlsViewAfterDeleayTimer {
  673. [_hidePlaybackControlsViewAfterDeleayTimer invalidate];
  674. _hidePlaybackControlsViewAfterDeleayTimer = hidePlaybackControlsViewAfterDeleayTimer;
  675. }
  676. - (VLCPlaybackInfoTVViewController *)infoViewController
  677. {
  678. if (!_infoViewController) {
  679. _infoViewController = [[VLCPlaybackInfoTVViewController alloc] initWithNibName:nil bundle:nil];
  680. }
  681. return _infoViewController;
  682. }
  683. #pragma mark - playback controller delegation
  684. - (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
  685. {
  686. self.audioView.hidden = YES;
  687. }
  688. - (void)playbackDidStop
  689. {
  690. [self dismissViewControllerAnimated:YES completion:nil];
  691. }
  692. - (void)mediaPlayerStateChanged:(VLCMediaPlayerState)currentState
  693. isPlaying:(BOOL)isPlaying
  694. currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
  695. currentMediaHasChapters:(BOOL)currentMediaHasChapters
  696. forPlaybackController:(VLCPlaybackController *)controller
  697. {
  698. [self updateActivityIndicatorForState:currentState];
  699. if (controller.isPlaying) {
  700. // we sometimes don't set the vout correctly if playback stops and restarts without dismising and redisplaying the VC
  701. // hence, manually reset the vout container here if it doesn't have sufficient children
  702. if (self.movieView.subviews.count < 2) {
  703. controller.videoOutputView = self.movieView;
  704. }
  705. if (!self.bufferingLabel.hidden) {
  706. [UIView animateWithDuration:.3 animations:^{
  707. self.bufferingLabel.hidden = YES;
  708. }];
  709. }
  710. }
  711. }
  712. - (void)displayMetadataForPlaybackController:(VLCPlaybackController *)controller
  713. title:(NSString *)title
  714. artwork:(UIImage *)artwork
  715. artist:(NSString *)artist
  716. album:(NSString *)album
  717. audioOnly:(BOOL)audioOnly
  718. {
  719. self.titleLabel.text = title;
  720. if (audioOnly) {
  721. self.audioArtworkImageView.image = nil;
  722. self.audioDescriptionTextView.hidden = YES;
  723. [self stopAudioDescriptionAnimation];
  724. if (artist != nil && album != nil) {
  725. [UIView animateWithDuration:.3 animations:^{
  726. self.audioArtistLabel.text = artist;
  727. self.audioArtistLabel.hidden = NO;
  728. self.audioAlbumNameLabel.text = album;
  729. self.audioAlbumNameLabel.hidden = NO;
  730. }];
  731. APLog(@"Audio-only track meta changed, tracing artist '%@' and album '%@'", artist, album);
  732. } else if (artist != nil) {
  733. [UIView animateWithDuration:.3 animations:^{
  734. self.audioArtistLabel.text = artist;
  735. self.audioArtistLabel.hidden = NO;
  736. self.audioAlbumNameLabel.hidden = YES;
  737. }];
  738. APLog(@"Audio-only track meta changed, tracing artist '%@'", artist);
  739. } else if (title != nil) {
  740. NSRange deviderRange = [title rangeOfString:@" - "];
  741. if (deviderRange.length != 0) { // for radio stations, all we have is "ARTIST - TITLE"
  742. artist = [title substringToIndex:deviderRange.location];
  743. title = [title substringFromIndex:deviderRange.location + deviderRange.length];
  744. }
  745. APLog(@"Audio-only track meta changed, tracing artist '%@'", artist);
  746. [UIView animateWithDuration:.3 animations:^{
  747. self.audioArtistLabel.text = artist;
  748. self.audioArtistLabel.hidden = NO;
  749. self.audioAlbumNameLabel.hidden = YES;
  750. }];
  751. }
  752. if (![self.lastArtist isEqualToString:artist]) {
  753. UIImage *dummyImage = [UIImage imageNamed:@"about-app-icon"];
  754. [UIView animateWithDuration:.3 animations:^{
  755. self.audioArtworkImageView.image = dummyImage;
  756. self.audioLargeBackgroundImageView.image = dummyImage;
  757. }];
  758. }
  759. self.lastArtist = artist;
  760. self.audioTitleLabel.text = title;
  761. self.audioTitleLabel.hidden = NO;
  762. [UIView animateWithDuration:0.3 animations:^{
  763. self.audioView.hidden = NO;
  764. }];
  765. } else if (!self.audioView.hidden) {
  766. self.audioView.hidden = YES;
  767. self.audioArtworkImageView.image = nil;
  768. [self.audioLargeBackgroundImageView stopAnimating];
  769. }
  770. }
  771. #pragma mark -
  772. - (void)updateTransportBarPosition
  773. {
  774. VLCPlaybackController *controller = [VLCPlaybackController sharedInstance];
  775. VLCTransportBar *transportBar = self.transportBar;
  776. transportBar.remainingTimeLabel.text = [[controller remainingTime] stringValue];
  777. transportBar.markerTimeLabel.text = [[controller playedTime] stringValue];
  778. transportBar.playbackFraction = controller.playbackPosition;
  779. }
  780. - (void)playbackPositionUpdated:(VLCPlaybackController *)controller
  781. {
  782. // FIXME: hard coded state since the state in mediaPlayer is incorrectly still buffering
  783. [self updateActivityIndicatorForState:VLCMediaPlayerStatePlaying];
  784. if (self.bottomOverlayView.alpha != 0.0) {
  785. [self updateTransportBarPosition];
  786. }
  787. }
  788. #pragma mark - gesture recognizer delegate
  789. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
  790. if ([gestureRecognizer.allowedPressTypes containsObject:@(UIPressTypeMenu)]) {
  791. return self.transportBar.scrubbing;
  792. }
  793. return YES;
  794. }
  795. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
  796. {
  797. return [self.simultaneousGestureRecognizers containsObject:gestureRecognizer];
  798. }
  799. - (void)scrollAudioDescriptionAnimationToTop
  800. {
  801. [self stopAudioDescriptionAnimation];
  802. [self.audioDescriptionTextView setContentOffset:CGPointZero animated:YES];
  803. [self startAudioDescriptionAnimation];
  804. }
  805. - (void)startAudioDescriptionAnimation
  806. {
  807. [self.audioDescriptionScrollTimer invalidate];
  808. self.audioDescriptionScrollTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
  809. target:self
  810. selector:@selector(animateAudioDescription)
  811. userInfo:nil repeats:NO];
  812. }
  813. - (void)stopAudioDescriptionAnimation
  814. {
  815. [self.audioDescriptionScrollTimer invalidate];
  816. self.audioDescriptionScrollTimer = nil;
  817. [self.displayLink invalidate];
  818. self.displayLink = nil;
  819. }
  820. - (void)animateAudioDescription
  821. {
  822. [self.displayLink invalidate];
  823. self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTriggered:)];
  824. [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
  825. }
  826. - (void)displayLinkTriggered:(CADisplayLink*)link
  827. {
  828. UIScrollView *scrollView = self.audioDescriptionTextView;
  829. CGFloat viewHeight = CGRectGetHeight(scrollView.frame);
  830. CGFloat maxOffsetY = scrollView.contentSize.height - viewHeight;
  831. CFTimeInterval secondsPerPage = 8.0;
  832. CGFloat offset = link.duration/secondsPerPage * viewHeight;
  833. CGFloat newYOffset = scrollView.contentOffset.y + offset;
  834. if (newYOffset > maxOffsetY+viewHeight) {
  835. scrollView.contentOffset = CGPointMake(0, -viewHeight);
  836. } else {
  837. scrollView.contentOffset = CGPointMake(0, newYOffset);
  838. }
  839. }
  840. @end
  841. @implementation VLCFullscreenMovieTVViewController (UIViewControllerTransitioningDelegate)
  842. - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
  843. {
  844. return [[VLCPlaybackInfoTVTransitioningAnimator alloc] init];
  845. }
  846. - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
  847. {
  848. return [[VLCPlaybackInfoTVTransitioningAnimator alloc] init];
  849. }
  850. @end