Browse Source

replace VLCUPnPServerListViewController with VLCNetworkServicebrowserUPnP

replace VLCUPnPServerListViewController with VLCNetworkServicebrowserUPnP
Tobias Conradi 9 years ago
parent
commit
fc76187671

+ 2 - 1
Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkServerBrowser-Protocol.h

@@ -39,13 +39,14 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic, readonly, nullable) id<VLCNetworkServerBrowser> containerBrowser;
 
 @property (nonatomic, readonly) NSString *name;
-@property (nonatomic, readonly) NSURL *URL;
+@property (nonatomic, readonly, nullable) NSURL *URL;
 @property (nonatomic, readonly, nullable) NSNumber *fileSizeBytes;
 
 @optional
 @property (nonatomic, readonly, nullable) NSString *duration;
 @property (nonatomic, readonly, nullable) NSURL *subtitleURL;
 @property (nonatomic, readonly, nullable) NSURL *thumbnailURL;
+@property (nonatomic, getter=isDownloadable, readonly) BOOL downloadable;
 @end
 
 

+ 48 - 0
Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkServerBrowserUPnP.h

@@ -0,0 +1,48 @@
+/*****************************************************************************
+ * VLCNetworkServerBrowserUPnP.h
+ * VLC for iOS
+ *****************************************************************************
+ * Copyright (c) 2015 VideoLAN. All rights reserved.
+ * $Id$
+ *
+ * Authors: Tobias Conradi <videolan # tobias-conradi.de>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+#import "VLCNetworkServerBrowser-Protocol.h"
+
+NS_ASSUME_NONNULL_BEGIN
+@class MediaServer1Device;
+@interface VLCNetworkServerBrowserUPnP : NSObject <VLCNetworkServerBrowser>
+- (id)initWithUPNPDevice:(MediaServer1Device *)device header:(NSString *)header andRootID:(NSString *)rootID NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+@end
+
+
+@class MediaServer1BasicObject;
+@interface VLCNetworkServerBrowserItemUPnP : NSObject <VLCNetworkServerBrowserItem>
+- (instancetype)initWithBasicObject:(MediaServer1BasicObject *)basicObject device:(MediaServer1Device *)device;
+@property (nonatomic, readonly, nullable) NSString *duration;
+@property (nonatomic, readonly, nullable) NSURL *subtitleURL;
+@property (nonatomic, readonly, nullable) NSURL *thumbnailURL;
+@property (nonatomic, getter=isDownloadable, readonly) BOOL downloadable;
+
+// UPnP specificis
+@property (nonatomic, readonly, nullable) UIImage *image;
+@end
+
+
+#pragma mark - Multi Ressource
+@class MediaServer1ItemObject;
+@interface VLCNetworkServerBrowserUPnPMultiRessource : NSObject <VLCNetworkServerBrowser>
+- (instancetype)initWithItem:(MediaServer1ItemObject *)itemObject device:(MediaServer1Device *)device;
+@end
+
+@interface VLCNetworkServerBrowserItemUPnPMultiRessource : NSObject <VLCNetworkServerBrowserItem>
+- (instancetype)initWithTitle:(NSString *)title url:(NSURL *)url;
+@end
+
+
+NS_ASSUME_NONNULL_END

+ 311 - 0
Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkServerBrowserUPnP.m

@@ -0,0 +1,311 @@
+/*****************************************************************************
+ * VLCNetworkServerBrowserUPnP.m
+ * VLC for iOS
+ *****************************************************************************
+ * Copyright (c) 2015 VideoLAN. All rights reserved.
+ * $Id$
+ *
+ * Authors: Tobias Conradi <videolan # tobias-conradi.de>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+#import "VLCNetworkServerBrowserUPnP.h"
+
+#import "MediaServerBasicObjectParser.h"
+#import "MediaServer1ItemObject.h"
+#import "MediaServer1ContainerObject.h"
+#import "MediaServer1Device.h"
+#import "BasicUPnPDevice+VLC.h"
+
+@interface VLCNetworkServerBrowserUPnP ()
+@property (nonatomic, readonly) MediaServer1Device *upnpDevice;
+@property (nonatomic, readonly) NSString *upnpRootID;
+@property (nonatomic, readonly) NSOperationQueue *upnpQueue;
+
+@property (nonatomic, readwrite) NSArray<id<VLCNetworkServerBrowserItem>> *items;
+
+@end
+
+@implementation VLCNetworkServerBrowserUPnP
+@synthesize title = _title, delegate = _delegate, items = _items;
+
+- (instancetype)initWithUPNPDevice:(MediaServer1Device *)device header:(NSString *)header andRootID:(NSString *)upnpRootID
+{
+    self = [super init];
+    if (self) {
+        _upnpDevice = device;
+        _title = header;
+        _upnpRootID = upnpRootID;
+        _upnpQueue = [[NSOperationQueue alloc] init];
+        _upnpQueue.maxConcurrentOperationCount = 1;
+        _upnpQueue.name = @"org.videolan.vlc-ios.upnp.update";
+        _items = [NSArray array];
+    }
+    return self;
+}
+- (void)update {
+    [self.upnpQueue addOperationWithBlock:^{
+
+        NSString *sortCriteria = @"";
+        NSMutableString *outSortCaps = [[NSMutableString alloc] init];
+        [[self.upnpDevice contentDirectory] GetSortCapabilitiesWithOutSortCaps:outSortCaps];
+
+        if ([outSortCaps rangeOfString:@"dc:title"].location != NSNotFound)
+        {
+            sortCriteria = @"+dc:title";
+        }
+
+        NSMutableString *outResult = [[NSMutableString alloc] init];
+        NSMutableString *outNumberReturned = [[NSMutableString alloc] init];
+        NSMutableString *outTotalMatches = [[NSMutableString alloc] init];
+        NSMutableString *outUpdateID = [[NSMutableString alloc] init];
+
+        [[self.upnpDevice contentDirectory] BrowseWithObjectID:self.upnpRootID BrowseFlag:@"BrowseDirectChildren" Filter:@"*" StartingIndex:@"0" RequestedCount:@"0" SortCriteria:sortCriteria OutResult:outResult OutNumberReturned:outNumberReturned OutTotalMatches:outTotalMatches OutUpdateID:outUpdateID];
+
+        NSData *didl = [outResult dataUsingEncoding:NSUTF8StringEncoding];
+        MediaServerBasicObjectParser *parser;
+        NSMutableArray *objectsArray = [[NSMutableArray alloc] init];
+        parser = [[MediaServerBasicObjectParser alloc] initWithMediaObjectArray:objectsArray itemsOnly:NO];
+        [parser parseFromData:didl];
+
+        NSMutableArray *itemsArray = [[NSMutableArray alloc] init];
+
+        for (MediaServer1BasicObject *object in objectsArray) {
+            [itemsArray addObject:[[VLCNetworkServerBrowserItemUPnP alloc] initWithBasicObject:object device:self.upnpDevice]];
+        }
+
+        self.items = [itemsArray copy];
+        [self.delegate networkServerBrowserDidUpdate:self];
+    }];
+}
+
+@end
+
+
+@interface MediaServer1ItemObject (VLC)
+@end
+
+@implementation MediaServer1ItemObject (VLC)
+
+- (id)vlc_ressourceItemForKey:(NSString *)key urlString:(NSString *)urlString device:(MediaServer1Device *)device {
+
+    // Provide users with a descriptive action sheet for them to choose based on the multiple resources advertised by DLNA devices (HDHomeRun for example)
+
+    NSRange position = [key rangeOfString:@"http-get:*:video/"];
+
+    if (position.location == NSNotFound)
+        return nil;
+
+    NSString *orgPNValue;
+    NSString *transcodeValue;
+
+    // Attempt to parse DLNA.ORG_PN first
+    NSArray *components = [key componentsSeparatedByString:@";"];
+    NSArray *nonFlagsComponents = [components[0] componentsSeparatedByString:@":"];
+    NSString *orgPN = [nonFlagsComponents lastObject];
+
+    // Check to see if we are where we should be
+    NSRange orgPNRange = [orgPN rangeOfString:@"DLNA.ORG_PN="];
+    if (orgPNRange.location == 0) {
+        orgPNValue = [orgPN substringFromIndex:orgPNRange.length];
+    }
+
+    // HDHomeRun: Get the transcode profile from the HTTP API if possible
+    if ([device VLC_isHDHomeRunMediaServer]) {
+        NSRange transcodeRange = [urlString rangeOfString:@"transcode="];
+        if (transcodeRange.location != NSNotFound) {
+            transcodeValue = [urlString substringFromIndex:transcodeRange.location + transcodeRange.length];
+            // Check that there are no more parameters
+            NSRange ampersandRange = [transcodeValue rangeOfString:@"&"];
+            if (ampersandRange.location != NSNotFound) {
+                transcodeValue = [transcodeValue substringToIndex:transcodeRange.location];
+            }
+
+            transcodeValue = [transcodeValue capitalizedString];
+        }
+    }
+
+    // Fallbacks to get the most descriptive resource title
+    NSString *profileTitle;
+    if ([transcodeValue length] && [orgPNValue length]) {
+        profileTitle = [NSString stringWithFormat:@"%@ (%@)", transcodeValue, orgPNValue];
+
+        // The extra whitespace is to get UIActionSheet to render the text better (this bug has been fixed in iOS 8)
+        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
+            if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
+                profileTitle = [NSString stringWithFormat:@" %@ ", profileTitle];
+            }
+        }
+    } else if ([transcodeValue length]) {
+        profileTitle = transcodeValue;
+    } else if ([orgPNValue length]) {
+        profileTitle = orgPNValue;
+    } else if ([key length]) {
+        profileTitle = key;
+    } else if ([urlString length]) {
+        profileTitle = urlString;
+    } else  {
+        profileTitle = NSLocalizedString(@"UNKNOWN", nil);
+    }
+
+    return [[VLCNetworkServerBrowserItemUPnPMultiRessource alloc] initWithTitle:profileTitle url:[NSURL URLWithString:urlString]];
+}
+
+- (NSArray *)vlc_ressourceItemsForDevice:(MediaServer1Device *)device {
+
+    // Store it so we can act on the action sheet callback.
+
+    NSMutableArray *array = [NSMutableArray array];
+    [uriCollection enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, NSString *  _Nonnull urlString, BOOL * _Nonnull stop) {
+        id item = [self vlc_ressourceItemForKey:key urlString:urlString device:device];
+        if (item) {
+            [array addObject:item];
+        }
+    }];
+    return [array copy];
+}
+
+@end
+
+@interface VLCNetworkServerBrowserItemUPnP ()
+@property (nonatomic, readonly) MediaServer1BasicObject *mediaServerObject;
+@property (nonatomic, readonly) MediaServer1Device *upnpDevice;
+
+@end
+
+@implementation VLCNetworkServerBrowserItemUPnP
+@synthesize container = _container, name = _name, URL = _URL, fileSizeBytes = _fileSizeBytes;
+- (instancetype)initWithBasicObject:(MediaServer1BasicObject *)basicObject device:(nonnull MediaServer1Device *)device
+{
+    self = [super init];
+    if (self) {
+        _mediaServerObject = basicObject;
+        _upnpDevice = device;
+        _name = basicObject.title;
+        _thumbnailURL = [NSURL URLWithString:basicObject.albumArt];
+
+        _fileSizeBytes = nil;
+        _duration = nil;
+        _URL = nil;
+
+        _container = basicObject.isContainer;
+        if (!_container && [basicObject isKindOfClass:[MediaServer1ItemObject class]]) {
+            MediaServer1ItemObject *mediaItem = (MediaServer1ItemObject *)basicObject;
+
+            long long mediaSize = 0;
+            unsigned int durationInSeconds = 0;
+            unsigned int bitrate = 0;
+
+            for (MediaServer1ItemRes *resource in mediaItem.resources) {
+                if (resource.bitrate > 0 && resource.durationInSeconds > 0) {
+                    mediaSize = resource.size;
+                    durationInSeconds = resource.durationInSeconds;
+                    bitrate = resource.bitrate;
+                }
+            }
+            if (mediaSize < 1)
+                mediaSize = [mediaItem.size integerValue];
+
+            if (mediaSize < 1)
+                mediaSize = (bitrate * durationInSeconds);
+
+            // 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)
+            if (mediaSize > 0) {
+                _fileSizeBytes = @(mediaSize);
+            }
+            if (durationInSeconds > 0) {
+                _duration = [VLCTime timeWithInt:durationInSeconds * 1000].stringValue;
+            }
+
+            NSArray<NSString *>* protocolStrings = [[mediaItem uriCollection] allKeys];
+            protocolStrings = [protocolStrings filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString * _Nonnull evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
+                return [evaluatedObject containsString:@"http-get:*:video/"];
+            }]];
+            if (protocolStrings.count == 1) {
+                _URL = [NSURL URLWithString:[mediaItem uri]];
+            } else if (protocolStrings.count > 1) {
+                // whith mutlple playable ressources we simulate to be a container
+                _container = YES;
+            }
+        }
+    }
+    return self;
+}
+
+- (BOOL)isContainer {
+    return self.mediaServerObject.isContainer;
+}
+- (BOOL)isDownloadable {
+    // 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.
+    BOOL downloadable = ![self.upnpDevice VLC_isHDHomeRunMediaServer];
+    return downloadable;
+}
+
+- (id<VLCNetworkServerBrowser>)containerBrowser {
+    MediaServer1BasicObject *basicObject = self.mediaServerObject;
+    if (basicObject.isContainer) {
+        return [[VLCNetworkServerBrowserUPnP alloc] initWithUPNPDevice:self.upnpDevice header:self.mediaServerObject.title andRootID:self.mediaServerObject.objectID];
+    } else if ([basicObject isKindOfClass:[MediaServer1ItemObject class]]) {
+        return [[VLCNetworkServerBrowserUPnPMultiRessource alloc] initWithItem:(MediaServer1ItemObject *)self.mediaServerObject device:self.upnpDevice];
+    } else {
+        return nil;
+    }
+}
+
+- (UIImage *)image {
+    UIImage *broadcastImage = nil;
+    // Custom TV icon for video broadcasts
+    if ([[self.mediaServerObject objectClass] isEqualToString:@"object.item.videoItem.videoBroadcast"]) {
+        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
+            broadcastImage = [UIImage imageNamed:@"TVBroadcastIcon"];
+        } else {
+            broadcastImage = [UIImage imageNamed:@"TVBroadcastIcon~ipad"];
+        }
+    }
+    return broadcastImage;
+}
+
+@end
+
+#pragma mark - Multi Ressource
+
+@implementation VLCNetworkServerBrowserUPnPMultiRessource
+@synthesize items = _items, title = _title, delegate = _delegate;
+
+- (instancetype)initWithItem:(MediaServer1ItemObject *)itemObject device:(MediaServer1Device *)device
+{
+    self = [super init];
+    if (self) {
+        _title = [itemObject title];
+        _items = [itemObject vlc_ressourceItemsForDevice:device];
+    }
+    return self;
+}
+
+- (void) update {
+    [self.delegate networkServerBrowserDidUpdate:self];
+}
+@end
+
+
+@implementation VLCNetworkServerBrowserItemUPnPMultiRessource
+@synthesize URL = _URL, container = _container, fileSizeBytes = _fileSizeBytes, name =_name;
+
+- (instancetype)initWithTitle:(NSString *)title url:(NSURL *)url
+{
+    self = [super init];
+    if (self) {
+        _name = title;
+        _URL = url;
+        _container = NO;
+        _fileSizeBytes = nil;
+    }
+    return self;
+}
+
+- (id<VLCNetworkServerBrowser>)containerBrowser {
+    return nil;
+}
+
+@end

+ 0 - 21
Sources/LocalNetworkConnectivity/ServerBrowsing/VLCUPnPServerListViewController.h

@@ -1,21 +0,0 @@
-/*****************************************************************************
- * VLCUPnPServerListViewController.h
- * VLC for iOS
- *****************************************************************************
- * Copyright (c) 2015 VideoLAN. All rights reserved.
- * $Id$
- *
- * Author: Felix Paul Kühne <fkuehne # videolan.org>
- *
- * Refer to the COPYING file of the official project for license.
- *****************************************************************************/
-
-#import "VLCNetworkListViewController.h"
-
-@class MediaServer1Device;
-
-@interface VLCUPnPServerListViewController : VLCNetworkListViewController
-
-- (id)initWithUPNPDevice:(MediaServer1Device *)device header:(NSString *)header andRootID:(NSString *)rootID;
-
-@end

+ 0 - 503
Sources/LocalNetworkConnectivity/ServerBrowsing/VLCUPnPServerListViewController.m

@@ -1,503 +0,0 @@
-/*****************************************************************************
- * VLCUPnPServerListViewController.m
- * VLC for iOS
- *****************************************************************************
- * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
- * $Id$
- *
- * Authors: Felix Paul Kühne <fkuehne # videolan.org>
- *          Marc Etcheverry <marc@taplightsoftware.com>
- *          Pierre SAGASPE <pierre.sagaspe # me.com>
- *
- * Refer to the COPYING file of the official project for license.
- *****************************************************************************/
-
-#import "VLCUPnPServerListViewController.h"
-#import "VLCDownloadViewController.h"
-
-#import "VLCNetworkListCell.h"
-#import "VLCPlaybackController.h"
-#import "VLCStatusLabel.h"
-#import "NSString+SupportedMedia.h"
-
-#import "MediaServerBasicObjectParser.h"
-#import "MediaServer1ItemObject.h"
-#import "MediaServer1ContainerObject.h"
-#import "MediaServer1Device.h"
-#import "BasicUPnPDevice+VLC.h"
-
-@interface VLCUPnPServerListViewController () <VLCNetworkListCellDelegate, UITableViewDataSource, UITableViewDelegate, UIActionSheetDelegate>
-{
-    MediaServer1Device *_UPNPdevice;
-    NSString *_UPNProotID;
-    NSMutableArray *_mutableObjectList;
-    NSMutableArray *_searchData;
-
-    MediaServer1ItemObject *_lastSelectedMediaItem;
-    UIView *_resourceSelectionActionSheetAnchorView;
-}
-
-@end
-
-@implementation VLCUPnPServerListViewController
-
-- (id)initWithUPNPDevice:(MediaServer1Device*)device header:(NSString*)header andRootID:(NSString*)rootID
-{
-    self = [super init];
-
-    if (self) {
-        _UPNPdevice = device;
-        self.title = header;
-        _UPNProotID = rootID;
-    }
-
-    return self;
-}
-
-- (void)viewDidLoad
-{
-    [super viewDidLoad];
-
-    @synchronized(self) {
-        _mutableObjectList = [[NSMutableArray alloc] init];
-    }
-
-    NSString *sortCriteria = @"";
-    NSMutableString *outSortCaps = [[NSMutableString alloc] init];
-    [[_UPNPdevice contentDirectory] GetSortCapabilitiesWithOutSortCaps:outSortCaps];
-
-    if ([outSortCaps rangeOfString:@"dc:title"].location != NSNotFound)
-    {
-        sortCriteria = @"+dc:title";
-    }
-
-    NSMutableString *outResult = [[NSMutableString alloc] init];
-    NSMutableString *outNumberReturned = [[NSMutableString alloc] init];
-    NSMutableString *outTotalMatches = [[NSMutableString alloc] init];
-    NSMutableString *outUpdateID = [[NSMutableString alloc] init];
-
-    [[_UPNPdevice contentDirectory] BrowseWithObjectID:_UPNProotID BrowseFlag:@"BrowseDirectChildren" Filter:@"*" StartingIndex:@"0" RequestedCount:@"0" SortCriteria:sortCriteria OutResult:outResult OutNumberReturned:outNumberReturned OutTotalMatches:outTotalMatches OutUpdateID:outUpdateID];
-
-    @synchronized(self) {
-        [_mutableObjectList removeAllObjects];
-    }
-    NSData *didl = [outResult dataUsingEncoding:NSUTF8StringEncoding];
-    MediaServerBasicObjectParser *parser;
-    @synchronized(self) {
-        parser = [[MediaServerBasicObjectParser alloc] initWithMediaObjectArray:_mutableObjectList itemsOnly:NO];
-    }
-    [parser parseFromData:didl];
-}
-
-#pragma mark - table view data source, for more see super
-
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
-{
-    NSUInteger count;
-
-    if (tableView == self.searchDisplayController.searchResultsTableView) {
-        @synchronized(self) {
-            count = _searchData.count;
-        }
-    } else {
-        @synchronized(self) {
-            count = _mutableObjectList.count;
-        }
-    }
-
-    return count;
-}
-
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
-{
-    VLCNetworkListCell *cell = (VLCNetworkListCell *)[tableView dequeueReusableCellWithIdentifier:VLCNetworkListCellIdentifier];
-    if (cell == nil)
-        cell = [VLCNetworkListCell cellWithReuseIdentifier:VLCNetworkListCellIdentifier];
-
-    MediaServer1BasicObject *item;
-    if (tableView == self.searchDisplayController.searchResultsTableView) {
-        @synchronized(self) {
-            item = _searchData[indexPath.row];
-        }
-    } else {
-        @synchronized(self) {
-            item = _mutableObjectList[indexPath.row];
-        }
-    }
-
-    if (![item isContainer]) {
-        MediaServer1ItemObject *mediaItem;
-        long long mediaSize = 0;
-        unsigned int durationInSeconds = 0;
-        unsigned int bitrate = 0;
-
-        if (tableView == self.searchDisplayController.searchResultsTableView) {
-            @synchronized(self) {
-                mediaItem = _searchData[indexPath.row];
-            }
-        } else {
-            @synchronized(self) {
-                mediaItem = _mutableObjectList[indexPath.row];
-            }
-        }
-
-        MediaServer1ItemRes *resource = nil;
-        NSEnumerator *e = [[mediaItem resources] objectEnumerator];
-        while((resource = (MediaServer1ItemRes*)[e nextObject])){
-            if (resource.bitrate > 0 && resource.durationInSeconds > 0) {
-                mediaSize = resource.size;
-                durationInSeconds = resource.durationInSeconds;
-                bitrate = resource.bitrate;
-            }
-        }
-        if (mediaSize < 1)
-            mediaSize = [mediaItem.size longLongValue];
-
-        if (mediaSize < 1)
-            mediaSize = (bitrate * durationInSeconds);
-
-        // 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)
-        if (mediaSize > 0 && durationInSeconds > 0) {
-            [cell setSubtitle: [NSString stringWithFormat:@"%@ (%@)", [NSByteCountFormatter stringFromByteCount:mediaSize countStyle:NSByteCountFormatterCountStyleFile], [VLCTime timeWithInt:durationInSeconds * 1000].stringValue]];
-        } else {
-            cell.titleLabelCentered = YES;
-        }
-
-        // Custom TV icon for video broadcasts
-        if ([[mediaItem objectClass] isEqualToString:@"object.item.videoItem.videoBroadcast"]) {
-            UIImage *broadcastImage;
-
-            if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
-                broadcastImage = [UIImage imageNamed:@"TVBroadcastIcon"];
-            } else {
-                broadcastImage = [UIImage imageNamed:@"TVBroadcastIcon~ipad"];
-            }
-            [cell setIcon:broadcastImage];
-        } else {
-            [cell setIcon:[UIImage imageNamed:@"blank"]];
-        }
-
-        [cell setIsDirectory:NO];
-        if (mediaItem.albumArt != nil)
-            [cell setIconURL:[NSURL URLWithString:mediaItem.albumArt]];
-
-        // 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.
-        if (![_UPNPdevice VLC_isHDHomeRunMediaServer]) {
-            cell.isDownloadable = YES;
-        }
-        cell.delegate = self;
-    } else {
-        [cell setIsDirectory:YES];
-        if (item.albumArt != nil)
-            [cell setIconURL:[NSURL URLWithString:item.albumArt]];
-        [cell setIcon:[UIImage imageNamed:@"folder"]];
-    }
-    [cell setTitle:[item title]];
-
-    return cell;
-}
-
-#pragma mark - table view delegate, for more see super
-
-- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-{
-    MediaServer1BasicObject *item;
-    if (tableView == self.searchDisplayController.searchResultsTableView) {
-        @synchronized(self) {
-            item = _searchData[indexPath.row];
-        }
-    } else {
-        @synchronized(self) {
-            item = _mutableObjectList[indexPath.row];
-        }
-    }
-
-    if ([item isContainer]) {
-        MediaServer1ContainerObject *container;
-        if (tableView == self.searchDisplayController.searchResultsTableView) {
-            @synchronized(self) {
-                container = _searchData[indexPath.row];
-            }
-        } else {
-            @synchronized(self) {
-                container = _mutableObjectList[indexPath.row];
-            }
-        }
-
-        VLCUPnPServerListViewController *targetViewController = [[VLCUPnPServerListViewController alloc] initWithUPNPDevice:_UPNPdevice header:[container title] andRootID:[container objectID]];
-        [[self navigationController] pushViewController:targetViewController animated:YES];
-    } else {
-        MediaServer1ItemObject *mediaItem;
-
-        if (tableView == self.searchDisplayController.searchResultsTableView) {
-            @synchronized(self) {
-                mediaItem = _searchData[indexPath.row];
-            }
-        } else {
-            @synchronized(self) {
-                mediaItem = _mutableObjectList[indexPath.row];
-            }
-        }
-
-        NSURL *itemURL;
-        NSArray *uriCollectionKeys = [[mediaItem uriCollection] allKeys];
-        NSUInteger count = uriCollectionKeys.count;
-        NSRange position;
-        NSUInteger correctIndex = 0;
-        NSUInteger numberOfDownloadableResources = 0;
-        for (NSUInteger i = 0; i < count; i++) {
-            position = [uriCollectionKeys[i] rangeOfString:@"http-get:*:video/"];
-            if (position.location != NSNotFound) {
-                correctIndex = i;
-                numberOfDownloadableResources++;
-            }
-        }
-        NSArray *uriCollectionObjects = [[mediaItem uriCollection] allValues];
-
-        // 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
-        if (numberOfDownloadableResources > 1) {
-            _resourceSelectionActionSheetAnchorView = [tableView cellForRowAtIndexPath:indexPath];
-            [self presentResourceSelectionActionSheetForUPnPMediaItem:mediaItem forDownloading:NO];
-        } else {
-            if (uriCollectionObjects.count > 0) {
-                itemURL = [NSURL URLWithString:uriCollectionObjects[correctIndex]];
-            }
-            if (itemURL) {
-                VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
-                [vpc playURL:itemURL successCallback:nil errorCallback:nil];
-            }
-        }
-    }
-
-    [tableView deselectRowAtIndexPath:indexPath animated:NO];
-}
-
-#pragma mark - VLCNetworkListCell delegation
-- (void)triggerDownloadForCell:(VLCNetworkListCell *)cell
-{
-    MediaServer1ItemObject *item;
-    if ([self.searchDisplayController isActive]) {
-        @synchronized(self) {
-            item = _searchData[[self.searchDisplayController.searchResultsTableView indexPathForCell:cell].row];
-        }
-    } else {
-        @synchronized(self) {
-            item = _mutableObjectList[[self.tableView indexPathForCell:cell].row];
-        }
-    }
-
-    [self _downloadUPNPFileFromMediaItem:item];
-    [cell.statusLabel showStatusMessage:NSLocalizedString(@"DOWNLOADING", nil)];
-}
-
-#pragma mark - UPnP specifics
-
-- (void)_downloadUPNPFileFromMediaItem:(MediaServer1ItemObject *)mediaItem
-{
-    NSURL *itemURL;
-    NSArray *uriCollectionKeys = [[mediaItem uriCollection] allKeys];
-    NSUInteger count = uriCollectionKeys.count;
-    NSRange position;
-    NSUInteger correctIndex = 0;
-    for (NSUInteger i = 0; i < count; i++) {
-        position = [uriCollectionKeys[i] rangeOfString:@"http-get:*:video/"];
-        if (position.location != NSNotFound)
-            correctIndex = i;
-    }
-    NSArray *uriCollectionObjects = [[mediaItem uriCollection] allValues];
-
-    if (uriCollectionObjects.count > 0)
-        itemURL = [NSURL URLWithString:uriCollectionObjects[correctIndex]];
-
-    if (itemURL) {
-        NSString *filename;
-
-        /* there are few crappy UPnP servers who don't reveal the correct file extension, so we use a generic fake (#11123) */
-        if (![itemURL.absoluteString isSupportedFormat])
-            filename = [mediaItem.title stringByAppendingString:@".vlc"];
-        else
-            filename = [[mediaItem.title stringByAppendingString:@"."] stringByAppendingString:[[itemURL absoluteString] pathExtension]];
-
-        [[VLCDownloadViewController sharedInstance] addURLToDownloadList:itemURL fileNameOfMedia:filename];
-    }
-}
-
-#pragma mark - UPnP Multiple Resources
-
-/// 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.
-- (void)presentResourceSelectionActionSheetForUPnPMediaItem:(MediaServer1ItemObject *)mediaItem forDownloading:(BOOL)forDownloading {
-    NSParameterAssert(mediaItem);
-
-    if (!mediaItem) {
-        return;
-    }
-
-    // Store it so we can act on the action sheet callback.
-    _lastSelectedMediaItem = mediaItem;
-
-    NSArray *uriCollectionKeys = [[_lastSelectedMediaItem uriCollection] allKeys];
-    NSArray *uriCollectionObjects = [[_lastSelectedMediaItem uriCollection] allValues];
-    NSUInteger count = uriCollectionKeys.count;
-    NSRange position;
-
-    NSString *titleString;
-
-    if (!forDownloading) {
-        titleString = NSLocalizedString(@"SELECT_RESOURCE_TO_PLAY", nil);
-    } else {
-        titleString = NSLocalizedString(@"SELECT_RESOURCE_TO_DOWNLOAD", nil);
-    }
-
-    UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:titleString
-                                                             delegate:self
-                                                    cancelButtonTitle:nil
-                                               destructiveButtonTitle:nil
-                                                    otherButtonTitles:nil];
-
-    // Provide users with a descriptive action sheet for them to choose based on the multiple resources advertised by DLNA devices (HDHomeRun for example)
-    for (NSUInteger i = 0; i < count; i++) {
-        position = [uriCollectionKeys[i] rangeOfString:@"http-get:*:video/"];
-
-        if (position.location == NSNotFound)
-            continue;
-
-        NSString *orgPNValue;
-        NSString *transcodeValue;
-
-        // Attempt to parse DLNA.ORG_PN first
-        NSString *protocolInfo = uriCollectionKeys[i];
-        NSArray *components = [protocolInfo componentsSeparatedByString:@";"];
-        NSArray *nonFlagsComponents = [components[0] componentsSeparatedByString:@":"];
-        NSString *orgPN = [nonFlagsComponents lastObject];
-
-        // Check to see if we are where we should be
-        NSRange orgPNRange = [orgPN rangeOfString:@"DLNA.ORG_PN="];
-        if (orgPNRange.location == 0) {
-            orgPNValue = [orgPN substringFromIndex:orgPNRange.length];
-        }
-
-        // HDHomeRun: Get the transcode profile from the HTTP API if possible
-        if ([_UPNPdevice VLC_isHDHomeRunMediaServer]) {
-            NSRange transcodeRange = [uriCollectionObjects[i] rangeOfString:@"transcode="];
-            if (transcodeRange.location != NSNotFound) {
-                transcodeValue = [uriCollectionObjects[i] substringFromIndex:transcodeRange.location + transcodeRange.length];
-                // Check that there are no more parameters
-                NSRange ampersandRange = [transcodeValue rangeOfString:@"&"];
-                if (ampersandRange.location != NSNotFound) {
-                    transcodeValue = [transcodeValue substringToIndex:transcodeRange.location];
-                }
-
-                transcodeValue = [transcodeValue capitalizedString];
-            }
-        }
-
-        // Fallbacks to get the most descriptive resource title
-        NSString *profileTitle;
-        if ([transcodeValue length] && [orgPNValue length]) {
-            profileTitle = [NSString stringWithFormat:@"%@ (%@)", transcodeValue, orgPNValue];
-
-            // The extra whitespace is to get UIActionSheet to render the text better (this bug has been fixed in iOS 8)
-            if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
-                if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
-                    profileTitle = [NSString stringWithFormat:@" %@ ", profileTitle];
-                }
-            }
-        } else if ([transcodeValue length]) {
-            profileTitle = transcodeValue;
-        } else if ([orgPNValue length]) {
-            profileTitle = orgPNValue;
-        } else if ([uriCollectionKeys[i] length]) {
-            profileTitle = uriCollectionKeys[i];
-        } else if ([uriCollectionObjects[i] length]) {
-            profileTitle = uriCollectionObjects[i];
-        } else  {
-            profileTitle = NSLocalizedString(@"UNKNOWN", nil);
-        }
-
-        [actionSheet addButtonWithTitle:profileTitle];
-    }
-
-    // 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
-    actionSheet.cancelButtonIndex = [actionSheet addButtonWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)];
-
-    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
-        // Attach it to a specific view (a cell, a download button, etc)
-        if (_resourceSelectionActionSheetAnchorView) {
-            CGRect presentationRect = [self.view convertRect:_resourceSelectionActionSheetAnchorView.frame fromView:_resourceSelectionActionSheetAnchorView.superview];
-            [actionSheet showFromRect:presentationRect inView:self.view animated:YES];
-        } else {
-            [actionSheet showInView:self.view];
-        }
-    } else {
-        [actionSheet showInView:self.view];
-    }
-}
-
-#pragma mark - UIActionSheetDelegate
-
-- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
-{
-    // Act on the selected resource that the user selected
-    if (_lastSelectedMediaItem) {
-        if (buttonIndex != actionSheet.cancelButtonIndex) {
-            // 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.
-            NSArray *uriCollectionKeys = [[_lastSelectedMediaItem uriCollection] allKeys];
-            NSArray *uriCollectionObjects = [[_lastSelectedMediaItem uriCollection] allValues];
-
-            if (uriCollectionObjects.count > 0) {
-                NSUInteger count = uriCollectionKeys.count;
-                NSMutableArray *possibleCollectionObjects = [[NSMutableArray alloc] initWithCapacity:[uriCollectionObjects count]];
-
-                for (NSUInteger i = 0; i < count; i++) {
-                    if ([uriCollectionKeys[i] rangeOfString:@"http-get:*:video/"].location != NSNotFound) {
-                        [possibleCollectionObjects addObject:uriCollectionObjects[i]];
-                    }
-                }
-
-                NSString *itemURLString = uriCollectionObjects[buttonIndex];
-
-                if ([itemURLString length]) {
-                    VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
-                    [vpc playURL:[NSURL URLWithString:itemURLString] successCallback:nil errorCallback:nil];
-                }
-            }
-        }
-
-        _lastSelectedMediaItem = nil;
-        _resourceSelectionActionSheetAnchorView = nil;
-
-        UITableView *activeTableView;
-        if ([self.searchDisplayController isActive]) {
-            activeTableView = self.searchDisplayController.searchResultsTableView;
-        } else {
-            activeTableView = self.tableView;
-        }
-        [activeTableView deselectRowAtIndexPath:[activeTableView indexPathForSelectedRow] animated:NO];
-    }
-}
-
-#pragma mark - search
-
-- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
-{
-    MediaServer1BasicObject *item;
-    NSInteger listCount = 0;
-
-    @synchronized(self) {
-        [_searchData removeAllObjects];
-        listCount = _mutableObjectList.count;
-
-        for (int i = 0; i < listCount; i++) {
-            NSRange nameRange;
-            item = _mutableObjectList[i];
-            nameRange = [[item title] rangeOfString:searchString options:NSCaseInsensitiveSearch];
-
-            if (nameRange.location != NSNotFound)
-                [_searchData addObject:_mutableObjectList[i]];
-        }
-    }
-
-    return YES;
-}
-
-@end

+ 4 - 3
Sources/LocalNetworkConnectivity/ServerDiscovery/VLCLocalNetworkService.m

@@ -197,7 +197,7 @@
 
 #pragma mark - UPnP
 #import "UPnPManager.h"
-#import "VLCUPnPServerListViewController.h"
+#import "VLCNetworkServerBrowserUPnP.h"
 
 @interface VLCLocalNetworkServiceUPnP ()
 @property (nonatomic, strong) BasicUPnPDevice *device;
@@ -226,12 +226,13 @@
     if (device != nil) {
         if ([[device urn] isEqualToString:@"urn:schemas-upnp-org:device:MediaServer:1"]) {
             MediaServer1Device *server = (MediaServer1Device*)device;
-            VLCUPnPServerListViewController *targetViewController = [[VLCUPnPServerListViewController alloc] initWithUPNPDevice:server header:[device friendlyName] andRootID:@"0"];
+            VLCNetworkServerBrowserUPnP *serverBrowser = [[VLCNetworkServerBrowserUPnP alloc] initWithUPNPDevice:server header:[device friendlyName] andRootID:@"0"];
+
+            VLCNetworkServerBrowserViewController *targetViewController = [[VLCNetworkServerBrowserViewController alloc] initWithServerBrowser:serverBrowser];
             return targetViewController;
         }
     }
     return nil;
 }
-
 @end
 

+ 0 - 1
Sources/LocalNetworkConnectivity/VLCServerListViewController.m

@@ -19,7 +19,6 @@
 #import "VLCPlaybackController.h"
 #import "VLCNetworkListCell.h"
 #import "VLCNetworkLoginViewController.h"
-#import "VLCUPnPServerListViewController.h"
 #import "VLCNetworkServerBrowserViewController.h"
 
 #import "VLCNetworkServerBrowserFTP.h"

+ 6 - 6
VLC for iOS.xcodeproj/project.pbxproj

@@ -182,7 +182,6 @@
 		7D9CB9DC1A4C55EF00BB74B4 /* VLCPlaybackNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D9CB9DB1A4C55EF00BB74B4 /* VLCPlaybackNavigationController.m */; };
 		7DA8B0FB173318E80029698C /* SourceCodePro-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7DA8B0F9173318E80029698C /* SourceCodePro-Regular.ttf */; };
 		7DAE0C2E1B2EDF7A00C53996 /* VLCNetworkListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DAE0C2D1B2EDF7A00C53996 /* VLCNetworkListViewController.m */; };
-		7DAE0C311B2EDFD600C53996 /* VLCUPnPServerListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DAE0C301B2EDFD600C53996 /* VLCUPnPServerListViewController.m */; };
 		7DAE0C341B2EE0C200C53996 /* VLCNetworkServerBrowserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DAE0C331B2EE0C200C53996 /* VLCNetworkServerBrowserViewController.m */; };
 		7DAE0C371B2EF85B00C53996 /* VLCSidebarController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DAE0C361B2EF85B00C53996 /* VLCSidebarController.m */; };
 		7DB638AB185BC0890003887C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7DB638AA185BC0890003887C /* Images.xcassets */; };
@@ -284,6 +283,7 @@
 		DD3EFE301BDA813F00B68579 /* VLCNetworkServerBrowserVLCMedia.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3EFE2F1BDA813F00B68579 /* VLCNetworkServerBrowserVLCMedia.m */; };
 		DD3EFE361BDAACD400B68579 /* VLCNetworkServerBrowserSharedLibrary.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3EFE351BDAACD400B68579 /* VLCNetworkServerBrowserSharedLibrary.m */; };
 		DD3EFE5A1BDC268D00B68579 /* VLCNetworkServerBrowserPlex.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3EFE591BDC268D00B68579 /* VLCNetworkServerBrowserPlex.m */; };
+		DD3EFE6C1BDCF58700B68579 /* VLCNetworkServerBrowserUPnP.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3EFE6B1BDCF58700B68579 /* VLCNetworkServerBrowserUPnP.m */; };
 		DD510B701B14E564003BA71C /* VLCPlayerDisplayController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD510B6F1B14E564003BA71C /* VLCPlayerDisplayController.m */; };
 		DD7110F01AF38B2B00854776 /* MLMediaLibrary+playlist.m in Sources */ = {isa = PBXBuildFile; fileRef = DD7110EF1AF38B2B00854776 /* MLMediaLibrary+playlist.m */; };
 		DD7BA2631B680C8E002D9F54 /* MediaLibraryKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD7BA2601B680C1B002D9F54 /* MediaLibraryKit.framework */; };
@@ -740,8 +740,6 @@
 		7DA8B0F9173318E80029698C /* SourceCodePro-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-Regular.ttf"; sourceTree = "<group>"; };
 		7DAE0C2C1B2EDF7A00C53996 /* VLCNetworkListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCNetworkListViewController.h; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkListViewController.h; sourceTree = SOURCE_ROOT; };
 		7DAE0C2D1B2EDF7A00C53996 /* VLCNetworkListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCNetworkListViewController.m; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkListViewController.m; sourceTree = SOURCE_ROOT; };
-		7DAE0C2F1B2EDFD600C53996 /* VLCUPnPServerListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCUPnPServerListViewController.h; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCUPnPServerListViewController.h; sourceTree = SOURCE_ROOT; };
-		7DAE0C301B2EDFD600C53996 /* VLCUPnPServerListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCUPnPServerListViewController.m; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCUPnPServerListViewController.m; sourceTree = SOURCE_ROOT; };
 		7DAE0C321B2EE0C200C53996 /* VLCNetworkServerBrowserViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCNetworkServerBrowserViewController.h; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkServerBrowserViewController.h; sourceTree = SOURCE_ROOT; };
 		7DAE0C331B2EE0C200C53996 /* VLCNetworkServerBrowserViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCNetworkServerBrowserViewController.m; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkServerBrowserViewController.m; sourceTree = SOURCE_ROOT; };
 		7DAE0C351B2EF85B00C53996 /* VLCSidebarController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCSidebarController.h; path = Sources/VLCSidebarController.h; sourceTree = SOURCE_ROOT; };
@@ -891,6 +889,8 @@
 		DD3EFE351BDAACD400B68579 /* VLCNetworkServerBrowserSharedLibrary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCNetworkServerBrowserSharedLibrary.m; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkServerBrowserSharedLibrary.m; sourceTree = SOURCE_ROOT; };
 		DD3EFE581BDC268D00B68579 /* VLCNetworkServerBrowserPlex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCNetworkServerBrowserPlex.h; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkServerBrowserPlex.h; sourceTree = SOURCE_ROOT; };
 		DD3EFE591BDC268D00B68579 /* VLCNetworkServerBrowserPlex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCNetworkServerBrowserPlex.m; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkServerBrowserPlex.m; sourceTree = SOURCE_ROOT; };
+		DD3EFE6A1BDCF58700B68579 /* VLCNetworkServerBrowserUPnP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCNetworkServerBrowserUPnP.h; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkServerBrowserUPnP.h; sourceTree = SOURCE_ROOT; };
+		DD3EFE6B1BDCF58700B68579 /* VLCNetworkServerBrowserUPnP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCNetworkServerBrowserUPnP.m; path = Sources/LocalNetworkConnectivity/ServerBrowsing/VLCNetworkServerBrowserUPnP.m; sourceTree = SOURCE_ROOT; };
 		DD510B6E1B14E564003BA71C /* VLCPlayerDisplayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCPlayerDisplayController.h; path = Sources/VLCPlayerDisplayController.h; sourceTree = SOURCE_ROOT; };
 		DD510B6F1B14E564003BA71C /* VLCPlayerDisplayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCPlayerDisplayController.m; path = Sources/VLCPlayerDisplayController.m; sourceTree = SOURCE_ROOT; };
 		DD7110EE1AF38B2B00854776 /* MLMediaLibrary+playlist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MLMediaLibrary+playlist.h"; sourceTree = "<group>"; };
@@ -1627,8 +1627,6 @@
 			children = (
 				7DAE0C2C1B2EDF7A00C53996 /* VLCNetworkListViewController.h */,
 				7DAE0C2D1B2EDF7A00C53996 /* VLCNetworkListViewController.m */,
-				7DAE0C2F1B2EDFD600C53996 /* VLCUPnPServerListViewController.h */,
-				7DAE0C301B2EDFD600C53996 /* VLCUPnPServerListViewController.m */,
 				7DAE0C321B2EE0C200C53996 /* VLCNetworkServerBrowserViewController.h */,
 				7DAE0C331B2EE0C200C53996 /* VLCNetworkServerBrowserViewController.m */,
 				DDE3C8E51BD2F75100C03B8B /* VLCNetworkServerBrowser-Protocol.h */,
@@ -1640,6 +1638,8 @@
 				DD3EFE351BDAACD400B68579 /* VLCNetworkServerBrowserSharedLibrary.m */,
 				DD3EFE581BDC268D00B68579 /* VLCNetworkServerBrowserPlex.h */,
 				DD3EFE591BDC268D00B68579 /* VLCNetworkServerBrowserPlex.m */,
+				DD3EFE6A1BDCF58700B68579 /* VLCNetworkServerBrowserUPnP.h */,
+				DD3EFE6B1BDCF58700B68579 /* VLCNetworkServerBrowserUPnP.m */,
 			);
 			name = "Server browsing";
 			sourceTree = "<group>";
@@ -2586,6 +2586,7 @@
 				7D37849E183A98DD009EE944 /* VLCThumbnailsCache.m in Sources */,
 				7D3784A1183A98EB009EE944 /* VLCBugreporter.m in Sources */,
 				7D3784A6183A98F5009EE944 /* VLCAboutViewController.m in Sources */,
+				DD3EFE6C1BDCF58700B68579 /* VLCNetworkServerBrowserUPnP.m in Sources */,
 				7D3784A7183A98F5009EE944 /* VLCSettingsController.m in Sources */,
 				DDE3C8D91BD1322E00C03B8B /* VLCLocalNetworkServiceBrowserNetService.m in Sources */,
 				7DC19AE41868C8EC00810BF7 /* VLCFirstStepsFirstPageViewController.m in Sources */,
@@ -2604,7 +2605,6 @@
 				DD3EA6311AF50CFE007FF096 /* VLCWatchMessage.m in Sources */,
 				41CD695D1A29D72600E60BCE /* VLCBoxTableViewController.m in Sources */,
 				7D3784C1183A9938009EE944 /* VLCMenuButton.m in Sources */,
-				7DAE0C311B2EDFD600C53996 /* VLCUPnPServerListViewController.m in Sources */,
 				41CD695C1A29D72600E60BCE /* VLCBoxController.m in Sources */,
 				7D3784C2183A9938009EE944 /* VLCSlider.m in Sources */,
 				7D3784C3183A9938009EE944 /* VLCStatusLabel.m in Sources */,