123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- /*****************************************************************************
- * 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];
- [[NSOperationQueue mainQueue] addOperationWithBlock:^{
- [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
|