├── .gitignore ├── .swift-version ├── LICENSE ├── README.md ├── VIMediaCache.podspec ├── VIMediaCache ├── Cache │ ├── NSString+VIMD5.h │ ├── NSString+VIMD5.m │ ├── VICacheAction.h │ ├── VICacheAction.m │ ├── VICacheConfiguration.h │ ├── VICacheConfiguration.m │ ├── VICacheManager.h │ ├── VICacheManager.m │ ├── VICacheSessionManager.h │ ├── VICacheSessionManager.m │ ├── VIMediaCacheWorker.h │ └── VIMediaCacheWorker.m ├── ResourceLoader │ ├── VIContentInfo.h │ ├── VIContentInfo.m │ ├── VIMediaDownloader.h │ ├── VIMediaDownloader.m │ ├── VIResourceLoader.h │ ├── VIResourceLoader.m │ ├── VIResourceLoaderManager.h │ ├── VIResourceLoaderManager.m │ ├── VIResourceLoadingRequestWorker.h │ └── VIResourceLoadingRequestWorker.m └── VIMediaCache.h ├── VIMediaCacheDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── VIMediaCacheDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── PlayerView.h ├── PlayerView.m ├── ViewController.h ├── ViewController.m └── main.m └── VIMediaCacheDemoTests ├── Info.plist └── VIMediaCacheDemoTests.m /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/objective-c 3 | 4 | ### Objective-C ### 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData/ 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.moved-aside 26 | *.xcuserstate 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | # CocoaPods 35 | # 36 | # We recommend against adding the Pods directory to your .gitignore. However 37 | # you should judge for yourself, the pros and cons are mentioned at: 38 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 39 | # 40 | # Pods/ 41 | 42 | # Carthage 43 | # 44 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 45 | # Carthage/Checkouts 46 | 47 | Carthage/Build 48 | 49 | # fastlane 50 | # 51 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 52 | # screenshots whenever they are needed. 53 | # For more information about the recommended setup visit: 54 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 55 | 56 | fastlane/report.xml 57 | fastlane/screenshots 58 | 59 | ### Objective-C Patch ### 60 | *.xcscmblueprint 61 | 62 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2016 Vito Zhang 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the “Software”), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VIMediaCache 2 | 3 | [中文说明](https://mp.weixin.qq.com/s/v1sw_Sb8oKeZ8sWyjBUXGA) 4 | 5 | Cache media file while play media using AVPlayerr. 6 | 7 | VIMediaCache use AVAssetResourceLoader to control AVPlayer download media data. 8 | 9 | ### CocoaPods 10 | 11 | `pod 'VIMediaCache'` 12 | 13 | ### Usage 14 | 15 | **Objective C** 16 | 17 | ```Objc 18 | NSURL *url = [NSURL URLWithString:@"https://mvvideo5.meitudata.com/571090934cea5517.mp4"]; 19 | VIResourceLoaderManager *resourceLoaderManager = [VIResourceLoaderManager new]; 20 | self.resourceLoaderManager = resourceLoaderManager; 21 | AVPlayerItem *playerItem = [resourceLoaderManager playerItemWithURL:url]; 22 | AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem]; 23 | ``` 24 | 25 | **Swift** 26 | 27 | ```Swift 28 | let url = URL(string: "https://mvvideo5.meitudata.com/571090934cea5517.mp4") 29 | let resourceLoaderManager = VIResourceLoaderManager() 30 | let playerItem = resourceLoaderManager.playerItem(with: url) 31 | let player = AVPlayer(playerItem: playerItem) 32 | ``` 33 | 34 | ### Contact 35 | 36 | vvitozhang@gmail.com 37 | 38 | ### License 39 | 40 | MIT 41 | -------------------------------------------------------------------------------- /VIMediaCache.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'VIMediaCache' 3 | s.version = '0.4' 4 | s.license = 'MIT' 5 | s.summary = 'VIMediaCache is a tool to cache media file while play media using AVPlayer' 6 | s.homepage = 'https://github.com/vitoziv/VIMediaCache' 7 | s.author = { 'Vito' => 'vvitozhang@gmail.com' } 8 | s.source = { :git => 'https://github.com/vitoziv/VIMediaCache.git', :tag => s.version.to_s } 9 | s.platform = :ios, '9.0' 10 | s.source_files = 'VIMediaCache/*.{h,m}', 'VIMediaCache/**/*.{h,m}' 11 | s.frameworks = 'MobileCoreServices', 'AVFoundation' 12 | s.requires_arc = true 13 | end 14 | 15 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/NSString+VIMD5.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+VIMD5.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 21/11/2017. 6 | // Copyright © 2017 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSString (VIMD5) 12 | - (NSString *)vi_md5; 13 | @end 14 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/NSString+VIMD5.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+VIMD5.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 21/11/2017. 6 | // Copyright © 2017 Vito. All rights reserved. 7 | // 8 | 9 | #import "NSString+VIMD5.h" 10 | #import 11 | 12 | @implementation NSString (VIMD5) 13 | 14 | - (NSString *)vi_md5 { 15 | const char* str = [self UTF8String]; 16 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 17 | CC_MD5(str, (CC_LONG)strlen(str), result); 18 | 19 | NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2]; 20 | for(int i = 0; i 10 | 11 | typedef NS_ENUM(NSUInteger, VICacheAtionType) { 12 | VICacheAtionTypeLocal = 0, 13 | VICacheAtionTypeRemote 14 | }; 15 | 16 | @interface VICacheAction : NSObject 17 | 18 | - (instancetype)initWithActionType:(VICacheAtionType)actionType range:(NSRange)range; 19 | 20 | @property (nonatomic) VICacheAtionType actionType; 21 | @property (nonatomic) NSRange range; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/VICacheAction.m: -------------------------------------------------------------------------------- 1 | // 2 | // VICacheAction.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "VICacheAction.h" 10 | 11 | @implementation VICacheAction 12 | 13 | - (instancetype)initWithActionType:(VICacheAtionType)actionType range:(NSRange)range { 14 | self = [super init]; 15 | if (self) { 16 | _actionType = actionType; 17 | _range = range; 18 | } 19 | return self; 20 | } 21 | 22 | - (BOOL)isEqual:(VICacheAction *)object { 23 | if (!NSEqualRanges(object.range, self.range)) { 24 | return NO; 25 | } 26 | 27 | if (object.actionType != self.actionType) { 28 | return NO; 29 | } 30 | 31 | return YES; 32 | } 33 | 34 | - (NSUInteger)hash { 35 | return [[NSString stringWithFormat:@"%@%@", NSStringFromRange(self.range), @(self.actionType)] hash]; 36 | } 37 | 38 | - (NSString *)description { 39 | return [NSString stringWithFormat:@"actionType %@, range: %@", @(self.actionType), NSStringFromRange(self.range)]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/VICacheConfiguration.h: -------------------------------------------------------------------------------- 1 | // 2 | // VICacheConfiguration.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "VIContentInfo.h" 11 | 12 | @interface VICacheConfiguration : NSObject 13 | 14 | + (NSString *)configurationFilePathForFilePath:(NSString *)filePath; 15 | 16 | + (instancetype)configurationWithFilePath:(NSString *)filePath; 17 | 18 | @property (nonatomic, copy, readonly) NSString *filePath; 19 | @property (nonatomic, strong) VIContentInfo *contentInfo; 20 | @property (nonatomic, strong) NSURL *url; 21 | 22 | - (NSArray *)cacheFragments; 23 | 24 | /** 25 | * cached progress 26 | */ 27 | @property (nonatomic, readonly) float progress; 28 | @property (nonatomic, readonly) long long downloadedBytes; 29 | @property (nonatomic, readonly) float downloadSpeed; // kb/s 30 | 31 | #pragma mark - update API 32 | 33 | - (void)save; 34 | - (void)addCacheFragment:(NSRange)fragment; 35 | 36 | /** 37 | * Record the download speed 38 | */ 39 | - (void)addDownloadedBytes:(long long)bytes spent:(NSTimeInterval)time; 40 | 41 | @end 42 | 43 | @interface VICacheConfiguration (VIConvenient) 44 | 45 | + (BOOL)createAndSaveDownloadedConfigurationForURL:(NSURL *)url error:(NSError **)error; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/VICacheConfiguration.m: -------------------------------------------------------------------------------- 1 | // 2 | // VICacheConfiguration.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "VICacheConfiguration.h" 10 | #import "VICacheManager.h" 11 | #import 12 | 13 | static NSString *kFileNameKey = @"kFileNameKey"; 14 | static NSString *kCacheFragmentsKey = @"kCacheFragmentsKey"; 15 | static NSString *kDownloadInfoKey = @"kDownloadInfoKey"; 16 | static NSString *kContentInfoKey = @"kContentInfoKey"; 17 | static NSString *kURLKey = @"kURLKey"; 18 | 19 | @interface VICacheConfiguration () 20 | 21 | @property (nonatomic, copy) NSString *filePath; 22 | @property (nonatomic, copy) NSString *fileName; 23 | @property (nonatomic, copy) NSArray *internalCacheFragments; 24 | @property (nonatomic, copy) NSArray *downloadInfo; 25 | 26 | @end 27 | 28 | @implementation VICacheConfiguration 29 | 30 | + (instancetype)configurationWithFilePath:(NSString *)filePath { 31 | filePath = [self configurationFilePathForFilePath:filePath]; 32 | VICacheConfiguration *configuration = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; 33 | 34 | if (!configuration) { 35 | configuration = [[VICacheConfiguration alloc] init]; 36 | configuration.fileName = [filePath lastPathComponent]; 37 | } 38 | configuration.filePath = filePath; 39 | 40 | return configuration; 41 | } 42 | 43 | + (NSString *)configurationFilePathForFilePath:(NSString *)filePath { 44 | return [filePath stringByAppendingPathExtension:@"mt_cfg"]; 45 | } 46 | 47 | - (NSArray *)internalCacheFragments { 48 | if (!_internalCacheFragments) { 49 | _internalCacheFragments = [NSArray array]; 50 | } 51 | return _internalCacheFragments; 52 | } 53 | 54 | - (NSArray *)downloadInfo { 55 | if (!_downloadInfo) { 56 | _downloadInfo = [NSArray array]; 57 | } 58 | return _downloadInfo; 59 | } 60 | 61 | - (NSArray *)cacheFragments { 62 | return [_internalCacheFragments copy]; 63 | } 64 | 65 | - (float)progress { 66 | float progress = self.downloadedBytes / (float)self.contentInfo.contentLength; 67 | return progress; 68 | } 69 | 70 | - (long long)downloadedBytes { 71 | float bytes = 0; 72 | @synchronized (self.internalCacheFragments) { 73 | for (NSValue *range in self.internalCacheFragments) { 74 | bytes += range.rangeValue.length; 75 | } 76 | } 77 | return bytes; 78 | } 79 | 80 | - (float)downloadSpeed { 81 | long long bytes = 0; 82 | NSTimeInterval time = 0; 83 | @synchronized (self.downloadInfo) { 84 | for (NSArray *a in self.downloadInfo) { 85 | bytes += [[a firstObject] longLongValue]; 86 | time += [[a lastObject] doubleValue]; 87 | } 88 | } 89 | return bytes / 1024.0 / time; 90 | } 91 | 92 | #pragma mark - NSCoding 93 | 94 | - (void)encodeWithCoder:(NSCoder *)aCoder { 95 | [aCoder encodeObject:self.fileName forKey:kFileNameKey]; 96 | [aCoder encodeObject:self.internalCacheFragments forKey:kCacheFragmentsKey]; 97 | [aCoder encodeObject:self.downloadInfo forKey:kDownloadInfoKey]; 98 | [aCoder encodeObject:self.contentInfo forKey:kContentInfoKey]; 99 | [aCoder encodeObject:self.url forKey:kURLKey]; 100 | } 101 | 102 | - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { 103 | self = [super init]; 104 | if (self) { 105 | _fileName = [aDecoder decodeObjectForKey:kFileNameKey]; 106 | _internalCacheFragments = [[aDecoder decodeObjectForKey:kCacheFragmentsKey] mutableCopy]; 107 | if (!_internalCacheFragments) { 108 | _internalCacheFragments = [NSArray array]; 109 | } 110 | _downloadInfo = [aDecoder decodeObjectForKey:kDownloadInfoKey]; 111 | _contentInfo = [aDecoder decodeObjectForKey:kContentInfoKey]; 112 | _url = [aDecoder decodeObjectForKey:kURLKey]; 113 | } 114 | return self; 115 | } 116 | 117 | #pragma mark - NSCopying 118 | 119 | - (id)copyWithZone:(nullable NSZone *)zone { 120 | VICacheConfiguration *configuration = [[VICacheConfiguration allocWithZone:zone] init]; 121 | configuration.fileName = self.fileName; 122 | configuration.filePath = self.filePath; 123 | configuration.internalCacheFragments = self.internalCacheFragments; 124 | configuration.downloadInfo = self.downloadInfo; 125 | configuration.url = self.url; 126 | configuration.contentInfo = self.contentInfo; 127 | 128 | return configuration; 129 | } 130 | 131 | #pragma mark - Update 132 | 133 | - (void)save { 134 | if ([NSThread isMainThread]) { 135 | // Called in main thread when VIMediaCacheWorker dealloc 136 | [self doDelaySaveAction]; 137 | } else { 138 | // Called in NSOperation which is dipatched by NSOperationQueue ("com.vimediacache.download") 139 | // After 1.0 second delay, the NSOperation will destory and the selector will never execute. 140 | // So dispatch in main queue. 141 | dispatch_async(dispatch_get_main_queue(), ^{ 142 | [self doDelaySaveAction]; 143 | }); 144 | } 145 | } 146 | 147 | - (void)doDelaySaveAction 148 | { 149 | [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(archiveData) object:nil]; 150 | [self performSelector:@selector(archiveData) withObject:nil afterDelay:1.0]; 151 | } 152 | 153 | - (void)archiveData { 154 | @synchronized (self.internalCacheFragments) { 155 | [NSKeyedArchiver archiveRootObject:self toFile:self.filePath]; 156 | } 157 | } 158 | 159 | - (void)addCacheFragment:(NSRange)fragment { 160 | if (fragment.location == NSNotFound || fragment.length == 0) { 161 | return; 162 | } 163 | 164 | @synchronized (self.internalCacheFragments) { 165 | NSMutableArray *internalCacheFragments = [self.internalCacheFragments mutableCopy]; 166 | 167 | NSValue *fragmentValue = [NSValue valueWithRange:fragment]; 168 | NSInteger count = self.internalCacheFragments.count; 169 | if (count == 0) { 170 | [internalCacheFragments addObject:fragmentValue]; 171 | } else { 172 | NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; 173 | [internalCacheFragments enumerateObjectsUsingBlock:^(NSValue * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 174 | NSRange range = obj.rangeValue; 175 | if ((fragment.location + fragment.length) <= range.location) { 176 | if (indexSet.count == 0) { 177 | [indexSet addIndex:idx]; 178 | } 179 | *stop = YES; 180 | } else if (fragment.location <= (range.location + range.length) && (fragment.location + fragment.length) > range.location) { 181 | [indexSet addIndex:idx]; 182 | } else if (fragment.location >= range.location + range.length) { 183 | if (idx == count - 1) { // Append to last index 184 | [indexSet addIndex:idx]; 185 | } 186 | } 187 | }]; 188 | 189 | if (indexSet.count > 1) { 190 | NSRange firstRange = self.internalCacheFragments[indexSet.firstIndex].rangeValue; 191 | NSRange lastRange = self.internalCacheFragments[indexSet.lastIndex].rangeValue; 192 | NSInteger location = MIN(firstRange.location, fragment.location); 193 | NSInteger endOffset = MAX(lastRange.location + lastRange.length, fragment.location + fragment.length); 194 | NSRange combineRange = NSMakeRange(location, endOffset - location); 195 | [internalCacheFragments removeObjectsAtIndexes:indexSet]; 196 | [internalCacheFragments insertObject:[NSValue valueWithRange:combineRange] atIndex:indexSet.firstIndex]; 197 | } else if (indexSet.count == 1) { 198 | NSRange firstRange = self.internalCacheFragments[indexSet.firstIndex].rangeValue; 199 | 200 | NSRange expandFirstRange = NSMakeRange(firstRange.location, firstRange.length + 1); 201 | NSRange expandFragmentRange = NSMakeRange(fragment.location, fragment.length + 1); 202 | NSRange intersectionRange = NSIntersectionRange(expandFirstRange, expandFragmentRange); 203 | if (intersectionRange.length > 0) { // Should combine 204 | NSInteger location = MIN(firstRange.location, fragment.location); 205 | NSInteger endOffset = MAX(firstRange.location + firstRange.length, fragment.location + fragment.length); 206 | NSRange combineRange = NSMakeRange(location, endOffset - location); 207 | [internalCacheFragments removeObjectAtIndex:indexSet.firstIndex]; 208 | [internalCacheFragments insertObject:[NSValue valueWithRange:combineRange] atIndex:indexSet.firstIndex]; 209 | } else { 210 | if (firstRange.location > fragment.location) { 211 | [internalCacheFragments insertObject:fragmentValue atIndex:[indexSet lastIndex]]; 212 | } else { 213 | [internalCacheFragments insertObject:fragmentValue atIndex:[indexSet lastIndex] + 1]; 214 | } 215 | } 216 | } 217 | } 218 | 219 | self.internalCacheFragments = [internalCacheFragments copy]; 220 | } 221 | } 222 | 223 | - (void)addDownloadedBytes:(long long)bytes spent:(NSTimeInterval)time { 224 | @synchronized (self.downloadInfo) { 225 | self.downloadInfo = [self.downloadInfo arrayByAddingObject:@[@(bytes), @(time)]]; 226 | } 227 | } 228 | 229 | @end 230 | 231 | @implementation VICacheConfiguration (VIConvenient) 232 | 233 | + (BOOL)createAndSaveDownloadedConfigurationForURL:(NSURL *)url error:(NSError **)error { 234 | NSString *filePath = [VICacheManager cachedFilePathForURL:url]; 235 | NSFileManager *fileManager = [NSFileManager defaultManager]; 236 | NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:error]; 237 | if (!attributes) { 238 | return NO; 239 | } 240 | 241 | NSUInteger fileSize = (NSUInteger)attributes.fileSize; 242 | NSRange range = NSMakeRange(0, fileSize); 243 | 244 | VICacheConfiguration *configuration = [VICacheConfiguration configurationWithFilePath:filePath]; 245 | configuration.url = url; 246 | 247 | VIContentInfo *contentInfo = [VIContentInfo new]; 248 | 249 | NSString *fileExtension = [url pathExtension]; 250 | NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL); 251 | NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); 252 | if (!contentType) { 253 | contentType = @"application/octet-stream"; 254 | } 255 | contentInfo.contentType = contentType; 256 | 257 | contentInfo.contentLength = fileSize; 258 | contentInfo.byteRangeAccessSupported = YES; 259 | contentInfo.downloadedContentLength = fileSize; 260 | configuration.contentInfo = contentInfo; 261 | 262 | [configuration addCacheFragment:range]; 263 | [configuration save]; 264 | 265 | return YES; 266 | } 267 | 268 | @end 269 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/VICacheManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // VICacheManager.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "VICacheConfiguration.h" 11 | 12 | extern NSString *VICacheManagerDidUpdateCacheNotification; 13 | extern NSString *VICacheManagerDidFinishCacheNotification; 14 | 15 | extern NSString *VICacheConfigurationKey; 16 | extern NSString *VICacheFinishedErrorKey; 17 | 18 | @interface VICacheManager : NSObject 19 | 20 | + (void)setCacheDirectory:(NSString *)cacheDirectory; 21 | + (NSString *)cacheDirectory; 22 | 23 | 24 | /** 25 | How often trigger `VICacheManagerDidUpdateCacheNotification` notification 26 | 27 | @param interval Minimum interval 28 | */ 29 | + (void)setCacheUpdateNotifyInterval:(NSTimeInterval)interval; 30 | + (NSTimeInterval)cacheUpdateNotifyInterval; 31 | 32 | + (NSString *)cachedFilePathForURL:(NSURL *)url; 33 | + (VICacheConfiguration *)cacheConfigurationForURL:(NSURL *)url; 34 | 35 | + (void)setFileNameRules:(NSString *(^)(NSURL *url))rules; 36 | 37 | 38 | /** 39 | Calculate cached files size 40 | 41 | @param error If error not empty, calculate failed 42 | @return files size, respresent by `byte`, if error occurs, return -1 43 | */ 44 | + (unsigned long long)calculateCachedSizeWithError:(NSError **)error; 45 | + (void)cleanAllCacheWithError:(NSError **)error; 46 | + (void)cleanCacheForURL:(NSURL *)url error:(NSError **)error; 47 | 48 | 49 | /** 50 | Useful when you upload a local file to the server 51 | 52 | @param filePath local file path 53 | @param url remote resource url 54 | @param error On input, a pointer to an error object. If an error occurs, this pointer is set to an actual error object containing the error information. 55 | */ 56 | + (BOOL)addCacheFile:(NSString *)filePath forURL:(NSURL *)url error:(NSError **)error; 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/VICacheManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // VICacheManager.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "VICacheManager.h" 10 | #import "VIMediaDownloader.h" 11 | #import "NSString+VIMD5.h" 12 | 13 | NSString *VICacheManagerDidUpdateCacheNotification = @"VICacheManagerDidUpdateCacheNotification"; 14 | NSString *VICacheManagerDidFinishCacheNotification = @"VICacheManagerDidFinishCacheNotification"; 15 | 16 | NSString *VICacheConfigurationKey = @"VICacheConfigurationKey"; 17 | NSString *VICacheFinishedErrorKey = @"VICacheFinishedErrorKey"; 18 | 19 | static NSString *kMCMediaCacheDirectory; 20 | static NSTimeInterval kMCMediaCacheNotifyInterval; 21 | static NSString *(^kMCFileNameRules)(NSURL *url); 22 | 23 | @implementation VICacheManager 24 | 25 | + (void)load { 26 | static dispatch_once_t onceToken; 27 | dispatch_once(&onceToken, ^{ 28 | [self setCacheDirectory:[NSTemporaryDirectory() stringByAppendingPathComponent:@"vimedia"]]; 29 | [self setCacheUpdateNotifyInterval:0.1]; 30 | }); 31 | } 32 | 33 | + (void)setCacheDirectory:(NSString *)cacheDirectory { 34 | kMCMediaCacheDirectory = cacheDirectory; 35 | } 36 | 37 | + (NSString *)cacheDirectory { 38 | return kMCMediaCacheDirectory; 39 | } 40 | 41 | + (void)setCacheUpdateNotifyInterval:(NSTimeInterval)interval { 42 | kMCMediaCacheNotifyInterval = interval; 43 | } 44 | 45 | + (NSTimeInterval)cacheUpdateNotifyInterval { 46 | return kMCMediaCacheNotifyInterval; 47 | } 48 | 49 | + (void)setFileNameRules:(NSString *(^)(NSURL *url))rules { 50 | kMCFileNameRules = rules; 51 | } 52 | 53 | + (NSString *)cachedFilePathForURL:(NSURL *)url { 54 | NSString *pathComponent = nil; 55 | if (kMCFileNameRules) { 56 | pathComponent = kMCFileNameRules(url); 57 | } else { 58 | pathComponent = [url.absoluteString vi_md5]; 59 | pathComponent = [pathComponent stringByAppendingPathExtension:url.pathExtension]; 60 | } 61 | return [[self cacheDirectory] stringByAppendingPathComponent:pathComponent]; 62 | } 63 | 64 | + (VICacheConfiguration *)cacheConfigurationForURL:(NSURL *)url { 65 | NSString *filePath = [self cachedFilePathForURL:url]; 66 | VICacheConfiguration *configuration = [VICacheConfiguration configurationWithFilePath:filePath]; 67 | return configuration; 68 | } 69 | 70 | + (unsigned long long)calculateCachedSizeWithError:(NSError **)error { 71 | NSFileManager *fileManager = [NSFileManager defaultManager]; 72 | NSString *cacheDirectory = [self cacheDirectory]; 73 | NSArray *files = [fileManager contentsOfDirectoryAtPath:cacheDirectory error:error]; 74 | unsigned long long size = 0; 75 | if (files) { 76 | for (NSString *path in files) { 77 | NSString *filePath = [cacheDirectory stringByAppendingPathComponent:path]; 78 | NSDictionary *attribute = [fileManager attributesOfItemAtPath:filePath error:error]; 79 | if (!attribute) { 80 | size = -1; 81 | break; 82 | } 83 | 84 | size += [attribute fileSize]; 85 | } 86 | } 87 | return size; 88 | } 89 | 90 | + (void)cleanAllCacheWithError:(NSError **)error { 91 | // Find downloaing file 92 | NSMutableSet *downloadingFiles = [NSMutableSet set]; 93 | [[[VIMediaDownloaderStatus shared] urls] enumerateObjectsUsingBlock:^(NSURL * _Nonnull obj, BOOL * _Nonnull stop) { 94 | NSString *file = [self cachedFilePathForURL:obj]; 95 | [downloadingFiles addObject:file]; 96 | NSString *configurationPath = [VICacheConfiguration configurationFilePathForFilePath:file]; 97 | [downloadingFiles addObject:configurationPath]; 98 | }]; 99 | 100 | // Remove files 101 | NSFileManager *fileManager = [NSFileManager defaultManager]; 102 | NSString *cacheDirectory = [self cacheDirectory]; 103 | 104 | NSArray *files = [fileManager contentsOfDirectoryAtPath:cacheDirectory error:error]; 105 | if (files) { 106 | for (NSString *path in files) { 107 | NSString *filePath = [cacheDirectory stringByAppendingPathComponent:path]; 108 | if ([downloadingFiles containsObject:filePath]) { 109 | continue; 110 | } 111 | if (![fileManager removeItemAtPath:filePath error:error]) { 112 | break; 113 | } 114 | } 115 | } 116 | } 117 | 118 | + (void)cleanCacheForURL:(NSURL *)url error:(NSError **)error { 119 | if ([[VIMediaDownloaderStatus shared] containsURL:url]) { 120 | NSString *description = [NSString stringWithFormat:NSLocalizedString(@"Clean cache for url `%@` can't be done, because it's downloading", nil), url]; 121 | if (error) { 122 | *error = [NSError errorWithDomain:@"com.mediadownload" code:2 userInfo:@{NSLocalizedDescriptionKey: description}]; 123 | } 124 | return; 125 | } 126 | 127 | NSFileManager *fileManager = [NSFileManager defaultManager]; 128 | NSString *filePath = [self cachedFilePathForURL:url]; 129 | 130 | if ([fileManager fileExistsAtPath:filePath]) { 131 | if (![fileManager removeItemAtPath:filePath error:error]) { 132 | return; 133 | } 134 | } 135 | 136 | NSString *configurationPath = [VICacheConfiguration configurationFilePathForFilePath:filePath]; 137 | if ([fileManager fileExistsAtPath:configurationPath]) { 138 | if (![fileManager removeItemAtPath:configurationPath error:error]) { 139 | return; 140 | } 141 | } 142 | } 143 | 144 | + (BOOL)addCacheFile:(NSString *)filePath forURL:(NSURL *)url error:(NSError **)error { 145 | NSFileManager *fileManager = [NSFileManager defaultManager]; 146 | 147 | NSString *cachePath = [VICacheManager cachedFilePathForURL:url]; 148 | NSString *cacheFolder = [cachePath stringByDeletingLastPathComponent]; 149 | if (![fileManager fileExistsAtPath:cacheFolder]) { 150 | if (![fileManager createDirectoryAtPath:cacheFolder 151 | withIntermediateDirectories:YES 152 | attributes:nil 153 | error:error]) { 154 | return NO; 155 | } 156 | } 157 | 158 | if (![fileManager copyItemAtPath:filePath toPath:cachePath error:error]) { 159 | return NO; 160 | } 161 | 162 | if (![VICacheConfiguration createAndSaveDownloadedConfigurationForURL:url error:error]) { 163 | [fileManager removeItemAtPath:cachePath error:nil]; // if remove failed, there is nothing we can do. 164 | return NO; 165 | } 166 | 167 | return YES; 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/VICacheSessionManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // VICacheSessionManager.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface VICacheSessionManager : NSObject 12 | 13 | @property (nonatomic, strong, readonly) NSOperationQueue *downloadQueue; 14 | 15 | + (instancetype)shared; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/VICacheSessionManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // VICacheSessionManager.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "VICacheSessionManager.h" 10 | 11 | @interface VICacheSessionManager () 12 | 13 | @property (nonatomic, strong) NSOperationQueue *downloadQueue; 14 | 15 | @end 16 | 17 | @implementation VICacheSessionManager 18 | 19 | + (instancetype)shared { 20 | static id instance = nil; 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | instance = [[self alloc] init]; 24 | }); 25 | 26 | return instance; 27 | } 28 | 29 | - (instancetype)init { 30 | self = [super init]; 31 | if (self) { 32 | NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 33 | queue.name = @"com.vimediacache.download"; 34 | _downloadQueue = queue; 35 | } 36 | return self; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/VIMediaCacheWorker.h: -------------------------------------------------------------------------------- 1 | // 2 | // VIMediaCacheWorker.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "VICacheConfiguration.h" 11 | 12 | @class VICacheAction; 13 | 14 | @interface VIMediaCacheWorker : NSObject 15 | 16 | - (instancetype)initWithURL:(NSURL *)url; 17 | 18 | @property (nonatomic, strong, readonly) VICacheConfiguration *cacheConfiguration; 19 | @property (nonatomic, strong, readonly) NSError *setupError; // Create fileHandler error, can't save/use cache 20 | 21 | - (void)cacheData:(NSData *)data forRange:(NSRange)range error:(NSError **)error; 22 | - (NSArray *)cachedDataActionsForRange:(NSRange)range; 23 | - (NSData *)cachedDataForRange:(NSRange)range error:(NSError **)error; 24 | 25 | - (void)setContentInfo:(VIContentInfo *)contentInfo error:(NSError **)error; 26 | 27 | - (void)save; 28 | 29 | - (void)startWritting; 30 | - (void)finishWritting; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /VIMediaCache/Cache/VIMediaCacheWorker.m: -------------------------------------------------------------------------------- 1 | // 2 | // VIMediaCacheWorker.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "VIMediaCacheWorker.h" 10 | #import "VICacheAction.h" 11 | #import "VICacheManager.h" 12 | 13 | @import UIKit; 14 | 15 | static NSInteger const kPackageLength = 512 * 1024; // 512 kb per package 16 | static NSString *kMCMediaCacheResponseKey = @"kMCMediaCacheResponseKey"; 17 | static NSString *VIMediaCacheErrorDoamin = @"com.vimediacache"; 18 | 19 | @interface VIMediaCacheWorker () 20 | 21 | @property (nonatomic, strong) NSFileHandle *readFileHandle; 22 | @property (nonatomic, strong) NSFileHandle *writeFileHandle; 23 | @property (nonatomic, strong, readwrite) NSError *setupError; 24 | @property (nonatomic, copy) NSString *filePath; 25 | @property (nonatomic, strong) VICacheConfiguration *internalCacheConfiguration; 26 | 27 | @property (nonatomic) long long currentOffset; 28 | 29 | @property (nonatomic, strong) NSDate *startWriteDate; 30 | @property (nonatomic) float writeBytes; 31 | @property (nonatomic) BOOL writting; 32 | 33 | @end 34 | 35 | @implementation VIMediaCacheWorker 36 | 37 | - (void)dealloc { 38 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 39 | [self save]; 40 | [_readFileHandle closeFile]; 41 | [_writeFileHandle closeFile]; 42 | } 43 | 44 | - (instancetype)initWithURL:(NSURL *)url { 45 | self = [super init]; 46 | if (self) { 47 | NSString *path = [VICacheManager cachedFilePathForURL:url]; 48 | NSFileManager *fileManager = [NSFileManager defaultManager]; 49 | _filePath = path; 50 | NSError *error; 51 | NSString *cacheFolder = [path stringByDeletingLastPathComponent]; 52 | if (![fileManager fileExistsAtPath:cacheFolder]) { 53 | [fileManager createDirectoryAtPath:cacheFolder 54 | withIntermediateDirectories:YES 55 | attributes:nil 56 | error:&error]; 57 | } 58 | 59 | if (!error) { 60 | if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { 61 | [[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil]; 62 | } 63 | NSURL *fileURL = [NSURL fileURLWithPath:path]; 64 | _readFileHandle = [NSFileHandle fileHandleForReadingFromURL:fileURL error:&error]; 65 | if (!error) { 66 | _writeFileHandle = [NSFileHandle fileHandleForWritingToURL:fileURL error:&error]; 67 | _internalCacheConfiguration = [VICacheConfiguration configurationWithFilePath:path]; 68 | _internalCacheConfiguration.url = url; 69 | } 70 | } 71 | 72 | _setupError = error; 73 | } 74 | return self; 75 | } 76 | 77 | - (VICacheConfiguration *)cacheConfiguration { 78 | return self.internalCacheConfiguration; 79 | } 80 | 81 | - (void)cacheData:(NSData *)data forRange:(NSRange)range error:(NSError **)error { 82 | @synchronized(self.writeFileHandle) { 83 | @try { 84 | [self.writeFileHandle seekToFileOffset:range.location]; 85 | [self.writeFileHandle writeData:data]; 86 | self.writeBytes += data.length; 87 | [self.internalCacheConfiguration addCacheFragment:range]; 88 | } @catch (NSException *exception) { 89 | NSLog(@"write to file error"); 90 | *error = [NSError errorWithDomain:exception.name code:123 userInfo:@{NSLocalizedDescriptionKey: exception.reason, @"exception": exception}]; 91 | } 92 | } 93 | } 94 | 95 | - (NSData *)cachedDataForRange:(NSRange)range error:(NSError **)error { 96 | @synchronized(self.readFileHandle) { 97 | @try { 98 | [self.readFileHandle seekToFileOffset:range.location]; 99 | NSData *data = [self.readFileHandle readDataOfLength:range.length]; // 空数据也会返回,所以如果 range 错误,会导致播放失效 100 | return data; 101 | } @catch (NSException *exception) { 102 | NSLog(@"read cached data error %@",exception); 103 | *error = [NSError errorWithDomain:exception.name code:123 userInfo:@{NSLocalizedDescriptionKey: exception.reason, @"exception": exception}]; 104 | } 105 | } 106 | return nil; 107 | } 108 | 109 | - (NSArray *)cachedDataActionsForRange:(NSRange)range { 110 | NSArray *cachedFragments = [self.internalCacheConfiguration cacheFragments]; 111 | NSMutableArray *actions = [NSMutableArray array]; 112 | 113 | if (range.location == NSNotFound) { 114 | return [actions copy]; 115 | } 116 | NSInteger endOffset = range.location + range.length; 117 | // Delete header and footer not in range 118 | [cachedFragments enumerateObjectsUsingBlock:^(NSValue * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 119 | NSRange fragmentRange = obj.rangeValue; 120 | NSRange intersectionRange = NSIntersectionRange(range, fragmentRange); 121 | if (intersectionRange.length > 0) { 122 | NSInteger package = intersectionRange.length / kPackageLength; 123 | for (NSInteger i = 0; i <= package; i++) { 124 | VICacheAction *action = [VICacheAction new]; 125 | action.actionType = VICacheAtionTypeLocal; 126 | 127 | NSInteger offset = i * kPackageLength; 128 | NSInteger offsetLocation = intersectionRange.location + offset; 129 | NSInteger maxLocation = intersectionRange.location + intersectionRange.length; 130 | NSInteger length = (offsetLocation + kPackageLength) > maxLocation ? (maxLocation - offsetLocation) : kPackageLength; 131 | action.range = NSMakeRange(offsetLocation, length); 132 | 133 | [actions addObject:action]; 134 | } 135 | } else if (fragmentRange.location >= endOffset) { 136 | *stop = YES; 137 | } 138 | }]; 139 | 140 | if (actions.count == 0) { 141 | VICacheAction *action = [VICacheAction new]; 142 | action.actionType = VICacheAtionTypeRemote; 143 | action.range = range; 144 | [actions addObject:action]; 145 | } else { 146 | // Add remote fragments 147 | NSMutableArray *localRemoteActions = [NSMutableArray array]; 148 | [actions enumerateObjectsUsingBlock:^(VICacheAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 149 | NSRange actionRange = obj.range; 150 | if (idx == 0) { 151 | if (range.location < actionRange.location) { 152 | VICacheAction *action = [VICacheAction new]; 153 | action.actionType = VICacheAtionTypeRemote; 154 | action.range = NSMakeRange(range.location, actionRange.location - range.location); 155 | [localRemoteActions addObject:action]; 156 | } 157 | [localRemoteActions addObject:obj]; 158 | } else { 159 | VICacheAction *lastAction = [localRemoteActions lastObject]; 160 | NSInteger lastOffset = lastAction.range.location + lastAction.range.length; 161 | if (actionRange.location > lastOffset) { 162 | VICacheAction *action = [VICacheAction new]; 163 | action.actionType = VICacheAtionTypeRemote; 164 | action.range = NSMakeRange(lastOffset, actionRange.location - lastOffset); 165 | [localRemoteActions addObject:action]; 166 | } 167 | [localRemoteActions addObject:obj]; 168 | } 169 | 170 | if (idx == actions.count - 1) { 171 | NSInteger localEndOffset = actionRange.location + actionRange.length; 172 | if (endOffset > localEndOffset) { 173 | VICacheAction *action = [VICacheAction new]; 174 | action.actionType = VICacheAtionTypeRemote; 175 | action.range = NSMakeRange(localEndOffset, endOffset - localEndOffset); 176 | [localRemoteActions addObject:action]; 177 | } 178 | } 179 | }]; 180 | 181 | actions = localRemoteActions; 182 | } 183 | 184 | return [actions copy]; 185 | } 186 | 187 | - (void)setContentInfo:(VIContentInfo *)contentInfo error:(NSError **)error { 188 | self.internalCacheConfiguration.contentInfo = contentInfo; 189 | @try { 190 | [self.writeFileHandle truncateFileAtOffset:contentInfo.contentLength]; 191 | [self.writeFileHandle synchronizeFile]; 192 | } @catch (NSException *exception) { 193 | NSLog(@"read cached data error %@", exception); 194 | *error = [NSError errorWithDomain:exception.name code:123 userInfo:@{NSLocalizedDescriptionKey: exception.reason, @"exception": exception}]; 195 | } 196 | } 197 | 198 | - (void)save { 199 | @synchronized (self.writeFileHandle) { 200 | [self.writeFileHandle synchronizeFile]; 201 | [self.internalCacheConfiguration save]; 202 | } 203 | } 204 | 205 | - (void)startWritting { 206 | if (!self.writting) { 207 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; 208 | } 209 | self.writting = YES; 210 | self.startWriteDate = [NSDate date]; 211 | self.writeBytes = 0; 212 | } 213 | 214 | - (void)finishWritting { 215 | if (self.writting) { 216 | self.writting = NO; 217 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 218 | NSTimeInterval time = [[NSDate date] timeIntervalSinceDate:self.startWriteDate]; 219 | [self.internalCacheConfiguration addDownloadedBytes:self.writeBytes spent:time]; 220 | } 221 | } 222 | 223 | #pragma mark - Notification 224 | 225 | - (void)applicationDidEnterBackground:(NSNotification *)notification { 226 | [self save]; 227 | } 228 | 229 | @end 230 | -------------------------------------------------------------------------------- /VIMediaCache/ResourceLoader/VIContentInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // VIContentInfo.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface VIContentInfo : NSObject 12 | 13 | @property (nonatomic, copy) NSString *contentType; 14 | @property (nonatomic, assign) BOOL byteRangeAccessSupported; 15 | @property (nonatomic, assign) unsigned long long contentLength; 16 | @property (nonatomic) unsigned long long downloadedContentLength; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /VIMediaCache/ResourceLoader/VIContentInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // VIContentInfo.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "VIContentInfo.h" 10 | 11 | static NSString *kContentLengthKey = @"kContentLengthKey"; 12 | static NSString *kContentTypeKey = @"kContentTypeKey"; 13 | static NSString *kByteRangeAccessSupported = @"kByteRangeAccessSupported"; 14 | 15 | @implementation VIContentInfo 16 | 17 | - (NSString *)debugDescription { 18 | return [NSString stringWithFormat:@"%@\ncontentLength: %lld\ncontentType: %@\nbyteRangeAccessSupported:%@", NSStringFromClass([self class]), self.contentLength, self.contentType, @(self.byteRangeAccessSupported)]; 19 | } 20 | 21 | - (void)encodeWithCoder:(NSCoder *)aCoder { 22 | [aCoder encodeObject:@(self.contentLength) forKey:kContentLengthKey]; 23 | [aCoder encodeObject:self.contentType forKey:kContentTypeKey]; 24 | [aCoder encodeObject:@(self.byteRangeAccessSupported) forKey:kByteRangeAccessSupported]; 25 | } 26 | 27 | - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { 28 | self = [super init]; 29 | if (self) { 30 | _contentLength = [[aDecoder decodeObjectForKey:kContentLengthKey] longLongValue]; 31 | _contentType = [aDecoder decodeObjectForKey:kContentTypeKey]; 32 | _byteRangeAccessSupported = [[aDecoder decodeObjectForKey:kByteRangeAccessSupported] boolValue]; 33 | } 34 | return self; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /VIMediaCache/ResourceLoader/VIMediaDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // VIMediaDownloader.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol VIMediaDownloaderDelegate; 12 | @class VIContentInfo; 13 | @class VIMediaCacheWorker; 14 | 15 | @interface VIMediaDownloaderStatus : NSObject 16 | 17 | + (instancetype)shared; 18 | 19 | - (void)addURL:(NSURL *)url; 20 | - (void)removeURL:(NSURL *)url; 21 | 22 | /** 23 | return YES if downloading the url source 24 | */ 25 | - (BOOL)containsURL:(NSURL *)url; 26 | - (NSSet *)urls; 27 | 28 | @end 29 | 30 | @interface VIMediaDownloader : NSObject 31 | 32 | - (instancetype)initWithURL:(NSURL *)url cacheWorker:(VIMediaCacheWorker *)cacheWorker; 33 | @property (nonatomic, strong, readonly) NSURL *url; 34 | @property (nonatomic, weak) id delegate; 35 | @property (nonatomic, strong) VIContentInfo *info; 36 | @property (nonatomic, assign) BOOL saveToCache; 37 | 38 | - (void)downloadTaskFromOffset:(unsigned long long)fromOffset 39 | length:(NSUInteger)length 40 | toEnd:(BOOL)toEnd; 41 | - (void)downloadFromStartToEnd; 42 | 43 | - (void)cancel; 44 | 45 | @end 46 | 47 | @protocol VIMediaDownloaderDelegate 48 | 49 | @optional 50 | - (void)mediaDownloader:(VIMediaDownloader *)downloader didReceiveResponse:(NSURLResponse *)response; 51 | - (void)mediaDownloader:(VIMediaDownloader *)downloader didReceiveData:(NSData *)data; 52 | - (void)mediaDownloader:(VIMediaDownloader *)downloader didFinishedWithError:(NSError *)error; 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /VIMediaCache/ResourceLoader/VIMediaDownloader.m: -------------------------------------------------------------------------------- 1 | // 2 | // VIMediaDownloader.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "VIMediaDownloader.h" 10 | #import "VIContentInfo.h" 11 | #import 12 | #import "VICacheSessionManager.h" 13 | 14 | #import "VIMediaCacheWorker.h" 15 | #import "VICacheManager.h" 16 | #import "VICacheAction.h" 17 | 18 | #pragma mark - Class: VIURLSessionDelegateObject 19 | 20 | @protocol VIURLSessionDelegateObjectDelegate 21 | 22 | - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler; 23 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler; 24 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data; 25 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error; 26 | 27 | @end 28 | 29 | static NSInteger kBufferSize = 10 * 1024; 30 | 31 | @interface VIURLSessionDelegateObject : NSObject 32 | 33 | - (instancetype)initWithDelegate:(id)delegate; 34 | 35 | @property (nonatomic, weak) id delegate; 36 | @property (nonatomic, strong) NSMutableData *bufferData; 37 | 38 | @end 39 | 40 | @implementation VIURLSessionDelegateObject 41 | 42 | - (instancetype)initWithDelegate:(id)delegate { 43 | self = [super init]; 44 | if (self) { 45 | _delegate = delegate; 46 | _bufferData = [NSMutableData data]; 47 | } 48 | return self; 49 | } 50 | 51 | #pragma mark - NSURLSessionDataDelegate 52 | - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{ 53 | [self.delegate URLSession:session didReceiveChallenge:challenge completionHandler:completionHandler]; 54 | } 55 | 56 | - (void)URLSession:(NSURLSession *)session 57 | dataTask:(NSURLSessionDataTask *)dataTask 58 | didReceiveResponse:(NSURLResponse *)response 59 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { 60 | [self.delegate URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler]; 61 | } 62 | 63 | - (void)URLSession:(NSURLSession *)session 64 | dataTask:(NSURLSessionDataTask *)dataTask 65 | didReceiveData:(NSData *)data { 66 | @synchronized (self.bufferData) { 67 | [self.bufferData appendData:data]; 68 | if (self.bufferData.length > kBufferSize) { 69 | NSRange chunkRange = NSMakeRange(0, self.bufferData.length); 70 | NSData *chunkData = [self.bufferData subdataWithRange:chunkRange]; 71 | [self.bufferData replaceBytesInRange:chunkRange withBytes:NULL length:0]; 72 | [self.delegate URLSession:session dataTask:dataTask didReceiveData:chunkData]; 73 | } 74 | } 75 | } 76 | 77 | - (void)URLSession:(NSURLSession *)session 78 | task:(NSURLSessionDataTask *)task 79 | didCompleteWithError:(nullable NSError *)error { 80 | @synchronized (self.bufferData) { 81 | if (self.bufferData.length > 0 && !error) { 82 | NSRange chunkRange = NSMakeRange(0, self.bufferData.length); 83 | NSData *chunkData = [self.bufferData subdataWithRange:chunkRange]; 84 | [self.bufferData replaceBytesInRange:chunkRange withBytes:NULL length:0]; 85 | [self.delegate URLSession:session dataTask:task didReceiveData:chunkData]; 86 | } 87 | } 88 | [self.delegate URLSession:session task:task didCompleteWithError:error]; 89 | } 90 | 91 | @end 92 | 93 | #pragma mark - Class: VIActionWorker 94 | 95 | @class VIActionWorker; 96 | 97 | @protocol VIActionWorkerDelegate 98 | 99 | - (void)actionWorker:(VIActionWorker *)actionWorker didReceiveResponse:(NSURLResponse *)response; 100 | - (void)actionWorker:(VIActionWorker *)actionWorker didReceiveData:(NSData *)data isLocal:(BOOL)isLocal; 101 | - (void)actionWorker:(VIActionWorker *)actionWorker didFinishWithError:(NSError *)error; 102 | 103 | @end 104 | 105 | @interface VIActionWorker : NSObject 106 | 107 | @property (nonatomic, strong) NSMutableArray *actions; 108 | - (instancetype)initWithActions:(NSArray *)actions url:(NSURL *)url cacheWorker:(VIMediaCacheWorker *)cacheWorker; 109 | 110 | @property (nonatomic, assign) BOOL canSaveToCache; 111 | @property (nonatomic, weak) id delegate; 112 | 113 | - (void)start; 114 | - (void)cancel; 115 | 116 | 117 | @property (nonatomic, getter=isCancelled) BOOL cancelled; 118 | 119 | @property (nonatomic, strong) VIMediaCacheWorker *cacheWorker; 120 | @property (nonatomic, strong) NSURL *url; 121 | 122 | @property (nonatomic, strong) NSURLSession *session; 123 | @property (nonatomic, strong) VIURLSessionDelegateObject *sessionDelegateObject; 124 | @property (nonatomic, strong) NSURLSessionDataTask *task; 125 | @property (nonatomic) NSInteger startOffset; 126 | 127 | @end 128 | 129 | @interface VIActionWorker () 130 | 131 | @property (nonatomic) NSTimeInterval notifyTime; 132 | 133 | @end 134 | 135 | @implementation VIActionWorker 136 | 137 | - (void)dealloc { 138 | [self cancel]; 139 | } 140 | 141 | - (instancetype)initWithActions:(NSArray *)actions url:(NSURL *)url cacheWorker:(VIMediaCacheWorker *)cacheWorker { 142 | self = [super init]; 143 | if (self) { 144 | _canSaveToCache = YES; 145 | _actions = [actions mutableCopy]; 146 | _cacheWorker = cacheWorker; 147 | _url = url; 148 | } 149 | return self; 150 | } 151 | 152 | - (void)start { 153 | [self processActions]; 154 | } 155 | 156 | - (void)cancel { 157 | if (_session) { 158 | [self.session invalidateAndCancel]; 159 | } 160 | self.cancelled = YES; 161 | } 162 | 163 | - (VIURLSessionDelegateObject *)sessionDelegateObject { 164 | if (!_sessionDelegateObject) { 165 | _sessionDelegateObject = [[VIURLSessionDelegateObject alloc] initWithDelegate:self]; 166 | } 167 | 168 | return _sessionDelegateObject; 169 | } 170 | 171 | - (NSURLSession *)session { 172 | if (!_session) { 173 | NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; 174 | NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self.sessionDelegateObject delegateQueue:[VICacheSessionManager shared].downloadQueue]; 175 | _session = session; 176 | } 177 | return _session; 178 | } 179 | 180 | - (void)processActions { 181 | if (self.isCancelled) { 182 | return; 183 | } 184 | 185 | VICacheAction *action = [self popFirstActionInList]; 186 | if (!action) { 187 | return; 188 | } 189 | 190 | if (action.actionType == VICacheAtionTypeLocal) { 191 | NSError *error; 192 | NSData *data = [self.cacheWorker cachedDataForRange:action.range error:&error]; 193 | if (error) { 194 | if ([self.delegate respondsToSelector:@selector(actionWorker:didFinishWithError:)]) { 195 | [self.delegate actionWorker:self didFinishWithError:error]; 196 | } 197 | } else { 198 | if ([self.delegate respondsToSelector:@selector(actionWorker:didReceiveData:isLocal:)]) { 199 | [self.delegate actionWorker:self didReceiveData:data isLocal:YES]; 200 | } 201 | [self processActionsLater]; 202 | } 203 | } else { 204 | long long fromOffset = action.range.location; 205 | long long endOffset = action.range.location + action.range.length - 1; 206 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.url]; 207 | request.cachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData; 208 | NSString *range = [NSString stringWithFormat:@"bytes=%lld-%lld", fromOffset, endOffset]; 209 | [request setValue:range forHTTPHeaderField:@"Range"]; 210 | self.startOffset = action.range.location; 211 | self.task = [self.session dataTaskWithRequest:request]; 212 | [self.task resume]; 213 | } 214 | } 215 | 216 | // process data recursively, 217 | - (void)processActionsLater { 218 | __weak typeof(self) weakSelf = self; 219 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 220 | __strong typeof(self) self = weakSelf; 221 | [self processActions]; 222 | }); 223 | } 224 | 225 | - (VICacheAction *)popFirstActionInList { 226 | @synchronized (self) { 227 | VICacheAction *action = [self.actions firstObject]; 228 | if (action) { 229 | [self.actions removeObjectAtIndex:0]; 230 | return action; 231 | } 232 | } 233 | if ([self.delegate respondsToSelector:@selector(actionWorker:didFinishWithError:)]) { 234 | [self.delegate actionWorker:self didFinishWithError:nil]; 235 | } 236 | return nil; 237 | } 238 | 239 | #pragma mark - Notify 240 | 241 | - (void)notifyDownloadProgressWithFlush:(BOOL)flush finished:(BOOL)finished { 242 | double currentTime = CFAbsoluteTimeGetCurrent(); 243 | double interval = [VICacheManager cacheUpdateNotifyInterval]; 244 | if ((self.notifyTime < currentTime - interval) || flush) { 245 | self.notifyTime = currentTime; 246 | VICacheConfiguration *configuration = [self.cacheWorker.cacheConfiguration copy]; 247 | [[NSNotificationCenter defaultCenter] postNotificationName:VICacheManagerDidUpdateCacheNotification 248 | object:self 249 | userInfo:@{ 250 | VICacheConfigurationKey: configuration, 251 | }]; 252 | 253 | if (finished && configuration.progress >= 1.0) { 254 | [self notifyDownloadFinishedWithError:nil]; 255 | } 256 | } 257 | } 258 | 259 | - (void)notifyDownloadFinishedWithError:(NSError *)error { 260 | VICacheConfiguration *configuration = [self.cacheWorker.cacheConfiguration copy]; 261 | NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; 262 | [userInfo setValue:configuration forKey:VICacheConfigurationKey]; 263 | [userInfo setValue:error forKey:VICacheFinishedErrorKey]; 264 | 265 | [[NSNotificationCenter defaultCenter] postNotificationName:VICacheManagerDidFinishCacheNotification 266 | object:self 267 | userInfo:userInfo]; 268 | } 269 | 270 | #pragma mark - VIURLSessionDelegateObjectDelegate 271 | 272 | - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { 273 | NSURLCredential *card = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust]; 274 | completionHandler(NSURLSessionAuthChallengeUseCredential,card); 275 | } 276 | 277 | - (void)URLSession:(NSURLSession *)session 278 | dataTask:(NSURLSessionDataTask *)dataTask 279 | didReceiveResponse:(NSURLResponse *)response 280 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { 281 | NSString *mimeType = response.MIMEType; 282 | // Only download video/audio data 283 | if ([mimeType rangeOfString:@"video/"].location == NSNotFound && 284 | [mimeType rangeOfString:@"audio/"].location == NSNotFound && 285 | [mimeType rangeOfString:@"application"].location == NSNotFound) { 286 | completionHandler(NSURLSessionResponseCancel); 287 | } else { 288 | if ([self.delegate respondsToSelector:@selector(actionWorker:didReceiveResponse:)]) { 289 | [self.delegate actionWorker:self didReceiveResponse:response]; 290 | } 291 | if (self.canSaveToCache) { 292 | [self.cacheWorker startWritting]; 293 | } 294 | completionHandler(NSURLSessionResponseAllow); 295 | } 296 | } 297 | 298 | - (void)URLSession:(NSURLSession *)session 299 | dataTask:(NSURLSessionDataTask *)dataTask 300 | didReceiveData:(NSData *)data { 301 | if (self.isCancelled) { 302 | return; 303 | } 304 | 305 | if (self.canSaveToCache) { 306 | NSRange range = NSMakeRange(self.startOffset, data.length); 307 | NSError *error; 308 | [self.cacheWorker cacheData:data forRange:range error:&error]; 309 | if (error) { 310 | if ([self.delegate respondsToSelector:@selector(actionWorker:didFinishWithError:)]) { 311 | [self.delegate actionWorker:self didFinishWithError:error]; 312 | } 313 | return; 314 | } 315 | [self.cacheWorker save]; 316 | } 317 | 318 | self.startOffset += data.length; 319 | if ([self.delegate respondsToSelector:@selector(actionWorker:didReceiveData:isLocal:)]) { 320 | [self.delegate actionWorker:self didReceiveData:data isLocal:NO]; 321 | } 322 | 323 | [self notifyDownloadProgressWithFlush:NO finished:NO]; 324 | } 325 | 326 | - (void)URLSession:(NSURLSession *)session 327 | task:(NSURLSessionTask *)task 328 | didCompleteWithError:(nullable NSError *)error { 329 | if (self.canSaveToCache) { 330 | [self.cacheWorker finishWritting]; 331 | [self.cacheWorker save]; 332 | } 333 | if (error) { 334 | if ([self.delegate respondsToSelector:@selector(actionWorker:didFinishWithError:)]) { 335 | [self.delegate actionWorker:self didFinishWithError:error]; 336 | } 337 | [self notifyDownloadFinishedWithError:error]; 338 | } else { 339 | [self notifyDownloadProgressWithFlush:YES finished:YES]; 340 | [self processActions]; 341 | } 342 | } 343 | 344 | @end 345 | 346 | #pragma mark - Class: VIMediaDownloaderStatus 347 | 348 | 349 | @interface VIMediaDownloaderStatus () 350 | 351 | @property (nonatomic, strong) NSMutableSet *downloadingURLS; 352 | 353 | @end 354 | 355 | @implementation VIMediaDownloaderStatus 356 | 357 | + (instancetype)shared { 358 | static VIMediaDownloaderStatus *instance = nil; 359 | static dispatch_once_t onceToken; 360 | dispatch_once(&onceToken, ^{ 361 | instance = [[self alloc] init]; 362 | instance.downloadingURLS = [NSMutableSet set]; 363 | }); 364 | 365 | return instance; 366 | } 367 | 368 | - (void)addURL:(NSURL *)url { 369 | @synchronized (self.downloadingURLS) { 370 | [self.downloadingURLS addObject:url]; 371 | } 372 | } 373 | 374 | - (void)removeURL:(NSURL *)url { 375 | @synchronized (self.downloadingURLS) { 376 | [self.downloadingURLS removeObject:url]; 377 | } 378 | } 379 | 380 | - (BOOL)containsURL:(NSURL *)url { 381 | @synchronized (self.downloadingURLS) { 382 | return [self.downloadingURLS containsObject:url]; 383 | } 384 | } 385 | 386 | - (NSSet *)urls { 387 | return [self.downloadingURLS copy]; 388 | } 389 | 390 | @end 391 | 392 | #pragma mark - Class: VIMediaDownloader 393 | 394 | @interface VIMediaDownloader () 395 | 396 | @property (nonatomic, strong) NSURL *url; 397 | @property (nonatomic, strong) NSURLSessionDataTask *task; 398 | 399 | @property (nonatomic, strong) VIMediaCacheWorker *cacheWorker; 400 | @property (nonatomic, strong) VIActionWorker *actionWorker; 401 | 402 | @property (nonatomic) BOOL downloadToEnd; 403 | 404 | @end 405 | 406 | @implementation VIMediaDownloader 407 | 408 | - (void)dealloc { 409 | [[VIMediaDownloaderStatus shared] removeURL:self.url]; 410 | } 411 | 412 | - (instancetype)initWithURL:(NSURL *)url cacheWorker:(VIMediaCacheWorker *)cacheWorker { 413 | self = [super init]; 414 | if (self) { 415 | _saveToCache = YES; 416 | _url = url; 417 | _cacheWorker = cacheWorker; 418 | _info = _cacheWorker.cacheConfiguration.contentInfo; 419 | [[VIMediaDownloaderStatus shared] addURL:self.url]; 420 | } 421 | return self; 422 | } 423 | 424 | - (void)downloadTaskFromOffset:(unsigned long long)fromOffset 425 | length:(NSUInteger)length 426 | toEnd:(BOOL)toEnd { 427 | // --- 428 | NSRange range = NSMakeRange((NSUInteger)fromOffset, length); 429 | 430 | if (toEnd) { 431 | range.length = (NSUInteger)self.cacheWorker.cacheConfiguration.contentInfo.contentLength - range.location; 432 | } 433 | 434 | NSArray *actions = [self.cacheWorker cachedDataActionsForRange:range]; 435 | 436 | self.actionWorker = [[VIActionWorker alloc] initWithActions:actions url:self.url cacheWorker:self.cacheWorker]; 437 | self.actionWorker.canSaveToCache = self.saveToCache; 438 | self.actionWorker.delegate = self; 439 | [self.actionWorker start]; 440 | } 441 | 442 | - (void)downloadFromStartToEnd { 443 | // --- 444 | self.downloadToEnd = YES; 445 | NSRange range = NSMakeRange(0, 2); 446 | NSArray *actions = [self.cacheWorker cachedDataActionsForRange:range]; 447 | 448 | self.actionWorker = [[VIActionWorker alloc] initWithActions:actions url:self.url cacheWorker:self.cacheWorker]; 449 | self.actionWorker.canSaveToCache = self.saveToCache; 450 | self.actionWorker.delegate = self; 451 | [self.actionWorker start]; 452 | } 453 | 454 | - (void)cancel { 455 | self.actionWorker.delegate = nil; 456 | [[VIMediaDownloaderStatus shared] removeURL:self.url]; 457 | [self.actionWorker cancel]; 458 | self.actionWorker = nil; 459 | } 460 | 461 | #pragma mark - VIActionWorkerDelegate 462 | 463 | - (void)actionWorker:(VIActionWorker *)actionWorker didReceiveResponse:(NSURLResponse *)response { 464 | if (!self.info) { 465 | VIContentInfo *info = [VIContentInfo new]; 466 | 467 | if ([response isKindOfClass:[NSHTTPURLResponse class]]) { 468 | NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response; 469 | NSString *acceptRange = HTTPURLResponse.allHeaderFields[@"Accept-Ranges"]; 470 | info.byteRangeAccessSupported = [acceptRange isEqualToString:@"bytes"]; 471 | info.contentLength = [[[HTTPURLResponse.allHeaderFields[@"Content-Range"] componentsSeparatedByString:@"/"] lastObject] longLongValue]; 472 | } 473 | NSString *mimeType = response.MIMEType; 474 | CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL); 475 | info.contentType = CFBridgingRelease(contentType); 476 | self.info = info; 477 | 478 | NSError *error; 479 | [self.cacheWorker setContentInfo:info error:&error]; 480 | if (error) { 481 | if ([self.delegate respondsToSelector:@selector(mediaDownloader:didFinishedWithError:)]) { 482 | [self.delegate mediaDownloader:self didFinishedWithError:error]; 483 | } 484 | return; 485 | } 486 | } 487 | 488 | if ([self.delegate respondsToSelector:@selector(mediaDownloader:didReceiveResponse:)]) { 489 | [self.delegate mediaDownloader:self didReceiveResponse:response]; 490 | } 491 | } 492 | 493 | - (void)actionWorker:(VIActionWorker *)actionWorker didReceiveData:(NSData *)data isLocal:(BOOL)isLocal { 494 | if ([self.delegate respondsToSelector:@selector(mediaDownloader:didReceiveData:)]) { 495 | [self.delegate mediaDownloader:self didReceiveData:data]; 496 | } 497 | } 498 | 499 | - (void)actionWorker:(VIActionWorker *)actionWorker didFinishWithError:(NSError *)error { 500 | [[VIMediaDownloaderStatus shared] removeURL:self.url]; 501 | 502 | if (!error && self.downloadToEnd) { 503 | self.downloadToEnd = NO; 504 | [self downloadTaskFromOffset:2 length:(NSUInteger)(self.cacheWorker.cacheConfiguration.contentInfo.contentLength - 2) toEnd:YES]; 505 | } else { 506 | if ([self.delegate respondsToSelector:@selector(mediaDownloader:didFinishedWithError:)]) { 507 | [self.delegate mediaDownloader:self didFinishedWithError:error]; 508 | } 509 | } 510 | } 511 | 512 | @end 513 | -------------------------------------------------------------------------------- /VIMediaCache/ResourceLoader/VIResourceLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // VIResoureLoader.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | @import AVFoundation; 11 | @protocol VIResourceLoaderDelegate; 12 | 13 | @interface VIResourceLoader : NSObject 14 | 15 | @property (nonatomic, strong, readonly) NSURL *url; 16 | @property (nonatomic, weak) id delegate; 17 | 18 | - (instancetype)initWithURL:(NSURL *)url; 19 | 20 | - (void)addRequest:(AVAssetResourceLoadingRequest *)request; 21 | - (void)removeRequest:(AVAssetResourceLoadingRequest *)request; 22 | 23 | - (void)cancel; 24 | 25 | @end 26 | 27 | @protocol VIResourceLoaderDelegate 28 | 29 | - (void)resourceLoader:(VIResourceLoader *)resourceLoader didFailWithError:(NSError *)error; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /VIMediaCache/ResourceLoader/VIResourceLoader.m: -------------------------------------------------------------------------------- 1 | // 2 | // VIResoureLoader.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "VIResourceLoader.h" 10 | #import "VIMediaDownloader.h" 11 | #import "VIResourceLoadingRequestWorker.h" 12 | #import "VIContentInfo.h" 13 | #import "VIMediaCacheWorker.h" 14 | 15 | NSString * const MCResourceLoaderErrorDomain = @"LSFilePlayerResourceLoaderErrorDomain"; 16 | 17 | @interface VIResourceLoader () 18 | 19 | @property (nonatomic, strong, readwrite) NSURL *url; 20 | @property (nonatomic, strong) VIMediaCacheWorker *cacheWorker; 21 | @property (nonatomic, strong) VIMediaDownloader *mediaDownloader; 22 | @property (nonatomic, strong) NSMutableArray *pendingRequestWorkers; 23 | 24 | @property (nonatomic, getter=isCancelled) BOOL cancelled; 25 | 26 | @end 27 | 28 | @implementation VIResourceLoader 29 | 30 | 31 | - (void)dealloc { 32 | [_mediaDownloader cancel]; 33 | } 34 | 35 | - (instancetype)initWithURL:(NSURL *)url { 36 | self = [super init]; 37 | if (self) { 38 | _url = url; 39 | _cacheWorker = [[VIMediaCacheWorker alloc] initWithURL:url]; 40 | _mediaDownloader = [[VIMediaDownloader alloc] initWithURL:url cacheWorker:_cacheWorker]; 41 | _pendingRequestWorkers = [NSMutableArray array]; 42 | } 43 | return self; 44 | } 45 | 46 | - (instancetype)init { 47 | NSAssert(NO, @"Use - initWithURL: instead"); 48 | return nil; 49 | } 50 | 51 | - (void)addRequest:(AVAssetResourceLoadingRequest *)request { 52 | if (self.pendingRequestWorkers.count > 0) { 53 | [self startNoCacheWorkerWithRequest:request]; 54 | } else { 55 | [self startWorkerWithRequest:request]; 56 | } 57 | } 58 | 59 | - (void)removeRequest:(AVAssetResourceLoadingRequest *)request { 60 | __block VIResourceLoadingRequestWorker *requestWorker = nil; 61 | [self.pendingRequestWorkers enumerateObjectsUsingBlock:^(VIResourceLoadingRequestWorker * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 62 | if (obj.request == request) { 63 | requestWorker = obj; 64 | *stop = YES; 65 | } 66 | }]; 67 | if (requestWorker) { 68 | [requestWorker finish]; 69 | [self.pendingRequestWorkers removeObject:requestWorker]; 70 | } 71 | } 72 | 73 | - (void)cancel { 74 | [self.mediaDownloader cancel]; 75 | [self.pendingRequestWorkers removeAllObjects]; 76 | 77 | [[VIMediaDownloaderStatus shared] removeURL:self.url]; 78 | } 79 | 80 | #pragma mark - VIResourceLoadingRequestWorkerDelegate 81 | 82 | - (void)resourceLoadingRequestWorker:(VIResourceLoadingRequestWorker *)requestWorker didCompleteWithError:(NSError *)error { 83 | [self removeRequest:requestWorker.request]; 84 | if (error && [self.delegate respondsToSelector:@selector(resourceLoader:didFailWithError:)]) { 85 | [self.delegate resourceLoader:self didFailWithError:error]; 86 | } 87 | if (self.pendingRequestWorkers.count == 0) { 88 | [[VIMediaDownloaderStatus shared] removeURL:self.url]; 89 | } 90 | } 91 | 92 | #pragma mark - Helper 93 | 94 | - (void)startNoCacheWorkerWithRequest:(AVAssetResourceLoadingRequest *)request { 95 | [[VIMediaDownloaderStatus shared] addURL:self.url]; 96 | VIMediaDownloader *mediaDownloader = [[VIMediaDownloader alloc] initWithURL:self.url cacheWorker:self.cacheWorker]; 97 | VIResourceLoadingRequestWorker *requestWorker = [[VIResourceLoadingRequestWorker alloc] initWithMediaDownloader:mediaDownloader 98 | resourceLoadingRequest:request]; 99 | [self.pendingRequestWorkers addObject:requestWorker]; 100 | requestWorker.delegate = self; 101 | [requestWorker startWork]; 102 | } 103 | 104 | - (void)startWorkerWithRequest:(AVAssetResourceLoadingRequest *)request { 105 | [[VIMediaDownloaderStatus shared] addURL:self.url]; 106 | VIResourceLoadingRequestWorker *requestWorker = [[VIResourceLoadingRequestWorker alloc] initWithMediaDownloader:self.mediaDownloader 107 | resourceLoadingRequest:request]; 108 | [self.pendingRequestWorkers addObject:requestWorker]; 109 | requestWorker.delegate = self; 110 | [requestWorker startWork]; 111 | 112 | } 113 | 114 | - (NSError *)loaderCancelledError { 115 | NSError *error = [[NSError alloc] initWithDomain:MCResourceLoaderErrorDomain 116 | code:-3 117 | userInfo:@{NSLocalizedDescriptionKey:@"Resource loader cancelled"}]; 118 | return error; 119 | } 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /VIMediaCache/ResourceLoader/VIResourceLoaderManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // VIResourceLoaderManager.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @protocol VIResourceLoaderManagerDelegate; 13 | 14 | @interface VIResourceLoaderManager : NSObject 15 | 16 | 17 | @property (nonatomic, weak) id delegate; 18 | 19 | /** 20 | Normally you no need to call this method to clean cache. Cache cleaned after AVPlayer delloc. 21 | If you have a singleton AVPlayer then you need call this method to clean cache at suitable time. 22 | */ 23 | - (void)cleanCache; 24 | 25 | /** 26 | Cancel all downloading loaders. 27 | */ 28 | - (void)cancelLoaders; 29 | 30 | @end 31 | 32 | @protocol VIResourceLoaderManagerDelegate 33 | 34 | - (void)resourceLoaderManagerLoadURL:(NSURL *)url didFailWithError:(NSError *)error; 35 | 36 | @end 37 | 38 | @interface VIResourceLoaderManager (Convenient) 39 | 40 | + (NSURL *)assetURLWithURL:(NSURL *)url; 41 | - (AVPlayerItem *)playerItemWithURL:(NSURL *)url; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /VIMediaCache/ResourceLoader/VIResourceLoaderManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // VIResourceLoaderManager.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "VIResourceLoaderManager.h" 10 | #import "VIResourceLoader.h" 11 | 12 | static NSString *kCacheScheme = @"VIMediaCache:"; 13 | 14 | @interface VIResourceLoaderManager () 15 | 16 | @property (nonatomic, strong) NSMutableDictionary, VIResourceLoader *> *loaders; 17 | 18 | @end 19 | 20 | @implementation VIResourceLoaderManager 21 | 22 | - (instancetype)init { 23 | self = [super init]; 24 | if (self) { 25 | _loaders = [NSMutableDictionary dictionary]; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)cleanCache { 31 | [self.loaders removeAllObjects]; 32 | } 33 | 34 | - (void)cancelLoaders { 35 | [self.loaders enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, VIResourceLoader * _Nonnull obj, BOOL * _Nonnull stop) { 36 | [obj cancel]; 37 | }]; 38 | [self.loaders removeAllObjects]; 39 | } 40 | 41 | #pragma mark - AVAssetResourceLoaderDelegate 42 | 43 | - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest { 44 | NSURL *resourceURL = [loadingRequest.request URL]; 45 | if ([resourceURL.absoluteString hasPrefix:kCacheScheme]) { 46 | VIResourceLoader *loader = [self loaderForRequest:loadingRequest]; 47 | if (!loader) { 48 | NSURL *originURL = nil; 49 | NSString *originStr = [resourceURL absoluteString]; 50 | originStr = [originStr stringByReplacingOccurrencesOfString:kCacheScheme withString:@""]; 51 | originURL = [NSURL URLWithString:originStr]; 52 | loader = [[VIResourceLoader alloc] initWithURL:originURL]; 53 | loader.delegate = self; 54 | NSString *key = [self keyForResourceLoaderWithURL:resourceURL]; 55 | self.loaders[key] = loader; 56 | } 57 | [loader addRequest:loadingRequest]; 58 | return YES; 59 | } 60 | 61 | return NO; 62 | } 63 | 64 | - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest { 65 | VIResourceLoader *loader = [self loaderForRequest:loadingRequest]; 66 | [loader removeRequest:loadingRequest]; 67 | } 68 | 69 | #pragma mark - VIResourceLoaderDelegate 70 | 71 | - (void)resourceLoader:(VIResourceLoader *)resourceLoader didFailWithError:(NSError *)error { 72 | [resourceLoader cancel]; 73 | if ([self.delegate respondsToSelector:@selector(resourceLoaderManagerLoadURL:didFailWithError:)]) { 74 | [self.delegate resourceLoaderManagerLoadURL:resourceLoader.url didFailWithError:error]; 75 | } 76 | } 77 | 78 | #pragma mark - Helper 79 | 80 | - (NSString *)keyForResourceLoaderWithURL:(NSURL *)requestURL { 81 | if([[requestURL absoluteString] hasPrefix:kCacheScheme]){ 82 | NSString *s = requestURL.absoluteString; 83 | return s; 84 | } 85 | return nil; 86 | } 87 | 88 | - (VIResourceLoader *)loaderForRequest:(AVAssetResourceLoadingRequest *)request { 89 | NSString *requestKey = [self keyForResourceLoaderWithURL:request.request.URL]; 90 | VIResourceLoader *loader = self.loaders[requestKey]; 91 | return loader; 92 | } 93 | 94 | @end 95 | 96 | @implementation VIResourceLoaderManager (Convenient) 97 | 98 | + (NSURL *)assetURLWithURL:(NSURL *)url { 99 | if (!url) { 100 | return nil; 101 | } 102 | 103 | NSURL *assetURL = [NSURL URLWithString:[kCacheScheme stringByAppendingString:[url absoluteString]]]; 104 | if (assetURL == nil) { 105 | return url; 106 | } 107 | return assetURL; 108 | } 109 | 110 | - (AVPlayerItem *)playerItemWithURL:(NSURL *)url { 111 | NSURL *assetURL = [VIResourceLoaderManager assetURLWithURL:url]; 112 | AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:assetURL options:nil]; 113 | [urlAsset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()]; 114 | AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:urlAsset]; 115 | if ([playerItem respondsToSelector:@selector(setCanUseNetworkResourcesForLiveStreamingWhilePaused:)]) { 116 | playerItem.canUseNetworkResourcesForLiveStreamingWhilePaused = YES; 117 | } 118 | return playerItem; 119 | } 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /VIMediaCache/ResourceLoader/VIResourceLoadingRequestWorker.h: -------------------------------------------------------------------------------- 1 | // 2 | // VIResourceLoadingRequestWorker.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class VIMediaDownloader, AVAssetResourceLoadingRequest; 12 | @protocol VIResourceLoadingRequestWorkerDelegate; 13 | 14 | @interface VIResourceLoadingRequestWorker : NSObject 15 | 16 | - (instancetype)initWithMediaDownloader:(VIMediaDownloader *)mediaDownloader resourceLoadingRequest:(AVAssetResourceLoadingRequest *)request; 17 | 18 | @property (nonatomic, weak) id delegate; 19 | 20 | @property (nonatomic, strong, readonly) AVAssetResourceLoadingRequest *request; 21 | 22 | - (void)startWork; 23 | - (void)cancel; 24 | - (void)finish; 25 | 26 | @end 27 | 28 | @protocol VIResourceLoadingRequestWorkerDelegate 29 | 30 | - (void)resourceLoadingRequestWorker:(VIResourceLoadingRequestWorker *)requestWorker didCompleteWithError:(NSError *)error; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /VIMediaCache/ResourceLoader/VIResourceLoadingRequestWorker.m: -------------------------------------------------------------------------------- 1 | // 2 | // VIResourceLoadingRequestWorker.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/21/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "VIResourceLoadingRequestWorker.h" 10 | #import "VIMediaDownloader.h" 11 | #import "VIContentInfo.h" 12 | 13 | @import MobileCoreServices; 14 | @import AVFoundation; 15 | @import UIKit; 16 | 17 | @interface VIResourceLoadingRequestWorker () 18 | 19 | @property (nonatomic, strong, readwrite) AVAssetResourceLoadingRequest *request; 20 | @property (nonatomic, strong) VIMediaDownloader *mediaDownloader; 21 | 22 | @end 23 | 24 | @implementation VIResourceLoadingRequestWorker 25 | 26 | - (instancetype)initWithMediaDownloader:(VIMediaDownloader *)mediaDownloader resourceLoadingRequest:(AVAssetResourceLoadingRequest *)request { 27 | self = [super init]; 28 | if (self) { 29 | _mediaDownloader = mediaDownloader; 30 | _mediaDownloader.delegate = self; 31 | _request = request; 32 | 33 | [self fullfillContentInfo]; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)startWork { 39 | AVAssetResourceLoadingDataRequest *dataRequest = self.request.dataRequest; 40 | 41 | long long offset = dataRequest.requestedOffset; 42 | NSInteger length = dataRequest.requestedLength; 43 | if (dataRequest.currentOffset != 0) { 44 | offset = dataRequest.currentOffset; 45 | } 46 | 47 | BOOL toEnd = NO; 48 | if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) { 49 | if (dataRequest.requestsAllDataToEndOfResource) { 50 | toEnd = YES; 51 | } 52 | } 53 | [self.mediaDownloader downloadTaskFromOffset:offset length:length toEnd:toEnd]; 54 | } 55 | 56 | - (void)cancel { 57 | [self.mediaDownloader cancel]; 58 | } 59 | 60 | - (void)finish { 61 | if (!self.request.isFinished) { 62 | [self.mediaDownloader cancel]; 63 | [self.request finishLoadingWithError:[self loaderCancelledError]]; 64 | } 65 | } 66 | 67 | - (NSError *)loaderCancelledError{ 68 | NSError *error = [[NSError alloc] initWithDomain:@"com.resourceloader" 69 | code:-3 70 | userInfo:@{NSLocalizedDescriptionKey:@"Resource loader cancelled"}]; 71 | return error; 72 | } 73 | 74 | - (void)fullfillContentInfo { 75 | AVAssetResourceLoadingContentInformationRequest *contentInformationRequest = self.request.contentInformationRequest; 76 | if (self.mediaDownloader.info && !contentInformationRequest.contentType) { 77 | // Fullfill content information 78 | contentInformationRequest.contentType = self.mediaDownloader.info.contentType; 79 | contentInformationRequest.contentLength = self.mediaDownloader.info.contentLength; 80 | contentInformationRequest.byteRangeAccessSupported = self.mediaDownloader.info.byteRangeAccessSupported; 81 | } 82 | } 83 | 84 | #pragma mark - VIMediaDownloaderDelegate 85 | 86 | - (void)mediaDownloader:(VIMediaDownloader *)downloader didReceiveResponse:(NSURLResponse *)response { 87 | [self fullfillContentInfo]; 88 | } 89 | 90 | - (void)mediaDownloader:(VIMediaDownloader *)downloader didReceiveData:(NSData *)data { 91 | [self.request.dataRequest respondWithData:data]; 92 | } 93 | 94 | - (void)mediaDownloader:(VIMediaDownloader *)downloader didFinishedWithError:(NSError *)error { 95 | if (error.code == NSURLErrorCancelled) { 96 | return; 97 | } 98 | 99 | if (!error) { 100 | [self.request finishLoading]; 101 | } else { 102 | [self.request finishLoadingWithError:error]; 103 | } 104 | 105 | [self.delegate resourceLoadingRequestWorker:self didCompleteWithError:error]; 106 | } 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /VIMediaCache/VIMediaCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // VIMediaCache.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 4/22/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #ifndef VIMediaCache_h 10 | #define VIMediaCache_h 11 | 12 | #import "VIResourceLoaderManager.h" 13 | #import "VICacheManager.h" 14 | #import "VIMediaDownloader.h" 15 | #import "VIContentInfo.h" 16 | 17 | #endif /* VIMediaCache_h */ 18 | -------------------------------------------------------------------------------- /VIMediaCacheDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5F2836BD1D96548E000910CA /* VICacheAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F2836B41D96548E000910CA /* VICacheAction.m */; }; 11 | 5F2836BE1D96548E000910CA /* VICacheConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F2836B61D96548E000910CA /* VICacheConfiguration.m */; }; 12 | 5F2836BF1D96548E000910CA /* VICacheManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F2836B81D96548E000910CA /* VICacheManager.m */; }; 13 | 5F2836C01D96548E000910CA /* VICacheSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F2836BA1D96548E000910CA /* VICacheSessionManager.m */; }; 14 | 5F2836C11D96548E000910CA /* VIMediaCacheWorker.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F2836BC1D96548E000910CA /* VIMediaCacheWorker.m */; }; 15 | 5F5FA6101CEA2BE2004439D3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F5FA60F1CEA2BE2004439D3 /* main.m */; }; 16 | 5F5FA6131CEA2BE2004439D3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F5FA6121CEA2BE2004439D3 /* AppDelegate.m */; }; 17 | 5F5FA6161CEA2BE2004439D3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F5FA6151CEA2BE2004439D3 /* ViewController.m */; }; 18 | 5F5FA6191CEA2BE2004439D3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5F5FA6171CEA2BE2004439D3 /* Main.storyboard */; }; 19 | 5F5FA61B1CEA2BE2004439D3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5F5FA61A1CEA2BE2004439D3 /* Assets.xcassets */; }; 20 | 5F5FA61E1CEA2BE2004439D3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5F5FA61C1CEA2BE2004439D3 /* LaunchScreen.storyboard */; }; 21 | 5F5FA6291CEA2BE2004439D3 /* VIMediaCacheDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F5FA6281CEA2BE2004439D3 /* VIMediaCacheDemoTests.m */; }; 22 | 5F5FA67F1CEA2D95004439D3 /* PlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F5FA67E1CEA2D95004439D3 /* PlayerView.m */; }; 23 | 5FC756B41FC47A4C00608495 /* NSString+VIMD5.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FC756B31FC47A4C00608495 /* NSString+VIMD5.m */; }; 24 | 5FDCF2AE1CEBF26800188E0C /* VIContentInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDCF2971CEBF26800188E0C /* VIContentInfo.m */; }; 25 | 5FDCF2AF1CEBF26800188E0C /* VIMediaDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDCF2991CEBF26800188E0C /* VIMediaDownloader.m */; }; 26 | 5FDCF2B01CEBF26800188E0C /* VIResourceLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDCF29B1CEBF26800188E0C /* VIResourceLoader.m */; }; 27 | 5FDCF2B11CEBF26800188E0C /* VIResourceLoaderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDCF29D1CEBF26800188E0C /* VIResourceLoaderManager.m */; }; 28 | 5FDCF2B21CEBF26800188E0C /* VIResourceLoadingRequestWorker.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDCF29F1CEBF26800188E0C /* VIResourceLoadingRequestWorker.m */; }; 29 | 5FDCF2BA1CEBF74600188E0C /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FDCF2B91CEBF74600188E0C /* MobileCoreServices.framework */; }; 30 | 5FDCF2BC1CEBF76200188E0C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FDCF2BB1CEBF76200188E0C /* AVFoundation.framework */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | 5F5FA6251CEA2BE2004439D3 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 5F5FA6031CEA2BE2004439D3 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 5F5FA60A1CEA2BE2004439D3; 39 | remoteInfo = VIMediaCacheDemo; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 5F2836B31D96548E000910CA /* VICacheAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VICacheAction.h; sourceTree = ""; }; 45 | 5F2836B41D96548E000910CA /* VICacheAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VICacheAction.m; sourceTree = ""; }; 46 | 5F2836B51D96548E000910CA /* VICacheConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VICacheConfiguration.h; sourceTree = ""; }; 47 | 5F2836B61D96548E000910CA /* VICacheConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VICacheConfiguration.m; sourceTree = ""; }; 48 | 5F2836B71D96548E000910CA /* VICacheManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VICacheManager.h; sourceTree = ""; }; 49 | 5F2836B81D96548E000910CA /* VICacheManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VICacheManager.m; sourceTree = ""; }; 50 | 5F2836B91D96548E000910CA /* VICacheSessionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VICacheSessionManager.h; sourceTree = ""; }; 51 | 5F2836BA1D96548E000910CA /* VICacheSessionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VICacheSessionManager.m; sourceTree = ""; }; 52 | 5F2836BB1D96548E000910CA /* VIMediaCacheWorker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VIMediaCacheWorker.h; sourceTree = ""; }; 53 | 5F2836BC1D96548E000910CA /* VIMediaCacheWorker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VIMediaCacheWorker.m; sourceTree = ""; }; 54 | 5F5FA60B1CEA2BE2004439D3 /* VIMediaCacheDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VIMediaCacheDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 5F5FA60F1CEA2BE2004439D3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 56 | 5F5FA6111CEA2BE2004439D3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 57 | 5F5FA6121CEA2BE2004439D3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 58 | 5F5FA6141CEA2BE2004439D3 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 59 | 5F5FA6151CEA2BE2004439D3 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 60 | 5F5FA6181CEA2BE2004439D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 61 | 5F5FA61A1CEA2BE2004439D3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 62 | 5F5FA61D1CEA2BE2004439D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63 | 5F5FA61F1CEA2BE2004439D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | 5F5FA6241CEA2BE2004439D3 /* VIMediaCacheDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VIMediaCacheDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | 5F5FA6281CEA2BE2004439D3 /* VIMediaCacheDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VIMediaCacheDemoTests.m; sourceTree = ""; }; 66 | 5F5FA62A1CEA2BE2004439D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | 5F5FA67D1CEA2D95004439D3 /* PlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayerView.h; sourceTree = ""; }; 68 | 5F5FA67E1CEA2D95004439D3 /* PlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayerView.m; sourceTree = ""; }; 69 | 5FC756B21FC47A4C00608495 /* NSString+VIMD5.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+VIMD5.h"; sourceTree = ""; }; 70 | 5FC756B31FC47A4C00608495 /* NSString+VIMD5.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+VIMD5.m"; sourceTree = ""; }; 71 | 5FDCF2961CEBF26800188E0C /* VIContentInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VIContentInfo.h; sourceTree = ""; }; 72 | 5FDCF2971CEBF26800188E0C /* VIContentInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VIContentInfo.m; sourceTree = ""; }; 73 | 5FDCF2981CEBF26800188E0C /* VIMediaDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VIMediaDownloader.h; sourceTree = ""; }; 74 | 5FDCF2991CEBF26800188E0C /* VIMediaDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VIMediaDownloader.m; sourceTree = ""; }; 75 | 5FDCF29A1CEBF26800188E0C /* VIResourceLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VIResourceLoader.h; sourceTree = ""; }; 76 | 5FDCF29B1CEBF26800188E0C /* VIResourceLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VIResourceLoader.m; sourceTree = ""; }; 77 | 5FDCF29C1CEBF26800188E0C /* VIResourceLoaderManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VIResourceLoaderManager.h; sourceTree = ""; }; 78 | 5FDCF29D1CEBF26800188E0C /* VIResourceLoaderManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VIResourceLoaderManager.m; sourceTree = ""; }; 79 | 5FDCF29E1CEBF26800188E0C /* VIResourceLoadingRequestWorker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VIResourceLoadingRequestWorker.h; sourceTree = ""; }; 80 | 5FDCF29F1CEBF26800188E0C /* VIResourceLoadingRequestWorker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VIResourceLoadingRequestWorker.m; sourceTree = ""; }; 81 | 5FDCF2AD1CEBF26800188E0C /* VIMediaCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VIMediaCache.h; sourceTree = ""; }; 82 | 5FDCF2B91CEBF74600188E0C /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 83 | 5FDCF2BB1CEBF76200188E0C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 84 | /* End PBXFileReference section */ 85 | 86 | /* Begin PBXFrameworksBuildPhase section */ 87 | 5F5FA6081CEA2BE2004439D3 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | 5FDCF2BC1CEBF76200188E0C /* AVFoundation.framework in Frameworks */, 92 | 5FDCF2BA1CEBF74600188E0C /* MobileCoreServices.framework in Frameworks */, 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | 5F5FA6211CEA2BE2004439D3 /* Frameworks */ = { 97 | isa = PBXFrameworksBuildPhase; 98 | buildActionMask = 2147483647; 99 | files = ( 100 | ); 101 | runOnlyForDeploymentPostprocessing = 0; 102 | }; 103 | /* End PBXFrameworksBuildPhase section */ 104 | 105 | /* Begin PBXGroup section */ 106 | 5F2836B21D96548E000910CA /* Cache */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 5F2836B31D96548E000910CA /* VICacheAction.h */, 110 | 5F2836B41D96548E000910CA /* VICacheAction.m */, 111 | 5F2836B51D96548E000910CA /* VICacheConfiguration.h */, 112 | 5F2836B61D96548E000910CA /* VICacheConfiguration.m */, 113 | 5F2836B71D96548E000910CA /* VICacheManager.h */, 114 | 5F2836B81D96548E000910CA /* VICacheManager.m */, 115 | 5F2836B91D96548E000910CA /* VICacheSessionManager.h */, 116 | 5F2836BA1D96548E000910CA /* VICacheSessionManager.m */, 117 | 5F2836BB1D96548E000910CA /* VIMediaCacheWorker.h */, 118 | 5F2836BC1D96548E000910CA /* VIMediaCacheWorker.m */, 119 | 5FC756B21FC47A4C00608495 /* NSString+VIMD5.h */, 120 | 5FC756B31FC47A4C00608495 /* NSString+VIMD5.m */, 121 | ); 122 | path = Cache; 123 | sourceTree = ""; 124 | }; 125 | 5F5FA6021CEA2BE2004439D3 = { 126 | isa = PBXGroup; 127 | children = ( 128 | 5F5FA60D1CEA2BE2004439D3 /* VIMediaCacheDemo */, 129 | 5F5FA6271CEA2BE2004439D3 /* VIMediaCacheDemoTests */, 130 | 5F5FA60C1CEA2BE2004439D3 /* Products */, 131 | ); 132 | sourceTree = ""; 133 | }; 134 | 5F5FA60C1CEA2BE2004439D3 /* Products */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 5F5FA60B1CEA2BE2004439D3 /* VIMediaCacheDemo.app */, 138 | 5F5FA6241CEA2BE2004439D3 /* VIMediaCacheDemoTests.xctest */, 139 | ); 140 | name = Products; 141 | sourceTree = ""; 142 | }; 143 | 5F5FA60D1CEA2BE2004439D3 /* VIMediaCacheDemo */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 5FDCF2941CEBF26800188E0C /* VIMediaCache */, 147 | 5F5FA6111CEA2BE2004439D3 /* AppDelegate.h */, 148 | 5F5FA6121CEA2BE2004439D3 /* AppDelegate.m */, 149 | 5F5FA6141CEA2BE2004439D3 /* ViewController.h */, 150 | 5F5FA6151CEA2BE2004439D3 /* ViewController.m */, 151 | 5F5FA6171CEA2BE2004439D3 /* Main.storyboard */, 152 | 5F5FA61A1CEA2BE2004439D3 /* Assets.xcassets */, 153 | 5F5FA61C1CEA2BE2004439D3 /* LaunchScreen.storyboard */, 154 | 5F5FA61F1CEA2BE2004439D3 /* Info.plist */, 155 | 5F5FA60E1CEA2BE2004439D3 /* Supporting Files */, 156 | 5F5FA67D1CEA2D95004439D3 /* PlayerView.h */, 157 | 5F5FA67E1CEA2D95004439D3 /* PlayerView.m */, 158 | ); 159 | path = VIMediaCacheDemo; 160 | sourceTree = ""; 161 | }; 162 | 5F5FA60E1CEA2BE2004439D3 /* Supporting Files */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 5FDCF2BB1CEBF76200188E0C /* AVFoundation.framework */, 166 | 5FDCF2B91CEBF74600188E0C /* MobileCoreServices.framework */, 167 | 5F5FA60F1CEA2BE2004439D3 /* main.m */, 168 | ); 169 | name = "Supporting Files"; 170 | sourceTree = ""; 171 | }; 172 | 5F5FA6271CEA2BE2004439D3 /* VIMediaCacheDemoTests */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | 5F5FA6281CEA2BE2004439D3 /* VIMediaCacheDemoTests.m */, 176 | 5F5FA62A1CEA2BE2004439D3 /* Info.plist */, 177 | ); 178 | path = VIMediaCacheDemoTests; 179 | sourceTree = ""; 180 | }; 181 | 5FDCF2941CEBF26800188E0C /* VIMediaCache */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | 5F2836B21D96548E000910CA /* Cache */, 185 | 5FDCF2951CEBF26800188E0C /* ResourceLoader */, 186 | 5FDCF2AD1CEBF26800188E0C /* VIMediaCache.h */, 187 | ); 188 | path = VIMediaCache; 189 | sourceTree = SOURCE_ROOT; 190 | }; 191 | 5FDCF2951CEBF26800188E0C /* ResourceLoader */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 5FDCF2961CEBF26800188E0C /* VIContentInfo.h */, 195 | 5FDCF2971CEBF26800188E0C /* VIContentInfo.m */, 196 | 5FDCF2981CEBF26800188E0C /* VIMediaDownloader.h */, 197 | 5FDCF2991CEBF26800188E0C /* VIMediaDownloader.m */, 198 | 5FDCF29A1CEBF26800188E0C /* VIResourceLoader.h */, 199 | 5FDCF29B1CEBF26800188E0C /* VIResourceLoader.m */, 200 | 5FDCF29C1CEBF26800188E0C /* VIResourceLoaderManager.h */, 201 | 5FDCF29D1CEBF26800188E0C /* VIResourceLoaderManager.m */, 202 | 5FDCF29E1CEBF26800188E0C /* VIResourceLoadingRequestWorker.h */, 203 | 5FDCF29F1CEBF26800188E0C /* VIResourceLoadingRequestWorker.m */, 204 | ); 205 | path = ResourceLoader; 206 | sourceTree = ""; 207 | }; 208 | /* End PBXGroup section */ 209 | 210 | /* Begin PBXNativeTarget section */ 211 | 5F5FA60A1CEA2BE2004439D3 /* VIMediaCacheDemo */ = { 212 | isa = PBXNativeTarget; 213 | buildConfigurationList = 5F5FA62D1CEA2BE2004439D3 /* Build configuration list for PBXNativeTarget "VIMediaCacheDemo" */; 214 | buildPhases = ( 215 | 5F5FA6071CEA2BE2004439D3 /* Sources */, 216 | 5F5FA6081CEA2BE2004439D3 /* Frameworks */, 217 | 5F5FA6091CEA2BE2004439D3 /* Resources */, 218 | ); 219 | buildRules = ( 220 | ); 221 | dependencies = ( 222 | ); 223 | name = VIMediaCacheDemo; 224 | productName = VIMediaCacheDemo; 225 | productReference = 5F5FA60B1CEA2BE2004439D3 /* VIMediaCacheDemo.app */; 226 | productType = "com.apple.product-type.application"; 227 | }; 228 | 5F5FA6231CEA2BE2004439D3 /* VIMediaCacheDemoTests */ = { 229 | isa = PBXNativeTarget; 230 | buildConfigurationList = 5F5FA6301CEA2BE2004439D3 /* Build configuration list for PBXNativeTarget "VIMediaCacheDemoTests" */; 231 | buildPhases = ( 232 | 5F5FA6201CEA2BE2004439D3 /* Sources */, 233 | 5F5FA6211CEA2BE2004439D3 /* Frameworks */, 234 | 5F5FA6221CEA2BE2004439D3 /* Resources */, 235 | ); 236 | buildRules = ( 237 | ); 238 | dependencies = ( 239 | 5F5FA6261CEA2BE2004439D3 /* PBXTargetDependency */, 240 | ); 241 | name = VIMediaCacheDemoTests; 242 | productName = VIMediaCacheDemoTests; 243 | productReference = 5F5FA6241CEA2BE2004439D3 /* VIMediaCacheDemoTests.xctest */; 244 | productType = "com.apple.product-type.bundle.unit-test"; 245 | }; 246 | /* End PBXNativeTarget section */ 247 | 248 | /* Begin PBXProject section */ 249 | 5F5FA6031CEA2BE2004439D3 /* Project object */ = { 250 | isa = PBXProject; 251 | attributes = { 252 | LastUpgradeCheck = 0800; 253 | ORGANIZATIONNAME = Vito; 254 | TargetAttributes = { 255 | 5F5FA60A1CEA2BE2004439D3 = { 256 | CreatedOnToolsVersion = 7.3.1; 257 | DevelopmentTeam = ZZX7396L9W; 258 | }; 259 | 5F5FA6231CEA2BE2004439D3 = { 260 | CreatedOnToolsVersion = 7.3.1; 261 | TestTargetID = 5F5FA60A1CEA2BE2004439D3; 262 | }; 263 | }; 264 | }; 265 | buildConfigurationList = 5F5FA6061CEA2BE2004439D3 /* Build configuration list for PBXProject "VIMediaCacheDemo" */; 266 | compatibilityVersion = "Xcode 3.2"; 267 | developmentRegion = English; 268 | hasScannedForEncodings = 0; 269 | knownRegions = ( 270 | en, 271 | Base, 272 | ); 273 | mainGroup = 5F5FA6021CEA2BE2004439D3; 274 | productRefGroup = 5F5FA60C1CEA2BE2004439D3 /* Products */; 275 | projectDirPath = ""; 276 | projectRoot = ""; 277 | targets = ( 278 | 5F5FA60A1CEA2BE2004439D3 /* VIMediaCacheDemo */, 279 | 5F5FA6231CEA2BE2004439D3 /* VIMediaCacheDemoTests */, 280 | ); 281 | }; 282 | /* End PBXProject section */ 283 | 284 | /* Begin PBXResourcesBuildPhase section */ 285 | 5F5FA6091CEA2BE2004439D3 /* Resources */ = { 286 | isa = PBXResourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 5F5FA61E1CEA2BE2004439D3 /* LaunchScreen.storyboard in Resources */, 290 | 5F5FA61B1CEA2BE2004439D3 /* Assets.xcassets in Resources */, 291 | 5F5FA6191CEA2BE2004439D3 /* Main.storyboard in Resources */, 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | 5F5FA6221CEA2BE2004439D3 /* Resources */ = { 296 | isa = PBXResourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | }; 302 | /* End PBXResourcesBuildPhase section */ 303 | 304 | /* Begin PBXSourcesBuildPhase section */ 305 | 5F5FA6071CEA2BE2004439D3 /* Sources */ = { 306 | isa = PBXSourcesBuildPhase; 307 | buildActionMask = 2147483647; 308 | files = ( 309 | 5FDCF2AF1CEBF26800188E0C /* VIMediaDownloader.m in Sources */, 310 | 5FDCF2B21CEBF26800188E0C /* VIResourceLoadingRequestWorker.m in Sources */, 311 | 5F2836BE1D96548E000910CA /* VICacheConfiguration.m in Sources */, 312 | 5F5FA67F1CEA2D95004439D3 /* PlayerView.m in Sources */, 313 | 5F5FA6161CEA2BE2004439D3 /* ViewController.m in Sources */, 314 | 5F2836C11D96548E000910CA /* VIMediaCacheWorker.m in Sources */, 315 | 5F5FA6131CEA2BE2004439D3 /* AppDelegate.m in Sources */, 316 | 5FDCF2B01CEBF26800188E0C /* VIResourceLoader.m in Sources */, 317 | 5FDCF2B11CEBF26800188E0C /* VIResourceLoaderManager.m in Sources */, 318 | 5F2836C01D96548E000910CA /* VICacheSessionManager.m in Sources */, 319 | 5F5FA6101CEA2BE2004439D3 /* main.m in Sources */, 320 | 5FC756B41FC47A4C00608495 /* NSString+VIMD5.m in Sources */, 321 | 5F2836BD1D96548E000910CA /* VICacheAction.m in Sources */, 322 | 5F2836BF1D96548E000910CA /* VICacheManager.m in Sources */, 323 | 5FDCF2AE1CEBF26800188E0C /* VIContentInfo.m in Sources */, 324 | ); 325 | runOnlyForDeploymentPostprocessing = 0; 326 | }; 327 | 5F5FA6201CEA2BE2004439D3 /* Sources */ = { 328 | isa = PBXSourcesBuildPhase; 329 | buildActionMask = 2147483647; 330 | files = ( 331 | 5F5FA6291CEA2BE2004439D3 /* VIMediaCacheDemoTests.m in Sources */, 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | }; 335 | /* End PBXSourcesBuildPhase section */ 336 | 337 | /* Begin PBXTargetDependency section */ 338 | 5F5FA6261CEA2BE2004439D3 /* PBXTargetDependency */ = { 339 | isa = PBXTargetDependency; 340 | target = 5F5FA60A1CEA2BE2004439D3 /* VIMediaCacheDemo */; 341 | targetProxy = 5F5FA6251CEA2BE2004439D3 /* PBXContainerItemProxy */; 342 | }; 343 | /* End PBXTargetDependency section */ 344 | 345 | /* Begin PBXVariantGroup section */ 346 | 5F5FA6171CEA2BE2004439D3 /* Main.storyboard */ = { 347 | isa = PBXVariantGroup; 348 | children = ( 349 | 5F5FA6181CEA2BE2004439D3 /* Base */, 350 | ); 351 | name = Main.storyboard; 352 | sourceTree = ""; 353 | }; 354 | 5F5FA61C1CEA2BE2004439D3 /* LaunchScreen.storyboard */ = { 355 | isa = PBXVariantGroup; 356 | children = ( 357 | 5F5FA61D1CEA2BE2004439D3 /* Base */, 358 | ); 359 | name = LaunchScreen.storyboard; 360 | sourceTree = ""; 361 | }; 362 | /* End PBXVariantGroup section */ 363 | 364 | /* Begin XCBuildConfiguration section */ 365 | 5F5FA62B1CEA2BE2004439D3 /* Debug */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | ALWAYS_SEARCH_USER_PATHS = NO; 369 | CLANG_ANALYZER_NONNULL = YES; 370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 371 | CLANG_CXX_LIBRARY = "libc++"; 372 | CLANG_ENABLE_MODULES = YES; 373 | CLANG_ENABLE_OBJC_ARC = YES; 374 | CLANG_WARN_BOOL_CONVERSION = YES; 375 | CLANG_WARN_CONSTANT_CONVERSION = YES; 376 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 377 | CLANG_WARN_EMPTY_BODY = YES; 378 | CLANG_WARN_ENUM_CONVERSION = YES; 379 | CLANG_WARN_INFINITE_RECURSION = YES; 380 | CLANG_WARN_INT_CONVERSION = YES; 381 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 382 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 386 | COPY_PHASE_STRIP = NO; 387 | DEBUG_INFORMATION_FORMAT = dwarf; 388 | ENABLE_STRICT_OBJC_MSGSEND = YES; 389 | ENABLE_TESTABILITY = YES; 390 | GCC_C_LANGUAGE_STANDARD = gnu99; 391 | GCC_DYNAMIC_NO_PIC = NO; 392 | GCC_NO_COMMON_BLOCKS = YES; 393 | GCC_OPTIMIZATION_LEVEL = 0; 394 | GCC_PREPROCESSOR_DEFINITIONS = ( 395 | "DEBUG=1", 396 | "$(inherited)", 397 | ); 398 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 399 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 400 | GCC_WARN_UNDECLARED_SELECTOR = YES; 401 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 402 | GCC_WARN_UNUSED_FUNCTION = YES; 403 | GCC_WARN_UNUSED_VARIABLE = YES; 404 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 405 | MTL_ENABLE_DEBUG_INFO = YES; 406 | ONLY_ACTIVE_ARCH = YES; 407 | SDKROOT = iphoneos; 408 | }; 409 | name = Debug; 410 | }; 411 | 5F5FA62C1CEA2BE2004439D3 /* Release */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | ALWAYS_SEARCH_USER_PATHS = NO; 415 | CLANG_ANALYZER_NONNULL = YES; 416 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 417 | CLANG_CXX_LIBRARY = "libc++"; 418 | CLANG_ENABLE_MODULES = YES; 419 | CLANG_ENABLE_OBJC_ARC = YES; 420 | CLANG_WARN_BOOL_CONVERSION = YES; 421 | CLANG_WARN_CONSTANT_CONVERSION = YES; 422 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 423 | CLANG_WARN_EMPTY_BODY = YES; 424 | CLANG_WARN_ENUM_CONVERSION = YES; 425 | CLANG_WARN_INFINITE_RECURSION = YES; 426 | CLANG_WARN_INT_CONVERSION = YES; 427 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 428 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 429 | CLANG_WARN_UNREACHABLE_CODE = YES; 430 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 431 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 432 | COPY_PHASE_STRIP = NO; 433 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 434 | ENABLE_NS_ASSERTIONS = NO; 435 | ENABLE_STRICT_OBJC_MSGSEND = YES; 436 | GCC_C_LANGUAGE_STANDARD = gnu99; 437 | GCC_NO_COMMON_BLOCKS = YES; 438 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 439 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 440 | GCC_WARN_UNDECLARED_SELECTOR = YES; 441 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 442 | GCC_WARN_UNUSED_FUNCTION = YES; 443 | GCC_WARN_UNUSED_VARIABLE = YES; 444 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 445 | MTL_ENABLE_DEBUG_INFO = NO; 446 | SDKROOT = iphoneos; 447 | VALIDATE_PRODUCT = YES; 448 | }; 449 | name = Release; 450 | }; 451 | 5F5FA62E1CEA2BE2004439D3 /* Debug */ = { 452 | isa = XCBuildConfiguration; 453 | buildSettings = { 454 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 455 | DEVELOPMENT_TEAM = ZZX7396L9W; 456 | INFOPLIST_FILE = VIMediaCacheDemo/Info.plist; 457 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 458 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 459 | PRODUCT_BUNDLE_IDENTIFIER = com.vito.VIMediaCacheDemo; 460 | PRODUCT_NAME = "$(TARGET_NAME)"; 461 | }; 462 | name = Debug; 463 | }; 464 | 5F5FA62F1CEA2BE2004439D3 /* Release */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 468 | DEVELOPMENT_TEAM = ZZX7396L9W; 469 | INFOPLIST_FILE = VIMediaCacheDemo/Info.plist; 470 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 471 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 472 | PRODUCT_BUNDLE_IDENTIFIER = com.vito.VIMediaCacheDemo; 473 | PRODUCT_NAME = "$(TARGET_NAME)"; 474 | }; 475 | name = Release; 476 | }; 477 | 5F5FA6311CEA2BE2004439D3 /* Debug */ = { 478 | isa = XCBuildConfiguration; 479 | buildSettings = { 480 | BUNDLE_LOADER = "$(TEST_HOST)"; 481 | INFOPLIST_FILE = VIMediaCacheDemoTests/Info.plist; 482 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 483 | PRODUCT_BUNDLE_IDENTIFIER = com.vito.VIMediaCacheDemoTests; 484 | PRODUCT_NAME = "$(TARGET_NAME)"; 485 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VIMediaCacheDemo.app/VIMediaCacheDemo"; 486 | }; 487 | name = Debug; 488 | }; 489 | 5F5FA6321CEA2BE2004439D3 /* Release */ = { 490 | isa = XCBuildConfiguration; 491 | buildSettings = { 492 | BUNDLE_LOADER = "$(TEST_HOST)"; 493 | INFOPLIST_FILE = VIMediaCacheDemoTests/Info.plist; 494 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 495 | PRODUCT_BUNDLE_IDENTIFIER = com.vito.VIMediaCacheDemoTests; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VIMediaCacheDemo.app/VIMediaCacheDemo"; 498 | }; 499 | name = Release; 500 | }; 501 | /* End XCBuildConfiguration section */ 502 | 503 | /* Begin XCConfigurationList section */ 504 | 5F5FA6061CEA2BE2004439D3 /* Build configuration list for PBXProject "VIMediaCacheDemo" */ = { 505 | isa = XCConfigurationList; 506 | buildConfigurations = ( 507 | 5F5FA62B1CEA2BE2004439D3 /* Debug */, 508 | 5F5FA62C1CEA2BE2004439D3 /* Release */, 509 | ); 510 | defaultConfigurationIsVisible = 0; 511 | defaultConfigurationName = Release; 512 | }; 513 | 5F5FA62D1CEA2BE2004439D3 /* Build configuration list for PBXNativeTarget "VIMediaCacheDemo" */ = { 514 | isa = XCConfigurationList; 515 | buildConfigurations = ( 516 | 5F5FA62E1CEA2BE2004439D3 /* Debug */, 517 | 5F5FA62F1CEA2BE2004439D3 /* Release */, 518 | ); 519 | defaultConfigurationIsVisible = 0; 520 | defaultConfigurationName = Release; 521 | }; 522 | 5F5FA6301CEA2BE2004439D3 /* Build configuration list for PBXNativeTarget "VIMediaCacheDemoTests" */ = { 523 | isa = XCConfigurationList; 524 | buildConfigurations = ( 525 | 5F5FA6311CEA2BE2004439D3 /* Debug */, 526 | 5F5FA6321CEA2BE2004439D3 /* Release */, 527 | ); 528 | defaultConfigurationIsVisible = 0; 529 | defaultConfigurationName = Release; 530 | }; 531 | /* End XCConfigurationList section */ 532 | }; 533 | rootObject = 5F5FA6031CEA2BE2004439D3 /* Project object */; 534 | } 535 | -------------------------------------------------------------------------------- /VIMediaCacheDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VIMediaCacheDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 5/17/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /VIMediaCacheDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 5/17/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /VIMediaCacheDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /VIMediaCacheDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /VIMediaCacheDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /VIMediaCacheDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | LSRequiresIPhoneOS 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /VIMediaCacheDemo/PlayerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerView.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 5/17/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | @import AVFoundation; 11 | 12 | @interface PlayerView : UIView 13 | 14 | - (AVPlayerLayer *)playerLayer; 15 | 16 | - (void)setPlayer:(AVPlayer *)player; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /VIMediaCacheDemo/PlayerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerView.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 5/17/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "PlayerView.h" 10 | 11 | @implementation PlayerView 12 | 13 | + (Class)layerClass { 14 | return [AVPlayerLayer class]; 15 | } 16 | 17 | - (AVPlayer*)player { 18 | return [(AVPlayerLayer *)[self layer] player]; 19 | } 20 | 21 | - (void)setPlayer:(AVPlayer *)player { 22 | [(AVPlayerLayer *)[self layer] setPlayer:player]; 23 | } 24 | 25 | - (AVPlayerLayer *)playerLayer 26 | { 27 | return (AVPlayerLayer *)self.layer; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /VIMediaCacheDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 5/17/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /VIMediaCacheDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 5/17/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "VIMediaCache.h" 11 | #import "PlayerView.h" 12 | 13 | @interface ViewController () 14 | 15 | @property (nonatomic, strong) VIResourceLoaderManager *resourceLoaderManager; 16 | @property (nonatomic, strong) AVPlayer *player; 17 | @property (nonatomic, strong) AVPlayerItem *playerItem; 18 | @property (nonatomic, strong) id timeObserver; 19 | @property (nonatomic) CMTime duration; 20 | 21 | @property (weak, nonatomic) IBOutlet PlayerView *playerView; 22 | @property (weak, nonatomic) IBOutlet UISlider *slider; 23 | @property (weak, nonatomic) IBOutlet UILabel *totalTimeLabel; 24 | @property (weak, nonatomic) IBOutlet UILabel *currentTimeLabel; 25 | 26 | @property (nonatomic, strong) VIMediaDownloader *downloader; 27 | 28 | @end 29 | 30 | @implementation ViewController 31 | 32 | - (void)dealloc { 33 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 34 | [self.player removeTimeObserver:self.timeObserver]; 35 | self.timeObserver = nil; 36 | [self.playerItem removeObserver:self forKeyPath:@"status"]; 37 | [self.player removeObserver:self forKeyPath:@"timeControlStatus"]; 38 | } 39 | 40 | - (void)viewDidLoad { 41 | [super viewDidLoad]; 42 | 43 | [self cleanCache]; 44 | 45 | 46 | // NSURL *url = [NSURL URLWithString:@"https://mvvideo5.meitudata.com/56ea0e90d6cb2653.mp4"]; 47 | // VIMediaDownloader *downloader = [[VIMediaDownloader alloc] initWithURL:url]; 48 | // [downloader downloadFromStartToEnd]; 49 | // self.downloader = downloader; 50 | 51 | [self setupPlayer]; 52 | 53 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mediaCacheDidChanged:) name:VICacheManagerDidUpdateCacheNotification object:nil]; 54 | } 55 | 56 | - (void)viewDidAppear:(BOOL)animated { 57 | [super viewDidAppear:animated]; 58 | [self.player play]; 59 | } 60 | 61 | - (void)cleanCache { 62 | unsigned long long fileSize = [VICacheManager calculateCachedSizeWithError:nil]; 63 | NSLog(@"file cache size: %@", @(fileSize)); 64 | NSError *error; 65 | [VICacheManager cleanAllCacheWithError:&error]; 66 | if (error) { 67 | NSLog(@"clean cache failure: %@", error); 68 | } 69 | 70 | [VICacheManager cleanAllCacheWithError:&error]; 71 | } 72 | 73 | - (IBAction)touchSliderAction:(UISlider *)sender { 74 | sender.tag = -1; 75 | } 76 | 77 | - (IBAction)sliderAction:(UISlider *)sender { 78 | CMTime duration = self.player.currentItem.asset.duration; 79 | CMTime seekTo = CMTimeMake((NSInteger)(duration.value * sender.value), duration.timescale); 80 | NSLog(@"seetTo %ld", (long)(duration.value * sender.value) / duration.timescale); 81 | __weak typeof(self)weakSelf = self; 82 | [self.player pause]; 83 | [self.player seekToTime:seekTo completionHandler:^(BOOL finished) { 84 | sender.tag = 0; 85 | [weakSelf.player play]; 86 | }]; 87 | } 88 | 89 | - (IBAction)toggleAction:(id)sender { 90 | [self cleanCache]; 91 | 92 | [self.playerItem removeObserver:self forKeyPath:@"status"]; 93 | [self.player removeObserver:self forKeyPath:@"timeControlStatus"]; 94 | 95 | [self.resourceLoaderManager cancelLoaders]; 96 | 97 | NSURL *url = [NSURL URLWithString:@"https://mvvideo5.meitudata.com/56ea0e90d6cb2653.mp4"]; 98 | AVPlayerItem *playerItem = [self.resourceLoaderManager playerItemWithURL:url]; 99 | self.playerItem = playerItem; 100 | 101 | [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; 102 | [self.player addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil]; 103 | [self.player replaceCurrentItemWithPlayerItem:playerItem]; 104 | } 105 | 106 | #pragma mark - Setup 107 | 108 | - (void)setupPlayer { 109 | 110 | // NSURL *url = [NSURL URLWithString:@"http://gedftnj8mkvfefuaefm.exp.bcevod.com/mda-hc2s2difdjz6c5y9/hd/mda-hc2s2difdjz6c5y9.mp4?playlist%3D%5B%22hd%22%5D&auth_key=1500559192-0-0-dcb501bf19beb0bd4e0f7ad30c380763&bcevod_channel=searchbox_feed&srchid=3ed366b1b0bf70e0&channel_id=2&d_t=2&b_v=9.1.0.0"]; 111 | // NSURL *url = [NSURL URLWithString:@"https://mvvideo5.meitudata.com/56a9e1389b9706520.mp4"]; 112 | NSURL *url = [NSURL URLWithString:@"https://mvvideo5.meitudata.com/56ea0e90d6cb2653.mp4"]; 113 | 114 | VIResourceLoaderManager *resourceLoaderManager = [VIResourceLoaderManager new]; 115 | self.resourceLoaderManager = resourceLoaderManager; 116 | 117 | AVPlayerItem *playerItem = [resourceLoaderManager playerItemWithURL:url]; 118 | self.playerItem = playerItem; 119 | 120 | VICacheConfiguration *configuration = [VICacheManager cacheConfigurationForURL:url]; 121 | if (configuration.progress >= 1.0) { 122 | NSLog(@"cache completed"); 123 | } 124 | 125 | AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem]; 126 | // AVPlayer *player = [AVPlayer playerWithURL:url]; 127 | player.automaticallyWaitsToMinimizeStalling = NO; 128 | self.player = player; 129 | [self.playerView setPlayer:player]; 130 | 131 | 132 | __weak typeof(self)weakSelf = self; 133 | self.timeObserver = 134 | [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 10) 135 | queue:dispatch_queue_create("player.time.queue", NULL) 136 | usingBlock:^(CMTime time) { 137 | dispatch_async(dispatch_get_main_queue(), ^(void) { 138 | if (weakSelf.slider.tag == 0) { 139 | CGFloat duration = CMTimeGetSeconds(weakSelf.player.currentItem.duration); 140 | weakSelf.totalTimeLabel.text = [NSString stringWithFormat:@"%.f", duration]; 141 | CGFloat currentDuration = CMTimeGetSeconds(time); 142 | weakSelf.currentTimeLabel.text = [NSString stringWithFormat:@"%.f", currentDuration]; 143 | weakSelf.slider.value = currentDuration / duration; 144 | } 145 | }); 146 | }]; 147 | 148 | [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; 149 | [self.player addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil]; 150 | 151 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapPlayerViewAction:)]; 152 | [self.playerView addGestureRecognizer:tap]; 153 | } 154 | 155 | - (void)tapPlayerViewAction:(UITapGestureRecognizer *)gesture { 156 | if (gesture.state == UIGestureRecognizerStateEnded) { 157 | if (self.player.rate > 0.0) { 158 | [self.player pause]; 159 | } else { 160 | [self.player play]; 161 | } 162 | } 163 | } 164 | 165 | #pragma mark - KVO 166 | 167 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 168 | change:(NSDictionary *)change context:(void *)context { 169 | if (object == self.playerItem && [keyPath isEqualToString:@"status"]) { 170 | NSLog(@"player status %@, rate %@, error: %@", @(self.playerItem.status), @(self.player.rate), self.playerItem.error); 171 | if (self.playerItem.status == AVPlayerItemStatusReadyToPlay) { 172 | dispatch_async(dispatch_get_main_queue(), ^(void) { 173 | CGFloat duration = CMTimeGetSeconds(self.playerItem.duration); 174 | self.totalTimeLabel.text = [NSString stringWithFormat:@"%.f", duration]; 175 | }); 176 | } else if (self.playerItem.status == AVPlayerItemStatusFailed) { 177 | // something went wrong. player.error should contain some information 178 | NSLog(@"player error %@", self.playerItem.error); 179 | } 180 | } else if (object == self.player && [keyPath isEqualToString:@"timeControlStatus"]) { 181 | NSLog(@"timeControlStatus: %@, reason: %@, rate: %@", @(self.player.timeControlStatus), self.player.reasonForWaitingToPlay, @(self.player.rate)); 182 | } 183 | } 184 | 185 | #pragma mark - notification 186 | 187 | - (void)mediaCacheDidChanged:(NSNotification *)notification { 188 | NSDictionary *userInfo = notification.userInfo; 189 | VICacheConfiguration *configuration = userInfo[VICacheConfigurationKey]; 190 | NSArray *cachedFragments = configuration.cacheFragments; 191 | long long contentLength = configuration.contentInfo.contentLength; 192 | 193 | NSInteger number = 100; 194 | NSMutableString *progressStr = [NSMutableString string]; 195 | 196 | [cachedFragments enumerateObjectsUsingBlock:^(NSValue * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 197 | NSRange range = obj.rangeValue; 198 | 199 | NSInteger location = roundf((range.location / (double)contentLength) * number); 200 | 201 | NSInteger progressCount = progressStr.length; 202 | [self string:progressStr appendString:@"0" muti:location - progressCount]; 203 | 204 | NSInteger length = roundf((range.length / (double)contentLength) * number); 205 | [self string:progressStr appendString:@"1" muti:length]; 206 | 207 | 208 | if (idx == cachedFragments.count - 1 && (location + length) <= number + 1) { 209 | [self string:progressStr appendString:@"0" muti:number - (length + location)]; 210 | } 211 | }]; 212 | 213 | NSLog(@"%@", progressStr); 214 | } 215 | 216 | - (void)string:(NSMutableString *)string appendString:(NSString *)appendString muti:(NSInteger)muti { 217 | for (NSInteger i = 0; i < muti; i++) { 218 | [string appendString:appendString]; 219 | } 220 | } 221 | 222 | @end 223 | -------------------------------------------------------------------------------- /VIMediaCacheDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // VIMediaCacheDemo 4 | // 5 | // Created by Vito on 5/17/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /VIMediaCacheDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /VIMediaCacheDemoTests/VIMediaCacheDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // VIMediaCacheDemoTests.m 3 | // VIMediaCacheDemoTests 4 | // 5 | // Created by Vito on 5/17/16. 6 | // Copyright © 2016 Vito. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "VICacheConfiguration.h" 11 | #import "VIMediaCacheWorker.h" 12 | #import "VICacheAction.h" 13 | 14 | @interface VIMediaCacheDemoTests : XCTestCase 15 | 16 | @end 17 | 18 | @implementation VIMediaCacheDemoTests 19 | 20 | - (void)setUp { 21 | [super setUp]; 22 | // Put setup code here. This method is called before the invocation of each test method in the class. 23 | } 24 | 25 | - (void)tearDown { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | [super tearDown]; 28 | } 29 | - (void)testConfiguration1 { 30 | VICacheConfiguration *configuration = [[VICacheConfiguration alloc] init]; 31 | NSRange range1 = NSMakeRange(10, 10); 32 | [configuration addCacheFragment:range1]; 33 | NSArray *fragments = [configuration cacheFragments]; 34 | XCTAssert(fragments.count == 1 && NSEqualRanges([fragments[0] rangeValue], range1) , @"add (10, 10) to [], should equal [(10, 10)]"); 35 | 36 | [configuration addCacheFragment:range1]; 37 | fragments = [configuration cacheFragments]; 38 | XCTAssert(fragments.count == 1 && NSEqualRanges([fragments[0] rangeValue], range1) , @"add (10, 10) to [(10, 10)], should equal [(10, 10)]"); 39 | 40 | NSRange range0 = NSMakeRange(5, 1); 41 | [configuration addCacheFragment:range0]; 42 | fragments = [configuration cacheFragments]; 43 | XCTAssert(fragments.count == 2 && NSEqualRanges([fragments[0] rangeValue], range0) && NSEqualRanges([fragments[1] rangeValue], range1), @"add (5, 1) to [(10, 10)], should equal [(5, 1), (10, 10)]"); 44 | 45 | NSRange range3 = NSMakeRange(1, 1); 46 | [configuration addCacheFragment:range3]; 47 | fragments = [configuration cacheFragments]; 48 | XCTAssert(fragments.count == 3 && 49 | NSEqualRanges([fragments[0] rangeValue], range3) && 50 | NSEqualRanges([fragments[1] rangeValue], range0) && 51 | NSEqualRanges([fragments[2] rangeValue], range1), 52 | @"add (1, 1) to [(5, 1), (10, 10)], should equal [(1, 1), (5, 1), (10, 10)]"); 53 | 54 | NSRange range4 = NSMakeRange(0, 9); 55 | [configuration addCacheFragment:range4]; 56 | fragments = [configuration cacheFragments]; 57 | XCTAssert(fragments.count == 2 && 58 | NSEqualRanges([fragments[0] rangeValue], NSMakeRange(0, 9)) && 59 | NSEqualRanges([fragments[1] rangeValue], range1), 60 | @"add (0, 9) to [(1, 1), (5, 1), (10, 10)], should equal [(0, 9), (10, 10)]"); 61 | } 62 | 63 | - (void)testConfiguration2 { 64 | VICacheConfiguration *configuration = [[VICacheConfiguration alloc] init]; 65 | NSRange range1 = NSMakeRange(10, 10); 66 | [configuration addCacheFragment:range1]; 67 | NSArray *fragments = [configuration cacheFragments]; 68 | XCTAssert(fragments.count == 1 && NSEqualRanges([fragments[0] rangeValue], range1) , @"add (10, 10) to [], should equal [(10, 10)]"); 69 | 70 | NSRange range2 = NSMakeRange(30, 10); 71 | [configuration addCacheFragment:range2]; 72 | fragments = [configuration cacheFragments]; 73 | XCTAssert(fragments.count == 2 && NSEqualRanges([fragments[0] rangeValue], range1) && NSEqualRanges([fragments[1] rangeValue], range2), @"add (30, 10) to [(10, 10)] should equal [(10, 10), (30, 10)]"); 74 | 75 | NSRange range3 = NSMakeRange(50, 10); 76 | [configuration addCacheFragment:range3]; 77 | fragments = [configuration cacheFragments]; 78 | XCTAssert(fragments.count == 3 && 79 | NSEqualRanges([fragments[0] rangeValue], range1) && 80 | NSEqualRanges([fragments[1] rangeValue], range2) && 81 | NSEqualRanges([fragments[2] rangeValue], range3), 82 | @"add (50, 10) to [(10, 10), (30, 10)] should equal [(10, 10), (30, 10), (50, 10)]"); 83 | 84 | NSRange range4 = NSMakeRange(25, 26); 85 | [configuration addCacheFragment:range4]; 86 | fragments = [configuration cacheFragments]; 87 | XCTAssert(fragments.count == 2 && 88 | NSEqualRanges([fragments[0] rangeValue], range1) && 89 | NSEqualRanges([fragments[1] rangeValue], NSMakeRange(25, 35)), 90 | @"add (25, 26) to [(10, 10), (30, 10), (50, 10)] should equal [(10, 10), (25, 35)]"); 91 | } 92 | 93 | - (void)testCacheWorker { 94 | VIMediaCacheWorker *cacheWorker = [[VIMediaCacheWorker alloc] initWithCacheName:@"test.mp4"]; 95 | 96 | NSArray *startOffsets = @[@(50), @(80), @(200), @(708), @(1024), @(1500)]; 97 | [cacheWorker setCacheResponse:nil]; 98 | 99 | if (!cacheWorker.cachedResponse) { 100 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"testUrl"] 101 | MIMEType:@"mime" 102 | expectedContentLength:2048 103 | textEncodingName:nil]; 104 | [cacheWorker setCacheResponse:response]; 105 | 106 | 107 | for (NSNumber *offset in startOffsets) { 108 | NSString *str = @"ddddddddddddddddddddddddddddddddddddddddd"; // 42 109 | const char *utfStr = [str UTF8String]; 110 | NSData *data = [NSData dataWithBytes:utfStr length:strlen(utfStr) + 1]; 111 | [cacheWorker cacheData:data forRange:NSMakeRange(offset.integerValue, data.length)]; 112 | [cacheWorker save]; 113 | } 114 | } 115 | 116 | NSRange range = NSMakeRange(0, 50); 117 | NSArray *cacheDataActions1 = [cacheWorker cachedDataActionsForRange:range]; 118 | NSArray *expectActions1 = @[ 119 | [[VICacheAction alloc] initWithActionType:VICacheAtionTypeRemote range:range] 120 | ]; 121 | XCTAssert([cacheDataActions1 isEqualToArray:expectActions1], @"cacheDataActions1 count should equal to %@", expectActions1); 122 | 123 | 124 | NSRange range2 = NSMakeRange(51, 204); 125 | NSArray *cacheDataActions2 = [cacheWorker cachedDataActionsForRange:range2]; 126 | XCTAssert(cacheDataActions2.count == 4, @"actions count should equal startoffsets's count"); 127 | 128 | NSRange range3 = NSMakeRange(1300, 300); 129 | NSArray *cacheDataActions3 = [cacheWorker cachedDataActionsForRange:range3]; 130 | NSArray *expectActions3 = @[ 131 | [[VICacheAction alloc] initWithActionType:VICacheAtionTypeRemote range:NSMakeRange(1300, 200)], 132 | [[VICacheAction alloc] initWithActionType:VICacheAtionTypeLocal range:NSMakeRange(1500, 42)], 133 | [[VICacheAction alloc] initWithActionType:VICacheAtionTypeRemote range:NSMakeRange(1542, 58)] 134 | ]; 135 | XCTAssert([cacheDataActions3 isEqualToArray:expectActions3], @"actions count should equal"); 136 | } 137 | 138 | @end 139 | --------------------------------------------------------------------------------