├── AVPlayerCacheSupport ├── AVPlayerItem+MCCacheSupport.h ├── AVPlayerItem+MCCacheSupport.m └── Classes │ ├── MCAVPlayerItemCacheFile.h │ ├── MCAVPlayerItemCacheFile.m │ ├── MCAVPlayerItemCacheLoader.h │ ├── MCAVPlayerItemCacheLoader.m │ ├── MCAVPlayerItemCacheTask.h │ ├── MCAVPlayerItemCacheTask.m │ ├── MCAVPlayerItemLocalCacheTask.h │ ├── MCAVPlayerItemLocalCacheTask.m │ ├── MCAVPlayerItemRemoteCacheTask.h │ ├── MCAVPlayerItemRemoteCacheTask.m │ ├── MCCacheSupportUtils.h │ └── MCCacheSupportUtils.m ├── Demo ├── Common │ ├── AudioThumb.png │ ├── MediaManifest.json │ └── VideoThumb.png ├── LICENSE.txt ├── Objective-C │ ├── AVFoundationQueuePlayer-iOS.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ └── AVFoundationQueuePlayer-iOS.xcscmblueprint │ └── AVFoundationQueuePlayer-iOS │ │ ├── AAPLAppDelegate.h │ │ ├── AAPLAppDelegate.m │ │ ├── AAPLPlayerView.h │ │ ├── AAPLPlayerView.m │ │ ├── AAPLPlayerViewController.h │ │ ├── AAPLPlayerViewController.m │ │ ├── AAPLQueuedItemCollectionViewCell.h │ │ ├── AAPLQueuedItemCollectionViewCell.m │ │ ├── Base.lproj │ │ └── Main.storyboard │ │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── PauseButton.imageset │ │ │ ├── Contents.json │ │ │ ├── PauseButton.png │ │ │ ├── PauseButton@2x.png │ │ │ └── PauseButton@3x.png │ │ ├── PlayButton.imageset │ │ │ ├── Contents.json │ │ │ ├── PlayButton.png │ │ │ ├── PlayButton@2x.png │ │ │ └── PlayButton@3x.png │ │ ├── ScanBackwardButton.imageset │ │ │ ├── Contents.json │ │ │ ├── ScanBackwardButton.png │ │ │ ├── ScanBackwardButton@2x.png │ │ │ └── ScanBackwardButton@3x.png │ │ └── ScanForwardButton.imageset │ │ │ ├── Contents.json │ │ │ ├── ScanForwardButton.png │ │ │ ├── ScanForwardButton@2x.png │ │ │ └── ScanForwardButton@3x.png │ │ ├── Info.plist │ │ ├── en.lproj │ │ ├── Localizable.strings │ │ └── Localizable.stringsdict │ │ └── main.m └── README.md └── README.md /AVPlayerCacheSupport/AVPlayerItem+MCCacheSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // AVPlayerItem+MCCacheSupport.h 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | AVF_EXPORT NSString *const AVPlayerMCCacheErrorDomain NS_AVAILABLE(10_9, 7_0); 12 | 13 | typedef NS_ENUM(NSUInteger, AVPlayerMCCacheError) 14 | { 15 | AVPlayerMCCacheErrorFileURL = -1111900, 16 | AVPlayerMCCacheErrorSchemeNotHTTP = -1111901, 17 | AVPlayerMCCacheErrorUnsupportFormat = -1111902, 18 | AVPlayerMCCacheErrorCreateCacheFileFailed = -1111903, 19 | }; 20 | 21 | @interface AVPlayerItem (MCCacheSupport) 22 | 23 | /** 24 | * get original url, do not use [(AVURLAsset *)self.asset URL]. 25 | */ 26 | @property (nonatomic,copy,readonly) NSURL *mc_URL; 27 | 28 | /** 29 | * cache file path. 30 | */ 31 | @property (nonatomic,copy,readonly) NSString *mc_cacheFilePath NS_AVAILABLE(10_9, 7_0); 32 | 33 | /** 34 | * AVPlayerItem with cache support, same to -mc_playerItemWithRemoteURL:URL options:nil error:error 35 | * 36 | * @param URL original request url 37 | * 38 | * @return AVPlayerItem with cache support 39 | */ 40 | + (instancetype)mc_playerItemWithRemoteURL:(NSURL *)URL error:(NSError **)error NS_AVAILABLE(10_9, 7_0); 41 | 42 | /** 43 | * AVPlayerItem with cache support, same to -mc_playerItemWithRemoteURL:URL options:options cacheFilePath:nil error:error 44 | * 45 | * @param URL original request url 46 | * @param options An instance of NSDictionary that contains keys for specifying options for the initialization of the AVURLAsset. See AVURLAssetPreferPreciseDurationAndTimingKey and AVURLAssetReferenceRestrictionsKey 47 | * 48 | * @return AVPlayerItem with cache support 49 | */ 50 | + (instancetype)mc_playerItemWithRemoteURL:(NSURL *)URL options:(NSDictionary *)options error:(NSError **)error NS_AVAILABLE(10_9, 7_0); 51 | 52 | /** 53 | * create AVPlayerItem with cache support 54 | * 55 | * @param URL original request url 56 | * @param options An instance of NSDictionary that contains keys for specifying options for the initialization of the AVURLAsset. See AVURLAssetPreferPreciseDurationAndTimingKey and AVURLAssetReferenceRestrictionsKey 57 | * 58 | * @param cacheFilePath cache file path, if cacheFilePath is nil the cache will be put in NSTemporaryDirectory()/AVPlayerMCCache 59 | * @param error nil means success, otherwise failed, input url will not be cached 60 | * 61 | * @return AVPlayerItem with cache support 62 | */ 63 | + (instancetype)mc_playerItemWithRemoteURL:(NSURL *)URL options:(NSDictionary *)options cacheFilePath:(NSString *)cacheFilePath error:(NSError **)error NS_AVAILABLE(10_9, 7_0); 64 | 65 | /** 66 | * remove cache file and cache index file 67 | * 68 | * @param cacheFilePath cache file path. 69 | */ 70 | + (void)mc_removeCacheWithCacheFilePath:(NSString *)cacheFilePath NS_AVAILABLE(10_9, 7_0); 71 | @end 72 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/AVPlayerItem+MCCacheSupport.m: -------------------------------------------------------------------------------- 1 | // 2 | // AVPlayerItem+MCCacheSupport.m 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import "AVPlayerItem+MCCacheSupport.h" 10 | #import "MCAVPlayerItemCacheLoader.h" 11 | #import "MCCacheSupportUtils.h" 12 | #import 13 | 14 | NSString *const AVPlayerMCCacheErrorDomain = @"AVPlayerMCCacheErrorDomain"; 15 | static const void * const kAVPlayerItemMCCacheSupportCacheLoaderKey = &kAVPlayerItemMCCacheSupportCacheLoaderKey; 16 | 17 | @implementation AVPlayerItem (MCCacheSupport) 18 | 19 | #pragma mark - init 20 | + (NSError *)mc_errorWithCode:(AVPlayerMCCacheError)errorCode reason:(NSString *)reason 21 | { 22 | return [NSError errorWithDomain:AVPlayerMCCacheErrorDomain code:errorCode userInfo:@{ 23 | NSLocalizedDescriptionKey : @"The operation couldn’t be completed.", 24 | NSLocalizedFailureReasonErrorKey : reason, 25 | }]; 26 | } 27 | 28 | + (NSError *)mc_checkURL:(NSURL *)URL 29 | { 30 | AVPlayerMCCacheError errorCode = 0; 31 | NSString *reason = nil; 32 | if ([URL isFileURL]) 33 | { 34 | errorCode = AVPlayerMCCacheErrorFileURL; 35 | reason = @"can not cache file URL."; 36 | } 37 | else if (![[[URL scheme] lowercaseString] hasPrefix:@"http"]) 38 | { 39 | errorCode = AVPlayerMCCacheErrorSchemeNotHTTP; 40 | reason = @"only support URL with http scheme."; 41 | } 42 | else if ([URL mc_isM3U]) 43 | { 44 | errorCode = AVPlayerMCCacheErrorUnsupportFormat; 45 | reason = @"do not support playlist format."; 46 | } 47 | 48 | if (errorCode == 0) 49 | { 50 | return nil; 51 | } 52 | else 53 | { 54 | return [self mc_errorWithCode:errorCode reason:reason]; 55 | } 56 | } 57 | 58 | + (instancetype)mc_playerItemWithRemoteURL:(NSURL *)URL error:(NSError *__autoreleasing *)error 59 | { 60 | return [self mc_playerItemWithRemoteURL:URL options:nil cacheFilePath:nil error:error]; 61 | } 62 | 63 | + (instancetype)mc_playerItemWithRemoteURL:(NSURL *)URL options:(NSDictionary *)options error:(NSError *__autoreleasing *)error 64 | { 65 | return [self mc_playerItemWithRemoteURL:URL options:options cacheFilePath:nil error:error]; 66 | } 67 | 68 | + (instancetype)mc_playerItemWithRemoteURL:(NSURL *)URL options:(NSDictionary *)options cacheFilePath:(NSString *)cacheFilePath error:(NSError *__autoreleasing *)error 69 | { 70 | NSError *err = [self mc_checkURL:URL]; 71 | if (err) 72 | { 73 | if (error != NULL) 74 | { 75 | *error = err; 76 | } 77 | return [self playerItemWithURL:URL]; 78 | } 79 | 80 | NSString *path = cacheFilePath; 81 | if (!path) 82 | { 83 | path = [[MCCacheTemporaryDirectory() stringByAppendingPathComponent:[[URL absoluteString] mc_md5]] stringByAppendingPathExtension:[URL pathExtension]]; 84 | } 85 | 86 | MCAVPlayerItemCacheLoader *cacheLoader = [MCAVPlayerItemCacheLoader cacheLoaderWithCacheFilePath:path]; 87 | if (!cacheLoader) 88 | { 89 | if (*error) 90 | { 91 | *error = [self mc_errorWithCode:AVPlayerMCCacheErrorCreateCacheFileFailed reason:@"create cache file failed."]; 92 | } 93 | return [self playerItemWithURL:URL]; 94 | } 95 | AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[URL mc_avplayerCacheSupportURL] options:options]; 96 | [asset.resourceLoader setDelegate:cacheLoader queue:dispatch_get_main_queue()]; 97 | AVPlayerItem *item = [self playerItemWithAsset:asset]; 98 | objc_setAssociatedObject(item, kAVPlayerItemMCCacheSupportCacheLoaderKey, cacheLoader, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 99 | return item; 100 | } 101 | 102 | + (void)mc_removeCacheWithCacheFilePath:(NSString *)cacheFilePath 103 | { 104 | [MCAVPlayerItemCacheLoader removeCacheWithCacheFilePath:cacheFilePath]; 105 | } 106 | 107 | #pragma mark - property 108 | - (MCAVPlayerItemCacheLoader *)mc_cacheLoader 109 | { 110 | return objc_getAssociatedObject(self, kAVPlayerItemMCCacheSupportCacheLoaderKey); 111 | } 112 | 113 | - (NSURL *)mc_URL 114 | { 115 | if (![self.asset isKindOfClass:[AVURLAsset class]]) 116 | { 117 | return nil; 118 | } 119 | AVURLAsset *asset = (AVURLAsset *)self.asset; 120 | return [asset.URL mc_avplayerOriginalURL]; 121 | } 122 | 123 | - (NSString *)mc_cacheFilePath 124 | { 125 | return [self mc_cacheLoader].cacheFilePath; 126 | } 127 | @end 128 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCAVPlayerItemCacheFile.h: -------------------------------------------------------------------------------- 1 | // 2 | // MCAVPlayerCacheFile.h 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MCAVPlayerItemCacheFile : NSObject 12 | 13 | @property (nonatomic,copy,readonly) NSString *cacheFilePath; 14 | @property (nonatomic,copy,readonly) NSString *indexFilePath; 15 | @property (nonatomic,assign,readonly) NSUInteger fileLength; 16 | @property (nonatomic,assign,readonly) NSUInteger readOffset; 17 | @property (nonatomic,copy,readonly) NSDictionary *responseHeaders; 18 | @property (nonatomic,readonly) BOOL isCompeleted; 19 | @property (nonatomic,readonly) BOOL isEof; 20 | @property (nonatomic,readonly) BOOL isFileLengthValid; 21 | 22 | @property (nonatomic,readonly) NSUInteger cachedDataBound; 23 | 24 | + (instancetype)cacheFileWithFilePath:(NSString *)filePath; 25 | - (instancetype)initWithFilePath:(NSString *)filePath; 26 | 27 | + (NSString *)indexFileExtension; 28 | 29 | - (NSRange)cachedRangeForRange:(NSRange)range; 30 | - (NSRange)cachedRangeContainsPosition:(NSUInteger)pos; 31 | - (NSRange)firstNotCachedRangeFromPosition:(NSUInteger)pos; 32 | 33 | - (void)seekToPosition:(NSUInteger)pos; 34 | - (void)seekToEnd; 35 | 36 | - (NSData *)readDataWithLength:(NSUInteger)length; 37 | - (NSData *)dataWithRange:(NSRange)range; 38 | 39 | - (BOOL)setResponse:(NSHTTPURLResponse *)response; 40 | - (BOOL)saveData:(NSData *)data atOffset:(NSUInteger)offset synchronize:(BOOL)synchronize; 41 | - (BOOL)synchronize; 42 | 43 | - (void)removeCache; 44 | @end 45 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCAVPlayerItemCacheFile.m: -------------------------------------------------------------------------------- 1 | // 2 | // MCAVPlayerCacheFile.m 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import "MCAVPlayerItemCacheFile.h" 10 | #import "MCCacheSupportUtils.h" 11 | 12 | const NSString *MCAVPlayerCacheFileZoneKey = @"zone"; 13 | const NSString *MCAVPlayerCacheFileSizeKey = @"size"; 14 | const NSString *MCAVPlayerCacheFileResponseHeadersKey = @"responseHeaders"; 15 | 16 | @interface MCAVPlayerItemCacheFile () 17 | { 18 | @private 19 | NSMutableArray *_ranges; 20 | NSFileHandle *_writeFileHandle; 21 | NSFileHandle *_readFileHandle; 22 | BOOL _compelete; 23 | } 24 | @end 25 | 26 | @implementation MCAVPlayerItemCacheFile 27 | 28 | #pragma mark - init & dealloc 29 | + (instancetype)cacheFileWithFilePath:(NSString *)filePath 30 | { 31 | return [[self alloc] initWithFilePath:filePath]; 32 | } 33 | 34 | - (instancetype)initWithFilePath:(NSString *)filePath 35 | { 36 | if (!filePath) 37 | { 38 | return nil; 39 | } 40 | 41 | self = [super init]; 42 | if (self) 43 | { 44 | NSString *cacheFilePath = [filePath copy]; 45 | NSString *indexFilePath = [NSString stringWithFormat:@"%@%@",filePath,[[self class] indexFileExtension]]; 46 | 47 | BOOL cacheFileExist = [[NSFileManager defaultManager] fileExistsAtPath:cacheFilePath]; 48 | BOOL indexFileExist = [[NSFileManager defaultManager] fileExistsAtPath:indexFilePath]; 49 | 50 | BOOL fileExist = cacheFileExist && indexFileExist; 51 | if (!fileExist) 52 | { 53 | NSString *directory = [cacheFilePath stringByDeletingLastPathComponent]; 54 | if (![[NSFileManager defaultManager] fileExistsAtPath:directory]) 55 | { 56 | [[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:nil]; 57 | } 58 | fileExist = [[NSFileManager defaultManager] createFileAtPath:cacheFilePath contents:nil attributes:nil]; 59 | fileExist = fileExist && [[NSFileManager defaultManager] createFileAtPath:indexFilePath contents:nil attributes:nil]; 60 | } 61 | 62 | if (!fileExist) 63 | { 64 | return nil; 65 | } 66 | 67 | _cacheFilePath = cacheFilePath; 68 | _indexFilePath = indexFilePath; 69 | _ranges = [[NSMutableArray alloc] init]; 70 | _readFileHandle = [NSFileHandle fileHandleForReadingAtPath:_cacheFilePath]; 71 | _writeFileHandle = [NSFileHandle fileHandleForWritingAtPath:_cacheFilePath]; 72 | 73 | NSString *indexStr = [NSString stringWithContentsOfFile:_indexFilePath encoding:NSUTF8StringEncoding error:nil]; 74 | NSData *data = [indexStr dataUsingEncoding:NSUTF8StringEncoding]; 75 | NSDictionary *indexDic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers | NSJSONReadingAllowFragments error:nil]; 76 | if (![self serializeIndex:indexDic]) 77 | { 78 | [self truncateFileWithFileLength:0]; 79 | } 80 | [self checkCompelete]; 81 | } 82 | return self; 83 | } 84 | 85 | + (NSString *)indexFileExtension 86 | { 87 | return @".idx!"; 88 | } 89 | 90 | - (void)dealloc 91 | { 92 | [_readFileHandle closeFile]; 93 | [_writeFileHandle closeFile]; 94 | } 95 | 96 | #pragma mark - serialize 97 | - (BOOL)serializeIndex:(NSDictionary *)indexDic 98 | { 99 | if (![indexDic isKindOfClass:[NSDictionary class]]) 100 | { 101 | return NO; 102 | } 103 | 104 | NSNumber *fileSize = indexDic[MCAVPlayerCacheFileSizeKey]; 105 | if (fileSize && [fileSize isKindOfClass:[NSNumber class]]) 106 | { 107 | _fileLength = [fileSize unsignedIntegerValue]; 108 | } 109 | 110 | if (_fileLength == 0) 111 | { 112 | return NO; 113 | } 114 | 115 | [_ranges removeAllObjects]; 116 | NSMutableArray *rangeArray = indexDic[MCAVPlayerCacheFileZoneKey]; 117 | for (NSString *rangeStr in rangeArray) 118 | { 119 | NSRange range = NSRangeFromString(rangeStr); 120 | [_ranges addObject:[NSValue valueWithRange:range]]; 121 | } 122 | 123 | _responseHeaders = indexDic[MCAVPlayerCacheFileResponseHeadersKey]; 124 | 125 | return YES; 126 | } 127 | 128 | - (NSString *)unserializeIndex 129 | { 130 | NSMutableArray *rangeArray = [[NSMutableArray alloc] init]; 131 | for (NSValue *range in _ranges) 132 | { 133 | [rangeArray addObject:NSStringFromRange([range rangeValue])]; 134 | } 135 | NSMutableDictionary *dict = [@{ 136 | MCAVPlayerCacheFileSizeKey: @(_fileLength), 137 | MCAVPlayerCacheFileZoneKey: rangeArray 138 | } mutableCopy]; 139 | if (_responseHeaders) 140 | { 141 | dict[MCAVPlayerCacheFileResponseHeadersKey] = _responseHeaders; 142 | } 143 | 144 | NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil]; 145 | if (data) 146 | { 147 | return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 148 | } 149 | return nil; 150 | } 151 | 152 | - (BOOL)synchronize 153 | { 154 | NSString *indexStr = [self unserializeIndex]; 155 | return [indexStr writeToFile:_indexFilePath atomically:YES encoding:NSUTF8StringEncoding error:NULL]; 156 | } 157 | 158 | #pragma mark - property 159 | - (NSUInteger)cachedDataBound 160 | { 161 | if (_ranges.count > 0) 162 | { 163 | NSRange range = [[_ranges lastObject] rangeValue]; 164 | return NSMaxRange(range); 165 | } 166 | return 0; 167 | } 168 | 169 | - (BOOL)isFileLengthValid 170 | { 171 | return _fileLength != 0; 172 | } 173 | 174 | - (BOOL)isCompeleted 175 | { 176 | return _compelete; 177 | } 178 | 179 | - (BOOL)isEof 180 | { 181 | if (_readOffset + 1 >= _fileLength) 182 | { 183 | return YES; 184 | } 185 | return NO; 186 | } 187 | 188 | #pragma mark - range 189 | - (void)mergeRanges 190 | { 191 | for (int i = 0; i < _ranges.count; ++i) 192 | { 193 | if ((i + 1) < _ranges.count) 194 | { 195 | NSRange currentRange = [_ranges[i] rangeValue]; 196 | NSRange nextRange = [_ranges[i + 1] rangeValue]; 197 | if (MCRangeCanMerge(currentRange, nextRange)) 198 | { 199 | [_ranges removeObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, 2)]]; 200 | [_ranges insertObject:[NSValue valueWithRange:NSUnionRange(currentRange, nextRange)] atIndex:i]; 201 | i -= 1; 202 | } 203 | } 204 | } 205 | } 206 | 207 | - (void)addRange:(NSRange)range 208 | { 209 | if (range.length == 0 || range.location >= _fileLength) 210 | { 211 | return; 212 | } 213 | 214 | BOOL inserted = NO; 215 | for (int i = 0; i < _ranges.count; ++i) 216 | { 217 | NSRange currentRange = [_ranges[i] rangeValue]; 218 | if (currentRange.location >= range.location) 219 | { 220 | [_ranges insertObject:[NSValue valueWithRange:range] atIndex:i]; 221 | inserted = YES; 222 | break; 223 | } 224 | } 225 | if (!inserted) 226 | { 227 | [_ranges addObject:[NSValue valueWithRange:range]]; 228 | } 229 | [self mergeRanges]; 230 | [self checkCompelete]; 231 | } 232 | 233 | - (NSRange)cachedRangeForRange:(NSRange)range 234 | { 235 | NSRange cachedRange = [self cachedRangeContainsPosition:range.location]; 236 | NSRange ret = NSIntersectionRange(cachedRange, range); 237 | if (ret.length > 0) 238 | { 239 | return ret; 240 | } 241 | else 242 | { 243 | return MCInvalidRange; 244 | } 245 | } 246 | 247 | - (NSRange)cachedRangeContainsPosition:(NSUInteger)pos 248 | { 249 | if (pos >= _fileLength) 250 | { 251 | return MCInvalidRange; 252 | } 253 | 254 | for (int i = 0; i < _ranges.count; ++i) 255 | { 256 | NSRange range = [_ranges[i] rangeValue]; 257 | if (NSLocationInRange(pos, range)) 258 | { 259 | return range; 260 | } 261 | } 262 | return MCInvalidRange; 263 | } 264 | 265 | - (NSRange)firstNotCachedRangeFromPosition:(NSUInteger)pos 266 | { 267 | if (pos >= _fileLength) 268 | { 269 | return MCInvalidRange; 270 | } 271 | 272 | NSUInteger start = pos; 273 | for (int i = 0; i < _ranges.count; ++i) 274 | { 275 | NSRange range = [_ranges[i] rangeValue]; 276 | if (NSLocationInRange(start, range)) 277 | { 278 | start = NSMaxRange(range); 279 | } 280 | else 281 | { 282 | if (start >= NSMaxRange(range)) 283 | { 284 | continue; 285 | } 286 | else 287 | { 288 | return NSMakeRange(start, range.location - start); 289 | } 290 | } 291 | } 292 | 293 | if (start < _fileLength) 294 | { 295 | return NSMakeRange(start, _fileLength - start); 296 | } 297 | return MCInvalidRange; 298 | } 299 | 300 | - (void)checkCompelete 301 | { 302 | if (_ranges && _ranges.count == 1) 303 | { 304 | NSRange range = [_ranges[0] rangeValue]; 305 | if (range.location == 0 && (range.length == _fileLength)) 306 | { 307 | _compelete = YES; 308 | return; 309 | } 310 | } 311 | _compelete = NO; 312 | } 313 | 314 | #pragma mark - file 315 | - (BOOL)truncateFileWithFileLength:(NSUInteger)fileLength; 316 | { 317 | if (!_writeFileHandle) 318 | { 319 | return NO; 320 | } 321 | 322 | _fileLength = fileLength; 323 | @try 324 | { 325 | [_writeFileHandle truncateFileAtOffset:_fileLength * sizeof(Byte)]; 326 | unsigned long long end = [_writeFileHandle seekToEndOfFile]; 327 | if (end != _fileLength) 328 | { 329 | return NO; 330 | } 331 | } 332 | @catch (NSException * e) 333 | { 334 | return NO; 335 | } 336 | 337 | return YES; 338 | } 339 | 340 | - (void)removeCache 341 | { 342 | [[NSFileManager defaultManager] removeItemAtPath:_cacheFilePath error:NULL]; 343 | [[NSFileManager defaultManager] removeItemAtPath:_indexFilePath error:NULL]; 344 | } 345 | 346 | - (BOOL)setResponse:(NSHTTPURLResponse *)response 347 | { 348 | BOOL success = YES; 349 | if (![self isFileLengthValid]) 350 | { 351 | success = [self truncateFileWithFileLength:(NSUInteger)response.mc_fileLength]; 352 | } 353 | _responseHeaders = [[response allHeaderFields] copy]; 354 | success = success && [self synchronize]; 355 | return success; 356 | } 357 | 358 | - (BOOL)saveData:(NSData *)data atOffset:(NSUInteger)offset synchronize:(BOOL)synchronize 359 | { 360 | if (!_writeFileHandle) 361 | { 362 | return NO; 363 | } 364 | 365 | @try 366 | { 367 | [_writeFileHandle seekToFileOffset:offset]; 368 | [_writeFileHandle writeData:data]; 369 | } 370 | @catch (NSException * e) 371 | { 372 | return NO; 373 | } 374 | [self addRange:NSMakeRange(offset, [data length])]; 375 | if (synchronize) 376 | { 377 | [self synchronize]; 378 | } 379 | 380 | return YES; 381 | } 382 | 383 | #pragma mark - read data 384 | - (NSData *)dataWithRange:(NSRange)range 385 | { 386 | if (!MCValidFileRange(range)) 387 | { 388 | return nil; 389 | } 390 | 391 | if (_readOffset != range.location) 392 | { 393 | [self seekToPosition:range.location]; 394 | } 395 | return [self readDataWithLength:range.length]; 396 | } 397 | 398 | - (NSData *)readDataWithLength:(NSUInteger)length 399 | { 400 | NSRange range = [self cachedRangeForRange:NSMakeRange(_readOffset, length)]; 401 | if (MCValidFileRange(range)) 402 | { 403 | NSData *data = [_readFileHandle readDataOfLength:range.length]; 404 | _readOffset += [data length]; 405 | return data; 406 | } 407 | return nil; 408 | } 409 | 410 | #pragma mark - seek 411 | - (void)seekToPosition:(NSUInteger)pos 412 | { 413 | [_readFileHandle seekToFileOffset:pos]; 414 | _readOffset = (NSUInteger)_readFileHandle.offsetInFile; 415 | } 416 | 417 | - (void)seekToEnd 418 | { 419 | [_readFileHandle seekToEndOfFile]; 420 | _readOffset = (NSUInteger)_readFileHandle.offsetInFile; 421 | } 422 | @end 423 | 424 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCAVPlayerItemCacheLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MCAVPlayerItemCacheLoader.h 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface MCAVPlayerItemCacheLoader : NSObject 13 | 14 | @property (nonatomic,readonly) NSString *cacheFilePath; 15 | 16 | + (instancetype)cacheLoaderWithCacheFilePath:(NSString *)cacheFilePath; 17 | - (instancetype)initWithCacheFilePath:(NSString *)cacheFilePath; 18 | + (void)removeCacheWithCacheFilePath:(NSString *)cacheFilePath; 19 | @end 20 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCAVPlayerItemCacheLoader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MCAVPlayerItemCacheLoader.m 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import "MCAVPlayerItemCacheLoader.h" 10 | #import "MCAVPlayerItemLocalCacheTask.h" 11 | #import "MCAVPlayerItemRemoteCacheTask.h" 12 | #import "MCCacheSupportUtils.h" 13 | #import "MCAVPlayerItemCacheFile.h" 14 | 15 | @interface MCAVPlayerItemCacheLoader () 16 | { 17 | @private 18 | NSMutableArray *_pendingRequests; 19 | AVAssetResourceLoadingRequest *_currentRequest; 20 | NSRange _currentDataRange; 21 | MCAVPlayerItemCacheFile *_cacheFile; 22 | NSHTTPURLResponse *_response; 23 | } 24 | @property (nonatomic,strong) NSOperationQueue *operationQueue; 25 | @end 26 | 27 | @implementation MCAVPlayerItemCacheLoader 28 | 29 | #pragma mark - init & dealloc 30 | + (instancetype)cacheLoaderWithCacheFilePath:(NSString *)cacheFilePath 31 | { 32 | return [[self alloc] initWithCacheFilePath:cacheFilePath]; 33 | } 34 | 35 | - (instancetype)initWithCacheFilePath:(NSString *)cacheFilePath 36 | { 37 | self = [super init]; 38 | if (self) 39 | { 40 | _cacheFile = [MCAVPlayerItemCacheFile cacheFileWithFilePath:cacheFilePath]; 41 | if (!_cacheFile) 42 | { 43 | return nil; 44 | } 45 | _pendingRequests = [[NSMutableArray alloc] init]; 46 | _operationQueue = [[NSOperationQueue alloc] init]; 47 | _operationQueue.maxConcurrentOperationCount = 1; 48 | _operationQueue.name = @"com.avplayeritem.mccache"; 49 | _currentDataRange = MCInvalidRange; 50 | } 51 | return self; 52 | } 53 | 54 | - (NSString *)cacheFilePath 55 | { 56 | return _cacheFile.cacheFilePath; 57 | } 58 | 59 | + (void)removeCacheWithCacheFilePath:(NSString *)cacheFilePath 60 | { 61 | [[NSFileManager defaultManager] removeItemAtPath:cacheFilePath error:NULL]; 62 | [[NSFileManager defaultManager] removeItemAtPath:[cacheFilePath stringByAppendingString:[MCAVPlayerItemCacheFile indexFileExtension]] error:NULL]; 63 | } 64 | 65 | - (void)dealloc 66 | { 67 | [_operationQueue cancelAllOperations]; 68 | } 69 | 70 | #pragma mark - loading request 71 | - (void)startNextRequest 72 | { 73 | if (_currentRequest || _pendingRequests.count == 0) 74 | { 75 | return; 76 | } 77 | 78 | _currentRequest = [_pendingRequests firstObject]; 79 | 80 | //data range 81 | if ([_currentRequest.dataRequest respondsToSelector:@selector(requestsAllDataToEndOfResource)] && _currentRequest.dataRequest.requestsAllDataToEndOfResource) 82 | { 83 | _currentDataRange = NSMakeRange((NSUInteger)_currentRequest.dataRequest.requestedOffset, NSUIntegerMax); 84 | } 85 | else 86 | { 87 | _currentDataRange = NSMakeRange((NSUInteger)_currentRequest.dataRequest.requestedOffset, _currentRequest.dataRequest.requestedLength); 88 | } 89 | 90 | //response 91 | if (!_response && _cacheFile.responseHeaders.count > 0) 92 | { 93 | if (_currentDataRange.length == NSUIntegerMax) 94 | { 95 | _currentDataRange.length = [_cacheFile fileLength] - _currentDataRange.location; 96 | } 97 | 98 | NSMutableDictionary *responseHeaders = [_cacheFile.responseHeaders mutableCopy]; 99 | NSString *contentRangeKey = @"Content-Range"; 100 | BOOL supportRange = responseHeaders[contentRangeKey] != nil; 101 | if (supportRange && MCValidByteRange(_currentDataRange)) 102 | { 103 | responseHeaders[contentRangeKey] = MCRangeToHTTPRangeReponseHeader(_currentDataRange, [_cacheFile fileLength]); 104 | } 105 | else 106 | { 107 | [responseHeaders removeObjectForKey:contentRangeKey]; 108 | } 109 | responseHeaders[@"Content-Length"] = [NSString stringWithFormat:@"%tu",_currentDataRange.length]; 110 | 111 | NSInteger statusCode = supportRange ? 206 : 200; 112 | _response = [[NSHTTPURLResponse alloc] initWithURL:_currentRequest.request.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:responseHeaders]; 113 | [_currentRequest mc_fillContentInformation:_response]; 114 | } 115 | [self startCurrentRequest]; 116 | } 117 | 118 | - (void)startCurrentRequest 119 | { 120 | _operationQueue.suspended = YES; 121 | if (_currentDataRange.length == NSUIntegerMax) 122 | { 123 | [self addTaskWithRange:NSMakeRange(_currentDataRange.location, NSUIntegerMax) cached:NO]; 124 | } 125 | else 126 | { 127 | NSUInteger start = _currentDataRange.location; 128 | NSUInteger end = NSMaxRange(_currentDataRange); 129 | while (start < end) 130 | { 131 | NSRange firstNotCachedRange = [_cacheFile firstNotCachedRangeFromPosition:start]; 132 | if (!MCValidFileRange(firstNotCachedRange)) 133 | { 134 | [self addTaskWithRange:NSMakeRange(start, end - start) cached:_cacheFile.cachedDataBound > 0]; 135 | start = end; 136 | } 137 | else if (firstNotCachedRange.location >= end) 138 | { 139 | [self addTaskWithRange:NSMakeRange(start, end - start) cached:YES]; 140 | start = end; 141 | } 142 | else if (firstNotCachedRange.location >= start) 143 | { 144 | if (firstNotCachedRange.location > start) 145 | { 146 | [self addTaskWithRange:NSMakeRange(start, firstNotCachedRange.location) cached:YES]; 147 | } 148 | NSUInteger notCachedEnd = MIN(NSMaxRange(firstNotCachedRange), end); 149 | [self addTaskWithRange:NSMakeRange(firstNotCachedRange.location, notCachedEnd - firstNotCachedRange.location) cached:NO]; 150 | start = notCachedEnd; 151 | } 152 | else 153 | { 154 | [self addTaskWithRange:NSMakeRange(start, end - start) cached:YES]; 155 | start = end; 156 | } 157 | } 158 | } 159 | _operationQueue.suspended = NO; 160 | } 161 | 162 | - (void)cancelCurrentRequest 163 | { 164 | [_operationQueue cancelAllOperations]; 165 | [self cleanUpCurrentRequest]; 166 | } 167 | 168 | - (void)currentRequestFinished:(NSError *)error 169 | { 170 | if (error) 171 | { 172 | [_currentRequest finishLoadingWithError:error]; 173 | } 174 | else 175 | { 176 | [_currentRequest finishLoading]; 177 | } 178 | [self cleanUpCurrentRequest]; 179 | [self startNextRequest]; 180 | } 181 | 182 | - (void)cleanUpCurrentRequest 183 | { 184 | [_pendingRequests removeObject:_currentRequest]; 185 | _currentRequest = nil; 186 | _response = nil; 187 | _currentDataRange = MCInvalidRange; 188 | } 189 | 190 | - (void)addTaskWithRange:(NSRange)range cached:(BOOL)cached 191 | { 192 | MCAVPlayerItemCacheTask *task = nil; 193 | if (cached) 194 | { 195 | task = [[MCAVPlayerItemLocalCacheTask alloc] initWithCacheFile:_cacheFile loadingRequest:_currentRequest range:range]; 196 | } 197 | else 198 | { 199 | task = [[MCAVPlayerItemRemoteCacheTask alloc] initWithCacheFile:_cacheFile loadingRequest:_currentRequest range:range]; 200 | [(MCAVPlayerItemRemoteCacheTask *)task setResponse:_response]; 201 | } 202 | __weak typeof(self)weakSelf = self; 203 | task.finishBlock = ^(NSError *error) 204 | { 205 | __strong __typeof(weakSelf)strongSelf = weakSelf; 206 | if (error) 207 | { 208 | [strongSelf currentRequestFinished:error]; 209 | } 210 | else 211 | { 212 | if (strongSelf.operationQueue.operationCount == 1) 213 | { 214 | [strongSelf currentRequestFinished:nil]; 215 | } 216 | } 217 | }; 218 | [_operationQueue addOperation:task]; 219 | } 220 | 221 | #pragma mark - resource loader delegate 222 | - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest 223 | { 224 | [_pendingRequests addObject:loadingRequest]; 225 | [self startNextRequest]; 226 | return YES; 227 | } 228 | 229 | - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest 230 | { 231 | if (_currentRequest == loadingRequest) 232 | { 233 | [self cancelCurrentRequest]; 234 | } 235 | else 236 | { 237 | [_pendingRequests removeObject:loadingRequest]; 238 | } 239 | } 240 | @end 241 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCAVPlayerItemCacheTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MCAVPlayerItemCacheTask.h 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void (^MCAVPlayerItemCacheTaskFinishedBlock)(NSError *error); 12 | 13 | @class MCAVPlayerItemCacheFile; 14 | @class AVAssetResourceLoadingRequest; 15 | @interface MCAVPlayerItemCacheTask : NSOperation 16 | { 17 | @protected 18 | AVAssetResourceLoadingRequest *_loadingRequest; 19 | NSRange _range; 20 | MCAVPlayerItemCacheFile *_cacheFile; 21 | } 22 | 23 | @property (nonatomic,copy) MCAVPlayerItemCacheTaskFinishedBlock finishBlock; 24 | 25 | - (instancetype)initWithCacheFile:(MCAVPlayerItemCacheFile *)cacheFile loadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest range:(NSRange)range; 26 | @end 27 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCAVPlayerItemCacheTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MCAVPlayerItemCacheTask.m 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import "MCAVPlayerItemCacheTask.h" 10 | 11 | @implementation MCAVPlayerItemCacheTask 12 | - (instancetype)initWithCacheFile:(MCAVPlayerItemCacheFile *)cacheFile loadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest range:(NSRange)range 13 | { 14 | self = [super init]; 15 | if (self) 16 | { 17 | _loadingRequest = loadingRequest; 18 | _range = range; 19 | _cacheFile = cacheFile; 20 | } 21 | return self; 22 | } 23 | 24 | - (void)cancel 25 | { 26 | 27 | } 28 | 29 | - (void)main 30 | { 31 | @autoreleasepool 32 | { 33 | if (_finishBlock) 34 | { 35 | _finishBlock(nil); 36 | } 37 | } 38 | } 39 | @end 40 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCAVPlayerItemLocalCacheTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MCAVPlayerItemLocalCacheTask.h 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import "MCAVPlayerItemCacheTask.h" 10 | 11 | @interface MCAVPlayerItemLocalCacheTask : MCAVPlayerItemCacheTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCAVPlayerItemLocalCacheTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MCAVPlayerItemLocalCacheTask.m 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import "MCAVPlayerItemLocalCacheTask.h" 10 | #import "MCAVPlayerItemCacheFile.h" 11 | #import "MCCacheSupportUtils.h" 12 | 13 | @implementation MCAVPlayerItemLocalCacheTask 14 | - (void)main 15 | { 16 | @autoreleasepool 17 | { 18 | if ([self isCancelled]) 19 | { 20 | [self handleFinished]; 21 | return; 22 | } 23 | 24 | NSUInteger offset = _range.location; 25 | NSUInteger lengthPerRead = 10000; 26 | while (offset < NSMaxRange(_range)) 27 | { 28 | if ([self isCancelled]) 29 | { 30 | break; 31 | } 32 | @autoreleasepool 33 | { 34 | NSRange range = NSMakeRange(offset, MIN(NSMaxRange(_range) - offset,lengthPerRead)); 35 | NSData *data = [_cacheFile dataWithRange:range]; 36 | [_loadingRequest.dataRequest respondWithData:data]; 37 | offset = NSMaxRange(range); 38 | } 39 | } 40 | [self handleFinished]; 41 | } 42 | } 43 | 44 | - (void)handleFinished 45 | { 46 | if (self.finishBlock) 47 | { 48 | self.finishBlock(nil); 49 | } 50 | } 51 | @end 52 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCAVPlayerItemRemoteCacheTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MCAVPlayerItemRemoteCacheTask.h 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import "MCAVPlayerItemCacheTask.h" 10 | 11 | @interface MCAVPlayerItemRemoteCacheTask : MCAVPlayerItemCacheTask 12 | 13 | @property (nonatomic,strong) NSHTTPURLResponse *response; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCAVPlayerItemRemoteCacheTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MCAVPlayerItemRemoteCacheTask.m 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import "MCAVPlayerItemRemoteCacheTask.h" 10 | #import "MCAVPlayerItemCacheFile.h" 11 | #import "MCCacheSupportUtils.h" 12 | 13 | @interface MCAVPlayerItemRemoteCacheTask () 14 | { 15 | @private 16 | NSUInteger _offset; 17 | NSUInteger _requestLength; 18 | 19 | NSError *_error; 20 | 21 | NSURLConnection *_connection; 22 | BOOL _dataSaved; 23 | 24 | CFRunLoopRef _runloop; 25 | } 26 | @end 27 | 28 | @implementation MCAVPlayerItemRemoteCacheTask 29 | 30 | + (NSOperationQueue *)downloadOperationQueue 31 | { 32 | static dispatch_once_t onceToken; 33 | static NSOperationQueue *__downloadOperationQueue; 34 | dispatch_once(&onceToken, ^{ 35 | __downloadOperationQueue = [[NSOperationQueue alloc] init]; 36 | __downloadOperationQueue.name = @"com.avplayeritem.mccache.download"; 37 | }); 38 | return __downloadOperationQueue; 39 | } 40 | 41 | - (void)main 42 | { 43 | @autoreleasepool 44 | { 45 | if ([self isCancelled]) 46 | { 47 | [self handleFinished]; 48 | return; 49 | } 50 | [self startURLRequestWithRequest:_loadingRequest range:_range]; 51 | [self handleFinished]; 52 | } 53 | } 54 | 55 | - (void)handleFinished 56 | { 57 | if (self.finishBlock) 58 | { 59 | self.finishBlock(_error); 60 | } 61 | } 62 | 63 | - (void)cancel 64 | { 65 | [super cancel]; 66 | [_connection cancel]; 67 | [self synchronizeCacheFileIfNeeded]; 68 | [self stopRunLoop]; 69 | } 70 | 71 | - (void)startURLRequestWithRequest:(AVAssetResourceLoadingRequest *)loadingRequest range:(NSRange)range 72 | { 73 | NSMutableURLRequest *urlRequest = [loadingRequest.request mutableCopy]; 74 | urlRequest.URL = [loadingRequest.request.URL mc_avplayerOriginalURL]; 75 | _offset = 0; 76 | _requestLength = 0; 77 | if (!(_response && ![_response mc_supportRange])) 78 | { 79 | NSString *rangeValue = MCRangeToHTTPRangeHeader(range); 80 | if (rangeValue) 81 | { 82 | [urlRequest setValue:rangeValue forHTTPHeaderField:@"Range"]; 83 | _offset = range.location; 84 | _requestLength = range.length; 85 | } 86 | } 87 | 88 | _connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:NO]; 89 | [_connection setDelegateQueue:[[self class] downloadOperationQueue]]; 90 | [_connection start]; 91 | [self startRunLoop]; 92 | } 93 | 94 | - (void)synchronizeCacheFileIfNeeded 95 | { 96 | if (_dataSaved) 97 | { 98 | [_cacheFile synchronize]; 99 | } 100 | } 101 | 102 | - (void)startRunLoop 103 | { 104 | _runloop = CFRunLoopGetCurrent(); 105 | CFRunLoopRun(); 106 | } 107 | 108 | - (void)stopRunLoop 109 | { 110 | if (_runloop) 111 | { 112 | CFRunLoopStop(_runloop); 113 | } 114 | } 115 | 116 | #pragma mark - handle connection 117 | - (nullable NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(nullable NSURLResponse *)response 118 | { 119 | if (response) 120 | { 121 | _loadingRequest.redirect = request; 122 | } 123 | return request; 124 | } 125 | 126 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 127 | { 128 | if (_response || !response) 129 | { 130 | return; 131 | } 132 | if ([response isKindOfClass:[NSHTTPURLResponse class]]) 133 | { 134 | _response = (NSHTTPURLResponse *)response; 135 | [_cacheFile setResponse:_response]; 136 | [_loadingRequest mc_fillContentInformation:_response]; 137 | } 138 | if (![_response mc_supportRange]) 139 | { 140 | _offset = 0; 141 | } 142 | if (_offset == NSUIntegerMax) 143 | { 144 | _offset = (NSUInteger)_response.mc_fileLength - _requestLength; 145 | } 146 | } 147 | 148 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 149 | { 150 | if ([_cacheFile saveData:data atOffset:_offset synchronize:NO]) 151 | { 152 | _dataSaved = YES; 153 | _offset += [data length]; 154 | if (![self isCancelled]) 155 | { 156 | [_loadingRequest.dataRequest respondWithData:data]; 157 | } 158 | } 159 | } 160 | 161 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 162 | { 163 | [self synchronizeCacheFileIfNeeded]; 164 | [self stopRunLoop]; 165 | } 166 | 167 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 168 | { 169 | [self synchronizeCacheFileIfNeeded]; 170 | _error = error; 171 | [self stopRunLoop]; 172 | } 173 | @end 174 | -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCCacheSupportUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // MCCacheSupportUtils.h 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | FOUNDATION_EXTERN const NSRange MCInvalidRange; 13 | FOUNDATION_EXTERN NSString *const MCCacheSubDirectoryName; 14 | 15 | NS_INLINE BOOL MCValidByteRange(NSRange range) 16 | { 17 | return ((range.location != NSNotFound) || (range.length > 0)); 18 | } 19 | 20 | NS_INLINE BOOL MCValidFileRange(NSRange range) 21 | { 22 | return ((range.location != NSNotFound) && range.length > 0 && range.length != NSUIntegerMax); 23 | } 24 | 25 | NS_INLINE BOOL MCRangeCanMerge(NSRange range1,NSRange range2) 26 | { 27 | return (NSMaxRange(range1) == range2.location) || (NSMaxRange(range2) == range1.location) || NSIntersectionRange(range1, range2).length > 0; 28 | } 29 | 30 | NS_INLINE NSString* MCRangeToHTTPRangeHeader(NSRange range) 31 | { 32 | if (MCValidByteRange(range)) 33 | { 34 | if (range.location == NSNotFound) 35 | { 36 | return [NSString stringWithFormat:@"bytes=-%tu",range.length]; 37 | } 38 | else if (range.length == NSUIntegerMax) 39 | { 40 | return [NSString stringWithFormat:@"bytes=%tu-",range.location]; 41 | } 42 | else 43 | { 44 | return [NSString stringWithFormat:@"bytes=%tu-%tu",range.location, NSMaxRange(range) - 1]; 45 | } 46 | } 47 | else 48 | { 49 | return nil; 50 | } 51 | } 52 | 53 | NS_INLINE NSString* MCRangeToHTTPRangeReponseHeader(NSRange range,NSUInteger length) 54 | { 55 | if (MCValidByteRange(range)) 56 | { 57 | NSUInteger start = range.location; 58 | NSUInteger end = NSMaxRange(range) - 1; 59 | if (range.location == NSNotFound) 60 | { 61 | start = range.location; 62 | } 63 | else if (range.length == NSUIntegerMax) 64 | { 65 | start = length - range.length; 66 | end = start + range.length - 1; 67 | } 68 | return [NSString stringWithFormat:@"bytes %tu-%tu/%tu",start,end,length]; 69 | } 70 | else 71 | { 72 | return nil; 73 | } 74 | } 75 | 76 | NS_INLINE NSString *MCCacheTemporaryDirectory() 77 | { 78 | return [NSTemporaryDirectory() stringByAppendingPathComponent:MCCacheSubDirectoryName]; 79 | } 80 | 81 | NS_INLINE NSString *MCCacheDocumentyDirectory() 82 | { 83 | NSArray *directories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 84 | if (directories.count > 0) 85 | { 86 | return [[directories firstObject] stringByAppendingPathComponent:MCCacheSubDirectoryName]; 87 | } 88 | return nil; 89 | } 90 | 91 | @interface NSString (MCCacheSupport) 92 | - (NSString *)mc_md5; 93 | - (BOOL)mc_isM3U; 94 | @end 95 | 96 | @interface NSURL (MCCacheSupport) 97 | - (NSURL *)mc_URLByReplacingSchemeWithString:(NSString *)scheme; 98 | - (NSURL *)mc_avplayerCacheSupportURL; 99 | - (NSURL *)mc_avplayerOriginalURL; 100 | - (BOOL)mc_isAvPlayerCacheSupportURL; 101 | - (NSString *)mc_pathComponentRelativeToURL:(NSURL *)baseURL; 102 | - (BOOL)mc_isM3U; 103 | @end 104 | 105 | @interface NSURLRequest (MCCacheSupport) 106 | @property (nonatomic,readonly) NSRange mc_range; 107 | @end 108 | 109 | @interface NSHTTPURLResponse (MCCacheSupport) 110 | - (long long)mc_fileLength; 111 | - (BOOL)mc_supportRange; 112 | @end 113 | 114 | @interface AVAssetResourceLoadingRequest (MCCacheSupport) 115 | - (void)mc_fillContentInformation:(NSHTTPURLResponse *)response; 116 | @end -------------------------------------------------------------------------------- /AVPlayerCacheSupport/Classes/MCCacheSupportUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // MCCacheSupportUtils.m 3 | // AVPlayerCacheSupport 4 | // 5 | // Created by Chengyin on 16/3/21. 6 | // Copyright © 2016年 Chengyin. All rights reserved. 7 | // 8 | 9 | #import "MCCacheSupportUtils.h" 10 | #import 11 | #import 12 | 13 | static NSString *const kAVPlayerItemMCCacheSupportUrlSchemeSuffix = @"-stream"; 14 | NSString *const MCCacheSubDirectoryName = @"AVPlayerMCCache"; 15 | const NSRange MCInvalidRange = {NSNotFound,0}; 16 | 17 | @implementation NSString (MCCacheSupport) 18 | - (NSString *)mc_md5 19 | { 20 | const char *cStr = [self UTF8String]; 21 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 22 | CC_MD5( cStr, (int)strlen(cStr), result ); // This is the md5 call 23 | return [NSString stringWithFormat: 24 | @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 25 | result[0], result[1], result[2], result[3], 26 | result[4], result[5], result[6], result[7], 27 | result[8], result[9], result[10], result[11], 28 | result[12], result[13], result[14], result[15] 29 | ]; 30 | } 31 | 32 | - (BOOL)mc_isM3U 33 | { 34 | return [[[self pathExtension] lowercaseString] hasPrefix:@"m3u"]; 35 | } 36 | @end 37 | 38 | @implementation NSURL (MCCacheSupport) 39 | - (NSURL *)mc_URLByReplacingSchemeWithString:(NSString *)scheme 40 | { 41 | NSURLComponents *components = [NSURLComponents componentsWithURL:self resolvingAgainstBaseURL:YES]; 42 | components.scheme = scheme; 43 | return components.URL; 44 | } 45 | 46 | - (NSURL *)mc_avplayerCacheSupportURL 47 | { 48 | if (![self mc_isAvPlayerCacheSupportURL]) 49 | { 50 | NSString *scheme = [[self scheme] stringByAppendingString:kAVPlayerItemMCCacheSupportUrlSchemeSuffix]; 51 | return [self mc_URLByReplacingSchemeWithString:scheme]; 52 | } 53 | return self; 54 | } 55 | 56 | - (NSURL *)mc_avplayerOriginalURL 57 | { 58 | if ([self mc_isAvPlayerCacheSupportURL]) 59 | { 60 | NSString *scheme = [[self scheme] stringByReplacingOccurrencesOfString:kAVPlayerItemMCCacheSupportUrlSchemeSuffix withString:@""]; 61 | return [self mc_URLByReplacingSchemeWithString:scheme]; 62 | } 63 | return self; 64 | } 65 | 66 | - (BOOL)mc_isAvPlayerCacheSupportURL 67 | { 68 | return [[self scheme] hasSuffix:kAVPlayerItemMCCacheSupportUrlSchemeSuffix]; 69 | } 70 | 71 | - (NSString *)mc_pathComponentRelativeToURL:(NSURL *)baseURL 72 | { 73 | NSString *absoluteString = [self absoluteString]; 74 | NSString *baseURLString = [baseURL absoluteString]; 75 | NSRange range = [absoluteString rangeOfString:baseURLString]; 76 | if (range.location == 0) 77 | { 78 | NSString *subString = [absoluteString substringFromIndex:range.location + range.length]; 79 | return subString; 80 | } 81 | return nil; 82 | } 83 | 84 | - (BOOL)mc_isM3U 85 | { 86 | return [[[self pathExtension] lowercaseString] hasPrefix:@"m3u"]; 87 | } 88 | @end 89 | 90 | @implementation NSURLRequest (MCCacheSupport) 91 | - (NSRange)mc_range 92 | { 93 | NSRange range = NSMakeRange(NSNotFound, 0); 94 | NSString *rangeString = [self allHTTPHeaderFields][@"Range"]; 95 | if ([rangeString hasPrefix:@"bytes="]) 96 | { 97 | NSArray* components = [[rangeString substringFromIndex:6] componentsSeparatedByString:@","]; 98 | if (components.count == 1) 99 | { 100 | components = [[components firstObject] componentsSeparatedByString:@"-"]; 101 | if (components.count == 2) 102 | { 103 | NSString* startString = [components objectAtIndex:0]; 104 | NSInteger startValue = [startString integerValue]; 105 | NSString* endString = [components objectAtIndex:1]; 106 | NSInteger endValue = [endString integerValue]; 107 | if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) 108 | { // The second 500 bytes: "500-999" 109 | range.location = startValue; 110 | range.length = endValue - startValue + 1; 111 | } 112 | else if (startString.length && (startValue >= 0)) 113 | { // The bytes after 9500 bytes: "9500-" 114 | range.location = startValue; 115 | range.length = NSUIntegerMax; 116 | } 117 | else if (endString.length && (endValue > 0)) 118 | { // The final 500 bytes: "-500" 119 | range.location = NSNotFound; 120 | range.length = endValue; 121 | } 122 | } 123 | } 124 | } 125 | return range; 126 | } 127 | @end 128 | 129 | @implementation NSHTTPURLResponse (MCCacheSupport) 130 | - (long long)mc_fileLength 131 | { 132 | NSString *range = [self allHeaderFields][@"Content-Range"]; 133 | if (range) 134 | { 135 | NSArray *ranges = [range componentsSeparatedByString:@"/"]; 136 | if (ranges.count > 0) 137 | { 138 | NSString *lengthString = [[ranges lastObject] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 139 | return [lengthString longLongValue]; 140 | } 141 | } 142 | else 143 | { 144 | return [self expectedContentLength]; 145 | } 146 | return 0; 147 | } 148 | 149 | - (BOOL)mc_supportRange 150 | { 151 | return [self allHeaderFields][@"Content-Range"] != nil; 152 | } 153 | @end 154 | 155 | @implementation AVAssetResourceLoadingRequest (MCCacheSupport) 156 | - (void)mc_fillContentInformation:(NSHTTPURLResponse *)response 157 | { 158 | if (!response) 159 | { 160 | return; 161 | } 162 | 163 | self.response = response; 164 | 165 | if (!self.contentInformationRequest) 166 | { 167 | return; 168 | } 169 | 170 | NSString *mimeType = [response MIMEType]; 171 | CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL); 172 | self.contentInformationRequest.byteRangeAccessSupported = [response mc_supportRange]; 173 | self.contentInformationRequest.contentType = CFBridgingRelease(contentType); 174 | self.contentInformationRequest.contentLength = [response mc_fileLength]; 175 | } 176 | @end -------------------------------------------------------------------------------- /Demo/Common/AudioThumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Common/AudioThumb.png -------------------------------------------------------------------------------- /Demo/Common/MediaManifest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title" : "Audio Stream", 4 | "mediaURL" : "http://m1.music.126.net/aweVZVkfQM-QvxP72KhGEA==/1143492092887042.mp3", 5 | "thumbnailResourceName" : "AudioThumb.png" 6 | }, 7 | { 8 | "title" : "Video Stream", 9 | "mediaURL" : "http://wvideo.spriteapp.cn/video/2016/0328/56f8ec01d9bfe_wpd.mp4", 10 | "thumbnailResourceName" : "VideoThumb.png" 11 | }, 12 | ] 13 | -------------------------------------------------------------------------------- /Demo/Common/VideoThumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Common/VideoThumb.png -------------------------------------------------------------------------------- /Demo/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Sample code project: AVFoundationQueuePlayer-iOS: Using a Mixture of Local File Based Assets and HTTP Live Streaming Assets with AVFoundation 2 | Version: 1.0 3 | 4 | IMPORTANT: This Apple software is supplied to you by Apple 5 | Inc. ("Apple") in consideration of your agreement to the following 6 | terms, and your use, installation, modification or redistribution of 7 | this Apple software constitutes acceptance of these terms. If you do 8 | not agree with these terms, please do not use, install, modify or 9 | redistribute this Apple software. 10 | 11 | In consideration of your agreement to abide by the following terms, and 12 | subject to these terms, Apple grants you a personal, non-exclusive 13 | license, under Apple's copyrights in this original Apple software (the 14 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 15 | Software, with or without modifications, in source and/or binary forms; 16 | provided that if you redistribute the Apple Software in its entirety and 17 | without modifications, you must retain this notice and the following 18 | text and disclaimers in all such redistributions of the Apple Software. 19 | Neither the name, trademarks, service marks or logos of Apple Inc. may 20 | be used to endorse or promote products derived from the Apple Software 21 | without specific prior written permission from Apple. Except as 22 | expressly stated in this notice, no other rights or licenses, express or 23 | implied, are granted by Apple herein, including but not limited to any 24 | patent rights that may be infringed by your derivative works or by other 25 | works in which the Apple Software may be incorporated. 26 | 27 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 28 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 29 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 30 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 31 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 32 | 33 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 35 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 36 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 37 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 38 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 39 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 40 | POSSIBILITY OF SUCH DAMAGE. 41 | 42 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 43 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D2385B461AF5181400DC8ADE /* AAPLAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D2385B451AF5181400DC8ADE /* AAPLAppDelegate.m */; }; 11 | D2385B4B1AF5181400DC8ADE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2385B491AF5181400DC8ADE /* Main.storyboard */; }; 12 | D2385B4D1AF5181400DC8ADE /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2385B4C1AF5181400DC8ADE /* Images.xcassets */; }; 13 | D265BBB71B1792720005C539 /* AAPLPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D265BBB61B1792720005C539 /* AAPLPlayerViewController.m */; }; 14 | D274898E1B1CC58A0020D82A /* AAPLPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = D274898B1B1CC58A0020D82A /* AAPLPlayerView.m */; }; 15 | D274898F1B1CC58A0020D82A /* AAPLQueuedItemCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D274898D1B1CC58A0020D82A /* AAPLQueuedItemCollectionViewCell.m */; }; 16 | D27489941B1CC6E00020D82A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D27489901B1CC6E00020D82A /* Localizable.strings */; }; 17 | D27489951B1CC6E00020D82A /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = D27489921B1CC6E00020D82A /* Localizable.stringsdict */; }; 18 | D27755701B0D0A4100C9D649 /* MediaManifest.json in Resources */ = {isa = PBXBuildFile; fileRef = D277556F1B0D0A4100C9D649 /* MediaManifest.json */; }; 19 | D27755781B0D0A5400C9D649 /* AudioThumb.png in Resources */ = {isa = PBXBuildFile; fileRef = D27755741B0D0A5400C9D649 /* AudioThumb.png */; }; 20 | D27755791B0D0A5400C9D649 /* VideoThumb.png in Resources */ = {isa = PBXBuildFile; fileRef = D27755751B0D0A5400C9D649 /* VideoThumb.png */; }; 21 | D2C148E71B0FA816004F41DA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C148E61B0FA816004F41DA /* main.m */; }; 22 | DE4C62E31CD8558E004B60FC /* AVPlayerItem+MCCacheSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = DE4C62D51CD8558E004B60FC /* AVPlayerItem+MCCacheSupport.m */; }; 23 | DE4C62E91CD8558E004B60FC /* MCCacheSupportUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DE4C62E21CD8558E004B60FC /* MCCacheSupportUtils.m */; }; 24 | DEE18EF91CD9D75300662D5E /* MCAVPlayerItemCacheFile.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE18EF01CD9D75300662D5E /* MCAVPlayerItemCacheFile.m */; }; 25 | DEE18EFA1CD9D75300662D5E /* MCAVPlayerItemCacheLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE18EF21CD9D75300662D5E /* MCAVPlayerItemCacheLoader.m */; }; 26 | DEE18EFB1CD9D75300662D5E /* MCAVPlayerItemCacheTask.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE18EF41CD9D75300662D5E /* MCAVPlayerItemCacheTask.m */; }; 27 | DEE18EFC1CD9D75300662D5E /* MCAVPlayerItemLocalCacheTask.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE18EF61CD9D75300662D5E /* MCAVPlayerItemLocalCacheTask.m */; }; 28 | DEE18EFD1CD9D75300662D5E /* MCAVPlayerItemRemoteCacheTask.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE18EF81CD9D75300662D5E /* MCAVPlayerItemRemoteCacheTask.m */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | D2385B421AF5181400DC8ADE /* AVFoundationQueuePlayer-ObjC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AVFoundationQueuePlayer-ObjC.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | D2385B451AF5181400DC8ADE /* AAPLAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AAPLAppDelegate.m; sourceTree = ""; }; 34 | D2385B4A1AF5181400DC8ADE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 35 | D2385B4C1AF5181400DC8ADE /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 36 | D2385B511AF5181400DC8ADE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | D265BBB61B1792720005C539 /* AAPLPlayerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AAPLPlayerViewController.m; sourceTree = ""; }; 38 | D27328DB1B1E22FD004EE77D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 39 | D274898A1B1CC58A0020D82A /* AAPLPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLPlayerView.h; sourceTree = ""; }; 40 | D274898B1B1CC58A0020D82A /* AAPLPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AAPLPlayerView.m; sourceTree = ""; }; 41 | D274898C1B1CC58A0020D82A /* AAPLQueuedItemCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLQueuedItemCollectionViewCell.h; sourceTree = ""; }; 42 | D274898D1B1CC58A0020D82A /* AAPLQueuedItemCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AAPLQueuedItemCollectionViewCell.m; sourceTree = ""; }; 43 | D27489911B1CC6E00020D82A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 44 | D27489931B1CC6E00020D82A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 45 | D277556F1B0D0A4100C9D649 /* MediaManifest.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = MediaManifest.json; path = ../Common/MediaManifest.json; sourceTree = SOURCE_ROOT; }; 46 | D27755741B0D0A5400C9D649 /* AudioThumb.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = AudioThumb.png; path = ../Common/AudioThumb.png; sourceTree = SOURCE_ROOT; }; 47 | D27755751B0D0A5400C9D649 /* VideoThumb.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = VideoThumb.png; path = ../Common/VideoThumb.png; sourceTree = SOURCE_ROOT; }; 48 | D2C148E61B0FA816004F41DA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 49 | D2C148E91B0FAAE1004F41DA /* AAPLAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLAppDelegate.h; sourceTree = ""; }; 50 | D2C148EA1B0FAAF2004F41DA /* AAPLPlayerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLPlayerViewController.h; sourceTree = ""; }; 51 | DE4C62D41CD8558E004B60FC /* AVPlayerItem+MCCacheSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AVPlayerItem+MCCacheSupport.h"; sourceTree = ""; }; 52 | DE4C62D51CD8558E004B60FC /* AVPlayerItem+MCCacheSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AVPlayerItem+MCCacheSupport.m"; sourceTree = ""; }; 53 | DE4C62E11CD8558E004B60FC /* MCCacheSupportUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCCacheSupportUtils.h; sourceTree = ""; }; 54 | DE4C62E21CD8558E004B60FC /* MCCacheSupportUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCCacheSupportUtils.m; sourceTree = ""; }; 55 | DEE18EEF1CD9D75300662D5E /* MCAVPlayerItemCacheFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCAVPlayerItemCacheFile.h; sourceTree = ""; }; 56 | DEE18EF01CD9D75300662D5E /* MCAVPlayerItemCacheFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCAVPlayerItemCacheFile.m; sourceTree = ""; }; 57 | DEE18EF11CD9D75300662D5E /* MCAVPlayerItemCacheLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCAVPlayerItemCacheLoader.h; sourceTree = ""; }; 58 | DEE18EF21CD9D75300662D5E /* MCAVPlayerItemCacheLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCAVPlayerItemCacheLoader.m; sourceTree = ""; }; 59 | DEE18EF31CD9D75300662D5E /* MCAVPlayerItemCacheTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCAVPlayerItemCacheTask.h; sourceTree = ""; }; 60 | DEE18EF41CD9D75300662D5E /* MCAVPlayerItemCacheTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCAVPlayerItemCacheTask.m; sourceTree = ""; }; 61 | DEE18EF51CD9D75300662D5E /* MCAVPlayerItemLocalCacheTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCAVPlayerItemLocalCacheTask.h; sourceTree = ""; }; 62 | DEE18EF61CD9D75300662D5E /* MCAVPlayerItemLocalCacheTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCAVPlayerItemLocalCacheTask.m; sourceTree = ""; }; 63 | DEE18EF71CD9D75300662D5E /* MCAVPlayerItemRemoteCacheTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCAVPlayerItemRemoteCacheTask.h; sourceTree = ""; }; 64 | DEE18EF81CD9D75300662D5E /* MCAVPlayerItemRemoteCacheTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCAVPlayerItemRemoteCacheTask.m; sourceTree = ""; }; 65 | /* End PBXFileReference section */ 66 | 67 | /* Begin PBXFrameworksBuildPhase section */ 68 | D2385B3F1AF5181400DC8ADE /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | /* End PBXFrameworksBuildPhase section */ 76 | 77 | /* Begin PBXGroup section */ 78 | D2385B391AF5181400DC8ADE = { 79 | isa = PBXGroup; 80 | children = ( 81 | D27328DB1B1E22FD004EE77D /* README.md */, 82 | DE4C62D31CD8558E004B60FC /* AVPlayerCacheSupport */, 83 | D2385B441AF5181400DC8ADE /* AVFoundationQueuePlayer-iOS */, 84 | D2385B431AF5181400DC8ADE /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | D2385B431AF5181400DC8ADE /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | D2385B421AF5181400DC8ADE /* AVFoundationQueuePlayer-ObjC.app */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | D2385B441AF5181400DC8ADE /* AVFoundationQueuePlayer-iOS */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | D2C148E91B0FAAE1004F41DA /* AAPLAppDelegate.h */, 100 | D2385B451AF5181400DC8ADE /* AAPLAppDelegate.m */, 101 | D2C148EA1B0FAAF2004F41DA /* AAPLPlayerViewController.h */, 102 | D265BBB61B1792720005C539 /* AAPLPlayerViewController.m */, 103 | D274898A1B1CC58A0020D82A /* AAPLPlayerView.h */, 104 | D274898B1B1CC58A0020D82A /* AAPLPlayerView.m */, 105 | D274898C1B1CC58A0020D82A /* AAPLQueuedItemCollectionViewCell.h */, 106 | D274898D1B1CC58A0020D82A /* AAPLQueuedItemCollectionViewCell.m */, 107 | D2385B491AF5181400DC8ADE /* Main.storyboard */, 108 | D2385B4C1AF5181400DC8ADE /* Images.xcassets */, 109 | D2385B511AF5181400DC8ADE /* Info.plist */, 110 | D27489901B1CC6E00020D82A /* Localizable.strings */, 111 | D27489921B1CC6E00020D82A /* Localizable.stringsdict */, 112 | D2C148E81B0FA81E004F41DA /* Supporting Files */, 113 | D28778461B011B1900E31BDD /* Common */, 114 | ); 115 | path = "AVFoundationQueuePlayer-iOS"; 116 | sourceTree = ""; 117 | }; 118 | D28778461B011B1900E31BDD /* Common */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | D277556F1B0D0A4100C9D649 /* MediaManifest.json */, 122 | D28778471B011B2800E31BDD /* thumbnails */, 123 | ); 124 | name = Common; 125 | sourceTree = ""; 126 | }; 127 | D28778471B011B2800E31BDD /* thumbnails */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | D27755741B0D0A5400C9D649 /* AudioThumb.png */, 131 | D27755751B0D0A5400C9D649 /* VideoThumb.png */, 132 | ); 133 | name = thumbnails; 134 | sourceTree = ""; 135 | }; 136 | D2C148E81B0FA81E004F41DA /* Supporting Files */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | D2C148E61B0FA816004F41DA /* main.m */, 140 | ); 141 | name = "Supporting Files"; 142 | sourceTree = ""; 143 | }; 144 | DE4C62D31CD8558E004B60FC /* AVPlayerCacheSupport */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | DE4C62D41CD8558E004B60FC /* AVPlayerItem+MCCacheSupport.h */, 148 | DE4C62D51CD8558E004B60FC /* AVPlayerItem+MCCacheSupport.m */, 149 | DE4C62D61CD8558E004B60FC /* Classes */, 150 | ); 151 | name = AVPlayerCacheSupport; 152 | path = ../../AVPlayerCacheSupport; 153 | sourceTree = ""; 154 | }; 155 | DE4C62D61CD8558E004B60FC /* Classes */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | DE4C62E11CD8558E004B60FC /* MCCacheSupportUtils.h */, 159 | DE4C62E21CD8558E004B60FC /* MCCacheSupportUtils.m */, 160 | DEE18EF11CD9D75300662D5E /* MCAVPlayerItemCacheLoader.h */, 161 | DEE18EF21CD9D75300662D5E /* MCAVPlayerItemCacheLoader.m */, 162 | DEE18EEF1CD9D75300662D5E /* MCAVPlayerItemCacheFile.h */, 163 | DEE18EF01CD9D75300662D5E /* MCAVPlayerItemCacheFile.m */, 164 | DEE18EF31CD9D75300662D5E /* MCAVPlayerItemCacheTask.h */, 165 | DEE18EF41CD9D75300662D5E /* MCAVPlayerItemCacheTask.m */, 166 | DEE18EF51CD9D75300662D5E /* MCAVPlayerItemLocalCacheTask.h */, 167 | DEE18EF61CD9D75300662D5E /* MCAVPlayerItemLocalCacheTask.m */, 168 | DEE18EF71CD9D75300662D5E /* MCAVPlayerItemRemoteCacheTask.h */, 169 | DEE18EF81CD9D75300662D5E /* MCAVPlayerItemRemoteCacheTask.m */, 170 | ); 171 | path = Classes; 172 | sourceTree = ""; 173 | }; 174 | /* End PBXGroup section */ 175 | 176 | /* Begin PBXNativeTarget section */ 177 | D2385B411AF5181400DC8ADE /* AVFoundationQueuePlayer-iOS */ = { 178 | isa = PBXNativeTarget; 179 | buildConfigurationList = D2385B5F1AF5181400DC8ADE /* Build configuration list for PBXNativeTarget "AVFoundationQueuePlayer-iOS" */; 180 | buildPhases = ( 181 | D2385B3E1AF5181400DC8ADE /* Sources */, 182 | D2385B3F1AF5181400DC8ADE /* Frameworks */, 183 | D2385B401AF5181400DC8ADE /* Resources */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | ); 189 | name = "AVFoundationQueuePlayer-iOS"; 190 | productName = "AVFoundationQueuePlayer-iOS"; 191 | productReference = D2385B421AF5181400DC8ADE /* AVFoundationQueuePlayer-ObjC.app */; 192 | productType = "com.apple.product-type.application"; 193 | }; 194 | /* End PBXNativeTarget section */ 195 | 196 | /* Begin PBXProject section */ 197 | D2385B3A1AF5181400DC8ADE /* Project object */ = { 198 | isa = PBXProject; 199 | attributes = { 200 | LastUpgradeCheck = 0720; 201 | ORGANIZATIONNAME = "Apple Inc."; 202 | TargetAttributes = { 203 | D2385B411AF5181400DC8ADE = { 204 | CreatedOnToolsVersion = 7.0; 205 | }; 206 | }; 207 | }; 208 | buildConfigurationList = D2385B3D1AF5181400DC8ADE /* Build configuration list for PBXProject "AVFoundationQueuePlayer-iOS" */; 209 | compatibilityVersion = "Xcode 3.2"; 210 | developmentRegion = English; 211 | hasScannedForEncodings = 0; 212 | knownRegions = ( 213 | en, 214 | Base, 215 | ); 216 | mainGroup = D2385B391AF5181400DC8ADE; 217 | productRefGroup = D2385B431AF5181400DC8ADE /* Products */; 218 | projectDirPath = ""; 219 | projectRoot = ""; 220 | targets = ( 221 | D2385B411AF5181400DC8ADE /* AVFoundationQueuePlayer-iOS */, 222 | ); 223 | }; 224 | /* End PBXProject section */ 225 | 226 | /* Begin PBXResourcesBuildPhase section */ 227 | D2385B401AF5181400DC8ADE /* Resources */ = { 228 | isa = PBXResourcesBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | D27489941B1CC6E00020D82A /* Localizable.strings in Resources */, 232 | D2385B4B1AF5181400DC8ADE /* Main.storyboard in Resources */, 233 | D27489951B1CC6E00020D82A /* Localizable.stringsdict in Resources */, 234 | D27755701B0D0A4100C9D649 /* MediaManifest.json in Resources */, 235 | D27755791B0D0A5400C9D649 /* VideoThumb.png in Resources */, 236 | D27755781B0D0A5400C9D649 /* AudioThumb.png in Resources */, 237 | D2385B4D1AF5181400DC8ADE /* Images.xcassets in Resources */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | /* End PBXResourcesBuildPhase section */ 242 | 243 | /* Begin PBXSourcesBuildPhase section */ 244 | D2385B3E1AF5181400DC8ADE /* Sources */ = { 245 | isa = PBXSourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | D274898F1B1CC58A0020D82A /* AAPLQueuedItemCollectionViewCell.m in Sources */, 249 | DE4C62E31CD8558E004B60FC /* AVPlayerItem+MCCacheSupport.m in Sources */, 250 | D2385B461AF5181400DC8ADE /* AAPLAppDelegate.m in Sources */, 251 | DEE18EF91CD9D75300662D5E /* MCAVPlayerItemCacheFile.m in Sources */, 252 | D274898E1B1CC58A0020D82A /* AAPLPlayerView.m in Sources */, 253 | DE4C62E91CD8558E004B60FC /* MCCacheSupportUtils.m in Sources */, 254 | DEE18EFC1CD9D75300662D5E /* MCAVPlayerItemLocalCacheTask.m in Sources */, 255 | DEE18EFB1CD9D75300662D5E /* MCAVPlayerItemCacheTask.m in Sources */, 256 | DEE18EFD1CD9D75300662D5E /* MCAVPlayerItemRemoteCacheTask.m in Sources */, 257 | DEE18EFA1CD9D75300662D5E /* MCAVPlayerItemCacheLoader.m in Sources */, 258 | D265BBB71B1792720005C539 /* AAPLPlayerViewController.m in Sources */, 259 | D2C148E71B0FA816004F41DA /* main.m in Sources */, 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | /* End PBXSourcesBuildPhase section */ 264 | 265 | /* Begin PBXVariantGroup section */ 266 | D2385B491AF5181400DC8ADE /* Main.storyboard */ = { 267 | isa = PBXVariantGroup; 268 | children = ( 269 | D2385B4A1AF5181400DC8ADE /* Base */, 270 | ); 271 | name = Main.storyboard; 272 | sourceTree = ""; 273 | }; 274 | D27489901B1CC6E00020D82A /* Localizable.strings */ = { 275 | isa = PBXVariantGroup; 276 | children = ( 277 | D27489911B1CC6E00020D82A /* en */, 278 | ); 279 | name = Localizable.strings; 280 | sourceTree = ""; 281 | }; 282 | D27489921B1CC6E00020D82A /* Localizable.stringsdict */ = { 283 | isa = PBXVariantGroup; 284 | children = ( 285 | D27489931B1CC6E00020D82A /* en */, 286 | ); 287 | name = Localizable.stringsdict; 288 | sourceTree = ""; 289 | }; 290 | /* End PBXVariantGroup section */ 291 | 292 | /* Begin XCBuildConfiguration section */ 293 | D2385B5D1AF5181400DC8ADE /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ALWAYS_SEARCH_USER_PATHS = NO; 297 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 298 | CLANG_CXX_LIBRARY = "libc++"; 299 | CLANG_ENABLE_MODULES = YES; 300 | CLANG_ENABLE_OBJC_ARC = YES; 301 | CLANG_WARN_BOOL_CONVERSION = YES; 302 | CLANG_WARN_CONSTANT_CONVERSION = YES; 303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 304 | CLANG_WARN_EMPTY_BODY = YES; 305 | CLANG_WARN_ENUM_CONVERSION = YES; 306 | CLANG_WARN_INT_CONVERSION = YES; 307 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 308 | CLANG_WARN_UNREACHABLE_CODE = YES; 309 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 310 | CODE_SIGN_IDENTITY = "iPhone Developer"; 311 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 312 | COPY_PHASE_STRIP = NO; 313 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 314 | ENABLE_STRICT_OBJC_MSGSEND = YES; 315 | ENABLE_TESTABILITY = YES; 316 | GCC_C_LANGUAGE_STANDARD = gnu99; 317 | GCC_DYNAMIC_NO_PIC = NO; 318 | GCC_NO_COMMON_BLOCKS = YES; 319 | GCC_OPTIMIZATION_LEVEL = 0; 320 | GCC_PREPROCESSOR_DEFINITIONS = ( 321 | "DEBUG=1", 322 | "$(inherited)", 323 | ); 324 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 325 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 326 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 327 | GCC_WARN_UNDECLARED_SELECTOR = YES; 328 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 329 | GCC_WARN_UNUSED_FUNCTION = YES; 330 | GCC_WARN_UNUSED_VARIABLE = YES; 331 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 332 | MTL_ENABLE_DEBUG_INFO = YES; 333 | ONLY_ACTIVE_ARCH = YES; 334 | SDKROOT = iphoneos; 335 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 336 | TARGETED_DEVICE_FAMILY = "1,2"; 337 | }; 338 | name = Debug; 339 | }; 340 | D2385B5E1AF5181400DC8ADE /* Release */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ALWAYS_SEARCH_USER_PATHS = NO; 344 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 345 | CLANG_CXX_LIBRARY = "libc++"; 346 | CLANG_ENABLE_MODULES = YES; 347 | CLANG_ENABLE_OBJC_ARC = YES; 348 | CLANG_WARN_BOOL_CONVERSION = YES; 349 | CLANG_WARN_CONSTANT_CONVERSION = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_EMPTY_BODY = YES; 352 | CLANG_WARN_ENUM_CONVERSION = YES; 353 | CLANG_WARN_INT_CONVERSION = YES; 354 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 355 | CLANG_WARN_UNREACHABLE_CODE = YES; 356 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 357 | CODE_SIGN_IDENTITY = "iPhone Developer"; 358 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 359 | COPY_PHASE_STRIP = NO; 360 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 361 | ENABLE_NS_ASSERTIONS = NO; 362 | ENABLE_STRICT_OBJC_MSGSEND = YES; 363 | GCC_C_LANGUAGE_STANDARD = gnu99; 364 | GCC_NO_COMMON_BLOCKS = YES; 365 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 366 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 367 | GCC_WARN_UNDECLARED_SELECTOR = YES; 368 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 369 | GCC_WARN_UNUSED_FUNCTION = YES; 370 | GCC_WARN_UNUSED_VARIABLE = YES; 371 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 372 | MTL_ENABLE_DEBUG_INFO = NO; 373 | SDKROOT = iphoneos; 374 | TARGETED_DEVICE_FAMILY = "1,2"; 375 | VALIDATE_PRODUCT = YES; 376 | }; 377 | name = Release; 378 | }; 379 | D2385B601AF5181400DC8ADE /* Debug */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 383 | CODE_SIGN_IDENTITY = "iPhone Developer"; 384 | INFOPLIST_FILE = "AVFoundationQueuePlayer-iOS/Info.plist"; 385 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 386 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 387 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)"; 388 | PRODUCT_NAME = "AVFoundationQueuePlayer-ObjC"; 389 | PROVISIONING_PROFILE = ""; 390 | }; 391 | name = Debug; 392 | }; 393 | D2385B611AF5181400DC8ADE /* Release */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 397 | CODE_SIGN_IDENTITY = "iPhone Developer"; 398 | INFOPLIST_FILE = "AVFoundationQueuePlayer-iOS/Info.plist"; 399 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 400 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 401 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)"; 402 | PRODUCT_NAME = "AVFoundationQueuePlayer-ObjC"; 403 | PROVISIONING_PROFILE = ""; 404 | }; 405 | name = Release; 406 | }; 407 | /* End XCBuildConfiguration section */ 408 | 409 | /* Begin XCConfigurationList section */ 410 | D2385B3D1AF5181400DC8ADE /* Build configuration list for PBXProject "AVFoundationQueuePlayer-iOS" */ = { 411 | isa = XCConfigurationList; 412 | buildConfigurations = ( 413 | D2385B5D1AF5181400DC8ADE /* Debug */, 414 | D2385B5E1AF5181400DC8ADE /* Release */, 415 | ); 416 | defaultConfigurationIsVisible = 0; 417 | defaultConfigurationName = Release; 418 | }; 419 | D2385B5F1AF5181400DC8ADE /* Build configuration list for PBXNativeTarget "AVFoundationQueuePlayer-iOS" */ = { 420 | isa = XCConfigurationList; 421 | buildConfigurations = ( 422 | D2385B601AF5181400DC8ADE /* Debug */, 423 | D2385B611AF5181400DC8ADE /* Release */, 424 | ); 425 | defaultConfigurationIsVisible = 0; 426 | defaultConfigurationName = Release; 427 | }; 428 | /* End XCConfigurationList section */ 429 | }; 430 | rootObject = D2385B3A1AF5181400DC8ADE /* Project object */; 431 | } 432 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS.xcodeproj/project.xcworkspace/xcshareddata/AVFoundationQueuePlayer-iOS.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "06A1804BE31D5AFD55791C12300724265363A8DE", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "06A1804BE31D5AFD55791C12300724265363A8DE" : 0, 8 | "3129A4513291312DE59545E1E7E7C4F338086BD0" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "ECACF624-BED0-4192-96AB-BD8B9D2EBED0", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "06A1804BE31D5AFD55791C12300724265363A8DE" : "AVPlayerCacheSupport\/", 13 | "3129A4513291312DE59545E1E7E7C4F338086BD0" : "AVPlayerCacheSupport\/AVPlayerCacheSupport\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "AVFoundationQueuePlayer-iOS", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Demo\/Objective-C\/AVFoundationQueuePlayer-iOS.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/msching\/AVPlayerCacheSupport.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "06A1804BE31D5AFD55791C12300724265363A8DE" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/git.hz.netease.com\/git\/chengyin\/AVPlayerCacheSupport.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "3129A4513291312DE59545E1E7E7C4F338086BD0" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/AAPLAppDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | Application delegate. 7 | */ 8 | 9 | @import UIKit; 10 | 11 | @interface AAPLAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/AAPLAppDelegate.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | Application delegate. 7 | */ 8 | 9 | #import "AAPLAppDelegate.h" 10 | 11 | @implementation AAPLAppDelegate 12 | @end 13 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/AAPLPlayerView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | View containing an AVPlayerLayer. 7 | */ 8 | 9 | @import UIKit; 10 | 11 | @class AVPlayer; 12 | 13 | @interface AAPLPlayerView : UIView 14 | @property AVPlayer *player; 15 | @property (readonly) AVPlayerLayer *playerLayer; 16 | @end 17 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/AAPLPlayerView.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | View containing an AVPlayerLayer. 7 | */ 8 | 9 | @import Foundation; 10 | @import AVFoundation; 11 | #import "AAPLPlayerView.h" 12 | 13 | 14 | @implementation AAPLPlayerView 15 | 16 | - (AVPlayer *)player { 17 | return self.playerLayer.player; 18 | } 19 | 20 | - (void)setPlayer:(AVPlayer *)player { 21 | self.playerLayer.player = player; 22 | } 23 | 24 | // override UIView 25 | + (Class)layerClass { 26 | return [AVPlayerLayer class]; 27 | } 28 | 29 | - (AVPlayerLayer *)playerLayer { 30 | return (AVPlayerLayer *)self.layer; 31 | } 32 | 33 | @end 34 | 35 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/AAPLPlayerViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | View controller containing a player view and basic playback controls. 7 | */ 8 | 9 | @import UIKit; 10 | 11 | 12 | @class AAPLPlayerView; 13 | 14 | @interface AAPLPlayerViewController : UIViewController 15 | 16 | @property (readonly) AVQueuePlayer *player; 17 | 18 | /* 19 | @{ 20 | NSURL(asset URL) : @{ 21 | NSString(title) : NSString, 22 | NSString(thumbnail) : UIImage 23 | } 24 | } 25 | */ 26 | @property NSMutableDictionary *loadedAssets; 27 | 28 | @property CMTime currentTime; 29 | @property (readonly) CMTime duration; 30 | @property float rate; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/AAPLPlayerViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | View controller containing a player view and basic playback controls. 7 | */ 8 | 9 | 10 | @import Foundation; 11 | @import AVFoundation; 12 | @import CoreMedia.CMTime; 13 | #import "AAPLPlayerViewController.h" 14 | #import "AAPLPlayerView.h" 15 | #import "AAPLQueuedItemCollectionViewCell.h" 16 | #import "AVPlayerItem+MCCacheSupport.h" 17 | 18 | 19 | // Private properties 20 | @interface AAPLPlayerViewController () 21 | { 22 | AVQueuePlayer *_player; 23 | AVURLAsset *_asset; 24 | 25 | /* 26 | A token obtained from calling `player`'s `addPeriodicTimeObserverForInterval(_:queue:usingBlock:)` 27 | method. 28 | */ 29 | id _timeObserverToken; 30 | AVPlayerItem *_playerItem; 31 | } 32 | 33 | @property (readonly) AVPlayerLayer *playerLayer; 34 | 35 | @property NSMutableDictionary *assetTitlesAndThumbnailsByURL; 36 | 37 | // Formatter to provide formatted value for seconds displayed in `startTimeLabel` and `durationLabel`. 38 | @property (readonly) NSDateComponentsFormatter *timeRemainingFormatter; 39 | 40 | @property BOOL seeking; 41 | 42 | @property (weak) IBOutlet UISlider *timeSlider; 43 | @property (weak) IBOutlet UILabel *startTimeLabel; 44 | @property (weak) IBOutlet UILabel *durationLabel; 45 | @property (weak) IBOutlet UIButton *rewindButton; 46 | @property (weak) IBOutlet UIButton *playPauseButton; 47 | @property (weak) IBOutlet UIButton *fastForwardButton; 48 | @property (weak) IBOutlet UIButton *clearButton; 49 | @property (weak) IBOutlet UICollectionView *collectionView; 50 | @property (weak) IBOutlet UILabel *queueLabel; 51 | @property (weak) IBOutlet AAPLPlayerView *playerView; 52 | 53 | @end 54 | 55 | @implementation AAPLPlayerViewController 56 | 57 | // MARK: - View Controller 58 | 59 | /* 60 | KVO context used to differentiate KVO callbacks for this class versus other 61 | classes in its class hierarchy. 62 | */ 63 | static int AAPLPlayerViewControllerKVOContext = 0; 64 | 65 | - (void)viewWillAppear:(BOOL)animated { 66 | [super viewWillAppear:animated]; 67 | 68 | /* 69 | Update the UI when these player properties change. 70 | 71 | Use the context parameter to distinguish KVO for our particular observers and not 72 | those destined for a subclass that also happens to be observing these properties. 73 | */ 74 | [self addObserver:self forKeyPath:@"player.currentItem.duration" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&AAPLPlayerViewControllerKVOContext]; 75 | [self addObserver:self forKeyPath:@"player.rate" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&AAPLPlayerViewControllerKVOContext]; 76 | [self addObserver:self forKeyPath:@"player.currentItem.status" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&AAPLPlayerViewControllerKVOContext]; 77 | [self addObserver:self forKeyPath:@"player.currentItem" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&AAPLPlayerViewControllerKVOContext]; 78 | 79 | self.playerView.playerLayer.player = self.player; 80 | 81 | /* 82 | Read the list of assets we'll be using from a JSON file. 83 | */ 84 | [self asynchronouslyLoadURLAssetsWithManifestURL:[[NSBundle mainBundle] URLForResource:@"MediaManifest" withExtension:@"json"]]; 85 | 86 | // Use a weak self variable to avoid a retain cycle in the block. 87 | AAPLPlayerViewController __weak *weakSelf = self; 88 | _timeObserverToken = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) { 89 | double timeElapsed = CMTimeGetSeconds(time); 90 | if (!weakSelf.timeSlider.tracking && !weakSelf.seeking) { 91 | 92 | weakSelf.timeSlider.value = timeElapsed; 93 | } 94 | weakSelf.startTimeLabel.text = [weakSelf createTimeString: timeElapsed]; 95 | }]; 96 | } 97 | 98 | - (void)viewDidDisappear:(BOOL)animated { 99 | [super viewDidDisappear:animated]; 100 | 101 | if (_timeObserverToken) { 102 | [self.player removeTimeObserver:_timeObserverToken]; 103 | _timeObserverToken = nil; 104 | } 105 | 106 | [self.player pause]; 107 | 108 | [self removeObserver:self forKeyPath:@"player.currentItem.duration" context:&AAPLPlayerViewControllerKVOContext]; 109 | [self removeObserver:self forKeyPath:@"player.rate" context:&AAPLPlayerViewControllerKVOContext]; 110 | [self removeObserver:self forKeyPath:@"player.currentItem.status" context:&AAPLPlayerViewControllerKVOContext]; 111 | [self removeObserver:self forKeyPath:@"player.currentItem" context:&AAPLPlayerViewControllerKVOContext]; 112 | } 113 | 114 | 115 | // MARK: - Properties 116 | 117 | // Will attempt load and test these asset keys before playing 118 | + (NSArray *)assetKeysRequiredToPlay { 119 | return @[@"playable", @"hasProtectedContent"]; 120 | } 121 | 122 | - (AVQueuePlayer *)player { 123 | if (!_player) { 124 | _player = [[AVQueuePlayer alloc] init]; 125 | } 126 | return _player; 127 | } 128 | 129 | - (CMTime)currentTime { 130 | return self.player.currentTime; 131 | } 132 | 133 | - (void)setCurrentTime:(CMTime)newCurrentTime { 134 | self.seeking = YES; 135 | AAPLPlayerViewController __weak *weakSelf = self; 136 | [self.player seekToTime:newCurrentTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) { 137 | weakSelf.seeking = NO; 138 | }]; 139 | } 140 | 141 | - (CMTime)duration { 142 | return self.player.currentItem ? self.player.currentItem.duration : kCMTimeZero; 143 | } 144 | 145 | - (float)rate { 146 | return self.player.rate; 147 | } 148 | 149 | - (void)setRate:(float)newRate { 150 | self.player.rate = newRate; 151 | } 152 | 153 | - (AVPlayerLayer *)playerLayer { 154 | return self.playerView.playerLayer; 155 | } 156 | 157 | - (NSDateComponentsFormatter *)timeRemainingFormatter { 158 | NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; 159 | formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad; 160 | formatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond; 161 | 162 | return formatter; 163 | } 164 | 165 | // MARK: - Asset Loading 166 | 167 | /* 168 | Prepare an AVAsset for use on a background thread. When the minimum set 169 | of properties we require (`assetKeysRequiredToPlay`) are loaded then add 170 | the asset to the `assetTitlesAndThumbnails` dictionary. We'll use that 171 | dictionary to populate the "Add Item" button popover. 172 | */ 173 | - (void)asynchronouslyLoadURLAsset:(AVURLAsset *)asset title:(NSString *)title thumbnailResourceName:(NSString *)thumbnailResourceName { 174 | 175 | /* 176 | Using AVAsset now runs the risk of blocking the current thread (the 177 | main UI thread) whilst I/O happens to populate the properties. It's 178 | prudent to defer our work until the properties we need have been loaded. 179 | */ 180 | [asset loadValuesAsynchronouslyForKeys:AAPLPlayerViewController.assetKeysRequiredToPlay completionHandler:^{ 181 | 182 | /* 183 | The asset invokes its completion handler on an arbitrary queue. 184 | To avoid multiple threads using our internal state at the same time 185 | we'll elect to use the main thread at all times, let's dispatch 186 | our handler to the main queue. 187 | */ 188 | dispatch_async(dispatch_get_main_queue(), ^{ 189 | 190 | /* 191 | This method is called when the `AVAsset` for our URL has 192 | completed the loading of the values of the specified array 193 | of keys. 194 | */ 195 | 196 | /* 197 | Test whether the values of each of the keys we need have been 198 | successfully loaded. 199 | */ 200 | for (NSString *key in self.class.assetKeysRequiredToPlay) { 201 | NSError *error = nil; 202 | if ([asset statusOfValueForKey:key error:&error] == AVKeyValueStatusFailed) { 203 | NSString *stringFormat = NSLocalizedString(@"error.asset_%@_key_%@_failed.description", @"Can't use this AVAsset because one of it's keys failed to load"); 204 | 205 | NSString *message = [NSString localizedStringWithFormat:stringFormat, title, key]; 206 | 207 | [self handleErrorWithMessage:message error:error]; 208 | 209 | return; 210 | } 211 | } 212 | 213 | // We can't play this asset. 214 | if (!asset.playable || asset.hasProtectedContent) { 215 | NSString *stringFormat = NSLocalizedString(@"error.asset_%@_not_playable.description", @"Can't use this AVAsset because it isn't playable or has protected content"); 216 | 217 | NSString *message = [NSString localizedStringWithFormat:stringFormat, title]; 218 | 219 | [self handleErrorWithMessage:message error:nil]; 220 | 221 | return; 222 | } 223 | 224 | /* 225 | We can play this asset. Create a new AVPlayerItem and make it 226 | our player's current item. 227 | */ 228 | if (!self.loadedAssets) 229 | self.loadedAssets = [NSMutableDictionary dictionary]; 230 | self.loadedAssets[title] = asset; 231 | 232 | NSString *path = [[NSBundle mainBundle] pathForResource:[thumbnailResourceName stringByDeletingPathExtension] ofType:[thumbnailResourceName pathExtension]]; 233 | UIImage *thumbnail = [[UIImage alloc] initWithContentsOfFile:path]; 234 | if (!self.assetTitlesAndThumbnailsByURL) { 235 | self.assetTitlesAndThumbnailsByURL = [NSMutableDictionary dictionary]; 236 | } 237 | self.assetTitlesAndThumbnailsByURL[asset.URL] = @{ @"title" : title, @"thumbnail" : thumbnail }; 238 | }); 239 | }]; 240 | } 241 | 242 | /* 243 | Read the asset URLs, titles and thumbnail resource names from a JSON manifest 244 | file - then load each asset. 245 | */ 246 | - (void)asynchronouslyLoadURLAssetsWithManifestURL:(NSURL *)jsonURL 247 | { 248 | NSArray *assetsArray = nil; 249 | 250 | NSData *jsonData = [[NSData alloc] initWithContentsOfURL:jsonURL]; 251 | if (jsonData) { 252 | assetsArray = (NSArray *)[NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil]; 253 | if (!assetsArray) { 254 | [self handleErrorWithMessage:NSLocalizedString(@"error.json_parse_failed.description", @"Failed to parse the assets manifest JSON") error:nil]; 255 | } 256 | } 257 | else { 258 | [self handleErrorWithMessage:NSLocalizedString(@"error.json_open_failed.description", @"Failed to open the assets manifest JSON") error:nil]; 259 | } 260 | 261 | for (NSDictionary *assetDict in assetsArray) { 262 | 263 | NSURL *mediaURL = nil; 264 | NSString *optionalResourceName = assetDict[@"mediaResourceName"]; 265 | NSString *optionalURLString = assetDict[@"mediaURL"]; 266 | if (optionalResourceName) { 267 | mediaURL = [[NSBundle mainBundle] URLForResource:[optionalResourceName stringByDeletingPathExtension] withExtension:optionalResourceName.pathExtension]; 268 | } 269 | else if (optionalURLString) { 270 | mediaURL = [NSURL URLWithString:optionalURLString]; 271 | } 272 | 273 | [self asynchronouslyLoadURLAsset:[AVURLAsset URLAssetWithURL:mediaURL options:nil] 274 | title:assetDict[@"title"] 275 | thumbnailResourceName:assetDict[@"thumbnailResourceName"]]; 276 | } 277 | } 278 | 279 | // MARK: - IBActions 280 | 281 | - (IBAction)playPauseButtonWasPressed:(UIButton *)sender { 282 | if (self.player.rate != 1.0) { 283 | // Not playing foward; so play. 284 | if (CMTIME_COMPARE_INLINE(self.currentTime, ==, self.duration)) { 285 | // At end; so got back to beginning. 286 | self.currentTime = kCMTimeZero; 287 | } 288 | [self.player play]; 289 | } else { 290 | // Playing; so pause. 291 | [self.player pause]; 292 | } 293 | } 294 | 295 | - (IBAction)rewindButtonWasPressed:(UIButton *)sender { 296 | self.rate = MAX(self.player.rate - 2.0, -2.0); // rewind no faster than -2.0 297 | } 298 | 299 | - (IBAction)fastForwardButtonWasPressed:(UIButton *)sender { 300 | self.rate = MIN(self.player.rate + 2.0, 2.0); // fast forward no faster than 2.0 301 | } 302 | 303 | - (IBAction)timeSliderDidChange:(UISlider *)sender { 304 | self.currentTime = CMTimeMakeWithSeconds(sender.value, 1000); 305 | } 306 | 307 | - (void)presentModalPopoverAlertController:(UIAlertController *)alertController sender:(UIButton *)sender { 308 | alertController.modalPresentationStyle = UIModalPresentationPopover; 309 | 310 | alertController.popoverPresentationController.sourceView = sender; 311 | alertController.popoverPresentationController.sourceRect = sender.bounds; 312 | alertController.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionAny; 313 | 314 | [self presentViewController:alertController animated:true completion:nil]; 315 | } 316 | 317 | - (IBAction)addItemToQueueButtonPressed:(UIButton *)sender { 318 | 319 | NSString *alertTitle = NSLocalizedString(@"popover.title.addItem", @"Title of popover that adds items to the queue"); 320 | NSString *alertMessage = NSLocalizedString(@"popover.message.addItem", @"Message on popover that adds items to the queue"); 321 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:alertTitle message:alertMessage preferredStyle:UIAlertControllerStyleActionSheet]; 322 | 323 | // Populate the sheet with the titles of the assets we have loaded. 324 | for (NSString *loadedAssetTitle in self.loadedAssets.allKeys) { 325 | AVURLAsset *loadedAsset = self.loadedAssets[loadedAssetTitle]; 326 | AAPLPlayerViewController __weak *weakSelf = self; 327 | [alertController addAction:[UIAlertAction actionWithTitle:loadedAssetTitle style:UIAlertActionStyleDefault handler: 328 | ^(UIAlertAction *action){ 329 | NSArray *oldItemsArray = [weakSelf.player items]; 330 | NSError *error = nil; 331 | AVPlayerItem *newPlayerItem = [AVPlayerItem mc_playerItemWithRemoteURL:[loadedAsset URL] error:&error]; 332 | if (error) 333 | { 334 | NSLog(@"cache failed, %@",error); 335 | } 336 | [newPlayerItem.asset loadValuesAsynchronouslyForKeys:AAPLPlayerViewController.assetKeysRequiredToPlay completionHandler:^{ 337 | dispatch_async(dispatch_get_main_queue(), ^{ 338 | [weakSelf.player insertItem:newPlayerItem afterItem:nil]; 339 | [weakSelf queueDidChangeFromArray:oldItemsArray toArray:[self.player items]]; 340 | }); 341 | }]; 342 | }]]; 343 | } 344 | 345 | NSString *cancelActionTitle = NSLocalizedString(@"popover.title.cancel", @"Title of popover cancel action"); 346 | [alertController addAction:[UIAlertAction actionWithTitle:cancelActionTitle style:UIAlertActionStyleCancel handler:nil]]; 347 | 348 | [self presentModalPopoverAlertController:alertController sender:sender]; 349 | } 350 | 351 | - (IBAction)clearQueueButtonWasPressed:(UIButton *)sender { 352 | 353 | NSString *alertTitle = NSLocalizedString(@"popover.title.clear", @"Title of popover that clears the queue"); 354 | NSString *alertMessage = NSLocalizedString(@"popover.message.clear", @"Message on popover that clears the queue"); 355 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:alertTitle message:alertMessage preferredStyle:UIAlertControllerStyleActionSheet]; 356 | 357 | AAPLPlayerViewController __weak *weakSelf = self; 358 | [alertController addAction:[UIAlertAction actionWithTitle:@"Clear Queue" style:UIAlertActionStyleDestructive handler: 359 | ^(UIAlertAction *action){ 360 | NSArray *oldItemsArray = [weakSelf.player items]; 361 | [weakSelf.player removeAllItems]; 362 | [weakSelf queueDidChangeFromArray:oldItemsArray toArray:[self.player items]]; 363 | 364 | }]]; 365 | 366 | NSString *cancelActionTitle = NSLocalizedString(@"popover.title.cancel", @"Title of popover cancel action"); 367 | [alertController addAction:[UIAlertAction actionWithTitle:cancelActionTitle style:UIAlertActionStyleCancel handler:nil]]; 368 | 369 | [self presentModalPopoverAlertController:alertController sender:sender]; 370 | } 371 | 372 | // MARK: - KV Observation 373 | 374 | // Update our UI when player or player.currentItem changes 375 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 376 | 377 | if (context != &AAPLPlayerViewControllerKVOContext) { 378 | // KVO isn't for us. 379 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 380 | return; 381 | } 382 | 383 | if ([keyPath isEqualToString:@"player.currentItem"]) { 384 | [self queueDidChangeFromArray:nil toArray:[self.player items]]; 385 | 386 | } 387 | else if ([keyPath isEqualToString:@"player.currentItem.duration"]) { 388 | // Update timeSlider and enable/disable controls when duration > 0.0 389 | 390 | // Handle NSNull value for NSKeyValueChangeNewKey, i.e. when player.currentItem is nil 391 | NSValue *newDurationAsValue = change[NSKeyValueChangeNewKey]; 392 | CMTime newDuration = [newDurationAsValue isKindOfClass:[NSValue class]] ? newDurationAsValue.CMTimeValue : kCMTimeZero; 393 | BOOL hasValidDuration = CMTIME_IS_NUMERIC(newDuration) && newDuration.value != 0; 394 | double currentTime = hasValidDuration ? CMTimeGetSeconds(self.currentTime) : 0.0; 395 | double newDurationSeconds = hasValidDuration ? CMTimeGetSeconds(newDuration) : 0.0; 396 | 397 | self.timeSlider.maximumValue = newDurationSeconds; 398 | self.timeSlider.value = currentTime; 399 | self.rewindButton.enabled = hasValidDuration; 400 | self.playPauseButton.enabled = hasValidDuration; 401 | self.fastForwardButton.enabled = hasValidDuration; 402 | self.timeSlider.enabled = hasValidDuration; 403 | self.startTimeLabel.enabled = hasValidDuration; 404 | self.startTimeLabel.text = [self createTimeString:currentTime]; 405 | self.durationLabel.enabled = hasValidDuration; 406 | self.durationLabel.text = [self createTimeString:newDurationSeconds]; 407 | } 408 | else if ([keyPath isEqualToString:@"player.rate"]) { 409 | // Update playPauseButton image 410 | 411 | double newRate = [change[NSKeyValueChangeNewKey] doubleValue]; 412 | UIImage *buttonImage = (newRate == 1.0) ? [UIImage imageNamed:@"PauseButton"] : [UIImage imageNamed:@"PlayButton"]; 413 | [self.playPauseButton setImage:buttonImage forState:UIControlStateNormal]; 414 | 415 | } 416 | else if ([keyPath isEqualToString:@"player.currentItem.status"]) { 417 | // Display an error if status becomes Failed 418 | 419 | // Handle NSNull value for NSKeyValueChangeNewKey, i.e. when player.currentItem is nil 420 | NSNumber *newStatusAsNumber = change[NSKeyValueChangeNewKey]; 421 | AVPlayerItemStatus newStatus = [newStatusAsNumber isKindOfClass:[NSNumber class]] ? newStatusAsNumber.integerValue : AVPlayerItemStatusUnknown; 422 | 423 | if (newStatus == AVPlayerItemStatusFailed) { 424 | [self handleErrorWithMessage:self.player.currentItem.error.localizedDescription error:self.player.currentItem.error]; 425 | } 426 | 427 | } else { 428 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 429 | } 430 | } 431 | 432 | // Trigger KVO for anyone observing our properties affected by player and player.currentItem 433 | + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { 434 | if ([key isEqualToString:@"duration"]) { 435 | return [NSSet setWithArray:@[ @"player.currentItem.duration" ]]; 436 | } else if ([key isEqualToString:@"currentTime"]) { 437 | return [NSSet setWithArray:@[ @"player.currentItem.currentTime" ]]; 438 | } else if ([key isEqualToString:@"rate"]) { 439 | return [NSSet setWithArray:@[ @"player.rate" ]]; 440 | } else { 441 | return [super keyPathsForValuesAffectingValueForKey:key]; 442 | } 443 | } 444 | 445 | // player.items is not KV observable so we need to call this function every time the queue changes 446 | - (void)queueDidChangeFromArray:(NSArray *)oldPlayerItems toArray:(NSArray *)newPlayerItems { 447 | 448 | if (newPlayerItems.count == 0) { 449 | self.queueLabel.text = NSLocalizedString(@"label.queue.empty", @"Queue is empty"); 450 | } 451 | else { 452 | NSString *stringFormat = NSLocalizedString(@"label.queue.%lu items", @"Queue of n item(s)"); 453 | 454 | self.queueLabel.text = [NSString localizedStringWithFormat:stringFormat, newPlayerItems.count]; 455 | } 456 | 457 | BOOL isQueueEmpty = newPlayerItems.count == 0; 458 | self.clearButton.enabled = !isQueueEmpty; 459 | 460 | [self.collectionView reloadData]; 461 | } 462 | 463 | // MARK: - Error Handling 464 | 465 | - (void)handleErrorWithMessage:(NSString *)message error:(NSError *)error { 466 | NSLog(@"Error occurred with message: %@, error: %@.", message, error); 467 | 468 | NSString *alertTitle = NSLocalizedString(@"alert.error.title", @"Alert title for errors"); 469 | NSString *defaultAlertMessage = NSLocalizedString(@"error.default.description", @"Default error message when no NSError provided"); 470 | UIAlertController *controller = [UIAlertController alertControllerWithTitle:alertTitle message:message ?: defaultAlertMessage preferredStyle:UIAlertControllerStyleAlert]; 471 | 472 | NSString *alertActionTitle = NSLocalizedString(@"alert.error.actions.OK", @"OK on error alert"); 473 | UIAlertAction *action = [UIAlertAction actionWithTitle:alertActionTitle style:UIAlertActionStyleDefault handler:nil]; 474 | [controller addAction:action]; 475 | 476 | [self presentViewController:controller animated:YES completion:nil]; 477 | } 478 | 479 | // MARK: UICollectionViewDataSource 480 | 481 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 482 | return [self.player items].count; 483 | } 484 | 485 | - (NSDictionary *)titleAndThumbnailForPlayerItemAtIndexPath:(NSIndexPath *)indexPath { 486 | AVPlayerItem *item = [self.player items][[indexPath indexAtPosition:1]]; 487 | return self.assetTitlesAndThumbnailsByURL[item.mc_URL]; 488 | } 489 | 490 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 491 | 492 | AAPLQueuedItemCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"ItemCell" forIndexPath:indexPath]; 493 | 494 | NSDictionary *titleAndThumbnail = [self titleAndThumbnailForPlayerItemAtIndexPath:indexPath]; 495 | cell.label.text = titleAndThumbnail[@"title"]; 496 | cell.backgroundView = [[UIImageView alloc] initWithImage:titleAndThumbnail[@"thumbnail"]]; 497 | 498 | return cell; 499 | } 500 | 501 | // MARK: Convenience 502 | 503 | - (NSString *)createTimeString:(double)time { 504 | NSDateComponents *components = [[NSDateComponents alloc] init]; 505 | components.second = (NSInteger)fmax(0.0, time); 506 | 507 | return [self.timeRemainingFormatter stringFromDateComponents:components]; 508 | } 509 | 510 | @end 511 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/AAPLQueuedItemCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | Collection view cell to represent an AVPlayerItem in an AVQueuePlayer's queue. 7 | */ 8 | 9 | @import UIKit; 10 | 11 | 12 | @interface AAPLQueuedItemCollectionViewCell: UICollectionViewCell 13 | @property (weak) IBOutlet UILabel *label; 14 | @end 15 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/AAPLQueuedItemCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | Collection view cell to represent an AVPlayerItem in an AVQueuePlayer's queue. 7 | */ 8 | 9 | #import "AAPLQueuedItemCollectionViewCell.h" 10 | 11 | 12 | @implementation AAPLQueuedItemCollectionViewCell 13 | @end 14 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | HelveticaNeue-Italic 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 36 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 72 | 81 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 147 | 166 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PauseButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "PauseButton.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "PauseButton@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "PauseButton@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@2x.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@3x.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PlayButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "PlayButton.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "PlayButton@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "PlayButton@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@2x.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@3x.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ScanBackwardButton.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ScanBackwardButton@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ScanBackwardButton@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@2x.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@3x.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ScanForwardButton.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ScanForwardButton@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ScanForwardButton@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@2x.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstcyx/AVPlayerCacheSupport/5a389538d995c734db3ffee652ae3c3bbcc33f58/Demo/Objective-C/AVFoundationQueuePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@3x.png -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/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 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | Main 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UIRequiresFullScreen 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Localizable strings. 5 | 6 | 7 | */ 8 | 9 | // Alert title for errors. 10 | "alert.error.title" = "Error"; 11 | 12 | // OK action on error alert 13 | "alert.error.actions.OK" = "OK"; 14 | 15 | // Queue is empty. 16 | "label.queue.empty" = "Media queue is empty"; 17 | 18 | // Queue of n item(s). 19 | "label.queue.%lu items" = "Queue of %n media items"; 20 | 21 | // Title on button to clear the queue. 22 | "button.title.clear" = "Clear Queue"; 23 | 24 | // Title of popover that clears the queue. 25 | "popover.title.clear" = "Clear"; 26 | 27 | // Message on popover that clears the queue. 28 | "popover.message.clear" = "Remove all media from the queue?"; 29 | 30 | // Title of popover cancel action. 31 | "popover.title.cancel" = "Cancel"; 32 | 33 | // Title of popover that adds items to the queue. 34 | "popover.title.addItem" = "Add Media"; 35 | 36 | // Message on popover that adds items to the queue. 37 | "popover.message.addItem" = "Select a media type to add to the queue"; 38 | 39 | // Can't use this AVAsset because one of it's keys failed to load. 40 | "error.asset_%@_key_%@_failed.description" = "Media \"%@\" failed to load key \"%@\""; 41 | 42 | // Can't use this AVAsset because it isn't playable or has protected content. 43 | "error.asset_%@_not_playable.description" = "Media \"%@\" isn't playable or has protected content"; 44 | 45 | // Failed to parse the assets manifest JSON. 46 | "error.json_parse_failed.description" = "Failed to parse the manifest"; 47 | 48 | // Failed to open the assets manifest JSON. 49 | "error.json_open_failed.description" = "Failed to open the manifest"; 50 | 51 | // Default error message when no NSError provided. 52 | "error.default.description" = "Unknown error"; 53 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/en.lproj/Localizable.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | label.queue.%lu items 6 | 7 | NSStringLocalizedFormatKey 8 | Queue of %#@lu_items@ 9 | lu_items 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | lu 15 | one 16 | %lu item 17 | other 18 | %lu items 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Demo/Objective-C/AVFoundationQueuePlayer-iOS/main.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: AAPLAppDelegate.m 3 | 4 | Abstract: Sample code for AVFoundationSimplePlayer-ObjC-iOS 5 | 6 | Version: 1.0 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by 9 | Apple Inc. ("Apple") in consideration of your agreement to the 10 | following terms, and your use, installation, modification or 11 | redistribution of this Apple software constitutes acceptance of these 12 | terms. If you do not agree with these terms, please do not use, 13 | install, modify or redistribute this Apple software. 14 | 15 | In consideration of your agreement to abide by the following terms, and 16 | subject to these terms, Apple grants you a personal, non-exclusive 17 | license, under Apple's copyrights in this original Apple software (the 18 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 19 | Software, with or without modifications, in source and/or binary forms; 20 | provided that if you redistribute the Apple Software in its entirety and 21 | without modifications, you must retain this notice and the following 22 | text and disclaimers in all such redistributions of the Apple Software. 23 | Neither the name, trademarks, service marks or logos of Apple Inc. 24 | may be used to endorse or promote products derived from the Apple 25 | Software without specific prior written permission from Apple. Except 26 | as expressly stated in this notice, no other rights or licenses, express 27 | or implied, are granted by Apple herein, including but not limited to 28 | any patent rights that may be infringed by your derivative works or by 29 | other works in which the Apple Software may be incorporated. 30 | 31 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 32 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 33 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 34 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 35 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 36 | 37 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 38 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 40 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 41 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 42 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 43 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 44 | POSSIBILITY OF SUCH DAMAGE. 45 | 46 | Copyright (C) 2013-2015 Apple Inc. All Rights Reserved. 47 | */ 48 | 49 | #import 50 | #import "AAPLAppDelegate.h" 51 | 52 | 53 | 54 | 55 | int main(int argc, char * argv[]) { 56 | @autoreleasepool { 57 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AAPLAppDelegate class])); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Demo/README.md: -------------------------------------------------------------------------------- 1 | # AVFoundationQueuePlayer-iOS: Using a Mixture of Local File Based Assets and HTTP Live Streaming Assets with AVFoundation 2 | 3 | Demonstrates how to create a movie queue playback app using only the AVQueuePlayer and AVPlayerLayer classes of AVFoundation (not AVKit). You’ll find out how to manage a queue compromised of local, HTTP-live-streamed, and progressive-download movies. You’ll also see how to implement play, pause, skip, time slider updating, and scrubbing. 4 | 5 | ## Requirements 6 | 7 | ### Build 8 | 9 | Xcode 7.0, iOS 9.0 SDK 10 | 11 | ### Runtime 12 | 13 | iOS 9.0 or later 14 | 15 | Copyright (C) 2015 Apple Inc. All rights reserved. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AVPlayerCacheSupport 2 | ============= 3 | 4 | An easy way to add cache for AVPlayer. 5 | 6 | ## Features 7 | 8 | - [x] Cache http streaming data For AVPlayer 9 | - [x] Support both audio & video http streaming (except [m3u](https://en.wikipedia.org/wiki/M3U) or other playlist format, e.g. [HTTP Live Streaming](https://en.wikipedia.org/wiki/HTTP_Live_Streaming), take a look at [this](http://stackoverflow.com/a/30239876) post) 10 | - [x] Byte range access & cache 11 | - [x] Simple API 12 | - [ ] Cache management 13 | 14 | ## Install 15 | 16 | Drag the folder named **AVPlayerCacheSupport** into your project. 17 | 18 | ## Usage 19 | 20 | Before 21 | 22 | ```objc 23 | AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url]; 24 | self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem]; 25 | ``` 26 | 27 | Now, with cache support 28 | 29 | ```objc 30 | #import "AVPlayerItem+MCCacheSupport.h" 31 | 32 | AVPlayerItem *item = [AVPlayerItem mc_playerItemWithRemoteURL:url]; 33 | self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem]; 34 | 35 | ``` 36 | 37 | ## Requries 38 | 39 | * iOS 7.0+ or MacOSX 10.9+ 40 | 41 | ## License 42 | 43 | MIT 44 | 45 | ## Demo 46 | 47 | Sitting on WWDC 2015 sample code [AVFoundationQueuePlayer-iOS](https://developer.apple.com/library/ios/samplecode/AVFoundationQueuePlayer-iOS/Introduction/Intro.html). --------------------------------------------------------------------------------