Browse Source

LocalServer: Add Plex web server support for stream or download with external subtitle

Signed-off-by: Felix Paul Kühne <fkuehne@videolan.org>
Pierre SAGASPE 11 years ago
parent
commit
d0eeb1effe

+ 21 - 0
Sources/VLCLocalPlexFolderListViewController.h

@@ -0,0 +1,21 @@
+/*****************************************************************************
+ * VLCLocalPlexFolderListViewController.h
+ * VLC for iOS
+ *****************************************************************************
+ * Copyright (c) 2014 VideoLAN. All rights reserved.
+ *
+ * Authors: Felix Paul Kühne <fkuehne # videolan.org>
+ *          Pierre SAGASPE <pierre.sagaspe # me.com>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+@interface VLCLocalPlexFolderListViewController : UIViewController
+
+@property (nonatomic, strong) UITableView *tableView;
+
+- (id)initWithPlexServer:(NSString *)serverName serverAddress:(NSString *)serverAddress portNumber:(NSString *)portNumber atPath:(NSString *)path;
+
+@end

+ 293 - 0
Sources/VLCLocalPlexFolderListViewController.m

@@ -0,0 +1,293 @@
+/*****************************************************************************
+ * VLCLocalPlexFolderListViewController.m
+ * VLC for iOS
+ *****************************************************************************
+ * Copyright (c) 2014 VideoLAN. All rights reserved.
+ *
+ * Authors: Felix Paul Kühne <fkuehne # videolan.org>
+ *          Pierre SAGASPE <pierre.sagaspe # me.com>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+#import "VLCLocalPlexFolderListViewController.h"
+#import "VLCPlexParser.h"
+#import "VLCLocalNetworkListCell.h"
+#import "VLCAppDelegate.h"
+#import "VLCPlaylistViewController.h"
+#import "UINavigationController+Theme.h"
+#import "VLCDownloadViewController.h"
+#import "NSString+SupportedMedia.h"
+#import "VLCStatusLabel.h"
+
+@interface VLCLocalPlexFolderListViewController () <UITableViewDataSource, UITableViewDelegate, VLCLocalNetworkListCell, UISearchBarDelegate, UISearchDisplayDelegate>
+{
+    UIBarButtonItem *_backButton;
+
+    NSMutableArray *_mutableObjectList;
+
+    NSString *_PlexServerName;
+    NSString *_PlexServerAddress;
+    NSString *_PlexServerPort;
+    NSString *_PlexServerPath;
+    VLCPlexParser *_PlexParser;
+
+    NSMutableArray *_searchData;
+    UISearchBar *_searchBar;
+    UISearchDisplayController *_searchDisplayController;
+}
+@end
+
+@implementation VLCLocalPlexFolderListViewController
+
+- (void)loadView
+{
+    _tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain];
+    _tableView.backgroundColor = [UIColor colorWithWhite:.122 alpha:1.];
+    _tableView.delegate = self;
+    _tableView.dataSource = self;
+    _tableView.rowHeight = [VLCLocalNetworkListCell heightOfCell];
+    self.view = _tableView;
+}
+
+- (id)initWithPlexServer:(NSString *)serverName serverAddress:(NSString *)serverAddress portNumber:(NSString *)portNumber atPath:(NSString *)path
+{
+    self = [super init];
+    if (self) {
+        _PlexServerName = serverName;
+        _PlexServerAddress = serverAddress;
+        _PlexServerPort = portNumber;
+        _PlexServerPath = path;
+
+        _mutableObjectList = [[NSMutableArray alloc] init];
+
+        _PlexParser = [[VLCPlexParser alloc] init];
+    }
+    return self;
+}
+
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+
+    [_mutableObjectList removeAllObjects];
+    _mutableObjectList = [_PlexParser PlexMediaServerParser:_PlexServerAddress port:_PlexServerPort navigationPath:_PlexServerPath];
+
+    self.tableView.separatorColor = [UIColor colorWithWhite:.122 alpha:1.];
+    self.view.backgroundColor = [UIColor colorWithWhite:.122 alpha:1.];
+
+    NSString *titleValue;
+    if ([_PlexServerPath isEqualToString:@""])
+        titleValue = _PlexServerName;
+    else
+        titleValue = [_PlexServerPath lastPathComponent];
+
+    self.title = titleValue;
+
+    _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
+    _searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:_searchBar contentsController:self];
+    _searchDisplayController.delegate = self;
+    _searchDisplayController.searchResultsDataSource = self;
+    _searchDisplayController.searchResultsDelegate = self;
+    if (SYSTEM_RUNS_IOS7_OR_LATER)
+        _searchDisplayController.searchBar.searchBarStyle = UIBarStyleBlack;
+    _searchBar.delegate = self;
+    self.tableView.tableHeaderView = _searchBar;
+
+    _searchData = [[NSMutableArray alloc] init];
+    [_searchData removeAllObjects];
+}
+
+- (BOOL)shouldAutorotate
+{
+    UIInterfaceOrientation toInterfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
+    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
+        return NO;
+    return YES;
+}
+
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+    if (tableView == self.searchDisplayController.searchResultsTableView)
+        return _searchData.count;
+    else
+        return _mutableObjectList.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    static NSString *CellIdentifier = @"PlexCellDetail";
+
+    VLCLocalNetworkListCell *cell = (VLCLocalNetworkListCell *)[[self tableView] dequeueReusableCellWithIdentifier:CellIdentifier];
+
+    if (cell == nil)
+        cell = [VLCLocalNetworkListCell cellWithReuseIdentifier:CellIdentifier];
+
+    NSMutableArray *ObjList = [[NSMutableArray alloc] init];
+    [ObjList removeAllObjects];
+
+    if (tableView == self.searchDisplayController.searchResultsTableView)
+        [ObjList addObjectsFromArray:_searchData];
+    else
+        [ObjList addObjectsFromArray:_mutableObjectList];
+
+    [cell setTitle:[[ObjList objectAtIndex:indexPath.row] objectForKey:@"title"]];
+
+    NSString *thumbPath = [[ObjList objectAtIndex:indexPath.row] objectForKey:@"thumb"];
+    if (thumbPath)
+        [cell setIconURL:[NSURL URLWithString:thumbPath]];
+
+    if ([[[ObjList objectAtIndex:indexPath.row] objectForKey:@"container"] isEqualToString:@"item"]) {
+        NSInteger size = [[[ObjList objectAtIndex:indexPath.row] objectForKey:@"size"] integerValue];
+        NSString *mediaSize = [NSByteCountFormatter stringFromByteCount:size countStyle:NSByteCountFormatterCountStyleFile];
+        NSString *durationInSeconds = [[ObjList objectAtIndex:indexPath.row] objectForKey:@"duration"];
+        [cell setIsDirectory:NO];
+        [cell setIcon:[UIImage imageNamed:@"blank"]];
+        [cell setSubtitle:[NSString stringWithFormat:@"%@ (%@)", mediaSize, durationInSeconds]];
+        [cell setIsDownloadable:YES];
+        [cell setDelegate:self];
+    } else {
+        [cell setIsDirectory:YES];
+        [cell setIcon:[UIImage imageNamed:@"folder"]];
+    }
+    return cell;
+}
+
+#pragma mark - Table view delegate
+
+- (void)tableView:(UITableView *)tableView willDisplayCell:(VLCLocalNetworkListCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    UIColor *color = (indexPath.row % 2 == 0)? [UIColor blackColor]: [UIColor colorWithWhite:.122 alpha:1.];
+    cell.contentView.backgroundColor = cell.titleLabel.backgroundColor = cell.folderTitleLabel.backgroundColor = cell.subtitleLabel.backgroundColor =  color;
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    NSMutableArray *ObjList = [[NSMutableArray alloc] init];
+    [ObjList removeAllObjects];
+    NSString *newPath = nil;
+
+    if (tableView == self.searchDisplayController.searchResultsTableView)
+        [ObjList addObjectsFromArray:_searchData];
+    else
+        [ObjList addObjectsFromArray:_mutableObjectList];
+
+    NSString *keyValue = [[ObjList objectAtIndex:indexPath.row] objectForKey:@"key"];
+
+    if ([keyValue rangeOfString:@"library"].location == NSNotFound)
+        newPath = [_PlexServerPath stringByAppendingPathComponent:keyValue];
+    else
+        newPath = keyValue;
+
+    if ([[[ObjList objectAtIndex:indexPath.row] objectForKey:@"container"] isEqualToString:@"item"]) {
+        [ObjList removeAllObjects];
+        ObjList = [_PlexParser PlexMediaServerParser:_PlexServerAddress port:_PlexServerPort navigationPath:newPath];
+        NSString *URLofSubtitle = nil;
+        if ([[ObjList objectAtIndex:0] objectForKey:@"keySubtitle"])
+            URLofSubtitle = [self _getFileSubtitleFromPlexServer:ObjList modeStream:YES];
+
+        NSURL *itemURL = [NSURL URLWithString:[[ObjList objectAtIndex:0] objectForKey:@"keyMedia"]];
+        if (itemURL) {
+            VLCAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
+            [appDelegate openMovieWithExternalSubtitleFromURL:itemURL externalSubURL:URLofSubtitle];
+        }
+    } else {
+        VLCLocalPlexFolderListViewController *targetViewController = [[VLCLocalPlexFolderListViewController alloc] initWithPlexServer:_PlexServerName serverAddress:_PlexServerAddress portNumber:_PlexServerPort atPath:newPath];
+        [[self navigationController] pushViewController:targetViewController animated:YES];
+    }
+    [tableView deselectRowAtIndexPath:indexPath animated:NO];
+}
+
+#pragma mark - Specifics
+
+- (void)_downloadFileFromMediaItem:(NSMutableArray *)mutableMediaObject
+{
+    NSURL *itemURL = [NSURL URLWithString:[[mutableMediaObject objectAtIndex:0] objectForKey:@"keyMedia"]];
+
+    if (![[itemURL absoluteString] isSupportedFormat]) {
+        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"FILE_NOT_SUPPORTED", @"") message:[NSString stringWithFormat:NSLocalizedString(@"FILE_NOT_SUPPORTED_LONG", @""), [itemURL absoluteString]] delegate:self cancelButtonTitle:NSLocalizedString(@"BUTTON_CANCEL", @"") otherButtonTitles:nil];
+        [alert show];
+    } else if (itemURL) {
+        NSString *fileName = [[mutableMediaObject objectAtIndex:0] objectForKey:@"namefile"];
+        [[(VLCAppDelegate *)[UIApplication sharedApplication].delegate downloadViewController] addURLToDownloadList:itemURL fileNameOfMedia:fileName];
+    }
+}
+
+- (NSString *)_getFileSubtitleFromPlexServer:(NSMutableArray *)mutableMediaObject modeStream:(BOOL)modeStream
+{
+    NSURL *url = [NSURL URLWithString:[[mutableMediaObject objectAtIndex:0] objectForKey:@"keySubtitle"]];
+    NSString *receivedSub = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
+    NSArray *searchPaths =  nil;
+    if (modeStream)
+        searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+    else
+        searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+
+    NSString *directoryPath = [searchPaths objectAtIndex:0];
+    NSString *FileSubtitlePath = [[directoryPath stringByAppendingPathComponent:[[[mutableMediaObject objectAtIndex:0] objectForKey:@"namefile"] stringByDeletingPathExtension]] stringByAppendingPathExtension:[[mutableMediaObject objectAtIndex:0] objectForKey:@"codecSubtitle"]];
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    if (![fileManager fileExistsAtPath:FileSubtitlePath]) {
+        [fileManager createFileAtPath:FileSubtitlePath contents:nil attributes:nil];
+        if (![fileManager fileExistsAtPath:FileSubtitlePath])
+            APLog(@"file creation failed, no data was saved");
+    }
+    [receivedSub writeToFile:FileSubtitlePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
+    return FileSubtitlePath;
+}
+
+#pragma mark - VLCLocalNetworkListCell delegation
+
+- (void)triggerDownloadForCell:(VLCLocalNetworkListCell *)cell
+{
+    NSMutableArray *ObjList = [[NSMutableArray alloc] init];
+    [ObjList removeAllObjects];
+
+    if ([self.searchDisplayController isActive])
+        [ObjList addObject:_searchData[[self.searchDisplayController.searchResultsTableView indexPathForCell:cell].row]];
+    else
+        [ObjList addObject:_mutableObjectList[[self.tableView indexPathForCell:cell].row]];
+
+    NSString *path = [[ObjList objectAtIndex:0] objectForKey:@"key"];
+    [ObjList removeAllObjects];
+    ObjList = [_PlexParser PlexMediaServerParser:_PlexServerAddress port:_PlexServerPort navigationPath:path];
+
+    if ([[ObjList objectAtIndex:0] objectForKey:@"keySubtitle"])
+        [self _getFileSubtitleFromPlexServer:ObjList modeStream:NO];
+
+    [self _downloadFileFromMediaItem:ObjList];
+    [cell.statusLabel showStatusMessage:NSLocalizedString(@"DOWNLOADING", @"")];
+}
+
+#pragma mark - Search Display Controller Delegate
+
+- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
+{
+    [_searchData removeAllObjects];
+
+    for (int i = 0; i < [_mutableObjectList count]; i++) {
+        NSRange nameRange;
+        nameRange = [[[_mutableObjectList objectAtIndex:i] objectForKey:@"title"] rangeOfString:searchString options:NSCaseInsensitiveSearch];
+        if (nameRange.location != NSNotFound)
+            [_searchData addObject:_mutableObjectList[i]];
+    }
+    return YES;
+}
+
+- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView
+{
+    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
+        tableView.rowHeight = 80.0f;
+    else
+        tableView.rowHeight = 68.0f;
+
+    tableView.backgroundColor = [UIColor blackColor];
+}
+
+@end

+ 8 - 4
Sources/VLCLocalServerListViewController.m

@@ -18,6 +18,7 @@
 #import "UPnPManager.h"
 #import "VLCLocalNetworkListCell.h"
 #import "VLCLocalServerFolderListViewController.h"
+#import "VLCLocalPlexFolderListViewController.h"
 #import <QuartzCore/QuartzCore.h>
 #import "GHRevealViewController.h"
 #import "VLCNetworkLoginViewController.h"
@@ -304,9 +305,12 @@
             [self.navigationController pushViewController:targetViewController animated:YES];
         }
     } else if (section == 1) {
-        //target Plex servers here
-        APLog(@"Hello I'm a Plex Media Server, my name is %@ my adress is %@%@", [_PlexServicesInfo[row] objectForKey:@"name"], [_PlexServicesInfo[row] objectForKey:@"hostName"], [_PlexServicesInfo[row] objectForKey:@"port"]);
-   } else if (section == 2) {
+        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) {
         UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:_loginViewController];
         [navCon loadTheme];
         navCon.navigationBarHidden = NO;
@@ -328,7 +332,7 @@
         else
             _loginViewController.hostname = @"";
     } else if (section == 3) {
-        VLCAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
+        VLCAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
         [appDelegate openMovieFromURL:[[_sapDiscoverer.discoveredMedia mediaAtIndex:row] url]];
     }
 }

+ 17 - 0
Sources/VLCPlexParser.h

@@ -0,0 +1,17 @@
+/*****************************************************************************
+ * VLCPlexParser.h
+ * VLC for iOS
+ *****************************************************************************
+ * Copyright (c) 2014 VideoLAN. All rights reserved.
+ *
+ * Authors: Pierre Sagaspe <pierre.sagaspe # me.com>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+#import <UIKit/UIKit.h>
+@interface VLCPlexParser : NSObject
+
+- (NSMutableArray *)PlexMediaServerParser:(NSString *)adress port:(NSString *)port navigationPath:(NSString *)navPath;
+
+@end

+ 106 - 0
Sources/VLCPlexParser.m

@@ -0,0 +1,106 @@
+/*****************************************************************************
+ * VLCPlexParser.m
+ * VLC for iOS
+ *****************************************************************************
+ * Copyright (c) 2014 VideoLAN. All rights reserved.
+ *
+ * Authors: Pierre Sagaspe <pierre.sagaspe # me.com>
+ *
+ * Refer to the COPYING file of the official project for license.
+ *****************************************************************************/
+
+#import "VLCPlexParser.h"
+
+#define kPlexMediaServerDirInit @"library/sections"
+
+@interface VLCPlexParser () <NSXMLParserDelegate>
+{
+    NSMutableArray *_containerInfo;
+    NSMutableDictionary *_dicoInfo;
+    NSString *_PlexMediaServerUrl;
+}
+@end
+
+@implementation VLCPlexParser
+
+- (NSMutableArray *)PlexMediaServerParser:(NSString *)adress port:(NSString *)port navigationPath:(NSString *)path
+{
+    _containerInfo = [[NSMutableArray alloc] init];
+    [_containerInfo removeAllObjects];
+    _dicoInfo = [[NSMutableDictionary alloc] init];
+    _PlexMediaServerUrl = [NSString stringWithFormat:@"http://%@%@",adress, port];
+    NSString *mediaServerUrl;
+
+    if ([path isEqualToString:@""])
+        mediaServerUrl = [NSString stringWithFormat:@"%@/%@",_PlexMediaServerUrl, kPlexMediaServerDirInit];
+    else {
+        if ([path rangeOfString:@"library"].location != NSNotFound)
+            mediaServerUrl = [NSString stringWithFormat:@"%@%@",_PlexMediaServerUrl, path];
+        else
+            mediaServerUrl = [NSString stringWithFormat:@"%@/%@/%@",_PlexMediaServerUrl, kPlexMediaServerDirInit, path];
+    }
+
+    NSURL *url = [[NSURL alloc] initWithString:mediaServerUrl];
+    NSXMLParser *xmlparser = [[NSXMLParser alloc] initWithContentsOfURL:url];
+    [xmlparser setDelegate:self];
+
+    if (![xmlparser parse])
+        APLog(@"PlexParser url Errors : %@", url);
+
+    return _containerInfo;
+}
+
+- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
+{
+    if([elementName isEqualToString:@"MediaContainer"]) {
+        if ([attributeDict objectForKey:@"friendlyName"])
+            [_dicoInfo setObject:[attributeDict objectForKey:@"friendlyName"] forKey:@"libTitle"];
+        else if ([attributeDict objectForKey:@"title1"])
+            [_dicoInfo setObject:[attributeDict objectForKey:@"title1"] forKey:@"libTitle"];
+    } else if([elementName isEqualToString:@"Directory"]) {
+        [_dicoInfo setObject:@"directory" forKey:@"container"];
+        [_dicoInfo setObject:[attributeDict objectForKey:@"key"] forKey:@"key"];
+        [_dicoInfo setObject:[attributeDict objectForKey:@"title"] forKey:@"title"];
+    } else if([elementName isEqualToString:@"Video"]) {
+        [_dicoInfo setObject:@"item" forKey:@"container"];
+        [_dicoInfo setObject:[attributeDict objectForKey:@"key"] forKey:@"key"];
+        [_dicoInfo setObject:[attributeDict objectForKey:@"title"] forKey:@"title"];
+    } else if([elementName isEqualToString:@"Part"]) {
+        [_dicoInfo setObject:[NSString stringWithFormat:@"%@%@",_PlexMediaServerUrl, [attributeDict objectForKey:@"key"]] forKey:@"keyMedia"];
+        if([attributeDict objectForKey:@"file"])
+            [_dicoInfo setObject:[[attributeDict objectForKey:@"file"] lastPathComponent] forKey:@"namefile"];
+        NSString *duration = [self timeFormatted:[[attributeDict objectForKey:@"duration"] intValue]];
+        [_dicoInfo setObject:duration forKey:@"duration"];
+        NSString *sizeFile = (NSString *)[attributeDict objectForKey:@"size"];
+        [_dicoInfo setObject:sizeFile forKey:@"size"];
+    } else if([elementName isEqualToString:@"Stream"]) {
+        if([attributeDict objectForKey:@"key"]){
+            [_dicoInfo setObject:[NSString stringWithFormat:@"%@%@",_PlexMediaServerUrl, [attributeDict objectForKey:@"key"]] forKey:@"keySubtitle"];
+            [_dicoInfo setObject:[attributeDict objectForKey:@"codec"] forKey:@"codecSubtitle"];
+            [_dicoInfo setObject:[attributeDict objectForKey:@"language"] forKey:@"languageSubtitle"];
+        }
+    }
+
+    if ([attributeDict objectForKey:@"thumb"] && ([elementName isEqualToString:@"Video"] || [elementName isEqualToString:@"Directory"] || [elementName isEqualToString:@"Part"]))
+        [_dicoInfo setObject:[NSString stringWithFormat:@"%@%@", _PlexMediaServerUrl, [attributeDict objectForKey:@"thumb"]] forKey:@"thumb"];
+
+}
+
+- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
+{
+    if(([elementName isEqualToString:@"Video"] || [elementName isEqualToString:@"Directory"] || [elementName isEqualToString:@"MediaContainer"]) && [_dicoInfo count] > 0) {
+        [_containerInfo addObject:_dicoInfo];
+        _dicoInfo = [[NSMutableDictionary alloc] init];
+    }
+}
+
+- (NSString *)timeFormatted:(int)mSeconds
+{
+    mSeconds = (int)(mSeconds / 1000);
+    int seconds = (int)(mSeconds % 60);
+    int minutes = (int)((mSeconds / 60) % 60);
+    int hours = (int)(mSeconds / 3600);
+    return [NSString stringWithFormat:@"%02d:%02d:%02d",hours, minutes, seconds];
+}
+
+@end

+ 12 - 0
VLC for iOS.xcodeproj/project.pbxproj

@@ -7,6 +7,8 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		265D511C1922746C00E38383 /* VLCLocalPlexFolderListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 265D51191922746C00E38383 /* VLCLocalPlexFolderListViewController.m */; };
+		265D511D1922746C00E38383 /* VLCPlexParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 265D511B1922746C00E38383 /* VLCPlexParser.m */; };
 		29125E5617492219003F03E5 /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = 29125E5417492219003F03E5 /* index.html */; };
 		2915540117490A1E00B86CAD /* DDData.m in Sources */ = {isa = PBXBuildFile; fileRef = 291553EB17490A1E00B86CAD /* DDData.m */; };
 		2915540217490A1E00B86CAD /* DDNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = 291553ED17490A1E00B86CAD /* DDNumber.m */; };
@@ -405,6 +407,10 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		265D51181922746C00E38383 /* VLCLocalPlexFolderListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCLocalPlexFolderListViewController.h; path = Sources/VLCLocalPlexFolderListViewController.h; sourceTree = SOURCE_ROOT; };
+		265D51191922746C00E38383 /* VLCLocalPlexFolderListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCLocalPlexFolderListViewController.m; path = Sources/VLCLocalPlexFolderListViewController.m; sourceTree = SOURCE_ROOT; };
+		265D511A1922746C00E38383 /* VLCPlexParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCPlexParser.h; path = Sources/VLCPlexParser.h; sourceTree = SOURCE_ROOT; };
+		265D511B1922746C00E38383 /* VLCPlexParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCPlexParser.m; path = Sources/VLCPlexParser.m; sourceTree = SOURCE_ROOT; };
 		29125E5417492219003F03E5 /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = "<group>"; };
 		291553EA17490A1E00B86CAD /* DDData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDData.h; sourceTree = "<group>"; };
 		291553EB17490A1E00B86CAD /* DDData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDData.m; sourceTree = "<group>"; };
@@ -1488,10 +1494,14 @@
 		7D31002117B676D500E6516D /* Local Network Connectivity */ = {
 			isa = PBXGroup;
 			children = (
+				265D511A1922746C00E38383 /* VLCPlexParser.h */,
+				265D511B1922746C00E38383 /* VLCPlexParser.m */,
 				7D30F3D1183AB2F100FFC021 /* VLCLocalNetworkListCell.h */,
 				7D30F3D2183AB2F100FFC021 /* VLCLocalNetworkListCell.m */,
 				7D30F3D3183AB2F100FFC021 /* VLCLocalServerFolderListViewController.h */,
 				7D30F3D4183AB2F100FFC021 /* VLCLocalServerFolderListViewController.m */,
+				265D51181922746C00E38383 /* VLCLocalPlexFolderListViewController.h */,
+				265D51191922746C00E38383 /* VLCLocalPlexFolderListViewController.m */,
 				7D30F3D5183AB2F100FFC021 /* VLCLocalServerListViewController.h */,
 				7D30F3D6183AB2F100FFC021 /* VLCLocalServerListViewController.m */,
 				7D30F3DA183AB2F900FFC021 /* VLCNetworkLoginViewController.h */,
@@ -2691,7 +2701,9 @@
 				7D30F3CA183AB27A00FFC021 /* VLCDownloadViewController.m in Sources */,
 				7D30F3CD183AB29300FFC021 /* VLCMenuTableViewController.m in Sources */,
 				7D30F3D0183AB2AC00FFC021 /* VLCMediaFileDiscoverer.m in Sources */,
+				265D511D1922746C00E38383 /* VLCPlexParser.m in Sources */,
 				7D30F3D7183AB2F100FFC021 /* VLCLocalNetworkListCell.m in Sources */,
+				265D511C1922746C00E38383 /* VLCLocalPlexFolderListViewController.m in Sources */,
 				7D30F3D8183AB2F100FFC021 /* VLCLocalServerFolderListViewController.m in Sources */,
 				7D30F3D9183AB2F100FFC021 /* VLCLocalServerListViewController.m in Sources */,
 				7D30F3DC183AB2F900FFC021 /* VLCNetworkLoginViewController.m in Sources */,