VLCFullscreenMovieTVViewController.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  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 "JSAnimatedImagesView.h"
  18. #import "MetaDataFetcherKit.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 () <JSAnimatedImagesViewDataSource, MDFHatchetFetcherDataRecipient>
  28. @property (nonatomic) NSTimer *hidePlaybackControlsViewAfterDeleayTimer;
  29. @property (nonatomic) VLCPlaybackInfoTVViewController *infoViewController;
  30. @property (nonatomic) NSNumber *scanSavedPlaybackRate;
  31. @property (nonatomic) VLCPlayerScanState scanState;
  32. @property (nonatomic) JSAnimatedImagesView *animatedImageView;
  33. @property (nonatomic) MDFHatchetFetcher *audioMetaDataFetcher;
  34. @property (nonatomic) NSString *lastArtist;
  35. @property (nonatomic) NSMutableArray *audioImagesArray;
  36. @end
  37. @implementation VLCFullscreenMovieTVViewController
  38. + (instancetype)fullscreenMovieTVViewController
  39. {
  40. return [[self alloc] initWithNibName:nil bundle:nil];
  41. }
  42. - (void)viewDidLoad
  43. {
  44. [super viewDidLoad];
  45. self.extendedLayoutIncludesOpaqueBars = YES;
  46. self.edgesForExtendedLayout = UIRectEdgeAll;
  47. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  48. [center addObserver:self
  49. selector:@selector(playbackDidStop:)
  50. name:VLCPlaybackControllerPlaybackDidStop
  51. object:nil];
  52. _movieView.userInteractionEnabled = NO;
  53. self.titleLabel.text = @"";
  54. self.transportBar.bufferStartFraction = 0.0;
  55. self.transportBar.bufferEndFraction = 1.0;
  56. self.transportBar.playbackFraction = 0.0;
  57. self.transportBar.scrubbingFraction = 0.0;
  58. self.dimmingView.alpha = 0.0;
  59. self.bottomOverlayView.alpha = 0.0;
  60. self.bufferingLabel.text = NSLocalizedString(@"PLEASE_WAIT", nil);
  61. // Panning and Swiping
  62. UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
  63. panGestureRecognizer.delegate = self;
  64. [self.view addGestureRecognizer:panGestureRecognizer];
  65. // Button presses
  66. UITapGestureRecognizer *playpauseGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playPausePressed)];
  67. playpauseGesture.allowedPressTypes = @[@(UIPressTypePlayPause)];
  68. [self.view addGestureRecognizer:playpauseGesture];
  69. UITapGestureRecognizer *menuTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuButtonPressed:)];
  70. menuTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
  71. menuTapGestureRecognizer.delegate = self;
  72. [self.view addGestureRecognizer:menuTapGestureRecognizer];
  73. // IR only recognizer
  74. UITapGestureRecognizer *downArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(showInfoVCIfNotScrubbing)];
  75. downArrowRecognizer.allowedPressTypes = @[@(UIPressTypeDownArrow)];
  76. [self.view addGestureRecognizer:downArrowRecognizer];
  77. UITapGestureRecognizer *leftArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIRPressLeft)];
  78. leftArrowRecognizer.allowedPressTypes = @[@(UIPressTypeLeftArrow)];
  79. [self.view addGestureRecognizer:leftArrowRecognizer];
  80. UITapGestureRecognizer *rightArrowRecognizer = [[VLCIRTVTapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIRPressRight)];
  81. rightArrowRecognizer.allowedPressTypes = @[@(UIPressTypeRightArrow)];
  82. [self.view addGestureRecognizer:rightArrowRecognizer];
  83. // Siri remote arrow presses
  84. VLCSiriRemoteGestureRecognizer *siriArrowRecognizer = [[VLCSiriRemoteGestureRecognizer alloc] initWithTarget:self action:@selector(handleSiriRemote:)];
  85. siriArrowRecognizer.delegate = self;
  86. [self.view addGestureRecognizer:siriArrowRecognizer];
  87. self.animatedImageView = [[JSAnimatedImagesView alloc] initWithFrame:self.view.frame];
  88. self.animatedImageView.dataSource = self;
  89. self.animatedImageView.backgroundColor = [UIColor blackColor];
  90. }
  91. - (void)didReceiveMemoryWarning
  92. {
  93. [super didReceiveMemoryWarning];
  94. self.infoViewController = nil;
  95. }
  96. #pragma mark - view events
  97. - (void)viewWillAppear:(BOOL)animated
  98. {
  99. [super viewWillAppear:animated];
  100. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  101. vpc.delegate = self;
  102. [vpc recoverPlaybackState];
  103. }
  104. - (void)viewDidAppear:(BOOL)animated
  105. {
  106. [super viewDidAppear:animated];
  107. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  108. [vpc recoverDisplayedMetadata];
  109. vpc.videoOutputView = nil;
  110. vpc.videoOutputView = self.movieView;
  111. [[NSNotificationCenter defaultCenter] removeObserver:self];
  112. }
  113. - (void)viewWillDisappear:(BOOL)animated
  114. {
  115. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  116. if (vpc.videoOutputView == self.movieView) {
  117. vpc.videoOutputView = nil;
  118. }
  119. [vpc stopPlayback];
  120. [super viewWillDisappear:animated];
  121. /* clean caches in case remote playback was used
  122. * note that if we cancel before the upload is complete
  123. * the cache won't be emptied, but on the next launch only (or if the system is under storage pressure)
  124. */
  125. [[VLCHTTPUploaderController sharedInstance] cleanCache];
  126. }
  127. - (BOOL)canBecomeFirstResponder
  128. {
  129. return YES;
  130. }
  131. #pragma mark - UIActions
  132. - (void)playPausePressed
  133. {
  134. [self showPlaybackControlsIfNeededForUserInteraction];
  135. [self setScanState:VLCPlayerScanStateNone];
  136. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  137. if (self.transportBar.scrubbing) {
  138. [self selectButtonPressed];
  139. } else {
  140. [vpc playPause];
  141. }
  142. }
  143. - (void)panGesture:(UIPanGestureRecognizer *)panGestureRecognizer
  144. {
  145. switch (panGestureRecognizer.state) {
  146. case UIGestureRecognizerStateCancelled:
  147. case UIGestureRecognizerStateFailed:
  148. return;
  149. default:
  150. break;
  151. }
  152. VLCTransportBar *bar = self.transportBar;
  153. UIView *view = self.view;
  154. CGPoint translation = [panGestureRecognizer translationInView:view];
  155. if (!bar.scrubbing) {
  156. if (ABS(translation.x) > 150.0) {
  157. [self startScrubbing];
  158. } else if (translation.y > 200.0) {
  159. panGestureRecognizer.enabled = NO;
  160. panGestureRecognizer.enabled = YES;
  161. [self showInfoVCIfNotScrubbing];
  162. return;
  163. } else {
  164. return;
  165. }
  166. }
  167. [self showPlaybackControlsIfNeededForUserInteraction];
  168. [self setScanState:VLCPlayerScanStateNone];
  169. const CGFloat scaleFactor = 8.0;
  170. CGFloat fractionInView = translation.x/CGRectGetWidth(view.bounds)/scaleFactor;
  171. CGFloat scrubbingFraction = MAX(0.0, MIN(bar.scrubbingFraction + fractionInView,1.0));
  172. if (ABS(scrubbingFraction - bar.playbackFraction)<0.005) {
  173. scrubbingFraction = bar.playbackFraction;
  174. } else {
  175. translation.x = 0.0;
  176. [panGestureRecognizer setTranslation:translation inView:view];
  177. }
  178. [UIView animateWithDuration:0.3
  179. delay:0.0
  180. options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState
  181. animations:^{
  182. bar.scrubbingFraction = scrubbingFraction;
  183. }
  184. completion:nil];
  185. [self updateTimeLabelsForScrubbingFraction:scrubbingFraction];
  186. }
  187. - (void)selectButtonPressed
  188. {
  189. [self showPlaybackControlsIfNeededForUserInteraction];
  190. [self setScanState:VLCPlayerScanStateNone];
  191. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  192. VLCTransportBar *bar = self.transportBar;
  193. if (bar.scrubbing) {
  194. bar.playbackFraction = bar.scrubbingFraction;
  195. [vpc.mediaPlayer setPosition:bar.scrubbingFraction];
  196. [self stopScrubbing];
  197. } else if(vpc.mediaPlayer.playing) {
  198. [vpc.mediaPlayer pause];
  199. }
  200. }
  201. - (void)menuButtonPressed:(UITapGestureRecognizer *)recognizer
  202. {
  203. VLCTransportBar *bar = self.transportBar;
  204. if (bar.scrubbing) {
  205. [UIView animateWithDuration:0.3 animations:^{
  206. bar.scrubbingFraction = bar.playbackFraction;
  207. [bar layoutIfNeeded];
  208. }];
  209. [self updateTimeLabelsForScrubbingFraction:bar.playbackFraction];
  210. [self stopScrubbing];
  211. [self hidePlaybackControlsIfNeededAfterDelay];
  212. }
  213. }
  214. - (void)showInfoVCIfNotScrubbing
  215. {
  216. if (self.transportBar.scrubbing) {
  217. return;
  218. }
  219. // TODO: configure with player info
  220. VLCPlaybackInfoTVViewController *infoViewController = self.infoViewController;
  221. infoViewController.transitioningDelegate = self;
  222. [self presentViewController:infoViewController animated:YES completion:nil];
  223. [self animatePlaybackControlsToVisibility:NO];
  224. }
  225. - (void)handleIRPressLeft
  226. {
  227. [self showPlaybackControlsIfNeededForUserInteraction];
  228. BOOL paused = ![VLCPlaybackController sharedInstance].isPlaying;
  229. if (paused) {
  230. [self jumpBackward];
  231. } else
  232. {
  233. [self scanForwardPrevious];
  234. }
  235. }
  236. - (void)handleIRPressRight
  237. {
  238. [self showPlaybackControlsIfNeededForUserInteraction];
  239. BOOL paused = ![VLCPlaybackController sharedInstance].isPlaying;
  240. if (paused) {
  241. [self jumpForward];
  242. } else {
  243. [self scanForwardNext];
  244. }
  245. }
  246. - (void)handleSiriRemote:(VLCSiriRemoteGestureRecognizer *)recognizer
  247. {
  248. [self showPlaybackControlsIfNeededForUserInteraction];
  249. VLCTransportBarHint hint = self.transportBar.hint;
  250. switch (recognizer.state) {
  251. case UIGestureRecognizerStateBegan:
  252. case UIGestureRecognizerStateChanged:
  253. if (recognizer.isLongPress) {
  254. if (recognizer.touchLocation == VLCSiriRemoteTouchLocationRight) {
  255. [self setScanState:VLCPlayerScanStateForward2];
  256. return;
  257. }
  258. } else {
  259. switch (recognizer.touchLocation) {
  260. case VLCSiriRemoteTouchLocationLeft:
  261. hint = VLCTransportBarHintJumpBackward10;
  262. break;
  263. case VLCSiriRemoteTouchLocationRight:
  264. hint = VLCTransportBarHintJumpForward10;
  265. break;
  266. default:
  267. hint = VLCTransportBarHintNone;
  268. break;
  269. }
  270. }
  271. break;
  272. case UIGestureRecognizerStateEnded:
  273. if (recognizer.isClick && !recognizer.isLongPress) {
  274. [self handleSiriPressUpAtLocation:recognizer.touchLocation];
  275. }
  276. [self setScanState:VLCPlayerScanStateNone];
  277. break;
  278. case UIGestureRecognizerStateCancelled:
  279. hint = VLCTransportBarHintNone;
  280. [self setScanState:VLCPlayerScanStateNone];
  281. break;
  282. default:
  283. break;
  284. }
  285. self.transportBar.hint = hint;
  286. }
  287. - (void)handleSiriPressUpAtLocation:(VLCSiriRemoteTouchLocation)location
  288. {
  289. switch (location) {
  290. case VLCSiriRemoteTouchLocationLeft:
  291. [self jumpBackward];
  292. break;
  293. case VLCSiriRemoteTouchLocationRight:
  294. [self jumpForward];
  295. break;
  296. default:
  297. [self selectButtonPressed];
  298. break;
  299. }
  300. }
  301. #pragma mark -
  302. static const NSInteger VLCJumpInterval = 10000; // 10 seconds
  303. - (void)jumpForward
  304. {
  305. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  306. VLCMediaPlayer *player = vpc.mediaPlayer;
  307. if (player.isPlaying) {
  308. [player jumpForward:VLCJumpInterval];
  309. } else {
  310. [self scrubbingJumpInterval:VLCJumpInterval];
  311. }
  312. }
  313. - (void)jumpBackward
  314. {
  315. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  316. VLCMediaPlayer *player = vpc.mediaPlayer;
  317. if (player.isPlaying) {
  318. [player jumpBackward:VLCJumpInterval];
  319. } else {
  320. [self scrubbingJumpInterval:-VLCJumpInterval];
  321. }
  322. }
  323. - (void)scrubbingJumpInterval:(NSInteger)interval
  324. {
  325. NSInteger duration = [VLCPlaybackController sharedInstance].mediaDuration;
  326. if (duration==0) {
  327. return;
  328. }
  329. CGFloat intervalFraction = ((CGFloat)interval)/((CGFloat)duration);
  330. VLCTransportBar *bar = self.transportBar;
  331. bar.scrubbing = YES;
  332. CGFloat currentFraction = bar.scrubbingFraction;
  333. currentFraction += intervalFraction;
  334. bar.scrubbingFraction = currentFraction;
  335. [self updateTimeLabelsForScrubbingFraction:currentFraction];
  336. }
  337. - (void)scanForwardNext
  338. {
  339. VLCPlayerScanState nextState = self.scanState;
  340. switch (self.scanState) {
  341. case VLCPlayerScanStateNone:
  342. nextState = VLCPlayerScanStateForward2;
  343. break;
  344. case VLCPlayerScanStateForward2:
  345. nextState = VLCPlayerScanStateForward4;
  346. break;
  347. case VLCPlayerScanStateForward4:
  348. return;
  349. default:
  350. return;
  351. }
  352. [self setScanState:nextState];
  353. }
  354. - (void)scanForwardPrevious
  355. {
  356. VLCPlayerScanState nextState = self.scanState;
  357. switch (self.scanState) {
  358. case VLCPlayerScanStateNone:
  359. return;
  360. case VLCPlayerScanStateForward2:
  361. nextState = VLCPlayerScanStateNone;
  362. break;
  363. case VLCPlayerScanStateForward4:
  364. nextState = VLCPlayerScanStateForward2;
  365. break;
  366. default:
  367. return;
  368. }
  369. [self setScanState:nextState];
  370. }
  371. - (void)setScanState:(VLCPlayerScanState)scanState
  372. {
  373. if (_scanState == scanState) {
  374. return;
  375. }
  376. if (_scanState == VLCPlayerScanStateNone) {
  377. self.scanSavedPlaybackRate = @([VLCPlaybackController sharedInstance].playbackRate);
  378. }
  379. _scanState = scanState;
  380. float rate = 1.0;
  381. VLCTransportBarHint hint = VLCTransportBarHintNone;
  382. switch (scanState) {
  383. case VLCPlayerScanStateForward2:
  384. rate = 2.0;
  385. hint = VLCTransportBarHintScanForward;
  386. break;
  387. case VLCPlayerScanStateForward4:
  388. rate = 4.0;
  389. hint = VLCTransportBarHintScanForward;
  390. break;
  391. case VLCPlayerScanStateNone:
  392. default:
  393. rate = self.scanSavedPlaybackRate.floatValue ?: 1.0;
  394. hint = VLCTransportBarHintNone;
  395. self.scanSavedPlaybackRate = nil;
  396. break;
  397. }
  398. [VLCPlaybackController sharedInstance].playbackRate = rate;
  399. [self.transportBar setHint:hint];
  400. }
  401. #pragma mark -
  402. - (void)updateTimeLabelsForScrubbingFraction:(CGFloat)scrubbingFraction
  403. {
  404. VLCTransportBar *bar = self.transportBar;
  405. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  406. // MAX 1, _ is ugly hack to prevent --:-- instead of 00:00
  407. int scrubbingTimeInt = MAX(1,vpc.mediaDuration*scrubbingFraction);
  408. VLCTime *scrubbingTime = [VLCTime timeWithInt:scrubbingTimeInt];
  409. bar.markerTimeLabel.text = [scrubbingTime stringValue];
  410. VLCTime *remainingTime = [VLCTime timeWithInt:-(int)(vpc.mediaDuration-scrubbingTime.intValue)];
  411. bar.remainingTimeLabel.text = [remainingTime stringValue];
  412. }
  413. - (void)startScrubbing
  414. {
  415. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  416. self.transportBar.scrubbing = YES;
  417. [self updateDimmingView];
  418. if (vpc.isPlaying) {
  419. [vpc playPause];
  420. }
  421. }
  422. - (void)stopScrubbing
  423. {
  424. self.transportBar.scrubbing = NO;
  425. [self updateDimmingView];
  426. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  427. [vpc.mediaPlayer play];
  428. }
  429. - (void)updateDimmingView
  430. {
  431. BOOL shouldBeVisible = self.transportBar.scrubbing;
  432. BOOL isVisible = self.dimmingView.alpha == 1.0;
  433. if (shouldBeVisible != isVisible) {
  434. [UIView animateWithDuration:0.3 animations:^{
  435. self.dimmingView.alpha = shouldBeVisible ? 1.0 : 0.0;
  436. }];
  437. }
  438. }
  439. - (void)updateActivityIndicatorForState:(VLCMediaPlayerState)state {
  440. UIActivityIndicatorView *indicator = self.activityIndicator;
  441. switch (state) {
  442. case VLCMediaPlayerStateBuffering:
  443. if (!indicator.isAnimating) {
  444. self.activityIndicator.alpha = 1.0;
  445. [self.activityIndicator startAnimating];
  446. }
  447. break;
  448. default:
  449. if (indicator.isAnimating) {
  450. [self.activityIndicator stopAnimating];
  451. self.activityIndicator.alpha = 0.0;
  452. }
  453. break;
  454. }
  455. }
  456. #pragma mark - PlaybackControls
  457. - (void)fireHidePlaybackControlsIfNotPlayingTimer:(NSTimer *)timer
  458. {
  459. BOOL playing = [[VLCPlaybackController sharedInstance] isPlaying];
  460. if (playing) {
  461. [self animatePlaybackControlsToVisibility:NO];
  462. }
  463. }
  464. - (void)showPlaybackControlsIfNeededForUserInteraction
  465. {
  466. if (self.bottomOverlayView.alpha == 0.0) {
  467. [self animatePlaybackControlsToVisibility:YES];
  468. }
  469. [self hidePlaybackControlsIfNeededAfterDelay];
  470. }
  471. - (void)hidePlaybackControlsIfNeededAfterDelay
  472. {
  473. self.hidePlaybackControlsViewAfterDeleayTimer = [NSTimer scheduledTimerWithTimeInterval:3.0
  474. target:self
  475. selector:@selector(fireHidePlaybackControlsIfNotPlayingTimer:)
  476. userInfo:nil repeats:NO];
  477. }
  478. - (void)animatePlaybackControlsToVisibility:(BOOL)visible
  479. {
  480. NSTimeInterval duration = visible ? 0.3 : 1.0;
  481. CGFloat alpha = visible ? 1.0 : 0.0;
  482. [UIView animateWithDuration:duration
  483. animations:^{
  484. self.bottomOverlayView.alpha = alpha;
  485. }];
  486. }
  487. #pragma mark - Properties
  488. - (void)setHidePlaybackControlsViewAfterDeleayTimer:(NSTimer *)hidePlaybackControlsViewAfterDeleayTimer {
  489. [_hidePlaybackControlsViewAfterDeleayTimer invalidate];
  490. _hidePlaybackControlsViewAfterDeleayTimer = hidePlaybackControlsViewAfterDeleayTimer;
  491. }
  492. - (VLCPlaybackInfoTVViewController *)infoViewController
  493. {
  494. if (!_infoViewController) {
  495. _infoViewController = [[VLCPlaybackInfoTVViewController alloc] initWithNibName:nil bundle:nil];
  496. }
  497. return _infoViewController;
  498. }
  499. #pragma mark - playback controller delegation
  500. - (void)prepareForMediaPlayback:(VLCPlaybackController *)controller
  501. {
  502. APLog(@"%s", __PRETTY_FUNCTION__);
  503. }
  504. - (void)playbackDidStop:(NSNotification *)aNotification
  505. {
  506. [self dismissViewControllerAnimated:YES completion:nil];
  507. }
  508. - (void)mediaPlayerStateChanged:(VLCMediaPlayerState)currentState
  509. isPlaying:(BOOL)isPlaying
  510. currentMediaHasTrackToChooseFrom:(BOOL)currentMediaHasTrackToChooseFrom
  511. currentMediaHasChapters:(BOOL)currentMediaHasChapters
  512. forPlaybackController:(VLCPlaybackController *)controller
  513. {
  514. [self updateActivityIndicatorForState:currentState];
  515. if (controller.isPlaying) {
  516. [self hidePlaybackControlsIfNeededAfterDelay];
  517. } else {
  518. [self showPlaybackControlsIfNeededForUserInteraction];
  519. }
  520. if (controller.isPlaying && !self.bufferingLabel.hidden) {
  521. [UIView animateWithDuration:.3 animations:^{
  522. self.bufferingLabel.hidden = YES;
  523. }];
  524. }
  525. }
  526. - (void)displayMetadataForPlaybackController:(VLCPlaybackController *)controller
  527. title:(NSString *)title
  528. artwork:(UIImage *)artwork
  529. artist:(NSString *)artist
  530. album:(NSString *)album
  531. audioOnly:(BOOL)audioOnly
  532. {
  533. self.titleLabel.text = title;
  534. if (audioOnly) {
  535. if (!self.audioImagesArray)
  536. self.audioImagesArray = [NSMutableArray array];
  537. if (!self.audioMetaDataFetcher) {
  538. self.audioMetaDataFetcher = [[MDFHatchetFetcher alloc] init];
  539. self.audioMetaDataFetcher.dataRecipient = self;
  540. }
  541. [self.audioMetaDataFetcher cancelAllRequests];
  542. if (artist != nil && album != nil) {
  543. [self.audioMetaDataFetcher searchForAlbum:album ofArtist:artist];
  544. APLog(@"Audio-only track meta changed, tracing artist '%@' and album '%@'", artist, album);
  545. } else if (artist != nil) {
  546. [self.audioMetaDataFetcher searchForArtist:artist];
  547. APLog(@"Audio-only track meta changed, tracing artist '%@'", artist);
  548. } else if (title != nil) {
  549. NSRange deviderRange = [title rangeOfString:@" - "];
  550. if (deviderRange.length != 0) { // for radio stations, all we have is "ARTIST - TITLE"
  551. title = [title substringToIndex:deviderRange.location];
  552. }
  553. APLog(@"Audio-only track meta changed, tracing artist '%@'", title);
  554. [self.audioMetaDataFetcher searchForArtist:title];
  555. }
  556. [self performSelectorOnMainThread:@selector(showAnimatedImagesView) withObject:nil waitUntilDone:NO];
  557. } else if (self.animatedImageView.superview != nil) {
  558. [self.audioMetaDataFetcher cancelAllRequests];
  559. [self.animatedImageView stopAnimating];
  560. [self.animatedImageView removeFromSuperview];
  561. if (self.audioImagesArray) {
  562. [self.audioImagesArray removeAllObjects];
  563. }
  564. }
  565. }
  566. #pragma mark -
  567. - (void)playbackPositionUpdated:(VLCPlaybackController *)controller
  568. {
  569. VLCMediaPlayer *mediaPlayer = controller.mediaPlayer;
  570. // FIXME: hard coded state since the state in mediaPlayer is incorrectly still buffering
  571. [self updateActivityIndicatorForState:VLCMediaPlayerStatePlaying];
  572. VLCTransportBar *transportBar = self.transportBar;
  573. transportBar.remainingTimeLabel.text = [[mediaPlayer remainingTime] stringValue];
  574. transportBar.markerTimeLabel.text = [[mediaPlayer time] stringValue];
  575. transportBar.playbackFraction = mediaPlayer.position;
  576. }
  577. #pragma mark - gesture recognizer delegate
  578. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
  579. if ([gestureRecognizer.allowedPressTypes containsObject:@(UIPressTypeMenu)]) {
  580. return self.transportBar.scrubbing;
  581. }
  582. return YES;
  583. }
  584. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
  585. {
  586. return YES;
  587. }
  588. #pragma mark - slide show view data source
  589. - (NSUInteger)animatedImagesNumberOfImages:(JSAnimatedImagesView *)animatedImagesView
  590. {
  591. NSUInteger retValue;
  592. @synchronized(self.audioImagesArray) {
  593. retValue = self.audioImagesArray.count;
  594. }
  595. return retValue;
  596. }
  597. - (UIImage *)animatedImagesView:(JSAnimatedImagesView *)animatedImagesView imageAtIndex:(NSUInteger)index
  598. {
  599. UIImage *retImage;
  600. @synchronized(self.audioImagesArray) {
  601. if (index < self.audioImagesArray.count) {
  602. retImage = self.audioImagesArray[index];
  603. }
  604. }
  605. return retImage;
  606. }
  607. - (void)showAnimatedImagesView
  608. {
  609. NSUInteger imageCount;
  610. @synchronized(self.audioImagesArray) {
  611. imageCount = self.audioImagesArray.count;
  612. }
  613. if (self.animatedImageView.superview == nil && imageCount > 1) {
  614. [self.animatedImageView reloadData];
  615. self.animatedImageView.frame = self.view.frame;
  616. [self.view addSubview:self.animatedImageView];
  617. } else if (imageCount > 1) {
  618. [self.animatedImageView reloadData];
  619. [self.animatedImageView startAnimating];
  620. }
  621. }
  622. #pragma mark - meta data recipient
  623. - (void)MDFHatchetFetcher:(MDFHatchetFetcher *)aFetcher didFindAlbum:(MDFMusicAlbum *)album forArtistName:(NSString *)artistName
  624. {
  625. if (![self.lastArtist isEqualToString:artistName]) {
  626. @synchronized(self.audioImagesArray) {
  627. [self.animatedImageView stopAnimating];
  628. [self.audioImagesArray removeAllObjects];
  629. }
  630. }
  631. self.lastArtist = artistName;
  632. NSString *artworkImageURLString = album.artworkImage;
  633. if (artworkImageURLString != nil)
  634. [self fetchAudioImage:[NSURL URLWithString:artworkImageURLString]];
  635. NSArray *imageURLStrings = album.largeSizedArtistImages;
  636. NSUInteger imageCount = imageURLStrings.count;
  637. /* reasonably limit the number of images we fetch */
  638. if (imageCount > 10)
  639. imageCount = 10;
  640. for (NSUInteger x = 0; x < imageCount; x++)
  641. [self fetchAudioImage:[NSURL URLWithString:imageURLStrings[x]]];
  642. /* when we only have 1 HD image, duplicate it */
  643. if (imageCount == 1) {
  644. [self fetchAudioImage:[NSURL URLWithString:[imageURLStrings firstObject]]];
  645. }
  646. /* if we have too few HD pictures, try to add some medium quality ones */
  647. if (imageCount < 4) {
  648. imageURLStrings = album.mediumSizedArtistImages;
  649. imageCount = imageURLStrings.count;
  650. if (imageCount > 10)
  651. imageCount = 10;
  652. for (NSUInteger x = 0; x < imageCount; x++)
  653. [self fetchAudioImage:[NSURL URLWithString:imageURLStrings[x]]];
  654. }
  655. }
  656. - (void)MDFHatchetFetcher:(MDFHatchetFetcher *)aFetcher didFailToFindAlbum:(NSString *)albumName forArtistName:(NSString *)artistName
  657. {
  658. APLog(@"%s: %@ %@", __PRETTY_FUNCTION__, artistName, albumName);
  659. }
  660. - (void)MDFHatchetFetcher:(MDFHatchetFetcher *)aFetcher didFindArtist:(MDFArtist *)artist forSearchRequest:(NSString *)searchRequest
  661. {
  662. if (![self.lastArtist isEqualToString:searchRequest]) {
  663. @synchronized(self.audioImagesArray) {
  664. [self.animatedImageView stopAnimating];
  665. [self.audioImagesArray removeAllObjects];
  666. }
  667. }
  668. self.lastArtist = searchRequest;
  669. NSArray *imageURLStrings = artist.largeSizedImages;
  670. NSUInteger imageCount = imageURLStrings.count;
  671. /* reasonably limit the number of images we fetch */
  672. if (imageCount > 10)
  673. imageCount = 10;
  674. for (NSUInteger x = 0; x < imageCount; x++)
  675. [self fetchAudioImage:[NSURL URLWithString:imageURLStrings[x]]];
  676. /* when we only have 1 HD image, duplicate it */
  677. if (imageCount == 1) {
  678. [self fetchAudioImage:[NSURL URLWithString:[imageURLStrings firstObject]]];
  679. }
  680. /* if we have too few HD pictures, try to add some medium quality ones */
  681. if (imageCount < 4) {
  682. imageURLStrings = artist.mediumSizedImages;
  683. imageCount = imageURLStrings.count;
  684. if (imageCount > 10)
  685. imageCount = 10;
  686. for (NSUInteger x = 0; x < imageCount; x++)
  687. [self fetchAudioImage:[NSURL URLWithString:imageURLStrings[x]]];
  688. }
  689. }
  690. - (void)MDFHatchetFetcher:(MDFHatchetFetcher *)aFetcher didFailToFindArtistForSearchRequest:(NSString *)searchRequest
  691. {
  692. APLog(@"%s: %@", __PRETTY_FUNCTION__, searchRequest);
  693. }
  694. - (void)fetchAudioImage:(NSURL *)url
  695. {
  696. __weak typeof(self) weakSelf = self;
  697. NSURLSession *sharedSession = [NSURLSession sharedSession];
  698. NSURLSessionDataTask *task = [sharedSession dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  699. if (!data) {
  700. return;
  701. }
  702. UIImage *image = [UIImage imageWithData:data];
  703. @synchronized(weakSelf.audioImagesArray) {
  704. [weakSelf.audioImagesArray addObject:image];
  705. }
  706. [weakSelf performSelectorOnMainThread:@selector(showAnimatedImagesView) withObject:nil waitUntilDone:NO];
  707. }];
  708. [task resume];
  709. }
  710. @end
  711. @implementation VLCFullscreenMovieTVViewController (UIViewControllerTransitioningDelegate)
  712. - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
  713. {
  714. return [[VLCPlaybackInfoTVTransitioningAnimator alloc] init];
  715. }
  716. - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
  717. {
  718. return [[VLCPlaybackInfoTVTransitioningAnimator alloc] init];
  719. }
  720. @end