VDLPlaybackViewController.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. /* Copyright (c) 2013, Felix Paul Kühne and VideoLAN
  2. * All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are met:
  6. *
  7. * 1. Redistributions of source code must retain the above copyright notice,
  8. * this list of conditions and the following disclaimer.
  9. *
  10. * 2. Redistributions in binary form must reproduce the above copyright notice,
  11. * this list of conditions and the following disclaimer in the documentation
  12. * and/or other materials provided with the distribution.
  13. *
  14. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  15. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  17. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  18. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  19. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  20. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  21. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  22. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  23. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  24. * POSSIBILITY OF SUCH DAMAGE. */
  25. #import "VDLPlaybackViewController.h"
  26. #import <AVFoundation/AVFoundation.h>
  27. @interface VDLPlaybackViewController () <UIGestureRecognizerDelegate, UIActionSheetDelegate>
  28. {
  29. VLCMediaPlayer *_mediaplayer;
  30. BOOL _setPosition;
  31. BOOL _displayRemainingTime;
  32. int _currentAspectRatioMask;
  33. NSArray *_aspectRatios;
  34. UIActionSheet *_audiotrackActionSheet;
  35. UIActionSheet *_subtitleActionSheet;
  36. NSURL *_url;
  37. NSTimer *_idleTimer;
  38. }
  39. @end
  40. @implementation VDLPlaybackViewController
  41. - (void)viewDidLoad
  42. {
  43. [super viewDidLoad];
  44. /* fix-up UI */
  45. self.wantsFullScreenLayout = YES;
  46. [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
  47. /* we want to influence the system volume */
  48. [[AVAudioSession sharedInstance] setDelegate:self];
  49. /* populate array of supported aspect ratios (there are more!) */
  50. _aspectRatios = @[@"DEFAULT", @"FILL_TO_SCREEN", @"4:3", @"16:9", @"16:10", @"2.21:1"];
  51. /* fix-up the UI */
  52. CGRect rect = self.toolbar.frame;
  53. rect.size.height += 20.;
  54. self.toolbar.frame = rect;
  55. [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
  56. /* this looks a bit weird, but let's try to support iOS 5 */
  57. UISlider *volumeSlider = nil;
  58. for (id aView in self.volumeView.subviews){
  59. if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
  60. volumeSlider = (UISlider *)aView;
  61. break;
  62. }
  63. }
  64. [volumeSlider addTarget:self
  65. action:@selector(volumeSliderAction:)
  66. forControlEvents:UIControlEventValueChanged];
  67. /* setup gesture recognizer to toggle controls' visibility */
  68. _movieView.userInteractionEnabled = NO;
  69. UITapGestureRecognizer *tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
  70. tapOnVideoRecognizer.delegate = self;
  71. [self.view addGestureRecognizer:tapOnVideoRecognizer];
  72. }
  73. - (void)playMediaFromURL:(NSURL*)theURL
  74. {
  75. _url = theURL;
  76. }
  77. - (IBAction)playandPause:(id)sender
  78. {
  79. if (_mediaplayer.isPlaying)
  80. [_mediaplayer pause];
  81. [_mediaplayer play];
  82. }
  83. - (IBAction)closePlayback:(id)sender
  84. {
  85. [self.navigationController dismissViewControllerAnimated:YES completion:nil];
  86. }
  87. - (void)viewWillAppear:(BOOL)animated
  88. {
  89. [super viewWillAppear:animated];
  90. [self.navigationController setNavigationBarHidden:YES animated:YES];
  91. /* setup the media player instance, give it a delegate and something to draw into */
  92. _mediaplayer = [[VLCMediaPlayer alloc] init];
  93. _mediaplayer.delegate = self;
  94. _mediaplayer.drawable = self.movieView;
  95. /* listen for notifications from the player */
  96. [_mediaplayer addObserver:self forKeyPath:@"time" options:0 context:nil];
  97. [_mediaplayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];
  98. /* create a media object and give it to the player */
  99. _mediaplayer.media = [VLCMedia mediaWithURL:_url];
  100. [_mediaplayer play];
  101. if (self.controllerPanel.hidden)
  102. [self toggleControlsVisible];
  103. [self _resetIdleTimer];
  104. }
  105. - (void)viewWillDisappear:(BOOL)animated
  106. {
  107. [super viewWillDisappear:animated];
  108. if (_mediaplayer) {
  109. @try {
  110. [_mediaplayer removeObserver:self forKeyPath:@"time"];
  111. [_mediaplayer removeObserver:self forKeyPath:@"remainingTime"];
  112. }
  113. @catch (NSException *exception) {
  114. NSLog(@"we weren't an observer yet");
  115. }
  116. if (_mediaplayer.media)
  117. [_mediaplayer stop];
  118. if (_mediaplayer)
  119. _mediaplayer = nil;
  120. }
  121. if (_idleTimer) {
  122. [_idleTimer invalidate];
  123. _idleTimer = nil;
  124. }
  125. [self.navigationController setNavigationBarHidden:NO animated:YES];
  126. [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
  127. }
  128. - (UIResponder *)nextResponder
  129. {
  130. [self _resetIdleTimer];
  131. return [super nextResponder];
  132. }
  133. - (IBAction)positionSliderAction:(UISlider *)sender
  134. {
  135. [self _resetIdleTimer];
  136. /* we need to limit the number of events sent by the slider, since otherwise, the user
  137. * wouldn't see the I-frames when seeking on current mobile devices. This isn't a problem
  138. * within the Simulator, but especially on older ARMv7 devices, it's clearly noticeable. */
  139. [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
  140. _setPosition = NO;
  141. }
  142. - (void)_setPositionForReal
  143. {
  144. if (!_setPosition) {
  145. _mediaplayer.position = _positionSlider.value;
  146. _setPosition = YES;
  147. }
  148. }
  149. - (IBAction)positionSliderDrag:(id)sender
  150. {
  151. [self _resetIdleTimer];
  152. }
  153. - (IBAction)volumeSliderAction:(id)sender
  154. {
  155. [self _resetIdleTimer];
  156. }
  157. - (void)mediaPlayerStateChanged:(NSNotification *)aNotification
  158. {
  159. VLCMediaPlayerState currentState = _mediaplayer.state;
  160. /* distruct view controller on error */
  161. if (currentState == VLCMediaPlayerStateError)
  162. [self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.];
  163. /* or if playback ended */
  164. if (currentState == VLCMediaPlayerStateEnded || currentState == VLCMediaPlayerStateStopped)
  165. [self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.];
  166. [self.playPauseButton setTitle:[_mediaplayer isPlaying]? @"Pause" : @"Play" forState:UIControlStateNormal];
  167. }
  168. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  169. {
  170. self.positionSlider.value = [_mediaplayer position];
  171. if (_displayRemainingTime)
  172. [self.timeDisplay setTitle:[[_mediaplayer remainingTime] stringValue] forState:UIControlStateNormal];
  173. else
  174. [self.timeDisplay setTitle:[[_mediaplayer time] stringValue] forState:UIControlStateNormal];
  175. }
  176. - (IBAction)toggleTimeDisplay:(id)sender
  177. {
  178. [self _resetIdleTimer];
  179. _displayRemainingTime = !_displayRemainingTime;
  180. }
  181. - (void)toggleControlsVisible
  182. {
  183. BOOL controlsHidden = !self.controllerPanel.hidden;
  184. self.controllerPanel.hidden = controlsHidden;
  185. self.toolbar.hidden = controlsHidden;
  186. [[UIApplication sharedApplication] setStatusBarHidden:controlsHidden withAnimation:UIStatusBarAnimationFade];
  187. }
  188. - (void)_resetIdleTimer
  189. {
  190. if (!_idleTimer)
  191. _idleTimer = [NSTimer scheduledTimerWithTimeInterval:5.
  192. target:self
  193. selector:@selector(idleTimerExceeded)
  194. userInfo:nil
  195. repeats:NO];
  196. else {
  197. if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 5.)
  198. [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:5.]];
  199. }
  200. }
  201. - (void)idleTimerExceeded
  202. {
  203. _idleTimer = nil;
  204. if (!self.controllerPanel.hidden)
  205. [self toggleControlsVisible];
  206. }
  207. - (IBAction)switchVideoDimensions:(id)sender
  208. {
  209. [self _resetIdleTimer];
  210. NSUInteger count = [_aspectRatios count];
  211. if (_currentAspectRatioMask + 1 > count - 1) {
  212. _mediaplayer.videoAspectRatio = NULL;
  213. _mediaplayer.videoCropGeometry = NULL;
  214. _currentAspectRatioMask = 0;
  215. NSLog(@"crop disabled");
  216. } else {
  217. _currentAspectRatioMask++;
  218. if ([_aspectRatios[_currentAspectRatioMask] isEqualToString:@"FILL_TO_SCREEN"]) {
  219. UIScreen *screen = [UIScreen mainScreen];
  220. float f_ar = screen.bounds.size.width / screen.bounds.size.height;
  221. if (f_ar == (float)(640./1136.)) // iPhone 5 aka 16:9.01
  222. _mediaplayer.videoCropGeometry = "16:9";
  223. else if (f_ar == (float)(2./3.)) // all other iPhones
  224. _mediaplayer.videoCropGeometry = "16:10"; // libvlc doesn't support 2:3 crop
  225. else if (f_ar == .75) // all iPads
  226. _mediaplayer.videoCropGeometry = "4:3";
  227. else if (f_ar == .5625) // AirPlay
  228. _mediaplayer.videoCropGeometry = "16:9";
  229. else
  230. NSLog(@"unknown screen format %f, can't crop", f_ar);
  231. NSLog(@"FILL_TO_SCREEN");
  232. return;
  233. }
  234. _mediaplayer.videoCropGeometry = NULL;
  235. _mediaplayer.videoAspectRatio = (char *)[_aspectRatios[_currentAspectRatioMask] UTF8String];
  236. NSLog(@"crop switched to %@", _aspectRatios[_currentAspectRatioMask]);
  237. }
  238. }
  239. - (IBAction)switchAudioTrack:(id)sender
  240. {
  241. _audiotrackActionSheet = [[UIActionSheet alloc] initWithTitle:@"audio track selector" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
  242. NSArray *audioTracks = [_mediaplayer audioTrackNames];
  243. NSArray *audioTrackIndexes = [_mediaplayer audioTrackIndexes];
  244. NSUInteger count = [audioTracks count];
  245. for (NSUInteger i = 0; i < count; i++) {
  246. NSString *indexIndicator = ([audioTrackIndexes[i] intValue] == [_mediaplayer currentAudioTrackIndex])? @"\u2713": @"";
  247. NSString *buttonTitle = [NSString stringWithFormat:@"%@ %@", indexIndicator, audioTracks[i]];
  248. [_audiotrackActionSheet addButtonWithTitle:buttonTitle];
  249. }
  250. [_audiotrackActionSheet addButtonWithTitle:@"Cancel"];
  251. [_audiotrackActionSheet setCancelButtonIndex:[_audiotrackActionSheet numberOfButtons] - 1];
  252. [_audiotrackActionSheet showInView:self.audioSwitcherButton];
  253. }
  254. - (IBAction)switchSubtitleTrack:(id)sender
  255. {
  256. NSArray *spuTracks = [_mediaplayer videoSubTitlesNames];
  257. NSArray *spuTrackIndexes = [_mediaplayer videoSubTitlesIndexes];
  258. NSUInteger count = [spuTracks count];
  259. if (count <= 1)
  260. return;
  261. _subtitleActionSheet = [[UIActionSheet alloc] initWithTitle:@"subtitle track selector" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
  262. for (NSUInteger i = 0; i < count; i++) {
  263. NSString *indexIndicator = ([spuTrackIndexes[i] intValue] == [_mediaplayer currentVideoSubTitleIndex])? @"\u2713": @"";
  264. NSString *buttonTitle = [NSString stringWithFormat:@"%@ %@", indexIndicator, spuTracks[i]];
  265. [_subtitleActionSheet addButtonWithTitle:buttonTitle];
  266. }
  267. [_subtitleActionSheet addButtonWithTitle:@"Cancel"];
  268. [_subtitleActionSheet setCancelButtonIndex:[_subtitleActionSheet numberOfButtons] - 1];
  269. [_subtitleActionSheet showInView: self.subtitleSwitcherButton];
  270. }
  271. - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
  272. if (buttonIndex == [actionSheet cancelButtonIndex])
  273. return;
  274. NSArray *indexArray;
  275. if (actionSheet == _subtitleActionSheet) {
  276. indexArray = _mediaplayer.videoSubTitlesIndexes;
  277. if (buttonIndex <= indexArray.count) {
  278. _mediaplayer.currentVideoSubTitleIndex = [indexArray[buttonIndex] intValue];
  279. }
  280. } else if (actionSheet == _audiotrackActionSheet) {
  281. indexArray = _mediaplayer.audioTrackIndexes;
  282. if (buttonIndex <= indexArray.count) {
  283. _mediaplayer.currentAudioTrackIndex = [indexArray[buttonIndex] intValue];
  284. }
  285. }
  286. }
  287. - (void)didReceiveMemoryWarning
  288. {
  289. [super didReceiveMemoryWarning];
  290. // Dispose of any resources that can be recreated.
  291. }
  292. @end