VLCFullscreenMovieTVViewController.m 40 KB

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