├── .swift-version
├── VIMediaCacheDemo.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── VIMediaCacheDemo
├── ViewController.h
├── AppDelegate.h
├── PlayerView.h
├── main.m
├── PlayerView.m
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── AppDelegate.m
└── ViewController.m
├── VIMediaCache
├── Cache
│ ├── NSString+VIMD5.h
│ ├── VICacheSessionManager.h
│ ├── VICacheAction.h
│ ├── NSString+VIMD5.m
│ ├── VICacheSessionManager.m
│ ├── VIMediaCacheWorker.h
│ ├── VICacheAction.m
│ ├── VICacheConfiguration.h
│ ├── VICacheManager.h
│ ├── VICacheManager.m
│ ├── VIMediaCacheWorker.m
│ └── VICacheConfiguration.m
├── VIMediaCache.h
└── ResourceLoader
│ ├── VIContentInfo.h
│ ├── VIResourceLoader.h
│ ├── VIResourceLoadingRequestWorker.h
│ ├── VIResourceLoaderManager.h
│ ├── VIContentInfo.m
│ ├── VIMediaDownloader.h
│ ├── VIResourceLoadingRequestWorker.m
│ ├── VIResourceLoaderManager.m
│ ├── VIResourceLoader.m
│ └── VIMediaDownloader.m
├── VIMediaCache.podspec
├── VIMediaCacheDemoTests
├── Info.plist
└── VIMediaCacheDemoTests.m
├── README.md
├── LICENSE
└── .gitignore
/.swift-version:
--------------------------------------------------------------------------------
1 | 3.0
2 |
--------------------------------------------------------------------------------
/VIMediaCacheDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/Cache/VICacheAction.h:
--------------------------------------------------------------------------------
1 | //
2 | // VICacheAction.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 | 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.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------