VLCOpenNetworkStreamViewController.m 18.2 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCOpenNetworkStreamViewController.m
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
6 7 8 9 10
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Gleb Pinigin <gpinigin # gmail.com>
 *          Pierre Sagaspe <pierre.sagaspe # me.com>
11
 *          Adam Viaud <mcnight # mcnight.fr>
12 13
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
14 15

#import "VLCOpenNetworkStreamViewController.h"
16
#import "VLCPlaybackController.h"
17
#import "VLCLibraryViewController.h"
18
#import "VLCMenuTableViewController.h"
19
#import "VLCStreamingHistoryCell.h"
20
#import "UIDevice+VLC.h"
21

22
@interface VLCOpenNetworkStreamViewController () <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate, UIAlertViewDelegate, VLCStreamingHistoryCellMenuItemProtocol>
23 24
{
    NSMutableArray *_recentURLs;
25
    NSMutableDictionary *_recentURLTitles;
26 27 28 29 30 31 32 33 34
}
@end

@implementation VLCOpenNetworkStreamViewController

+ (void)initialize
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

35
    NSDictionary *appDefaults = @{kVLCRecentURLs : @[], kVLCRecentURLTitles : @{}, kVLCPrivateWebStreaming : @(NO)};
36 37 38 39

    [defaults registerDefaults:appDefaults];
}

40 41 42 43 44
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
    [self updatePasteboardTextInURLField];
}

45 46 47 48
- (void)ubiquitousKeyValueStoreDidChange:(NSNotification *)notification
{
    /* TODO: don't blindly trust that the Cloud knows best */
    _recentURLs = [NSMutableArray arrayWithArray:[[NSUbiquitousKeyValueStore defaultStore] arrayForKey:kVLCRecentURLs]];
49
    _recentURLTitles = [NSMutableDictionary dictionaryWithDictionary:[[NSUbiquitousKeyValueStore defaultStore] dictionaryForKey:kVLCRecentURLTitles]];
50 51 52
    [self.historyTableView reloadData];
}

53 54
- (void)dealloc
{
55
    [[NSNotificationCenter defaultCenter] removeObserver:self];
56 57
}

58 59 60
- (void)viewDidLoad
{
    [super viewDidLoad];
61

62 63 64 65 66 67 68 69 70 71 72 73
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self
                           selector:@selector(ubiquitousKeyValueStoreDidChange:)
                               name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
                             object:[NSUbiquitousKeyValueStore defaultStore]];

    /* force store update */
    NSUbiquitousKeyValueStore *ubiquitousKeyValueStore = [NSUbiquitousKeyValueStore defaultStore];
    [ubiquitousKeyValueStore synchronize];

    /* fetch data from cloud */
    _recentURLs = [NSMutableArray arrayWithArray:[[NSUbiquitousKeyValueStore defaultStore] arrayForKey:kVLCRecentURLs]];
74
    _recentURLTitles = [NSMutableDictionary dictionaryWithDictionary:[[NSUbiquitousKeyValueStore defaultStore] dictionaryForKey:kVLCRecentURLTitles]];
75 76 77 78 79 80 81 82 83 84 85 86 87

    /* merge data from local storage (aka legacy VLC versions) */
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSArray *localRecentUrls = [defaults objectForKey:kVLCRecentURLs];
    if (localRecentUrls != nil) {
        if (localRecentUrls.count != 0) {
            [_recentURLs addObjectsFromArray:localRecentUrls];
            [defaults setObject:nil forKey:kVLCRecentURLs];
            [ubiquitousKeyValueStore setArray:_recentURLs forKey:kVLCRecentURLs];
            [ubiquitousKeyValueStore synchronize];
        }
    }

88 89 90 91 92 93
    /*
     * Observe changes to the pasteboard so we can automatically paste it into the URL field.
     * Do not use UIPasteboardChangedNotification because we have copy actions that will trigger it on this screen.
     * Instead when the user comes back to the application from the background (or the inactive state by pulling down notification center), update the URL field.
     * Using the 'active' rather than 'foreground' notification for future proofing if iOS ever allows running multiple apps on the same screen (which would allow the pasteboard to be changed without truly backgrounding the app).
     */
94 95 96 97
    [notificationCenter addObserver:self
                           selector:@selector(applicationDidBecomeActive:)
                               name:UIApplicationDidBecomeActiveNotification
                             object:[UIApplication sharedApplication]];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
98 99

    [self.openButton setTitle:NSLocalizedString(@"OPEN_NETWORK", nil) forState:UIControlStateNormal];
100
    [self.privateModeLabel setText:NSLocalizedString(@"PRIVATE_PLAYBACK_TOGGLE", nil)];
101 102 103 104
    UILabel *scanSubModelabel = self.ScanSubModeLabel;
    [scanSubModelabel setText:NSLocalizedString(@"SCAN_SUBTITLE_TOGGLE", nil)];
    [scanSubModelabel setAdjustsFontSizeToFitWidth:YES];
    [scanSubModelabel setNumberOfLines:0];
105
    self.title = NSLocalizedString(@"OPEN_NETWORK", nil);
106
    self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedRevealMenuButtonWithTarget:self andSelector:@selector(goBack:)];
107
    [self.whatToOpenHelpLabel setText:NSLocalizedString(@"OPEN_NETWORK_HELP", nil)];
108
    self.urlField.delegate = self;
109
    self.urlField.keyboardType = UIKeyboardTypeURL;
110
    self.historyTableView.backgroundColor = [UIColor VLCDarkBackgroundColor];
111

Felix Paul Kühne's avatar
Felix Paul Kühne committed
112 113 114
    NSAttributedString *coloredAttributedPlaceholder = [[NSAttributedString alloc] initWithString:@"http://myserver.com/file.mkv" attributes:@{NSForegroundColorAttributeName: [UIColor VLCLightTextColor]}];
    self.urlField.attributedPlaceholder = coloredAttributedPlaceholder;
    self.edgesForExtendedLayout = UIRectEdgeNone;
115 116 117

    // This will be called every time this VC is opened by the side menu controller
    [self updatePasteboardTextInURLField];
118 119 120 121 122 123 124 125

    // Registering a custom menu item for renaming streams
    NSString *renameTitle = NSLocalizedString(@"BUTTON_RENAME", nil);
    SEL renameStreamSelector = @selector(renameStream:);
    UIMenuItem *renameItem = [[UIMenuItem alloc] initWithTitle:renameTitle action:renameStreamSelector];
    UIMenuController *sharedMenuController = [UIMenuController sharedMenuController];
    [sharedMenuController setMenuItems:@[renameItem]];
    [sharedMenuController update];
126 127
}

128
- (void)updatePasteboardTextInURLField
129
{
130 131 132
    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
    if ([pasteboard containsPasteboardTypes:@[@"public.url"]])
        self.urlField.text = [[pasteboard valueForPasteboardType:@"public.url"] absoluteString];
133
}
134

135 136
- (void)viewWillAppear:(BOOL)animated
{
137 138
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    self.privateToggleSwitch.on = [defaults boolForKey:kVLCPrivateWebStreaming];
139
    self.ScanSubToggleSwitch.on = [defaults boolForKey:kVLChttpScanSubtitle];
140 141 142 143

    [super viewWillAppear:animated];
}

144 145
- (void)viewWillDisappear:(BOOL)animated
{
146 147 148 149
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIApplicationDidBecomeActiveNotification
                                                  object:[UIApplication sharedApplication]];

150 151
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setBool:self.privateToggleSwitch.on forKey:kVLCPrivateWebStreaming];
152
    [defaults setBool:self.ScanSubToggleSwitch.on forKey:kVLChttpScanSubtitle];
153 154
    [defaults synchronize];

155 156 157
    /* force update before we leave */
    [[NSUbiquitousKeyValueStore defaultStore] synchronize];

158 159 160
    [super viewWillDisappear:animated];
}

161 162 163 164
- (CGSize)contentSizeForViewInPopover {
    return [self.view sizeThatFits:CGSizeMake(320, 800)];
}

165
#pragma mark - UI interaction
166
- (BOOL)shouldAutorotate
167
{
168
    UIInterfaceOrientation toInterfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
169 170 171 172 173
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
        return NO;
    return YES;
}

174 175
- (IBAction)goBack:(id)sender
{
176
    [self.view endEditing:YES];
177
    [[VLCSidebarController sharedInstance] toggleSidebar];
178 179
}

180 181 182 183
- (IBAction)openButtonAction:(id)sender
{
    if ([self.urlField.text length] > 0) {
        if (!self.privateToggleSwitch.on) {
184 185 186
            NSString *urlString = self.urlField.text;
            if ([_recentURLs indexOfObject:urlString] != NSNotFound)
                [_recentURLs removeObject:urlString];
187

188
            if (_recentURLs.count >= 100)
189
                [_recentURLs removeLastObject];
190
            [_recentURLs addObject:urlString];
191
            [[NSUbiquitousKeyValueStore defaultStore] setArray:_recentURLs forKey:kVLCRecentURLs];
192

193 194
            [self.historyTableView reloadData];
        }
195
        [self.urlField resignFirstResponder];
196
        [self performSelectorInBackground:@selector(_openURLStringAndDismiss:) withObject:self.urlField.text];
197 198 199
    }
}

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
- (void)renameStreamFromCell:(UITableViewCell *)cell {
    NSIndexPath *cellIndexPath = [self.historyTableView indexPathForCell:cell];
    NSString *renameString = NSLocalizedString(@"BUTTON_RENAME", nil);
    NSString *cancelString = NSLocalizedString(@"BUTTON_CANCEL", nil);

    if ([UIAlertController class])
    {
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:renameString
                                                                                 message:nil
                                                                          preferredStyle:UIAlertControllerStyleAlert];

        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelString
                                                        style:UIAlertActionStyleCancel
                                                             handler:nil];
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            NSString *streamTitle = alertController.textFields.firstObject.text;
            [self renameStreamWithTitle:streamTitle atIndex:cellIndexPath.row];
        }];

        [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
            textField.text = cell.textLabel.text;

            [[NSNotificationCenter defaultCenter] addObserverForName:UITextFieldTextDidChangeNotification
                                                              object:textField
                                                               queue:[NSOperationQueue mainQueue]
                                                          usingBlock:^(NSNotification * _Nonnull note) {
                okAction.enabled = (textField.text.length != 0);
            }];
        }];

        [alertController addAction:cancelAction];
        [alertController addAction:okAction];

        [self presentViewController:alertController animated:YES completion:nil];
    }
    else
    {
        UIAlertView *alertView = [[UIAlertView alloc] init];
        alertView.delegate = self;
        alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
        alertView.title = renameString;
        alertView.tag = cellIndexPath.row; // Dirty...
        [alertView addButtonWithTitle:cancelString];
        [alertView addButtonWithTitle:@"OK"];
        [alertView show];
    }
}

- (void)renameStreamWithTitle:(NSString *)title atIndex:(NSInteger)index {
    [_recentURLTitles setObject:title forKey:[@(index) stringValue]];
    [[NSUbiquitousKeyValueStore defaultStore] setDictionary:_recentURLTitles forKey:kVLCRecentURLTitles];
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self.historyTableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
    }];
}

#pragma mark - alert view delegate (iOS 7)

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    NSString *streamTitle = [alertView textFieldAtIndex:0].text;
    [self renameStreamWithTitle:streamTitle atIndex:alertView.tag]; // Dirty...
}

263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
#pragma mark - table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _recentURLs.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"StreamingHistoryCell";

278
    VLCStreamingHistoryCell *cell = (VLCStreamingHistoryCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
279
    if (cell == nil) {
280 281 282
        cell = [[VLCStreamingHistoryCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        cell.delegate = self;
        [cell customizeAppearance];
283 284
    }

Felix Paul Kühne's avatar
Felix Paul Kühne committed
285
    NSString *content = [_recentURLs[indexPath.row] stringByRemovingPercentEncoding];
286 287
    NSString *possibleTitle = _recentURLTitles[[@(indexPath.row) stringValue]];

288
    cell.detailTextLabel.text = content;
289
    cell.textLabel.text = (possibleTitle != nil) ? possibleTitle : [content lastPathComponent];
290 291 292 293 294 295 296 297

    return cell;
}

#pragma mark - table view delegate

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
298
    cell.backgroundColor = (indexPath.row % 2 == 0)? [UIColor blackColor]: [UIColor VLCDarkBackgroundColor];
299 300 301 302 303 304 305 306 307 308 309
}

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [_recentURLs removeObjectAtIndex:indexPath.row];
310
        [_recentURLTitles removeObjectForKey:[@(indexPath.row) stringValue]];
311
        [[NSUbiquitousKeyValueStore defaultStore] setArray:_recentURLs forKey:kVLCRecentURLs];
312
        [[NSUbiquitousKeyValueStore defaultStore] setDictionary:_recentURLTitles forKey:kVLCRecentURLTitles];
313 314 315 316 317 318 319
        [tableView reloadData];
    }
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self.historyTableView deselectRowAtIndexPath:indexPath animated:NO];
320
    [self performSelectorInBackground:@selector(_openURLStringAndDismiss:) withObject:_recentURLs[indexPath.row]];
321 322
}

323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
- (void)tableView:(UITableView *)tableView
    performAction:(SEL)action
forRowAtIndexPath:(NSIndexPath *)indexPath
       withSender:(id)sender
{
    NSString *actionText = NSStringFromSelector(action);

    if ([actionText isEqualToString:@"copy:"])
        [UIPasteboard generalPasteboard].string = _recentURLs[indexPath.row];
}

- (BOOL)tableView:(UITableView *)tableView
 canPerformAction:(SEL)action
forRowAtIndexPath:(NSIndexPath *)indexPath
       withSender:(id)sender
{
    NSString *actionText = NSStringFromSelector(action);

    if ([actionText isEqualToString:@"copy:"])
        return YES;

    return NO;
}

- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES;
}

352 353 354
#pragma mark - internals
- (void)_openURLStringAndDismiss:(NSString *)url
{
355 356 357 358
    NSURL *URLscheme = [NSURL URLWithString:url];
    NSString *URLofSubtitle = nil;

    if ([URLscheme.scheme isEqualToString:@"http"])
359 360
        if (self.ScanSubToggleSwitch.on)
            URLofSubtitle = [self _checkURLofSubtitle:url];
361

362 363
    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
    [vpc playURL:[NSURL URLWithString:url] subtitlesFilePath:URLofSubtitle];
364 365 366 367 368
}

- (NSString *)_checkURLofSubtitle:(NSString *)url
{
    NSCharacterSet *characterFilter = [NSCharacterSet characterSetWithCharactersInString:@"\\.():$"];
369
    NSString *subtitleFileExtensions = [[kSupportedSubtitleFileExtensions componentsSeparatedByCharactersInSet:characterFilter] componentsJoinedByString:@""];
370
    NSArray *arraySubtitleFileExtensions = [subtitleFileExtensions componentsSeparatedByString:@"|"];
371
    NSString *urlTemp = [[url stringByDeletingPathExtension] stringByAppendingString:@"."];
372
    NSUInteger count = arraySubtitleFileExtensions.count;
373

374 375
    for (int i = 0; i < count; i++) {
        NSString *checkAddress = [urlTemp stringByAppendingString:arraySubtitleFileExtensions[i]];
376 377 378 379
        NSURL *checkURL = [NSURL URLWithString:checkAddress];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:checkURL];
        request.HTTPMethod = @"HEAD";

380
        NSURLResponse *response = nil;
381
        [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
382 383 384
        NSInteger httpStatus = [(NSHTTPURLResponse *)response statusCode];

        if (httpStatus == 200) {
385
            NSString *fileSubtitlePath = [self _getFileSubtitleFromServer:checkURL];
386
            return fileSubtitlePath;
387 388 389
        }
    }
    return nil;
390 391
}

392 393
- (NSString *)_getFileSubtitleFromServer:(NSURL *)url
{
394
    NSString *fileSubtitlePath = nil;
395 396 397 398 399 400
    NSString *fileName = [[url path] lastPathComponent];
    NSData *receivedSub = [NSData dataWithContentsOfURL:url];

    if (receivedSub.length < [[UIDevice currentDevice] freeDiskspace].longLongValue) {
        NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        NSString *directoryPath = [searchPaths objectAtIndex:0];
401
        fileSubtitlePath = [directoryPath stringByAppendingPathComponent:fileName];
402
        NSFileManager *fileManager = [NSFileManager defaultManager];
403 404 405
        if (![fileManager fileExistsAtPath:fileSubtitlePath]) {
            [fileManager createFileAtPath:fileSubtitlePath contents:nil attributes:nil];
            if (![fileManager fileExistsAtPath:fileSubtitlePath])
406 407
                APLog(@"file creation failed, no data was saved");
        }
408
        [receivedSub writeToFile:fileSubtitlePath atomically:YES];
409
    } else {
410 411 412 413 414
        VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"DISK_FULL", nil)
                                                          message:[NSString stringWithFormat:NSLocalizedString(@"DISK_FULL_FORMAT", nil), fileName, [[UIDevice currentDevice] model]]
                                                         delegate:self
                                                cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
                                                otherButtonTitles:nil];
415
        [alert performSelectorOnMainThread:@selector(show) withObject:nil waitUntilDone:NO];
416 417
    }

418
    return fileSubtitlePath;
419 420
}

421
#pragma mark - text view delegate
422 423
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
424 425 426 427
    [self.urlField resignFirstResponder];
    return NO;
}

428
@end