VIMediaCacheWorker.m 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. //
  2. // VIMediaCacheWorker.m
  3. // VIMediaCacheDemo
  4. //
  5. // Created by Vito on 4/21/16.
  6. // Copyright © 2016 Vito. All rights reserved.
  7. //
  8. #import "VIMediaCacheWorker.h"
  9. #import "VICacheAction.h"
  10. #import "VICacheManager.h"
  11. @import UIKit;
  12. static NSInteger const kPackageLength = 204800; // 200kb per package
  13. static NSString *kMCMediaCacheResponseKey = @"kMCMediaCacheResponseKey";
  14. static NSString *VIMediaCacheErrorDoamin = @"com.vimediacache";
  15. @interface VIMediaCacheWorker ()
  16. @property (nonatomic, strong) NSFileHandle *readFileHandle;
  17. @property (nonatomic, strong) NSFileHandle *writeFileHandle;
  18. @property (nonatomic, strong, readwrite) NSError *setupError;
  19. @property (nonatomic, copy) NSString *filePath;
  20. @property (nonatomic, strong) VICacheConfiguration *internalCacheConfiguration;
  21. @property (nonatomic) long long currentOffset;
  22. @property (nonatomic, strong) NSDate *startWriteDate;
  23. @property (nonatomic) float writeBytes;
  24. @property (nonatomic) BOOL writting;
  25. @end
  26. @implementation VIMediaCacheWorker
  27. - (void)dealloc {
  28. [[NSNotificationCenter defaultCenter] removeObserver:self];
  29. [self save];
  30. [_readFileHandle closeFile];
  31. [_writeFileHandle closeFile];
  32. }
  33. - (instancetype)initWithURL:(NSURL *)url {
  34. self = [super init];
  35. if (self) {
  36. NSString *path = [VICacheManager cachedFilePathForURL:url];
  37. NSFileManager *fileManager = [NSFileManager defaultManager];
  38. _filePath = path;
  39. NSError *error;
  40. NSString *cacheFolder = [path stringByDeletingLastPathComponent];
  41. if (![fileManager fileExistsAtPath:cacheFolder]) {
  42. [fileManager createDirectoryAtPath:cacheFolder
  43. withIntermediateDirectories:YES
  44. attributes:nil
  45. error:&error];
  46. }
  47. if (!error) {
  48. if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
  49. [[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil];
  50. }
  51. NSURL *fileURL = [NSURL fileURLWithPath:path];
  52. _readFileHandle = [NSFileHandle fileHandleForReadingFromURL:fileURL error:&error];
  53. if (!error) {
  54. _writeFileHandle = [NSFileHandle fileHandleForWritingToURL:fileURL error:&error];
  55. _internalCacheConfiguration = [VICacheConfiguration configurationWithFilePath:path];
  56. _internalCacheConfiguration.url = url;
  57. }
  58. }
  59. _setupError = error;
  60. }
  61. return self;
  62. }
  63. - (VICacheConfiguration *)cacheConfiguration {
  64. return self.internalCacheConfiguration;
  65. }
  66. - (void)cacheData:(NSData *)data forRange:(NSRange)range error:(NSError **)error {
  67. @synchronized(self.writeFileHandle) {
  68. @try {
  69. [self.writeFileHandle seekToFileOffset:range.location];
  70. [self.writeFileHandle writeData:data];
  71. self.writeBytes += data.length;
  72. [self.internalCacheConfiguration addCacheFragment:range];
  73. } @catch (NSException *exception) {
  74. NSLog(@"write to file error");
  75. *error = [NSError errorWithDomain:exception.name code:123 userInfo:@{NSLocalizedDescriptionKey: exception.reason, @"exception": exception}];
  76. }
  77. }
  78. }
  79. - (NSData *)cachedDataForRange:(NSRange)range error:(NSError **)error {
  80. @synchronized(self.readFileHandle) {
  81. @try {
  82. [self.readFileHandle seekToFileOffset:range.location];
  83. NSData *data = [self.readFileHandle readDataOfLength:range.length]; // 空数据也会返回,所以如果 range 错误,会导致播放失效
  84. return data;
  85. } @catch (NSException *exception) {
  86. NSLog(@"read cached data error %@",exception);
  87. *error = [NSError errorWithDomain:exception.name code:123 userInfo:@{NSLocalizedDescriptionKey: exception.reason, @"exception": exception}];
  88. }
  89. }
  90. return nil;
  91. }
  92. - (NSArray<VICacheAction *> *)cachedDataActionsForRange:(NSRange)range {
  93. NSArray *cachedFragments = [self.internalCacheConfiguration cacheFragments];
  94. NSMutableArray *actions = [NSMutableArray array];
  95. if (range.location == NSNotFound) {
  96. return [actions copy];
  97. }
  98. NSInteger endOffset = range.location + range.length;
  99. // Delete header and footer not in range
  100. [cachedFragments enumerateObjectsUsingBlock:^(NSValue * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  101. NSRange fragmentRange = obj.rangeValue;
  102. NSRange intersectionRange = NSIntersectionRange(range, fragmentRange);
  103. if (intersectionRange.length > 0) {
  104. NSInteger package = intersectionRange.length / kPackageLength;
  105. for (NSInteger i = 0; i <= package; i++) {
  106. VICacheAction *action = [VICacheAction new];
  107. action.actionType = VICacheAtionTypeLocal;
  108. NSInteger offset = i * kPackageLength;
  109. NSInteger offsetLocation = intersectionRange.location + offset;
  110. NSInteger maxLocation = intersectionRange.location + intersectionRange.length;
  111. NSInteger length = (offsetLocation + kPackageLength) > maxLocation ? (maxLocation - offsetLocation) : kPackageLength;
  112. action.range = NSMakeRange(offsetLocation, length);
  113. [actions addObject:action];
  114. }
  115. } else if (fragmentRange.location >= endOffset) {
  116. *stop = YES;
  117. }
  118. }];
  119. if (actions.count == 0) {
  120. VICacheAction *action = [VICacheAction new];
  121. action.actionType = VICacheAtionTypeRemote;
  122. action.range = range;
  123. [actions addObject:action];
  124. } else {
  125. // Add remote fragments
  126. NSMutableArray *localRemoteActions = [NSMutableArray array];
  127. [actions enumerateObjectsUsingBlock:^(VICacheAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  128. NSRange actionRange = obj.range;
  129. if (idx == 0) {
  130. if (range.location < actionRange.location) {
  131. VICacheAction *action = [VICacheAction new];
  132. action.actionType = VICacheAtionTypeRemote;
  133. action.range = NSMakeRange(range.location, actionRange.location - range.location);
  134. [localRemoteActions addObject:action];
  135. }
  136. [localRemoteActions addObject:obj];
  137. } else {
  138. VICacheAction *lastAction = [localRemoteActions lastObject];
  139. NSInteger lastOffset = lastAction.range.location + lastAction.range.length;
  140. if (actionRange.location > lastOffset) {
  141. VICacheAction *action = [VICacheAction new];
  142. action.actionType = VICacheAtionTypeRemote;
  143. action.range = NSMakeRange(lastOffset, actionRange.location - lastOffset);
  144. [localRemoteActions addObject:action];
  145. }
  146. [localRemoteActions addObject:obj];
  147. }
  148. if (idx == actions.count - 1) {
  149. NSInteger localEndOffset = actionRange.location + actionRange.length;
  150. if (endOffset > localEndOffset) {
  151. VICacheAction *action = [VICacheAction new];
  152. action.actionType = VICacheAtionTypeRemote;
  153. action.range = NSMakeRange(localEndOffset, endOffset - localEndOffset);
  154. [localRemoteActions addObject:action];
  155. }
  156. }
  157. }];
  158. actions = localRemoteActions;
  159. }
  160. return [actions copy];
  161. }
  162. - (void)setContentInfo:(VIContentInfo *)contentInfo error:(NSError **)error {
  163. self.internalCacheConfiguration.contentInfo = contentInfo;
  164. @try {
  165. [self.writeFileHandle truncateFileAtOffset:contentInfo.contentLength];
  166. [self.writeFileHandle synchronizeFile];
  167. } @catch (NSException *exception) {
  168. NSLog(@"read cached data error %@", exception);
  169. *error = [NSError errorWithDomain:exception.name code:123 userInfo:@{NSLocalizedDescriptionKey: exception.reason, @"exception": exception}];
  170. }
  171. }
  172. - (void)save {
  173. @synchronized (self.writeFileHandle) {
  174. [self.writeFileHandle synchronizeFile];
  175. [self.internalCacheConfiguration save];
  176. }
  177. }
  178. - (void)startWritting {
  179. if (!self.writting) {
  180. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
  181. }
  182. self.writting = YES;
  183. self.startWriteDate = [NSDate date];
  184. self.writeBytes = 0;
  185. }
  186. - (void)finishWritting {
  187. if (self.writting) {
  188. self.writting = NO;
  189. [[NSNotificationCenter defaultCenter] removeObserver:self];
  190. NSTimeInterval time = [[NSDate date] timeIntervalSinceDate:self.startWriteDate];
  191. [self.internalCacheConfiguration addDownloadedBytes:self.writeBytes spent:time];
  192. }
  193. }
  194. #pragma mark - Notification
  195. - (void)applicationDidEnterBackground:(NSNotification *)notification {
  196. [self save];
  197. }
  198. @end