VLCAppDelegate.m 30.5 KB
Newer Older
1
2
3
4
/*****************************************************************************
 * VLCAppDelegate.m
 * VLC for iOS
 *****************************************************************************
Felix Paul Kühne's avatar
Felix Paul Kühne committed
5
 * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
6
7
8
9
10
11
12
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Gleb Pinigin <gpinigin # gmail.com>
 *          Jean-Romain Prévost <jr # 3on.fr>
 *          Luis Fernandes <zipleen # gmail.com>
 *          Carola Nitz <nitz.carola # googlemail.com>
13
 *          Tamas Timar <ttimar.vlc # gmail.com>
Tobias's avatar
Tobias committed
14
 *          Tobias Conradi <videolan@tobias-conradi.de>
15
16
17
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
Felix Paul Kühne's avatar
Felix Paul Kühne committed
18
19

#import "VLCAppDelegate.h"
Gleb Pinigin's avatar
Gleb Pinigin committed
20
#import "VLCMediaFileDiscoverer.h"
21
#import "NSString+SupportedMedia.h"
Felix Paul Kühne's avatar
Felix Paul Kühne committed
22
#import "UIDevice+VLC.h"
Felix Paul Kühne's avatar
Felix Paul Kühne committed
23
#import "VLCPlaylistViewController.h"
24
#import "VLCMovieViewController.h"
25
#import "VLCPlaybackNavigationController.h"
26
#import "PAPasscodeViewController.h"
27
#import "VLCHTTPUploaderController.h"
28
#import "VLCMenuTableViewController.h"
Carola's avatar
Carola committed
29
#import "VLCMigrationViewController.h"
30
#import "VLCAlertView.h"
Carola's avatar
Carola committed
31
#import <BoxSDK/BoxSDK.h>
32
#import "VLCNotificationRelay.h"
33
#import "VLCPlaybackController.h"
34
#import "VLCNavigationController.h"
35

36
37
38
#define HAVE_FABRIC 0

#if HAVE_FABRIC
39
40
#import <Fabric/Fabric.h>
#import <Crashlytics/Crashlytics.h>
41
#endif
42

Tobias's avatar
Tobias committed
43
@interface VLCAppDelegate () <PAPasscodeViewControllerDelegate, VLCMediaFileDiscovererDelegate> {
Gleb Pinigin's avatar
Gleb Pinigin committed
44
    PAPasscodeViewController *_passcodeLockController;
45
    VLCDownloadViewController *_downloadViewController;
46
    VLCDropboxTableViewController *_dropboxTableViewController;
47
    int _idleCounter;
48
    int _networkActivityCounter;
49
    BOOL _passcodeValidated;
Carola's avatar
Carola committed
50
    BOOL _isRunningMigration;
Carola's avatar
Carola committed
51
    BOOL _isComingFromHandoff;
Gleb Pinigin's avatar
Gleb Pinigin committed
52
}
53

54
55
@property (nonatomic, strong) VLCMovieViewController *movieViewController;

56
@end
Felix Paul Kühne's avatar
Felix Paul Kühne committed
57
58
59

@implementation VLCAppDelegate

60
61
62
63
+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

64
65
66
67
68
    NSNumber *skipLoopFilterDefaultValue;
    int deviceSpeedCategory = [[UIDevice currentDevice] speedCategory];
    if (deviceSpeedCategory < 3)
        skipLoopFilterDefaultValue = kVLCSettingSkipLoopFilterNonKey;
    else
69
        skipLoopFilterDefaultValue = kVLCSettingSkipLoopFilterNonRef;
70

Felix Paul Kühne's avatar
Felix Paul Kühne committed
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
    NSDictionary *appDefaults = @{kVLCSettingPasscodeKey : @"",
                                  kVLCSettingPasscodeOnKey : @(NO),
                                  kVLCSettingContinueAudioInBackgroundKey : @(YES),
                                  kVLCSettingStretchAudio : @(NO),
                                  kVLCSettingTextEncoding : kVLCSettingTextEncodingDefaultValue,
                                  kVLCSettingSkipLoopFilter : skipLoopFilterDefaultValue,
                                  kVLCSettingSubtitlesFont : kVLCSettingSubtitlesFontDefaultValue,
                                  kVLCSettingSubtitlesFontColor : kVLCSettingSubtitlesFontColorDefaultValue,
                                  kVLCSettingSubtitlesFontSize : kVLCSettingSubtitlesFontSizeDefaultValue,
                                  kVLCSettingSubtitlesBoldFont: kVLCSettingSubtitlesBoldFontDefaultValue,
                                  kVLCSettingDeinterlace : kVLCSettingDeinterlaceDefaultValue,
                                  kVLCSettingNetworkCaching : kVLCSettingNetworkCachingDefaultValue,
                                  kVLCSettingPlaybackGestures : [NSNumber numberWithBool:YES],
                                  kVLCSettingFTPTextEncoding : kVLCSettingFTPTextEncodingDefaultValue,
                                  kVLCSettingWiFiSharingIPv6 : kVLCSettingWiFiSharingIPv6DefaultValue,
                                  kVLCSettingEqualizerProfile : kVLCSettingEqualizerProfileDefaultValue,
                                  kVLCSettingPlaybackForwardSkipLength : kVLCSettingPlaybackForwardSkipLengthDefaultValue,
                                  kVLCSettingPlaybackBackwardSkipLength : kVLCSettingPlaybackBackwardSkipLengthDefaultValue,
                                  kVLCSettingOpenAppForPlayback : kVLCSettingOpenAppForPlaybackDefaultValue};
90
91
92
    [defaults registerDefaults:appDefaults];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
93
94
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
95
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
96
        // Change the keyboard for UISearchBar
97
        [[UITextField appearance] setKeyboardAppearance:UIKeyboardAppearanceDark];
98
99
100
101
102
103
        // For the cursor
        [[UITextField appearance] setTintColor:[UIColor VLCOrangeTintColor]];
        // Don't override the 'Cancel' button color in the search bar with the previous UITextField call. Use the default blue color
        [[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil] setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor colorWithRed:0.0 green:122.0/255.0 blue:1.0 alpha:1.0]} forState:UIControlStateNormal];
        // For the edit selection indicators
        [[UITableView appearance] setTintColor:[UIColor VLCOrangeTintColor]];
104
105
    }

106
107
    [[UISwitch appearance] setOnTintColor:[UIColor VLCOrangeTintColor]];

108
    /* clean caches on launch (since those are used for wifi upload only) */
109
    [self cleanCache];
110

111
112
113
    // Init the HTTP Server
    self.uploadController = [[VLCHTTPUploaderController alloc] init];

Felix Paul Kühne's avatar
Felix Paul Kühne committed
114
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
Carola's avatar
Carola committed
115
    // enable crash preventer
116
117
     void (^setupBlock)() = ^{
         _playlistViewController = [[VLCPlaylistViewController alloc] init];
118
        UINavigationController *navCon = [[VLCNavigationController alloc] initWithRootViewController:_playlistViewController];
Carola's avatar
Carola committed
119
120
121
122
123
124
125
126
127
128
129
130

        _revealController = [[GHRevealViewController alloc] initWithNibName:nil bundle:nil];
        _revealController.wantsFullScreenLayout = YES;
        _menuViewController = [[VLCMenuTableViewController alloc] initWithNibName:nil bundle:nil];
        _revealController.sidebarViewController = _menuViewController;
        _revealController.contentViewController = navCon;

        self.window.rootViewController = self.revealController;
        // necessary to avoid navbar blinking in VLCOpenNetworkStreamViewController & VLCDownloadViewController
        _revealController.contentViewController.view.backgroundColor = [UIColor VLCDarkBackgroundColor];
        [self.window makeKeyAndVisible];

131
132
133
134
        [self validatePasscode];

        [[MLMediaLibrary sharedMediaLibrary] applicationWillStart];

Carola's avatar
Carola committed
135
136
137
138
139
140
        VLCMediaFileDiscoverer *discoverer = [VLCMediaFileDiscoverer sharedInstance];
        [discoverer addObserver:self];
        [discoverer startDiscovering:[self directoryPath]];
    };

    NSError *error = nil;
141
    [self pathMigrationToGroupsIfNeeded:&error];
Carola's avatar
Carola committed
142
143
144
145
146
147
148
149
150
151
152
    if ([self migrationNeeded:&error]){
        _isRunningMigration = YES;

        VLCMigrationViewController *migrationController = [[VLCMigrationViewController alloc] initWithNibName:@"VLCMigrationViewController" bundle:nil];
        migrationController.completionHandler = ^{
            //migrate
            setupBlock();
            _isRunningMigration = NO;
            [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
            [self updateMediaList];
        };
Felix Paul Kühne's avatar
Felix Paul Kühne committed
153

Carola's avatar
Carola committed
154
155
        self.window.rootViewController = migrationController;
        [self.window makeKeyAndVisible];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
156

Carola's avatar
Carola committed
157
158
159
160
161
162
163
164
    } else {
        if (error != nil) {
            NSLog(@"removed persistentStore since it was corrupt");
            NSURL *storeURL = ((MLMediaLibrary *)[MLMediaLibrary sharedMediaLibrary]).persistentStoreURL;
            [[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
        }
        setupBlock();
    }
165

166
167
    [[VLCNotificationRelay sharedRelay] addRelayLocalName:NSManagedObjectContextDidSaveNotification toRemoteName:@"org.videolan.ios-app.dbupdate"];

168
    [[VLCNotificationRelay sharedRelay] addRelayLocalName:kVLCNotificationNowPlayingInfoUpdate toRemoteName:kVLCDarwinNotificationNowPlayingInfoUpdate];
169

170
#if HAVE_FABRIC
171
    [Fabric with:@[CrashlyticsKit]];
172
#endif
173

Carola's avatar
Carola committed
174
175
    return YES;
}
176

Carola's avatar
Carola committed
177
178
179
180
181
#pragma mark - Handoff

- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType {


Carola's avatar
Carola committed
182
    if ([userActivityType isEqualToString:@"org.videolan.vlc-ios.librarymode"] ||[userActivityType isEqualToString:@"org.videolan.vlc-ios.playing"]) {
Carola's avatar
Carola committed
183
184
185
186
187
188
189
190
191
192
        return YES;
    }
    return NO;
}

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler {

    if([userActivity.activityType isEqualToString:@"org.videolan.vlc-ios.librarymode"]) {
        NSDictionary *dict = userActivity.userInfo;
        NSInteger row = [(NSNumber *)dict[@"state"] integerValue];
Carola's avatar
Carola committed
193
194
195
196
        
        [self.menuViewController selectRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:0] animated:NO scrollPosition:UITableViewScrollPositionTop];
        [self.playlistViewController restoreUserActivityState:userActivity];
        _isComingFromHandoff = YES;
Carola's avatar
Carola committed
197
198
199
200
201
202
203
204
205
206
207
208
        return YES;
    }
    return NO;
}

- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error
{
    if (error.code != NSUserCancelledError){
        //TODO: present alert
    }
}

209
210
- (void)pathMigrationToGroupsIfNeeded:(NSError **)migrationError
{
211
212
213
214
215
216
217
    /*
     * We can't and don't need to migrate to groups on pre-iOS 7
     */
    if (![[NSFileManager defaultManager] respondsToSelector:@selector(containerURLForSecurityApplicationGroupIdentifier:)]) {
        return;
    }

218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
    MLMediaLibrary *mediaLibrary = [MLMediaLibrary sharedMediaLibrary];
    NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.org.videolan.vlc-ios"];

    NSString *oldBasePath = [mediaLibrary libraryBasePath];
    NSString *oldPersistentStorePath = [oldBasePath stringByAppendingPathComponent: @"MediaLibrary.sqlite"];

    NSError *error = nil;
    if (![[NSFileManager defaultManager] fileExistsAtPath:oldPersistentStorePath]) {
        mediaLibrary.libraryBasePath = groupURL.path;
    } else if (![mediaLibrary migrateLibraryToBasePath:groupURL.path error:&error]) {
        NSLog(@"Failed to migrate Library to new location with error: %@", error);
    }

    if (migrationError && error) {
        *migrationError = error;
    }
}

Carola's avatar
Carola committed
236
- (BOOL)migrationNeeded:(NSError **) migrationCheckError {
237

Carola's avatar
Carola committed
238
    BOOL migrationNeeded = NO;
239

Carola's avatar
Carola committed
240
241
242
243
244
245
246
247
248
249
    if ([[NSFileManager defaultManager] fileExistsAtPath:((MLMediaLibrary *)[MLMediaLibrary sharedMediaLibrary]).persistentStoreURL.path]) {
        NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
                                                                                                  URL:((MLMediaLibrary *)[MLMediaLibrary sharedMediaLibrary]).persistentStoreURL
                                                                                                error:migrationCheckError];
        if (*migrationCheckError) {
            return NO;
        }
        NSManagedObjectModel *destinationModel = [[MLMediaLibrary sharedMediaLibrary] managedObjectModel];
        migrationNeeded = ![destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata];
    }
250

Carola's avatar
Carola committed
251
    return migrationNeeded;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
252
253
}

254
255
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
256
257
258
259
    if ([[DBSession sharedSession] handleOpenURL:url]) {
        [self.dropboxTableViewController updateViewAfterSessionChange];
        return YES;
    }
260

261
262
    if (_playlistViewController && url != nil) {
        APLog(@"%@ requested %@ to be opened", sourceApplication, url);
263

264
        if (url.isFileURL) {
265
266
267
268
269
270
            NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
            NSString *directoryPath = searchPaths[0];
            NSURL *destinationURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@", directoryPath, url.lastPathComponent]];
            NSError *theError;
            [[NSFileManager defaultManager] moveItemAtURL:url toURL:destinationURL error:&theError];
            if (theError.code != noErr)
271
                APLog(@"saving the file failed (%li): %@", (long)theError.code, theError.localizedDescription);
272

273
            [self updateMediaList];
274
275
276
277
278
        } else if ([url.scheme isEqualToString:@"vlc-x-callback"] || [url.host isEqualToString:@"x-callback-url"]) {
            // URL confirmes to the x-callback-url specification
            // vlc-x-callback://x-callback-url/action?param=value&x-success=callback
            APLog(@"x-callback-url with host '%@' path '%@' parameters '%@'", url.host, url.path, url.query);
            NSString *action = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
279
280
281
282
            NSURL *movieURL;
            NSURL *successCallback;
            NSURL *errorCallback;
            NSString *fileName;
283
284
285
286
287
            for (NSString *entry in [url.query componentsSeparatedByString:@"&"]) {
                NSArray *keyvalue = [entry componentsSeparatedByString:@"="];
                if (keyvalue.count < 2) continue;
                NSString *key = keyvalue[0];
                NSString *value = [keyvalue[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
288
289

                if ([key isEqualToString:@"url"])
290
                    movieURL = [NSURL URLWithString:value];
291
                else if ([key isEqualToString:@"filename"])
292
                    fileName = value;
293
                else if ([key isEqualToString:@"x-success"])
294
                    successCallback = [NSURL URLWithString:value];
295
296
                else if ([key isEqualToString:@"x-error"])
                    errorCallback = [NSURL URLWithString:value];
297
298
            }
            if ([action isEqualToString:@"stream"] && movieURL) {
299
                [self openMovieFromURL:movieURL successCallback:successCallback errorCallback:errorCallback];
300
            }
301
302
303
            else if ([action isEqualToString:@"download"] && movieURL) {
                [self downloadMovieFromURL:movieURL fileNameOfMedia:fileName];
            }
Luis's avatar
Luis committed
304
        } else {
305
306
307
308
309
310
311
312
313
314
            NSString *receivedUrl = [url absoluteString];
            if ([receivedUrl length] > 6) {
                NSString *verifyVlcUrl = [receivedUrl substringToIndex:6];
                if ([verifyVlcUrl isEqualToString:@"vlc://"]) {
                    NSString *parsedString = [receivedUrl substringFromIndex:6];
                    NSUInteger location = [parsedString rangeOfString:@"//"].location;

                    /* Safari & al mangle vlc://http:// so fix this */
                    if (location != NSNotFound && [parsedString characterAtIndex:location - 1] != 0x3a) { // :
                            parsedString = [NSString stringWithFormat:@"%@://%@", [parsedString substringToIndex:location], [parsedString substringFromIndex:location+2]];
315
316
317
318
319
320
                    } else {
                        parsedString = [receivedUrl substringFromIndex:6];
                        if (![parsedString hasPrefix:@"http://"] && ![parsedString hasPrefix:@"https://"] && ![parsedString hasPrefix:@"ftp://"]) {
                            parsedString = [@"http://" stringByAppendingString:[receivedUrl substringFromIndex:6]];
                        }
                    }
321
322
323
                    url = [NSURL URLWithString:parsedString];
                }
            }
324
            [self.menuViewController selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] animated:NO scrollPosition:UITableViewScrollPositionNone];
325
326
327

            NSString *scheme = url.scheme;
            if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"ftp"]) {
328
                VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"OPEN_STREAM_OR_DOWNLOAD", nil) message:url.absoluteString cancelButtonTitle:NSLocalizedString(@"BUTTON_DOWNLOAD", nil) otherButtonTitles:@[NSLocalizedString(@"BUTTON_PLAY", nil)]];
329
330
                alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
                    if (cancelled)
331
                        [self downloadMovieFromURL:url fileNameOfMedia:nil];
332
333
334
335
336
337
                    else
                        [self openMovieFromURL:url];
                };
                [alert show];
            } else
                [self openMovieFromURL:url];
Luis's avatar
Luis committed
338
        }
339
340
341
342
343
        return YES;
    }
    return NO;
}

344
345
346
347
348
- (void)applicationWillEnterForeground:(UIApplication *)application
{
    [[MLMediaLibrary sharedMediaLibrary] applicationWillStart];
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
349
350
- (void)applicationWillResignActive:(UIApplication *)application
{
351
    _passcodeValidated = NO;
352
    [self validatePasscode];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
353
354
355
356
357
    [[MLMediaLibrary sharedMediaLibrary] applicationWillExit];
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
Carola's avatar
Carola committed
358
    if (!_isRunningMigration && !_isComingFromHandoff) {
Carola's avatar
Carola committed
359
360
        [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
        [self updateMediaList];
Carola's avatar
Carola committed
361
362
    } else if(_isComingFromHandoff) {
        _isComingFromHandoff = NO;
Carola's avatar
Carola committed
363
    }
364

365
366
367
368
369
370
371
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];

    if (!vpc.audioOnlyPlaybackSession) {
        if (vpc.isPlaying && !self.movieViewController.presentingViewController)
            [self presentMovieViewController];
    } else
        [self.playlistViewController displayMiniPlaybackViewIfNeeded];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
372
373
}

374
375
- (void)applicationWillTerminate:(UIApplication *)application
{
376
    _passcodeValidated = NO;
377
378
379
    [[NSUserDefaults standardUserDefaults] synchronize];
}

Gleb Pinigin's avatar
Gleb Pinigin committed
380
381
382
#pragma mark - properties
- (VLCDropboxTableViewController *)dropboxTableViewController
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
383
    if (_dropboxTableViewController == nil)
384
        _dropboxTableViewController = [[VLCDropboxTableViewController alloc] initWithNibName:@"VLCCloudStorageTableViewController" bundle:nil];
Gleb Pinigin's avatar
Gleb Pinigin committed
385
386
387
388

    return _dropboxTableViewController;
}

389
390
- (VLCDownloadViewController *)downloadViewController
{
391
392
393
394
395
396
    if (_downloadViewController == nil) {
        if (SYSTEM_RUNS_IOS7_OR_LATER)
            _downloadViewController = [[VLCDownloadViewController alloc] initWithNibName:@"VLCFutureDownloadViewController" bundle:nil];
        else
            _downloadViewController = [[VLCDownloadViewController alloc] initWithNibName:@"VLCDownloadViewController" bundle:nil];
    }
397
398
399
400

    return _downloadViewController;
}

401
402
403
404
405
406
407
408
409
- (VLCMovieViewController *)movieViewController
{
    if (!_movieViewController) {
        _movieViewController = [[VLCMovieViewController alloc] initWithNibName:nil bundle:nil];
        [VLCPlaybackController sharedInstance].delegate = _movieViewController;
    }
    return _movieViewController;
}

Gleb Pinigin's avatar
Gleb Pinigin committed
410
#pragma mark - media discovering
411

412
413
- (void)mediaFileAdded:(NSString *)fileName loading:(BOOL)isLoading
{
Gleb Pinigin's avatar
Gleb Pinigin committed
414
415
416
    if (!isLoading) {
        MLMediaLibrary *sharedLibrary = [MLMediaLibrary sharedMediaLibrary];
        [sharedLibrary addFilePaths:@[fileName]];
417

Gleb Pinigin's avatar
Gleb Pinigin committed
418
419
420
        /* exclude media files from backup (QA1719) */
        NSURL *excludeURL = [NSURL fileURLWithPath:fileName];
        [excludeURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
421

Gleb Pinigin's avatar
Gleb Pinigin committed
422
423
        // TODO Should we update media db after adding new files?
        [sharedLibrary updateMediaDatabase];
424
        [_playlistViewController updateViewContents];
425
426
427
    }
}

428
429
- (void)mediaFileDeleted:(NSString *)name
{
Gleb Pinigin's avatar
Gleb Pinigin committed
430
431
432
433
    [[MLMediaLibrary sharedMediaLibrary] updateMediaDatabase];
    [_playlistViewController updateViewContents];
}

434
435
- (void)cleanCache
{
436
437
438
    if ([self haveNetworkActivity])
        return;

439
440
441
442
443
444
445
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString* uploadDirPath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:uploadDirPath])
        [fileManager removeItemAtPath:uploadDirPath error:nil];
}

446
447
448
#pragma mark - media list methods

- (NSString *)directoryPath
Felix Paul Kühne's avatar
Felix Paul Kühne committed
449
{
450
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
451
    NSString *directoryPath = searchPaths[0];
452
453
454
455
456
457
    return directoryPath;
}

- (void)updateMediaList
{
    NSString *directoryPath = [self directoryPath];
458
    NSMutableArray *foundFiles = [NSMutableArray arrayWithArray:[[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:nil]];
459
    NSMutableArray *filePaths = [NSMutableArray array];
460
    NSURL *fileURL;
461
462
463
464
465
    while (foundFiles.count) {
        NSString *fileName = foundFiles.firstObject;
        NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName];
        [foundFiles removeObject:fileName];

466
        if ([fileName isSupportedMediaFormat] || [fileName isSupportedAudioMediaFormat]) {
467
            [filePaths addObject:filePath];
468
469

            /* exclude media files from backup (QA1719) */
470
            fileURL = [NSURL fileURLWithPath:filePath];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
471
            [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
472
473
474
        } else {
            BOOL isDirectory = NO;
            BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
475

476
477
            // add folders
            if (exists && isDirectory) {
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
                NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:filePath error:nil];
                for (NSString* file in files) {
                    NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:file];
                    isDirectory = NO;
                    exists = [[NSFileManager defaultManager] fileExistsAtPath:fullFilePath isDirectory:&isDirectory];
                    //only add folders or files in folders
                    if ((exists && isDirectory) || ![filePath.lastPathComponent isEqualToString:@"Documents"]) {
                        NSString *folderpath = [filePath stringByReplacingOccurrencesOfString:directoryPath withString:@""];
                        if (![folderpath isEqualToString:@""]) {
                            folderpath = [folderpath stringByAppendingString:@"/"];
                        }
                        NSString *path = [folderpath stringByAppendingString:file];
                        [foundFiles addObject:path];
                    }
                }
493
            }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
494
495
496
        }
    }
    [[MLMediaLibrary sharedMediaLibrary] addFilePaths:filePaths];
497
    [_playlistViewController updateViewContents];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
498
499
}

500
501
#pragma mark - pass code validation

502
503
504
505
506
- (BOOL)passcodeValidated
{
    return _passcodeValidated;
}

507
508
509
510
- (void)validatePasscode
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *passcode = [defaults objectForKey:kVLCSettingPasscodeKey];
511
    if ([passcode isEqualToString:@""] || ![[defaults objectForKey:kVLCSettingPasscodeOnKey] boolValue]) {
512
        _passcodeValidated = YES;
513
514
        return;
    }
515

516
    if (!_passcodeValidated) {
517
518
519
        _passcodeLockController = [[PAPasscodeViewController alloc] initForAction:PasscodeActionEnter];
        _passcodeLockController.delegate = self;
        _passcodeLockController.passcode = passcode;
520
521
522
523
524
525
526

        if (self.window.rootViewController.presentedViewController)
            [self.window.rootViewController dismissViewControllerAnimated:NO completion:nil];

        UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:_passcodeLockController];
        navCon.modalPresentationStyle = UIModalPresentationFullScreen;
        [self.window.rootViewController presentViewController:navCon animated:NO completion:nil];
527
528
529
530
531
    }
}

- (void)PAPasscodeViewControllerDidEnterPasscode:(PAPasscodeViewController *)controller
{
532
533
    _passcodeValidated = YES;
    [self.playlistViewController updateViewContents];
534
    [self.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
535
536
537
538
}

- (void)PAPasscodeViewController:(PAPasscodeViewController *)controller didFailToEnterPasscode:(NSInteger)attempts
{
539
    // FIXME: handle countless failed passcode attempts
540
541
}

542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
#pragma mark - idle timer preventer
- (void)disableIdleTimer
{
    _idleCounter++;
    if ([UIApplication sharedApplication].idleTimerDisabled == NO)
        [UIApplication sharedApplication].idleTimerDisabled = YES;
}

- (void)activateIdleTimer
{
    _idleCounter--;
    if (_idleCounter < 1)
        [UIApplication sharedApplication].idleTimerDisabled = NO;
}

557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
- (void)networkActivityStarted
{
    _networkActivityCounter++;
    if ([UIApplication sharedApplication].networkActivityIndicatorVisible == NO)
        [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (BOOL)haveNetworkActivity
{
    return _networkActivityCounter >= 1;
}

- (void)networkActivityStopped
{
    _networkActivityCounter--;
    if (_networkActivityCounter < 1)
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

576
577
578
579
580
581
582
583
584
585
586
#pragma mark - download handling

- (void)downloadMovieFromURL:(NSURL *)url
             fileNameOfMedia:(NSString *)fileName
{
    [self.downloadViewController addURLToDownloadList:url fileNameOfMedia:fileName];

    // select Downloads menu item and reveal corresponding viewcontroller
    [self.menuViewController selectRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:1] animated:NO scrollPosition:UITableViewScrollPositionNone];
}

587
588
#pragma mark - playback view handling

589
- (void)presentMovieViewController
590
{
591
592
593
594
595
596
597
598
    if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
        return;
    }

    UINavigationController *navCon = [[VLCPlaybackNavigationController alloc] initWithRootViewController:self.movieViewController];
    navCon.modalPresentationStyle = UIModalPresentationFullScreen;
    [self.window.rootViewController presentViewController:navCon animated:YES completion:nil];
}
599

600
601
- (void)openMediaFromManagedObject:(NSManagedObject *)mediaObject
{
602
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
603
604

    if ([mediaObject isKindOfClass:[MLFile class]])
605
        vpc.fileFromMediaLibrary = (MLFile *)mediaObject;
606
    else if ([mediaObject isKindOfClass:[MLAlbumTrack class]])
607
        vpc.fileFromMediaLibrary = [(MLAlbumTrack*)mediaObject files].anyObject;
608
    else if ([mediaObject isKindOfClass:[MLShowEpisode class]])
609
610
        vpc.fileFromMediaLibrary = [(MLShowEpisode*)mediaObject files].anyObject;
    [(MLFile *)vpc.fileFromMediaLibrary setUnread:@(NO)];
611

612
    [vpc startPlayback];
613
614
615
}

- (void)openMovieFromURL:(NSURL *)url
616
         successCallback:(NSURL *)successCallback
617
           errorCallback:(NSURL *)errorCallback
618
619
{

620
621
622
623
624
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];

    vpc.url = url;
    vpc.successCallback = successCallback;
    vpc.errorCallback = errorCallback;
625

626
    [vpc startPlayback];
627
628
}

629
630
- (void)openMovieFromURL:(NSURL *)url
{
631
    [self openMovieFromURL:url successCallback:nil errorCallback:nil];
632
633
}

634
- (void)openMediaList:(VLCMediaList *)list atIndex:(int)index
635
{
636
637
638
639
640
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];

    vpc.mediaList = list;
    vpc.itemInMediaListToBePlayedFirst = index;
    vpc.pathToExternalSubtitlesFile = nil;
641

642
    [vpc startPlayback];
643
644
645
646
}

- (void)openMovieWithExternalSubtitleFromURL:(NSURL *)url externalSubURL:(NSString *)SubtitlePath
{
647
648
649
650
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];

    vpc.url = url;
    vpc.pathToExternalSubtitlesFile = SubtitlePath;
651

652
    [vpc startPlayback];
653
654
}

655
#pragma mark - watch struff
656
657
658
659
660
661
662
663
664
665
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
    /* dispatch background task */
    __block UIBackgroundTaskIdentifier taskIdentifier = [application beginBackgroundTaskWithName:nil
                                                                               expirationHandler:^{
                                                                                   [application endBackgroundTask:taskIdentifier];
                                                                                   taskIdentifier = UIBackgroundTaskInvalid;
    }];

    NSDictionary *responseDict = nil;
666
    if ([userInfo[@"name"] isEqualToString:@"getNowPlayingInfo"]) {
667
        responseDict = [self nowPlayingResponseDict];
668
    } else if ([userInfo[@"name"] isEqualToString:@"playpause"]) {
669
670
        [[VLCPlaybackController sharedInstance] playPause];
        responseDict = @{@"playing": @([VLCPlaybackController sharedInstance].isPlaying)};
671
    } else if ([userInfo[@"name"] isEqualToString:@"skipForward"]) {
672
        [[VLCPlaybackController sharedInstance] forward];
673
    } else if ([userInfo[@"name"] isEqualToString:@"skipBackward"]) {
674
        [[VLCPlaybackController sharedInstance] backward];
675
676
    } else if ([userInfo[@"name"] isEqualToString:@"playFile"]) {
        [self playFileFromWatch:userInfo[@"userInfo"]];
677
678
    } else if ([userInfo[@"name"] isEqualToString:@"setVolume"]) {
        [self setVolumeFromWatch:userInfo[@"userInfo"]];
679
    } else {
680
681
        NSLog(@"Did not handle request from WatchKit Extension: %@",userInfo);
    }
682
    reply(responseDict);
683
}
684

685
686
- (void)playFileFromWatch:(NSDictionary *)userInfo
{
687
    NSManagedObject *managedObject = nil;
688
    NSString *uriString = userInfo[@"URIRepresentation"];
689
690
691
692
    if (uriString) {
        NSURL *uriRepresentation = [NSURL URLWithString:uriString];
        managedObject = [[MLMediaLibrary sharedMediaLibrary] objectForURIRepresentation:uriRepresentation];
    }
693
694
695
696
697
698
    if (managedObject == nil) {
        NSLog(@"%s file not found: %@",__PRETTY_FUNCTION__,userInfo);
        return;
    }

    [self openMediaFromManagedObject:managedObject];
699
700
}

701
702
703
704
705
706
707
708
709
710
711
712
- (void)setVolumeFromWatch:(NSDictionary *)userInfo
{
    NSNumber *volume = userInfo[@"volume"];
    if ([volume isKindOfClass:[NSNumber class]]) {
        /*
         * Since WatchKit doen't provide something like MPVolumeView we use deprecated API.
         * rdar://20783803 Feature Request: WatchKit equivalent for MPVolumeView
         */
        [MPMusicPlayerController applicationMusicPlayer].volume = volume.floatValue;
    }
}

713
714
- (NSDictionary *)nowPlayingResponseDict {
    NSMutableDictionary *response = [NSMutableDictionary new];
715
    NSMutableDictionary *nowPlayingInfo = [[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo mutableCopy];
716
    NSNumber *playbackTime = [VLCPlaybackController sharedInstance].mediaPlayer.time.numberValue;
717
718
719
    if (playbackTime) {
        nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(playbackTime.floatValue/1000);
    }
720
721
722
    if (nowPlayingInfo) {
        response[@"nowPlayingInfo"] = nowPlayingInfo;
    }
723
    MLFile *currentFile = [VLCPlaybackController sharedInstance].currentlyPlayingMediaFile;
724
725
726
    NSString *URIString = currentFile.objectID.URIRepresentation.absoluteString;
    if (URIString) {
        response[@"URIRepresentation"] = URIString;
727
    }
728
729
730

    response[@"volume"] = @([MPMusicPlayerController applicationMusicPlayer].volume);

731
    return response;
732
733
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
734
@end