Commit a2fecce1 authored by 曹云霄's avatar 曹云霄

修改项说明:视频缓存处理

parent 81fff7a1
......@@ -10,4 +10,11 @@
@interface OnlineLearningDetailViewController : BaseViewController
/**
学习项ID
*/
@property (nonatomic,copy) NSString *studyTypeID;
@end
......@@ -22,6 +22,7 @@
[super viewDidLoad];
[self addChildViewController];
[self getStudyItemDetailAction];
}
#pragma mark -
......@@ -29,7 +30,7 @@
{
//播放窗口
VideoHelperViewController *videoWindow = [[[self class] getLearningCenterStoryboardClass] instantiateViewControllerWithIdentifier:@"VideoHelperViewController"];
videoWindow.videoUrl = @"http://gomoretech1.oss-cn-shanghai.aliyuncs.com/211534962/studyingSkills/coffee.mp4";
videoWindow.videoUrl = @"http://mpvideo-test.b0.upaiyun.com/5813998fb092e5771.mp4";
videoWindow.view.frame = CGRectMake(0, NavigationHeight, ScreenWidth*2/3, ScreenHeight/2);
[self addChildViewController:videoWindow];
[self.view addSubview:videoWindow.view];
......@@ -63,4 +64,26 @@
}];
}
#pragma mark - 获取学习项详情
- (void)getStudyItemDetailAction
{
WS(weakSelf);
NSString *url = [NSString stringWithFormat:SERVERREQUESTURL(STUDYITEMDETAIL),self.studyTypeID];
[[NetworkRequestClassManager Manager] NetworkWithDictionaryRequestWithURL:url WithCallClass:weakSelf WithRequestType:ONE WithParameter:nil WithReturnValueBlock:^(id returnValue) {
NSLog(@"%@",returnValue);
} WithErrorCodeBlock:^(id errorCodeValue) {
} WithFailureBlock:^(NSError *error) {
}];
}
@end
......@@ -11,8 +11,19 @@
@interface OnlineLearningTableViewCell : UICollectionViewCell
/**
类型imageView
*/
@property (weak, nonatomic) IBOutlet UIImageView *studyItemImageView;
/**
类型title
*/
@property (weak, nonatomic) IBOutlet UILabel *studyItemTitleLabel;
/**
学习类型
*/
@property (nonatomic,strong) TOStudyTypeEntity *studeType;
@end
......@@ -16,4 +16,11 @@
}
- (void)setStudeType:(TOStudyTypeEntity *)studeType
{
_studeType = studeType;
[self.studyItemImageView sd_setImageWithURL:[NSURL URLWithString:_studeType.attachment.thumbnailUrl] placeholderImage:REPLACEIMAGE];
self.studyItemTitleLabel.text = _studeType.name;
}
@end
......@@ -46,7 +46,8 @@
[weakSelf RemoveMBProgressHUDLoding];
if ([returnValue[@"code"] isEqualToNumber:@0]) {
[weakSelf.studyTypeArray addObjectsFromArray:returnValue[@"data"]];
StudyTypeResponse *response = [[StudyTypeResponse alloc]initWithDictionary:returnValue[@"data"] error:nil];
[weakSelf.studyTypeArray addObjectsFromArray:response.types];
}else {
[weakSelf ErrorMBProgressView:returnValue[@"message"]];
}
......@@ -62,19 +63,20 @@
#pragma mark - <UICollectionViewDelegate,UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.studyTypeArray.count+1;
return self.studyTypeArray.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
OnlineLearningTableViewCell *learningCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"OnlineLearningTableViewCell" forIndexPath:indexPath];
// learningCell.studyItemTitleLabel.text = self.studyTypeArray[indexPath.row];
learningCell.studeType = self.studyTypeArray[indexPath.row];
return learningCell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
OnlineLearningDetailViewController *studyDetail = [[[self class] getLearningCenterStoryboardClass]instantiateViewControllerWithIdentifier:@"OnlineLearningDetailViewController"];
studyDetail.studyTypeID = [self.studyTypeArray[indexPath.row] fid];
[self.navigationController pushViewController:studyDetail animated:YES];
}
......
......@@ -7,7 +7,7 @@
//
#import "VideoHelperViewController.h"
#import "VIMediaCache.h"
@interface VideoHelperViewController ()
......@@ -15,6 +15,7 @@
@property (nonatomic,strong) AVPlayer *customPlayer;
@property (nonatomic,strong) AVPlayerItem *playerItem;
@property (nonatomic,strong) id avplayerServer;
@property (nonatomic,strong) VIResourceLoaderManager *resourceLoaderManager;
/**
导航栏、工具类是否隐藏
......@@ -45,12 +46,19 @@
#pragma mark - SetUp AVPlayer
- (void)setUpAVPlayer
{
_playerItem = [[AVPlayerItem alloc]initWithURL:[NSURL URLWithString:self.videoUrl]];
_customPlayer = [[AVPlayer alloc] initWithPlayerItem:_playerItem];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_customPlayer];
_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_playerLayer.frame = CGRectMake(0, 0, ScreenWidth*2/3, ScreenHeight/2);
[self.view.layer insertSublayer:_playerLayer atIndex:0];
VIResourceLoaderManager *resourceLoaderManager = [VIResourceLoaderManager new];
self.resourceLoaderManager = resourceLoaderManager;
self.playerItem = [resourceLoaderManager playerItemWithURL:[NSURL URLWithString:self.videoUrl]];
VICacheConfiguration *configuration = [VICacheManager cacheConfigurationForURL:[NSURL URLWithString:self.videoUrl]];
if (configuration.progress >= 1.0) {
NSLog(@"缓存完成");
}
self.customPlayer = [[AVPlayer alloc] initWithPlayerItem:_playerItem];
self.customPlayer.automaticallyWaitsToMinimizeStalling = YES;
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:_customPlayer];
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
self.playerLayer.frame = CGRectMake(0, 0, ScreenWidth*2/3, ScreenHeight/2);
[self.view.layer insertSublayer:self.playerLayer atIndex:0];
[self CreateMBProgressHUDLoding];
//设置静音模式播放声音
AVAudioSession * session = [AVAudioSession sharedInstance];
......
......@@ -203,7 +203,6 @@
//设置检测用户标示
[Bugly setUserIdentifier:self.userName.text];
//保持导购信息
LoginResult *result = [[LoginResult alloc]initWithDictionary:returnValue[@"data"] error:nil];
[Shoppersmanager manager].Shoppers = result;
......
This diff is collapsed.
......@@ -332,6 +332,11 @@ extern NSString *const STUDYTYPES;
* 论坛讨论项
*/
extern NSString *const FORUMTYPS;
/**
* 学习项详情
*/
extern NSString *const STUDYITEMDETAIL;
/*****************************************接口地址*****************************************/
......
......@@ -326,6 +326,11 @@ NSString *const STUDYTYPES = @"/study/getTypes";
* 论坛讨论项
*/
NSString *const FORUMTYPS = @"/forum/getCategorys";
/**
* 学习项详情
*/
NSString *const STUDYITEMDETAIL = @"/study/getTask/%@";
/*****************************************接口地址*****************************************/
......
//
// VICacheAction.h
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, VICacheAtionType) {
VICacheAtionTypeLocal = 0,
VICacheAtionTypeRemote
};
@interface VICacheAction : NSObject
- (instancetype)initWithActionType:(VICacheAtionType)actionType range:(NSRange)range;
@property (nonatomic) VICacheAtionType actionType;
@property (nonatomic) NSRange range;
@end
//
// VICacheAction.m
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import "VICacheAction.h"
@implementation VICacheAction
- (instancetype)initWithActionType:(VICacheAtionType)actionType range:(NSRange)range {
self = [super init];
if (self) {
_actionType = actionType;
_range = range;
}
return self;
}
- (BOOL)isEqual:(VICacheAction *)object {
if (!NSEqualRanges(object.range, self.range)) {
return NO;
}
if (object.actionType != self.actionType) {
return NO;
}
return YES;
}
- (NSUInteger)hash {
return [[NSString stringWithFormat:@"%@%@", NSStringFromRange(self.range), @(self.actionType)] hash];
}
- (NSString *)description {
return [NSString stringWithFormat:@"actionType %@, range: %@", @(self.actionType), NSStringFromRange(self.range)];
}
@end
//
// VICacheConfiguration.h
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "VIContentInfo.h"
@interface VICacheConfiguration : NSObject <NSCopying>
+ (NSString *)configurationFilePathForFilePath:(NSString *)filePath;
+ (instancetype)configurationWithFilePath:(NSString *)filePath;
@property (nonatomic, copy, readonly) NSString *filePath;
@property (nonatomic, strong) VIContentInfo *contentInfo;
@property (nonatomic, strong) NSURL *url;
- (NSArray<NSValue *> *)cacheFragments;
/**
* cached progress
*/
@property (nonatomic, readonly) float progress;
@property (nonatomic, readonly) long long downloadedBytes;
@property (nonatomic, readonly) float downloadSpeed; // kb/s
#pragma mark - update API
- (void)save;
- (void)addCacheFragment:(NSRange)fragment;
/**
* Record the download speed
*/
- (void)addDownloadedBytes:(long long)bytes spent:(NSTimeInterval)time;
@end
@interface VICacheConfiguration (VIConvenient)
+ (BOOL)createAndSaveDownloadedConfigurationForURL:(NSURL *)url error:(NSError **)error;
@end
//
// VICacheConfiguration.m
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import "VICacheConfiguration.h"
#import "VICacheManager.h"
#import <MobileCoreServices/MobileCoreServices.h>
static NSString *kFileNameKey = @"kFileNameKey";
static NSString *kCacheFragmentsKey = @"kCacheFragmentsKey";
static NSString *kDownloadInfoKey = @"kDownloadInfoKey";
static NSString *kContentInfoKey = @"kContentInfoKey";
static NSString *kURLKey = @"kURLKey";
@interface VICacheConfiguration () <NSCoding>
@property (nonatomic, copy) NSString *filePath;
@property (nonatomic, copy) NSString *fileName;
@property (nonatomic, copy) NSArray<NSValue *> *internalCacheFragments;
@property (nonatomic, copy) NSArray *downloadInfo;
@end
@implementation VICacheConfiguration
+ (instancetype)configurationWithFilePath:(NSString *)filePath {
filePath = [self configurationFilePathForFilePath:filePath];
VICacheConfiguration *configuration = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
if (!configuration) {
configuration = [[VICacheConfiguration alloc] init];
configuration.fileName = [filePath lastPathComponent];
}
configuration.filePath = filePath;
return configuration;
}
+ (NSString *)configurationFilePathForFilePath:(NSString *)filePath {
return [filePath stringByAppendingPathExtension:@"mt_cfg"];
}
- (NSArray<NSValue *> *)internalCacheFragments {
if (!_internalCacheFragments) {
_internalCacheFragments = [NSArray array];
}
return _internalCacheFragments;
}
- (NSArray *)downloadInfo {
if (!_downloadInfo) {
_downloadInfo = [NSArray array];
}
return _downloadInfo;
}
- (NSArray<NSValue *> *)cacheFragments {
return [_internalCacheFragments copy];
}
- (float)progress {
float progress = self.downloadedBytes / (float)self.contentInfo.contentLength;
return progress;
}
- (long long)downloadedBytes {
float bytes = 0;
@synchronized (self.internalCacheFragments) {
for (NSValue *range in self.internalCacheFragments) {
bytes += range.rangeValue.length;
}
}
return bytes;
}
- (float)downloadSpeed {
long long bytes = 0;
NSTimeInterval time = 0;
@synchronized (self.downloadInfo) {
for (NSArray *a in self.downloadInfo) {
bytes += [[a firstObject] longLongValue];
time += [[a lastObject] doubleValue];
}
}
return bytes / 1024.0 / time;
}
#pragma mark - NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.fileName forKey:kFileNameKey];
[aCoder encodeObject:self.internalCacheFragments forKey:kCacheFragmentsKey];
[aCoder encodeObject:self.downloadInfo forKey:kDownloadInfoKey];
[aCoder encodeObject:self.contentInfo forKey:kContentInfoKey];
[aCoder encodeObject:self.url forKey:kURLKey];
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
_fileName = [aDecoder decodeObjectForKey:kFileNameKey];
_internalCacheFragments = [[aDecoder decodeObjectForKey:kCacheFragmentsKey] mutableCopy];
if (!_internalCacheFragments) {
_internalCacheFragments = [NSArray array];
}
_downloadInfo = [aDecoder decodeObjectForKey:kDownloadInfoKey];
_contentInfo = [aDecoder decodeObjectForKey:kContentInfoKey];
_url = [aDecoder decodeObjectForKey:kURLKey];
}
return self;
}
#pragma mark - NSCopying
- (id)copyWithZone:(nullable NSZone *)zone {
VICacheConfiguration *configuration = [[VICacheConfiguration allocWithZone:zone] init];
configuration.fileName = self.fileName;
configuration.filePath = self.filePath;
configuration.internalCacheFragments = self.internalCacheFragments;
configuration.downloadInfo = self.downloadInfo;
configuration.url = self.url;
configuration.contentInfo = self.contentInfo;
return configuration;
}
#pragma mark - Update
- (void)save {
@synchronized (self.internalCacheFragments) {
[NSKeyedArchiver archiveRootObject:self toFile:self.filePath];
}
}
- (void)addCacheFragment:(NSRange)fragment {
if (fragment.location == NSNotFound || fragment.length == 0) {
return;
}
@synchronized (self.internalCacheFragments) {
NSMutableArray *internalCacheFragments = [self.internalCacheFragments mutableCopy];
NSValue *fragmentValue = [NSValue valueWithRange:fragment];
NSInteger count = self.internalCacheFragments.count;
if (count == 0) {
[internalCacheFragments addObject:fragmentValue];
} else {
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
[internalCacheFragments enumerateObjectsUsingBlock:^(NSValue * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSRange range = obj.rangeValue;
if ((fragment.location + fragment.length) <= range.location) {
if (indexSet.count == 0) {
[indexSet addIndex:idx];
}
*stop = YES;
} else if (fragment.location <= (range.location + range.length) && (fragment.location + fragment.length) > range.location) {
[indexSet addIndex:idx];
} else if (fragment.location >= range.location + range.length) {
if (idx == count - 1) { // Append to last index
[indexSet addIndex:idx];
}
}
}];
if (indexSet.count > 1) {
NSRange firstRange = self.internalCacheFragments[indexSet.firstIndex].rangeValue;
NSRange lastRange = self.internalCacheFragments[indexSet.lastIndex].rangeValue;
NSInteger location = MIN(firstRange.location, fragment.location);
NSInteger endOffset = MAX(lastRange.location + lastRange.length, fragment.location + fragment.length);
NSRange combineRange = NSMakeRange(location, endOffset - location);
[internalCacheFragments removeObjectsAtIndexes:indexSet];
[internalCacheFragments insertObject:[NSValue valueWithRange:combineRange] atIndex:indexSet.firstIndex];
} else if (indexSet.count == 1) {
NSRange firstRange = self.internalCacheFragments[indexSet.firstIndex].rangeValue;
NSRange expandFirstRange = NSMakeRange(firstRange.location, firstRange.length + 1);
NSRange expandFragmentRange = NSMakeRange(fragment.location, fragment.length + 1);
NSRange intersectionRange = NSIntersectionRange(expandFirstRange, expandFragmentRange);
if (intersectionRange.length > 0) { // Should combine
NSInteger location = MIN(firstRange.location, fragment.location);
NSInteger endOffset = MAX(firstRange.location + firstRange.length, fragment.location + fragment.length);
NSRange combineRange = NSMakeRange(location, endOffset - location);
[internalCacheFragments removeObjectAtIndex:indexSet.firstIndex];
[internalCacheFragments insertObject:[NSValue valueWithRange:combineRange] atIndex:indexSet.firstIndex];
} else {
if (firstRange.location > fragment.location) {
[internalCacheFragments insertObject:fragmentValue atIndex:[indexSet lastIndex]];
} else {
[internalCacheFragments insertObject:fragmentValue atIndex:[indexSet lastIndex] + 1];
}
}
}
}
self.internalCacheFragments = [internalCacheFragments copy];
}
}
- (void)addDownloadedBytes:(long long)bytes spent:(NSTimeInterval)time {
@synchronized (self.downloadInfo) {
self.downloadInfo = [self.downloadInfo arrayByAddingObject:@[@(bytes), @(time)]];
}
}
@end
@implementation VICacheConfiguration (VIConvenient)
+ (BOOL)createAndSaveDownloadedConfigurationForURL:(NSURL *)url error:(NSError **)error {
NSString *filePath = [VICacheManager cachedFilePathForURL:url];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary<NSFileAttributeKey, id> *attributes = [fileManager attributesOfItemAtPath:filePath error:error];
if (!attributes) {
return NO;
}
long long fileSize = attributes.fileSize;
NSRange range = NSMakeRange(0, fileSize);
VICacheConfiguration *configuration = [VICacheConfiguration configurationWithFilePath:filePath];
configuration.url = url;
VIContentInfo *contentInfo = [VIContentInfo new];
NSString *fileExtension = [url pathExtension];
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
if (!contentType) {
contentType = @"application/octet-stream";
}
contentInfo.contentType = contentType;
contentInfo.contentLength = fileSize;
contentInfo.byteRangeAccessSupported = YES;
contentInfo.downloadedContentLength = fileSize;
configuration.contentInfo = contentInfo;
[configuration addCacheFragment:range];
[configuration save];
return YES;
}
@end
//
// VICacheManager.h
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "VICacheConfiguration.h"
extern NSString *VICacheManagerDidUpdateCacheNotification;
extern NSString *VICacheManagerDidFinishCacheNotification;
extern NSString *VICacheConfigurationKey;
extern NSString *VICacheFinishedErrorKey;
@interface VICacheManager : NSObject
+ (void)setCacheDirectory:(NSString *)cacheDirectory;
+ (NSString *)cacheDirectory;
/**
How often trigger `VICacheManagerDidUpdateCacheNotification` notification
@param interval Minimum interval
*/
+ (void)setCacheUpdateNotifyInterval:(NSTimeInterval)interval;
+ (NSTimeInterval)cacheUpdateNotifyInterval;
+ (NSString *)cachedFilePathForURL:(NSURL *)url;
+ (VICacheConfiguration *)cacheConfigurationForURL:(NSURL *)url;
/**
Calculate cached files size
@param error
@return files size, respresent by `byte`, if error occurs, return -1
*/
+ (unsigned long long)calculateCachedSizeWithError:(NSError **)error;
+ (void)cleanAllCacheWithError:(NSError **)error;
+ (void)cleanCacheForURL:(NSURL *)url error:(NSError **)error;
/**
Useful when you upload a local file to the server
@param filePath local file path
@param url remote resource url
@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.
*/
+ (BOOL)addCacheFile:(NSString *)filePath forURL:(NSURL *)url error:(NSError **)error;
@end
//
// VICacheManager.m
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import "VICacheManager.h"
#import "VIMediaDownloader.h"
NSString *VICacheManagerDidUpdateCacheNotification = @"VICacheManagerDidUpdateCacheNotification";
NSString *VICacheManagerDidFinishCacheNotification = @"VICacheManagerDidFinishCacheNotification";
NSString *VICacheConfigurationKey = @"VICacheConfigurationKey";
NSString *VICacheFinishedErrorKey = @"VICacheFinishedErrorKey";
static NSString *kMCMediaCacheDirectory;
static NSTimeInterval kMCMediaCacheNotifyInterval;
@implementation VICacheManager
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self setCacheDirectory:[NSTemporaryDirectory() stringByAppendingPathComponent:@"vimedia"]];
[self setCacheUpdateNotifyInterval:0.1];
});
}
+ (void)setCacheDirectory:(NSString *)cacheDirectory {
kMCMediaCacheDirectory = cacheDirectory;
}
+ (NSString *)cacheDirectory {
return kMCMediaCacheDirectory;
}
+ (void)setCacheUpdateNotifyInterval:(NSTimeInterval)interval {
kMCMediaCacheNotifyInterval = interval;
}
+ (NSTimeInterval)cacheUpdateNotifyInterval {
return kMCMediaCacheNotifyInterval;
}
+ (NSString *)cachedFilePathForURL:(NSURL *)url {
return [[self cacheDirectory] stringByAppendingPathComponent:[url lastPathComponent]];
}
+ (VICacheConfiguration *)cacheConfigurationForURL:(NSURL *)url {
NSString *filePath = [self cachedFilePathForURL:url];
VICacheConfiguration *configuration = [VICacheConfiguration configurationWithFilePath:filePath];
return configuration;
}
+ (unsigned long long)calculateCachedSizeWithError:(NSError **)error {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *cacheDirectory = [self cacheDirectory];
NSArray *files = [fileManager contentsOfDirectoryAtPath:cacheDirectory error:error];
unsigned long long size = 0;
if (files) {
for (NSString *path in files) {
NSString *filePath = [cacheDirectory stringByAppendingPathComponent:path];
NSDictionary<NSFileAttributeKey, id> *attribute = [fileManager attributesOfItemAtPath:filePath error:error];
if (!attribute) {
size = -1;
break;
}
size += [attribute fileSize];
}
}
return size;
}
+ (void)cleanAllCacheWithError:(NSError **)error {
// Find downloaing file
NSMutableSet *downloadingFiles = [NSMutableSet set];
[[[VIMediaDownloaderStatus shared] urls] enumerateObjectsUsingBlock:^(NSURL * _Nonnull obj, BOOL * _Nonnull stop) {
NSString *file = [self cachedFilePathForURL:obj];
[downloadingFiles addObject:file];
NSString *configurationPath = [VICacheConfiguration configurationFilePathForFilePath:file];
[downloadingFiles addObject:configurationPath];
}];
// Remove files
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *cacheDirectory = [self cacheDirectory];
NSArray *files = [fileManager contentsOfDirectoryAtPath:cacheDirectory error:error];
if (files) {
for (NSString *path in files) {
NSString *filePath = [cacheDirectory stringByAppendingPathComponent:path];
if ([downloadingFiles containsObject:filePath]) {
continue;
}
if (![fileManager removeItemAtPath:filePath error:error]) {
break;
}
}
}
}
+ (void)cleanCacheForURL:(NSURL *)url error:(NSError **)error {
if ([[VIMediaDownloaderStatus shared] containsURL:url]) {
NSString *description = [NSString stringWithFormat:NSLocalizedString(@"Clean cache for url `%@` can't be done, because it's downloading", nil), url];
*error = [NSError errorWithDomain:@"com.mediadownload" code:2 userInfo:@{NSLocalizedDescriptionKey: description}];
return;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *filePath = [self cachedFilePathForURL:url];
if ([fileManager fileExistsAtPath:filePath]) {
if (![fileManager removeItemAtPath:filePath error:error]) {
return;
}
}
NSString *configurationPath = [VICacheConfiguration configurationFilePathForFilePath:filePath];
if ([fileManager fileExistsAtPath:configurationPath]) {
if (![fileManager removeItemAtPath:configurationPath error:error]) {
return;
}
}
}
+ (BOOL)addCacheFile:(NSString *)filePath forURL:(NSURL *)url error:(NSError **)error {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *cachePath = [VICacheManager cachedFilePathForURL:url];
NSString *cacheFolder = [cachePath stringByDeletingLastPathComponent];
if (![fileManager fileExistsAtPath:cacheFolder]) {
if (![fileManager createDirectoryAtPath:cacheFolder
withIntermediateDirectories:YES
attributes:nil
error:error]) {
return NO;
}
}
if (![fileManager copyItemAtPath:filePath toPath:cachePath error:error]) {
return NO;
}
if (![VICacheConfiguration createAndSaveDownloadedConfigurationForURL:url error:error]) {
[fileManager removeItemAtPath:cachePath error:nil]; // if remove failed, there is nothing we can do.
return NO;
}
return YES;
}
@end
//
// VICacheSessionManager.h
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface VICacheSessionManager : NSObject
@property (nonatomic, strong, readonly) NSOperationQueue *downloadQueue;
+ (instancetype)shared;
@end
//
// VICacheSessionManager.m
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import "VICacheSessionManager.h"
@interface VICacheSessionManager ()
@property (nonatomic, strong) NSOperationQueue *downloadQueue;
@end
@implementation VICacheSessionManager
+ (instancetype)shared {
static id instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"com.vimediacache.download";
_downloadQueue = queue;
}
return self;
}
@end
//
// VIMediaCacheWorker.h
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "VICacheConfiguration.h"
@class VICacheAction;
@interface VIMediaCacheWorker : NSObject
- (instancetype)initWithURL:(NSURL *)url;
@property (nonatomic, strong, readonly) VICacheConfiguration *cacheConfiguration;
@property (nonatomic, strong, readonly) NSError *setupError; // Create fileHandler error, can't save/use cache
- (void)cacheData:(NSData *)data forRange:(NSRange)range;
- (NSArray<VICacheAction *> *)cachedDataActionsForRange:(NSRange)range;
- (NSData *)cachedDataForRange:(NSRange)range;
- (void)setContentInfo:(VIContentInfo *)contentInfo;
- (void)save;
- (void)startWritting;
- (void)finishWritting;
@end
//
// VIMediaCacheWorker.m
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import "VIMediaCacheWorker.h"
#import "VICacheAction.h"
#import "VICacheManager.h"
@import UIKit;
static NSInteger const kPackageLength = 204800; // 200kb per package
static NSString *kMCMediaCacheResponseKey = @"kMCMediaCacheResponseKey";
@interface VIMediaCacheWorker ()
@property (nonatomic, strong) NSFileHandle *readFileHandle;
@property (nonatomic, strong) NSFileHandle *writeFileHandle;
@property (nonatomic, strong, readwrite) NSError *setupError;
@property (nonatomic, copy) NSString *filePath;
@property (nonatomic, strong) VICacheConfiguration *internalCacheConfiguration;
@property (nonatomic) long long currentOffset;
@property (nonatomic, strong) NSDate *startWriteDate;
@property (nonatomic) float writeBytes;
@property (nonatomic) BOOL writting;
@end
@implementation VIMediaCacheWorker
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self save];
[_readFileHandle closeFile];
[_writeFileHandle closeFile];
}
- (instancetype)initWithURL:(NSURL *)url {
self = [super init];
if (self) {
NSString *path = [VICacheManager cachedFilePathForURL:url];
NSFileManager *fileManager = [NSFileManager defaultManager];
_filePath = path;
NSError *error;
NSString *cacheFolder = [path stringByDeletingLastPathComponent];
if (![fileManager fileExistsAtPath:cacheFolder]) {
[fileManager createDirectoryAtPath:cacheFolder
withIntermediateDirectories:YES
attributes:nil
error:&error];
}
if (!error) {
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil];
}
NSURL *fileURL = [NSURL fileURLWithPath:path];
_readFileHandle = [NSFileHandle fileHandleForReadingFromURL:fileURL error:&error];
if (!error) {
_writeFileHandle = [NSFileHandle fileHandleForWritingToURL:fileURL error:&error];
_internalCacheConfiguration = [VICacheConfiguration configurationWithFilePath:path];
_internalCacheConfiguration.url = url;
}
}
_setupError = error;
}
return self;
}
- (VICacheConfiguration *)cacheConfiguration {
return self.internalCacheConfiguration;
}
- (void)cacheData:(NSData *)data forRange:(NSRange)range {
@synchronized(self.writeFileHandle) {
@try {
[self.writeFileHandle seekToFileOffset:range.location];
[self.writeFileHandle writeData:data];
self.writeBytes += data.length;
[self.internalCacheConfiguration addCacheFragment:range];
} @catch (NSException *exception) {
NSLog(@"write to file error");
}
}
}
- (NSData *)cachedDataForRange:(NSRange)range {
@synchronized(self.readFileHandle) {
@try {
[self.readFileHandle seekToFileOffset:range.location];
NSData *data = [self.readFileHandle readDataOfLength:range.length]; // 空数据也会返回,所以如果 range 错误,会导致播放失效
return data;
} @catch (NSException *exception) {
NSLog(@"read cached data error %@",exception);
}
}
return nil;
}
- (NSArray<VICacheAction *> *)cachedDataActionsForRange:(NSRange)range {
NSArray *cachedFragments = [self.internalCacheConfiguration cacheFragments];
NSMutableArray *actions = [NSMutableArray array];
if (range.location == NSNotFound) {
return [actions copy];
}
NSInteger endOffset = range.location + range.length;
// Delete header and footer not in range
[cachedFragments enumerateObjectsUsingBlock:^(NSValue * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSRange fragmentRange = obj.rangeValue;
NSRange intersectionRange = NSIntersectionRange(range, fragmentRange);
if (intersectionRange.length > 0) {
NSInteger package = intersectionRange.length / kPackageLength;
for (NSInteger i = 0; i <= package; i++) {
VICacheAction *action = [VICacheAction new];
action.actionType = VICacheAtionTypeLocal;
NSInteger offset = i * kPackageLength;
NSInteger offsetLocation = intersectionRange.location + offset;
NSInteger maxLocation = intersectionRange.location + intersectionRange.length;
NSInteger length = (offsetLocation + kPackageLength) > maxLocation ? (maxLocation - offsetLocation) : kPackageLength;
action.range = NSMakeRange(offsetLocation, length);
[actions addObject:action];
}
} else if (fragmentRange.location >= endOffset) {
*stop = YES;
}
}];
if (actions.count == 0) {
VICacheAction *action = [VICacheAction new];
action.actionType = VICacheAtionTypeRemote;
action.range = range;
[actions addObject:action];
} else {
// Add remote fragments
NSMutableArray *localRemoteActions = [NSMutableArray array];
[actions enumerateObjectsUsingBlock:^(VICacheAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSRange actionRange = obj.range;
if (idx == 0) {
if (range.location < actionRange.location) {
VICacheAction *action = [VICacheAction new];
action.actionType = VICacheAtionTypeRemote;
action.range = NSMakeRange(range.location, actionRange.location - range.location);
[localRemoteActions addObject:action];
}
[localRemoteActions addObject:obj];
} else {
VICacheAction *lastAction = [localRemoteActions lastObject];
NSInteger lastOffset = lastAction.range.location + lastAction.range.length;
if (actionRange.location > lastOffset) {
VICacheAction *action = [VICacheAction new];
action.actionType = VICacheAtionTypeRemote;
action.range = NSMakeRange(lastOffset, actionRange.location - lastOffset);
[localRemoteActions addObject:action];
}
[localRemoteActions addObject:obj];
}
if (idx == actions.count - 1) {
NSInteger localEndOffset = actionRange.location + actionRange.length;
if (endOffset > localEndOffset) {
VICacheAction *action = [VICacheAction new];
action.actionType = VICacheAtionTypeRemote;
action.range = NSMakeRange(localEndOffset, endOffset - localEndOffset);
[localRemoteActions addObject:action];
}
}
}];
actions = localRemoteActions;
}
return [actions copy];
}
- (void)setContentInfo:(VIContentInfo *)contentInfo {
self.internalCacheConfiguration.contentInfo = contentInfo;
[self.writeFileHandle truncateFileAtOffset:contentInfo.contentLength];
[self.writeFileHandle synchronizeFile];
}
- (void)save {
@synchronized (self.writeFileHandle) {
[self.writeFileHandle synchronizeFile];
[self.internalCacheConfiguration save];
}
}
- (void)startWritting {
if (!self.writting) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
self.writting = YES;
self.startWriteDate = [NSDate date];
self.writeBytes = 0;
}
- (void)finishWritting {
if (self.writting) {
self.writting = NO;
[[NSNotificationCenter defaultCenter] removeObserver:self];
NSTimeInterval time = [[NSDate date] timeIntervalSinceDate:self.startWriteDate];
[self.internalCacheConfiguration addDownloadedBytes:self.writeBytes spent:time];
}
}
#pragma mark - Notification
- (void)applicationDidEnterBackground:(NSNotification *)notification {
[self save];
}
@end
//
// VIContentInfo.h
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface VIContentInfo : NSObject <NSCoding>
@property (nonatomic, copy) NSString *contentType;
@property (nonatomic, assign) BOOL byteRangeAccessSupported;
@property (nonatomic, assign) unsigned long long contentLength;
@property (nonatomic) unsigned long long downloadedContentLength;
@end
//
// VIContentInfo.m
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import "VIContentInfo.h"
static NSString *kContentLengthKey = @"kContentLengthKey";
static NSString *kContentTypeKey = @"kContentTypeKey";
static NSString *kByteRangeAccessSupported = @"kByteRangeAccessSupported";
@implementation VIContentInfo
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"%@\ncontentLength: %lld\ncontentType: %@\nbyteRangeAccessSupported:%@", NSStringFromClass([self class]), self.contentLength, self.contentType, @(self.byteRangeAccessSupported)];
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(self.contentLength) forKey:kContentLengthKey];
[aCoder encodeObject:self.contentType forKey:kContentTypeKey];
[aCoder encodeObject:@(self.byteRangeAccessSupported) forKey:kByteRangeAccessSupported];
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
_contentLength = [[aDecoder decodeObjectForKey:kContentLengthKey] longLongValue];
_contentType = [aDecoder decodeObjectForKey:kContentTypeKey];
_byteRangeAccessSupported = [[aDecoder decodeObjectForKey:kByteRangeAccessSupported] boolValue];
}
return self;
}
@end
//
// VIMediaDownloader.h
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol MediaDownloaderDelegate;
@class VIContentInfo;
@interface VIMediaDownloaderStatus : NSObject
+ (instancetype)shared;
/**
return YES if downloading the url source
*/
- (BOOL)containsURL:(NSURL *)url;
- (NSSet *)urls;
@end
@interface VIMediaDownloader : NSObject
- (instancetype)initWithURL:(NSURL *)url;
@property (nonatomic, strong, readonly) NSURL *url;
@property (nonatomic, weak) id<MediaDownloaderDelegate> delegate;
@property (nonatomic, strong) VIContentInfo *info;
- (void)downloadTaskFromOffset:(unsigned long long)fromOffset
length:(NSInteger)length
toEnd:(BOOL)toEnd;
- (void)downloadFromStartToEnd;
- (void)cancel;
- (void)invalidateAndCancel;
@end
@protocol MediaDownloaderDelegate <NSObject>
@optional
- (void)mediaDownloader:(VIMediaDownloader *)downloader didReceiveResponse:(NSURLResponse *)response;
- (void)mediaDownloader:(VIMediaDownloader *)downloader didReceiveData:(NSData *)data;
- (void)mediaDownloader:(VIMediaDownloader *)downloader didFinishedWithError:(NSError *)error;
@end
This diff is collapsed.
//
// VIResoureLoader.h
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import <Foundation/Foundation.h>
@import AVFoundation;
@protocol VIResourceLoaderDelegate;
@interface VIResourceLoader : NSObject
@property (nonatomic, strong, readonly) NSURL *url;
@property (nonatomic, weak) id<VIResourceLoaderDelegate> delegate;
- (instancetype)initWithURL:(NSURL *)url;
- (void)addRequest:(AVAssetResourceLoadingRequest *)request;
- (void)removeRequest:(AVAssetResourceLoadingRequest *)request;
- (void)cancel;
@end
@protocol VIResourceLoaderDelegate <NSObject>
- (void)resourceLoader:(VIResourceLoader *)resourceLoader didFailWithError:(NSError *)error;
@end
//
// VIResoureLoader.m
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import "VIResourceLoader.h"
#import "VIMediaDownloader.h"
#import "VIResourceLoadingRequestWorker.h"
#import "VIContentInfo.h"
NSString * const MCResourceLoaderErrorDomain = @"LSFilePlayerResourceLoaderErrorDomain";
@interface VIResourceLoader () <VIResourceLoadingRequestWorkerDelegate>
@property (nonatomic, strong, readwrite) NSURL *url;
@property (nonatomic, strong) VIMediaDownloader *mediaDownloader;
@property (nonatomic, strong) NSMutableDictionary *pendingRequestWorkers;
@property (nonatomic, getter=isCancelled) BOOL cancelled;
@end
@implementation VIResourceLoader
- (void)dealloc {
[_mediaDownloader invalidateAndCancel];
}
- (instancetype)initWithURL:(NSURL *)url {
self = [super init];
if (self) {
_url = url;
_mediaDownloader = [[VIMediaDownloader alloc] initWithURL:url];
_pendingRequestWorkers = [NSMutableDictionary dictionary];
}
return self;
}
- (instancetype)init {
NSAssert(NO, @"Use - initWithURL: instead");
return nil;
}
- (void)addRequest:(AVAssetResourceLoadingRequest *)request {
[self.pendingRequestWorkers enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, VIResourceLoadingRequestWorker * _Nonnull obj, BOOL * _Nonnull stop) {
[obj cancel];
[obj finish];
}];
[self.pendingRequestWorkers removeAllObjects];
[self startWorkerWithRequest:request];
}
- (void)removeRequest:(AVAssetResourceLoadingRequest *)request {
NSString *key = [self keyForRequest:request];
VIResourceLoadingRequestWorker *requestWorker = self.pendingRequestWorkers[key];
[requestWorker finish];
[self.pendingRequestWorkers removeObjectForKey:key];
}
- (void)cancel {
[self.mediaDownloader cancel];
}
#pragma mark - VIResourceLoadingRequestWorkerDelegate
- (void)resourceLoadingRequestWorker:(VIResourceLoadingRequestWorker *)requestWorker didCompleteWithError:(NSError *)error {
[self removeRequest:requestWorker.request];
if (error && [self.delegate respondsToSelector:@selector(resourceLoader:didFailWithError:)]) {
[self.delegate resourceLoader:self didFailWithError:error];
}
}
#pragma mark - Helper
- (void)startWorkerWithRequest:(AVAssetResourceLoadingRequest *)request {
NSString *key = [self keyForRequest:request];
VIResourceLoadingRequestWorker *requestWorker = [[VIResourceLoadingRequestWorker alloc] initWithMediaDownloader:self.mediaDownloader
resourceLoadingRequest:request];
requestWorker.delegate = self;
self.pendingRequestWorkers[key] = requestWorker;
[requestWorker startWork];
}
- (NSString *)keyForRequest:(AVAssetResourceLoadingRequest *)request {
return [NSString stringWithFormat:@"%@%@", request.request.URL.absoluteString, request.request.allHTTPHeaderFields[@"Range"]];
}
- (NSError *)loaderCancelledError{
NSError *error = [[NSError alloc] initWithDomain:MCResourceLoaderErrorDomain
code:-3
userInfo:@{NSLocalizedDescriptionKey:@"Resource loader cancelled"}];
return error;
}
@end
//
// VIResourceLoaderManager.h
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import <Foundation/Foundation.h>
@import AVFoundation;
@protocol VIResourceLoaderManagerDelegate;
@interface VIResourceLoaderManager : NSObject <AVAssetResourceLoaderDelegate>
@property (nonatomic, weak) id<VIResourceLoaderManagerDelegate> delegate;
/**
Normally you no need to call this method to clean cache. Cache cleaned after AVPlayer delloc.
If you have a singleton AVPlayer then you need call this method to clean cache at suitable time.
*/
- (void)cleanCache;
@end
@protocol VIResourceLoaderManagerDelegate <NSObject>
- (void)resourceLoaderManagerLoadURL:(NSURL *)url didFailWithError:(NSError *)error;
@end
@interface VIResourceLoaderManager (Convenient)
+ (NSURL *)assetURLWithURL:(NSURL *)url;
- (AVPlayerItem *)playerItemWithURL:(NSURL *)url;
@end
//
// VIResourceLoaderManager.m
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import "VIResourceLoaderManager.h"
#import "VIResourceLoader.h"
static NSString *kCacheScheme = @"VIMediaCache";
@interface VIResourceLoaderManager () <VIResourceLoaderDelegate>
@property (nonatomic, strong) NSMutableDictionary<id<NSCoding>, VIResourceLoader *> *loaders;
@end
@implementation VIResourceLoaderManager
- (instancetype)init {
self = [super init];
if (self) {
_loaders = [NSMutableDictionary dictionary];
}
return self;
}
- (void)cleanCache {
[self.loaders removeAllObjects];
}
#pragma mark - AVAssetResourceLoaderDelegate
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
NSURL *resourceURL = [loadingRequest.request URL];
if ([resourceURL.scheme isEqualToString:kCacheScheme]) {
VIResourceLoader *loader = [self loaderForRequest:loadingRequest];
if (!loader) {
NSURLComponents *components = [NSURLComponents componentsWithString:resourceURL.absoluteString];
NSURL *originURL;
if ([components respondsToSelector:@selector(queryItems)]) {
NSURLQueryItem *queryItem = [components.queryItems lastObject];
originURL = [NSURL URLWithString:queryItem.value];
} else {
NSString *url = [[components.query componentsSeparatedByString:@"="] lastObject];
originURL = [NSURL URLWithString:url];
}
loader = [[VIResourceLoader alloc] initWithURL:originURL];
loader.delegate = self;
NSString *key = [self keyForResourceLoaderWithURL:resourceURL];
self.loaders[key] = loader;
}
[loader addRequest:loadingRequest];
return YES;
}
return NO;
}
- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {
VIResourceLoader *loader = [self loaderForRequest:loadingRequest];
[loader cancel];
[loader removeRequest:loadingRequest];
}
#pragma mark - VIResourceLoaderDelegate
- (void)resourceLoader:(VIResourceLoader *)resourceLoader didFailWithError:(NSError *)error {
[resourceLoader cancel];
if ([self.delegate respondsToSelector:@selector(resourceLoaderManagerLoadURL:didFailWithError:)]) {
[self.delegate resourceLoaderManagerLoadURL:resourceLoader.url didFailWithError:error];
}
}
#pragma mark - Helper
- (NSString *)keyForResourceLoaderWithURL:(NSURL *)requestURL {
if([requestURL.scheme isEqualToString:kCacheScheme]){
NSString *s = requestURL.absoluteString;
return s;
}
return nil;
}
- (VIResourceLoader *)loaderForRequest:(AVAssetResourceLoadingRequest *)request {
NSString *requestKey = [self keyForResourceLoaderWithURL:request.request.URL];
VIResourceLoader *loader = self.loaders[requestKey];
return loader;
}
@end
@implementation VIResourceLoaderManager (Convenient)
+ (NSURL *)assetURLWithURL:(NSURL *)url {
NSURLComponents *componnents = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
componnents.scheme = kCacheScheme;
NSString *appendStr = componnents.query.length > 0 ? @"&" : @"?";
NSURL *assetURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@MCurl=%@", componnents.URL.absoluteString, appendStr, url.absoluteString]];
return assetURL;
}
- (AVPlayerItem *)playerItemWithURL:(NSURL *)url {
NSURL *assetURL = [VIResourceLoaderManager assetURLWithURL:url];
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:assetURL options:nil];
[urlAsset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:urlAsset];
if ([playerItem respondsToSelector:@selector(setCanUseNetworkResourcesForLiveStreamingWhilePaused:)]) {
playerItem.canUseNetworkResourcesForLiveStreamingWhilePaused = YES;
}
return playerItem;
}
@end
//
// VIResourceLoadingRequestWorker.h
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import <Foundation/Foundation.h>
@class VIMediaDownloader, AVAssetResourceLoadingRequest;
@protocol VIResourceLoadingRequestWorkerDelegate;
@interface VIResourceLoadingRequestWorker : NSObject
- (instancetype)initWithMediaDownloader:(VIMediaDownloader *)mediaDownloader resourceLoadingRequest:(AVAssetResourceLoadingRequest *)request;
@property (nonatomic, weak) id<VIResourceLoadingRequestWorkerDelegate> delegate;
@property (nonatomic, strong, readonly) AVAssetResourceLoadingRequest *request;
- (void)startWork;
- (void)cancel;
- (void)finish;
@end
@protocol VIResourceLoadingRequestWorkerDelegate <NSObject>
- (void)resourceLoadingRequestWorker:(VIResourceLoadingRequestWorker *)requestWorker didCompleteWithError:(NSError *)error;
@end
//
// VIResourceLoadingRequestWorker.m
// VIMediaCacheDemo
//
// Created by Vito on 4/21/16.
// Copyright © 2016 Vito. All rights reserved.
//
#import "VIResourceLoadingRequestWorker.h"
#import "VIMediaDownloader.h"
#import "VIContentInfo.h"
@import MobileCoreServices;
@import AVFoundation;
@import UIKit;
@interface VIResourceLoadingRequestWorker () <MediaDownloaderDelegate>
@property (nonatomic, strong, readwrite) AVAssetResourceLoadingRequest *request;
@property (nonatomic, strong) VIMediaDownloader *mediaDownloader;
@end
@implementation VIResourceLoadingRequestWorker
- (instancetype)initWithMediaDownloader:(VIMediaDownloader *)mediaDownloader resourceLoadingRequest:(AVAssetResourceLoadingRequest *)request {
self = [super init];
if (self) {
_mediaDownloader = mediaDownloader;
_mediaDownloader.delegate = self;
_request = request;
[self fullfillContentInfo];
}
return self;
}
- (void)startWork {
AVAssetResourceLoadingDataRequest *dataRequest = self.request.dataRequest;
long long offset = dataRequest.requestedOffset;
NSInteger length = dataRequest.requestedLength;
if (dataRequest.currentOffset != 0) {
offset = dataRequest.currentOffset;
}
BOOL toEnd = NO;
if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
if (dataRequest.requestsAllDataToEndOfResource) {
toEnd = YES;
}
}
[self.mediaDownloader downloadTaskFromOffset:offset length:length toEnd:toEnd];
}
- (void)cancel {
[self.mediaDownloader cancel];
}
- (void)finish {
if (!self.request.isFinished) {
[self.request finishLoadingWithError:[self loaderCancelledError]];
}
}
- (NSError *)loaderCancelledError{
NSError *error = [[NSError alloc] initWithDomain:@"com.resourceloader"
code:-3
userInfo:@{NSLocalizedDescriptionKey:@"Resource loader cancelled"}];
return error;
}
- (void)fullfillContentInfo {
AVAssetResourceLoadingContentInformationRequest *contentInformationRequest = self.request.contentInformationRequest;
if (self.mediaDownloader.info && !contentInformationRequest.contentType) {
// Fullfill content information
contentInformationRequest.contentType = self.mediaDownloader.info.contentType;
contentInformationRequest.contentLength = self.mediaDownloader.info.contentLength;
contentInformationRequest.byteRangeAccessSupported = self.mediaDownloader.info.byteRangeAccessSupported;
}
}
#pragma mark - MediaDownloaderDelegate
- (void)mediaDownloader:(VIMediaDownloader *)downloader didReceiveResponse:(NSURLResponse *)response {
[self fullfillContentInfo];
}
- (void)mediaDownloader:(VIMediaDownloader *)downloader didReceiveData:(NSData *)data {
[self.request.dataRequest respondWithData:data];
}
- (void)mediaDownloader:(VIMediaDownloader *)downloader didFinishedWithError:(NSError *)error {
if (error.code == NSURLErrorCancelled) {
return;
}
if (!error) {
[self.request finishLoading];
} else {
[self.request finishLoadingWithError:error];
}
[self.delegate resourceLoadingRequestWorker:self didCompleteWithError:error];
}
@end
//
// VIMediaCache.h
// VIMediaCacheDemo
//
// Created by Vito on 4/22/16.
// Copyright © 2016 Vito. All rights reserved.
//
#ifndef VIMediaCache_h
#define VIMediaCache_h
#import "VIResourceLoaderManager.h"
#import "VICacheManager.h"
#import "VIMediaDownloader.h"
#import "VIContentInfo.h"
#endif /* VIMediaCache_h */
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment