VLCOpenNetworkStreamViewController.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. /*****************************************************************************
  2. * VLCOpenNetworkStreamViewController.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  9. * Gleb Pinigin <gpinigin # gmail.com>
  10. * Pierre Sagaspe <pierre.sagaspe # me.com>
  11. * Adam Viaud <mcnight # mcnight.fr>
  12. * Refer to the COPYING file of the official project for license.
  13. *****************************************************************************/
  14. #import "VLCOpenNetworkStreamViewController.h"
  15. #import "VLCPlaybackController.h"
  16. #import "VLCLibraryViewController.h"
  17. #import "VLCMenuTableViewController.h"
  18. #import "VLCStreamingHistoryCell.h"
  19. #import "UIDevice+VLC.h"
  20. @interface VLCOpenNetworkStreamViewController () <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate, UIAlertViewDelegate, VLCStreamingHistoryCellMenuItemProtocol>
  21. {
  22. NSMutableArray *_recentURLs;
  23. NSMutableDictionary *_recentURLTitles;
  24. }
  25. @end
  26. @implementation VLCOpenNetworkStreamViewController
  27. + (void)initialize
  28. {
  29. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  30. NSDictionary *appDefaults = @{kVLCRecentURLs : @[], kVLCRecentURLTitles : @{}, kVLCPrivateWebStreaming : @(NO)};
  31. [defaults registerDefaults:appDefaults];
  32. }
  33. - (void)applicationDidBecomeActive:(NSNotification *)notification
  34. {
  35. [self updatePasteboardTextInURLField];
  36. }
  37. - (void)ubiquitousKeyValueStoreDidChange:(NSNotification *)notification
  38. {
  39. /* TODO: don't blindly trust that the Cloud knows best */
  40. _recentURLs = [NSMutableArray arrayWithArray:[[NSUbiquitousKeyValueStore defaultStore] arrayForKey:kVLCRecentURLs]];
  41. _recentURLTitles = [NSMutableDictionary dictionaryWithDictionary:[[NSUbiquitousKeyValueStore defaultStore] dictionaryForKey:kVLCRecentURLTitles]];
  42. [self.historyTableView reloadData];
  43. }
  44. - (void)dealloc
  45. {
  46. [[NSNotificationCenter defaultCenter] removeObserver:self];
  47. }
  48. - (void)viewDidLoad
  49. {
  50. [super viewDidLoad];
  51. NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
  52. [notificationCenter addObserver:self
  53. selector:@selector(ubiquitousKeyValueStoreDidChange:)
  54. name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
  55. object:[NSUbiquitousKeyValueStore defaultStore]];
  56. /* force store update */
  57. NSUbiquitousKeyValueStore *ubiquitousKeyValueStore = [NSUbiquitousKeyValueStore defaultStore];
  58. [ubiquitousKeyValueStore synchronize];
  59. /* fetch data from cloud */
  60. _recentURLs = [NSMutableArray arrayWithArray:[[NSUbiquitousKeyValueStore defaultStore] arrayForKey:kVLCRecentURLs]];
  61. _recentURLTitles = [NSMutableDictionary dictionaryWithDictionary:[[NSUbiquitousKeyValueStore defaultStore] dictionaryForKey:kVLCRecentURLTitles]];
  62. /* merge data from local storage (aka legacy VLC versions) */
  63. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  64. NSArray *localRecentUrls = [defaults objectForKey:kVLCRecentURLs];
  65. if (localRecentUrls != nil) {
  66. if (localRecentUrls.count != 0) {
  67. [_recentURLs addObjectsFromArray:localRecentUrls];
  68. [defaults setObject:nil forKey:kVLCRecentURLs];
  69. [ubiquitousKeyValueStore setArray:_recentURLs forKey:kVLCRecentURLs];
  70. [ubiquitousKeyValueStore synchronize];
  71. }
  72. }
  73. /*
  74. * Observe changes to the pasteboard so we can automatically paste it into the URL field.
  75. * Do not use UIPasteboardChangedNotification because we have copy actions that will trigger it on this screen.
  76. * 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.
  77. * 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).
  78. */
  79. [notificationCenter addObserver:self
  80. selector:@selector(applicationDidBecomeActive:)
  81. name:UIApplicationDidBecomeActiveNotification
  82. object:[UIApplication sharedApplication]];
  83. [self.openButton setTitle:NSLocalizedString(@"OPEN_NETWORK", nil) forState:UIControlStateNormal];
  84. [self.privateModeLabel setText:NSLocalizedString(@"PRIVATE_PLAYBACK_TOGGLE", nil)];
  85. UILabel *scanSubModelabel = self.ScanSubModeLabel;
  86. [scanSubModelabel setText:NSLocalizedString(@"SCAN_SUBTITLE_TOGGLE", nil)];
  87. [scanSubModelabel setAdjustsFontSizeToFitWidth:YES];
  88. [scanSubModelabel setNumberOfLines:0];
  89. self.title = NSLocalizedString(@"OPEN_NETWORK", nil);
  90. self.navigationItem.leftBarButtonItem = [UIBarButtonItem themedRevealMenuButtonWithTarget:self andSelector:@selector(goBack:)];
  91. [self.whatToOpenHelpLabel setText:NSLocalizedString(@"OPEN_NETWORK_HELP", nil)];
  92. self.urlField.delegate = self;
  93. self.urlField.keyboardType = UIKeyboardTypeURL;
  94. self.historyTableView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  95. NSAttributedString *coloredAttributedPlaceholder = [[NSAttributedString alloc] initWithString:@"http://myserver.com/file.mkv" attributes:@{NSForegroundColorAttributeName: [UIColor VLCLightTextColor]}];
  96. self.urlField.attributedPlaceholder = coloredAttributedPlaceholder;
  97. self.edgesForExtendedLayout = UIRectEdgeNone;
  98. // This will be called every time this VC is opened by the side menu controller
  99. [self updatePasteboardTextInURLField];
  100. // Registering a custom menu item for renaming streams
  101. NSString *renameTitle = NSLocalizedString(@"BUTTON_RENAME", nil);
  102. SEL renameStreamSelector = @selector(renameStream:);
  103. UIMenuItem *renameItem = [[UIMenuItem alloc] initWithTitle:renameTitle action:renameStreamSelector];
  104. UIMenuController *sharedMenuController = [UIMenuController sharedMenuController];
  105. [sharedMenuController setMenuItems:@[renameItem]];
  106. [sharedMenuController update];
  107. }
  108. - (void)updatePasteboardTextInURLField
  109. {
  110. UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
  111. if ([pasteboard containsPasteboardTypes:@[@"public.url"]])
  112. self.urlField.text = [[pasteboard valueForPasteboardType:@"public.url"] absoluteString];
  113. }
  114. - (void)viewWillAppear:(BOOL)animated
  115. {
  116. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  117. self.privateToggleSwitch.on = [defaults boolForKey:kVLCPrivateWebStreaming];
  118. self.ScanSubToggleSwitch.on = [defaults boolForKey:kVLChttpScanSubtitle];
  119. [super viewWillAppear:animated];
  120. }
  121. - (void)viewWillDisappear:(BOOL)animated
  122. {
  123. [[NSNotificationCenter defaultCenter] removeObserver:self
  124. name:UIApplicationDidBecomeActiveNotification
  125. object:[UIApplication sharedApplication]];
  126. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  127. [defaults setBool:self.privateToggleSwitch.on forKey:kVLCPrivateWebStreaming];
  128. [defaults setBool:self.ScanSubToggleSwitch.on forKey:kVLChttpScanSubtitle];
  129. [defaults synchronize];
  130. /* force update before we leave */
  131. [[NSUbiquitousKeyValueStore defaultStore] synchronize];
  132. [super viewWillDisappear:animated];
  133. }
  134. - (CGSize)contentSizeForViewInPopover {
  135. return [self.view sizeThatFits:CGSizeMake(320, 800)];
  136. }
  137. #pragma mark - UI interaction
  138. - (BOOL)shouldAutorotate
  139. {
  140. UIInterfaceOrientation toInterfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
  141. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
  142. return NO;
  143. return YES;
  144. }
  145. - (IBAction)goBack:(id)sender
  146. {
  147. [self.view endEditing:YES];
  148. [[VLCSidebarController sharedInstance] toggleSidebar];
  149. }
  150. - (IBAction)openButtonAction:(id)sender
  151. {
  152. if ([self.urlField.text length] > 0) {
  153. if (!self.privateToggleSwitch.on) {
  154. NSString *urlString = self.urlField.text;
  155. if ([_recentURLs indexOfObject:urlString] != NSNotFound)
  156. [_recentURLs removeObject:urlString];
  157. if (_recentURLs.count >= 100)
  158. [_recentURLs removeLastObject];
  159. [_recentURLs addObject:urlString];
  160. [[NSUbiquitousKeyValueStore defaultStore] setArray:_recentURLs forKey:kVLCRecentURLs];
  161. [self.historyTableView reloadData];
  162. }
  163. [self.urlField resignFirstResponder];
  164. [self performSelectorInBackground:@selector(_openURLStringAndDismiss:) withObject:self.urlField.text];
  165. }
  166. }
  167. - (void)renameStreamFromCell:(UITableViewCell *)cell {
  168. NSIndexPath *cellIndexPath = [self.historyTableView indexPathForCell:cell];
  169. NSString *renameString = NSLocalizedString(@"BUTTON_RENAME", nil);
  170. NSString *cancelString = NSLocalizedString(@"BUTTON_CANCEL", nil);
  171. if ([UIAlertController class])
  172. {
  173. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:renameString
  174. message:nil
  175. preferredStyle:UIAlertControllerStyleAlert];
  176. UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelString
  177. style:UIAlertActionStyleCancel
  178. handler:nil];
  179. UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  180. NSString *streamTitle = alertController.textFields.firstObject.text;
  181. [self renameStreamWithTitle:streamTitle atIndex:cellIndexPath.row];
  182. }];
  183. [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
  184. textField.text = cell.textLabel.text;
  185. [[NSNotificationCenter defaultCenter] addObserverForName:UITextFieldTextDidChangeNotification
  186. object:textField
  187. queue:[NSOperationQueue mainQueue]
  188. usingBlock:^(NSNotification * _Nonnull note) {
  189. okAction.enabled = (textField.text.length != 0);
  190. }];
  191. }];
  192. [alertController addAction:cancelAction];
  193. [alertController addAction:okAction];
  194. [self presentViewController:alertController animated:YES completion:nil];
  195. }
  196. else
  197. {
  198. UIAlertView *alertView = [[UIAlertView alloc] init];
  199. alertView.delegate = self;
  200. alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
  201. alertView.title = renameString;
  202. alertView.tag = cellIndexPath.row; // Dirty...
  203. [alertView addButtonWithTitle:cancelString];
  204. [alertView addButtonWithTitle:@"OK"];
  205. [alertView show];
  206. }
  207. }
  208. - (void)renameStreamWithTitle:(NSString *)title atIndex:(NSInteger)index {
  209. [_recentURLTitles setObject:title forKey:[@(index) stringValue]];
  210. [[NSUbiquitousKeyValueStore defaultStore] setDictionary:_recentURLTitles forKey:kVLCRecentURLTitles];
  211. [[NSOperationQueue mainQueue] addOperationWithBlock:^{
  212. [self.historyTableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
  213. }];
  214. }
  215. #pragma mark - alert view delegate (iOS 7)
  216. - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
  217. NSString *streamTitle = [alertView textFieldAtIndex:0].text;
  218. [self renameStreamWithTitle:streamTitle atIndex:alertView.tag]; // Dirty...
  219. }
  220. #pragma mark - table view data source
  221. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  222. {
  223. return 1;
  224. }
  225. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  226. {
  227. return _recentURLs.count;
  228. }
  229. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  230. {
  231. static NSString *CellIdentifier = @"StreamingHistoryCell";
  232. VLCStreamingHistoryCell *cell = (VLCStreamingHistoryCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  233. if (cell == nil) {
  234. cell = [[VLCStreamingHistoryCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
  235. cell.delegate = self;
  236. [cell customizeAppearance];
  237. }
  238. NSString *content = [_recentURLs[indexPath.row] stringByRemovingPercentEncoding];
  239. NSString *possibleTitle = _recentURLTitles[[@(indexPath.row) stringValue]];
  240. cell.detailTextLabel.text = content;
  241. cell.textLabel.text = (possibleTitle != nil) ? possibleTitle : [content lastPathComponent];
  242. return cell;
  243. }
  244. #pragma mark - table view delegate
  245. - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
  246. {
  247. cell.backgroundColor = (indexPath.row % 2 == 0)? [UIColor blackColor]: [UIColor VLCDarkBackgroundColor];
  248. }
  249. - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
  250. {
  251. return YES;
  252. }
  253. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
  254. {
  255. if (editingStyle == UITableViewCellEditingStyleDelete) {
  256. [_recentURLs removeObjectAtIndex:indexPath.row];
  257. [_recentURLTitles removeObjectForKey:[@(indexPath.row) stringValue]];
  258. [[NSUbiquitousKeyValueStore defaultStore] setArray:_recentURLs forKey:kVLCRecentURLs];
  259. [[NSUbiquitousKeyValueStore defaultStore] setDictionary:_recentURLTitles forKey:kVLCRecentURLTitles];
  260. [tableView reloadData];
  261. }
  262. }
  263. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  264. {
  265. [self.historyTableView deselectRowAtIndexPath:indexPath animated:NO];
  266. [self performSelectorInBackground:@selector(_openURLStringAndDismiss:) withObject:_recentURLs[indexPath.row]];
  267. }
  268. - (void)tableView:(UITableView *)tableView
  269. performAction:(SEL)action
  270. forRowAtIndexPath:(NSIndexPath *)indexPath
  271. withSender:(id)sender
  272. {
  273. NSString *actionText = NSStringFromSelector(action);
  274. if ([actionText isEqualToString:@"copy:"])
  275. [UIPasteboard generalPasteboard].string = _recentURLs[indexPath.row];
  276. }
  277. - (BOOL)tableView:(UITableView *)tableView
  278. canPerformAction:(SEL)action
  279. forRowAtIndexPath:(NSIndexPath *)indexPath
  280. withSender:(id)sender
  281. {
  282. NSString *actionText = NSStringFromSelector(action);
  283. if ([actionText isEqualToString:@"copy:"])
  284. return YES;
  285. return NO;
  286. }
  287. - (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
  288. {
  289. return YES;
  290. }
  291. #pragma mark - internals
  292. - (void)_openURLStringAndDismiss:(NSString *)url
  293. {
  294. NSURL *URLscheme = [NSURL URLWithString:url];
  295. NSString *URLofSubtitle = nil;
  296. if ([URLscheme.scheme isEqualToString:@"http"])
  297. if (self.ScanSubToggleSwitch.on)
  298. URLofSubtitle = [self _checkURLofSubtitle:url];
  299. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  300. [vpc playURL:[NSURL URLWithString:url] subtitlesFilePath:URLofSubtitle];
  301. }
  302. - (NSString *)_checkURLofSubtitle:(NSString *)url
  303. {
  304. NSCharacterSet *characterFilter = [NSCharacterSet characterSetWithCharactersInString:@"\\.():$"];
  305. NSString *subtitleFileExtensions = [[kSupportedSubtitleFileExtensions componentsSeparatedByCharactersInSet:characterFilter] componentsJoinedByString:@""];
  306. NSArray *arraySubtitleFileExtensions = [subtitleFileExtensions componentsSeparatedByString:@"|"];
  307. NSString *urlTemp = [[url stringByDeletingPathExtension] stringByAppendingString:@"."];
  308. NSUInteger count = arraySubtitleFileExtensions.count;
  309. for (int i = 0; i < count; i++) {
  310. NSString *checkAddress = [urlTemp stringByAppendingString:arraySubtitleFileExtensions[i]];
  311. NSURL *checkURL = [NSURL URLWithString:checkAddress];
  312. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:checkURL];
  313. request.HTTPMethod = @"HEAD";
  314. NSURLResponse *response = nil;
  315. [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
  316. NSInteger httpStatus = [(NSHTTPURLResponse *)response statusCode];
  317. if (httpStatus == 200) {
  318. NSString *fileSubtitlePath = [self _getFileSubtitleFromServer:checkURL];
  319. return fileSubtitlePath;
  320. }
  321. }
  322. return nil;
  323. }
  324. - (NSString *)_getFileSubtitleFromServer:(NSURL *)url
  325. {
  326. NSString *fileSubtitlePath = nil;
  327. NSString *fileName = [[url path] lastPathComponent];
  328. NSData *receivedSub = [NSData dataWithContentsOfURL:url];
  329. if (receivedSub.length < [[UIDevice currentDevice] freeDiskspace].longLongValue) {
  330. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  331. NSString *directoryPath = [searchPaths objectAtIndex:0];
  332. fileSubtitlePath = [directoryPath stringByAppendingPathComponent:fileName];
  333. NSFileManager *fileManager = [NSFileManager defaultManager];
  334. if (![fileManager fileExistsAtPath:fileSubtitlePath]) {
  335. [fileManager createFileAtPath:fileSubtitlePath contents:nil attributes:nil];
  336. if (![fileManager fileExistsAtPath:fileSubtitlePath])
  337. APLog(@"file creation failed, no data was saved");
  338. }
  339. [receivedSub writeToFile:fileSubtitlePath atomically:YES];
  340. } else {
  341. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  342. message:[NSString stringWithFormat:NSLocalizedString(@"DISK_FULL_FORMAT", nil), fileName, [[UIDevice currentDevice] model]]
  343. delegate:self
  344. cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
  345. otherButtonTitles:nil];
  346. [alert performSelectorOnMainThread:@selector(show) withObject:nil waitUntilDone:NO];
  347. }
  348. return fileSubtitlePath;
  349. }
  350. #pragma mark - text view delegate
  351. - (BOOL)textFieldShouldReturn:(UITextField *)textField
  352. {
  353. [self.urlField resignFirstResponder];
  354. return NO;
  355. }
  356. @end