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

#import "VLCLocalServerListViewController.h"
#import "UIBarButtonItem+Theme.h"
#import "VLCAppDelegate.h"
#import "UPnPManager.h"
#import "VLCLocalNetworkListCell.h"
20
#import "VLCLocalServerFolderListViewController.h"
21
#import "VLCLocalPlexFolderListViewController.h"
22
23
#import <QuartzCore/QuartzCore.h>
#import "GHRevealViewController.h"
24
25
#import "VLCNetworkLoginViewController.h"
#import "UINavigationController+Theme.h"
26
#import "VLCPlaylistViewController.h"
27
#import "Reachability.h"
28

29
30
#define kPlexServiceType @"_plexmediasvr._tcp."

31
@interface VLCLocalServerListViewController () <UITableViewDataSource, UITableViewDelegate, NSNetServiceBrowserDelegate, VLCNetworkLoginViewController, NSNetServiceDelegate, VLCMediaListDelegate, UPnPDBObserver>
32
33
{
    UIBarButtonItem *_backToMenuButton;
34
35
    NSArray *_sectionHeaderTexts;

36
    NSNetServiceBrowser *_ftpNetServiceBrowser;
37
38
39
    NSNetServiceBrowser *_PlexNetServiceBrowser;
    NSMutableArray *_PlexServices;
    NSMutableArray *_PlexServicesInfo;
40
    NSMutableArray *_rawNetServices;
41
42
    NSMutableArray *_ftpServices;

43
44
    NSArray *_filteredUPNPDevices;
    NSArray *_UPNPdevices;
45

46
47
    VLCMediaDiscoverer * _sapDiscoverer;

48
    VLCNetworkLoginViewController *_loginViewController;
Pierre SAGASPE's avatar
Pierre SAGASPE committed
49

50
    UIRefreshControl *_refreshControl;
51
    UIActivityIndicatorView *_activityIndicator;
52
53
54
    Reachability *_reachability;

    BOOL _udnpDiscoveryRunning;
55
    NSTimer *_searchTimer;
56
57
58
59
60
61
}

@end

@implementation VLCLocalServerListViewController

62
63
- (void)dealloc
{
64
    [_reachability stopNotifier];
65
    [_ftpNetServiceBrowser stop];
66
    [_PlexNetServiceBrowser stop];
67
68
}

69
70
71
- (void)loadView
{
    _tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain];
72
    _tableView.backgroundColor = [UIColor VLCDarkBackgroundColor];
73
74
75
    _tableView.delegate = self;
    _tableView.dataSource = self;
    self.view = _tableView;
76
77
78
79
80
    _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
    _activityIndicator.center = _tableView.center;
    _activityIndicator.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
    _activityIndicator.hidesWhenStopped = YES;
    [self.view addSubview:_activityIndicator];
81
82
83
84
85
86
}

- (void)viewDidLoad
{
    [super viewDidLoad];

87
/*    if (SYSTEM_RUNS_IOS7_OR_LATER)
88
        _sectionHeaderTexts = @[@"Universal Plug'n'Play (UPNP)", @"File Transfer Protocol (FTP)", @"Network Streams (SAP)"];
89
    else*/
90
        _sectionHeaderTexts = @[@"Universal Plug'n'Play (UPnP)", @"Plex Media Server (via Bonjour)", @"File Transfer Protocol (FTP)"];
91
92
93
94

    _backToMenuButton = [UIBarButtonItem themedRevealMenuButtonWithTarget:self andSelector:@selector(goBack:)];
    self.navigationItem.leftBarButtonItem = _backToMenuButton;

95
    self.tableView.rowHeight = [VLCLocalNetworkListCell heightOfCell];
96
97
    self.tableView.separatorColor = [UIColor VLCDarkBackgroundColor];
    self.view.backgroundColor = [UIColor VLCDarkBackgroundColor];
98
99
100

    self.title = NSLocalizedString(@"LOCAL_NETWORK", @"");

101
    _ftpServices = [[NSMutableArray alloc] init];
102
    [_ftpServices addObject:NSLocalizedString(@"CONNECT_TO_SERVER", nil)];
103

104
    _rawNetServices = [[NSMutableArray alloc] init];
105

106
107
    _ftpNetServiceBrowser = [[NSNetServiceBrowser alloc] init];
    _ftpNetServiceBrowser.delegate = self;
Pierre SAGASPE's avatar
Pierre SAGASPE committed
108

109
110
111
112
113
    _PlexServices = [[NSMutableArray alloc] init];
    _PlexServicesInfo = [[NSMutableArray alloc] init];
    _PlexNetServiceBrowser = [[NSNetServiceBrowser alloc] init];
    _PlexNetServiceBrowser.delegate = self;

114
115
116
    _refreshControl = [[UIRefreshControl alloc] init];
    [_refreshControl addTarget:self action:@selector(handleRefresh) forControlEvents:UIControlEventValueChanged];
    [self.tableView addSubview:_refreshControl];
117

Carola's avatar
Carola committed
118
119
120
121
122
    if (SYSTEM_RUNS_IOS7_OR_LATER)
        _loginViewController = [[VLCNetworkLoginViewController alloc] initWithNibName:@"VLCFutureNetworkLoginViewController" bundle:nil];
    else
        _loginViewController = [[VLCNetworkLoginViewController alloc] initWithNibName:@"VLCNetworkLoginViewController" bundle:nil];

123
    _loginViewController.delegate = self;
124
125
126
127
128
129
130

    _reachability = [Reachability reachabilityForLocalWiFi];
    [_reachability startNotifier];

    [self netReachabilityChanged:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(netReachabilityChanged:) name:kReachabilityChangedNotification object:nil];
131
132
133
134
135
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
136
    [_activityIndicator stopAnimating];
137
    [_ftpNetServiceBrowser stop];
138
    [_PlexNetServiceBrowser stop];
139
140
141
142
}

- (void)viewWillAppear:(BOOL)animated
{
143
    [_ftpNetServiceBrowser searchForServicesOfType:@"_ftp._tcp." inDomain:@""];
144
    [_PlexNetServiceBrowser searchForServicesOfType:kPlexServiceType inDomain:@""];
145
    [_activityIndicator stopAnimating];
146
    [super viewWillAppear:animated];
147
148
149
150
151
152
153
154
155
156
157
158
159

    [self netReachabilityChanged:nil];
}

- (void)netReachabilityChanged:(NSNotification *)notification
{
    if (_reachability.currentReachabilityStatus == ReachableViaWiFi) {
        [self performSelectorInBackground:@selector(_startUPNPDiscovery) withObject:nil];
        [self performSelectorInBackground:@selector(_startSAPDiscovery) withObject:nil];
    } else {
        [self _stopUPNPDiscovery];
        [self _stopSAPDiscovery];
    }
160
161
}

162
163
- (void)_startUPNPDiscovery
{
164
165
166
    if (_reachability.currentReachabilityStatus != ReachableViaWiFi)
        return;

167
    UPnPManager *managerInstance = [UPnPManager GetInstance];
168

169
170
    _UPNPdevices = [[managerInstance DB] rootDevices];

171
172
173
    if (_UPNPdevices.count > 0)
        [self UPnPDBUpdated:nil];

174
    [[managerInstance DB] addObserver:self];
175
176

    //Optional; set User Agent
177
    [[managerInstance SSDP] setUserAgentProduct:[NSString stringWithFormat:@"VLCforiOS/%@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] andOS:[NSString stringWithFormat:@"iOS/%@", [[UIDevice currentDevice] systemVersion]]];
178
179

    //Search for UPnP Devices
180
    [[managerInstance SSDP] startSSDP];
181
182
183
184
185
186
187
188
189
190
    [[managerInstance SSDP] notifySSDPAlive];
    _searchTimer = [NSTimer timerWithTimeInterval:10.0 target:self selector:@selector(_performSSDPSearch) userInfo:nil repeats:YES];
    [_searchTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
    [[NSRunLoop mainRunLoop] addTimer:_searchTimer forMode:NSRunLoopCommonModes];
    _udnpDiscoveryRunning = YES;
}

- (void)_performSSDPSearch
{
    UPnPManager *managerInstance = [UPnPManager GetInstance];
191
    [[managerInstance SSDP] searchSSDP];
192
    [[managerInstance SSDP] searchForMediaServer];
193
    [[managerInstance SSDP] SSDPDBUpdate];
194
195
196
197
198
199
}

- (void)_stopUPNPDiscovery
{
    if (_udnpDiscoveryRunning) {
        UPnPManager *managerInstance = [UPnPManager GetInstance];
200
201
202
203
        [[managerInstance SSDP] notifySSDPByeBye];
        [_searchTimer invalidate];
        _searchTimer = nil;
        [[managerInstance DB] removeObserver:self];
204
205
206
        [[managerInstance SSDP] stopSSDP];
        _udnpDiscoveryRunning = NO;
    }
207
208
209
210
}

- (IBAction)goBack:(id)sender
{
211
    [self _stopUPNPDiscovery];
212
    [self _stopSAPDiscovery];
213

214
215
216
    [[(VLCAppDelegate*)[UIApplication sharedApplication].delegate revealController] toggleSidebar:![(VLCAppDelegate*)[UIApplication sharedApplication].delegate revealController].sidebarShowing duration:kGHRevealSidebarDefaultAnimationDuration];
}

217
- (BOOL)shouldAutorotate
218
{
219
    UIInterfaceOrientation toInterfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
220
221
222
223
224
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
        return NO;
    return YES;
}

225
226
#pragma mark - table view handling

227
228
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
229
    return _sectionHeaderTexts.count;
230
231
232
233
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
234
235
    if (section == 0)
        return _filteredUPNPDevices.count;
236
    else if (section == 1)
237
        return _PlexServices.count;
238
    else if (section == 2)
239
240
        return _ftpServices.count;
    else if (section == 3)
241
        return _sapDiscoverer.discoveredMedia.count;
242
243

    return 0;
244
245
}

246
- (void)tableView:(UITableView *)tableView willDisplayCell:(VLCLocalNetworkListCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
247
{
248
    UIColor *color = (indexPath.row % 2 == 0)? [UIColor blackColor]: [UIColor VLCDarkBackgroundColor];
249
    cell.contentView.backgroundColor = cell.titleLabel.backgroundColor = cell.folderTitleLabel.backgroundColor = cell.subtitleLabel.backgroundColor = color;
250
251
252
253
254
255
256
257
258
259
}

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

    VLCLocalNetworkListCell *cell = (VLCLocalNetworkListCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
        cell = [VLCLocalNetworkListCell cellWithReuseIdentifier:CellIdentifier];

260
261
262
263
    NSUInteger row = indexPath.row;
    NSUInteger section = indexPath.section;

    [cell setIsDirectory:YES];
264
    [cell setIcon:nil];
265
266

    if (section == 0) {
267
268
269
270
271
272
        UIImage *icon;
        if (_filteredUPNPDevices.count > row) {
            BasicUPnPDevice *device = _filteredUPNPDevices[row];
            [cell setTitle:[device friendlyName]];
            icon = [device smallIcon];
        }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
273
        [cell setIcon:icon != nil ? icon : [UIImage imageNamed:@"serverIcon"]];
274
    } else if (section == 1) {
275
        [cell setTitle:[_PlexServices[row] name]];
Pierre SAGASPE's avatar
Pierre SAGASPE committed
276
        [cell setIcon:[UIImage imageNamed:@"PlexServerIcon"]];
277
    } else if (section == 2) {
278
279
        if (row == 0)
            [cell setTitle:_ftpServices[row]];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
280
        else {
281
            [cell setTitle:[_ftpServices[row] name]];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
282
283
            [cell setIcon:[UIImage imageNamed:@"serverIcon"]];
        }
284
    } else if (section == 3)
285
        [cell setTitle:[[_sapDiscoverer.discoveredMedia mediaAtIndex:row] metadataForKey: VLCMetaInformationTitle]];
286
287
288
289
290
291

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
292
293
    [tableView deselectRowAtIndexPath:indexPath animated:YES];

294
295
296
297
    NSUInteger row = indexPath.row;
    NSUInteger section = indexPath.section;

    if (section == 0) {
298
299
300
        if (_filteredUPNPDevices.count < row || _filteredUPNPDevices.count == 0)
            return;

301
        [_activityIndicator startAnimating];
302
        BasicUPnPDevice *device = _filteredUPNPDevices[row];
303
304
        if ([[device urn] isEqualToString:@"urn:schemas-upnp-org:device:MediaServer:1"]) {
            MediaServer1Device *server = (MediaServer1Device*)device;
305
            VLCLocalServerFolderListViewController *targetViewController = [[VLCLocalServerFolderListViewController alloc] initWithUPNPDevice:server header:[device friendlyName] andRootID:@"0"];
306
            [self.navigationController pushViewController:targetViewController animated:YES];
307
        }
308
    } else if (section == 1) {
309
310
311
312
313
314
        NSString *name = [_PlexServicesInfo[row] objectForKey:@"name"];
        NSString *hostName = [_PlexServicesInfo[row] objectForKey:@"hostName"];
        NSString *portNum = [_PlexServicesInfo[row] objectForKey:@"port"];
        VLCLocalPlexFolderListViewController *targetViewController = [[VLCLocalPlexFolderListViewController alloc] initWithPlexServer:name serverAddress:hostName portNumber:portNum atPath:@""];
        [[self navigationController] pushViewController:targetViewController animated:YES];
    } else if (section == 2) {
315
316
317
318
319
320
        UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:_loginViewController];
        [navCon loadTheme];
        navCon.navigationBarHidden = NO;

        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
            navCon.modalPresentationStyle = UIModalPresentationFormSheet;
321
            [self presentViewController:navCon animated:YES completion:nil];
322
323

            if (_loginViewController.navigationItem.leftBarButtonItem == nil) {
324
                UIBarButtonItem *doneButton = [UIBarButtonItem themedDoneButtonWithTarget:_loginViewController andSelector:@selector(dismissWithAnimation:)];
325
326
327

                _loginViewController.navigationItem.leftBarButtonItem = doneButton;
            }
328
        } else
329
            [self.navigationController pushViewController:_loginViewController animated:YES];
330

Felix Paul Kühne's avatar
Felix Paul Kühne committed
331
        if (row != 0 && [_ftpServices[row] hostName].length > 0) // FTP Connect To Server Special Item and hostname is long enough
332
            _loginViewController.hostname = [_ftpServices[row] hostName];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
333
        else
334
            _loginViewController.hostname = @"";
335
    } else if (section == 3) {
336
        VLCAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
337
        [appDelegate openMovieFromURL:[[_sapDiscoverer.discoveredMedia mediaAtIndex:row] url]];
338
    }
339
340
}

Pierre SAGASPE's avatar
Pierre SAGASPE committed
341
342
343
344
#pragma mark - Refresh

-(void)handleRefresh
{
345
346
347
348
    if (_reachability.currentReachabilityStatus != ReachableViaWiFi) {
        [_refreshControl endRefreshing];
        return;
    }
Pierre SAGASPE's avatar
Pierre SAGASPE committed
349
    UPnPManager *managerInstance = [UPnPManager GetInstance];
350
    [[managerInstance DB] removeObserver:self];
Pierre SAGASPE's avatar
Pierre SAGASPE committed
351
352
    [[managerInstance SSDP] stopSSDP];

Pierre SAGASPE's avatar
Pierre SAGASPE committed
353
    //set the title while refreshing
354
    _refreshControl.attributedTitle = [[NSAttributedString alloc]initWithString:NSLocalizedString(@"LOCAL_SERVER_REFRESH",nil)];
Pierre SAGASPE's avatar
Pierre SAGASPE committed
355
356
357
    //set the date and time of refreshing
    NSDateFormatter *formattedDate = [[NSDateFormatter alloc]init];
    [formattedDate setDateFormat:@"MMM d, h:mm a"];
358
    NSString *lastupdated = [NSString stringWithFormat:NSLocalizedString(@"LOCAL_SERVER_LAST_UPDATE",nil),[formattedDate stringFromDate:[NSDate date]]];
359
    _refreshControl.attributedTitle = [[NSAttributedString alloc]initWithString:lastupdated];
Pierre SAGASPE's avatar
Pierre SAGASPE committed
360
    //end the refreshing
361
    [_refreshControl endRefreshing];
Pierre SAGASPE's avatar
Pierre SAGASPE committed
362
363
364
365

    [self.tableView reloadData];

    [self performSelectorInBackground:@selector(_startUPNPDiscovery) withObject:nil];
366
    [self performSelectorInBackground:@selector(_startSAPDiscovery) withObject:nil];
Pierre SAGASPE's avatar
Pierre SAGASPE committed
367
368
}

369
370
#pragma mark - login panel protocol

371
- (void)loginToURL:(NSURL *)url confirmedWithUsername:(NSString *)username andPassword:(NSString *)password
372
{
373
374
375
376
377
378
379
    if ([url.scheme isEqualToString:@"ftp"]) {
        if (url.host.length > 0) {
            VLCLocalServerFolderListViewController *targetViewController = [[VLCLocalServerFolderListViewController alloc] initWithFTPServer:url.host userName:username andPassword:password atPath:@"/"];
            [self.navigationController pushViewController:targetViewController animated:YES];
        }
    } else
        APLog(@"Unsupported URL Scheme requested %@", url.scheme);
380
381
}

382
383
#pragma mark - custom table view appearance

384
385
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
386
387
388
    return 21.f;
}

389
390
391
392
393
394
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    NSObject *headerText = NSLocalizedString(_sectionHeaderTexts[section], @"");
    UIView *headerView = nil;
    if (headerText != [NSNull null]) {
        headerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, [UIScreen mainScreen].bounds.size.height, 21.0f)];
395
396
397
398
399
400
401
402
        if (!SYSTEM_RUNS_IOS7_OR_LATER) {
            CAGradientLayer *gradient = [CAGradientLayer layer];
            gradient.frame = headerView.bounds;
            gradient.colors = @[
                          (id)[UIColor colorWithRed:(66.0f/255.0f) green:(66.0f/255.0f) blue:(66.0f/255.0f) alpha:1.0f].CGColor,
                          (id)[UIColor colorWithRed:(56.0f/255.0f) green:(56.0f/255.0f) blue:(56.0f/255.0f) alpha:1.0f].CGColor,
                          ];
            [headerView.layer insertSublayer:gradient atIndex:0];
403
        } else
404
            headerView.backgroundColor = [UIColor VLCDarkBackgroundColor];
405

406
        UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectInset(headerView.bounds, 12.0f, 0.f)];
407
408
409
        textLabel.text = (NSString *) headerText;
        textLabel.font = [UIFont fontWithName:@"Helvetica-Bold" size:([UIFont systemFontSize] * 0.8f)];
        textLabel.shadowOffset = CGSizeMake(0.0f, 1.0f);
410
        textLabel.shadowColor = [UIColor VLCDarkTextShadowColor];
411
412
413
414
415
416
417
418
419
420
421
422
423
        textLabel.textColor = [UIColor colorWithRed:(118.0f/255.0f) green:(118.0f/255.0f) blue:(118.0f/255.0f) alpha:1.0f];
        textLabel.backgroundColor = [UIColor clearColor];
        [headerView addSubview:textLabel];

        UIView *topLine = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, [UIScreen mainScreen].bounds.size.height, 1.0f)];
        topLine.backgroundColor = [UIColor colorWithRed:(95.0f/255.0f) green:(95.0f/255.0f) blue:(95.0f/255.0f) alpha:1.0f];
        [headerView addSubview:topLine];

        UIView *bottomLine = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 21.0f, [UIScreen mainScreen].bounds.size.height, 1.0f)];
        bottomLine.backgroundColor = [UIColor colorWithRed:(16.0f/255.0f) green:(16.0f/255.0f) blue:(16.0f/255.0f) alpha:1.0f];
        [headerView addSubview:bottomLine];
    }
    return headerView;
424
425
}

426
427
428
#pragma mark - bonjour discovery
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
{
429
    APLog(@"found bonjour service: %@ (%@)", aNetService.name, aNetService.type);
430
    [_rawNetServices addObject:aNetService];
431
    aNetService.delegate = self;
432
    [aNetService resolveWithTimeout:5.];
433
434
435
436
}

- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
{
437
    APLog(@"bonjour service disappeared: %@ (%i)", aNetService.name, moreComing);
438
439
    if ([_rawNetServices containsObject:aNetService])
        [_rawNetServices removeObject:aNetService];
440
441
    if ([aNetService.type isEqualToString:@"_ftp._tcp."])
        [_ftpServices removeObject:aNetService];
442
443
444
445
    if ([aNetService.type isEqualToString:kPlexServiceType]) {
        [_PlexServices removeObject:aNetService];
        [_PlexServicesInfo removeAllObjects];
    }
446
447
448
449
    if (!moreComing)
        [self.tableView reloadData];
}

450
451
452
453
454
- (void)netServiceDidResolveAddress:(NSNetService *)aNetService
{
    if ([aNetService.type isEqualToString:@"_ftp._tcp."]) {
        if (![_ftpServices containsObject:aNetService])
            [_ftpServices addObject:aNetService];
455
456
457
458
459
460
461
462
463
464
    } else if ([aNetService.type isEqualToString:kPlexServiceType]) {
        if (![_PlexServices containsObject:aNetService]) {
            [_PlexServices addObject:aNetService];
            NSMutableDictionary *_dictService = [[NSMutableDictionary alloc] init];
            [_dictService setObject:[aNetService name] forKey:@"name"];
            [_dictService setObject:[aNetService hostName] forKey:@"hostName"];
            NSString *portStr = [[NSString alloc] initWithFormat:@":%ld", (long)[aNetService port]];
            [_dictService setObject:portStr forKey:@"port"];
            [_PlexServicesInfo addObject:_dictService];
        }
465
    }
466
    [_rawNetServices removeObject:aNetService];
467
468
469
470
471
472
    [self.tableView reloadData];
}

- (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
{
    APLog(@"failed to resolve: %@", aNetService.name);
473
    [_rawNetServices removeObject:aNetService];
474
475
}

476
#pragma mark - UPNP details
477
//protocol UPnPDBObserver
478
479
- (void)UPnPDBWillUpdate:(UPnPDB*)sender
{
480
481
}

482
483
- (void)UPnPDBUpdated:(UPnPDB*)sender
{
484
    NSUInteger count = _UPNPdevices.count;
485
486
487
    BasicUPnPDevice *device;
    NSMutableArray *mutArray = [[NSMutableArray alloc] init];
    for (NSUInteger x = 0; x < count; x++) {
488
        device = _UPNPdevices[x];
489
490
        if ([[device urn] isEqualToString:@"urn:schemas-upnp-org:device:MediaServer:1"])
            [mutArray addObject:device];
491
492
        else
            APLog(@"found device '%@' with unsupported urn '%@'", [device friendlyName], [device urn]);
493
    }
494
495
    _filteredUPNPDevices = nil;
    _filteredUPNPDevices = [NSArray arrayWithArray:mutArray];
496

497
498
499
500
501
502
503
    [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
}

#pragma mark SAP discovery

- (void)_startSAPDiscovery
{
504
    return;
505

506
507
508
    if (_reachability.currentReachabilityStatus != ReachableViaWiFi)
        return;

509
510
511
512
513
514
    _sapDiscoverer = [[VLCMediaDiscoverer alloc] initWithName:@"sap"];
    _sapDiscoverer.discoveredMedia.delegate = self;
}

- (void)_stopSAPDiscovery
{
515
    return;
516

517
518
519
520
521
522
523
524
525
526
527
    _sapDiscoverer = nil;
}

- (void)mediaList:(VLCMediaList *)aMediaList mediaAdded:(VLCMedia *)media atIndex:(NSInteger)index
{
    [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
}

- (void)mediaList:(VLCMediaList *)aMediaList mediaRemovedAtIndex:(NSInteger)index
{
    [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
528
529
530
}

@end