VIMediaDownloader.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. //
  2. // VIMediaDownloader.m
  3. // VIMediaCacheDemo
  4. //
  5. // Created by Vito on 4/21/16.
  6. // Copyright © 2016 Vito. All rights reserved.
  7. //
  8. #import "VIMediaDownloader.h"
  9. #import "VIContentInfo.h"
  10. #import <MobileCoreServices/MobileCoreServices.h>
  11. #import "VICacheSessionManager.h"
  12. #import "VIMediaCacheWorker.h"
  13. #import "VICacheManager.h"
  14. #import "VICacheAction.h"
  15. #pragma mark - Class: VIURLSessionDelegateObject
  16. @protocol VIURLSessionDelegateObjectDelegate <NSObject>
  17. - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler;
  18. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
  19. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
  20. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error;
  21. @end
  22. static NSInteger kBufferSize = 10 * 1024;
  23. @interface VIURLSessionDelegateObject : NSObject <NSURLSessionDelegate>
  24. - (instancetype)initWithDelegate:(id<VIURLSessionDelegateObjectDelegate>)delegate;
  25. @property (nonatomic, weak) id<VIURLSessionDelegateObjectDelegate> delegate;
  26. @property (nonatomic, strong) NSMutableData *bufferData;
  27. @end
  28. @implementation VIURLSessionDelegateObject
  29. - (instancetype)initWithDelegate:(id<VIURLSessionDelegateObjectDelegate>)delegate {
  30. self = [super init];
  31. if (self) {
  32. _delegate = delegate;
  33. _bufferData = [NSMutableData data];
  34. }
  35. return self;
  36. }
  37. #pragma mark - NSURLSessionDataDelegate
  38. - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{
  39. [self.delegate URLSession:session didReceiveChallenge:challenge completionHandler:completionHandler];
  40. }
  41. - (void)URLSession:(NSURLSession *)session
  42. dataTask:(NSURLSessionDataTask *)dataTask
  43. didReceiveResponse:(NSURLResponse *)response
  44. completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
  45. [self.delegate URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
  46. }
  47. - (void)URLSession:(NSURLSession *)session
  48. dataTask:(NSURLSessionDataTask *)dataTask
  49. didReceiveData:(NSData *)data {
  50. @synchronized (self.bufferData) {
  51. [self.bufferData appendData:data];
  52. if (self.bufferData.length > kBufferSize) {
  53. NSRange chunkRange = NSMakeRange(0, self.bufferData.length);
  54. NSData *chunkData = [self.bufferData subdataWithRange:chunkRange];
  55. [self.bufferData replaceBytesInRange:chunkRange withBytes:NULL length:0];
  56. [self.delegate URLSession:session dataTask:dataTask didReceiveData:chunkData];
  57. }
  58. }
  59. }
  60. - (void)URLSession:(NSURLSession *)session
  61. task:(NSURLSessionDataTask *)task
  62. didCompleteWithError:(nullable NSError *)error {
  63. @synchronized (self.bufferData) {
  64. if (self.bufferData.length > 0 && !error) {
  65. NSRange chunkRange = NSMakeRange(0, self.bufferData.length);
  66. NSData *chunkData = [self.bufferData subdataWithRange:chunkRange];
  67. [self.bufferData replaceBytesInRange:chunkRange withBytes:NULL length:0];
  68. [self.delegate URLSession:session dataTask:task didReceiveData:chunkData];
  69. }
  70. }
  71. [self.delegate URLSession:session task:task didCompleteWithError:error];
  72. }
  73. @end
  74. #pragma mark - Class: VIActionWorker
  75. @class VIActionWorker;
  76. @protocol VIActionWorkerDelegate <NSObject>
  77. - (void)actionWorker:(VIActionWorker *)actionWorker didReceiveResponse:(NSURLResponse *)response;
  78. - (void)actionWorker:(VIActionWorker *)actionWorker didReceiveData:(NSData *)data isLocal:(BOOL)isLocal;
  79. - (void)actionWorker:(VIActionWorker *)actionWorker didFinishWithError:(NSError *)error;
  80. @end
  81. @interface VIActionWorker : NSObject <VIURLSessionDelegateObjectDelegate>
  82. @property (nonatomic, strong) NSMutableArray<VICacheAction *> *actions;
  83. - (instancetype)initWithActions:(NSArray<VICacheAction *> *)actions url:(NSURL *)url cacheWorker:(VIMediaCacheWorker *)cacheWorker;
  84. @property (nonatomic, assign) BOOL canSaveToCache;
  85. @property (nonatomic, weak) id<VIActionWorkerDelegate> delegate;
  86. - (void)start;
  87. - (void)cancel;
  88. @property (nonatomic, getter=isCancelled) BOOL cancelled;
  89. @property (nonatomic, strong) VIMediaCacheWorker *cacheWorker;
  90. @property (nonatomic, strong) NSURL *url;
  91. @property (nonatomic, strong) NSURLSession *session;
  92. @property (nonatomic, strong) VIURLSessionDelegateObject *sessionDelegateObject;
  93. @property (nonatomic, strong) NSURLSessionDataTask *task;
  94. @property (nonatomic) NSInteger startOffset;
  95. @end
  96. @interface VIActionWorker ()
  97. @property (nonatomic) NSTimeInterval notifyTime;
  98. @end
  99. @implementation VIActionWorker
  100. - (void)dealloc {
  101. [self cancel];
  102. }
  103. - (instancetype)initWithActions:(NSArray<VICacheAction *> *)actions url:(NSURL *)url cacheWorker:(VIMediaCacheWorker *)cacheWorker {
  104. self = [super init];
  105. if (self) {
  106. _canSaveToCache = YES;
  107. _actions = [actions mutableCopy];
  108. _cacheWorker = cacheWorker;
  109. _url = url;
  110. }
  111. return self;
  112. }
  113. - (void)start {
  114. [self processActions];
  115. }
  116. - (void)cancel {
  117. if (_session) {
  118. [self.session invalidateAndCancel];
  119. }
  120. self.cancelled = YES;
  121. }
  122. - (VIURLSessionDelegateObject *)sessionDelegateObject {
  123. if (!_sessionDelegateObject) {
  124. _sessionDelegateObject = [[VIURLSessionDelegateObject alloc] initWithDelegate:self];
  125. }
  126. return _sessionDelegateObject;
  127. }
  128. - (NSURLSession *)session {
  129. if (!_session) {
  130. NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
  131. NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self.sessionDelegateObject delegateQueue:[VICacheSessionManager shared].downloadQueue];
  132. _session = session;
  133. }
  134. return _session;
  135. }
  136. - (void)processActions {
  137. if (self.isCancelled) {
  138. return;
  139. }
  140. VICacheAction *action = [self.actions firstObject];
  141. if (!action) {
  142. if ([self.delegate respondsToSelector:@selector(actionWorker:didFinishWithError:)]) {
  143. [self.delegate actionWorker:self didFinishWithError:nil];
  144. }
  145. return;
  146. }
  147. [self.actions removeObjectAtIndex:0];
  148. if (action.actionType == VICacheAtionTypeLocal) {
  149. NSError *error;
  150. NSData *data = [self.cacheWorker cachedDataForRange:action.range error:&error];
  151. if (error) {
  152. if ([self.delegate respondsToSelector:@selector(actionWorker:didFinishWithError:)]) {
  153. [self.delegate actionWorker:self didFinishWithError:error];
  154. }
  155. } else {
  156. if ([self.delegate respondsToSelector:@selector(actionWorker:didReceiveData:isLocal:)]) {
  157. [self.delegate actionWorker:self didReceiveData:data isLocal:YES];
  158. }
  159. [self processActions];
  160. }
  161. } else {
  162. long long fromOffset = action.range.location;
  163. long long endOffset = action.range.location + action.range.length - 1;
  164. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.url];
  165. request.cachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
  166. NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", fromOffset, endOffset];
  167. [request setValue:range forHTTPHeaderField:@"Range"];
  168. self.startOffset = action.range.location;
  169. self.task = [self.session dataTaskWithRequest:request];
  170. [self.task resume];
  171. }
  172. }
  173. - (void)notifyDownloadProgressWithFlush:(BOOL)flush finished:(BOOL)finished {
  174. double currentTime = CFAbsoluteTimeGetCurrent();
  175. double interval = [VICacheManager cacheUpdateNotifyInterval];
  176. if ((self.notifyTime < currentTime - interval) || flush) {
  177. self.notifyTime = currentTime;
  178. VICacheConfiguration *configuration = [self.cacheWorker.cacheConfiguration copy];
  179. [[NSNotificationCenter defaultCenter] postNotificationName:VICacheManagerDidUpdateCacheNotification
  180. object:self
  181. userInfo:@{
  182. VICacheConfigurationKey: configuration,
  183. }];
  184. if (finished && configuration.progress >= 1.0) {
  185. [self notifyDownloadFinishedWithError:nil];
  186. }
  187. }
  188. }
  189. - (void)notifyDownloadFinishedWithError:(NSError *)error {
  190. VICacheConfiguration *configuration = [self.cacheWorker.cacheConfiguration copy];
  191. NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
  192. [userInfo setValue:configuration forKey:VICacheConfigurationKey];
  193. [userInfo setValue:error forKey:VICacheFinishedErrorKey];
  194. [[NSNotificationCenter defaultCenter] postNotificationName:VICacheManagerDidFinishCacheNotification
  195. object:self
  196. userInfo:userInfo];
  197. }
  198. #pragma mark - VIURLSessionDelegateObjectDelegate
  199. - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
  200. NSURLCredential *card = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
  201. completionHandler(NSURLSessionAuthChallengeUseCredential,card);
  202. }
  203. - (void)URLSession:(NSURLSession *)session
  204. dataTask:(NSURLSessionDataTask *)dataTask
  205. didReceiveResponse:(NSURLResponse *)response
  206. completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
  207. NSString *mimeType = response.MIMEType;
  208. // Only download video/audio data
  209. if ([mimeType rangeOfString:@"video/"].location == NSNotFound &&
  210. [mimeType rangeOfString:@"audio/"].location == NSNotFound &&
  211. [mimeType rangeOfString:@"application"].location == NSNotFound) {
  212. completionHandler(NSURLSessionResponseCancel);
  213. } else {
  214. if ([self.delegate respondsToSelector:@selector(actionWorker:didReceiveResponse:)]) {
  215. [self.delegate actionWorker:self didReceiveResponse:response];
  216. }
  217. if (self.canSaveToCache) {
  218. [self.cacheWorker startWritting];
  219. }
  220. completionHandler(NSURLSessionResponseAllow);
  221. }
  222. }
  223. - (void)URLSession:(NSURLSession *)session
  224. dataTask:(NSURLSessionDataTask *)dataTask
  225. didReceiveData:(NSData *)data {
  226. if (self.isCancelled) {
  227. return;
  228. }
  229. if (self.canSaveToCache) {
  230. NSRange range = NSMakeRange(self.startOffset, data.length);
  231. NSError *error;
  232. [self.cacheWorker cacheData:data forRange:range error:&error];
  233. if (error) {
  234. if ([self.delegate respondsToSelector:@selector(actionWorker:didFinishWithError:)]) {
  235. [self.delegate actionWorker:self didFinishWithError:error];
  236. }
  237. return;
  238. }
  239. [self.cacheWorker save];
  240. }
  241. self.startOffset += data.length;
  242. if ([self.delegate respondsToSelector:@selector(actionWorker:didReceiveData:isLocal:)]) {
  243. [self.delegate actionWorker:self didReceiveData:data isLocal:NO];
  244. }
  245. [self notifyDownloadProgressWithFlush:NO finished:NO];
  246. }
  247. - (void)URLSession:(NSURLSession *)session
  248. task:(NSURLSessionTask *)task
  249. didCompleteWithError:(nullable NSError *)error {
  250. if (self.canSaveToCache) {
  251. [self.cacheWorker finishWritting];
  252. [self.cacheWorker save];
  253. }
  254. if (error) {
  255. if ([self.delegate respondsToSelector:@selector(actionWorker:didFinishWithError:)]) {
  256. [self.delegate actionWorker:self didFinishWithError:error];
  257. }
  258. [self notifyDownloadFinishedWithError:error];
  259. } else {
  260. [self notifyDownloadProgressWithFlush:YES finished:YES];
  261. [self processActions];
  262. }
  263. }
  264. @end
  265. #pragma mark - Class: VIMediaDownloaderStatus
  266. @interface VIMediaDownloaderStatus ()
  267. @property (nonatomic, strong) NSMutableSet *downloadingURLS;
  268. @end
  269. @implementation VIMediaDownloaderStatus
  270. + (instancetype)shared {
  271. static VIMediaDownloaderStatus *instance = nil;
  272. static dispatch_once_t onceToken;
  273. dispatch_once(&onceToken, ^{
  274. instance = [[self alloc] init];
  275. instance.downloadingURLS = [NSMutableSet set];
  276. });
  277. return instance;
  278. }
  279. - (void)addURL:(NSURL *)url {
  280. @synchronized (self.downloadingURLS) {
  281. [self.downloadingURLS addObject:url];
  282. }
  283. }
  284. - (void)removeURL:(NSURL *)url {
  285. @synchronized (self.downloadingURLS) {
  286. [self.downloadingURLS removeObject:url];
  287. }
  288. }
  289. - (BOOL)containsURL:(NSURL *)url {
  290. @synchronized (self.downloadingURLS) {
  291. return [self.downloadingURLS containsObject:url];
  292. }
  293. }
  294. - (NSSet *)urls {
  295. return [self.downloadingURLS copy];
  296. }
  297. @end
  298. #pragma mark - Class: VIMediaDownloader
  299. @interface VIMediaDownloader () <VIActionWorkerDelegate>
  300. @property (nonatomic, strong) NSURL *url;
  301. @property (nonatomic, strong) NSURLSessionDataTask *task;
  302. @property (nonatomic, strong) VIMediaCacheWorker *cacheWorker;
  303. @property (nonatomic, strong) VIActionWorker *actionWorker;
  304. @property (nonatomic) BOOL downloadToEnd;
  305. @end
  306. @implementation VIMediaDownloader
  307. - (void)dealloc {
  308. [[VIMediaDownloaderStatus shared] removeURL:self.url];
  309. }
  310. - (instancetype)initWithURL:(NSURL *)url cacheWorker:(VIMediaCacheWorker *)cacheWorker {
  311. self = [super init];
  312. if (self) {
  313. _saveToCache = YES;
  314. _url = url;
  315. _cacheWorker = cacheWorker;
  316. _info = _cacheWorker.cacheConfiguration.contentInfo;
  317. [[VIMediaDownloaderStatus shared] addURL:self.url];
  318. }
  319. return self;
  320. }
  321. - (void)downloadTaskFromOffset:(unsigned long long)fromOffset
  322. length:(NSUInteger)length
  323. toEnd:(BOOL)toEnd {
  324. // ---
  325. NSRange range = NSMakeRange((NSUInteger)fromOffset, length);
  326. if (toEnd) {
  327. range.length = (NSUInteger)self.cacheWorker.cacheConfiguration.contentInfo.contentLength - range.location;
  328. }
  329. NSArray *actions = [self.cacheWorker cachedDataActionsForRange:range];
  330. self.actionWorker = [[VIActionWorker alloc] initWithActions:actions url:self.url cacheWorker:self.cacheWorker];
  331. self.actionWorker.canSaveToCache = self.saveToCache;
  332. self.actionWorker.delegate = self;
  333. [self.actionWorker start];
  334. }
  335. - (void)downloadFromStartToEnd {
  336. // ---
  337. self.downloadToEnd = YES;
  338. NSRange range = NSMakeRange(0, 2);
  339. NSArray *actions = [self.cacheWorker cachedDataActionsForRange:range];
  340. self.actionWorker = [[VIActionWorker alloc] initWithActions:actions url:self.url cacheWorker:self.cacheWorker];
  341. self.actionWorker.canSaveToCache = self.saveToCache;
  342. self.actionWorker.delegate = self;
  343. [self.actionWorker start];
  344. }
  345. - (void)cancel {
  346. self.actionWorker.delegate = nil;
  347. [[VIMediaDownloaderStatus shared] removeURL:self.url];
  348. [self.actionWorker cancel];
  349. self.actionWorker = nil;
  350. }
  351. #pragma mark - VIActionWorkerDelegate
  352. - (void)actionWorker:(VIActionWorker *)actionWorker didReceiveResponse:(NSURLResponse *)response {
  353. if (!self.info) {
  354. VIContentInfo *info = [VIContentInfo new];
  355. if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
  356. NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response;
  357. NSString *acceptRange = HTTPURLResponse.allHeaderFields[@"Accept-Ranges"];
  358. info.byteRangeAccessSupported = [acceptRange isEqualToString:@"bytes"];
  359. info.contentLength = [[[HTTPURLResponse.allHeaderFields[@"Content-Range"] componentsSeparatedByString:@"/"] lastObject] longLongValue];
  360. }
  361. NSString *mimeType = response.MIMEType;
  362. CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL);
  363. info.contentType = CFBridgingRelease(contentType);
  364. self.info = info;
  365. NSError *error;
  366. [self.cacheWorker setContentInfo:info error:&error];
  367. if (error) {
  368. if ([self.delegate respondsToSelector:@selector(mediaDownloader:didFinishedWithError:)]) {
  369. [self.delegate mediaDownloader:self didFinishedWithError:error];
  370. }
  371. return;
  372. }
  373. }
  374. if ([self.delegate respondsToSelector:@selector(mediaDownloader:didReceiveResponse:)]) {
  375. [self.delegate mediaDownloader:self didReceiveResponse:response];
  376. }
  377. }
  378. - (void)actionWorker:(VIActionWorker *)actionWorker didReceiveData:(NSData *)data isLocal:(BOOL)isLocal {
  379. if ([self.delegate respondsToSelector:@selector(mediaDownloader:didReceiveData:)]) {
  380. [self.delegate mediaDownloader:self didReceiveData:data];
  381. }
  382. }
  383. - (void)actionWorker:(VIActionWorker *)actionWorker didFinishWithError:(NSError *)error {
  384. [[VIMediaDownloaderStatus shared] removeURL:self.url];
  385. if (!error && self.downloadToEnd) {
  386. self.downloadToEnd = NO;
  387. [self downloadTaskFromOffset:2 length:(NSUInteger)(self.cacheWorker.cacheConfiguration.contentInfo.contentLength - 2) toEnd:YES];
  388. } else {
  389. if ([self.delegate respondsToSelector:@selector(mediaDownloader:didFinishedWithError:)]) {
  390. [self.delegate mediaDownloader:self didFinishedWithError:error];
  391. }
  392. }
  393. }
  394. @end