VDLPlaybackViewController.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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. }
  38. @end
  39. @implementation VDLPlaybackViewController
  40. - (void)viewDidLoad
  41. {
  42. [super viewDidLoad];
  43. /* fix-up UI */
  44. self.wantsFullScreenLayout = YES;
  45. [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
  46. /* we want to influence the system volume */
  47. [[AVAudioSession sharedInstance] setDelegate:self];
  48. /* populate array of supported aspect ratios (there are more!) */
  49. _aspectRatios = @[@"DEFAULT", @"FILL_TO_SCREEN", @"4:3", @"16:9", @"16:10", @"2.21:1"];
  50. /* fix-up the UI */
  51. CGRect rect = self.toolbar.frame;
  52. rect.size.height += 20.;
  53. self.toolbar.frame = rect;
  54. [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
  55. /* setup gesture recognizer to toggle controls' visibility */
  56. _movieView.userInteractionEnabled = NO;
  57. UITapGestureRecognizer *tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
  58. tapOnVideoRecognizer.delegate = self;
  59. [self.view addGestureRecognizer:tapOnVideoRecognizer];
  60. }
  61. - (void)playMediaFromURL:(NSURL*)theURL
  62. {
  63. _url = theURL;
  64. }
  65. - (IBAction)playandPause:(id)sender
  66. {
  67. if (_mediaplayer.isPlaying)
  68. [_mediaplayer pause];
  69. [_mediaplayer play];
  70. }
  71. - (IBAction)closePlayback:(id)sender
  72. {
  73. [self.navigationController dismissViewControllerAnimated:YES completion:nil];
  74. }
  75. - (void)viewWillAppear:(BOOL)animated
  76. {
  77. [super viewWillAppear:animated];
  78. [self.navigationController setNavigationBarHidden:YES animated:YES];
  79. /* setup the media player instance, give it a delegate and something to draw into */
  80. _mediaplayer = [[VLCMediaPlayer alloc] init];
  81. _mediaplayer.delegate = self;
  82. _mediaplayer.drawable = self.movieView;
  83. /* listen for notifications from the player */
  84. [_mediaplayer addObserver:self forKeyPath:@"time" options:0 context:nil];
  85. [_mediaplayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];
  86. /* create a media object and give it to the player */
  87. _mediaplayer.media = [VLCMedia mediaWithURL:_url];
  88. [_mediaplayer play];
  89. }
  90. - (void)viewWillDisappear:(BOOL)animated
  91. {
  92. [super viewWillDisappear:animated];
  93. if (_mediaplayer) {
  94. @try {
  95. [_mediaplayer removeObserver:self forKeyPath:@"time"];
  96. [_mediaplayer removeObserver:self forKeyPath:@"remainingTime"];
  97. }
  98. @catch (NSException *exception) {
  99. NSLog(@"we weren't an observer yet");
  100. }
  101. if (_mediaplayer.media)
  102. [_mediaplayer stop];
  103. if (_mediaplayer)
  104. _mediaplayer = nil;
  105. }
  106. [self.navigationController setNavigationBarHidden:NO animated:YES];
  107. [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
  108. }
  109. - (IBAction)positionSliderAction:(UISlider *)sender
  110. {
  111. /* we need to limit the number of events sent by the slider, since otherwise, the user
  112. * wouldn't see the I-frames when seeking on current mobile devices. This isn't a problem
  113. * within the Simulator, but especially on older ARMv7 devices, it's clearly noticeable. */
  114. [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
  115. _setPosition = NO;
  116. }
  117. - (void)_setPositionForReal
  118. {
  119. if (!_setPosition) {
  120. _mediaplayer.position = _positionSlider.value;
  121. _setPosition = YES;
  122. }
  123. }
  124. - (void)mediaPlayerStateChanged:(NSNotification *)aNotification
  125. {
  126. VLCMediaPlayerState currentState = _mediaplayer.state;
  127. /* distruct view controller on error */
  128. if (currentState == VLCMediaPlayerStateError)
  129. [self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.];
  130. /* or if playback ended */
  131. if (currentState == VLCMediaPlayerStateEnded || currentState == VLCMediaPlayerStateStopped)
  132. [self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.];
  133. [self.playPauseButton setTitle:[_mediaplayer isPlaying]? @"Pause" : @"Play" forState:UIControlStateNormal];
  134. }
  135. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  136. {
  137. self.positionSlider.value = [_mediaplayer position];
  138. if (_displayRemainingTime)
  139. [self.timeDisplay setTitle:[[_mediaplayer remainingTime] stringValue] forState:UIControlStateNormal];
  140. else
  141. [self.timeDisplay setTitle:[[_mediaplayer time] stringValue] forState:UIControlStateNormal];
  142. }
  143. - (IBAction)toggleTimeDisplay:(id)sender
  144. {
  145. _displayRemainingTime = !_displayRemainingTime;
  146. }
  147. - (void)toggleControlsVisible
  148. {
  149. BOOL controlsHidden = !self.controllerPanel.hidden;
  150. self.controllerPanel.hidden = controlsHidden;
  151. self.toolbar.hidden = controlsHidden;
  152. [[UIApplication sharedApplication] setStatusBarHidden:controlsHidden withAnimation:UIStatusBarAnimationFade];
  153. }
  154. - (IBAction)switchVideoDimensions:(id)sender
  155. {
  156. NSUInteger count = [_aspectRatios count];
  157. if (_currentAspectRatioMask + 1 > count - 1) {
  158. _mediaplayer.videoAspectRatio = NULL;
  159. _mediaplayer.videoCropGeometry = NULL;
  160. _currentAspectRatioMask = 0;
  161. NSLog(@"crop disabled");
  162. } else {
  163. _currentAspectRatioMask++;
  164. if ([_aspectRatios[_currentAspectRatioMask] isEqualToString:@"FILL_TO_SCREEN"]) {
  165. UIScreen *screen = [UIScreen mainScreen];
  166. float f_ar = screen.bounds.size.width / screen.bounds.size.height;
  167. if (f_ar == (float)(640./1136.)) // iPhone 5 aka 16:9.01
  168. _mediaplayer.videoCropGeometry = "16:9";
  169. else if (f_ar == (float)(2./3.)) // all other iPhones
  170. _mediaplayer.videoCropGeometry = "16:10"; // libvlc doesn't support 2:3 crop
  171. else if (f_ar == .75) // all iPads
  172. _mediaplayer.videoCropGeometry = "4:3";
  173. else if (f_ar == .5625) // AirPlay
  174. _mediaplayer.videoCropGeometry = "16:9";
  175. else
  176. NSLog(@"unknown screen format %f, can't crop", f_ar);
  177. NSLog(@"FILL_TO_SCREEN");
  178. return;
  179. }
  180. _mediaplayer.videoCropGeometry = NULL;
  181. _mediaplayer.videoAspectRatio = (char *)[_aspectRatios[_currentAspectRatioMask] UTF8String];
  182. NSLog(@"crop switched to %@", _aspectRatios[_currentAspectRatioMask]);
  183. }
  184. }
  185. - (IBAction)switchAudioTrack:(id)sender
  186. {
  187. _audiotrackActionSheet = [[UIActionSheet alloc] initWithTitle:@"audio track selector" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
  188. NSArray *audioTracks = [_mediaplayer audioTrackNames];
  189. NSArray *audioTrackIndexes = [_mediaplayer audioTrackIndexes];
  190. NSUInteger count = [audioTracks count];
  191. for (NSUInteger i = 0; i < count; i++) {
  192. NSString *indexIndicator = ([audioTrackIndexes[i] intValue] == [_mediaplayer currentAudioTrackIndex])? @"\u2713": @"";
  193. NSString *buttonTitle = [NSString stringWithFormat:@"%@ %@", indexIndicator, audioTracks[i]];
  194. [_audiotrackActionSheet addButtonWithTitle:buttonTitle];
  195. }
  196. [_audiotrackActionSheet addButtonWithTitle:@"Cancel"];
  197. [_audiotrackActionSheet setCancelButtonIndex:[_audiotrackActionSheet numberOfButtons] - 1];
  198. [_audiotrackActionSheet showInView:self.audioSwitcherButton];
  199. }
  200. - (IBAction)switchSubtitleTrack:(id)sender
  201. {
  202. NSArray *spuTracks = [_mediaplayer videoSubTitlesNames];
  203. NSArray *spuTrackIndexes = [_mediaplayer videoSubTitlesIndexes];
  204. NSUInteger count = [spuTracks count];
  205. if (count <= 1)
  206. return;
  207. _subtitleActionSheet = [[UIActionSheet alloc] initWithTitle:@"subtitle track selector" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
  208. for (NSUInteger i = 0; i < count; i++) {
  209. NSString *indexIndicator = ([spuTrackIndexes[i] intValue] == [_mediaplayer currentVideoSubTitleIndex])? @"\u2713": @"";
  210. NSString *buttonTitle = [NSString stringWithFormat:@"%@ %@", indexIndicator, spuTracks[i]];
  211. [_subtitleActionSheet addButtonWithTitle:buttonTitle];
  212. }
  213. [_subtitleActionSheet addButtonWithTitle:@"Cancel"];
  214. [_subtitleActionSheet setCancelButtonIndex:[_subtitleActionSheet numberOfButtons] - 1];
  215. [_subtitleActionSheet showInView: self.subtitleSwitcherButton];
  216. }
  217. - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
  218. if (buttonIndex == [actionSheet cancelButtonIndex])
  219. return;
  220. NSArray *indexArray;
  221. if (actionSheet == _subtitleActionSheet) {
  222. indexArray = _mediaplayer.videoSubTitlesIndexes;
  223. if (buttonIndex <= indexArray.count) {
  224. _mediaplayer.currentVideoSubTitleIndex = [indexArray[buttonIndex] intValue];
  225. }
  226. } else if (actionSheet == _audiotrackActionSheet) {
  227. indexArray = _mediaplayer.audioTrackIndexes;
  228. if (buttonIndex <= indexArray.count) {
  229. _mediaplayer.currentAudioTrackIndex = [indexArray[buttonIndex] intValue];
  230. }
  231. }
  232. }
  233. - (void)didReceiveMemoryWarning
  234. {
  235. [super didReceiveMemoryWarning];
  236. // Dispose of any resources that can be recreated.
  237. }
  238. @end