VLCPlaybackController.m 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392
  1. /*****************************************************************************
  2. * VLCPlaybackController.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. * Carola Nitz <caro # videolan.org>
  10. * Gleb Pinigin <gpinigin # gmail.com>
  11. * Pierre Sagaspe <pierre.sagaspe # me.com>
  12. * Tobias Conradi <videolan # tobias-conradi.de>
  13. * Sylver Bruneau <sylver.bruneau # gmail dot com>
  14. * Winston Weinert <winston # ml1 dot net>
  15. *
  16. * Refer to the COPYING file of the official project for license.
  17. *****************************************************************************/
  18. #import "VLCPlaybackController.h"
  19. #import <CommonCrypto/CommonDigest.h>
  20. #import "UIDevice+VLC.h"
  21. #import <AVFoundation/AVFoundation.h>
  22. #import <MediaPlayer/MediaPlayer.h>
  23. #import "VLCPlayerDisplayController.h"
  24. #import "VLCConstants.h"
  25. #if TARGET_OS_IOS
  26. #import "VLCKeychainCoordinator.h"
  27. #import "VLCThumbnailsCache.h"
  28. #import "VLCLibraryViewController.h"
  29. #import <WatchKit/WatchKit.h>
  30. #endif
  31. NSString *const VLCPlaybackControllerPlaybackDidStart = @"VLCPlaybackControllerPlaybackDidStart";
  32. NSString *const VLCPlaybackControllerPlaybackDidPause = @"VLCPlaybackControllerPlaybackDidPause";
  33. NSString *const VLCPlaybackControllerPlaybackDidResume = @"VLCPlaybackControllerPlaybackDidResume";
  34. NSString *const VLCPlaybackControllerPlaybackDidStop = @"VLCPlaybackControllerPlaybackDidStop";
  35. NSString *const VLCPlaybackControllerPlaybackMetadataDidChange = @"VLCPlaybackControllerPlaybackMetadataDidChange";
  36. NSString *const VLCPlaybackControllerPlaybackDidFail = @"VLCPlaybackControllerPlaybackDidFail";
  37. NSString *const VLCPlaybackControllerPlaybackPositionUpdated = @"VLCPlaybackControllerPlaybackPositionUpdated";
  38. typedef NS_ENUM(NSUInteger, VLCAspectRatio) {
  39. VLCAspectRatioDefault = 0,
  40. VLCAspectRatioFillToScreen,
  41. VLCAspectRatioFourToThree,
  42. VLCAspectRatioSixteenToNine,
  43. VLCAspectRatioSixteenToTen,
  44. };
  45. @interface VLCPlaybackController () <VLCMediaPlayerDelegate,
  46. #if TARGET_OS_IOS
  47. AVAudioSessionDelegate,
  48. #endif
  49. VLCMediaDelegate>
  50. {
  51. BOOL _playerIsSetup;
  52. BOOL _playbackFailed;
  53. BOOL _shouldResumePlaying;
  54. BOOL _shouldResumePlayingAfterInteruption;
  55. NSTimer *_sleepTimer;
  56. NSUInteger _currentAspectRatio;
  57. float _currentPlaybackRate;
  58. UIView *_videoOutputViewWrapper;
  59. UIView *_actualVideoOutputView;
  60. UIView *_preBackgroundWrapperView;
  61. /* cached stuff for the VC */
  62. NSString *_title;
  63. UIImage *_artworkImage;
  64. NSString *_artist;
  65. NSString *_albumName;
  66. BOOL _mediaIsAudioOnly;
  67. BOOL _needsMetadataUpdate;
  68. BOOL _mediaWasJustStarted;
  69. BOOL _recheckForExistingThumbnail;
  70. BOOL _activeSession;
  71. BOOL _headphonesWasPlugged;
  72. NSLock *_playbackSessionManagementLock;
  73. VLCDialogProvider *_dialogProvider;
  74. NSMutableArray *_shuffleStack;
  75. }
  76. @end
  77. @implementation VLCPlaybackController
  78. #pragma mark instance management
  79. + (VLCPlaybackController *)sharedInstance
  80. {
  81. static VLCPlaybackController *sharedInstance = nil;
  82. static dispatch_once_t pred;
  83. dispatch_once(&pred, ^{
  84. sharedInstance = [VLCPlaybackController new];
  85. });
  86. return sharedInstance;
  87. }
  88. - (void)dealloc
  89. {
  90. _dialogProvider = nil;
  91. [[NSNotificationCenter defaultCenter] removeObserver:self];
  92. }
  93. - (instancetype)init
  94. {
  95. self = [super init];
  96. if (self) {
  97. _headphonesWasPlugged = [self areHeadphonesPlugged];
  98. NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
  99. [defaultCenter addObserver:self selector:@selector(audioSessionRouteChange:)
  100. name:AVAudioSessionRouteChangeNotification object:nil];
  101. [defaultCenter addObserver:self selector:@selector(applicationWillResignActive:)
  102. name:UIApplicationWillResignActiveNotification object:nil];
  103. [defaultCenter addObserver:self selector:@selector(applicationDidBecomeActive:)
  104. name:UIApplicationDidBecomeActiveNotification object:nil];
  105. [defaultCenter addObserver:self selector:@selector(applicationDidEnterBackground:)
  106. name:UIApplicationDidEnterBackgroundNotification object:nil];
  107. _dialogProvider = [[VLCDialogProvider alloc] initWithLibrary:[VLCLibrary sharedLibrary] customUI:NO];
  108. _playbackSessionManagementLock = [[NSLock alloc] init];
  109. _shuffleMode = NO;
  110. _shuffleStack = [[NSMutableArray alloc] init];
  111. }
  112. return self;
  113. }
  114. #pragma mark - playback management
  115. - (BOOL)_isMediaSuitableForDevice:(VLCMedia *)media
  116. {
  117. NSArray *tracksInfo = media.tracksInformation;
  118. double width = 0.0, height = 0.0;
  119. NSDictionary *track;
  120. for (NSUInteger x = 0; x < tracksInfo.count; x++) {
  121. track = tracksInfo[x];
  122. if ([track[VLCMediaTracksInformationType] isEqualToString:VLCMediaTracksInformationTypeVideo]) {
  123. width = [track[VLCMediaTracksInformationVideoWidth] doubleValue];
  124. height = [track[VLCMediaTracksInformationVideoHeight] doubleValue];
  125. }
  126. }
  127. NSUInteger totalNumberOfPixels = width * height;
  128. VLCSpeedCategory speedCategory = [[UIDevice currentDevice] vlcSpeedCategory];
  129. if (speedCategory == VLCSpeedCategoryTwoDevices) {
  130. return (totalNumberOfPixels < 922000); // 720p
  131. } else if (speedCategory == VLCSpeedCategoryThreeDevices) {
  132. return (totalNumberOfPixels < 2074000); // 1080p
  133. } else if (speedCategory == VLCSpeedCategoryFourDevices) {
  134. return (totalNumberOfPixels < 8850000); // 4K
  135. }
  136. return YES;
  137. }
  138. - (void)playMediaList:(VLCMediaList *)mediaList firstIndex:(NSInteger)index
  139. {
  140. self.mediaList = mediaList;
  141. self.itemInMediaListToBePlayedFirst = (int)index;
  142. self.pathToExternalSubtitlesFile = nil;
  143. if (self.activePlaybackSession) {
  144. self.sessionWillRestart = YES;
  145. [self stopPlayback];
  146. } else {
  147. self.sessionWillRestart = NO;
  148. [self startPlayback];
  149. }
  150. }
  151. - (void)playURL:(NSURL *)url successCallback:(NSURL*)successCallback errorCallback:(NSURL *)errorCallback
  152. {
  153. self.url = url;
  154. self.successCallback = successCallback;
  155. self.errorCallback = errorCallback;
  156. if (self.activePlaybackSession) {
  157. self.sessionWillRestart = YES;
  158. [self stopPlayback];
  159. } else {
  160. self.sessionWillRestart = NO;
  161. [self startPlayback];
  162. }
  163. }
  164. - (void)playURL:(NSURL *)url subtitlesFilePath:(NSString *)subsFilePath
  165. {
  166. self.url = url;
  167. self.pathToExternalSubtitlesFile = subsFilePath;
  168. if (self.activePlaybackSession) {
  169. self.sessionWillRestart = YES;
  170. [self stopPlayback];
  171. } else {
  172. self.sessionWillRestart = NO;
  173. dispatch_async(dispatch_get_main_queue(), ^{
  174. [self startPlayback];
  175. });
  176. }
  177. }
  178. - (void)startPlayback
  179. {
  180. if (_playerIsSetup) {
  181. APLog(@"%s: player is already setup, bailing out", __PRETTY_FUNCTION__);
  182. return;
  183. }
  184. BOOL ret = [_playbackSessionManagementLock tryLock];
  185. if (!ret) {
  186. APLog(@"%s: locking failed", __PRETTY_FUNCTION__);
  187. return;
  188. }
  189. _activeSession = YES;
  190. #if TARGET_OS_IOS
  191. [[AVAudioSession sharedInstance] setDelegate:self];
  192. #endif
  193. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  194. if (!self.url && !self.mediaList) {
  195. APLog(@"%s: no URL and no media list set, stopping playback", __PRETTY_FUNCTION__);
  196. [_playbackSessionManagementLock unlock];
  197. [self stopPlayback];
  198. return;
  199. }
  200. /* video decoding permanently fails if we don't provide a UIView to draw into on init
  201. * hence we provide one which is not attached to any view controller for off-screen drawing
  202. * and disable video decoding once playback started */
  203. _actualVideoOutputView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
  204. _actualVideoOutputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  205. _actualVideoOutputView.autoresizesSubviews = YES;
  206. if (self.pathToExternalSubtitlesFile)
  207. _listPlayer = [[VLCMediaListPlayer alloc] initWithOptions:@[[NSString stringWithFormat:@"--%@=%@", kVLCSettingSubtitlesFilePath, self.pathToExternalSubtitlesFile]] andDrawable:_actualVideoOutputView];
  208. else
  209. _listPlayer = [[VLCMediaListPlayer alloc] initWithDrawable:_actualVideoOutputView];
  210. /* to enable debug logging for the playback library instance, switch the boolean below
  211. * note that the library instance used for playback may not necessarily match the instance
  212. * used for media discovery or thumbnailing */
  213. _listPlayer.mediaPlayer.libraryInstance.debugLogging = NO;
  214. _mediaPlayer = _listPlayer.mediaPlayer;
  215. [_mediaPlayer setDelegate:self];
  216. if ([[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue] != 0)
  217. [_mediaPlayer setRate: [[defaults objectForKey:kVLCSettingPlaybackSpeedDefaultValue] floatValue]];
  218. if ([[defaults objectForKey:kVLCSettingDeinterlace] intValue] != 0)
  219. [_mediaPlayer setDeinterlaceFilter:@"blend"];
  220. else
  221. [_mediaPlayer setDeinterlaceFilter:nil];
  222. if (self.pathToExternalSubtitlesFile)
  223. [_mediaPlayer addPlaybackSlave:[NSURL fileURLWithPath:self.pathToExternalSubtitlesFile] type:VLCMediaPlaybackSlaveTypeSubtitle enforce:YES];
  224. VLCMedia *media;
  225. if (_mediaList) {
  226. media = [_mediaList mediaAtIndex:_itemInMediaListToBePlayedFirst];
  227. [media parseWithOptions:VLCMediaParseLocal];
  228. media.delegate = self;
  229. } else {
  230. media = [VLCMedia mediaWithURL:self.url];
  231. media.delegate = self;
  232. [media parseWithOptions:VLCMediaParseLocal];
  233. [media addOptions:self.mediaOptionsDictionary];
  234. }
  235. if (self.mediaList) {
  236. [_listPlayer setMediaList:self.mediaList];
  237. } else {
  238. [_listPlayer setRootMedia:media];
  239. }
  240. [_listPlayer setRepeatMode:VLCDoNotRepeat];
  241. [_playbackSessionManagementLock unlock];
  242. if (![self _isMediaSuitableForDevice:media]) {
  243. #if TARGET_OS_IOS
  244. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"DEVICE_TOOSLOW_TITLE", nil)
  245. message:[NSString stringWithFormat:NSLocalizedString(@"DEVICE_TOOSLOW", nil), [[UIDevice currentDevice] model], media.url.lastPathComponent]
  246. delegate:self
  247. cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  248. otherButtonTitles:NSLocalizedString(@"BUTTON_OPEN", nil), nil];
  249. [alert show];
  250. #else
  251. UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"DEVICE_TOOSLOW_TITLE", nil)
  252. message:[NSString stringWithFormat:NSLocalizedString(@"DEVICE_TOOSLOW", nil), [[UIDevice currentDevice] model], media.url.lastPathComponent]
  253. preferredStyle:UIAlertControllerStyleAlert];
  254. UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_OPEN", nil)
  255. style:UIAlertActionStyleDefault
  256. handler:^(UIAlertAction * action) {
  257. [self _playNewMedia];
  258. }];
  259. UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  260. style:UIAlertActionStyleDestructive
  261. handler:^(UIAlertAction * action) {
  262. [self stopPlayback];
  263. }];
  264. [alert addAction:defaultAction];
  265. [alert addAction:cancelAction];
  266. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
  267. #endif
  268. } else
  269. [self _playNewMedia];
  270. }
  271. - (void)_playNewMedia
  272. {
  273. BOOL ret = [_playbackSessionManagementLock tryLock];
  274. if (!ret) {
  275. APLog(@"%s: locking failed", __PRETTY_FUNCTION__);
  276. return;
  277. }
  278. // Set last selected equalizer profile
  279. unsigned int profile = (unsigned int)[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingEqualizerProfile] integerValue];
  280. [_mediaPlayer resetEqualizerFromProfile:profile];
  281. [_mediaPlayer setPreAmplification:[_mediaPlayer preAmplification]];
  282. _mediaWasJustStarted = YES;
  283. [_mediaPlayer addObserver:self forKeyPath:@"time" options:0 context:nil];
  284. [_mediaPlayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];
  285. if (self.mediaList)
  286. [_listPlayer playItemAtNumber:@(self.itemInMediaListToBePlayedFirst)];
  287. else
  288. [_listPlayer playMedia:_listPlayer.rootMedia];
  289. if ([self.delegate respondsToSelector:@selector(prepareForMediaPlayback:)])
  290. [self.delegate prepareForMediaPlayback:self];
  291. _currentAspectRatio = VLCAspectRatioDefault;
  292. _mediaPlayer.videoAspectRatio = NULL;
  293. _mediaPlayer.scaleFactor = 0;
  294. [self subscribeRemoteCommands];
  295. _playerIsSetup = YES;
  296. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidStart object:self];
  297. [_playbackSessionManagementLock unlock];
  298. }
  299. #if TARGET_OS_IOS
  300. - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
  301. {
  302. if (buttonIndex == 1)
  303. [self _playNewMedia];
  304. else
  305. [self stopPlayback];
  306. }
  307. #endif
  308. - (void)stopPlayback
  309. {
  310. BOOL ret = [_playbackSessionManagementLock tryLock];
  311. if (!ret) {
  312. APLog(@"%s: locking failed", __PRETTY_FUNCTION__);
  313. return;
  314. }
  315. if (_mediaPlayer) {
  316. @try {
  317. [_mediaPlayer removeObserver:self forKeyPath:@"time"];
  318. [_mediaPlayer removeObserver:self forKeyPath:@"remainingTime"];
  319. }
  320. @catch (NSException *exception) {
  321. APLog(@"we weren't an observer yet");
  322. }
  323. if (_mediaPlayer.media) {
  324. [_mediaPlayer pause];
  325. #if TARGET_OS_IOS
  326. [self _savePlaybackState];
  327. #endif
  328. [_mediaPlayer stop];
  329. }
  330. if (_mediaPlayer)
  331. _mediaPlayer = nil;
  332. if (_listPlayer)
  333. _listPlayer = nil;
  334. }
  335. if (!_sessionWillRestart) {
  336. if (_mediaList)
  337. _mediaList = nil;
  338. if (_url)
  339. _url = nil;
  340. if (_pathToExternalSubtitlesFile) {
  341. NSFileManager *fileManager = [NSFileManager defaultManager];
  342. if ([fileManager fileExistsAtPath:_pathToExternalSubtitlesFile])
  343. [fileManager removeItemAtPath:_pathToExternalSubtitlesFile error:nil];
  344. _pathToExternalSubtitlesFile = nil;
  345. }
  346. }
  347. _playerIsSetup = NO;
  348. [_shuffleStack removeAllObjects];
  349. if (self.errorCallback && _playbackFailed && !_sessionWillRestart)
  350. [[UIApplication sharedApplication] openURL:self.errorCallback];
  351. else if (self.successCallback && !_sessionWillRestart)
  352. [[UIApplication sharedApplication] openURL:self.successCallback];
  353. [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nil;
  354. [self unsubscribeFromRemoteCommand];
  355. _activeSession = NO;
  356. [_playbackSessionManagementLock unlock];
  357. if (_playbackFailed) {
  358. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidFail object:self];
  359. } else if (!_sessionWillRestart) {
  360. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidStop object:self];
  361. } else {
  362. self.sessionWillRestart = NO;
  363. [self startPlayback];
  364. }
  365. }
  366. #if TARGET_OS_IOS
  367. - (void)_savePlaybackState
  368. {
  369. @try {
  370. [[MLMediaLibrary sharedMediaLibrary] save];
  371. }
  372. @catch (NSException *exception) {
  373. APLog(@"saving playback state failed");
  374. }
  375. MLFile *fileItem;
  376. NSArray *files = [MLFile fileForURL:_mediaPlayer.media.url];
  377. if (files.count > 0)
  378. fileItem = files.firstObject;
  379. if (!fileItem) {
  380. APLog(@"couldn't find file, not saving playback progress");
  381. return;
  382. }
  383. @try {
  384. float position = _mediaPlayer.position;
  385. fileItem.lastPosition = @(position);
  386. fileItem.lastAudioTrack = @(_mediaPlayer.currentAudioTrackIndex);
  387. fileItem.lastSubtitleTrack = @(_mediaPlayer.currentVideoSubTitleIndex);
  388. if (position > .95)
  389. return;
  390. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  391. NSString* newThumbnailPath = [searchPaths[0] stringByAppendingPathComponent:@"VideoSnapshots"];
  392. NSFileManager *fileManager = [NSFileManager defaultManager];
  393. if (![fileManager fileExistsAtPath:newThumbnailPath])
  394. [fileManager createDirectoryAtPath:newThumbnailPath withIntermediateDirectories:YES attributes:nil error:nil];
  395. newThumbnailPath = [newThumbnailPath stringByAppendingPathComponent:fileItem.objectID.URIRepresentation.lastPathComponent];
  396. [_mediaPlayer saveVideoSnapshotAt:newThumbnailPath withWidth:0 andHeight:0];
  397. _recheckForExistingThumbnail = YES;
  398. [self performSelector:@selector(_updateStoredThumbnailForFile:) withObject:fileItem afterDelay:.25];
  399. }
  400. @catch (NSException *exception) {
  401. APLog(@"failed to save current media state - file removed?");
  402. }
  403. }
  404. #endif
  405. #if TARGET_OS_IOS
  406. - (void)_updateStoredThumbnailForFile:(MLFile *)fileItem
  407. {
  408. NSFileManager *fileManager = [NSFileManager defaultManager];
  409. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  410. NSString* newThumbnailPath = [searchPaths[0] stringByAppendingPathComponent:@"VideoSnapshots"];
  411. newThumbnailPath = [newThumbnailPath stringByAppendingPathComponent:fileItem.objectID.URIRepresentation.lastPathComponent];
  412. if (![fileManager fileExistsAtPath:newThumbnailPath]) {
  413. if (_recheckForExistingThumbnail) {
  414. [self performSelector:@selector(_updateStoredThumbnailForFile:) withObject:fileItem afterDelay:1.];
  415. _recheckForExistingThumbnail = NO;
  416. } else
  417. return;
  418. }
  419. UIImage *newThumbnail = [UIImage imageWithContentsOfFile:newThumbnailPath];
  420. if (!newThumbnail) {
  421. if (_recheckForExistingThumbnail) {
  422. [self performSelector:@selector(_updateStoredThumbnailForFile:) withObject:fileItem afterDelay:1.];
  423. _recheckForExistingThumbnail = NO;
  424. } else
  425. return;
  426. }
  427. @try {
  428. [fileItem setComputedThumbnailScaledForDevice:newThumbnail];
  429. }
  430. @catch (NSException *exception) {
  431. APLog(@"updating thumbnail failed");
  432. }
  433. [fileManager removeItemAtPath:newThumbnailPath error:nil];
  434. }
  435. #endif
  436. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  437. {
  438. if (_mediaWasJustStarted) {
  439. _mediaWasJustStarted = NO;
  440. #if TARGET_OS_IOS
  441. if (self.mediaList) {
  442. MLFile *item;
  443. NSArray *matches = [MLFile fileForURL:_mediaPlayer.media.url];
  444. item = matches.firstObject;
  445. [self _recoverLastPlaybackStateOfItem:item];
  446. }
  447. #else
  448. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  449. BOOL bValue = [defaults boolForKey:kVLCSettingUseSPDIF];
  450. if (bValue) {
  451. _mediaPlayer.audio.passthrough = bValue;
  452. }
  453. #endif
  454. }
  455. if ([self.delegate respondsToSelector:@selector(playbackPositionUpdated:)])
  456. [self.delegate playbackPositionUpdated:self];
  457. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackPositionUpdated
  458. object:self];
  459. }
  460. - (NSInteger)mediaDuration
  461. {
  462. return _listPlayer.mediaPlayer.media.length.intValue;;
  463. }
  464. - (BOOL)isPlaying
  465. {
  466. return _mediaPlayer.isPlaying;
  467. }
  468. - (VLCRepeatMode)repeatMode
  469. {
  470. return _listPlayer.repeatMode;
  471. }
  472. - (void)setRepeatMode:(VLCRepeatMode)repeatMode
  473. {
  474. _listPlayer.repeatMode = repeatMode;
  475. }
  476. - (BOOL)currentMediaHasChapters
  477. {
  478. return [_mediaPlayer numberOfTitles] > 1 || [_mediaPlayer numberOfChaptersForTitle:_mediaPlayer.currentTitleIndex] > 1;
  479. }
  480. - (BOOL)currentMediaHasTrackToChooseFrom
  481. {
  482. return [[_mediaPlayer audioTrackIndexes] count] > 2 || [[_mediaPlayer videoSubTitlesIndexes] count] > 1;
  483. }
  484. - (BOOL)activePlaybackSession
  485. {
  486. return _activeSession;
  487. }
  488. - (BOOL)audioOnlyPlaybackSession
  489. {
  490. return _mediaIsAudioOnly;
  491. }
  492. - (NSString *)mediaTitle
  493. {
  494. return _title;
  495. }
  496. - (float)playbackRate
  497. {
  498. float f_rate = _mediaPlayer.rate;
  499. _currentPlaybackRate = f_rate;
  500. return f_rate;
  501. }
  502. - (void)setPlaybackRate:(float)playbackRate
  503. {
  504. if (_currentPlaybackRate != playbackRate)
  505. [_mediaPlayer setRate:playbackRate];
  506. _currentPlaybackRate = playbackRate;
  507. }
  508. - (void)setAudioDelay:(float)audioDelay
  509. {
  510. _mediaPlayer.currentAudioPlaybackDelay = 1000000.*audioDelay;
  511. }
  512. - (float)audioDelay
  513. {
  514. return _mediaPlayer.currentAudioPlaybackDelay/1000000.;
  515. }
  516. -(void)setSubtitleDelay:(float)subtitleDeleay
  517. {
  518. _mediaPlayer.currentVideoSubTitleDelay = 1000000.*subtitleDeleay;
  519. }
  520. - (float)subtitleDelay
  521. {
  522. return _mediaPlayer.currentVideoSubTitleDelay/1000000.;
  523. }
  524. - (void)mediaPlayerStateChanged:(NSNotification *)aNotification
  525. {
  526. VLCMediaPlayerState currentState = _mediaPlayer.state;
  527. if (currentState == VLCMediaPlayerStateBuffering) {
  528. /* attach delegate */
  529. _mediaPlayer.media.delegate = self;
  530. /* on-the-fly values through hidden API */
  531. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  532. [_mediaPlayer performSelector:@selector(setTextRendererFont:) withObject:[defaults objectForKey:kVLCSettingSubtitlesFont]];
  533. [_mediaPlayer performSelector:@selector(setTextRendererFontSize:) withObject:[defaults objectForKey:kVLCSettingSubtitlesFontSize]];
  534. [_mediaPlayer performSelector:@selector(setTextRendererFontColor:) withObject:[defaults objectForKey:kVLCSettingSubtitlesFontColor]];
  535. [_mediaPlayer performSelector:@selector(setTextRendererFontForceBold:) withObject:[defaults objectForKey:kVLCSettingSubtitlesBoldFont]];
  536. } else if (currentState == VLCMediaPlayerStateError) {
  537. APLog(@"Playback failed");
  538. _playbackFailed = YES;
  539. self.sessionWillRestart = NO;
  540. [self stopPlayback];
  541. } else if (currentState == VLCMediaPlayerStateEnded || currentState == VLCMediaPlayerStateStopped) {
  542. [_listPlayer.mediaList lock];
  543. NSUInteger listCount = _listPlayer.mediaList.count;
  544. if ([_listPlayer.mediaList indexOfMedia:_mediaPlayer.media] == listCount - 1 && self.repeatMode == VLCDoNotRepeat) {
  545. [_listPlayer.mediaList unlock];
  546. self.sessionWillRestart = NO;
  547. [self stopPlayback];
  548. return;
  549. } else if (listCount > 1) {
  550. [_listPlayer.mediaList unlock];
  551. [_listPlayer next];
  552. } else
  553. [_listPlayer.mediaList unlock];
  554. }
  555. if ([self.delegate respondsToSelector:@selector(mediaPlayerStateChanged:isPlaying:currentMediaHasTrackToChooseFrom:currentMediaHasChapters:forPlaybackController:)])
  556. [self.delegate mediaPlayerStateChanged:currentState
  557. isPlaying:_mediaPlayer.isPlaying
  558. currentMediaHasTrackToChooseFrom:self.currentMediaHasTrackToChooseFrom
  559. currentMediaHasChapters:self.currentMediaHasChapters
  560. forPlaybackController:self];
  561. [self setNeedsMetadataUpdate];
  562. }
  563. #pragma mark - playback controls
  564. - (void)playPause
  565. {
  566. if ([_mediaPlayer isPlaying]) {
  567. [_listPlayer pause];
  568. #if TARGET_OS_IOS
  569. [self _savePlaybackState];
  570. #endif
  571. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidPause object:self];
  572. } else {
  573. [_listPlayer play];
  574. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidResume object:self];
  575. }
  576. }
  577. - (void)forward
  578. {
  579. NSInteger mediaListCount = _mediaList.count;
  580. #if TARGET_OS_IOS
  581. if (mediaListCount > 2 && _shuffleMode) {
  582. NSNumber *nextIndex;
  583. NSUInteger currentIndex = [_mediaList indexOfMedia:_listPlayer.mediaPlayer.media];
  584. //Reached end of playlist
  585. if (_shuffleStack.count + 1 == mediaListCount) {
  586. if ([self repeatMode] == VLCDoNotRepeat)
  587. return;
  588. [_shuffleStack removeAllObjects];
  589. }
  590. [_shuffleStack addObject:[NSNumber numberWithUnsignedInteger:currentIndex]];
  591. do {
  592. nextIndex = [NSNumber numberWithUnsignedInt:arc4random_uniform((uint32_t)mediaListCount)];
  593. } while (currentIndex == nextIndex.unsignedIntegerValue || [_shuffleStack containsObject:nextIndex]);
  594. [_listPlayer playItemAtNumber:[NSNumber numberWithUnsignedInteger:nextIndex.unsignedIntegerValue]];
  595. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackMetadataDidChange object:self];
  596. return;
  597. }
  598. #endif
  599. if (mediaListCount > 1) {
  600. [_listPlayer next];
  601. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackMetadataDidChange object:self];
  602. } else {
  603. NSNumber *skipLength = [[NSUserDefaults standardUserDefaults] valueForKey:kVLCSettingPlaybackForwardSkipLength];
  604. [_mediaPlayer jumpForward:skipLength.intValue];
  605. }
  606. }
  607. - (void)backward
  608. {
  609. if (_mediaList.count > 1) {
  610. [_listPlayer previous];
  611. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackMetadataDidChange object:self];
  612. }
  613. else {
  614. NSNumber *skipLength = [[NSUserDefaults standardUserDefaults] valueForKey:kVLCSettingPlaybackBackwardSkipLength];
  615. [_mediaPlayer jumpBackward:skipLength.intValue];
  616. }
  617. }
  618. - (void)switchAspectRatio
  619. {
  620. if (_currentAspectRatio == VLCAspectRatioSixteenToTen) {
  621. _mediaPlayer.videoAspectRatio = NULL;
  622. _mediaPlayer.scaleFactor = 0;
  623. _currentAspectRatio = VLCAspectRatioDefault;
  624. } else {
  625. _currentAspectRatio++;
  626. if (_currentAspectRatio == VLCAspectRatioFillToScreen) {
  627. UIScreen *screen;
  628. if (![[UIDevice currentDevice] VLCHasExternalDisplay])
  629. screen = [UIScreen mainScreen];
  630. else
  631. screen = [UIScreen screens][1];
  632. float f_ar = screen.bounds.size.width / screen.bounds.size.height;
  633. if (f_ar == (float)(4.0/3.0) ||
  634. f_ar == (float)(1366./1024.)) {
  635. // all iPads
  636. _mediaPlayer.videoCropGeometry = "4:3";
  637. } else if (f_ar == (float)(2./3.) || f_ar == (float)(480./320.)) {
  638. // all other iPhones
  639. _mediaPlayer.videoCropGeometry = "16:10"; // libvlc doesn't support 2:3 crop
  640. } else if (f_ar == .5625) {
  641. // AirPlay
  642. _mediaPlayer.videoCropGeometry = "16:9";
  643. } else if (f_ar == (float)(640./1136.) ||
  644. f_ar == (float)(568./320.) ||
  645. f_ar == (float)(667./375.) ||
  646. f_ar == (float)(736./414.)) {
  647. // iPhone 5 and 6 and 6+
  648. _mediaPlayer.videoCropGeometry = "16:9";
  649. } else
  650. APLog(@"unknown screen format %f, can't crop", f_ar);
  651. } else {
  652. _mediaPlayer.videoAspectRatio = (char *)[[self stringForAspectRatio:_currentAspectRatio] UTF8String];
  653. _mediaPlayer.videoCropGeometry = NULL;
  654. }
  655. }
  656. if ([self.delegate respondsToSelector:@selector(showStatusMessage:forPlaybackController:)]) {
  657. [self.delegate showStatusMessage:[NSString stringWithFormat:NSLocalizedString(@"AR_CHANGED", nil), [self stringForAspectRatio:_currentAspectRatio]] forPlaybackController:self];
  658. }
  659. }
  660. - (NSString *)stringForAspectRatio:(VLCAspectRatio)ratio
  661. {
  662. switch (ratio) {
  663. case VLCAspectRatioFillToScreen:
  664. return NSLocalizedString(@"FILL_TO_SCREEN", nil);
  665. case VLCAspectRatioDefault:
  666. return NSLocalizedString(@"DEFAULT", nil);
  667. case VLCAspectRatioFourToThree:
  668. return @"4:3";
  669. case VLCAspectRatioSixteenToTen:
  670. return @"16:10";
  671. case VLCAspectRatioSixteenToNine:
  672. return @"16:9";
  673. default:
  674. NSAssert(NO, @"this shouldn't happen");
  675. }
  676. }
  677. - (void)setVideoOutputView:(UIView *)videoOutputView
  678. {
  679. if (videoOutputView) {
  680. if ([_actualVideoOutputView superview] != nil)
  681. [_actualVideoOutputView removeFromSuperview];
  682. _actualVideoOutputView.frame = (CGRect){CGPointZero, videoOutputView.frame.size};
  683. if (_mediaPlayer.currentVideoTrackIndex == -1)
  684. _mediaPlayer.currentVideoTrackIndex = 0;
  685. [videoOutputView addSubview:_actualVideoOutputView];
  686. [_actualVideoOutputView layoutSubviews];
  687. [_actualVideoOutputView updateConstraints];
  688. [_actualVideoOutputView setNeedsLayout];
  689. } else
  690. [_actualVideoOutputView removeFromSuperview];
  691. _videoOutputViewWrapper = videoOutputView;
  692. }
  693. - (UIView *)videoOutputView
  694. {
  695. return _videoOutputViewWrapper;
  696. }
  697. #pragma mark - 360 Support
  698. #if !TARGET_OS_TV
  699. - (BOOL)updateViewpoint:(CGFloat)yaw pitch:(CGFloat)pitch roll:(CGFloat)roll fov:(CGFloat)fov absolute:(BOOL)absolute
  700. {
  701. return [_mediaPlayer updateViewpoint:yaw pitch:pitch roll:roll fov:fov absolute:absolute];
  702. }
  703. - (NSInteger)currentMediaProjection
  704. {
  705. VLCMedia *media = [_mediaPlayer media];
  706. NSInteger currentVideoTrackIndex = [_mediaPlayer currentVideoTrackIndex];
  707. if (media && currentVideoTrackIndex >= 0) {
  708. NSArray *tracksInfo = media.tracksInformation;
  709. for (NSDictionary *track in tracksInfo) {
  710. if ([track[VLCMediaTracksInformationType] isEqualToString:VLCMediaTracksInformationTypeVideo]) {
  711. return [track[VLCMediaTracksInformationVideoProjection] integerValue];
  712. }
  713. }
  714. }
  715. return -1;
  716. }
  717. #endif
  718. #pragma mark - equalizer
  719. - (void)setAmplification:(CGFloat)amplification forBand:(unsigned int)index
  720. {
  721. if (!_mediaPlayer.equalizerEnabled)
  722. [_mediaPlayer setEqualizerEnabled:YES];
  723. [_mediaPlayer setAmplification:amplification forBand:index];
  724. // For some reason we have to apply again preamp to apply change
  725. [_mediaPlayer setPreAmplification:[_mediaPlayer preAmplification]];
  726. }
  727. - (CGFloat)amplificationOfBand:(unsigned int)index
  728. {
  729. return [_mediaPlayer amplificationOfBand:index];
  730. }
  731. - (NSArray *)equalizerProfiles
  732. {
  733. return _mediaPlayer.equalizerProfiles;
  734. }
  735. - (void)resetEqualizerFromProfile:(unsigned int)profile
  736. {
  737. [[NSUserDefaults standardUserDefaults] setObject:@(profile) forKey:kVLCSettingEqualizerProfile];
  738. [_mediaPlayer resetEqualizerFromProfile:profile];
  739. }
  740. - (void)setPreAmplification:(CGFloat)preAmplification
  741. {
  742. if (!_mediaPlayer.equalizerEnabled)
  743. [_mediaPlayer setEqualizerEnabled:YES];
  744. [_mediaPlayer setPreAmplification:preAmplification];
  745. }
  746. - (CGFloat)preAmplification
  747. {
  748. return [_mediaPlayer preAmplification];
  749. }
  750. #pragma mark - AVSession delegate
  751. - (void)beginInterruption
  752. {
  753. if ([_mediaPlayer isPlaying]) {
  754. [_mediaPlayer pause];
  755. _shouldResumePlayingAfterInteruption = YES;
  756. }
  757. }
  758. - (void)endInterruption
  759. {
  760. if (_shouldResumePlayingAfterInteruption) {
  761. [_mediaPlayer play];
  762. _shouldResumePlayingAfterInteruption = NO;
  763. }
  764. }
  765. - (BOOL)areHeadphonesPlugged
  766. {
  767. NSArray *outputs = [[AVAudioSession sharedInstance] currentRoute].outputs;
  768. NSString *portName = [[outputs firstObject] portName];
  769. return [portName isEqualToString:@"Headphones"];
  770. }
  771. - (void)audioSessionRouteChange:(NSNotification *)notification
  772. {
  773. NSDictionary *userInfo = notification.userInfo;
  774. NSInteger routeChangeReason = [[userInfo valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
  775. if (routeChangeReason == AVAudioSessionRouteChangeReasonRouteConfigurationChange)
  776. return;
  777. BOOL headphonesPlugged = [self areHeadphonesPlugged];
  778. if (_headphonesWasPlugged && !headphonesPlugged && [_mediaPlayer isPlaying]) {
  779. [_mediaPlayer pause];
  780. #if TARGET_OS_IOS
  781. [self _savePlaybackState];
  782. #endif
  783. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackDidPause object:self];
  784. }
  785. _headphonesWasPlugged = headphonesPlugged;
  786. }
  787. #pragma mark - Managing the media item
  788. #if TARGET_OS_IOS
  789. - (MLFile *)currentlyPlayingMediaFile {
  790. if (self.mediaList) {
  791. NSArray *results = [MLFile fileForURL:_mediaPlayer.media.url];
  792. return results.firstObject;
  793. }
  794. return nil;
  795. }
  796. #endif
  797. #pragma mark - metadata handling
  798. - (void)mediaDidFinishParsing:(VLCMedia *)aMedia
  799. {
  800. [self setNeedsMetadataUpdate];
  801. }
  802. - (void)mediaMetaDataDidChange:(VLCMedia*)aMedia
  803. {
  804. [self setNeedsMetadataUpdate];
  805. }
  806. - (void)setNeedsMetadataUpdate
  807. {
  808. if (_needsMetadataUpdate == NO) {
  809. _needsMetadataUpdate = YES;
  810. dispatch_async(dispatch_get_main_queue(), ^{
  811. [self _updateDisplayedMetadata];
  812. });
  813. }
  814. }
  815. - (void)_updateDisplayedMetadata
  816. {
  817. _needsMetadataUpdate = NO;
  818. NSNumber *trackNumber;
  819. NSString *title;
  820. NSString *artist;
  821. NSString *albumName;
  822. UIImage* artworkImage;
  823. BOOL mediaIsAudioOnly = NO;
  824. #if TARGET_OS_IOS
  825. MLFile *item;
  826. if (self.mediaList) {
  827. NSArray *matches = [MLFile fileForURL:_mediaPlayer.media.url];
  828. item = matches.firstObject;
  829. }
  830. if (item) {
  831. if (item.isAlbumTrack) {
  832. title = item.albumTrack.title;
  833. artist = item.albumTrack.artist;
  834. albumName = item.albumTrack.album.name;
  835. } else
  836. title = item.title;
  837. /* MLKit knows better than us if this thing is audio only or not */
  838. mediaIsAudioOnly = [item isSupportedAudioFile];
  839. } else {
  840. #endif
  841. NSDictionary * metaDict = _mediaPlayer.media.metaDictionary;
  842. if (metaDict) {
  843. title = metaDict[VLCMetaInformationNowPlaying] ? metaDict[VLCMetaInformationNowPlaying] : metaDict[VLCMetaInformationTitle];
  844. artist = metaDict[VLCMetaInformationArtist];
  845. albumName = metaDict[VLCMetaInformationAlbum];
  846. trackNumber = metaDict[VLCMetaInformationTrackNumber];
  847. }
  848. #if TARGET_OS_IOS
  849. }
  850. #endif
  851. if (!mediaIsAudioOnly) {
  852. /* either what we are playing is not a file known to MLKit or
  853. * MLKit fails to acknowledge that it is audio-only.
  854. * Either way, do a more expensive check to see if it is really audio-only */
  855. NSArray *tracks = _mediaPlayer.media.tracksInformation;
  856. NSUInteger trackCount = tracks.count;
  857. mediaIsAudioOnly = YES;
  858. for (NSUInteger x = 0 ; x < trackCount; x++) {
  859. if ([[tracks[x] objectForKey:VLCMediaTracksInformationType] isEqualToString:VLCMediaTracksInformationTypeVideo]) {
  860. mediaIsAudioOnly = NO;
  861. break;
  862. }
  863. }
  864. }
  865. if (mediaIsAudioOnly) {
  866. #if TARGET_OS_IOS
  867. artworkImage = [VLCThumbnailsCache thumbnailForManagedObject:item];
  868. if (artworkImage) {
  869. if (artist)
  870. title = [title stringByAppendingFormat:@" — %@", artist];
  871. if (albumName)
  872. title = [title stringByAppendingFormat:@" — %@", albumName];
  873. }
  874. #endif
  875. if (title.length < 1)
  876. title = [[_mediaPlayer.media url] lastPathComponent];
  877. }
  878. /* populate delegate with metadata info */
  879. if ([self.delegate respondsToSelector:@selector(displayMetadataForPlaybackController:title:artwork:artist:album:audioOnly:)])
  880. [self.delegate displayMetadataForPlaybackController:self
  881. title:title
  882. artwork:artworkImage
  883. artist:artist
  884. album:albumName
  885. audioOnly:mediaIsAudioOnly];
  886. /* populate now playing info center with metadata information */
  887. NSMutableDictionary *currentlyPlayingTrackInfo = [NSMutableDictionary dictionary];
  888. currentlyPlayingTrackInfo[MPMediaItemPropertyPlaybackDuration] = @(_mediaPlayer.media.length.intValue / 1000.);
  889. currentlyPlayingTrackInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(_mediaPlayer.time.intValue / 1000.);
  890. currentlyPlayingTrackInfo[MPNowPlayingInfoPropertyPlaybackRate] = @(_mediaPlayer.isPlaying ? _mediaPlayer.rate : 0.0);
  891. /* don't leak sensitive information to the OS, if passcode lock is enabled */
  892. #if TARGET_OS_IOS
  893. if (![[VLCKeychainCoordinator defaultCoordinator] passcodeLockEnabled]) {
  894. #endif
  895. if (title)
  896. currentlyPlayingTrackInfo[MPMediaItemPropertyTitle] = title;
  897. if (artist.length > 0)
  898. currentlyPlayingTrackInfo[MPMediaItemPropertyArtist] = artist;
  899. if (albumName.length > 0)
  900. currentlyPlayingTrackInfo[MPMediaItemPropertyAlbumTitle] = albumName;
  901. if ([trackNumber intValue] > 0)
  902. currentlyPlayingTrackInfo[MPMediaItemPropertyAlbumTrackNumber] = trackNumber;
  903. #if TARGET_OS_IOS
  904. /* FIXME: UGLY HACK
  905. * iOS 8.2 and 8.3 include an issue which will lead to a termination of the client app if we set artwork
  906. * when the playback initialized through the watch extension
  907. * radar://pending */
  908. if ([WKInterfaceDevice class] != nil) {
  909. if ([WKInterfaceDevice currentDevice] != nil)
  910. goto setstuff;
  911. }
  912. if (artworkImage) {
  913. MPMediaItemArtwork *mpartwork = [[MPMediaItemArtwork alloc] initWithImage:artworkImage];
  914. currentlyPlayingTrackInfo[MPMediaItemPropertyArtwork] = mpartwork;
  915. }
  916. }
  917. #endif
  918. setstuff:
  919. [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = currentlyPlayingTrackInfo;
  920. [[NSNotificationCenter defaultCenter] postNotificationName:VLCPlaybackControllerPlaybackMetadataDidChange object:self];
  921. _title = title;
  922. _artist = artist;
  923. _albumName = albumName;
  924. _artworkImage = artworkImage;
  925. _mediaIsAudioOnly = mediaIsAudioOnly;
  926. }
  927. #if TARGET_OS_IOS
  928. - (void)_recoverLastPlaybackStateOfItem:(MLFile *)item
  929. {
  930. if (item) {
  931. if (_mediaPlayer.numberOfAudioTracks > 2) {
  932. if (item.lastAudioTrack.intValue > 0)
  933. _mediaPlayer.currentAudioTrackIndex = item.lastAudioTrack.intValue;
  934. }
  935. if (_mediaPlayer.numberOfSubtitlesTracks > 2) {
  936. if (item.lastSubtitleTrack.intValue > 0)
  937. _mediaPlayer.currentVideoSubTitleIndex = item.lastSubtitleTrack.intValue;
  938. }
  939. CGFloat lastPosition = .0;
  940. NSInteger duration = 0;
  941. if (item.lastPosition)
  942. lastPosition = item.lastPosition.floatValue;
  943. duration = item.duration.intValue;
  944. if (lastPosition < .95 && _mediaPlayer.position < lastPosition && (duration * lastPosition - duration) < -50000) {
  945. NSInteger continuePlayback;
  946. if ([item isAlbumTrack] || [item isSupportedAudioFile])
  947. continuePlayback = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingContinueAudioPlayback] integerValue];
  948. else
  949. continuePlayback = [[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingContinuePlayback] integerValue];
  950. if (continuePlayback == 1) {
  951. _mediaPlayer.position = lastPosition;
  952. } else if (continuePlayback == 0) {
  953. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"CONTINUE_PLAYBACK", nil)
  954. message:[NSString stringWithFormat:NSLocalizedString(@"CONTINUE_PLAYBACK_LONG", nil), item.title]
  955. delegate:self
  956. cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  957. otherButtonTitles:NSLocalizedString(@"BUTTON_CONTINUE", nil), nil];
  958. alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
  959. if (!cancelled) {
  960. _mediaPlayer.position = lastPosition;
  961. }
  962. };
  963. [alert show];
  964. }
  965. }
  966. }
  967. }
  968. #endif
  969. - (void)recoverDisplayedMetadata
  970. {
  971. if ([self.delegate respondsToSelector:@selector(displayMetadataForPlaybackController:title:artwork:artist:album:audioOnly:)])
  972. [self.delegate displayMetadataForPlaybackController:self
  973. title:_title
  974. artwork:_artworkImage
  975. artist:_artist
  976. album:_albumName
  977. audioOnly:_mediaIsAudioOnly];
  978. }
  979. - (void)recoverPlaybackState
  980. {
  981. if ([self.delegate respondsToSelector:@selector(mediaPlayerStateChanged:isPlaying:currentMediaHasTrackToChooseFrom:currentMediaHasChapters:forPlaybackController:)])
  982. [self.delegate mediaPlayerStateChanged:_mediaPlayer.state
  983. isPlaying:self.isPlaying
  984. currentMediaHasTrackToChooseFrom:self.currentMediaHasTrackToChooseFrom
  985. currentMediaHasChapters:self.currentMediaHasChapters
  986. forPlaybackController:self];
  987. if ([self.delegate respondsToSelector:@selector(prepareForMediaPlayback:)])
  988. [self.delegate prepareForMediaPlayback:self];
  989. }
  990. - (void)scheduleSleepTimerWithInterval:(NSTimeInterval)timeInterval
  991. {
  992. if (_sleepTimer) {
  993. [_sleepTimer invalidate];
  994. _sleepTimer = nil;
  995. }
  996. _sleepTimer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(stopPlayback) userInfo:nil repeats:NO];
  997. }
  998. #pragma mark - remote events
  999. static inline NSArray * RemoteCommandCenterCommandsToHandle(MPRemoteCommandCenter *cc)
  1000. {
  1001. /* commmented out other available commands which we don't support now but may
  1002. * support at some point in the future */
  1003. return @[cc.pauseCommand, cc.playCommand, cc.stopCommand, cc.togglePlayPauseCommand,
  1004. cc.nextTrackCommand, cc.previousTrackCommand,
  1005. cc.skipForwardCommand, cc.skipBackwardCommand,
  1006. // cc.seekForwardCommand, cc.seekBackwardCommand,
  1007. // cc.ratingCommand,
  1008. cc.changePlaybackRateCommand,
  1009. // cc.likeCommand,cc.dislikeCommand,cc.bookmarkCommand,
  1010. ];
  1011. }
  1012. - (void)subscribeRemoteCommands
  1013. {
  1014. /* pre iOS 7.1 */
  1015. if (![MPRemoteCommandCenter class]) {
  1016. [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
  1017. return;
  1018. }
  1019. /* for iOS 7.1 and above: */
  1020. MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
  1021. /*
  1022. * since the control center and lockscreen shows only either skipForward/Backward
  1023. * or next/previousTrack buttons but prefers skip buttons,
  1024. * we only enable skip buttons if we have a no medialist
  1025. */
  1026. BOOL enableSkip = [VLCPlaybackController sharedInstance].mediaList.count <= 1;
  1027. commandCenter.skipForwardCommand.enabled = enableSkip;
  1028. commandCenter.skipBackwardCommand.enabled = enableSkip;
  1029. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  1030. NSNumber *forwardSkip = [defaults valueForKey:kVLCSettingPlaybackForwardSkipLength];
  1031. commandCenter.skipForwardCommand.preferredIntervals = @[forwardSkip];
  1032. NSNumber *backwardSkip = [defaults valueForKey:kVLCSettingPlaybackBackwardSkipLength];
  1033. commandCenter.skipBackwardCommand.preferredIntervals = @[backwardSkip];
  1034. NSArray *supportedPlaybackRates = @[@(0.5),@(0.75),@(1.0),@(1.25),@(1.5),@(1.75),@(2.0)];
  1035. commandCenter.changePlaybackRateCommand.supportedPlaybackRates = supportedPlaybackRates;
  1036. NSArray *commandsToSubscribe = RemoteCommandCenterCommandsToHandle(commandCenter);
  1037. for (MPRemoteCommand *command in commandsToSubscribe) {
  1038. [command addTarget:self action:@selector(remoteCommandEvent:)];
  1039. }
  1040. }
  1041. - (void)unsubscribeFromRemoteCommand
  1042. {
  1043. /* pre iOS 7.1 */
  1044. if (![MPRemoteCommandCenter class]) {
  1045. [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
  1046. return;
  1047. }
  1048. /* for iOS 7.1 and above: */
  1049. MPRemoteCommandCenter *cc = [MPRemoteCommandCenter sharedCommandCenter];
  1050. NSArray *commmandsToRemoveFrom = RemoteCommandCenterCommandsToHandle(cc);
  1051. for (MPRemoteCommand *command in commmandsToRemoveFrom) {
  1052. [command removeTarget:self];
  1053. }
  1054. }
  1055. - (MPRemoteCommandHandlerStatus )remoteCommandEvent:(MPRemoteCommandEvent *)event
  1056. {
  1057. MPRemoteCommandCenter *cc = [MPRemoteCommandCenter sharedCommandCenter];
  1058. MPRemoteCommandHandlerStatus result = MPRemoteCommandHandlerStatusSuccess;
  1059. if (event.command == cc.pauseCommand) {
  1060. [_listPlayer pause];
  1061. } else if (event.command == cc.playCommand) {
  1062. [_listPlayer play];
  1063. } else if (event.command == cc.stopCommand) {
  1064. [_listPlayer stop];
  1065. } else if (event.command == cc.togglePlayPauseCommand) {
  1066. [self playPause];
  1067. } else if (event.command == cc.nextTrackCommand) {
  1068. result = [_listPlayer next] ? MPRemoteCommandHandlerStatusSuccess : MPRemoteCommandHandlerStatusNoSuchContent;
  1069. } else if (event.command == cc.previousTrackCommand) {
  1070. result = [_listPlayer previous] ? MPRemoteCommandHandlerStatusSuccess : MPRemoteCommandHandlerStatusNoSuchContent;
  1071. } else if (event.command == cc.skipForwardCommand) {
  1072. if ([event isKindOfClass:[MPSkipIntervalCommandEvent class]]) {
  1073. MPSkipIntervalCommandEvent *skipEvent = (MPSkipIntervalCommandEvent *)event;
  1074. [_mediaPlayer jumpForward:skipEvent.interval];
  1075. } else {
  1076. result = MPRemoteCommandHandlerStatusCommandFailed;
  1077. }
  1078. } else if (event.command == cc.skipBackwardCommand) {
  1079. if ([event isKindOfClass:[MPSkipIntervalCommandEvent class]]) {
  1080. MPSkipIntervalCommandEvent *skipEvent = (MPSkipIntervalCommandEvent *)event;
  1081. [_mediaPlayer jumpBackward:skipEvent.interval];
  1082. } else {
  1083. result = MPRemoteCommandHandlerStatusCommandFailed;
  1084. }
  1085. } else if (event.command == cc.changePlaybackRateCommand) {
  1086. if ([event isKindOfClass:[MPChangePlaybackRateCommandEvent class]]) {
  1087. MPChangePlaybackRateCommandEvent *rateEvent = (MPChangePlaybackRateCommandEvent *)event;
  1088. [_mediaPlayer setRate:rateEvent.playbackRate];
  1089. } else {
  1090. result = MPRemoteCommandHandlerStatusCommandFailed;
  1091. }
  1092. /* stubs for when we want to support the other available commands */
  1093. // } else if (event.command == cc.seekForwardCommand) {
  1094. // } else if (event.command == cc.seekBackwardCommand) {
  1095. // } else if (event.command == cc.ratingCommand) {
  1096. // } else if (event.command == cc.likeCommand) {
  1097. // } else if (event.command == cc.dislikeCommand) {
  1098. // } else if (event.command == cc.bookmarkCommand) {
  1099. } else {
  1100. APLog(@"%s Unsupported remote control event: %@",__PRETTY_FUNCTION__,event);
  1101. result = MPRemoteCommandHandlerStatusCommandFailed;
  1102. }
  1103. if (result == MPRemoteCommandHandlerStatusCommandFailed)
  1104. APLog(@"%s Wasn't able to handle remote control event: %@",__PRETTY_FUNCTION__,event);
  1105. return result;
  1106. }
  1107. - (void)remoteControlReceivedWithEvent:(UIEvent *)event
  1108. {
  1109. switch (event.subtype) {
  1110. case UIEventSubtypeRemoteControlPlay:
  1111. [_listPlayer play];
  1112. break;
  1113. case UIEventSubtypeRemoteControlPause:
  1114. [_listPlayer pause];
  1115. break;
  1116. case UIEventSubtypeRemoteControlTogglePlayPause:
  1117. [self playPause];
  1118. break;
  1119. case UIEventSubtypeRemoteControlNextTrack:
  1120. [self forward];
  1121. break;
  1122. case UIEventSubtypeRemoteControlPreviousTrack:
  1123. [self backward];
  1124. break;
  1125. case UIEventSubtypeRemoteControlStop:
  1126. [self stopPlayback];
  1127. break;
  1128. default:
  1129. break;
  1130. }
  1131. }
  1132. #pragma mark - background interaction
  1133. - (void)applicationWillResignActive:(NSNotification *)aNotification
  1134. {
  1135. #if TARGET_OS_IOS
  1136. [self _savePlaybackState];
  1137. #endif
  1138. if (![[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingContinueAudioInBackgroundKey] boolValue]) {
  1139. if ([_mediaPlayer isPlaying]) {
  1140. [_mediaPlayer pause];
  1141. _shouldResumePlaying = YES;
  1142. }
  1143. }
  1144. }
  1145. - (void)applicationDidEnterBackground:(NSNotification *)notification
  1146. {
  1147. _preBackgroundWrapperView = _videoOutputViewWrapper;
  1148. if (_mediaPlayer.audioTrackIndexes.count > 0)
  1149. _mediaPlayer.currentVideoTrackIndex = -1;
  1150. }
  1151. - (void)applicationDidBecomeActive:(NSNotification *)notification
  1152. {
  1153. if (_preBackgroundWrapperView) {
  1154. [self setVideoOutputView:_preBackgroundWrapperView];
  1155. _preBackgroundWrapperView = nil;
  1156. }
  1157. if (_mediaPlayer.numberOfVideoTracks > 0) {
  1158. /* re-enable video decoding */
  1159. _mediaPlayer.currentVideoTrackIndex = 1;
  1160. }
  1161. if (_shouldResumePlaying) {
  1162. _shouldResumePlaying = NO;
  1163. [_listPlayer play];
  1164. }
  1165. }
  1166. #pragma mark - helpers
  1167. - (NSDictionary *)mediaOptionsDictionary
  1168. {
  1169. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  1170. return @{ kVLCSettingNetworkCaching : [defaults objectForKey:kVLCSettingNetworkCaching],
  1171. kVLCSettingStretchAudio : [[defaults objectForKey:kVLCSettingStretchAudio] boolValue] ? kVLCSettingStretchAudioOnValue : kVLCSettingStretchAudioOffValue,
  1172. kVLCSettingTextEncoding : [defaults objectForKey:kVLCSettingTextEncoding],
  1173. kVLCSettingSkipLoopFilter : [defaults objectForKey:kVLCSettingSkipLoopFilter],
  1174. #if TARGET_OS_IOS
  1175. kVLCSettingHWDecoding : [defaults objectForKey:kVLCSettingHWDecoding]};
  1176. #else
  1177. };
  1178. #endif
  1179. }
  1180. @end