VLCFullscreenMovieTVViewController.m 34 KB

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