VLCFullscreenMovieTVViewController.m 30 KB

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