VLCHTTPConnection.m 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. /*****************************************************************************
  2. * VLCHTTPConnection.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013-2018 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  9. * Pierre Sagaspe <pierre.sagaspe # me.com>
  10. * Carola Nitz <caro # videolan.org>
  11. * Jean-Baptiste Kempf <jb # videolan.org>
  12. *
  13. * Refer to the COPYING file of the official project for license.
  14. *****************************************************************************/
  15. #import "VLCActivityManager.h"
  16. #import "VLCHTTPConnection.h"
  17. #import "MultipartFormDataParser.h"
  18. #import "HTTPMessage.h"
  19. #import "HTTPDataResponse.h"
  20. #import "HTTPFileResponse.h"
  21. #import "MultipartMessageHeaderField.h"
  22. #import "HTTPDynamicFileResponse.h"
  23. #import "HTTPErrorResponse.h"
  24. #import "NSString+SupportedMedia.h"
  25. #import "UIDevice+VLC.h"
  26. #import "VLCHTTPUploaderController.h"
  27. #import "VLCMetaData.h"
  28. #if TARGET_OS_IOS
  29. #import "VLC-Swift.h"
  30. #import "VLCThumbnailsCache.h"
  31. #endif
  32. #if TARGET_OS_TV
  33. #import "VLCPlayerControlWebSocket.h"
  34. #endif
  35. @interface VLCHTTPConnection()
  36. {
  37. MultipartFormDataParser *_parser;
  38. NSFileHandle *_storeFile;
  39. NSString *_filepath;
  40. UInt64 _contentLength;
  41. UInt64 _receivedContent;
  42. #if TARGET_OS_TV
  43. NSMutableArray *_receivedFiles;
  44. #endif
  45. }
  46. @end
  47. @implementation VLCHTTPConnection
  48. - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
  49. {
  50. // Add support for POST
  51. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"])
  52. return YES;
  53. return [super supportsMethod:method atPath:path];
  54. }
  55. - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
  56. {
  57. // Inform HTTP server that we expect a body to accompany a POST request
  58. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
  59. // here we need to make sure, boundary is set in header
  60. NSString* contentType = [request headerField:@"Content-Type"];
  61. NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location;
  62. if (NSNotFound == paramsSeparator)
  63. return NO;
  64. if (paramsSeparator >= contentType.length - 1)
  65. return NO;
  66. NSString* type = [contentType substringToIndex:paramsSeparator];
  67. if (![type isEqualToString:@"multipart/form-data"]) {
  68. // we expect multipart/form-data content type
  69. return NO;
  70. }
  71. // enumerate all params in content-type, and find boundary there
  72. NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"];
  73. NSUInteger count = params.count;
  74. for (NSUInteger i = 0; i < count; i++) {
  75. NSString *param = params[i];
  76. paramsSeparator = [param rangeOfString:@"="].location;
  77. if ((NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1)
  78. continue;
  79. NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)];
  80. NSString* paramValue = [param substringFromIndex:paramsSeparator+1];
  81. if ([paramName isEqualToString: @"boundary"])
  82. // let's separate the boundary from content-type, to make it more handy to handle
  83. [request setHeaderField:@"boundary" value:paramValue];
  84. }
  85. // check if boundary specified
  86. if (nil == [request headerField:@"boundary"])
  87. return NO;
  88. return YES;
  89. }
  90. return [super expectsRequestBodyFromMethod:method atPath:path];
  91. }
  92. - (NSObject<HTTPResponse> *)_httpPOSTresponseUploadJSON
  93. {
  94. return [[HTTPDataResponse alloc] initWithData:[@"\"OK\"" dataUsingEncoding:NSUTF8StringEncoding]];
  95. }
  96. - (BOOL)fileIsInDocumentFolder:(NSString*)filepath
  97. {
  98. if (!filepath) return NO;
  99. NSError *error;
  100. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  101. NSString *directoryPath = [searchPaths firstObject];
  102. NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:&error];
  103. if (error != nil) {
  104. APLog(@"checking filerelationship failed %@", error);
  105. return NO;
  106. }
  107. return [array containsObject:filepath.lastPathComponent];
  108. }
  109. #if TARGET_OS_IOS
  110. - (NSObject<HTTPResponse> *)_httpGETDownloadForPath:(NSString *)path
  111. {
  112. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/download/" withString:@""] stringByRemovingPercentEncoding];
  113. if (![self fileIsInDocumentFolder:filePath]) {
  114. //return nil which gets handled as resource not found
  115. return nil;
  116. }
  117. HTTPFileResponse *fileResponse = [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];
  118. fileResponse.contentType = @"application/octet-stream";
  119. return fileResponse;
  120. }
  121. - (NSObject<HTTPResponse> *)_httpGETThumbnailForPath:(NSString *)path
  122. {
  123. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/Thumbnail/" withString:@""] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet];
  124. if ([filePath isEqualToString:@"/"]) return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  125. UIImage *thumbnail = [UIImage imageWithContentsOfFile:filePath];
  126. if (!thumbnail) return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  127. NSData *theData = UIImageJPEGRepresentation(thumbnail, .9);
  128. if (!theData) return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  129. HTTPDataResponse *dataResponse = [[HTTPDataResponse alloc] initWithData:theData];
  130. dataResponse.contentType = @"image/jpg";
  131. return dataResponse;
  132. }
  133. - (NSObject<HTTPResponse> *)_httpGETLibraryForPath:(NSString *)path
  134. {
  135. NSString *filePath = [self filePathForURI:path];
  136. NSString *documentRoot = [config documentRoot];
  137. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  138. BOOL shouldReturnLibVLCXML = [relativePath isEqualToString:@"/libMediaVLC.xml"];
  139. NSArray *allMedia = [self allMedia];
  140. return shouldReturnLibVLCXML ? [self generateXMLResponseFrom:allMedia path:path] : [self generateHttpResponseFrom:allMedia path:path];
  141. }
  142. - (NSArray *)allMedia
  143. {
  144. MediaLibraryService* medialibrary = [[VLCHTTPUploaderController sharedInstance] medialibrary];
  145. // Adding all Albums
  146. NSMutableArray *allMedia = [[medialibrary albumsWithSortingCriteria:VLCMLSortingCriteriaDefault desc:false] mutableCopy] ?: [NSMutableArray new];
  147. // Adding all Playlists
  148. [allMedia addObjectsFromArray:[medialibrary playlistsWithSortingCriteria:VLCMLSortingCriteriaDefault desc:false]];
  149. // Adding all Videos files
  150. [allMedia addObjectsFromArray:[medialibrary mediaOfType:VLCMLMediaTypeVideo sortingCriteria:VLCMLSortingCriteriaDefault desc:false]];
  151. //TODO: add all shows
  152. // Adding all audio files which are not in an Album
  153. NSArray* audioFiles = [medialibrary mediaOfType:VLCMLMediaTypeAudio sortingCriteria:VLCMLSortingCriteriaDefault desc:false];
  154. for (VLCMLMedia *track in audioFiles) {
  155. if (track.subtype != VLCMLMediaSubtypeAlbumTrack) {
  156. [allMedia addObject:track];
  157. }
  158. }
  159. return [allMedia copy];
  160. }
  161. - (NSString *)createHTMLMediaObjectFromMedia:(VLCMLMedia *)media
  162. {
  163. return [NSString stringWithFormat:
  164. @"<div style=\"background-image:url('Thumbnail/%@')\"> \
  165. <a href=\"download/%@\" class=\"inner\"> \
  166. <div class=\"down icon\"></div> \
  167. <div class=\"infos\"> \
  168. <span class=\"first-line\">%@</span> \
  169. <span class=\"second-line\">%@ - %@</span> \
  170. </div> \
  171. </a> \
  172. </div>",
  173. media.thumbnail.path,
  174. [[media mainFile].mrl.path
  175. stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet],
  176. media.title,
  177. [media mediaDuration], [media formatSize]];
  178. }
  179. - (NSString *)createHTMLFolderObjectWithImagePath:(NSString *)imagePath
  180. name:(NSString *)name
  181. count:(NSUInteger)count
  182. {
  183. return [NSString stringWithFormat:
  184. @"<div style=\"background-image:url('Thumbnail/%@')\"> \
  185. <a href=\"#\" class=\"inner folder\"> \
  186. <div class=\"open icon\"></div> \
  187. <div class=\"infos\"> \
  188. <span class=\"first-line\">%@</span> \
  189. <span class=\"second-line\">%lu items</span> \
  190. </div> \
  191. </a> \
  192. <div class=\"content\">",
  193. imagePath,
  194. name,
  195. count];
  196. }
  197. - (HTTPDynamicFileResponse *)generateHttpResponseFrom:(NSArray *)media path:(NSString *)path
  198. {
  199. NSMutableArray *mediaInHtml = [[NSMutableArray alloc] initWithCapacity:media.count];
  200. for (NSObject <VLCMLObject> *mediaObject in media) {
  201. if ([mediaObject isKindOfClass:[VLCMLMedia class]]) {
  202. [mediaInHtml addObject:[self createHTMLMediaObjectFromMedia:(VLCMLMedia *)mediaObject]];
  203. } else if ([mediaObject isKindOfClass:[VLCMLPlaylist class]]) {
  204. VLCMLPlaylist *playlist = (VLCMLPlaylist *)mediaObject;
  205. NSArray *playlistItems = [playlist media];
  206. [mediaInHtml addObject: [self createHTMLFolderObjectWithImagePath:playlist.artworkMrl
  207. name:playlist.name
  208. count:playlistItems.count]];
  209. for (VLCMLMedia *media in playlistItems) {
  210. [mediaInHtml addObject:[self createHTMLMediaObjectFromMedia:media]];
  211. }
  212. [mediaInHtml addObject:@"</div></div>"];
  213. } else if ([mediaObject isKindOfClass:[VLCMLAlbum class]]) {
  214. VLCMLAlbum *album = (VLCMLAlbum *)mediaObject;
  215. NSArray *albumTracks = [album tracks];
  216. [mediaInHtml addObject:[self createHTMLFolderObjectWithImagePath:[album artworkMRL].path
  217. name:album.title
  218. count:albumTracks.count]];
  219. for (VLCMLMedia *track in albumTracks) {
  220. [mediaInHtml addObject:[self createHTMLMediaObjectFromMedia:track]];
  221. }
  222. [mediaInHtml addObject:@"</div></div>"];
  223. }
  224. } // end of forloop
  225. NSString *deviceModel = [[UIDevice currentDevice] model];
  226. NSDictionary *replacementDict = @{@"FILES" : [mediaInHtml componentsJoinedByString:@" "],
  227. @"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil),
  228. @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
  229. @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG", nil), deviceModel],
  230. @"WEBINTF_DOWNLOADFILES" : NSLocalizedString(@"WEBINTF_DOWNLOADFILES", nil),
  231. @"WEBINTF_DOWNLOADFILES_LONG" : [NSString stringWithFormat: NSLocalizedString(@"WEBINTF_DOWNLOADFILES_LONG", nil), deviceModel]};
  232. HTTPDynamicFileResponse *fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  233. forConnection:self
  234. separator:@"%%"
  235. replacementDictionary:replacementDict];
  236. fileResponse.contentType = @"text/html";
  237. return fileResponse;
  238. }
  239. - (HTTPDynamicFileResponse *)generateXMLResponseFrom:(NSArray *)media path:(NSString *)path
  240. {
  241. NSMutableArray *mediaInXml = [[NSMutableArray alloc] initWithCapacity:media.count];
  242. NSString *hostName = [NSString stringWithFormat:@"%@:%@", [[VLCHTTPUploaderController sharedInstance] hostname], [[VLCHTTPUploaderController sharedInstance] hostnamePort]];
  243. for (NSObject <VLCMLObject> *mediaObject in media) {
  244. if ([mediaObject isKindOfClass:[VLCMLMedia class]]) {
  245. VLCMLMedia *file = (VLCMLMedia *)mediaObject;
  246. NSString *pathSub = [self _checkIfSubtitleWasFound:[file mainFile].mrl.path];
  247. if (pathSub)
  248. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  249. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/Thumbnail/%@\" duration=\"%@\" size=\"%@\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"%@\"/>",
  250. file.title,
  251. hostName,
  252. file.thumbnail.path,
  253. [file mediaDuration], [file formatSize],
  254. hostName,
  255. [[file mainFile].mrl.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet], pathSub]];
  256. } else if ([mediaObject isKindOfClass:[VLCMLPlaylist class]]) {
  257. VLCMLPlaylist *playlist = (VLCMLPlaylist *)mediaObject;
  258. NSArray *playlistItems = [playlist media];
  259. for (VLCMLMedia *file in playlistItems) {
  260. NSString *pathSub = [self _checkIfSubtitleWasFound:[file mainFile].mrl.path];
  261. if (pathSub)
  262. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  263. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/Thumbnail/%@\" duration=\"%@\" size=\"%@\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"%@\"/>", file.title,
  264. hostName,
  265. file.thumbnail.path,
  266. [file mediaDuration],
  267. [file formatSize],
  268. hostName,
  269. [[file mainFile].mrl.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet], pathSub]];
  270. }
  271. } else if ([mediaObject isKindOfClass:[VLCMLAlbum class]]) {
  272. VLCMLAlbum *album = (VLCMLAlbum *)mediaObject;
  273. NSArray *albumTracks = [album tracks];
  274. for (VLCMLMedia *track in albumTracks) {
  275. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/Thumbnail/%@\" duration=\"%@\" size=\"%@\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"\"/>", track.title,
  276. hostName,
  277. track.thumbnail.path,
  278. [track mediaDuration],
  279. [track formatSize],
  280. hostName,
  281. [[track mainFile].mrl.path stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLFragmentAllowedCharacterSet]]];
  282. }
  283. }
  284. } // end of forloop
  285. NSDictionary *replacementDict = @{@"FILES" : [mediaInXml componentsJoinedByString:@" "],
  286. @"NB_FILE" : [NSString stringWithFormat:@"%li", (unsigned long)mediaInXml.count],
  287. @"LIB_TITLE" : [[UIDevice currentDevice] name]};
  288. HTTPDynamicFileResponse *fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  289. forConnection:self
  290. separator:@"%%"
  291. replacementDictionary:replacementDict];
  292. fileResponse.contentType = @"application/xml";
  293. return fileResponse;
  294. }
  295. #else
  296. - (NSObject<HTTPResponse> *)_httpGETLibraryForPath:(NSString *)path
  297. {
  298. UIDevice *currentDevice = [UIDevice currentDevice];
  299. NSString *deviceModel = [currentDevice model];
  300. NSString *filePath = [self filePathForURI:path];
  301. NSString *documentRoot = [config documentRoot];
  302. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  303. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE_ATV", nil),
  304. @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
  305. @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG_ATV", nil), deviceModel],
  306. @"WEBINTF_OPEN_URL" : NSLocalizedString(@"ENTER_URL", nil)};
  307. HTTPDynamicFileResponse *fileResponse;
  308. if ([relativePath isEqualToString:@"/index.html"]) {
  309. fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  310. forConnection:self
  311. separator:@"%%"
  312. replacementDictionary:replacementDict];
  313. fileResponse.contentType = @"text/html";
  314. }
  315. return fileResponse;
  316. }
  317. #endif
  318. - (NSObject<HTTPResponse> *)_httpGETCSSForPath:(NSString *)path
  319. {
  320. #if TARGET_OS_IOS
  321. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil)};
  322. #else
  323. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE_ATV", nil)};
  324. #endif
  325. HTTPDynamicFileResponse *fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  326. forConnection:self
  327. separator:@"%%"
  328. replacementDictionary:replacementDict];
  329. fileResponse.contentType = @"text/css";
  330. return fileResponse;
  331. }
  332. #if TARGET_OS_TV
  333. - (NSObject <HTTPResponse> *)_HTTPGETPlaying
  334. {
  335. /* JSON response:
  336. {
  337. "currentTime": 42,
  338. "media": {
  339. "id": "some id",
  340. "title": "some title",
  341. "duration": 120000
  342. }
  343. }
  344. */
  345. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  346. if (!vpc.isPlaying) {
  347. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  348. }
  349. VLCMedia *media = [vpc currentlyPlayingMedia];
  350. if (!media) {
  351. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  352. }
  353. NSString *mediaTitle = vpc.metadata.title;
  354. if (!mediaTitle)
  355. mediaTitle = @"";
  356. NSDictionary *mediaDict = @{ @"id" : media.url.absoluteString,
  357. @"title" : mediaTitle,
  358. @"duration" : @([vpc mediaDuration])};
  359. NSDictionary *returnDict = @{ @"currentTime" : @([vpc playedTime].intValue),
  360. @"media" : mediaDict };
  361. NSError *error;
  362. NSData *returnData = [NSJSONSerialization dataWithJSONObject:returnDict options:0 error:&error];
  363. if (error != nil) {
  364. APLog(@"JSON serialization failed %@", error);
  365. return [[HTTPErrorResponse alloc] initWithErrorCode:500];
  366. }
  367. return [[HTTPDataResponse alloc] initWithData:returnData];
  368. }
  369. - (NSObject <HTTPResponse> *)_HTTPGETwebResources
  370. {
  371. /* JS response
  372. {
  373. "WEBINTF_URL_SENT" : "URL sent successfully.",
  374. "WEBINTF_URL_EMPTY" :"'URL cannot be empty.",
  375. "WEBINTF_URL_INVALID" : "Not a valid URL."
  376. }
  377. */
  378. NSString *returnString = [NSString stringWithFormat:
  379. @"var LOCALES = {\n" \
  380. "PLAYER_CONTROL: {\n" \
  381. "URL: {\n" \
  382. "EMPTY: \"%@\",\n" \
  383. "NOT_VALID: \"%@\",\n" \
  384. "SENT_SUCCESSFULLY: \"%@\"\n" \
  385. "}\n" \
  386. "}\n" \
  387. "}",
  388. NSLocalizedString(@"WEBINTF_URL_EMPTY", nil),
  389. NSLocalizedString(@"WEBINTF_URL_INVALID", nil),
  390. NSLocalizedString(@"WEBINTF_URL_SENT", nil)];
  391. NSData *returnData = [returnString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
  392. return [[HTTPDataResponse alloc] initWithData:returnData];
  393. }
  394. - (NSObject <HTTPResponse> *)_HTTPGETPlaylist
  395. {
  396. /* JSON response:
  397. [
  398. {
  399. "media": {
  400. "id": "some id 1",
  401. "title": "some title 1",
  402. "duration": 120000
  403. }
  404. },
  405. ...]
  406. */
  407. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  408. if (!vpc.isPlaying || !vpc.mediaList) {
  409. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  410. }
  411. VLCMediaList *mediaList = vpc.mediaList;
  412. [mediaList lock];
  413. NSUInteger mediaCount = mediaList.count;
  414. NSMutableArray *retArray = [NSMutableArray array];
  415. for (NSUInteger x = 0; x < mediaCount; x++) {
  416. VLCMedia *media = [mediaList mediaAtIndex:x];
  417. NSString *mediaTitle;
  418. if (media.parsedStatus == VLCMediaParsedStatusDone) {
  419. mediaTitle = [media metadataForKey:VLCMetaInformationTitle];
  420. } else {
  421. mediaTitle = media.url.lastPathComponent;
  422. }
  423. NSDictionary *mediaDict = @{ @"id" : media.url.absoluteString,
  424. @"title" : mediaTitle,
  425. @"duration" : @(media.length.intValue) };
  426. [retArray addObject:@{ @"media" : mediaDict }];
  427. }
  428. [mediaList unlock];
  429. NSError *error;
  430. NSData *returnData = [NSJSONSerialization dataWithJSONObject:retArray options:0 error:&error];
  431. if (error != nil) {
  432. APLog(@"JSON serialization failed %@", error);
  433. return [[HTTPErrorResponse alloc] initWithErrorCode:500];
  434. }
  435. return [[HTTPDataResponse alloc] initWithData:returnData];
  436. }
  437. #endif
  438. - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
  439. {
  440. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"])
  441. return [self _httpPOSTresponseUploadJSON];
  442. #if TARGET_OS_IOS
  443. if ([path hasPrefix:@"/download/"]) {
  444. return [self _httpGETDownloadForPath:path];
  445. }
  446. if ([path hasPrefix:@"/Thumbnail/"]) {
  447. return [self _httpGETThumbnailForPath:path];
  448. }
  449. #else
  450. if ([path hasPrefix:@"/playing"]) {
  451. return [self _HTTPGETPlaying];
  452. }
  453. if ([path hasPrefix:@"/playlist"]) {
  454. return [self _HTTPGETPlaylist];
  455. }
  456. if ([path hasPrefix:@"/web_resources.js"]) {
  457. return [self _HTTPGETwebResources];
  458. }
  459. #endif
  460. NSString *filePath = [self filePathForURI:path];
  461. NSString *documentRoot = [config documentRoot];
  462. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  463. if ([relativePath isEqualToString:@"/index.html"] || [relativePath isEqualToString:@"/libMediaVLC.xml"]) {
  464. return [self _httpGETLibraryForPath:path];
  465. } else if ([relativePath isEqualToString:@"/style.css"]) {
  466. return [self _httpGETCSSForPath:path];
  467. }
  468. return [super httpResponseForMethod:method URI:path];
  469. }
  470. #if TARGET_OS_TV
  471. - (WebSocket *)webSocketForURI:(NSString *)path
  472. {
  473. return [[VLCPlayerControlWebSocket alloc] initWithRequest:request socket:asyncSocket];
  474. }
  475. #endif
  476. - (void)prepareForBodyWithSize:(UInt64)contentLength
  477. {
  478. // set up mime parser
  479. NSString* boundary = [request headerField:@"boundary"];
  480. _parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
  481. _parser.delegate = self;
  482. APLog(@"expecting file of size %lli kB", contentLength / 1024);
  483. _contentLength = contentLength;
  484. }
  485. - (void)processBodyData:(NSData *)postDataChunk
  486. {
  487. /* append data to the parser. It will invoke callbacks to let us handle
  488. * parsed data. */
  489. [_parser appendData:postDataChunk];
  490. _receivedContent += postDataChunk.length;
  491. long long percentage = ((_receivedContent * 100) / _contentLength);
  492. APLog(@"received %lli kB (%lli %%)", _receivedContent / 1024, percentage);
  493. #if TARGET_OS_TV
  494. if (percentage >= 10) {
  495. [self performSelectorOnMainThread:@selector(startPlaybackOfPath:) withObject:_filepath waitUntilDone:NO];
  496. }
  497. #endif
  498. }
  499. #if TARGET_OS_TV
  500. - (void)startPlaybackOfPath:(NSString *)path
  501. {
  502. APLog(@"Starting playback of %@", path);
  503. if (_receivedFiles == nil)
  504. _receivedFiles = [[NSMutableArray alloc] init];
  505. if ([_receivedFiles containsObject:path])
  506. return;
  507. [_receivedFiles addObject:path];
  508. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  509. VLCMediaList *mediaList = vpc.mediaList;
  510. if (!mediaList) {
  511. mediaList = [[VLCMediaList alloc] init];
  512. }
  513. [mediaList addMedia:[VLCMedia mediaWithURL:[NSURL fileURLWithPath:path]]];
  514. if (!vpc.mediaList) {
  515. [vpc playMediaList:mediaList firstIndex:0 subtitlesFilePath:nil];
  516. }
  517. VLCFullscreenMovieTVViewController *movieVC = [VLCFullscreenMovieTVViewController fullscreenMovieTVViewController];
  518. if (![movieVC isBeingPresented]) {
  519. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:movieVC
  520. animated:YES
  521. completion:nil];
  522. }
  523. }
  524. #endif
  525. //-----------------------------------------------------------------
  526. #pragma mark multipart form data parser delegate
  527. - (void)processStartOfPartWithHeader:(MultipartMessageHeader*) header
  528. {
  529. /* in this sample, we are not interested in parts, other then file parts.
  530. * check content disposition to find out filename */
  531. MultipartMessageHeaderField* disposition = (header.fields)[@"Content-Disposition"];
  532. NSString* filename = (disposition.params)[@"filename"];
  533. if ((nil == filename) || [filename isEqualToString: @""]) {
  534. // it's either not a file part, or
  535. // an empty form sent. we won't handle it.
  536. return;
  537. }
  538. // create the path where to store the media temporarily
  539. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  540. NSString *uploadDirPath = [searchPaths.firstObject
  541. stringByAppendingPathComponent:kVLCHTTPUploadDirectory];
  542. NSFileManager *fileManager = [NSFileManager defaultManager];
  543. BOOL isDir = YES;
  544. if (![fileManager fileExistsAtPath:uploadDirPath isDirectory:&isDir])
  545. [fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil];
  546. _filepath = [uploadDirPath stringByAppendingPathComponent: filename];
  547. NSNumber *freeSpace = [[UIDevice currentDevice] VLCFreeDiskSpace];
  548. if (_contentLength >= freeSpace.longLongValue) {
  549. /* avoid deadlock since we are on a background thread */
  550. [self performSelectorOnMainThread:@selector(notifyUserAboutEndOfFreeStorage:) withObject:filename waitUntilDone:NO];
  551. [self handleResourceNotFound];
  552. [self stop];
  553. return;
  554. }
  555. APLog(@"Saving file to %@", _filepath);
  556. if (![fileManager createDirectoryAtPath:[_filepath stringByDeletingLastPathComponent]
  557. withIntermediateDirectories:true attributes:nil error:nil])
  558. APLog(@"Could not create directory at path: %@", _filepath);
  559. if (![fileManager createFileAtPath:_filepath contents:nil attributes:nil])
  560. APLog(@"Could not create file at path: %@", _filepath);
  561. _storeFile = [NSFileHandle fileHandleForWritingAtPath:_filepath];
  562. VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
  563. [activityManager networkActivityStarted];
  564. [activityManager disableIdleTimer];
  565. }
  566. - (void)notifyUserAboutEndOfFreeStorage:(NSString *)filename
  567. {
  568. #if TARGET_OS_IOS
  569. [VLCAlertViewController alertViewManagerWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  570. errorMessage:[NSString stringWithFormat:
  571. NSLocalizedString(@"DISK_FULL_FORMAT", nil),
  572. filename,
  573. [[UIDevice currentDevice] model]]
  574. viewController:[UIApplication sharedApplication].keyWindow.rootViewController];
  575. #else
  576. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  577. message:[NSString stringWithFormat:
  578. NSLocalizedString(@"DISK_FULL_FORMAT", nil),
  579. filename,
  580. [[UIDevice currentDevice] model]]
  581. preferredStyle:UIAlertControllerStyleAlert];
  582. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  583. style:UIAlertActionStyleCancel
  584. handler:nil]];
  585. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
  586. #endif
  587. }
  588. - (void)processContent:(NSData*)data WithHeader:(MultipartMessageHeader*) header
  589. {
  590. // here we just write the output from parser to the file.
  591. if (_storeFile) {
  592. @try {
  593. [_storeFile writeData:data];
  594. }
  595. @catch (NSException *exception) {
  596. APLog(@"File to write further data because storage is full.");
  597. [_storeFile closeFile];
  598. _storeFile = nil;
  599. /* don't block */
  600. [self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
  601. }
  602. }
  603. }
  604. - (void)processEndOfPartWithHeader:(MultipartMessageHeader*)header
  605. {
  606. // as the file part is over, we close the file.
  607. APLog(@"closing file");
  608. [_storeFile closeFile];
  609. _storeFile = nil;
  610. }
  611. - (BOOL)shouldDie
  612. {
  613. if (_filepath) {
  614. if (_filepath.length > 0) {
  615. [[VLCHTTPUploaderController sharedInstance] moveFileFrom:_filepath];
  616. #if TARGET_OS_TV
  617. [_receivedFiles removeObject:_filepath];
  618. #endif
  619. }
  620. }
  621. return [super shouldDie];
  622. }
  623. #pragma mark subtitle
  624. - (NSMutableArray *)_listOfSubtitles
  625. {
  626. NSMutableArray *listOfSubtitles = [[NSMutableArray alloc] init];
  627. NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  628. NSArray *allFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:nil];
  629. NSString *filePath;
  630. NSUInteger count = allFiles.count;
  631. for (NSUInteger i = 0; i < count; i++) {
  632. filePath = [[NSString stringWithFormat:@"%@/%@", documentsDirectory, allFiles[i]] stringByReplacingOccurrencesOfString:@"file://"withString:@""];
  633. if ([filePath isSupportedSubtitleFormat])
  634. [listOfSubtitles addObject:filePath];
  635. }
  636. return listOfSubtitles;
  637. }
  638. - (NSString *)_checkIfSubtitleWasFound:(NSString *)filePath
  639. {
  640. NSString *subtitlePath;
  641. NSString *fileSub;
  642. NSString *currentPath;
  643. NSString *fileName = [[filePath lastPathComponent] stringByDeletingPathExtension];
  644. if (fileName == nil)
  645. return nil;
  646. NSMutableArray *listOfSubtitles = [self _listOfSubtitles];
  647. NSUInteger count = listOfSubtitles.count;
  648. for (NSUInteger i = 0; i < count; i++) {
  649. currentPath = listOfSubtitles[i];
  650. fileSub = [NSString stringWithFormat:@"%@", currentPath];
  651. if ([fileSub rangeOfString:fileName].location != NSNotFound)
  652. subtitlePath = currentPath;
  653. }
  654. return subtitlePath;
  655. }
  656. @end