VLCMovieViewController.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. //
  2. // VLCMovieViewController.m
  3. // AspenProject
  4. //
  5. // Created by Felix Paul Kühne on 27.02.13.
  6. // Copyright (c) 2013 VideoLAN. All rights reserved.
  7. //
  8. #import "VLCMovieViewController.h"
  9. #import "VLCExternalDisplayController.h"
  10. #define INPUT_RATE_DEFAULT 1000.
  11. @interface VLCMovieViewController () <UIGestureRecognizerDelegate>
  12. @property (nonatomic, strong) UIPopoverController *masterPopoverController;
  13. @property (nonatomic, strong) UIWindow *externalWindow;
  14. @end
  15. @implementation VLCMovieViewController
  16. - (void)dealloc
  17. {
  18. [[NSNotificationCenter defaultCenter] removeObserver:self];
  19. }
  20. #pragma mark - Managing the media item
  21. - (void)setMediaItem:(id)newMediaItem
  22. {
  23. if (_mediaItem != newMediaItem)
  24. _mediaItem = newMediaItem;
  25. if (self.masterPopoverController != nil)
  26. [self.masterPopoverController dismissPopoverAnimated:YES];
  27. }
  28. - (void)viewDidLoad
  29. {
  30. [super viewDidLoad];
  31. self.wantsFullScreenLayout = YES;
  32. _mediaPlayer = [[VLCMediaPlayer alloc] init];
  33. [_mediaPlayer setDelegate:self];
  34. [_mediaPlayer setDrawable:self.movieView];
  35. self.videoFilterView.hidden = YES;
  36. _videoFiltersHidden = YES;
  37. _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", @"");
  38. _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", @"");
  39. _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", @"");
  40. _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", @"");
  41. _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", @"");
  42. self.playbackView.hidden = YES;
  43. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  44. [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
  45. name:UIScreenDidConnectNotification object:nil];
  46. [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
  47. name:UIScreenDidDisconnectNotification object:nil];
  48. [center addObserver:self selector:@selector(appWillResign:) name:UIApplicationWillResignActiveNotification object:nil];
  49. _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", @"");
  50. _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", @"");
  51. if ([self hasExternalDisplay])
  52. [self showOnExternalDisplay];
  53. _movieView.userInteractionEnabled = NO;
  54. UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toogleControlsVisible)];
  55. recognizer.delegate = self;
  56. [self.view addGestureRecognizer:recognizer];
  57. [self resetIdleTimer];
  58. }
  59. - (void)viewWillAppear:(BOOL)animated
  60. {
  61. [super viewWillAppear:animated];
  62. [self.navigationController setNavigationBarHidden:YES animated:YES];
  63. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
  64. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
  65. if (!self.mediaItem && !self.url)
  66. return;
  67. if (self.mediaItem) {
  68. self.title = [self.mediaItem title];
  69. [_mediaPlayer setMedia:[VLCMedia mediaWithURL:[NSURL URLWithString:self.mediaItem.url]]];
  70. } else {
  71. [_mediaPlayer setMedia:[VLCMedia mediaWithURL:self.url]];
  72. self.title = @"Network Stream";
  73. }
  74. [_mediaPlayer play];
  75. if (self.mediaItem.lastPosition && [self.mediaItem.lastPosition floatValue] < 0.99)
  76. [_mediaPlayer setPosition:[self.mediaItem.lastPosition floatValue]];
  77. self.playbackSpeedSlider.value = [self _playbackSpeed];
  78. [self _updatePlaybackSpeedIndicator];
  79. }
  80. - (void)viewWillDisappear:(BOOL)animated
  81. {
  82. if (_idleTimer)
  83. [_idleTimer invalidate];
  84. [self.navigationController setNavigationBarHidden:NO animated:YES];
  85. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
  86. [_mediaPlayer pause];
  87. [super viewWillDisappear:animated];
  88. self.mediaItem.lastPosition = @([_mediaPlayer position]);
  89. [_mediaPlayer stop];
  90. }
  91. - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  92. {
  93. self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  94. if (self)
  95. self.title = @"Video Playback";
  96. return self;
  97. }
  98. #pragma mark - remote events
  99. - (void)viewDidAppear:(BOOL)animated
  100. {
  101. [super viewDidAppear:animated];
  102. [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
  103. [self becomeFirstResponder];
  104. }
  105. - (void)viewDidDisappear:(BOOL)animated
  106. {
  107. [super viewDidDisappear:animated];
  108. [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
  109. [self resignFirstResponder];
  110. }
  111. - (BOOL)canBecomeFirstResponder
  112. {
  113. return YES;
  114. }
  115. - (void)remoteControlReceivedWithEvent:(UIEvent *)event
  116. {
  117. switch (event.subtype) {
  118. case UIEventSubtypeRemoteControlPlay:
  119. [_mediaPlayer play];
  120. break;
  121. case UIEventSubtypeRemoteControlPause:
  122. [_mediaPlayer pause];
  123. break;
  124. case UIEventSubtypeRemoteControlTogglePlayPause:
  125. [self playPause];
  126. break;
  127. default:
  128. break;
  129. }
  130. }
  131. #pragma mark - controls visibility
  132. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
  133. {
  134. if (touch.view != self.view)
  135. return NO;
  136. return YES;
  137. }
  138. - (void)toogleControlsVisible
  139. {
  140. _controlsHidden = !_controlsHidden;
  141. CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
  142. if (!_controlsHidden) {
  143. _controllerPanel.alpha = 0.0f;
  144. _controllerPanel.hidden = !_videoFiltersHidden || !_playbackViewHidden;
  145. _toolbar.alpha = 0.0f;
  146. _toolbar.hidden = NO;
  147. _videoFilterView.alpha = 0.0f;
  148. _videoFilterView.hidden = _videoFiltersHidden;
  149. _videoFilterButton.alpha = 0.0f;
  150. _videoFilterButton.hidden = NO;
  151. _playbackView.alpha = 0.0f;
  152. _playbackView.hidden = _playbackViewHidden;
  153. _playbackButton.alpha = 0.0f;
  154. _playbackButton.hidden = NO;
  155. }
  156. void (^animationBlock)() = ^() {
  157. _controllerPanel.alpha = alpha;
  158. _toolbar.alpha = alpha;
  159. _videoFilterView.alpha = alpha;
  160. _videoFilterButton.alpha = alpha;
  161. _playbackView.alpha = alpha;
  162. _playbackButton.alpha = alpha;
  163. };
  164. void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
  165. if (_videoFiltersHidden && _playbackViewHidden)
  166. _controllerPanel.hidden = _controlsHidden;
  167. else
  168. _controllerPanel.hidden = YES;
  169. _toolbar.hidden = _controlsHidden;
  170. _videoFilterView.hidden = _videoFiltersHidden;
  171. _videoFilterButton.hidden = _controlsHidden;
  172. _playbackView.hidden = _playbackViewHidden;
  173. _playbackButton.hidden = _controlsHidden;
  174. };
  175. [UIView animateWithDuration:0.3f animations:animationBlock completion:completionBlock];
  176. [[UIApplication sharedApplication] setStatusBarHidden:_controlsHidden withAnimation:UIStatusBarAnimationFade];
  177. }
  178. - (void)resetIdleTimer
  179. {
  180. if (!_idleTimer)
  181. _idleTimer = [NSTimer scheduledTimerWithTimeInterval:2.
  182. target:self
  183. selector:@selector(idleTimerExceeded)
  184. userInfo:nil
  185. repeats:NO];
  186. else {
  187. if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 2.)
  188. [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:2.]];
  189. }
  190. }
  191. - (void)idleTimerExceeded
  192. {
  193. _idleTimer = nil;
  194. if (!_controlsHidden)
  195. [self toogleControlsVisible];
  196. }
  197. - (UIResponder *)nextResponder
  198. {
  199. [self resetIdleTimer];
  200. return [super nextResponder];
  201. }
  202. #pragma mark - controls
  203. - (IBAction)closePlayback:(id)sender
  204. {
  205. [self.navigationController popViewControllerAnimated:YES];
  206. }
  207. - (IBAction)positionSliderAction:(UISlider *)sender
  208. {
  209. _mediaPlayer.position = sender.value;
  210. [self resetIdleTimer];
  211. }
  212. - (void)mediaPlayerTimeChanged:(NSNotification *)aNotification {
  213. self.positionSlider.value = [_mediaPlayer position];
  214. self.timeDisplay.title = [[_mediaPlayer remainingTime] stringValue];
  215. }
  216. - (void)mediaPlayerStateChanged:(NSNotification *)aNotification
  217. {
  218. // TODO
  219. NSString *playPauseTitle = [_mediaPlayer isPlaying]? @"Pse": @"Play";
  220. [_playPauseButton setTitle:playPauseTitle forState:UIControlStateNormal];
  221. }
  222. - (IBAction)playPause
  223. {
  224. if ([_mediaPlayer isPlaying]) {
  225. [_mediaPlayer pause];
  226. } else {
  227. [_mediaPlayer play];
  228. }
  229. }
  230. - (IBAction)forward:(id)sender
  231. {
  232. [_mediaPlayer mediumJumpForward];
  233. }
  234. - (IBAction)backward:(id)sender
  235. {
  236. [_mediaPlayer mediumJumpBackward];
  237. }
  238. - (IBAction)switchAudioTrack:(id)sender
  239. {
  240. _audiotrackActionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"CHOOSE_AUDIO_TRACK", @"audio track selector") delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
  241. NSArray *audioTracks = [_mediaPlayer audioTrackNames];
  242. NSUInteger count = [audioTracks count];
  243. for (NSUInteger i = 0; i < count; i++)
  244. [_audiotrackActionSheet addButtonWithTitle:audioTracks[i]];
  245. [_audiotrackActionSheet addButtonWithTitle:NSLocalizedString(@"BUTTON_CANCEL", @"cancel button")];
  246. [_audiotrackActionSheet setCancelButtonIndex:[_audiotrackActionSheet numberOfButtons] - 1];
  247. [_audiotrackActionSheet showFromRect:[self.audioSwitcherButton frame] inView:self.audioSwitcherButton animated:YES];
  248. }
  249. - (IBAction)switchSubtitleTrack:(id)sender
  250. {
  251. NSArray *spuTracks = [_mediaPlayer videoSubTitlesNames];
  252. NSUInteger count = [spuTracks count];
  253. if (count <= 1)
  254. return;
  255. _subtitleActionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"CHOOSE_SUBTITLE_TRACK", @"subtitle track selector") delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
  256. for (NSUInteger i = 0; i < count; i++)
  257. [_subtitleActionSheet addButtonWithTitle:spuTracks[i]];
  258. [_subtitleActionSheet addButtonWithTitle:NSLocalizedString(@"BUTTON_CANCEL", @"cancel button")];
  259. [_subtitleActionSheet setCancelButtonIndex:[_subtitleActionSheet numberOfButtons] - 1];
  260. [_subtitleActionSheet showFromRect:[self.subtitleSwitcherButton frame] inView:self.subtitleSwitcherButton animated:YES];
  261. }
  262. - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
  263. NSUInteger arrayIndex = 0;
  264. NSArray *indexArray;
  265. NSArray *namesArray;
  266. if (actionSheet == _subtitleActionSheet) {
  267. namesArray = _mediaPlayer.videoSubTitlesNames;
  268. arrayIndex = [namesArray indexOfObject:[actionSheet buttonTitleAtIndex:buttonIndex]];
  269. if (arrayIndex != NSNotFound) {
  270. indexArray = _mediaPlayer.videoSubTitlesIndexes;
  271. _mediaPlayer.currentVideoSubTitleIndex = [indexArray[arrayIndex] intValue];
  272. }
  273. } else if (actionSheet == _audiotrackActionSheet) {
  274. namesArray = _mediaPlayer.audioTrackNames;
  275. arrayIndex = [namesArray indexOfObject:[actionSheet buttonTitleAtIndex:buttonIndex]];
  276. if (arrayIndex != NSNotFound) {
  277. indexArray = _mediaPlayer.audioTrackIndexes;
  278. _mediaPlayer.currentAudioTrackIndex = [indexArray[arrayIndex] intValue];
  279. }
  280. } else if (actionSheet == _aspectRatioActionSheet) {
  281. if (actionSheet.cancelButtonIndex != buttonIndex) {
  282. if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:@"Default"])
  283. _mediaPlayer.videoAspectRatio = NULL;
  284. else
  285. _mediaPlayer.videoAspectRatio = (char *)[[actionSheet buttonTitleAtIndex:buttonIndex] UTF8String];
  286. }
  287. } else if (actionSheet == _cropActionSheet) {
  288. if (actionSheet.cancelButtonIndex != buttonIndex) {
  289. if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:@"Default"])
  290. _mediaPlayer.videoCropGeometry = NULL;
  291. else
  292. _mediaPlayer.videoCropGeometry = (char *)[[actionSheet buttonTitleAtIndex:buttonIndex] UTF8String];
  293. }
  294. }
  295. }
  296. #pragma mark - Video Filter UI
  297. - (IBAction)videoFilterToggle:(id)sender
  298. {
  299. if (!_playbackViewHidden)
  300. self.playbackView.hidden = _playbackViewHidden = YES;
  301. self.videoFilterView.hidden = !_videoFiltersHidden;
  302. _videoFiltersHidden = self.videoFilterView.hidden;
  303. self.controllerPanel.hidden = !_videoFiltersHidden;
  304. }
  305. - (IBAction)videoFilterSliderAction:(id)sender
  306. {
  307. if (sender == self.hueSlider)
  308. _mediaPlayer.hue = (int)self.hueSlider.value;
  309. else if (sender == self.contrastSlider)
  310. _mediaPlayer.contrast = self.contrastSlider.value;
  311. else if (sender == self.brightnessSlider) {
  312. if ([self hasExternalDisplay])
  313. _mediaPlayer.brightness = self.brightnessSlider.value;
  314. else
  315. [[UIScreen mainScreen] setBrightness:(self.brightnessSlider.value / 2.)];
  316. } else if (sender == self.saturationSlider)
  317. _mediaPlayer.saturation = self.saturationSlider.value;
  318. else if (sender == self.gammaSlider)
  319. _mediaPlayer.gamma = self.gammaSlider.value;
  320. else if (sender == self.resetVideoFilterButton) {
  321. _mediaPlayer.hue = self.hueSlider.value = 0.;
  322. _mediaPlayer.contrast = self.contrastSlider.value = 1.;
  323. _mediaPlayer.brightness = self.brightnessSlider.value = 1.;
  324. _mediaPlayer.saturation = self.saturationSlider.value = 1.;
  325. _mediaPlayer.gamma = self.gammaSlider.value = 1.;
  326. } else
  327. APLog(@"unknown sender for videoFilterSliderAction");
  328. [self resetIdleTimer];
  329. }
  330. #pragma mark - playback view
  331. - (IBAction)playbackSpeedSliderAction:(UISlider *)sender
  332. {
  333. double speed = pow(2, sender.value / 17.);
  334. float rate = INPUT_RATE_DEFAULT / speed;
  335. if (_currentPlaybackRate != rate)
  336. [_mediaPlayer setRate:INPUT_RATE_DEFAULT / rate];
  337. _currentPlaybackRate = rate;
  338. [self _updatePlaybackSpeedIndicator];
  339. [self resetIdleTimer];
  340. }
  341. - (void)_updatePlaybackSpeedIndicator
  342. {
  343. float f_value = self.playbackSpeedSlider.value;
  344. double speed = pow(2, f_value / 17.);
  345. self.playbackSpeedIndicator.text = [NSString stringWithFormat:@"%.2fx", speed];
  346. }
  347. - (float)_playbackSpeed
  348. {
  349. float f_rate = _mediaPlayer.rate;
  350. double value = 17 * log(f_rate) / log(2.);
  351. float returnValue = (int) ((value > 0) ? value + .5 : value - .5);
  352. if (returnValue < -34.)
  353. returnValue = -34.;
  354. else if (returnValue > 34.)
  355. returnValue = 34.;
  356. _currentPlaybackRate = returnValue;
  357. return returnValue;
  358. }
  359. - (IBAction)videoDimensionAction:(id)sender
  360. {
  361. if (sender == self.playbackButton) {
  362. if (!_videoFiltersHidden)
  363. self.videoFilterButton.hidden = _videoFiltersHidden = YES;
  364. self.playbackView.hidden = !_playbackViewHidden;
  365. _playbackViewHidden = self.playbackView.hidden;
  366. self.controllerPanel.hidden = !_playbackViewHidden;
  367. } else if (sender == self.aspectRatioButton) {
  368. NSArray *ratios = @[@"Default", @"1:1", @"4:3", @"16:9", @"16:10", @"2.21:1", @"2:35:1", @"2.39:1", @"5:4"];
  369. NSUInteger count = [ratios count];
  370. _aspectRatioActionSheet = [[UIActionSheet alloc] initWithTitle:@"Choose Aspect Ratio" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
  371. for (NSUInteger i = 0; i < count; i++)
  372. [_aspectRatioActionSheet addButtonWithTitle:ratios[i]];
  373. [_aspectRatioActionSheet addButtonWithTitle:NSLocalizedString(@"Cancel", @"subtitle track selector")];
  374. [_aspectRatioActionSheet setCancelButtonIndex:[_aspectRatioActionSheet numberOfButtons] - 1];
  375. [_aspectRatioActionSheet showFromRect:[self.aspectRatioButton frame] inView:self.aspectRatioButton animated:YES];
  376. } else if (sender == self.cropButton) {
  377. NSArray *ratios = @[@"Default", @"16:10", @"16:9", @"1.85:1", @"2.21:1", @"2.35:1", @"2:39:1", @"5:3", @"4:3", @"5:4", @"1:1"];
  378. NSUInteger count = [ratios count];
  379. _cropActionSheet = [[UIActionSheet alloc] initWithTitle:@"Choose Aspect Ratio" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
  380. for (NSUInteger i = 0; i < count; i++)
  381. [_cropActionSheet addButtonWithTitle:ratios[i]];
  382. [_cropActionSheet addButtonWithTitle:NSLocalizedString(@"Cancel", @"subtitle track selector")];
  383. [_cropActionSheet setCancelButtonIndex:[_cropActionSheet numberOfButtons] - 1];
  384. [_cropActionSheet showFromRect:[self.cropButton frame] inView:self.cropButton animated:YES];
  385. }
  386. }
  387. #pragma mark -
  388. - (void)appWillResign:(NSNotification *)aNotification
  389. {
  390. self.mediaItem.lastPosition = @([_mediaPlayer position]);
  391. }
  392. #pragma mark - autorotation
  393. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
  394. return UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
  395. || toInterfaceOrientation != UIInterfaceOrientationMaskPortraitUpsideDown;
  396. }
  397. #pragma mark - External Display
  398. - (BOOL)hasExternalDisplay
  399. {
  400. return ([[UIScreen screens] count] > 1);
  401. }
  402. - (void)showOnExternalDisplay
  403. {
  404. UIScreen *screen = [UIScreen screens][1];
  405. screen.overscanCompensation = UIScreenOverscanCompensationInsetApplicationFrame;
  406. self.externalWindow = [[UIWindow alloc] initWithFrame:screen.bounds];
  407. UIViewController *controller = [[VLCExternalDisplayController alloc] init];
  408. self.externalWindow.rootViewController = controller;
  409. [controller.view addSubview:_movieView];
  410. controller.view.frame = screen.bounds;
  411. _movieView.frame = screen.bounds;
  412. self.playingExternallyView.hidden = NO;
  413. self.externalWindow.screen = screen;
  414. self.externalWindow.hidden = NO;
  415. }
  416. - (void)hideFromExternalDisplay
  417. {
  418. [self.view addSubview:_movieView];
  419. [self.view sendSubviewToBack:_movieView];
  420. _movieView.frame = self.view.frame;
  421. self.playingExternallyView.hidden = YES;
  422. self.externalWindow.hidden = YES;
  423. self.externalWindow = nil;
  424. }
  425. - (void)handleExternalScreenDidConnect:(NSNotification *)notification
  426. {
  427. [self showOnExternalDisplay];
  428. }
  429. - (void)handleExternalScreenDidDisconnect:(NSNotification *)notification
  430. {
  431. [self hideFromExternalDisplay];
  432. }
  433. @end