VLCMovieViewController.m 83 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199
  1. /*****************************************************************************
  2. * VLCMovieViewController.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  9. * Gleb Pinigin <gpinigin # gmail.com>
  10. * Ahmad Harb <harb.dev.leb # gmail.com>
  11. * Fabio Ritrovato <sephiroth87 # videolan.org>
  12. * Pierre SAGASPE <pierre.sagaspe # me.com>
  13. * Jean-Baptiste Kempf <jb # videolan.org>
  14. *
  15. * Refer to the COPYING file of the official project for license.
  16. *****************************************************************************/
  17. #import "VLCMovieViewController.h"
  18. #import "VLCExternalDisplayController.h"
  19. #import <AVFoundation/AVFoundation.h>
  20. #import <CommonCrypto/CommonDigest.h>
  21. #import "UIDevice+VLC.h"
  22. #import "VLCBugreporter.h"
  23. #import "VLCThumbnailsCache.h"
  24. #import "VLCTrackSelectorTableViewCell.h"
  25. #import "VLCTrackSelectorHeaderView.h"
  26. #import "VLCEqualizerView.h"
  27. #import "VLCMultiSelectionMenuView.h"
  28. #import "OBSlider.h"
  29. #import "VLCStatusLabel.h"
  30. #define INPUT_RATE_DEFAULT 1000.
  31. #define TRACK_SELECTOR_TABLEVIEW_CELL @"track selector table view cell"
  32. #define TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER @"track selector table view section header"
  33. #define LOCKCHECK \
  34. if (_interfaceIsLocked) \
  35. return
  36. typedef NS_ENUM(NSInteger, VLCPanType) {
  37. VLCPanTypeNone,
  38. VLCPanTypeBrightness,
  39. VLCPanTypeSeek,
  40. VLCPanTypeVolume,
  41. };
  42. @interface VLCMovieViewController () <UIGestureRecognizerDelegate, AVAudioSessionDelegate, VLCMediaDelegate, UITableViewDataSource, UITableViewDelegate, VLCEqualizerViewDelegate, VLCMultiSelectionViewDelegate>
  43. {
  44. VLCMediaListPlayer *_listPlayer;
  45. VLCMediaPlayer *_mediaPlayer;
  46. BOOL _controlsHidden;
  47. BOOL _videoFiltersHidden;
  48. BOOL _playbackSpeedViewHidden;
  49. UIActionSheet *_subtitleActionSheet;
  50. UIActionSheet *_audiotrackActionSheet;
  51. float _currentPlaybackRate;
  52. NSArray *_aspectRatios;
  53. NSUInteger _currentAspectRatioMask;
  54. NSTimer *_idleTimer;
  55. NSTimer *_sleepTimer;
  56. BOOL _shouldResumePlaying;
  57. BOOL _viewAppeared;
  58. BOOL _displayRemainingTime;
  59. BOOL _positionSet;
  60. BOOL _playerIsSetup;
  61. BOOL _isScrubbing;
  62. BOOL _interfaceIsLocked;
  63. BOOL _switchingTracksNotChapters;
  64. BOOL _swipeGesturesEnabled;
  65. UIPinchGestureRecognizer *_pinchRecognizer;
  66. VLCPanType _currentPanType;
  67. UIPanGestureRecognizer *_panRecognizer;
  68. UISwipeGestureRecognizer *_swipeRecognizerLeft;
  69. UISwipeGestureRecognizer *_swipeRecognizerRight;
  70. UITapGestureRecognizer *_tapRecognizer;
  71. UITapGestureRecognizer *_tapOnVideoRecognizer;
  72. UIView *_trackSelectorContainer;
  73. UITableView *_trackSelectorTableView;
  74. VLCEqualizerView *_equalizerView;
  75. VLCMultiSelectionMenuView *_multiSelectionView;
  76. UIView *_sleepTimerContainer;
  77. UIDatePicker *_sleepTimeDatePicker;
  78. NSInteger _mediaDuration;
  79. BOOL _playbackFailed;
  80. }
  81. @property (nonatomic, strong) UIPopoverController *masterPopoverController;
  82. @property (nonatomic, strong) UIWindow *externalWindow;
  83. @end
  84. @implementation VLCMovieViewController
  85. + (void)initialize
  86. {
  87. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  88. NSDictionary *appDefaults = @{kVLCShowRemainingTime : @(YES)};
  89. [defaults registerDefaults:appDefaults];
  90. }
  91. - (void)dealloc
  92. {
  93. [[NSNotificationCenter defaultCenter] removeObserver:self];
  94. if (_tapRecognizer)
  95. [self.view removeGestureRecognizer:_tapRecognizer];
  96. if (_swipeRecognizerLeft)
  97. [self.view removeGestureRecognizer:_swipeRecognizerLeft];
  98. if (_swipeRecognizerRight)
  99. [self.view removeGestureRecognizer:_swipeRecognizerRight];
  100. if (_panRecognizer)
  101. [self.view removeGestureRecognizer:_panRecognizer];
  102. if (_pinchRecognizer)
  103. [self.view removeGestureRecognizer:_pinchRecognizer];
  104. [self.view removeGestureRecognizer:_tapOnVideoRecognizer];
  105. _tapRecognizer = nil;
  106. _swipeRecognizerLeft = nil;
  107. _swipeRecognizerRight = nil;
  108. _panRecognizer = nil;
  109. _pinchRecognizer = nil;
  110. _tapOnVideoRecognizer = nil;
  111. [[NSNotificationCenter defaultCenter] removeObserver:self];
  112. }
  113. #pragma mark - Managing the media item
  114. - (void)setFileFromMediaLibrary:(id)newFile
  115. {
  116. if (_fileFromMediaLibrary != newFile) {
  117. [self _stopPlayback];
  118. _fileFromMediaLibrary = newFile;
  119. if (_viewAppeared)
  120. [self _startPlayback];
  121. }
  122. if (self.masterPopoverController != nil)
  123. [self.masterPopoverController dismissPopoverAnimated:YES];
  124. }
  125. - (void)setUrl:(NSURL *)url
  126. {
  127. [self _stopPlayback];
  128. _url = url;
  129. _playerIsSetup = NO;
  130. if (_viewAppeared)
  131. [self _startPlayback];
  132. }
  133. - (void)setMediaList:(VLCMediaList *)mediaList
  134. {
  135. [self _stopPlayback];
  136. _mediaList = mediaList;
  137. _playerIsSetup = NO;
  138. if (_viewAppeared)
  139. [self _startPlayback];
  140. }
  141. - (void)viewDidLoad
  142. {
  143. [super viewDidLoad];
  144. CGRect rect;
  145. self.wantsFullScreenLayout = YES;
  146. self.videoFilterView.hidden = YES;
  147. _videoFiltersHidden = YES;
  148. _hueLabel.text = NSLocalizedString(@"VFILTER_HUE", nil);
  149. _hueSlider.accessibilityLabel = _hueLabel.text;
  150. _hueSlider.isAccessibilityElement = YES;
  151. _contrastLabel.text = NSLocalizedString(@"VFILTER_CONTRAST", nil);
  152. _contrastSlider.accessibilityLabel = _contrastLabel.text;
  153. _contrastSlider.isAccessibilityElement = YES;
  154. _brightnessLabel.text = NSLocalizedString(@"VFILTER_BRIGHTNESS", nil);
  155. _brightnessSlider.accessibilityLabel = _brightnessLabel.text;
  156. _brightnessSlider.isAccessibilityElement = YES;
  157. _saturationLabel.text = NSLocalizedString(@"VFILTER_SATURATION", nil);
  158. _saturationSlider.accessibilityLabel = _saturationLabel.text;
  159. _saturationSlider.isAccessibilityElement = YES;
  160. _gammaLabel.text = NSLocalizedString(@"VFILTER_GAMMA", nil);
  161. _gammaSlider.accessibilityLabel = _gammaLabel.text;
  162. _gammaSlider.isAccessibilityElement = YES;
  163. _playbackSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SPEED", nil);
  164. _playbackSpeedSlider.accessibilityLabel = _playbackSpeedLabel.text;
  165. _playbackSpeedSlider.isAccessibilityElement = YES;
  166. _audioDelayLabel.text = NSLocalizedString(@"AUDIO_DELAY", nil);
  167. _audioDelaySlider.accessibilityLabel = _audioDelayLabel.text;
  168. _audioDelaySlider.isAccessibilityElement = YES;
  169. _spuDelayLabel.text = NSLocalizedString(@"SPU_DELAY", nil);
  170. _spuDelaySlider.accessibilityLabel = _spuDelayLabel.text;
  171. _spuDelaySlider.isAccessibilityElement = YES;
  172. _positionSlider.accessibilityLabel = NSLocalizedString(@"PLAYBACK_POSITION", nil);
  173. _positionSlider.isAccessibilityElement = YES;
  174. _timeDisplay.isAccessibilityElement = YES;
  175. _trackSwitcherButton.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
  176. _trackSwitcherButton.isAccessibilityElement = YES;
  177. _trackSwitcherButtonLandscape.accessibilityLabel = NSLocalizedString(@"OPEN_TRACK_PANEL", nil);
  178. _trackSwitcherButtonLandscape.isAccessibilityElement = YES;
  179. _playbackSpeedButton.accessibilityLabel = _playbackSpeedLabel.text;
  180. _playbackSpeedButton.isAccessibilityElement = YES;
  181. _playbackSpeedButtonLandscape.accessibilityLabel = _playbackSpeedLabel.text;
  182. _playbackSpeedButtonLandscape.isAccessibilityElement = YES;
  183. _videoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
  184. _videoFilterButton.isAccessibilityElement = YES;
  185. _videoFilterButtonLandscape.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER", nil);
  186. _videoFilterButtonLandscape.isAccessibilityElement = YES;
  187. _resetVideoFilterButton.accessibilityLabel = NSLocalizedString(@"VIDEO_FILTER_RESET_BUTTON", nil);
  188. _resetVideoFilterButton.isAccessibilityElement = YES;
  189. _aspectRatioButton.accessibilityLabel = NSLocalizedString(@"VIDEO_ASPECT_RATIO_BUTTON", nil);
  190. _aspectRatioButton.isAccessibilityElement = YES;
  191. _playPauseButton.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
  192. _playPauseButton.isAccessibilityElement = YES;
  193. _playPauseButtonLandscape.accessibilityLabel = NSLocalizedString(@"PLAY_PAUSE_BUTTON", nil);
  194. _playPauseButtonLandscape.isAccessibilityElement = YES;
  195. _bwdButton.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
  196. _bwdButton.isAccessibilityElement = YES;
  197. _bwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"BWD_BUTTON", nil);
  198. _bwdButtonLandscape.isAccessibilityElement = YES;
  199. _fwdButton.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
  200. _fwdButton.isAccessibilityElement = YES;
  201. _fwdButtonLandscape.accessibilityLabel = NSLocalizedString(@"FWD_BUTTON", nil);
  202. _fwdButtonLandscape.isAccessibilityElement = YES;
  203. _sleepTimerButton.accessibilityLabel = NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil);
  204. _sleepTimerButton.isAccessibilityElement = YES;
  205. [_sleepTimerButton setTitle:NSLocalizedString(@"BUTTON_SLEEP_TIMER", nil) forState:UIControlStateNormal];
  206. _multiSelectionView = [[VLCMultiSelectionMenuView alloc] init];
  207. _multiSelectionView.delegate = self;
  208. _multiSelectionView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
  209. _multiSelectionView.hidden = YES;
  210. [self.view addSubview:_multiSelectionView];
  211. _scrubHelpLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HELP", nil);
  212. self.playbackSpeedView.hidden = YES;
  213. _playbackSpeedViewHidden = YES;
  214. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  215. [center addObserver:self selector:@selector(handleExternalScreenDidConnect:)
  216. name:UIScreenDidConnectNotification object:nil];
  217. [center addObserver:self selector:@selector(handleExternalScreenDidDisconnect:)
  218. name:UIScreenDidDisconnectNotification object:nil];
  219. [center addObserver:self selector:@selector(applicationWillResignActive:)
  220. name:UIApplicationWillResignActiveNotification object:nil];
  221. [center addObserver:self selector:@selector(applicationDidBecomeActive:)
  222. name:UIApplicationDidBecomeActiveNotification object:nil];
  223. [center addObserver:self selector:@selector(applicationDidEnterBackground:)
  224. name:UIApplicationDidEnterBackgroundNotification object:nil];
  225. [center addObserver:self selector:@selector(audioSessionRouteChange:)
  226. name:AVAudioSessionRouteChangeNotification object:nil];
  227. _playingExternallyTitle.text = NSLocalizedString(@"PLAYING_EXTERNALLY_TITLE", nil);
  228. _playingExternallyDescription.text = NSLocalizedString(@"PLAYING_EXTERNALLY_DESC", nil);
  229. if ([self hasExternalDisplay])
  230. [self showOnExternalDisplay];
  231. self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
  232. _movieView.userInteractionEnabled = NO;
  233. _tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
  234. _tapOnVideoRecognizer.delegate = self;
  235. [self.view addGestureRecognizer:_tapOnVideoRecognizer];
  236. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  237. _displayRemainingTime = [[defaults objectForKey:kVLCShowRemainingTime] boolValue];
  238. _swipeGesturesEnabled = [[defaults objectForKey:kVLCSettingPlaybackGestures] boolValue];
  239. _pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
  240. _pinchRecognizer.delegate = self;
  241. [self.view addGestureRecognizer:_pinchRecognizer];
  242. _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized)];
  243. [_tapRecognizer setNumberOfTouchesRequired:2];
  244. _currentPanType = VLCPanTypeNone;
  245. _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panRecognized:)];
  246. [_panRecognizer setMinimumNumberOfTouches:1];
  247. [_panRecognizer setMaximumNumberOfTouches:1];
  248. _swipeRecognizerLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
  249. _swipeRecognizerLeft.direction = UISwipeGestureRecognizerDirectionLeft;
  250. _swipeRecognizerRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
  251. _swipeRecognizerRight.direction = UISwipeGestureRecognizerDirectionRight;
  252. [self.view addGestureRecognizer:_swipeRecognizerLeft];
  253. [self.view addGestureRecognizer:_swipeRecognizerRight];
  254. [self.view addGestureRecognizer:_panRecognizer];
  255. [self.view addGestureRecognizer:_tapRecognizer];
  256. [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerLeft];
  257. [_panRecognizer requireGestureRecognizerToFail:_swipeRecognizerRight];
  258. _panRecognizer.delegate = self;
  259. _swipeRecognizerRight.delegate = self;
  260. _swipeRecognizerLeft.delegate = self;
  261. _tapRecognizer.delegate = self;
  262. _aspectRatios = @[@"DEFAULT", @"FILL_TO_SCREEN", @"4:3", @"16:9", @"16:10", @"2.21:1"];
  263. [self.aspectRatioButton setImage:[UIImage imageNamed:@"ratioIcon"] forState:UIControlStateNormal];
  264. if (SYSTEM_RUNS_IOS7_OR_LATER) {
  265. self.backButton.tintColor = [UIColor colorWithRed:(190.0f/255.0f) green:(190.0f/255.0f) blue:(190.0f/255.0f) alpha:1.];
  266. self.toolbar.tintColor = [UIColor whiteColor];
  267. self.toolbar.barStyle = UIBarStyleBlack;
  268. rect = self.resetVideoFilterButton.frame;
  269. rect.origin.y = rect.origin.y + 5.;
  270. self.resetVideoFilterButton.frame = rect;
  271. rect = self.toolbar.frame;
  272. rect.size.height = rect.size.height + rect.origin.y;
  273. rect.origin.y = 0;
  274. self.toolbar.frame = rect;
  275. rect = self.aspectRatioButton.frame;
  276. rect.size.width -= 19.;
  277. rect.origin.x += 19.;
  278. self.aspectRatioButton.frame = rect;
  279. rect = self.timeDisplay.frame;
  280. rect.origin.x += 19.;
  281. self.timeDisplay.frame = rect;
  282. rect = self.positionSlider.frame;
  283. rect.size.width += 19.;
  284. self.positionSlider.frame = rect;
  285. } else {
  286. rect = self.positionSlider.frame;
  287. rect.origin.y = rect.origin.y + 3.;
  288. self.positionSlider.frame = rect;
  289. [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButton"] forState:UIControlStateNormal];
  290. [self.aspectRatioButton setBackgroundImage:[UIImage imageNamed:@"ratioButtonHighlight"] forState:UIControlStateHighlighted];
  291. [self.toolbar setBackgroundImage:[UIImage imageNamed:@"seekbarBg"] forBarMetrics:UIBarMetricsDefault];
  292. [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButton"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
  293. [self.backButton setBackgroundImage:[UIImage imageNamed:@"playbackDoneButtonHighlight"] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
  294. }
  295. /* FIXME: there is a saner iOS 6+ API for this! */
  296. /* this looks a bit weird, but we need to support iOS 5 and should show the same appearance */
  297. void (^initVolumeSlider)(MPVolumeView *) = ^(MPVolumeView *volumeView){
  298. UISlider *volumeSlider = nil;
  299. for (id aView in volumeView.subviews){
  300. if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
  301. volumeSlider = (UISlider *)aView;
  302. break;
  303. }
  304. }
  305. if (!SYSTEM_RUNS_IOS7_OR_LATER) {
  306. [volumeSlider setMinimumTrackImage:[[UIImage imageNamed:@"sliderminiValue"]resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 0)] forState:UIControlStateNormal];
  307. [volumeSlider setMaximumTrackImage:[[UIImage imageNamed:@"slidermaxValue"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 4)] forState:UIControlStateNormal];
  308. [volumeSlider setThumbImage:[UIImage imageNamed:@"volumeballslider"] forState:UIControlStateNormal];
  309. } else
  310. [volumeView setVolumeThumbImage:[UIImage imageNamed:@"modernSliderKnob"] forState:UIControlStateNormal];
  311. [volumeSlider addTarget:self
  312. action:@selector(volumeSliderAction:)
  313. forControlEvents:UIControlEventValueChanged];
  314. };
  315. initVolumeSlider(self.volumeView);
  316. initVolumeSlider(self.volumeViewLandscape);
  317. [[AVAudioSession sharedInstance] setDelegate:self];
  318. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
  319. self.positionSlider.scrubbingSpeedChangePositions = @[@(0.), @(100.), @(200.), @(300)];
  320. _playerIsSetup = NO;
  321. [self.movieView setAccessibilityLabel:NSLocalizedString(@"VO_VIDEOPLAYER_TITLE", nil)];
  322. [self.movieView setAccessibilityHint:NSLocalizedString(@"VO_VIDEOPLAYER_DOUBLETAP", nil)];
  323. rect = self.view.frame;
  324. CGFloat width;
  325. CGFloat height;
  326. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  327. width = 300.;
  328. height = 320.;
  329. } else {
  330. width = 450.;
  331. height = 470.;
  332. }
  333. _trackSelectorTableView = [[UITableView alloc] initWithFrame:CGRectMake(0., 0., width, height) style:UITableViewStylePlain];
  334. _trackSelectorTableView.delegate = self;
  335. _trackSelectorTableView.dataSource = self;
  336. _trackSelectorTableView.separatorColor = [UIColor clearColor];
  337. _trackSelectorTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
  338. [_trackSelectorTableView registerClass:[VLCTrackSelectorTableViewCell class] forCellReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL];
  339. [_trackSelectorTableView registerClass:[VLCTrackSelectorHeaderView class] forHeaderFooterViewReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
  340. _trackSelectorTableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  341. _trackSelectorContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake((rect.size.width - width) / 2., (rect.size.height - height) / 2., width, height)];
  342. [_trackSelectorContainer addSubview:_trackSelectorTableView];
  343. _trackSelectorContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight;
  344. _trackSelectorContainer.hidden = YES;
  345. if ([[UIDevice currentDevice] speedCategory] >= 3) {
  346. _trackSelectorTableView.opaque = NO;
  347. _trackSelectorTableView.backgroundColor = [UIColor clearColor];
  348. } else
  349. _trackSelectorTableView.backgroundColor = [UIColor blackColor];
  350. [self.view addSubview:_trackSelectorContainer];
  351. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  352. _equalizerView = [[VLCEqualizerView alloc] initWithFrame:CGRectMake((rect.size.width - 450.) / 2., self.controllerPanel.frame.origin.y - 200., 450., 240.)];
  353. } else {
  354. _equalizerView = [[VLCEqualizerView alloc] initWithFrame:CGRectMake((rect.size.width - 450.) / 2., self.controllerPanel.frame.origin.y - 240., 450., 240.)];
  355. }
  356. _equalizerView.delegate = self;
  357. _equalizerView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
  358. _equalizerView.hidden = YES;
  359. [self.view addSubview:_equalizerView];
  360. }
  361. - (BOOL)_blobCheck
  362. {
  363. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  364. NSString *directoryPath = searchPaths[0];
  365. if (![[NSFileManager defaultManager] fileExistsAtPath:[directoryPath stringByAppendingPathComponent:@"blob.bin"]])
  366. return NO;
  367. NSData *data = [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:@"blob.bin"]];
  368. uint8_t digest[CC_SHA1_DIGEST_LENGTH];
  369. CC_SHA1(data.bytes, (unsigned int)data.length, digest);
  370. NSMutableString *hash = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
  371. for (unsigned int u = 0; u < CC_SHA1_DIGEST_LENGTH; u++)
  372. [hash appendFormat:@"%02x", digest[u]];
  373. if ([hash isEqualToString:kBlobHash])
  374. return YES;
  375. else
  376. return NO;
  377. }
  378. - (void)viewWillAppear:(BOOL)animated
  379. {
  380. [super viewWillAppear:animated];
  381. /* reset audio meta data views */
  382. self.artworkImageView.image = nil;
  383. self.trackNameLabel.text = nil;
  384. self.artistNameLabel.text = nil;
  385. self.albumNameLabel.text = nil;
  386. _swipeGesturesEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPlaybackGestures] boolValue];
  387. [self.navigationController setNavigationBarHidden:YES animated:YES];
  388. if (!SYSTEM_RUNS_IOS7_OR_LATER) {
  389. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
  390. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
  391. }
  392. [self _startPlayback];
  393. [self setControlsHidden:NO animated:YES];
  394. _viewAppeared = YES;
  395. }
  396. - (void)viewWillLayoutSubviews
  397. {
  398. CGRect multiSelectionFrame;
  399. CGRect controllerPanelFrame;
  400. if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPhone) {
  401. controllerPanelFrame = _controllerPanel.frame;
  402. multiSelectionFrame = (CGRect){CGPointMake(0., 0.), [_multiSelectionView proposedDisplaySize]};
  403. multiSelectionFrame.origin.x = controllerPanelFrame.size.width - multiSelectionFrame.size.width;
  404. multiSelectionFrame.origin.y = controllerPanelFrame.origin.y - multiSelectionFrame.size.height;
  405. _multiSelectionView.frame = multiSelectionFrame;
  406. _multiSelectionView.showsEqualizer = YES;
  407. return;
  408. }
  409. CGSize viewSize = self.view.frame.size;
  410. if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
  411. [_controllerPanel removeFromSuperview];
  412. controllerPanelFrame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanelLandscape.frame.size.height), CGSizeMake(viewSize.width, _controllerPanelLandscape.frame.size.height)};
  413. _controllerPanelLandscape.frame = controllerPanelFrame;
  414. [self.view addSubview:_controllerPanelLandscape];
  415. _multiSelectionView.showsEqualizer = YES;
  416. multiSelectionFrame = (CGRect){CGPointMake(0., 0.), [_multiSelectionView proposedDisplaySize]};
  417. multiSelectionFrame.origin.x = controllerPanelFrame.size.width - multiSelectionFrame.size.width;
  418. multiSelectionFrame.origin.y = controllerPanelFrame.origin.y - multiSelectionFrame.size.height;
  419. } else {
  420. [_controllerPanelLandscape removeFromSuperview];
  421. controllerPanelFrame = (CGRect){CGPointMake(0, viewSize.height - _controllerPanel.frame.size.height), CGSizeMake(viewSize.width, _controllerPanel.frame.size.height)};
  422. _controllerPanel.frame = controllerPanelFrame;
  423. [self.view addSubview:_controllerPanel];
  424. _multiSelectionView.showsEqualizer = NO;
  425. multiSelectionFrame = (CGRect){CGPointMake(0., 0.), [_multiSelectionView proposedDisplaySize]};
  426. multiSelectionFrame.origin.x = controllerPanelFrame.size.width - multiSelectionFrame.size.width;
  427. multiSelectionFrame.origin.y = controllerPanelFrame.origin.y - multiSelectionFrame.size.height;
  428. }
  429. _multiSelectionView.frame = multiSelectionFrame;
  430. }
  431. - (void)_startPlayback
  432. {
  433. if (_playerIsSetup)
  434. return;
  435. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  436. if (!self.fileFromMediaLibrary && !self.url && !self.mediaList) {
  437. [self _stopPlayback];
  438. return;
  439. }
  440. if (self.pathToExternalSubtitlesFile)
  441. _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[[NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFilePath, self.pathToExternalSubtitlesFile]]];
  442. else
  443. _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[@"-vvvv"]];
  444. _mediaPlayer = _listPlayer.mediaPlayer;
  445. [_mediaPlayer setDelegate:self];
  446. [_mediaPlayer setDrawable:self.movieView];
  447. if ([[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue] != 0)
  448. [_mediaPlayer setRate: [[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue]];
  449. if ([[defaults objectForKey:kVLCSettingDeinterlace] intValue] != 0)
  450. [_mediaPlayer setDeinterlaceFilter:@"blend"];
  451. else
  452. [_mediaPlayer setDeinterlaceFilter:nil];
  453. if (self.pathToExternalSubtitlesFile)
  454. [_mediaPlayer openVideoSubTitlesFromFile:self.pathToExternalSubtitlesFile];
  455. self.trackNameLabel.text = self.artistNameLabel.text = self.albumNameLabel.text = @"";
  456. VLCMedia *media;
  457. if (self.fileFromMediaLibrary) {
  458. MLFile *item = self.fileFromMediaLibrary;
  459. media = [VLCMedia mediaWithURL:[NSURL URLWithString:item.url]];
  460. } else if (self.mediaList) {
  461. media = [self.mediaList mediaAtIndex:self.itemInMediaListToBePlayedFirst];
  462. [media parse];
  463. } else {
  464. media = [VLCMedia mediaWithURL:self.url];
  465. [media parse];
  466. }
  467. NSMutableDictionary *mediaDictionary = [[NSMutableDictionary alloc] init];
  468. [mediaDictionary setObject:[defaults objectForKey:kVLCSettingNetworkCaching] forKey:kVLCSettingNetworkCaching];
  469. [mediaDictionary setObject:[[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue forKey:kVLCSettingStretchAudio];
  470. [mediaDictionary setObject:[defaults objectForKey:kVLCSettingTextEncoding] forKey:kVLCSettingTextEncoding];
  471. [mediaDictionary setObject:[defaults objectForKey:kVLCSettingSkipLoopFilter] forKey:kVLCSettingSkipLoopFilter];
  472. [NSTimeZone resetSystemTimeZone];
  473. NSString *tzName = [[NSTimeZone systemTimeZone] name];
  474. NSArray *tzNames = @[@"America/Adak", @"America/Anchorage", @"America/Boise", @"America/Chicago", @"America/Denver", @"America/Detroit", @"America/Indiana/Indianapolis", @"America/Indiana/Knox", @"America/Indiana/Marengo", @"America/Indiana/Petersburg", @"America/Indiana/Tell_City", @"America/Indiana/Vevay", @"America/Indiana/Vincennes", @"America/Indiana/Winamac", @"America/Juneau", @"America/Kentucky/Louisville", @"America/Kentucky/Monticello", @"America/Los_Angeles", @"America/Menominee", @"America/Metlakatla", @"America/New_York", @"America/Nome", @"America/North_Dakota/Beulah", @"America/North_Dakota/Center", @"America/North_Dakota/New_Salem", @"America/Phoenix", @"America/Puerto_Rico", @"America/Shiprock", @"America/Sitka", @"America/St_Thomas", @"America/Thule", @"America/Yakutat", @"Pacific/Guam", @"Pacific/Honolulu", @"Pacific/Johnston", @"Pacific/Kwajalein", @"Pacific/Midway", @"Pacific/Pago_Pago", @"Pacific/Saipan", @"Pacific/Wake"];
  475. if ([tzNames containsObject:tzName] || [[tzName stringByDeletingLastPathComponent] isEqualToString:@"US"]) {
  476. NSArray *tracksInfo = media.tracksInformation;
  477. for (NSUInteger x = 0; x < tracksInfo.count; x++) {
  478. if ([[tracksInfo[x] objectForKey:VLCMediaTracksInformationType] isEqualToString:VLCMediaTracksInformationTypeAudio])
  479. {
  480. NSInteger fourcc = [[tracksInfo[x] objectForKey:VLCMediaTracksInformationCodec] integerValue];
  481. switch (fourcc) {
  482. case 540161377:
  483. case 1647457633:
  484. case 858612577:
  485. case 862151027:
  486. case 862151013:
  487. case 1684566644:
  488. case 2126701:
  489. {
  490. if (![self _blobCheck]) {
  491. [mediaDictionary setObject:[NSNull null] forKey:@"no-audio"];
  492. APLog(@"audio playback disabled because an unsupported codec was found");
  493. }
  494. break;
  495. }
  496. default:
  497. break;
  498. }
  499. }
  500. }
  501. }
  502. if (self.mediaList) {
  503. VLCMediaList *list = self.mediaList;
  504. NSUInteger count = list.count;
  505. for (NSUInteger x = 0; x < count; x++)
  506. [[list mediaAtIndex:x] addOptions:mediaDictionary];
  507. [_listPlayer setMediaList:self.mediaList];
  508. } else {
  509. [media addOptions:mediaDictionary];
  510. [_listPlayer setRootMedia:media];
  511. }
  512. [_listPlayer setRepeatMode:VLCDoNotRepeat];
  513. self.positionSlider.value = 0.;
  514. [self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
  515. self.timeDisplay.accessibilityLabel = @"";
  516. if (![self _isMediaSuitableForDevice]) {
  517. UIAlertView * alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"DEVICE_TOOSLOW_TITLE", nil) message:[NSString stringWithFormat:NSLocalizedString(@"DEVICE_TOOSLOW", nil), [[UIDevice currentDevice] model], self.fileFromMediaLibrary.title] delegate:self cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", nil) otherButtonTitles:NSLocalizedString(@"BUTTON_OPEN", nil), nil];
  518. [alert show];
  519. } else
  520. [self _playNewMedia];
  521. if (![self hasExternalDisplay])
  522. self.brightnessSlider.value = [UIScreen mainScreen].brightness * 2.;
  523. }
  524. - (BOOL)_isMediaSuitableForDevice
  525. {
  526. if (!self.fileFromMediaLibrary)
  527. return YES;
  528. NSUInteger totalNumberOfPixels = [[[self.fileFromMediaLibrary videoTrack] valueForKey:@"width"] doubleValue] * [[[self.fileFromMediaLibrary videoTrack] valueForKey:@"height"] doubleValue];
  529. NSInteger speedCategory = [[UIDevice currentDevice] speedCategory];
  530. if (speedCategory == 1) {
  531. // iPhone 3GS, iPhone 4, first gen. iPad, 3rd and 4th generation iPod touch
  532. return (totalNumberOfPixels < 600000); // between 480p and 720p
  533. } else if (speedCategory == 2) {
  534. // iPhone 4S, iPad 2 and 3, iPod 4 and 5
  535. return (totalNumberOfPixels < 922000); // 720p
  536. } else if (speedCategory == 3) {
  537. // iPhone 5, iPad 4
  538. return (totalNumberOfPixels < 2074000); // 1080p
  539. } else if (speedCategory == 4) {
  540. // iPhone 6, 2014 iPads
  541. return (totalNumberOfPixels < 8850000); // 4K
  542. }
  543. return YES;
  544. }
  545. - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
  546. {
  547. if (buttonIndex == 1)
  548. [self _playNewMedia];
  549. else {
  550. [self _stopPlayback];
  551. [self closePlayback:nil];
  552. }
  553. }
  554. - (void)_playNewMedia
  555. {
  556. NSNumber *playbackPositionInTime = @(0);
  557. CGFloat lastPosition = .0;
  558. NSInteger duration = 0;
  559. MLFile *matchedFile;
  560. // Set last selected equalizer profile
  561. unsigned int profile = (unsigned int)[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingEqualizerProfile] integerValue];
  562. [_mediaPlayer resetEqualizerFromProfile:profile];
  563. [_mediaPlayer setPreAmplification:[_mediaPlayer preAmplification]];
  564. [_equalizerView reloadData];
  565. if (self.fileFromMediaLibrary)
  566. matchedFile = self.fileFromMediaLibrary;
  567. else if (self.mediaList) {
  568. /* TODO: move this code to MLKit */
  569. NSString *path = [[[self.mediaList mediaAtIndex:self.itemInMediaListToBePlayedFirst] url] absoluteString];
  570. NSString *componentString = @"";
  571. NSArray *pathComponents = [path componentsSeparatedByString:@"/"];
  572. NSUInteger componentCount = pathComponents.count;
  573. if ([pathComponents[componentCount - 2] isEqualToString:@"Documents"])
  574. componentString = [path lastPathComponent];
  575. else {
  576. NSUInteger firstElement = [pathComponents indexOfObject:@"Documents"] + 1;
  577. for (NSUInteger x = 0; x < componentCount - firstElement; x++) {
  578. if (x == 0)
  579. componentString = [componentString stringByAppendingFormat:@"%@", pathComponents[firstElement + x]];
  580. else
  581. componentString = [componentString stringByAppendingFormat:@"/%@", pathComponents[firstElement + x]];
  582. }
  583. }
  584. NSFetchRequest *request = [[NSFetchRequest alloc] init];
  585. NSManagedObjectContext *moc = [[MLMediaLibrary sharedMediaLibrary] managedObjectContext];
  586. NSEntityDescription *entity = [NSEntityDescription entityForName:@"File" inManagedObjectContext:moc];
  587. [request setEntity:entity];
  588. [request setPredicate:[NSPredicate predicateWithFormat:@"url CONTAINS %@", componentString]];
  589. NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)];
  590. [request setSortDescriptors:@[descriptor]];
  591. NSArray *matches = [moc executeFetchRequest:request error:nil];
  592. if (matches.count > 0)
  593. matchedFile = matches[0];
  594. }
  595. if (matchedFile.lastPosition)
  596. lastPosition = matchedFile.lastPosition.floatValue;
  597. duration = matchedFile.duration.intValue;
  598. if (lastPosition < .95) {
  599. if (duration != 0)
  600. playbackPositionInTime = @(lastPosition * (duration / 1000.));
  601. }
  602. if (playbackPositionInTime.intValue > 0 && (duration * lastPosition - duration) < -60000) {
  603. [_mediaPlayer.media addOptions:@{@"start-time": playbackPositionInTime}];
  604. APLog(@"set starttime to %i", playbackPositionInTime.intValue);
  605. }
  606. [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
  607. [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];
  608. if (self.mediaList)
  609. [_listPlayer playItemAtIndex:self.itemInMediaListToBePlayedFirst];
  610. else
  611. [_listPlayer playMedia:_listPlayer.rootMedia];
  612. if (matchedFile) {
  613. if (matchedFile.lastAudioTrack.intValue > 0)
  614. _mediaPlayer.currentAudioTrackIndex = matchedFile.lastAudioTrack.intValue;
  615. if (matchedFile.lastSubtitleTrack.intValue > 0)
  616. _mediaPlayer.currentVideoSubTitleIndex = matchedFile.lastSubtitleTrack.intValue;
  617. }
  618. self.playbackSpeedSlider.value = [self _playbackSpeed];
  619. [self _updatePlaybackSpeedIndicator];
  620. self.audioDelaySlider.value = _mediaPlayer.currentAudioPlaybackDelay / 1000000;
  621. self.audioDelayIndicator.text = [NSString stringWithFormat:@"%1.00f s", self.audioDelaySlider.value];
  622. self.spuDelaySlider.value = _mediaPlayer.currentVideoSubTitleDelay / 1000000;
  623. self.spuDelayIndicator.text = [NSString stringWithFormat:@"%1.00f s", self.spuDelaySlider.value];
  624. _currentAspectRatioMask = 0;
  625. _mediaPlayer.videoAspectRatio = NULL;
  626. /* some demuxers don't respect :start-time, so re-try here */
  627. if (lastPosition < .95 && _mediaPlayer.position < lastPosition && (duration * lastPosition - duration) < -60000)
  628. _mediaPlayer.position = lastPosition;
  629. [self _resetIdleTimer];
  630. _playerIsSetup = YES;
  631. }
  632. - (void)viewWillDisappear:(BOOL)animated
  633. {
  634. [self _stopPlayback];
  635. _viewAppeared = NO;
  636. if (_idleTimer) {
  637. [_idleTimer invalidate];
  638. _idleTimer = nil;
  639. }
  640. [self.navigationController setNavigationBarHidden:NO animated:YES];
  641. if (!SYSTEM_RUNS_IOS7_OR_LATER)
  642. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackOpaque;
  643. [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
  644. [super viewWillDisappear:animated];
  645. // hide filter UI for next run
  646. if (!_videoFiltersHidden)
  647. _videoFiltersHidden = YES;
  648. if (!_playbackSpeedViewHidden)
  649. _playbackSpeedViewHidden = YES;
  650. }
  651. - (void)_stopPlayback
  652. {
  653. if (_mediaPlayer) {
  654. @try {
  655. [_mediaPlayer removeObserver:self forKeyPath:@"time"];
  656. [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
  657. }
  658. @catch (NSException *exception) {
  659. APLog(@"we weren't an observer yet");
  660. }
  661. if (_mediaPlayer.media) {
  662. [_mediaPlayer pause];
  663. [self _saveCurrentState];
  664. [_mediaPlayer stop];
  665. }
  666. if (_mediaPlayer)
  667. _mediaPlayer = nil;
  668. if (_listPlayer)
  669. _listPlayer = nil;
  670. }
  671. if (_fileFromMediaLibrary)
  672. _fileFromMediaLibrary = nil;
  673. if (_mediaList)
  674. _mediaList = nil;
  675. if (_url)
  676. _url = nil;
  677. if (_pathToExternalSubtitlesFile) {
  678. NSFileManager *fileManager = [NSFileManager defaultManager];
  679. if ([fileManager fileExistsAtPath:_pathToExternalSubtitlesFile])
  680. [fileManager removeItemAtPath:_pathToExternalSubtitlesFile error:nil];
  681. _pathToExternalSubtitlesFile = nil;
  682. }
  683. _playerIsSetup = NO;
  684. }
  685. - (void)_saveCurrentState
  686. {
  687. if (self.fileFromMediaLibrary) {
  688. @try {
  689. MLFile *item = self.fileFromMediaLibrary;
  690. item.lastPosition = @([_mediaPlayer position]);
  691. item.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
  692. item.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
  693. }
  694. @catch (NSException *exception) {
  695. APLog(@"failed to save current media state - file removed?");
  696. }
  697. } else {
  698. NSArray *files = [MLFile fileForURL:[[_mediaPlayer.media url] absoluteString]];
  699. if (files.count > 0) {
  700. MLFile *fileFromList = files[0];
  701. fileFromList.lastPosition = @([_mediaPlayer position]);
  702. fileFromList.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
  703. fileFromList.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
  704. }
  705. }
  706. }
  707. - (NSString *)_resolveFontName
  708. {
  709. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  710. BOOL bold = [[defaults objectForKey:kVLCSettingSubtitlesBoldFont] boolValue];
  711. NSString *font = [defaults objectForKey:kVLCSettingSubtitlesFont];
  712. NSDictionary *fontMap = @{
  713. @"AmericanTypewriter": @"AmericanTypewriter-Bold",
  714. @"ArialMT": @"Arial-BoldMT",
  715. @"ArialHebrew": @"ArialHebrew-Bold",
  716. @"ChalkboardSE-Regular": @"ChalkboardSE-Bold",
  717. @"CourierNewPSMT": @"CourierNewPS-BoldMT",
  718. @"Georgia": @"Georgia-Bold",
  719. @"GillSans": @"GillSans-Bold",
  720. @"GujaratiSangamMN": @"GujaratiSangamMN-Bold",
  721. @"STHeitiSC-Light": @"STHeitiSC-Medium",
  722. @"STHeitiTC-Light": @"STHeitiTC-Medium",
  723. @"HelveticaNeue": @"HelveticaNeue-Bold",
  724. @"HiraKakuProN-W3": @"HiraKakuProN-W6",
  725. @"HiraMinProN-W3": @"HiraMinProN-W6",
  726. @"HoeflerText-Regular": @"HoeflerText-Black",
  727. @"Kailasa": @"Kailasa-Bold",
  728. @"KannadaSangamMN": @"KannadaSangamMN-Bold",
  729. @"MalayalamSangamMN": @"MalayalamSangamMN-Bold",
  730. @"OriyaSangamMN": @"OriyaSangamMN-Bold",
  731. @"SinhalaSangamMN": @"SinhalaSangamMN-Bold",
  732. @"SnellRoundhand": @"SnellRoundhand-Bold",
  733. @"TamilSangamMN": @"TamilSangamMN-Bold",
  734. @"TeluguSangamMN": @"TeluguSangamMN-Bold",
  735. @"TimesNewRomanPSMT": @"TimesNewRomanPS-BoldMT",
  736. @"Zapfino": @"Zapfino"
  737. };
  738. if (!bold) {
  739. return font;
  740. } else {
  741. return fontMap[font];
  742. }
  743. }
  744. #pragma mark - remote events
  745. - (void)viewDidAppear:(BOOL)animated
  746. {
  747. [super viewDidAppear:animated];
  748. [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
  749. [self becomeFirstResponder];
  750. }
  751. - (void)viewDidDisappear:(BOOL)animated
  752. {
  753. [super viewDidDisappear:animated];
  754. [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
  755. [self resignFirstResponder];
  756. [[NSUserDefaults standardUserDefaults] setBool:_displayRemainingTime forKey:kVLCShowRemainingTime];
  757. }
  758. - (BOOL)canBecomeFirstResponder
  759. {
  760. return YES;
  761. }
  762. - (void)remoteControlReceivedWithEvent:(UIEvent *)event
  763. {
  764. switch (event.subtype) {
  765. case UIEventSubtypeRemoteControlPlay:
  766. [_listPlayer play];
  767. break;
  768. case UIEventSubtypeRemoteControlPause:
  769. [_listPlayer pause];
  770. break;
  771. case UIEventSubtypeRemoteControlTogglePlayPause:
  772. [self playPause];
  773. break;
  774. case UIEventSubtypeRemoteControlNextTrack:
  775. [self forward:nil];
  776. break;
  777. case UIEventSubtypeRemoteControlPreviousTrack:
  778. [self backward:nil];
  779. break;
  780. case UIEventSubtypeRemoteControlStop:
  781. [self closePlayback:nil];
  782. break;
  783. default:
  784. break;
  785. }
  786. }
  787. #pragma mark - controls visibility
  788. - (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer
  789. {
  790. LOCKCHECK;
  791. if (!_swipeGesturesEnabled)
  792. return;
  793. if (recognizer.velocity < 0.)
  794. [self closePlayback:nil];
  795. }
  796. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
  797. {
  798. if (touch.view != self.view)
  799. return NO;
  800. return YES;
  801. }
  802. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
  803. {
  804. return YES;
  805. }
  806. - (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated
  807. {
  808. _controlsHidden = hidden;
  809. CGFloat alpha = _controlsHidden? 0.0f: 1.0f;
  810. if (!_controlsHidden) {
  811. _controllerPanel.alpha = 0.0f;
  812. _controllerPanel.hidden = !_videoFiltersHidden;
  813. _controllerPanelLandscape.alpha = 0.0f;
  814. _controllerPanelLandscape.hidden = !_videoFiltersHidden;
  815. _toolbar.alpha = 0.0f;
  816. _toolbar.hidden = NO;
  817. _videoFilterView.alpha = 0.0f;
  818. _videoFilterView.hidden = _videoFiltersHidden;
  819. _playbackSpeedView.alpha = 0.0f;
  820. _playbackSpeedView.hidden = _playbackSpeedViewHidden;
  821. _trackSelectorContainer.alpha = 0.0f;
  822. _trackSelectorContainer.hidden = YES;
  823. _equalizerView.alpha = 0.0f;
  824. _equalizerView.hidden = YES;
  825. if (_sleepTimerContainer) {
  826. _sleepTimerContainer.alpha = 0.0f;
  827. _sleepTimerContainer.hidden = YES;
  828. }
  829. _multiSelectionView.alpha = 0.0f;
  830. _multiSelectionView.hidden = YES;
  831. }
  832. void (^animationBlock)() = ^() {
  833. _controllerPanel.alpha = alpha;
  834. _controllerPanelLandscape.alpha = alpha;
  835. _toolbar.alpha = alpha;
  836. _videoFilterView.alpha = alpha;
  837. _playbackSpeedView.alpha = alpha;
  838. _trackSelectorContainer.alpha = alpha;
  839. _equalizerView.alpha = alpha;
  840. _multiSelectionView.alpha = alpha;
  841. if (_sleepTimerContainer)
  842. _sleepTimerContainer.alpha = alpha;
  843. };
  844. void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
  845. _controllerPanel.hidden = _videoFiltersHidden ? _controlsHidden : NO;
  846. _controllerPanelLandscape.hidden = _videoFiltersHidden ? _controlsHidden : NO;
  847. _toolbar.hidden = _controlsHidden;
  848. _videoFilterView.hidden = _videoFiltersHidden;
  849. _playbackSpeedView.hidden = _playbackSpeedViewHidden;
  850. _trackSelectorContainer.hidden = YES;
  851. _equalizerView.hidden = YES;
  852. if (_sleepTimerContainer)
  853. _sleepTimerContainer.hidden = YES;
  854. _multiSelectionView.hidden = YES;
  855. };
  856. UIStatusBarAnimation animationType = animated? UIStatusBarAnimationFade: UIStatusBarAnimationNone;
  857. NSTimeInterval animationDuration = animated? 0.3: 0.0;
  858. [[UIApplication sharedApplication] setStatusBarHidden:_viewAppeared ? _controlsHidden : NO withAnimation:animationType];
  859. [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
  860. _volumeView.hidden = _volumeViewLandscape.hidden = _controllerPanel.hidden;
  861. }
  862. - (void)toggleControlsVisible
  863. {
  864. if (_controlsHidden && !_videoFiltersHidden)
  865. _videoFiltersHidden = YES;
  866. [self setControlsHidden:!_controlsHidden animated:YES];
  867. }
  868. - (void)_resetIdleTimer
  869. {
  870. if (!_idleTimer)
  871. _idleTimer = [NSTimer scheduledTimerWithTimeInterval:4.
  872. target:self
  873. selector:@selector(idleTimerExceeded)
  874. userInfo:nil
  875. repeats:NO];
  876. else {
  877. if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 4.)
  878. [_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:4.]];
  879. }
  880. }
  881. - (void)idleTimerExceeded
  882. {
  883. _idleTimer = nil;
  884. if (!_controlsHidden)
  885. [self toggleControlsVisible];
  886. if (!_videoFiltersHidden)
  887. _videoFiltersHidden = YES;
  888. if (!_playbackSpeedViewHidden)
  889. _playbackSpeedViewHidden = YES;
  890. if (self.scrubIndicatorView.hidden == NO)
  891. self.scrubIndicatorView.hidden = YES;
  892. }
  893. - (UIResponder *)nextResponder
  894. {
  895. [self _resetIdleTimer];
  896. return [super nextResponder];
  897. }
  898. #pragma mark - controls
  899. - (IBAction)closePlayback:(id)sender
  900. {
  901. LOCKCHECK;
  902. [self setControlsHidden:NO animated:NO];
  903. [self.navigationController dismissViewControllerAnimated:YES completion:^{
  904. // switch back to the caller when user presses "Done" or playback fails
  905. if (self.successCallback && [sender isKindOfClass:[UIBarButtonItem class]])
  906. [[UIApplication sharedApplication] openURL:self.successCallback];
  907. else if (self.errorCallback && _playbackFailed)
  908. [[UIApplication sharedApplication] openURL:self.errorCallback];
  909. }];
  910. }
  911. - (IBAction)positionSliderAction:(UISlider *)sender
  912. {
  913. LOCKCHECK;
  914. /* we need to limit the number of events sent by the slider, since otherwise, the user
  915. * wouldn't see the I-frames when seeking on current mobile devices. This isn't a problem
  916. * within the Simulator, but especially on older ARMv7 devices, it's clearly noticeable. */
  917. [self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
  918. if (_mediaDuration > 0) {
  919. VLCTime *newPosition = [VLCTime timeWithInt:(int)(_positionSlider.value * _mediaDuration)];
  920. [self.timeDisplay setTitle:newPosition.stringValue forState:UIControlStateNormal];
  921. self.timeDisplay.accessibilityLabel = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PLAYBACK_POSITION", nil), newPosition.stringValue];
  922. _positionSet = NO;
  923. }
  924. [self _resetIdleTimer];
  925. }
  926. - (void)_setPositionForReal
  927. {
  928. if (!_positionSet) {
  929. _mediaPlayer.position = _positionSlider.value;
  930. _positionSet = YES;
  931. }
  932. }
  933. - (IBAction)positionSliderTouchDown:(id)sender
  934. {
  935. LOCKCHECK;
  936. [self _updateScrubLabel];
  937. self.scrubIndicatorView.hidden = NO;
  938. _isScrubbing = YES;
  939. }
  940. - (IBAction)positionSliderTouchUp:(id)sender
  941. {
  942. LOCKCHECK;
  943. self.scrubIndicatorView.hidden = YES;
  944. _isScrubbing = NO;
  945. }
  946. - (void)_updateScrubLabel
  947. {
  948. float speed = self.positionSlider.scrubbingSpeed;
  949. if (speed == 1.)
  950. self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HIGH", nil);
  951. else if (speed == .5)
  952. self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_HALF", nil);
  953. else if (speed == .25)
  954. self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_QUARTER", nil);
  955. else
  956. self.currentScrubSpeedLabel.text = NSLocalizedString(@"PLAYBACK_SCRUB_FINE", nil);
  957. [self _resetIdleTimer];
  958. }
  959. - (IBAction)positionSliderDrag:(id)sender
  960. {
  961. LOCKCHECK;
  962. [self _updateScrubLabel];
  963. }
  964. - (IBAction)volumeSliderAction:(id)sender
  965. {
  966. LOCKCHECK;
  967. [self _resetIdleTimer];
  968. }
  969. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  970. {
  971. if (!_isScrubbing) {
  972. self.positionSlider.value = [_mediaPlayer position];
  973. }
  974. if (_displayRemainingTime)
  975. [self.timeDisplay setTitle:[[_mediaPlayer remainingTime] stringValue] forState:UIControlStateNormal];
  976. else
  977. [self.timeDisplay setTitle:[[_mediaPlayer time] stringValue] forState:UIControlStateNormal];
  978. }
  979. - (void)mediaPlayerStateChanged:(NSNotification *)aNotification
  980. {
  981. VLCMediaPlayerState currentState = _mediaPlayer.state;
  982. if (currentState == VLCMediaPlayerStateBuffering) {
  983. /* attach delegate */
  984. _mediaPlayer.media.delegate = self;
  985. /* let's update meta data */
  986. [self _updateDisplayedMetadata];
  987. /* on-the-fly values through private API */
  988. [_mediaPlayer performSelector:@selector(setTextRendererFont:) withObject:[self _resolveFontName]];
  989. [_mediaPlayer performSelector:@selector(setTextRendererFontSize:) withObject:[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingSubtitlesFontSize]];
  990. [_mediaPlayer performSelector:@selector(setTextRendererFontColor:) withObject:[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingSubtitlesFontColor]];
  991. _mediaDuration = _listPlayer.mediaPlayer.media.length.intValue;
  992. }
  993. if (currentState == VLCMediaPlayerStateError) {
  994. [self.statusLabel showStatusMessage:NSLocalizedString(@"PLAYBACK_FAILED", nil)];
  995. _playbackFailed = YES;
  996. [self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.];
  997. }
  998. if ((currentState == VLCMediaPlayerStateEnded || currentState == VLCMediaPlayerStateStopped) && _listPlayer.repeatMode == VLCDoNotRepeat) {
  999. if ([_listPlayer.mediaList indexOfMedia:_mediaPlayer.media] == _listPlayer.mediaList.count - 1)
  1000. [self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.];
  1001. }
  1002. UIImage *playPauseImage = [_mediaPlayer isPlaying]? [UIImage imageNamed:@"pauseIcon"] : [UIImage imageNamed:@"playIcon"];
  1003. [_playPauseButton setImage:playPauseImage forState:UIControlStateNormal];
  1004. [_playPauseButtonLandscape setImage:playPauseImage forState:UIControlStateNormal];
  1005. if ([[_mediaPlayer audioTrackIndexes] count] > 2 || [[_mediaPlayer videoSubTitlesIndexes] count] > 1) {
  1006. self.trackSwitcherButton.hidden = NO;
  1007. self.trackSwitcherButtonLandscape.hidden = NO;
  1008. } else {
  1009. self.trackSwitcherButton.hidden = YES;
  1010. self.trackSwitcherButtonLandscape.hidden = YES;
  1011. }
  1012. if (_mediaPlayer.titles.count > 1 || [_mediaPlayer chaptersForTitleIndex:_mediaPlayer.currentTitleIndex].count > 1)
  1013. _multiSelectionView.mediaHasChapters = YES;
  1014. else
  1015. _multiSelectionView.mediaHasChapters = NO;
  1016. }
  1017. - (IBAction)playPause
  1018. {
  1019. LOCKCHECK;
  1020. if ([_mediaPlayer isPlaying])
  1021. [_listPlayer pause];
  1022. else
  1023. [_listPlayer play];
  1024. }
  1025. - (IBAction)forward:(id)sender
  1026. {
  1027. LOCKCHECK;
  1028. if (self.mediaList)
  1029. [_listPlayer next];
  1030. else
  1031. [_mediaPlayer mediumJumpForward];
  1032. }
  1033. - (IBAction)backward:(id)sender
  1034. {
  1035. LOCKCHECK;
  1036. if (self.mediaList)
  1037. [_listPlayer previous];
  1038. else
  1039. [_mediaPlayer mediumJumpBackward];
  1040. }
  1041. - (IBAction)switchTrack:(id)sender
  1042. {
  1043. LOCKCHECK;
  1044. if (_trackSelectorContainer.hidden == YES || _switchingTracksNotChapters == NO) {
  1045. _switchingTracksNotChapters = YES;
  1046. [_trackSelectorTableView reloadData];
  1047. _trackSelectorContainer.hidden = NO;
  1048. _trackSelectorContainer.alpha = 1.;
  1049. if (!_playbackSpeedViewHidden)
  1050. self.playbackSpeedView.hidden = _playbackSpeedViewHidden = YES;
  1051. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1052. if (!_controlsHidden) {
  1053. self.controllerPanel.hidden = _controlsHidden = YES;
  1054. self.controllerPanelLandscape.hidden = YES;
  1055. }
  1056. }
  1057. self.videoFilterView.hidden = _videoFiltersHidden = YES;
  1058. } else {
  1059. _trackSelectorContainer.hidden = YES;
  1060. _switchingTracksNotChapters = NO;
  1061. }
  1062. }
  1063. - (IBAction)toggleTimeDisplay:(id)sender
  1064. {
  1065. LOCKCHECK;
  1066. _displayRemainingTime = !_displayRemainingTime;
  1067. [self _resetIdleTimer];
  1068. }
  1069. - (IBAction)sleepTimer:(id)sender
  1070. {
  1071. if (!_sleepTimerContainer) {
  1072. self.playbackSpeedView.hidden = YES;
  1073. _sleepTimerContainer = [[VLCFrostedGlasView alloc] initWithFrame:CGRectMake(0., 0., 300., 162.)];
  1074. _sleepTimerContainer.center = self.view.center;
  1075. _sleepTimerContainer.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;
  1076. _sleepTimeDatePicker = [[UIDatePicker alloc] init];
  1077. if ([[UIDevice currentDevice] speedCategory] >= 3) {
  1078. _sleepTimeDatePicker.opaque = NO;
  1079. _sleepTimeDatePicker.backgroundColor = [UIColor clearColor];
  1080. } else
  1081. _sleepTimeDatePicker.backgroundColor = [UIColor blackColor];
  1082. _sleepTimeDatePicker.tintColor = [UIColor VLCLightTextColor];
  1083. [_sleepTimerContainer addSubview:_sleepTimeDatePicker];
  1084. /* adapt the date picker style to suit our needs */
  1085. [_sleepTimeDatePicker setValue:[UIColor whiteColor] forKeyPath:@"textColor"];
  1086. SEL selector = NSSelectorFromString(@"setHighlightsToday:");
  1087. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDatePicker instanceMethodSignatureForSelector:selector]];
  1088. BOOL no = NO;
  1089. [invocation setSelector:selector];
  1090. [invocation setArgument:&no atIndex:2];
  1091. [invocation invokeWithTarget:_sleepTimeDatePicker];
  1092. if (_sleepTimerContainer.subviews.count > 0) {
  1093. NSArray *subviewsOfSubview = [_sleepTimeDatePicker.subviews[0] subviews];
  1094. NSUInteger subviewCount = subviewsOfSubview.count;
  1095. for (NSUInteger x = 0; x < subviewCount; x++) {
  1096. if ([subviewsOfSubview[x] isKindOfClass:[UILabel class]])
  1097. [subviewsOfSubview[x] setTextColor:[UIColor VLCLightTextColor]];
  1098. }
  1099. }
  1100. _sleepTimeDatePicker.datePickerMode = UIDatePickerModeCountDownTimer;
  1101. _sleepTimeDatePicker.minuteInterval = 5;
  1102. _sleepTimeDatePicker.minimumDate = [NSDate date];
  1103. _sleepTimeDatePicker.countDownDuration = 1200.;
  1104. [_sleepTimeDatePicker addTarget:self action:@selector(sleepTimerAction:) forControlEvents:UIControlEventValueChanged];
  1105. [self.view addSubview:_sleepTimerContainer];
  1106. }
  1107. if (!_playbackSpeedViewHidden)
  1108. self.playbackSpeedView.hidden = _playbackSpeedViewHidden = YES;
  1109. if (_equalizerView.hidden == NO)
  1110. _equalizerView.hidden = YES;
  1111. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1112. if (!_controlsHidden) {
  1113. self.controllerPanel.hidden = _controlsHidden = YES;
  1114. self.controllerPanelLandscape.hidden = YES;
  1115. }
  1116. }
  1117. self.videoFilterView.hidden = _videoFiltersHidden = YES;
  1118. _sleepTimerContainer.alpha = 1.;
  1119. _sleepTimerContainer.hidden = NO;
  1120. }
  1121. - (IBAction)sleepTimerAction:(id)sender
  1122. {
  1123. if (_sleepTimer) {
  1124. [_sleepTimer invalidate];
  1125. _sleepTimer = nil;
  1126. }
  1127. _sleepTimer = [NSTimer scheduledTimerWithTimeInterval:_sleepTimeDatePicker.countDownDuration target:self selector:@selector(closePlayback:) userInfo:nil repeats:NO];
  1128. }
  1129. - (void)moreActions:(id)sender
  1130. {
  1131. if (_multiSelectionView.hidden == NO) {
  1132. [UIView animateWithDuration:.3
  1133. animations:^{
  1134. _multiSelectionView.hidden = YES;
  1135. }
  1136. completion:^(BOOL finished){
  1137. }];
  1138. return;
  1139. }
  1140. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1141. if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation))
  1142. _multiSelectionView.showsEqualizer = YES;
  1143. else
  1144. _multiSelectionView.showsEqualizer = NO;
  1145. }
  1146. CGRect workFrame = _multiSelectionView.frame;
  1147. workFrame.size = [_multiSelectionView proposedDisplaySize];
  1148. workFrame.origin.x = self.toolbar.frame.size.width - workFrame.size.width;
  1149. _multiSelectionView.alpha = 1.0f;
  1150. /* animate */
  1151. _multiSelectionView.frame = CGRectMake(workFrame.origin.x, workFrame.origin.y + workFrame.size.height, workFrame.size.width, 0.);
  1152. [UIView animateWithDuration:.3
  1153. animations:^{
  1154. _multiSelectionView.frame = workFrame;
  1155. _multiSelectionView.hidden = NO;
  1156. }
  1157. completion:^(BOOL finished){
  1158. }];
  1159. [self _resetIdleTimer];
  1160. }
  1161. #pragma mark - multi-select delegation
  1162. - (void)toggleUILock
  1163. {
  1164. _interfaceIsLocked = !_interfaceIsLocked;
  1165. }
  1166. - (void)toggleEqualizer
  1167. {
  1168. LOCKCHECK;
  1169. if (_equalizerView.hidden) {
  1170. if (!_playbackSpeedViewHidden)
  1171. self.playbackSpeedView.hidden = _playbackSpeedViewHidden = YES;
  1172. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1173. if (!_controlsHidden) {
  1174. self.controllerPanel.hidden = _controlsHidden = YES;
  1175. self.controllerPanelLandscape.hidden = YES;
  1176. self.toolbar.hidden = YES;
  1177. }
  1178. }
  1179. self.videoFilterView.hidden = _videoFiltersHidden = YES;
  1180. _equalizerView.alpha = 1.;
  1181. _equalizerView.hidden = NO;
  1182. } else
  1183. _equalizerView.hidden = YES;
  1184. }
  1185. - (void)toggleChapterAndTitleSelector
  1186. {
  1187. LOCKCHECK;
  1188. if (_trackSelectorContainer.hidden == YES || _switchingTracksNotChapters == YES) {
  1189. _switchingTracksNotChapters = NO;
  1190. [_trackSelectorTableView reloadData];
  1191. _trackSelectorContainer.hidden = NO;
  1192. _trackSelectorContainer.alpha = 1.;
  1193. if (!_playbackSpeedViewHidden)
  1194. self.playbackSpeedView.hidden = _playbackSpeedViewHidden = YES;
  1195. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1196. if (!_controlsHidden) {
  1197. self.controllerPanel.hidden = _controlsHidden = YES;
  1198. self.controllerPanelLandscape.hidden = YES;
  1199. }
  1200. }
  1201. self.videoFilterView.hidden = _videoFiltersHidden = YES;
  1202. } else {
  1203. _trackSelectorContainer.hidden = YES;
  1204. }
  1205. }
  1206. - (void)toggleRepeatMode
  1207. {
  1208. LOCKCHECK;
  1209. if (_listPlayer.repeatMode == VLCDoNotRepeat) {
  1210. _listPlayer.repeatMode = VLCRepeatCurrentItem;
  1211. _multiSelectionView.displayRepeatOne = YES;
  1212. } else {
  1213. _listPlayer.repeatMode = VLCDoNotRepeat;
  1214. _multiSelectionView.displayRepeatOne = NO;
  1215. }
  1216. }
  1217. - (void)hideMenu
  1218. {
  1219. [UIView animateWithDuration:.2
  1220. animations:^{
  1221. _multiSelectionView.hidden = YES;
  1222. }
  1223. completion:^(BOOL finished){
  1224. }];
  1225. [self _resetIdleTimer];
  1226. }
  1227. #pragma mark - track selector table view
  1228. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  1229. {
  1230. NSInteger ret = 0;
  1231. if (_switchingTracksNotChapters == YES) {
  1232. if (_mediaPlayer.audioTrackIndexes.count > 2)
  1233. ret++;
  1234. if (_mediaPlayer.videoSubTitlesIndexes.count > 1)
  1235. ret++;
  1236. } else {
  1237. if ([_mediaPlayer countOfTitles] > 1)
  1238. ret++;
  1239. if ([_mediaPlayer chaptersForTitleIndex:_mediaPlayer.currentTitleIndex].count > 1)
  1240. ret++;
  1241. }
  1242. return ret;
  1243. }
  1244. - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
  1245. {
  1246. UITableViewHeaderFooterView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
  1247. if (!view)
  1248. view = [[VLCTrackSelectorHeaderView alloc] initWithReuseIdentifier:TRACK_SELECTOR_TABLEVIEW_SECTIONHEADER];
  1249. return view;
  1250. }
  1251. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  1252. {
  1253. if (_switchingTracksNotChapters == YES) {
  1254. if (_mediaPlayer.audioTrackIndexes.count > 2 && section == 0)
  1255. return NSLocalizedString(@"CHOOSE_AUDIO_TRACK", nil);
  1256. if (_mediaPlayer.videoSubTitlesIndexes.count > 1)
  1257. return NSLocalizedString(@"CHOOSE_SUBTITLE_TRACK", nil);
  1258. } else {
  1259. if (_mediaPlayer.titles.count > 1 && section == 0)
  1260. return NSLocalizedString(@"CHOOSE_TITLE", nil);
  1261. if ([_mediaPlayer chaptersForTitleIndex:_mediaPlayer.currentTitleIndex].count > 1)
  1262. return NSLocalizedString(@"CHOOSE_CHAPTER", nil);
  1263. }
  1264. return @"unknown track type";
  1265. }
  1266. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  1267. {
  1268. VLCTrackSelectorTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL forIndexPath:indexPath];
  1269. if (!cell)
  1270. cell = [[VLCTrackSelectorTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TRACK_SELECTOR_TABLEVIEW_CELL];
  1271. NSInteger row = indexPath.row;
  1272. if (_switchingTracksNotChapters == YES) {
  1273. NSArray *indexArray;
  1274. if (_mediaPlayer.audioTrackIndexes.count > 2 && indexPath.section == 0) {
  1275. indexArray = _mediaPlayer.audioTrackIndexes;
  1276. if ([indexArray indexOfObjectIdenticalTo:[NSNumber numberWithInt:_mediaPlayer.currentAudioTrackIndex]] == row)
  1277. [cell setShowsCurrentTrack:YES];
  1278. else
  1279. [cell setShowsCurrentTrack:NO];
  1280. cell.textLabel.text = [NSString stringWithFormat:@"%@", _mediaPlayer.audioTrackNames[row]];
  1281. } else {
  1282. indexArray = _mediaPlayer.videoSubTitlesIndexes;
  1283. if ([indexArray indexOfObjectIdenticalTo:[NSNumber numberWithInt:_mediaPlayer.currentVideoSubTitleIndex]] == row)
  1284. [cell setShowsCurrentTrack:YES];
  1285. else
  1286. [cell setShowsCurrentTrack:NO];
  1287. cell.textLabel.text = [NSString stringWithFormat:@"%@", _mediaPlayer.videoSubTitlesNames[row]];
  1288. }
  1289. } else {
  1290. if (_mediaPlayer.titles.count > 1 && indexPath.section == 0) {
  1291. cell.textLabel.text = _mediaPlayer.titles[row];
  1292. if (row == _mediaPlayer.currentTitleIndex)
  1293. [cell setShowsCurrentTrack:YES];
  1294. else
  1295. [cell setShowsCurrentTrack:NO];
  1296. } else {
  1297. cell.textLabel.text = [_mediaPlayer chaptersForTitleIndex:_mediaPlayer.currentTitleIndex][row];
  1298. if (row == _mediaPlayer.currentChapterIndex)
  1299. [cell setShowsCurrentTrack:YES];
  1300. else
  1301. [cell setShowsCurrentTrack:NO];
  1302. }
  1303. }
  1304. return cell;
  1305. }
  1306. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  1307. {
  1308. NSInteger audioTrackCount = _mediaPlayer.audioTrackIndexes.count;
  1309. if (audioTrackCount > 2 && section == 0)
  1310. return audioTrackCount;
  1311. return _mediaPlayer.videoSubTitlesIndexes.count;
  1312. }
  1313. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  1314. {
  1315. [tableView deselectRowAtIndexPath:indexPath animated:NO];
  1316. NSInteger index = indexPath.row;
  1317. if (_switchingTracksNotChapters == YES) {
  1318. NSArray *indexArray;
  1319. if (_mediaPlayer.audioTrackIndexes.count > 2 && indexPath.section == 0) {
  1320. indexArray = _mediaPlayer.audioTrackIndexes;
  1321. if (index <= indexArray.count)
  1322. _mediaPlayer.currentAudioTrackIndex = [indexArray[index] intValue];
  1323. } else {
  1324. indexArray = _mediaPlayer.videoSubTitlesIndexes;
  1325. if (index <= indexArray.count)
  1326. _mediaPlayer.currentVideoSubTitleIndex = [indexArray[index] intValue];
  1327. }
  1328. } else {
  1329. if (_mediaPlayer.titles.count > 1 && indexPath.section == 0)
  1330. _mediaPlayer.currentTitleIndex = index;
  1331. else
  1332. _mediaPlayer.currentChapterIndex = index;
  1333. }
  1334. CGFloat alpha = 0.0f;
  1335. _trackSelectorContainer.alpha = 1.0f;
  1336. void (^animationBlock)() = ^() {
  1337. _trackSelectorContainer.alpha = alpha;
  1338. };
  1339. void (^completionBlock)(BOOL finished) = ^(BOOL finished) {
  1340. _trackSelectorContainer.hidden = YES;
  1341. };
  1342. NSTimeInterval animationDuration = .3;
  1343. [UIView animateWithDuration:animationDuration animations:animationBlock completion:completionBlock];
  1344. }
  1345. #pragma mark - multi-touch gestures
  1346. - (void)tapRecognized
  1347. {
  1348. LOCKCHECK;
  1349. if (!_swipeGesturesEnabled)
  1350. return;
  1351. if ([_mediaPlayer isPlaying]) {
  1352. [_listPlayer pause];
  1353. [self.statusLabel showStatusMessage:@" ▌▌"];
  1354. } else {
  1355. [_listPlayer play];
  1356. [self.statusLabel showStatusMessage:@" ►"];
  1357. }
  1358. }
  1359. - (VLCPanType)detectPanTypeForPan:(UIPanGestureRecognizer*)panRecognizer
  1360. {
  1361. NSString *deviceType = [[UIDevice currentDevice] model];
  1362. CGPoint location = [panRecognizer locationInView:self.view];
  1363. CGFloat position = location.x;
  1364. UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
  1365. CGRect screenRect = [[UIScreen mainScreen] bounds];
  1366. CGFloat screenWidth = .0;
  1367. if (orientation == UIDeviceOrientationPortrait)
  1368. screenWidth = screenRect.size.width;
  1369. else
  1370. screenWidth = screenRect.size.height;
  1371. VLCPanType panType = VLCPanTypeVolume; // default or right side of the screen
  1372. if (position < screenWidth / 2)
  1373. panType = VLCPanTypeBrightness;
  1374. // only check for seeking gesture if on iPad , will overwrite last statements if true
  1375. if ([deviceType isEqualToString:@"iPad"]) {
  1376. if (location.y < 110)
  1377. panType = VLCPanTypeSeek;
  1378. }
  1379. return panType;
  1380. }
  1381. - (void)panRecognized:(UIPanGestureRecognizer*)panRecognizer
  1382. {
  1383. LOCKCHECK;
  1384. if (!_swipeGesturesEnabled)
  1385. return;
  1386. CGFloat panDirectionX = [panRecognizer velocityInView:self.view].x;
  1387. CGFloat panDirectionY = [panRecognizer velocityInView:self.view].y;
  1388. if (panRecognizer.state == UIGestureRecognizerStateBegan) // Only detect panType when began to allow more freedom
  1389. _currentPanType = [self detectPanTypeForPan:panRecognizer];
  1390. if (_currentPanType == VLCPanTypeSeek) {
  1391. double timeRemainingDouble = (-_mediaPlayer.remainingTime.intValue*0.001);
  1392. int timeRemaining = timeRemainingDouble;
  1393. if (panDirectionX > 0) {
  1394. if (timeRemaining > 2 ) // to not go outside duration , video will stop
  1395. [_mediaPlayer jumpForward:1];
  1396. } else
  1397. [_mediaPlayer jumpBackward:1];
  1398. } else if (_currentPanType == VLCPanTypeVolume) {
  1399. MPMusicPlayerController *musicPlayer = [MPMusicPlayerController applicationMusicPlayer];
  1400. if (panDirectionY > 0)
  1401. musicPlayer.volume -= 0.01;
  1402. else
  1403. musicPlayer.volume += 0.01;
  1404. } else if (_currentPanType == VLCPanTypeBrightness) {
  1405. CGFloat brightness = [UIScreen mainScreen].brightness;
  1406. if (panDirectionY > 0)
  1407. brightness = brightness - 0.01;
  1408. else
  1409. brightness = brightness + 0.01;
  1410. // Sanity check since -[UIScreen brightness] does not go by 0.01 steps
  1411. if (brightness > 1.0)
  1412. brightness = 1.0;
  1413. else if (brightness < 0.0)
  1414. brightness = 0.0;
  1415. NSAssert(brightness >= 0 && brightness <= 1, @"Brightness must be within 0 and 1 (it is %f)", brightness);
  1416. [[UIScreen mainScreen] setBrightness:brightness];
  1417. self.brightnessSlider.value = brightness * 2.;
  1418. NSString *brightnessHUD = [NSString stringWithFormat:@"%@: %@ %%", NSLocalizedString(@"VFILTER_BRIGHTNESS", nil), [[[NSString stringWithFormat:@"%f",(brightness*100)] componentsSeparatedByString:@"."] objectAtIndex:0]];
  1419. [self.statusLabel showStatusMessage:brightnessHUD];
  1420. }
  1421. if (panRecognizer.state == UIGestureRecognizerStateEnded) {
  1422. _currentPanType = VLCPanTypeNone;
  1423. if ([_mediaPlayer isPlaying])
  1424. [_listPlayer play];
  1425. }
  1426. }
  1427. - (void)swipeRecognized:(UISwipeGestureRecognizer*)swipeRecognizer
  1428. {
  1429. LOCKCHECK;
  1430. if (!_swipeGesturesEnabled)
  1431. return;
  1432. NSString * hudString = @" ";
  1433. int swipeDuration = (int)(_mediaDuration*0.001*0.05);
  1434. if (swipeRecognizer.direction == UISwipeGestureRecognizerDirectionRight) {
  1435. double timeRemainingDouble = (-_mediaPlayer.remainingTime.intValue*0.001);
  1436. int timeRemaining = timeRemainingDouble;
  1437. if (swipeDuration < timeRemaining) {
  1438. if (swipeDuration < 1)
  1439. swipeDuration = 1;
  1440. [_mediaPlayer jumpForward:swipeDuration];
  1441. hudString = [NSString stringWithFormat:@"⇒ %is", swipeDuration];
  1442. } else {
  1443. [_mediaPlayer jumpForward:(timeRemaining - 5)];
  1444. hudString = [NSString stringWithFormat:@"⇒ %is",(timeRemaining - 5)];
  1445. }
  1446. }
  1447. else if (swipeRecognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
  1448. [_mediaPlayer jumpBackward:swipeDuration];
  1449. hudString = [NSString stringWithFormat:@"⇐ %is",swipeDuration];
  1450. }
  1451. if (swipeRecognizer.state == UIGestureRecognizerStateEnded) {
  1452. if ([_mediaPlayer isPlaying])
  1453. [_listPlayer play];
  1454. [self.statusLabel showStatusMessage:hudString];
  1455. }
  1456. }
  1457. #pragma mark - Video Filter UI
  1458. - (IBAction)videoFilterToggle:(id)sender
  1459. {
  1460. LOCKCHECK;
  1461. if (!_playbackSpeedViewHidden)
  1462. self.playbackSpeedView.hidden = _playbackSpeedViewHidden = YES;
  1463. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1464. if (!_controlsHidden) {
  1465. self.controllerPanel.hidden = _controlsHidden = YES;
  1466. self.controllerPanelLandscape.hidden = YES;
  1467. }
  1468. }
  1469. self.videoFilterView.hidden = !_videoFiltersHidden;
  1470. _videoFiltersHidden = self.videoFilterView.hidden;
  1471. }
  1472. - (IBAction)videoFilterSliderAction:(id)sender
  1473. {
  1474. if (sender == self.hueSlider)
  1475. _mediaPlayer.hue = (int)self.hueSlider.value;
  1476. else if (sender == self.contrastSlider)
  1477. _mediaPlayer.contrast = self.contrastSlider.value;
  1478. else if (sender == self.brightnessSlider) {
  1479. if ([self hasExternalDisplay])
  1480. _mediaPlayer.brightness = self.brightnessSlider.value;
  1481. else
  1482. [[UIScreen mainScreen] setBrightness:(self.brightnessSlider.value / 2.)];
  1483. } else if (sender == self.saturationSlider)
  1484. _mediaPlayer.saturation = self.saturationSlider.value;
  1485. else if (sender == self.gammaSlider)
  1486. _mediaPlayer.gamma = self.gammaSlider.value;
  1487. else if (sender == self.resetVideoFilterButton) {
  1488. _mediaPlayer.hue = self.hueSlider.value = 0.;
  1489. _mediaPlayer.contrast = self.contrastSlider.value = 1.;
  1490. _mediaPlayer.brightness = self.brightnessSlider.value = 1.;
  1491. [[UIScreen mainScreen] setBrightness:(self.brightnessSlider.value / 2.)];
  1492. _mediaPlayer.saturation = self.saturationSlider.value = 1.;
  1493. _mediaPlayer.gamma = self.gammaSlider.value = 1.;
  1494. } else
  1495. APLog(@"unknown sender for videoFilterSliderAction");
  1496. [self _resetIdleTimer];
  1497. }
  1498. #pragma mark - equalizer
  1499. - (void)setAmplification:(CGFloat)amplification forBand:(unsigned int)index
  1500. {
  1501. [self _resetIdleTimer];
  1502. if (!_mediaPlayer.equalizerEnabled)
  1503. [_mediaPlayer setEqualizerEnabled:YES];
  1504. [_mediaPlayer setAmplification:amplification forBand:index];
  1505. // For some reason we have to apply again preamp to apply change
  1506. [_mediaPlayer setPreAmplification:[_mediaPlayer preAmplification]];
  1507. }
  1508. - (CGFloat)amplificationOfBand:(unsigned int)index
  1509. {
  1510. return [_mediaPlayer amplificationOfBand:index];
  1511. }
  1512. - (NSArray *)equalizerProfiles
  1513. {
  1514. return _mediaPlayer.equalizerProfiles;
  1515. }
  1516. - (void)resetEqualizerFromProfile:(unsigned int)profile
  1517. {
  1518. [[NSUserDefaults standardUserDefaults] setObject:@(profile) forKey:kVLCSettingEqualizerProfile];
  1519. [_mediaPlayer resetEqualizerFromProfile:profile];
  1520. }
  1521. - (void)setPreAmplification:(CGFloat)preAmplification
  1522. {
  1523. if (!_mediaPlayer.equalizerEnabled)
  1524. [_mediaPlayer setEqualizerEnabled:YES];
  1525. [self _resetIdleTimer];
  1526. [_mediaPlayer setPreAmplification:preAmplification];
  1527. }
  1528. - (CGFloat)preAmplification
  1529. {
  1530. return [_mediaPlayer preAmplification];
  1531. }
  1532. #pragma mark - playback view
  1533. - (IBAction)playbackSliderAction:(UISlider *)sender
  1534. {
  1535. LOCKCHECK;
  1536. if (sender == _playbackSpeedSlider) {
  1537. double speed = pow(2, sender.value / 17.);
  1538. float rate = INPUT_RATE_DEFAULT / speed;
  1539. if (_currentPlaybackRate != rate)
  1540. [_mediaPlayer setRate:INPUT_RATE_DEFAULT / rate];
  1541. _currentPlaybackRate = rate;
  1542. [self _updatePlaybackSpeedIndicator];
  1543. } else if (sender == _audioDelaySlider) {
  1544. _mediaPlayer.currentAudioPlaybackDelay = _audioDelaySlider.value * 1000000;
  1545. _audioDelayIndicator.text = [NSString stringWithFormat:@"%1.2f s", _audioDelaySlider.value];
  1546. } else if (sender == _spuDelaySlider) {
  1547. _mediaPlayer.currentVideoSubTitleDelay = _spuDelaySlider.value * 1000000;
  1548. _spuDelayIndicator.text = [NSString stringWithFormat:@"%1.00f s", _spuDelaySlider.value];
  1549. }
  1550. [self _resetIdleTimer];
  1551. }
  1552. - (void)_updatePlaybackSpeedIndicator
  1553. {
  1554. float f_value = self.playbackSpeedSlider.value;
  1555. double speed = pow(2, f_value / 17.);
  1556. self.playbackSpeedIndicator.text = [NSString stringWithFormat:@"%.2fx", speed];
  1557. /* rate changed, so update the exported info */
  1558. [self performSelectorInBackground:@selector(_updateDisplayedMetadata) withObject:nil];
  1559. }
  1560. - (float)_playbackSpeed
  1561. {
  1562. float f_rate = _mediaPlayer.rate;
  1563. double value = 17 * log(f_rate) / log(2.);
  1564. float returnValue = (int) ((value > 0) ? value + .5 : value - .5);
  1565. if (returnValue < -34.)
  1566. returnValue = -34.;
  1567. else if (returnValue > 34.)
  1568. returnValue = 34.;
  1569. _currentPlaybackRate = returnValue;
  1570. return returnValue;
  1571. }
  1572. - (IBAction)videoDimensionAction:(id)sender
  1573. {
  1574. LOCKCHECK;
  1575. if (sender == self.playbackSpeedButton || sender == self.playbackSpeedButtonLandscape) {
  1576. if (!_videoFiltersHidden)
  1577. self.videoFilterView.hidden = _videoFiltersHidden = YES;
  1578. self.playbackSpeedView.hidden = !_playbackSpeedViewHidden;
  1579. _playbackSpeedViewHidden = self.playbackSpeedView.hidden;
  1580. [self _resetIdleTimer];
  1581. } else if (sender == self.aspectRatioButton) {
  1582. NSUInteger count = [_aspectRatios count];
  1583. if (_currentAspectRatioMask + 1 > count - 1) {
  1584. _mediaPlayer.videoAspectRatio = NULL;
  1585. _mediaPlayer.videoCropGeometry = NULL;
  1586. _currentAspectRatioMask = 0;
  1587. [self.statusLabel showStatusMessage:[NSString stringWithFormat:NSLocalizedString(@"AR_CHANGED", nil), NSLocalizedString(@"DEFAULT", nil)]];
  1588. } else {
  1589. _currentAspectRatioMask++;
  1590. if ([_aspectRatios[_currentAspectRatioMask] isEqualToString:@"FILL_TO_SCREEN"]) {
  1591. UIScreen *screen;
  1592. if (![self hasExternalDisplay])
  1593. screen = [UIScreen mainScreen];
  1594. else
  1595. screen = [UIScreen screens][1];
  1596. float f_ar = screen.bounds.size.width / screen.bounds.size.height;
  1597. if (f_ar == (float)(640./1136.)) // iPhone 5 aka 16:9.01
  1598. _mediaPlayer.videoCropGeometry = "16:9";
  1599. else if (f_ar == (float)(2./3.)) // all other iPhones
  1600. _mediaPlayer.videoCropGeometry = "16:10"; // libvlc doesn't support 2:3 crop
  1601. else if (f_ar == .75) // all iPads
  1602. _mediaPlayer.videoCropGeometry = "4:3";
  1603. else if (f_ar == .5625) // AirPlay
  1604. _mediaPlayer.videoCropGeometry = "16:9";
  1605. else
  1606. APLog(@"unknown screen format %f, can't crop", f_ar);
  1607. [self.statusLabel showStatusMessage:NSLocalizedString(@"FILL_TO_SCREEN", nil)];
  1608. return;
  1609. }
  1610. _mediaPlayer.videoCropGeometry = NULL;
  1611. _mediaPlayer.videoAspectRatio = (char *)[_aspectRatios[_currentAspectRatioMask] UTF8String];
  1612. [self.statusLabel showStatusMessage:[NSString stringWithFormat:NSLocalizedString(@"AR_CHANGED", nil), _aspectRatios[_currentAspectRatioMask]]];
  1613. }
  1614. }
  1615. }
  1616. #pragma mark - background interaction
  1617. - (void)applicationWillResignActive:(NSNotification *)aNotification
  1618. {
  1619. [self _saveCurrentState];
  1620. _mediaPlayer.currentVideoTrackIndex = 0;
  1621. if (![[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingContinueAudioInBackgroundKey] boolValue]) {
  1622. if ([_mediaPlayer isPlaying]) {
  1623. [_mediaPlayer pause];
  1624. _shouldResumePlaying = YES;
  1625. }
  1626. }
  1627. }
  1628. - (void)applicationDidEnterBackground:(NSNotification *)notification
  1629. {
  1630. _shouldResumePlaying = NO;
  1631. }
  1632. - (void)applicationDidBecomeActive:(NSNotification *)notification
  1633. {
  1634. _mediaPlayer.currentVideoTrackIndex = 1;
  1635. if (_shouldResumePlaying) {
  1636. _shouldResumePlaying = NO;
  1637. [_listPlayer play];
  1638. }
  1639. }
  1640. - (void)audioSessionRouteChange:(NSNotification *)notification
  1641. {
  1642. NSArray *outputs = [[AVAudioSession sharedInstance] currentRoute].outputs;
  1643. NSString *portName = [[outputs objectAtIndex:0] portName];
  1644. if (![portName isEqualToString:@"Headphones"] && [_mediaPlayer isPlaying])
  1645. [_listPlayer pause];
  1646. }
  1647. - (void)mediaDidFinishParsing:(VLCMedia *)aMedia
  1648. {
  1649. [self _updateDisplayedMetadata];
  1650. }
  1651. - (void)mediaMetaDataDidChange:(VLCMedia*)aMedia
  1652. {
  1653. [self _updateDisplayedMetadata];
  1654. }
  1655. - (void)_updateDisplayedMetadata
  1656. {
  1657. MLFile *item;
  1658. NSString *title;
  1659. NSString *artist;
  1660. NSString *albumName;
  1661. NSString *trackNumber;
  1662. BOOL mediaIsAudioOnly = YES;
  1663. if (self.fileFromMediaLibrary)
  1664. item = self.fileFromMediaLibrary;
  1665. else if (self.mediaList) {
  1666. NSArray *matches = [MLFile fileForURL:[_mediaPlayer.media.url absoluteString]];
  1667. if (matches.count > 0)
  1668. item = matches[0];
  1669. }
  1670. if (item) {
  1671. if (item.isAlbumTrack) {
  1672. title = item.albumTrack.title;
  1673. artist = item.albumTrack.artist;
  1674. albumName = item.albumTrack.album.name;
  1675. } else
  1676. title = item.title;
  1677. /* MLKit knows better than us if this thing is audio only or not */
  1678. mediaIsAudioOnly = [item isSupportedAudioFile];
  1679. if (mediaIsAudioOnly)
  1680. self.artworkImageView.image = [VLCThumbnailsCache thumbnailForMediaFile:item];
  1681. } else {
  1682. NSDictionary * metaDict = _mediaPlayer.media.metaDictionary;
  1683. /* this is a non file media, so we need to actually check if there is there is
  1684. * a video track included or not */
  1685. NSArray *tracks = _mediaPlayer.media.tracksInformation;
  1686. NSUInteger trackCount = tracks.count;
  1687. for (NSUInteger x = 0 ; x < trackCount; x++) {
  1688. if ([[tracks[x] objectForKey:VLCMediaTracksInformationType] isEqualToString:VLCMediaTracksInformationTypeVideo]) {
  1689. mediaIsAudioOnly = NO;
  1690. break;
  1691. }
  1692. }
  1693. if (metaDict) {
  1694. title = metaDict[VLCMetaInformationNowPlaying] ? metaDict[VLCMetaInformationNowPlaying] : metaDict[VLCMetaInformationTitle];
  1695. artist = metaDict[VLCMetaInformationArtist];
  1696. albumName = metaDict[VLCMetaInformationAlbum];
  1697. trackNumber = metaDict[VLCMetaInformationTrackNumber];
  1698. if (mediaIsAudioOnly)
  1699. self.artworkImageView.image = [VLCThumbnailsCache thumbnailForMediaItemWithTitle:title Artist:artist andAlbumName:albumName];
  1700. }
  1701. }
  1702. if (mediaIsAudioOnly) {
  1703. if (!self.artworkImageView.image) {
  1704. self.trackNameLabel.text = title;
  1705. self.artistNameLabel.text = artist;
  1706. self.albumNameLabel.text = albumName;
  1707. } else {
  1708. NSString *trackName = title;
  1709. if (artist)
  1710. trackName = [trackName stringByAppendingFormat:@" — %@", artist];
  1711. if (albumName)
  1712. trackName = [trackName stringByAppendingFormat:@" — %@", albumName];
  1713. self.trackNameLabel.text = trackName;
  1714. }
  1715. if (self.trackNameLabel.text.length < 1)
  1716. self.trackNameLabel.text = [[_mediaPlayer.media url] lastPathComponent];
  1717. if (!self.aspectRatioButton.hidden) {
  1718. CGRect rect = self.timeDisplay.frame;
  1719. rect.origin.x += self.aspectRatioButton.frame.size.width;
  1720. self.timeDisplay.frame = rect;
  1721. rect = self.positionSlider.frame;
  1722. rect.size.width += self.aspectRatioButton.frame.size.width;
  1723. self.positionSlider.frame = rect;
  1724. self.aspectRatioButton.hidden = YES;
  1725. }
  1726. } else {
  1727. if (self.aspectRatioButton.hidden) {
  1728. CGRect rect = self.timeDisplay.frame;
  1729. rect.origin.x -= self.aspectRatioButton.frame.size.width;
  1730. self.timeDisplay.frame = rect;
  1731. rect = self.positionSlider.frame;
  1732. rect.size.width -= self.aspectRatioButton.frame.size.width;
  1733. self.positionSlider.frame = rect;
  1734. self.aspectRatioButton.hidden = NO;
  1735. }
  1736. }
  1737. self.videoFilterButton.hidden = mediaIsAudioOnly;
  1738. /* don't leak sensitive information to the OS, if passcode lock is enabled */
  1739. BOOL passcodeLockEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingPasscodeOnKey] boolValue];
  1740. NSMutableDictionary *currentlyPlayingTrackInfo;
  1741. if (passcodeLockEnabled)
  1742. currentlyPlayingTrackInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:@(_mediaPlayer.media.length.intValue / 1000.), MPMediaItemPropertyPlaybackDuration, @(_mediaPlayer.time.intValue / 1000.), MPNowPlayingInfoPropertyElapsedPlaybackTime, @(_mediaPlayer.rate), MPNowPlayingInfoPropertyPlaybackRate, nil];
  1743. else {
  1744. currentlyPlayingTrackInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: title, MPMediaItemPropertyTitle, @(_mediaPlayer.media.length.intValue / 1000.), MPMediaItemPropertyPlaybackDuration, @(_mediaPlayer.time.intValue / 1000.), MPNowPlayingInfoPropertyElapsedPlaybackTime, @(_mediaPlayer.rate), MPNowPlayingInfoPropertyPlaybackRate, nil];
  1745. if (artist.length > 0)
  1746. [currentlyPlayingTrackInfo setObject:artist forKey:MPMediaItemPropertyArtist];
  1747. if (albumName.length > 0)
  1748. [currentlyPlayingTrackInfo setObject:albumName forKey:MPMediaItemPropertyAlbumTitle];
  1749. [currentlyPlayingTrackInfo setObject:[NSNumber numberWithInt:[trackNumber intValue]] forKey:MPMediaItemPropertyAlbumTrackNumber];
  1750. if (self.artworkImageView.image) {
  1751. MPMediaItemArtwork *mpartwork = [[MPMediaItemArtwork alloc] initWithImage:self.artworkImageView.image];
  1752. [currentlyPlayingTrackInfo setObject:mpartwork forKey:MPMediaItemPropertyArtwork];
  1753. }
  1754. }
  1755. [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = currentlyPlayingTrackInfo;
  1756. }
  1757. #pragma mark - autorotation
  1758. - (BOOL)rotationIsDisabled
  1759. {
  1760. return _interfaceIsLocked;
  1761. }
  1762. - (BOOL)shouldAutorotate
  1763. {
  1764. UIInterfaceOrientation toInterfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
  1765. return UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
  1766. || toInterfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
  1767. }
  1768. - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
  1769. {
  1770. [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
  1771. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1772. if (self.artworkImageView.image)
  1773. self.trackNameLabel.hidden = UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
  1774. if (!_equalizerView.hidden)
  1775. _equalizerView.hidden = YES;
  1776. }
  1777. }
  1778. #pragma mark - AVSession delegate
  1779. - (void)beginInterruption
  1780. {
  1781. if ([_mediaPlayer isPlaying]) {
  1782. [_mediaPlayer pause];
  1783. _shouldResumePlaying = YES;
  1784. }
  1785. }
  1786. - (void)endInterruption
  1787. {
  1788. if (_shouldResumePlaying) {
  1789. [_mediaPlayer play];
  1790. _shouldResumePlaying = NO;
  1791. }
  1792. }
  1793. #pragma mark - External Display
  1794. - (BOOL)hasExternalDisplay
  1795. {
  1796. return ([[UIScreen screens] count] > 1);
  1797. }
  1798. - (void)showOnExternalDisplay
  1799. {
  1800. UIScreen *screen = [UIScreen screens][1];
  1801. screen.overscanCompensation = UIScreenOverscanCompensationInsetApplicationFrame;
  1802. self.externalWindow = [[UIWindow alloc] initWithFrame:screen.bounds];
  1803. UIViewController *controller = [[VLCExternalDisplayController alloc] init];
  1804. self.externalWindow.rootViewController = controller;
  1805. [controller.view addSubview:_movieView];
  1806. controller.view.frame = screen.bounds;
  1807. _movieView.frame = screen.bounds;
  1808. self.playingExternallyView.hidden = NO;
  1809. self.externalWindow.screen = screen;
  1810. self.externalWindow.hidden = NO;
  1811. }
  1812. - (void)hideFromExternalDisplay
  1813. {
  1814. [self.view addSubview:_movieView];
  1815. [self.view sendSubviewToBack:_movieView];
  1816. _movieView.frame = self.view.frame;
  1817. self.playingExternallyView.hidden = YES;
  1818. self.externalWindow.hidden = YES;
  1819. self.externalWindow = nil;
  1820. }
  1821. - (void)handleExternalScreenDidConnect:(NSNotification *)notification
  1822. {
  1823. [self showOnExternalDisplay];
  1824. }
  1825. - (void)handleExternalScreenDidDisconnect:(NSNotification *)notification
  1826. {
  1827. [self hideFromExternalDisplay];
  1828. }
  1829. @end