VLCLocalServerDiscoveryController.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /*****************************************************************************
  2. * VLCLocalServerListViewController.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. * Pierre SAGASPE <pierre.sagaspe # me.com>
  10. * Tobias Conradi <videolan # tobias-conradi.de>
  11. *
  12. * Refer to the COPYING file of the official project for license.
  13. *****************************************************************************/
  14. #import "VLCLocalServerDiscoveryController.h"
  15. #import "VLCServerListViewController.h"
  16. #import "VLCPlaybackController.h"
  17. #import "UPnPManager.h"
  18. #import "VLCNetworkListCell.h"
  19. #import "VLCLocalPlexFolderListViewController.h"
  20. #import "VLCFTPServerListViewController.h"
  21. #import "VLCUPnPServerListViewController.h"
  22. #import "VLCDiscoveryListViewController.h"
  23. #import "VLCSharedLibraryListViewController.h"
  24. #import "VLCSharedLibraryParser.h"
  25. #import "VLCHTTPUploaderController.h"
  26. #import "Reachability.h"
  27. #define kPlexServiceType @"_plexmediasvr._tcp."
  28. typedef NS_ENUM(NSUInteger, VLCLocalServerSections) {
  29. VLCLocalServerSectionGeneric = 0,
  30. VLCLocalServerSectionUPnP,
  31. VLCLocalServerSectionPlex,
  32. VLCLocalServerSectionFTP,
  33. VLCLocalServerSectionVLCiOS,
  34. VLCLocalServerSectionSMB,
  35. VLCLocalServerSectionSAP
  36. };
  37. @interface NSMutableArray(VLCLocalNetworkServiceNetService)
  38. -(NSUInteger)vlc_indexOfServiceWithNetService:(NSNetService*)netService;
  39. -(void)vlc_removeServiceWithNetService:(NSNetService*)netService;
  40. @end
  41. @implementation NSMutableArray (VLCLocalNetworkServiceNetService)
  42. - (NSUInteger)vlc_indexOfServiceWithNetService:(NSNetService *)netService {
  43. NSUInteger index = [self indexOfObjectPassingTest:^BOOL(VLCLocalNetworkServiceNetService *obj, NSUInteger idx, BOOL * _Nonnull stop) {
  44. if (![obj respondsToSelector:@selector(netService)]) return false;
  45. BOOL equal = [obj.netService isEqual:netService];
  46. if (equal) {
  47. *stop = YES;
  48. }
  49. return equal;
  50. }];
  51. return index;
  52. }
  53. -(void)vlc_removeServiceWithNetService:(NSNetService *)netService {
  54. NSUInteger index = [self vlc_indexOfServiceWithNetService:netService];
  55. if (index != NSNotFound) {
  56. [self removeObjectAtIndex:index];
  57. }
  58. }
  59. @end
  60. @interface VLCLocalServerDiscoveryController () <NSNetServiceBrowserDelegate, NSNetServiceDelegate, VLCMediaListDelegate, UPnPDBObserver>
  61. {
  62. NSNetServiceBrowser *_ftpNetServiceBrowser;
  63. NSNetServiceBrowser *_PlexNetServiceBrowser;
  64. NSNetServiceBrowser *_httpNetServiceBrowser;
  65. NSMutableArray<VLCLocalNetworkServicePlex*> *_plexServices;
  66. NSMutableArray<VLCLocalNetworkServiceHTTP*> *_httpVLCServices;
  67. NSMutableArray<VLCLocalNetworkServiceFTP*> *_ftpServices;
  68. // to keep strong references while resolving
  69. NSMutableArray *_rawNetServices;
  70. NSArray<VLCLocalNetworkServiceUPnP*> *_filteredUPNPDevices;
  71. NSArray *_UPNPdevices;
  72. VLCMediaDiscoverer *_sapDiscoverer;
  73. VLCMediaDiscoverer *_dsmDiscoverer;
  74. VLCSharedLibraryParser *_httpParser;
  75. Reachability *_reachability;
  76. NSString *_myHostName;
  77. BOOL _udnpDiscoveryRunning;
  78. NSTimer *_searchTimer;
  79. BOOL _setup;
  80. }
  81. @end
  82. @implementation VLCLocalServerDiscoveryController
  83. - (void)dealloc
  84. {
  85. [[NSNotificationCenter defaultCenter] removeObserver:self];
  86. [_reachability stopNotifier];
  87. [_ftpNetServiceBrowser stop];
  88. [_PlexNetServiceBrowser stop];
  89. [_httpNetServiceBrowser stop];
  90. }
  91. - (void)stopDiscovery
  92. {
  93. [self _stopUPNPDiscovery];
  94. [self _stopSAPDiscovery];
  95. [self _stopDSMDiscovery];
  96. [_ftpNetServiceBrowser stop];
  97. [_PlexNetServiceBrowser stop];
  98. [_httpNetServiceBrowser stop];
  99. }
  100. - (void)startDiscovery
  101. {
  102. [_ftpNetServiceBrowser searchForServicesOfType:@"_ftp._tcp." inDomain:@""];
  103. [_PlexNetServiceBrowser searchForServicesOfType:kPlexServiceType inDomain:@""];
  104. [_httpNetServiceBrowser searchForServicesOfType:@"_http._tcp." inDomain:@""];
  105. [self netReachabilityChanged];
  106. }
  107. - (NSArray *)sectionHeaderTexts
  108. {
  109. return @[@"Generic",
  110. @"Universal Plug'n'Play (UPnP)",
  111. @"Plex Media Server (via Bonjour)",
  112. @"File Transfer Protocol (FTP)",
  113. NSLocalizedString(@"SHARED_VLC_IOS_LIBRARY", nil),
  114. NSLocalizedString(@"SMB_CIFS_FILE_SERVERS", nil),
  115. @"SAP"];
  116. }
  117. - (instancetype)init
  118. {
  119. self = [super init];
  120. if (!self)
  121. return nil;
  122. NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
  123. [defaultCenter addObserver:self
  124. selector:@selector(stopDiscovery)
  125. name:UIApplicationWillResignActiveNotification
  126. object:[UIApplication sharedApplication]];
  127. [defaultCenter addObserver:self
  128. selector:@selector(startDiscovery:)
  129. name:UIApplicationDidBecomeActiveNotification
  130. object:[UIApplication sharedApplication]];
  131. [defaultCenter addObserver:self
  132. selector:@selector(sharedLibraryFound:)
  133. name:VLCSharedLibraryParserDeterminedNetserviceAsVLCInstance
  134. object:nil];
  135. _ftpServices = [[NSMutableArray alloc] init];
  136. _rawNetServices = [[NSMutableArray alloc] init];
  137. _ftpNetServiceBrowser = [[NSNetServiceBrowser alloc] init];
  138. _ftpNetServiceBrowser.delegate = self;
  139. _plexServices = [[NSMutableArray alloc] init];
  140. _PlexNetServiceBrowser = [[NSNetServiceBrowser alloc] init];
  141. _PlexNetServiceBrowser.delegate = self;
  142. _httpVLCServices = [[NSMutableArray alloc] init];
  143. _httpNetServiceBrowser = [[NSNetServiceBrowser alloc] init];
  144. _httpNetServiceBrowser.delegate = self;
  145. _reachability = [Reachability reachabilityForLocalWiFi];
  146. [_reachability startNotifier];
  147. [self netReachabilityChanged];
  148. _myHostName = [[VLCHTTPUploaderController sharedInstance] hostname];
  149. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(netReachabilityChanged) name:kReachabilityChangedNotification object:nil];
  150. return self;
  151. }
  152. - (void)netReachabilityChanged
  153. {
  154. if (_reachability.currentReachabilityStatus == ReachableViaWiFi) {
  155. [self _startUPNPDiscovery];
  156. [self _startSAPDiscovery];
  157. [self _startDSMDiscovery];
  158. } else {
  159. [self _stopUPNPDiscovery];
  160. [self _stopSAPDiscovery];
  161. [self _stopDSMDiscovery];
  162. }
  163. }
  164. - (IBAction)goBack:(id)sender
  165. {
  166. [self _stopUPNPDiscovery];
  167. [self _stopSAPDiscovery];
  168. [self _stopDSMDiscovery];
  169. [[VLCSidebarController sharedInstance] toggleSidebar];
  170. }
  171. - (BOOL)shouldAutorotate
  172. {
  173. UIInterfaceOrientation toInterfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
  174. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
  175. return NO;
  176. return YES;
  177. }
  178. #pragma mark - table view handling
  179. - (id<VLCLocalNetworkService>)networkServiceForIndexPath:(NSIndexPath *)indexPath
  180. {
  181. VLCLocalServerSections section = indexPath.section;
  182. NSUInteger row = indexPath.row;
  183. switch (section) {
  184. case VLCLocalServerSectionGeneric:
  185. {
  186. return [[VLCLocalNetworkServiceItemLogin alloc] init];
  187. }
  188. case VLCLocalServerSectionUPnP:
  189. {
  190. if (_filteredUPNPDevices.count > row) {
  191. return _filteredUPNPDevices[row];
  192. }
  193. }
  194. case VLCLocalServerSectionPlex:
  195. {
  196. return _plexServices[row];
  197. }
  198. case VLCLocalServerSectionFTP:
  199. {
  200. return _ftpServices[row];
  201. }
  202. case VLCLocalServerSectionVLCiOS:
  203. {
  204. return _httpVLCServices[row];
  205. }
  206. case VLCLocalServerSectionSMB:
  207. {
  208. return [[VLCLocalNetworkServiceDSM alloc] initWithMediaItem:[_dsmDiscoverer.discoveredMedia mediaAtIndex:row]];
  209. }
  210. case VLCLocalServerSectionSAP:
  211. {
  212. return [[VLCLocalNetworkServiceSAP alloc] initWithMediaItem:[_sapDiscoverer.discoveredMedia mediaAtIndex:row]];
  213. }
  214. default:
  215. break;
  216. }
  217. return [[VLCLocalNetworkServiceItem alloc] initWithTile:@"FAIL"
  218. icon:nil];
  219. }
  220. - (NSInteger)numberOfItemsInSection:(NSInteger)section
  221. {
  222. switch (section) {
  223. case VLCLocalServerSectionGeneric:
  224. return 1;
  225. case VLCLocalServerSectionUPnP:
  226. return _filteredUPNPDevices.count;
  227. case VLCLocalServerSectionPlex:
  228. return _plexServices.count;
  229. case VLCLocalServerSectionFTP:
  230. return _ftpServices.count;
  231. case VLCLocalServerSectionVLCiOS:
  232. return _httpVLCServices.count;
  233. case VLCLocalServerSectionSMB:
  234. return _dsmDiscoverer.discoveredMedia.count;
  235. case VLCLocalServerSectionSAP:
  236. return _sapDiscoverer.discoveredMedia.count;
  237. default:
  238. return 0;
  239. }
  240. }
  241. - (BOOL)refreshDiscoveredData
  242. {
  243. if (_reachability.currentReachabilityStatus != ReachableViaWiFi)
  244. return NO;
  245. UPnPManager *managerInstance = [UPnPManager GetInstance];
  246. [[managerInstance DB] removeObserver:self];
  247. [[managerInstance SSDP] stopSSDP];
  248. [self _stopDSMDiscovery];
  249. [self _startUPNPDiscovery];
  250. [self _startSAPDiscovery];
  251. [self _startDSMDiscovery];
  252. return YES;
  253. }
  254. #pragma mark - bonjour discovery
  255. - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
  256. {
  257. APLog(@"found bonjour service: %@ (%@)", aNetService.name, aNetService.type);
  258. [_rawNetServices addObject:aNetService];
  259. aNetService.delegate = self;
  260. [aNetService resolveWithTimeout:5.];
  261. }
  262. - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
  263. {
  264. APLog(@"bonjour service disappeared: %@ (%i)", aNetService.name, moreComing);
  265. if ([_rawNetServices containsObject:aNetService])
  266. [_rawNetServices removeObject:aNetService];
  267. if ([aNetService.type isEqualToString:@"_ftp._tcp."]) {
  268. [_ftpServices vlc_removeServiceWithNetService:aNetService];
  269. }
  270. if ([aNetService.type isEqualToString:kPlexServiceType]) {
  271. [_plexServices vlc_removeServiceWithNetService:aNetService];
  272. }
  273. if ([aNetService.type isEqualToString:@"_http._tcp."]) {
  274. NSUInteger index = [_httpVLCServices vlc_indexOfServiceWithNetService:aNetService];
  275. if (index != NSNotFound) {
  276. [_httpVLCServices removeObjectAtIndex:index];
  277. }
  278. }
  279. if (!moreComing) {
  280. if (self.delegate) {
  281. if ([self.delegate respondsToSelector:@selector(discoveryFoundSomethingNew)]) {
  282. [self.delegate discoveryFoundSomethingNew];
  283. }
  284. }
  285. }
  286. }
  287. - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
  288. {
  289. if ([aNetService.type isEqualToString:@"_ftp._tcp."]) {
  290. NSUInteger index = [_ftpServices vlc_indexOfServiceWithNetService:aNetService];
  291. if (index == NSNotFound) {
  292. [_ftpServices addObject:[[VLCLocalNetworkServiceFTP alloc] initWithNetService:aNetService]];
  293. }
  294. } else if ([aNetService.type isEqualToString:kPlexServiceType]) {
  295. if ([_plexServices vlc_indexOfServiceWithNetService:aNetService] == NSNotFound) {
  296. [_plexServices addObject:[[VLCLocalNetworkServicePlex alloc] initWithNetService: aNetService]];
  297. }
  298. } else if ([aNetService.type isEqualToString:@"_http._tcp."]) {
  299. if ([[aNetService hostName] rangeOfString:_myHostName].location == NSNotFound) {
  300. if (!_httpParser)
  301. _httpParser = [[VLCSharedLibraryParser alloc] init];
  302. [_httpParser checkNetserviceForVLCService:aNetService];
  303. }
  304. }
  305. [_rawNetServices removeObject:aNetService];
  306. if (self.delegate) {
  307. if ([self.delegate respondsToSelector:@selector(discoveryFoundSomethingNew)]) {
  308. [self.delegate discoveryFoundSomethingNew];
  309. }
  310. }
  311. }
  312. - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
  313. {
  314. APLog(@"failed to resolve: %@", aNetService.name);
  315. [_rawNetServices removeObject:aNetService];
  316. }
  317. #pragma mark - shared library stuff
  318. - (void)sharedLibraryFound:(NSNotification *)aNotification
  319. {
  320. NSNetService *aNetService = [aNotification.userInfo objectForKey:@"aNetService"];
  321. NSUInteger index = [_httpVLCServices vlc_indexOfServiceWithNetService:aNetService];
  322. if (index == NSNotFound) {
  323. [_httpVLCServices addObject:[[VLCLocalNetworkServiceHTTP alloc] initWithNetService:aNetService]];
  324. }
  325. }
  326. #pragma mark - UPNP discovery
  327. - (void)_startUPNPDiscovery
  328. {
  329. if (_reachability.currentReachabilityStatus != ReachableViaWiFi)
  330. return;
  331. UPnPManager *managerInstance = [UPnPManager GetInstance];
  332. _UPNPdevices = [[managerInstance DB] rootDevices];
  333. if (_UPNPdevices.count > 0)
  334. [self UPnPDBUpdated:nil];
  335. [[managerInstance DB] addObserver:self];
  336. //Optional; set User Agent
  337. if (!_setup) {
  338. [[managerInstance SSDP] setUserAgentProduct:[NSString stringWithFormat:@"VLCforiOS/%@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]] andOS:[NSString stringWithFormat:@"iOS/%@", [[UIDevice currentDevice] systemVersion]]];
  339. _setup = YES;
  340. }
  341. //Search for UPnP Devices
  342. [[managerInstance SSDP] startSSDP];
  343. [[managerInstance SSDP] notifySSDPAlive];
  344. _searchTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1.0] interval:10.0 target:self selector:@selector(_performSSDPSearch) userInfo:nil repeats:YES];
  345. [[NSRunLoop mainRunLoop] addTimer:_searchTimer forMode:NSRunLoopCommonModes];
  346. _udnpDiscoveryRunning = YES;
  347. }
  348. - (void)_performSSDPSearch
  349. {
  350. UPnPManager *managerInstance = [UPnPManager GetInstance];
  351. [[managerInstance SSDP] searchSSDP];
  352. [[managerInstance SSDP] searchForMediaServer];
  353. [[managerInstance SSDP] performSelectorInBackground:@selector(SSDPDBUpdate) withObject:nil];
  354. }
  355. - (void)_stopUPNPDiscovery
  356. {
  357. if (_udnpDiscoveryRunning) {
  358. UPnPManager *managerInstance = [UPnPManager GetInstance];
  359. [[managerInstance SSDP] notifySSDPByeBye];
  360. [_searchTimer invalidate];
  361. _searchTimer = nil;
  362. [[managerInstance DB] removeObserver:self];
  363. [[managerInstance SSDP] stopSSDP];
  364. _udnpDiscoveryRunning = NO;
  365. }
  366. }
  367. //protocol UPnPDBObserver
  368. - (void)UPnPDBWillUpdate:(UPnPDB*)sender
  369. {
  370. }
  371. - (void)UPnPDBUpdated:(UPnPDB*)sender
  372. {
  373. NSUInteger count = _UPNPdevices.count;
  374. BasicUPnPDevice *device;
  375. NSMutableArray<VLCLocalNetworkServiceUPnP*> *mutArray = [[NSMutableArray alloc] init];
  376. for (NSUInteger x = 0; x < count; x++) {
  377. device = _UPNPdevices[x];
  378. if ([[device urn] isEqualToString:@"urn:schemas-upnp-org:device:MediaServer:1"])
  379. [mutArray addObject:[[VLCLocalNetworkServiceUPnP alloc] initWithUPnPDevice:device]];
  380. else
  381. APLog(@"found device '%@' with unsupported urn '%@'", [device friendlyName], [device urn]);
  382. }
  383. _filteredUPNPDevices = nil;
  384. _filteredUPNPDevices = [NSArray arrayWithArray:mutArray];
  385. if (self.delegate) {
  386. if ([self.delegate respondsToSelector:@selector(discoveryFoundSomethingNew)]) {
  387. [self.delegate performSelectorOnMainThread:@selector(discoveryFoundSomethingNew) withObject:nil waitUntilDone:NO];
  388. }
  389. }
  390. }
  391. #pragma mark SAP discovery
  392. - (void)_startSAPDiscovery
  393. {
  394. if (_reachability.currentReachabilityStatus != ReachableViaWiFi)
  395. return;
  396. if (!_sapDiscoverer)
  397. _sapDiscoverer = [[VLCMediaDiscoverer alloc] initWithName:@"sap"];
  398. [_sapDiscoverer startDiscoverer];
  399. _sapDiscoverer.discoveredMedia.delegate = self;
  400. }
  401. - (void)_stopSAPDiscovery
  402. {
  403. [_sapDiscoverer stopDiscoverer];
  404. _sapDiscoverer = nil;
  405. }
  406. - (void)mediaList:(VLCMediaList *)aMediaList mediaAdded:(VLCMedia *)media atIndex:(NSInteger)index
  407. {
  408. [media parseWithOptions:VLCMediaParseNetwork];
  409. if (self.delegate) {
  410. if ([self.delegate respondsToSelector:@selector(discoveryFoundSomethingNew)]) {
  411. [self.delegate performSelectorOnMainThread:@selector(discoveryFoundSomethingNew) withObject:nil waitUntilDone:NO];
  412. }
  413. }
  414. }
  415. - (void)mediaList:(VLCMediaList *)aMediaList mediaRemovedAtIndex:(NSInteger)index
  416. {
  417. if (self.delegate) {
  418. if ([self.delegate respondsToSelector:@selector(discoveryFoundSomethingNew)]) {
  419. [self.delegate performSelectorOnMainThread:@selector(discoveryFoundSomethingNew) withObject:nil waitUntilDone:NO];
  420. }
  421. }
  422. }
  423. #pragma DSM discovery
  424. - (void)_startDSMDiscovery
  425. {
  426. if (_reachability.currentReachabilityStatus != ReachableViaWiFi)
  427. return;
  428. if (_dsmDiscoverer)
  429. return;
  430. _dsmDiscoverer = [[VLCMediaDiscoverer alloc] initWithName:@"dsm"];
  431. [_dsmDiscoverer startDiscoverer];
  432. _dsmDiscoverer.discoveredMedia.delegate = self;
  433. }
  434. - (void)_stopDSMDiscovery
  435. {
  436. [_dsmDiscoverer stopDiscoverer];
  437. _dsmDiscoverer = nil;
  438. }
  439. @end