VLCLocalServerFolderListViewController.m 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. /*****************************************************************************
  2. * VLCLocalServerFolderListViewController.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  9. * Pierre SAGASPE <pierre.sagaspe # me.com>
  10. *
  11. * Refer to the COPYING file of the official project for license.
  12. *****************************************************************************/
  13. #import "VLCLocalServerFolderListViewController.h"
  14. #import "MediaServerBasicObjectParser.h"
  15. #import "MediaServer1ItemObject.h"
  16. #import "MediaServer1ContainerObject.h"
  17. #import "MediaServer1Device.h"
  18. #import "VLCLocalNetworkListCell.h"
  19. #import "VLCAppDelegate.h"
  20. #import "VLCPlaylistViewController.h"
  21. #import "VLCDownloadViewController.h"
  22. #import "WhiteRaccoon.h"
  23. #import "NSString+SupportedMedia.h"
  24. #import "VLCStatusLabel.h"
  25. #import "BasicUPnPDevice+VLC.h"
  26. #import "UIDevice+VLC.h"
  27. #define kVLCServerTypeUPNP 0
  28. #define kVLCServerTypeFTP 1
  29. @interface VLCLocalServerFolderListViewController () <UITableViewDataSource, UITableViewDelegate, WRRequestDelegate, VLCLocalNetworkListCell, UISearchBarDelegate, UISearchDisplayDelegate, UIActionSheetDelegate>
  30. {
  31. /* UI */
  32. UIBarButtonItem *_menuButton;
  33. /* generic data storage */
  34. NSString *_listTitle;
  35. NSArray *_objectList;
  36. NSMutableArray *_mutableObjectList;
  37. NSUInteger _serverType;
  38. /* UPNP specifics */
  39. MediaServer1Device *_UPNPdevice;
  40. NSString *_UPNProotID;
  41. /* FTP specifics */
  42. NSString *_ftpServerAddress;
  43. NSString *_ftpServerUserName;
  44. NSString *_ftpServerPassword;
  45. NSString *_ftpServerPath;
  46. WRRequestListDirectory *_FTPListDirRequest;
  47. NSMutableArray *_searchData;
  48. UISearchBar *_searchBar;
  49. UISearchDisplayController *_searchDisplayController;
  50. /* UPnP items with multiple resources specifics */
  51. MediaServer1ItemObject *_lastSelectedMediaItem;
  52. UIView *_resourceSelectionActionSheetAnchorView;
  53. }
  54. @end
  55. @implementation VLCLocalServerFolderListViewController
  56. - (void)loadView
  57. {
  58. _tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain];
  59. _tableView.backgroundColor = [UIColor VLCDarkBackgroundColor];
  60. _tableView.delegate = self;
  61. _tableView.dataSource = self;
  62. _tableView.rowHeight = [VLCLocalNetworkListCell heightOfCell];
  63. _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  64. _tableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
  65. self.view = _tableView;
  66. }
  67. - (id)initWithUPNPDevice:(MediaServer1Device*)device header:(NSString*)header andRootID:(NSString*)rootID
  68. {
  69. self = [super init];
  70. if (self) {
  71. _UPNPdevice = device;
  72. _listTitle = header;
  73. _UPNProotID = rootID;
  74. _serverType = kVLCServerTypeUPNP;
  75. _mutableObjectList = [[NSMutableArray alloc] init];
  76. }
  77. return self;
  78. }
  79. - (id)initWithFTPServer:(NSString *)serverAddress userName:(NSString *)username andPassword:(NSString *)password atPath:(NSString *)path
  80. {
  81. self = [super init];
  82. if (self) {
  83. _ftpServerAddress = serverAddress;
  84. _ftpServerUserName = username;
  85. _ftpServerPassword = password;
  86. _ftpServerPath = path;
  87. _serverType = kVLCServerTypeFTP;
  88. }
  89. return self;
  90. }
  91. - (void)viewDidLoad
  92. {
  93. [super viewDidLoad];
  94. if (_serverType == kVLCServerTypeUPNP) {
  95. NSString *sortCriteria = @"";
  96. NSMutableString *outSortCaps = [[NSMutableString alloc] init];
  97. [[_UPNPdevice contentDirectory] GetSortCapabilitiesWithOutSortCaps:outSortCaps];
  98. if ([outSortCaps rangeOfString:@"dc:title"].location != NSNotFound)
  99. {
  100. sortCriteria = @"+dc:title";
  101. }
  102. NSMutableString *outResult = [[NSMutableString alloc] init];
  103. NSMutableString *outNumberReturned = [[NSMutableString alloc] init];
  104. NSMutableString *outTotalMatches = [[NSMutableString alloc] init];
  105. NSMutableString *outUpdateID = [[NSMutableString alloc] init];
  106. [[_UPNPdevice contentDirectory] BrowseWithObjectID:_UPNProotID BrowseFlag:@"BrowseDirectChildren" Filter:@"*" StartingIndex:@"0" RequestedCount:@"0" SortCriteria:sortCriteria OutResult:outResult OutNumberReturned:outNumberReturned OutTotalMatches:outTotalMatches OutUpdateID:outUpdateID];
  107. [_mutableObjectList removeAllObjects];
  108. NSData *didl = [outResult dataUsingEncoding:NSUTF8StringEncoding];
  109. MediaServerBasicObjectParser *parser = [[MediaServerBasicObjectParser alloc] initWithMediaObjectArray:_mutableObjectList itemsOnly:NO];
  110. [parser parseFromData:didl];
  111. } else if (_serverType == kVLCServerTypeFTP) {
  112. if ([_ftpServerPath isEqualToString:@"/"])
  113. _listTitle = _ftpServerAddress;
  114. else
  115. _listTitle = [_ftpServerPath lastPathComponent];
  116. [self _listFTPDirectory];
  117. }
  118. self.tableView.separatorColor = [UIColor VLCDarkBackgroundColor];
  119. self.view.backgroundColor = [UIColor VLCDarkBackgroundColor];
  120. self.title = _listTitle;
  121. _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
  122. UINavigationBar *navBar = self.navigationController.navigationBar;
  123. if (SYSTEM_RUNS_IOS7_OR_LATER)
  124. _searchBar.barTintColor = navBar.barTintColor;
  125. _searchBar.tintColor = navBar.tintColor;
  126. _searchBar.translucent = navBar.translucent;
  127. _searchBar.opaque = navBar.opaque;
  128. _searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:_searchBar contentsController:self];
  129. _searchDisplayController.delegate = self;
  130. _searchDisplayController.searchResultsDataSource = self;
  131. _searchDisplayController.searchResultsDelegate = self;
  132. _searchDisplayController.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  133. _searchDisplayController.searchResultsTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
  134. if (SYSTEM_RUNS_IOS7_OR_LATER)
  135. _searchDisplayController.searchBar.searchBarStyle = UIBarStyleBlack;
  136. _searchBar.delegate = self;
  137. _searchBar.hidden = YES;
  138. UITapGestureRecognizer *tapTwiceGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapTwiceGestureAction:)];
  139. [tapTwiceGesture setNumberOfTapsRequired:2];
  140. [self.navigationController.navigationBar addGestureRecognizer:tapTwiceGesture];
  141. _menuButton = [UIBarButtonItem themedRevealMenuButtonWithTarget:self andSelector:@selector(menuButtonAction:)];
  142. self.navigationItem.rightBarButtonItem = _menuButton;
  143. _searchData = [[NSMutableArray alloc] init];
  144. [_searchData removeAllObjects];
  145. }
  146. - (BOOL)shouldAutorotate
  147. {
  148. UIInterfaceOrientation toInterfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
  149. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
  150. return NO;
  151. return YES;
  152. }
  153. - (IBAction)menuButtonAction:(id)sender
  154. {
  155. [[(VLCAppDelegate*)[UIApplication sharedApplication].delegate revealController] toggleSidebar:![(VLCAppDelegate*)[UIApplication sharedApplication].delegate revealController].sidebarShowing duration:kGHRevealSidebarDefaultAnimationDuration];
  156. if (self.isEditing)
  157. [self setEditing:NO animated:YES];
  158. }
  159. #pragma mark - Table view data source
  160. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  161. {
  162. return 1;
  163. }
  164. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  165. {
  166. if (tableView == self.searchDisplayController.searchResultsTableView)
  167. return _searchData.count;
  168. else {
  169. if (_serverType == kVLCServerTypeUPNP)
  170. return _mutableObjectList.count;
  171. return _objectList.count;
  172. }
  173. }
  174. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  175. {
  176. static NSString *CellIdentifier = @"LocalNetworkCellDetail";
  177. VLCLocalNetworkListCell *cell = (VLCLocalNetworkListCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  178. if (cell == nil)
  179. cell = [VLCLocalNetworkListCell cellWithReuseIdentifier:CellIdentifier];
  180. if (_serverType == kVLCServerTypeUPNP) {
  181. MediaServer1BasicObject *item;
  182. if (tableView == self.searchDisplayController.searchResultsTableView)
  183. item = _searchData[indexPath.row];
  184. else
  185. item = _mutableObjectList[indexPath.row];
  186. if (![item isContainer]) {
  187. MediaServer1ItemObject *mediaItem;
  188. long long mediaSize = 0;
  189. unsigned int durationInSeconds = 0;
  190. unsigned int bitrate = 0;
  191. if (tableView == self.searchDisplayController.searchResultsTableView)
  192. mediaItem = _searchData[indexPath.row];
  193. else
  194. mediaItem = _mutableObjectList[indexPath.row];
  195. MediaServer1ItemRes *resource = nil;
  196. NSEnumerator *e = [[mediaItem resources] objectEnumerator];
  197. while((resource = (MediaServer1ItemRes*)[e nextObject])){
  198. if (resource.bitrate > 0 && resource.durationInSeconds > 0) {
  199. mediaSize = resource.size;
  200. durationInSeconds = resource.durationInSeconds;
  201. bitrate = resource.bitrate;
  202. }
  203. }
  204. if (mediaSize < 1)
  205. mediaSize = [mediaItem.size longLongValue];
  206. if (mediaSize < 1)
  207. mediaSize = (bitrate * durationInSeconds);
  208. // object.item.videoItem.videoBroadcast items (like the HDHomeRun) may not have this information. Center the title (this makes channel names look better for the HDHomeRun)
  209. if (mediaSize > 0 && durationInSeconds > 0) {
  210. [cell setSubtitle: [NSString stringWithFormat:@"%@ (%@)", [NSByteCountFormatter stringFromByteCount:mediaSize countStyle:NSByteCountFormatterCountStyleFile], [VLCTime timeWithInt:durationInSeconds * 1000].stringValue]];
  211. } else {
  212. cell.titleLabelCentered = YES;
  213. }
  214. // Custom TV icon for video broadcasts
  215. if ([[mediaItem objectClass] isEqualToString:@"object.item.videoItem.videoBroadcast"]) {
  216. UIImage *broadcastImage;
  217. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  218. broadcastImage = [UIImage imageNamed:@"TVBroadcastIcon"];
  219. } else {
  220. broadcastImage = [UIImage imageNamed:@"TVBroadcastIcon~ipad"];
  221. }
  222. [cell setIcon:broadcastImage];
  223. } else {
  224. [cell setIcon:[UIImage imageNamed:@"blank"]];
  225. }
  226. [cell setIsDirectory:NO];
  227. if (mediaItem.albumArt != nil)
  228. [cell setIconURL:[NSURL URLWithString:mediaItem.albumArt]];
  229. // Disable downloading for the HDHomeRun for now to avoid infinite downloads (URI needs a duration parameter, otherwise you are just downloading a live stream). VLC also needs an extension in the file name for this to work.
  230. if (![_UPNPdevice VLC_isHDHomeRunMediaServer]) {
  231. cell.isDownloadable = YES;
  232. }
  233. cell.delegate = self;
  234. } else {
  235. [cell setIsDirectory:YES];
  236. if (item.albumArt != nil)
  237. [cell setIconURL:[NSURL URLWithString:item.albumArt]];
  238. [cell setIcon:[UIImage imageNamed:@"folder"]];
  239. }
  240. [cell setTitle:[item title]];
  241. } else if (_serverType == kVLCServerTypeFTP) {
  242. NSMutableArray *ObjList = [[NSMutableArray alloc] init];
  243. [ObjList removeAllObjects];
  244. if (tableView == self.searchDisplayController.searchResultsTableView)
  245. [ObjList addObjectsFromArray:_searchData];
  246. else
  247. [ObjList addObjectsFromArray:_objectList];
  248. NSString *rawFileName = [ObjList[indexPath.row] objectForKey:(id)kCFFTPResourceName];
  249. NSData *flippedData = [rawFileName dataUsingEncoding:[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingFTPTextEncoding] intValue] allowLossyConversion:YES];
  250. cell.title = [[NSString alloc] initWithData:flippedData encoding:NSUTF8StringEncoding];
  251. if ([[ObjList[indexPath.row] objectForKey:(id)kCFFTPResourceType] intValue] == 4) {
  252. cell.isDirectory = YES;
  253. cell.icon = [UIImage imageNamed:@"folder"];
  254. } else {
  255. cell.isDirectory = NO;
  256. cell.icon = [UIImage imageNamed:@"blank"];
  257. cell.subtitle = [NSString stringWithFormat:@"%0.2f MB", (float)([[ObjList[indexPath.row] objectForKey:(id)kCFFTPResourceSize] intValue] / 1e6)];
  258. cell.isDownloadable = YES;
  259. cell.delegate = self;
  260. }
  261. }
  262. return cell;
  263. }
  264. #pragma mark - Table view delegate
  265. - (void)tableView:(UITableView *)tableView willDisplayCell:(VLCLocalNetworkListCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
  266. {
  267. UIColor *color = (indexPath.row % 2 == 0)? [UIColor blackColor]: [UIColor VLCDarkBackgroundColor];
  268. cell.contentView.backgroundColor = cell.titleLabel.backgroundColor = cell.folderTitleLabel.backgroundColor = cell.subtitleLabel.backgroundColor = color;
  269. if (_serverType == kVLCServerTypeFTP)
  270. if([indexPath row] == ((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row)
  271. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate networkActivityStopped];
  272. }
  273. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  274. {
  275. if (_serverType == kVLCServerTypeUPNP) {
  276. MediaServer1BasicObject *item;
  277. if (tableView == self.searchDisplayController.searchResultsTableView)
  278. item = _searchData[indexPath.row];
  279. else
  280. item = _mutableObjectList[indexPath.row];
  281. if ([item isContainer]) {
  282. MediaServer1ContainerObject *container;
  283. if (tableView == self.searchDisplayController.searchResultsTableView)
  284. container = _searchData[indexPath.row];
  285. else
  286. container = _mutableObjectList[indexPath.row];
  287. VLCLocalServerFolderListViewController *targetViewController = [[VLCLocalServerFolderListViewController alloc] initWithUPNPDevice:_UPNPdevice header:[container title] andRootID:[container objectID]];
  288. [[self navigationController] pushViewController:targetViewController animated:YES];
  289. } else {
  290. MediaServer1ItemObject *mediaItem;
  291. if (tableView == self.searchDisplayController.searchResultsTableView)
  292. mediaItem = _searchData[indexPath.row];
  293. else
  294. mediaItem = _mutableObjectList[indexPath.row];
  295. NSURL *itemURL;
  296. NSArray *uriCollectionKeys = [[mediaItem uriCollection] allKeys];
  297. NSUInteger count = uriCollectionKeys.count;
  298. NSRange position;
  299. NSUInteger correctIndex = 0;
  300. NSUInteger numberOfDownloadableResources = 0;
  301. for (NSUInteger i = 0; i < count; i++) {
  302. position = [uriCollectionKeys[i] rangeOfString:@"http-get:*:video/"];
  303. if (position.location != NSNotFound) {
  304. correctIndex = i;
  305. numberOfDownloadableResources++;
  306. }
  307. }
  308. NSArray *uriCollectionObjects = [[mediaItem uriCollection] allValues];
  309. // Present an action sheet for the user to choose which URI to download. Do not deselect the cell to provide visual feedback to the user
  310. if (numberOfDownloadableResources > 1) {
  311. _resourceSelectionActionSheetAnchorView = [tableView cellForRowAtIndexPath:indexPath];
  312. [self presentResourceSelectionActionSheetForUPnPMediaItem:mediaItem forDownloading:NO];
  313. } else {
  314. if (uriCollectionObjects.count > 0) {
  315. itemURL = [NSURL URLWithString:uriCollectionObjects[correctIndex]];
  316. }
  317. if (itemURL) {
  318. VLCAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
  319. [appDelegate openMovieFromURL:itemURL];
  320. }
  321. }
  322. }
  323. } else if (_serverType == kVLCServerTypeFTP) {
  324. NSMutableArray *ObjList = [[NSMutableArray alloc] init];
  325. [ObjList removeAllObjects];
  326. if (tableView == self.searchDisplayController.searchResultsTableView)
  327. [ObjList addObjectsFromArray:_searchData];
  328. else
  329. [ObjList addObjectsFromArray:_objectList];
  330. if ([[ObjList[indexPath.row] objectForKey:(id)kCFFTPResourceType] intValue] == 4) {
  331. NSString *newPath = [NSString stringWithFormat:@"%@/%@", _ftpServerPath, [ObjList[indexPath.row] objectForKey:(id)kCFFTPResourceName]];
  332. VLCLocalServerFolderListViewController *targetViewController = [[VLCLocalServerFolderListViewController alloc] initWithFTPServer:_ftpServerAddress userName:_ftpServerUserName andPassword:_ftpServerPassword atPath:newPath];
  333. [self.navigationController pushViewController:targetViewController animated:YES];
  334. } else {
  335. NSString *rawObjectName = [ObjList[indexPath.row] objectForKey:(id)kCFFTPResourceName];
  336. NSData *flippedData = [rawObjectName dataUsingEncoding:[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingFTPTextEncoding] intValue] allowLossyConversion:YES];
  337. NSString *properObjectName = [[NSString alloc] initWithData:flippedData encoding:NSUTF8StringEncoding];
  338. if (![properObjectName isSupportedFormat]) {
  339. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"FILE_NOT_SUPPORTED", nil)
  340. message:[NSString stringWithFormat:NSLocalizedString(@"FILE_NOT_SUPPORTED_LONG", nil), properObjectName]
  341. delegate:self
  342. cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  343. otherButtonTitles:nil];
  344. [alert show];
  345. } else
  346. [self _streamFTPFile:properObjectName];
  347. }
  348. }
  349. [tableView deselectRowAtIndexPath:indexPath animated:NO];
  350. }
  351. #pragma mark - UPnP Multiple Resources
  352. /// Presents an UIActionSheet for the user to choose which <res> resource to play or download. Contains some display code specific to the HDHomeRun devices. Also parses "DLNA.ORG_PN" protocolInfo.
  353. - (void)presentResourceSelectionActionSheetForUPnPMediaItem:(MediaServer1ItemObject *)mediaItem forDownloading:(BOOL)forDownloading {
  354. NSParameterAssert(mediaItem);
  355. if (!mediaItem) {
  356. return;
  357. }
  358. // Store it so we can act on the action sheet callback.
  359. _lastSelectedMediaItem = mediaItem;
  360. NSArray *uriCollectionKeys = [[_lastSelectedMediaItem uriCollection] allKeys];
  361. NSArray *uriCollectionObjects = [[_lastSelectedMediaItem uriCollection] allValues];
  362. NSUInteger count = uriCollectionKeys.count;
  363. NSRange position;
  364. NSString *titleString;
  365. if (!forDownloading) {
  366. titleString = NSLocalizedString(@"SELECT_RESOURCE_TO_PLAY", nil);
  367. } else {
  368. titleString = NSLocalizedString(@"SELECT_RESOURCE_TO_DOWNLOAD", nil);
  369. }
  370. UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:titleString
  371. delegate:self
  372. cancelButtonTitle:nil
  373. destructiveButtonTitle:nil
  374. otherButtonTitles:nil];
  375. // Provide users with a descriptive action sheet for them to choose based on the multiple resources advertised by DLNA devices (HDHomeRun for example)
  376. for (NSUInteger i = 0; i < count; i++) {
  377. position = [uriCollectionKeys[i] rangeOfString:@"http-get:*:video/"];
  378. if (position.location != NSNotFound) {
  379. NSString *orgPNValue;
  380. NSString *transcodeValue;
  381. // Attempt to parse DLNA.ORG_PN first
  382. NSString *protocolInfo = uriCollectionKeys[i];
  383. NSArray *components = [protocolInfo componentsSeparatedByString:@";"];
  384. NSArray *nonFlagsComponents = [components[0] componentsSeparatedByString:@":"];
  385. NSString *orgPN = [nonFlagsComponents lastObject];
  386. // Check to see if we are where we should be
  387. NSRange orgPNRange = [orgPN rangeOfString:@"DLNA.ORG_PN="];
  388. if (orgPNRange.location == 0) {
  389. orgPNValue = [orgPN substringFromIndex:orgPNRange.length];
  390. }
  391. // HDHomeRun: Get the transcode profile from the HTTP API if possible
  392. if ([_UPNPdevice VLC_isHDHomeRunMediaServer]) {
  393. NSRange transcodeRange = [uriCollectionObjects[i] rangeOfString:@"transcode="];
  394. if (transcodeRange.location != NSNotFound) {
  395. transcodeValue = [uriCollectionObjects[i] substringFromIndex:transcodeRange.location + transcodeRange.length];
  396. // Check that there are no more parameters
  397. NSRange ampersandRange = [transcodeValue rangeOfString:@"&"];
  398. if (ampersandRange.location != NSNotFound) {
  399. transcodeValue = [transcodeValue substringToIndex:transcodeRange.location];
  400. }
  401. transcodeValue = [transcodeValue capitalizedString];
  402. }
  403. }
  404. // Fallbacks to get the most descriptive resource title
  405. NSString *profileTitle;
  406. if ([transcodeValue length] && [orgPNValue length]) {
  407. profileTitle = [NSString stringWithFormat:@"%@ (%@)", transcodeValue, orgPNValue];
  408. // The extra whitespace is to get UIActionSheet to render the text better (this bug has been fixed in iOS 8)
  409. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  410. if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
  411. profileTitle = [NSString stringWithFormat:@" %@ ", profileTitle];
  412. }
  413. }
  414. } else if ([transcodeValue length]) {
  415. profileTitle = transcodeValue;
  416. } else if ([orgPNValue length]) {
  417. profileTitle = orgPNValue;
  418. } else if ([uriCollectionKeys[i] length]) {
  419. profileTitle = uriCollectionKeys[i];
  420. } else if ([uriCollectionObjects[i] length]) {
  421. profileTitle = uriCollectionObjects[i];
  422. } else {
  423. profileTitle = NSLocalizedString(@"UNKNOWN", nil);
  424. }
  425. [actionSheet addButtonWithTitle:profileTitle];
  426. }
  427. }
  428. // If no resources are found, an empty action sheet will be presented, but the fact that we got here implies that we have playable resources, so no special handling for this case is included
  429. actionSheet.cancelButtonIndex = [actionSheet addButtonWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)];
  430. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  431. // Attach it to a specific view (a cell, a download button, etc)
  432. if (_resourceSelectionActionSheetAnchorView) {
  433. CGRect presentationRect = [self.view convertRect:_resourceSelectionActionSheetAnchorView.frame fromView:_resourceSelectionActionSheetAnchorView.superview];
  434. [actionSheet showFromRect:presentationRect inView:self.view animated:YES];
  435. } else {
  436. [actionSheet showInView:self.view];
  437. }
  438. } else {
  439. [actionSheet showInView:self.view];
  440. }
  441. }
  442. #pragma mark - UIActionSheetDelegate
  443. - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
  444. {
  445. // Act on the selected resource that the user selected
  446. if (_lastSelectedMediaItem) {
  447. if (buttonIndex != actionSheet.cancelButtonIndex) {
  448. // Check again through our raw list which items are playable, since this same code was used to build the action sheet. Make sure we choose the right object based on the action sheet button index.
  449. NSArray *uriCollectionKeys = [[_lastSelectedMediaItem uriCollection] allKeys];
  450. NSArray *uriCollectionObjects = [[_lastSelectedMediaItem uriCollection] allValues];
  451. if (uriCollectionObjects.count > 0) {
  452. NSUInteger count = uriCollectionKeys.count;
  453. NSMutableArray *possibleCollectionObjects = [[NSMutableArray alloc] initWithCapacity:[uriCollectionObjects count]];
  454. for (NSUInteger i = 0; i < count; i++) {
  455. if ([uriCollectionKeys[i] rangeOfString:@"http-get:*:video/"].location != NSNotFound) {
  456. [possibleCollectionObjects addObject:uriCollectionObjects[i]];
  457. }
  458. }
  459. NSString *itemURLString = uriCollectionObjects[buttonIndex];
  460. if ([itemURLString length]) {
  461. VLCAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
  462. [appDelegate openMovieFromURL:[NSURL URLWithString:itemURLString]];
  463. }
  464. }
  465. }
  466. _lastSelectedMediaItem = nil;
  467. _resourceSelectionActionSheetAnchorView = nil;
  468. UITableView *activeTableView;
  469. if ([self.searchDisplayController isActive]) {
  470. activeTableView = self.searchDisplayController.searchResultsTableView;
  471. } else {
  472. activeTableView = self.tableView;
  473. }
  474. [activeTableView deselectRowAtIndexPath:[activeTableView indexPathForSelectedRow] animated:NO];
  475. }
  476. }
  477. #pragma mark - FTP specifics
  478. - (void)_listFTPDirectory
  479. {
  480. if (_FTPListDirRequest)
  481. return;
  482. _FTPListDirRequest = [[WRRequestListDirectory alloc] init];
  483. _FTPListDirRequest.delegate = self;
  484. _FTPListDirRequest.hostname = _ftpServerAddress;
  485. _FTPListDirRequest.username = _ftpServerUserName;
  486. _FTPListDirRequest.password = _ftpServerPassword;
  487. _FTPListDirRequest.path = _ftpServerPath;
  488. _FTPListDirRequest.passive = YES;
  489. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate networkActivityStarted];
  490. [_FTPListDirRequest start];
  491. }
  492. - (NSString *)_credentials
  493. {
  494. NSString *cred;
  495. if (_ftpServerUserName.length > 0) {
  496. if (_ftpServerPassword.length > 0)
  497. cred = [NSString stringWithFormat:@"%@:%@@", _ftpServerUserName, _ftpServerPassword];
  498. else
  499. cred = [NSString stringWithFormat:@"%@@", _ftpServerPassword];
  500. } else
  501. cred = @"";
  502. return [cred stringByStandardizingPath];
  503. }
  504. - (void)_downloadFTPFile:(NSString *)fileName
  505. {
  506. NSURL *URLToQueue = [NSURL URLWithString:[[@"ftp" stringByAppendingFormat:@"://%@%@/%@/%@", [self _credentials], _ftpServerAddress, _ftpServerPath, fileName] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
  507. [[(VLCAppDelegate*)[UIApplication sharedApplication].delegate downloadViewController] addURLToDownloadList:URLToQueue fileNameOfMedia:nil];
  508. }
  509. - (void)_downloadUPNPFileFromMediaItem:(MediaServer1ItemObject *)mediaItem
  510. {
  511. NSURL *itemURL;
  512. NSArray *uriCollectionKeys = [[mediaItem uriCollection] allKeys];
  513. NSUInteger count = uriCollectionKeys.count;
  514. NSRange position;
  515. NSUInteger correctIndex = 0;
  516. for (NSUInteger i = 0; i < count; i++) {
  517. position = [uriCollectionKeys[i] rangeOfString:@"http-get:*:video/"];
  518. if (position.location != NSNotFound)
  519. correctIndex = i;
  520. }
  521. NSArray *uriCollectionObjects = [[mediaItem uriCollection] allValues];
  522. if (uriCollectionObjects.count > 0)
  523. itemURL = [NSURL URLWithString:uriCollectionObjects[correctIndex]];
  524. if (itemURL) {
  525. NSString *filename;
  526. /* there are few crappy UPnP servers who don't reveal the correct file extension, so we use a generic fake (#11123) */
  527. if (![itemURL.absoluteString isSupportedFormat])
  528. filename = [mediaItem.title stringByAppendingString:@".vlc"];
  529. else
  530. filename = [[mediaItem.title stringByAppendingString:@"."] stringByAppendingString:[[itemURL absoluteString] pathExtension]];
  531. [[(VLCAppDelegate*)[UIApplication sharedApplication].delegate downloadViewController] addURLToDownloadList:itemURL fileNameOfMedia:filename];
  532. }
  533. }
  534. - (void)requestCompleted:(WRRequest *)request
  535. {
  536. if (request == _FTPListDirRequest) {
  537. NSMutableArray *filteredList = [[NSMutableArray alloc] init];
  538. NSArray *rawList = [(WRRequestListDirectory*)request filesInfo];
  539. NSUInteger count = rawList.count;
  540. for (NSUInteger x = 0; x < count; x++) {
  541. if (![[rawList[x] objectForKey:(id)kCFFTPResourceName] hasPrefix:@"."])
  542. [filteredList addObject:rawList[x]];
  543. }
  544. _objectList = [NSArray arrayWithArray:filteredList];
  545. [self.tableView reloadData];
  546. } else
  547. APLog(@"unknown request %@ completed", request);
  548. }
  549. - (void)requestFailed:(WRRequest *)request
  550. {
  551. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"LOCAL_SERVER_CONNECTION_FAILED_TITLE", nil)
  552. message:NSLocalizedString(@"LOCAL_SERVER_CONNECTION_FAILED_MESSAGE", nil)
  553. delegate:self
  554. cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  555. otherButtonTitles:nil];
  556. [alert show];
  557. APLog(@"request %@ failed with error %i", request, request.error.errorCode);
  558. }
  559. #pragma mark - VLCLocalNetworkListCell delegation
  560. - (void)triggerDownloadForCell:(VLCLocalNetworkListCell *)cell
  561. {
  562. if (_serverType == kVLCServerTypeUPNP) {
  563. MediaServer1ItemObject *item;
  564. if ([self.searchDisplayController isActive])
  565. item = _searchData[[self.searchDisplayController.searchResultsTableView indexPathForCell:cell].row];
  566. else
  567. item = _mutableObjectList[[self.tableView indexPathForCell:cell].row];
  568. [self _downloadUPNPFileFromMediaItem:item];
  569. [cell.statusLabel showStatusMessage:NSLocalizedString(@"DOWNLOADING", nil)];
  570. }else if (_serverType == kVLCServerTypeFTP) {
  571. NSString *rawObjectName;
  572. NSInteger size;
  573. NSMutableArray *ObjList = [[NSMutableArray alloc] init];
  574. [ObjList removeAllObjects];
  575. if ([self.searchDisplayController isActive]) {
  576. [ObjList addObjectsFromArray:_searchData];
  577. rawObjectName = [ObjList[[self.searchDisplayController.searchResultsTableView indexPathForCell:cell].row] objectForKey:(id)kCFFTPResourceName];
  578. size = [[ObjList[[self.searchDisplayController.searchResultsTableView indexPathForCell:cell].row] objectForKey:(id)kCFFTPResourceSize] intValue];
  579. } else {
  580. [ObjList addObjectsFromArray:_objectList];
  581. rawObjectName = [ObjList[[self.tableView indexPathForCell:cell].row] objectForKey:(id)kCFFTPResourceName];
  582. size = [[ObjList[[self.tableView indexPathForCell:cell].row] objectForKey:(id)kCFFTPResourceSize] intValue];
  583. }
  584. NSData *flippedData = [rawObjectName dataUsingEncoding:[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingFTPTextEncoding] intValue] allowLossyConversion:YES];
  585. NSString *properObjectName = [[NSString alloc] initWithData:flippedData encoding:NSUTF8StringEncoding];
  586. if (![properObjectName isSupportedFormat]) {
  587. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"FILE_NOT_SUPPORTED", nil)
  588. message:[NSString stringWithFormat:NSLocalizedString(@"FILE_NOT_SUPPORTED_LONG", nil), properObjectName]
  589. delegate:self
  590. cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  591. otherButtonTitles:nil];
  592. [alert show];
  593. } else {
  594. if (size < [[UIDevice currentDevice] freeDiskspace].longLongValue) {
  595. [self _downloadFTPFile:properObjectName];
  596. [cell.statusLabel showStatusMessage:NSLocalizedString(@"DOWNLOADING", nil)];
  597. } else {
  598. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  599. message:[NSString stringWithFormat:NSLocalizedString(@"DISK_FULL_FORMAT", nil), properObjectName, [[UIDevice currentDevice] model]]
  600. delegate:self
  601. cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
  602. otherButtonTitles:nil];
  603. [alert show];
  604. }
  605. }
  606. }
  607. }
  608. #pragma mark - communication with playback engine
  609. - (void)_streamFTPFile:(NSString *)fileName
  610. {
  611. NSString *URLofSubtitle = nil;
  612. NSMutableArray *SubtitlesList = [[NSMutableArray alloc] init];
  613. [SubtitlesList removeAllObjects];
  614. SubtitlesList = [self _searchSubtitle:fileName];
  615. if(SubtitlesList.count > 0)
  616. URLofSubtitle = [self _getFileSubtitleFromFtpServer:SubtitlesList[0]];
  617. NSURL *URLToPlay = [NSURL URLWithString:[[@"ftp" stringByAppendingFormat:@"://%@%@/%@/%@", [self _credentials], _ftpServerAddress, _ftpServerPath, fileName] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
  618. VLCAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
  619. [appDelegate openMovieWithExternalSubtitleFromURL:URLToPlay externalSubURL:URLofSubtitle];
  620. }
  621. - (NSMutableArray *)_searchSubtitle:(NSString *)url
  622. {
  623. NSString *urlTemp = [[url lastPathComponent] stringByDeletingPathExtension];
  624. NSMutableArray *ObjList = [[NSMutableArray alloc] init];
  625. [ObjList removeAllObjects];
  626. for (int loop = 0; loop < _objectList.count; loop++)
  627. [ObjList addObject:[_objectList[loop] objectForKey:(id)kCFFTPResourceName]];
  628. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@", urlTemp];
  629. NSArray *results = [ObjList filteredArrayUsingPredicate:predicate];
  630. [ObjList removeAllObjects];
  631. for (int cnt = 0; cnt < results.count; cnt++) {
  632. if ([results[cnt] isSupportedSubtitleFormat])
  633. [ObjList addObject:results[cnt]];
  634. }
  635. return ObjList;
  636. }
  637. - (NSString *)_getFileSubtitleFromFtpServer:(NSString *)fileName
  638. {
  639. NSURL *url = [NSURL URLWithString:[[@"ftp" stringByAppendingFormat:@"://%@%@/%@/%@", [self _credentials], _ftpServerAddress, _ftpServerPath, fileName] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
  640. NSString *FileSubtitlePath = nil;
  641. NSData *receivedSub = [NSData dataWithContentsOfURL:url];
  642. if (receivedSub.length < [[UIDevice currentDevice] freeDiskspace].longLongValue) {
  643. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  644. NSString *directoryPath = searchPaths[0];
  645. FileSubtitlePath = [directoryPath stringByAppendingPathComponent:[fileName lastPathComponent]];
  646. NSFileManager *fileManager = [NSFileManager defaultManager];
  647. if (![fileManager fileExistsAtPath:FileSubtitlePath]) {
  648. //create local subtitle file
  649. [fileManager createFileAtPath:FileSubtitlePath contents:nil attributes:nil];
  650. if (![fileManager fileExistsAtPath:FileSubtitlePath])
  651. APLog(@"file creation failed, no data was saved");
  652. }
  653. [receivedSub writeToFile:FileSubtitlePath atomically:YES];
  654. } else {
  655. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  656. message:[NSString stringWithFormat:NSLocalizedString(@"DISK_FULL_FORMAT", nil), [fileName lastPathComponent], [[UIDevice currentDevice] model]]
  657. delegate:self
  658. cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
  659. otherButtonTitles:nil];
  660. [alert show];
  661. }
  662. return FileSubtitlePath;
  663. }
  664. #pragma mark - Search Display Controller Delegate
  665. - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
  666. {
  667. MediaServer1BasicObject *item;
  668. NSInteger listCount = 0;
  669. [_searchData removeAllObjects];
  670. if (_serverType == kVLCServerTypeUPNP)
  671. listCount = _mutableObjectList.count;
  672. else if (_serverType == kVLCServerTypeFTP)
  673. listCount = _objectList.count;
  674. for (int i = 0; i < listCount; i++) {
  675. NSRange nameRange;
  676. if (_serverType == kVLCServerTypeUPNP) {
  677. item = _mutableObjectList[i];
  678. nameRange = [[item title] rangeOfString:searchString options:NSCaseInsensitiveSearch];
  679. } else if (_serverType == kVLCServerTypeFTP) {
  680. NSString *rawObjectName = [_objectList[i] objectForKey:(id)kCFFTPResourceName];
  681. NSData *flippedData = [rawObjectName dataUsingEncoding:[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingFTPTextEncoding] intValue] allowLossyConversion:YES];
  682. NSString *properObjectName = [[NSString alloc] initWithData:flippedData encoding:NSUTF8StringEncoding];
  683. nameRange = [properObjectName rangeOfString:searchString options:NSCaseInsensitiveSearch];
  684. }
  685. if (nameRange.location != NSNotFound) {
  686. if (_serverType == kVLCServerTypeUPNP)
  687. [_searchData addObject:_mutableObjectList[i]];
  688. else
  689. [_searchData addObject:_objectList[i]];
  690. }
  691. }
  692. return YES;
  693. }
  694. - (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView
  695. {
  696. if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
  697. tableView.rowHeight = 80.0f;
  698. else
  699. tableView.rowHeight = 68.0f;
  700. tableView.backgroundColor = [UIColor blackColor];
  701. }
  702. #pragma mark - Gesture Action
  703. - (void)tapTwiceGestureAction:(UIGestureRecognizer *)recognizer
  704. {
  705. _searchBar.hidden = !_searchBar.hidden;
  706. if (_searchBar.hidden)
  707. self.tableView.tableHeaderView = nil;
  708. else
  709. self.tableView.tableHeaderView = _searchBar;
  710. [self.tableView setContentOffset:CGPointMake(0.0f, -self.tableView.contentInset.top) animated:NO];
  711. }
  712. @end