// // SVGAParser.m // SVGAPlayer // // Created by 崔明辉 on 16/6/17. // Copyright © 2016年 UED Center. All rights reserved. // #import "SVGAParser.h" #import "SVGAVideoEntity.h" #import "Svga.pbobjc.h" #import #import #import #define ZIP_MAGIC_NUMBER "PK" @interface SVGAParser () @end @implementation SVGAParser static NSOperationQueue *parseQueue; static NSOperationQueue *unzipQueue; + (void)load { parseQueue = [NSOperationQueue new]; parseQueue.maxConcurrentOperationCount = 8; unzipQueue = [NSOperationQueue new]; unzipQueue.maxConcurrentOperationCount = 1; } - (void)parseWithURL:(nonnull NSURL *)URL completionBlock:(void ( ^ _Nonnull )(SVGAVideoEntity * _Nullable videoItem))completionBlock failureBlock:(void ( ^ _Nullable)(NSError * _Nullable error))failureBlock { [self parseWithURLRequest:[NSURLRequest requestWithURL:URL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:20.0] completionBlock:completionBlock failureBlock:failureBlock]; } - (void)parseWithURLRequest:(NSURLRequest *)URLRequest completionBlock:(void (^)(SVGAVideoEntity * _Nullable))completionBlock failureBlock:(void (^)(NSError * _Nullable))failureBlock { if (URLRequest.URL == nil) { if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock([NSError errorWithDomain:@"SVGAParser" code:411 userInfo:@{NSLocalizedDescriptionKey: @"URL cannot be nil."}]); }]; } return; } NSString *cacheKeyMD5 = [self cacheKey:URLRequest.URL]; if ([[NSFileManager defaultManager] fileExistsAtPath:[self cacheDirectory:cacheKeyMD5]]) { [self parseWithCacheKey:cacheKeyMD5 completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) { if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(videoItem); }]; } } failureBlock:^(NSError * _Nonnull error) { [self clearCache:cacheKeyMD5]; if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock(error); }]; } }]; return; } if ([[NSFileManager defaultManager] fileExistsAtPath:[self SVGADataCacheFilePath:cacheKeyMD5]]) { [self parseLocalSVGADataWithCacheKey:cacheKeyMD5 completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) { if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(videoItem); }]; } } failureBlock:^(NSError * _Nonnull error) { [self clearLocalSVGADataCache:cacheKeyMD5]; if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock(error); }]; } }]; return; } // 网络请求SVGAData __weak typeof(self) weakSelf = self; [[[NSURLSession sharedSession] dataTaskWithRequest:URLRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error == nil && data != nil) { __strong typeof(weakSelf) strongSelf = weakSelf; [strongSelf parseWithSVGAData:data cacheKey:cacheKeyMD5 completionBlock:completionBlock failureBlock:failureBlock]; [strongSelf saveToLocalWithSVGAData:data cacheKey:cacheKeyMD5]; } else { if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock(error); }]; } } }] resume]; } - (void)parseWithSVGAData:(NSData *)data cacheKey:(NSString *)cacheKey completionBlock:(void (^)(SVGAVideoEntity * _Nullable))completionBlock failureBlock:(void (^)(NSError * _Nullable))failureBlock { [self parseWithData:data cacheKey:cacheKey completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) { if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(videoItem); }]; } } failureBlock:^(NSError * _Nonnull error) { [self clearCache:cacheKey]; [self clearLocalSVGADataCache:cacheKey]; if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock(error); }]; } }]; } - (void)parseWithNamed:(NSString *)named inBundle:(NSBundle *)inBundle completionBlock:(void (^)(SVGAVideoEntity * _Nonnull))completionBlock failureBlock:(void (^)(NSError * _Nonnull))failureBlock { NSString *filePath = [(inBundle ?: [NSBundle mainBundle]) pathForResource:named ofType:@"svga"]; if (filePath == nil) { if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock([NSError errorWithDomain:@"SVGAParser" code:404 userInfo:@{NSLocalizedDescriptionKey: @"File not exist."}]); }]; } return; } [self parseWithData:[NSData dataWithContentsOfFile:filePath] cacheKey:[self cacheKey:[NSURL fileURLWithPath:filePath]] completionBlock:completionBlock failureBlock:failureBlock]; } - (void)parseWithCacheKey:(nonnull NSString *)cacheKey completionBlock:(void ( ^ _Nullable)(SVGAVideoEntity * _Nonnull videoItem))completionBlock failureBlock:(void ( ^ _Nullable)(NSError * _Nonnull error))failureBlock { [parseQueue addOperationWithBlock:^{ SVGAVideoEntity *cacheItem = [SVGAVideoEntity readCache:cacheKey]; if (cacheItem != nil) { if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(cacheItem); }]; } return; } NSString *cacheDir = [self cacheDirectory:cacheKey]; if ([[NSFileManager defaultManager] fileExistsAtPath:[cacheDir stringByAppendingString:@"/movie.binary"]]) { NSError *err; NSData *protoData = [NSData dataWithContentsOfFile:[cacheDir stringByAppendingString:@"/movie.binary"]]; SVGAProtoMovieEntity *protoObject = [SVGAProtoMovieEntity parseFromData:protoData error:&err]; if (!err && [protoObject isKindOfClass:[SVGAProtoMovieEntity class]]) { SVGAVideoEntity *videoItem = [[SVGAVideoEntity alloc] initWithProtoObject:protoObject cacheDir:cacheDir]; [videoItem resetImagesWithProtoObject:protoObject]; [videoItem resetSpritesWithProtoObject:protoObject]; [videoItem resetAudiosWithProtoObject:protoObject]; if (self.enabledMemoryCache) { [videoItem saveCache:cacheKey]; } else { [videoItem saveWeakCache:cacheKey]; } if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(videoItem); }]; } } else { if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock([NSError errorWithDomain:NSFilePathErrorKey code:-1 userInfo:nil]); }]; } } } else { NSError *err; NSData *JSONData = [NSData dataWithContentsOfFile:[cacheDir stringByAppendingString:@"/movie.spec"]]; if (JSONData != nil) { NSDictionary *JSONObject = [NSJSONSerialization JSONObjectWithData:JSONData options:kNilOptions error:&err]; if ([JSONObject isKindOfClass:[NSDictionary class]]) { SVGAVideoEntity *videoItem = [[SVGAVideoEntity alloc] initWithJSONObject:JSONObject cacheDir:cacheDir]; [videoItem resetImagesWithJSONObject:JSONObject]; [videoItem resetSpritesWithJSONObject:JSONObject]; if (self.enabledMemoryCache) { [videoItem saveCache:cacheKey]; } else { [videoItem saveWeakCache:cacheKey]; } if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(videoItem); }]; } } } else { if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock([NSError errorWithDomain:NSFilePathErrorKey code:-1 userInfo:nil]); }]; } } } }]; } - (void)clearCache:(nonnull NSString *)cacheKey { NSString *cacheDir = [self cacheDirectory:cacheKey]; [[NSFileManager defaultManager] removeItemAtPath:cacheDir error:NULL]; } + (BOOL)isZIPData:(NSData *)data { BOOL result = NO; if (!strncmp([data bytes], ZIP_MAGIC_NUMBER, strlen(ZIP_MAGIC_NUMBER))) { result = YES; } return result; } - (void)parseWithData:(nonnull NSData *)data cacheKey:(nonnull NSString *)cacheKey completionBlock:(void ( ^ _Nullable)(SVGAVideoEntity * _Nonnull videoItem))completionBlock failureBlock:(void ( ^ _Nullable)(NSError * _Nonnull error))failureBlock { SVGAVideoEntity *cacheItem = [SVGAVideoEntity readCache:cacheKey]; if (cacheItem != nil) { if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(cacheItem); }]; } return; } if (!data || data.length < 4) { return; } if (![SVGAParser isZIPData:data]) { // Maybe is SVGA 2.0.0 [parseQueue addOperationWithBlock:^{ NSData *inflateData = [self zlibInflate:data]; NSError *err; SVGAProtoMovieEntity *protoObject = [SVGAProtoMovieEntity parseFromData:inflateData error:&err]; if (!err && [protoObject isKindOfClass:[SVGAProtoMovieEntity class]]) { SVGAVideoEntity *videoItem = [[SVGAVideoEntity alloc] initWithProtoObject:protoObject cacheDir:@""]; [videoItem resetImagesWithProtoObject:protoObject]; [videoItem resetSpritesWithProtoObject:protoObject]; [videoItem resetAudiosWithProtoObject:protoObject]; if (self.enabledMemoryCache) { [videoItem saveCache:cacheKey]; } else { [videoItem saveWeakCache:cacheKey]; } if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(videoItem); }]; } } }]; return ; } [unzipQueue addOperationWithBlock:^{ if ([[NSFileManager defaultManager] fileExistsAtPath:[self cacheDirectory:cacheKey]]) { [self parseWithCacheKey:cacheKey completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) { if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(videoItem); }]; } } failureBlock:^(NSError * _Nonnull error) { [self clearCache:cacheKey]; [self clearLocalSVGADataCache:cacheKey]; if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock(error); }]; } }]; return; } NSString *tmpPath = [NSTemporaryDirectory() stringByAppendingFormat:@"%u.svga", arc4random()]; if (data != nil) { [data writeToFile:tmpPath atomically:YES]; NSString *cacheDir = [self cacheDirectory:cacheKey]; if ([cacheDir isKindOfClass:[NSString class]]) { [[NSFileManager defaultManager] createDirectoryAtPath:cacheDir withIntermediateDirectories:NO attributes:nil error:nil]; [SSZipArchive unzipFileAtPath:tmpPath toDestination:[self cacheDirectory:cacheKey] progressHandler:^(NSString * _Nonnull entry, unz_file_info zipInfo, long entryNumber, long total) { } completionHandler:^(NSString *path, BOOL succeeded, NSError *error) { if (error != nil) { if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock(error); }]; } } else { if ([[NSFileManager defaultManager] fileExistsAtPath:[cacheDir stringByAppendingString:@"/movie.binary"]]) { NSError *err; NSData *protoData = [NSData dataWithContentsOfFile:[cacheDir stringByAppendingString:@"/movie.binary"]]; SVGAProtoMovieEntity *protoObject = [SVGAProtoMovieEntity parseFromData:protoData error:&err]; if (!err) { SVGAVideoEntity *videoItem = [[SVGAVideoEntity alloc] initWithProtoObject:protoObject cacheDir:cacheDir]; [videoItem resetImagesWithProtoObject:protoObject]; [videoItem resetSpritesWithProtoObject:protoObject]; if (self.enabledMemoryCache) { [videoItem saveCache:cacheKey]; } else { [videoItem saveWeakCache:cacheKey]; } if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(videoItem); }]; } } else { if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock([NSError errorWithDomain:NSFilePathErrorKey code:-1 userInfo:nil]); }]; } } } else { NSError *err; NSData *JSONData = [NSData dataWithContentsOfFile:[cacheDir stringByAppendingString:@"/movie.spec"]]; if (JSONData != nil) { NSDictionary *JSONObject = [NSJSONSerialization JSONObjectWithData:JSONData options:kNilOptions error:&err]; if ([JSONObject isKindOfClass:[NSDictionary class]]) { SVGAVideoEntity *videoItem = [[SVGAVideoEntity alloc] initWithJSONObject:JSONObject cacheDir:cacheDir]; [videoItem resetImagesWithJSONObject:JSONObject]; [videoItem resetSpritesWithJSONObject:JSONObject]; if (self.enabledMemoryCache) { [videoItem saveCache:cacheKey]; } else { [videoItem saveWeakCache:cacheKey]; } if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(videoItem); }]; } } } else { if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock([NSError errorWithDomain:NSFilePathErrorKey code:-1 userInfo:nil]); }]; } } } } }]; } else { if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock([NSError errorWithDomain:NSFilePathErrorKey code:-1 userInfo:nil]); }]; } } } else { if (failureBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ failureBlock([NSError errorWithDomain:@"Data Error" code:-1 userInfo:nil]); }]; } } }]; } - (nonnull NSString *)cacheKey:(NSURL *)URL { return [self MD5String:URL.absoluteString]; } - (nullable NSString *)cacheDirectory:(NSString *)cacheKey { NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; return [cacheDir stringByAppendingFormat:@"/%@", cacheKey]; } - (NSString *)MD5String:(NSString *)str { const char *cstr = [str UTF8String]; unsigned char result[16]; CC_MD5(cstr, (CC_LONG)strlen(cstr), result); return [NSString stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], result[8], result[9], result[10], result[11], result[12], result[13], result[14], result[15] ]; } - (NSData *)zlibInflate:(NSData *)data { if ([data length] == 0) return data; unsigned full_length = (unsigned)[data length]; unsigned half_length = (unsigned)[data length] / 2; NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length]; BOOL done = NO; int status; z_stream strm; strm.next_in = (Bytef *)[data bytes]; strm.avail_in = (unsigned)[data length]; strm.total_out = 0; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; if (inflateInit (&strm) != Z_OK) return nil; while (!done) { // Make sure we have enough room and reset the lengths. if (strm.total_out >= [decompressed length]) [decompressed increaseLengthBy: half_length]; strm.next_out = [decompressed mutableBytes] + strm.total_out; strm.avail_out = (uInt)([decompressed length] - strm.total_out); // Inflate another chunk. status = inflate (&strm, Z_SYNC_FLUSH); if (status == Z_STREAM_END) done = YES; else if (status != Z_OK) break; } if (inflateEnd (&strm) != Z_OK) return nil; // Set real length. if (done) { [decompressed setLength: strm.total_out]; return [NSData dataWithData: decompressed]; } else return nil; } #pragma mark - Customer 方法 - (void)parseLocalSVGADataWithCacheKey:(nonnull NSString *)cacheKey completionBlock:(void ( ^ _Nullable)(SVGAVideoEntity * _Nonnull videoItem))completionBlock failureBlock:(void ( ^ _Nullable)(NSError * _Nonnull error))failureBlock { SVGAVideoEntity *cacheItem = [SVGAVideoEntity readCache:cacheKey]; if (cacheItem != nil) { if (completionBlock) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ completionBlock(cacheItem); }]; } return; } // 读取本地SVGAData缓存 NSData *data = [self readLocalSVGADataWithCacheKey:cacheKey]; if (data != nil) { [self parseWithSVGAData:data cacheKey:cacheKey completionBlock:completionBlock failureBlock:failureBlock]; return; } } - (nullable NSString *)SVGADataCacheFilePath:(NSString *)cacheKey { NSString *SVGADataCacheDir = [self SVGADataCacheDirectory]; if (![[NSFileManager defaultManager] fileExistsAtPath: SVGADataCacheDir]) { [[NSFileManager defaultManager] createDirectoryAtPath:SVGADataCacheDir withIntermediateDirectories:NO attributes:nil error:nil]; } return [SVGADataCacheDir stringByAppendingFormat:@"/%@", cacheKey]; } - (nullable NSString *)SVGADataCacheDirectory { return [[self diskCacheDirectory] stringByAppendingFormat:@"/SVGA_data_cache"]; } - (nullable NSString *)diskCacheDirectory { return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];; } /** 保存二进制SVGAData到本地 * */ - (void)saveToLocalWithSVGAData:(NSData *)data cacheKey:(NSString *)cacheKey { if (![[NSFileManager defaultManager] fileExistsAtPath:[self SVGADataCacheFilePath:cacheKey]]) { [data writeToFile:[self SVGADataCacheFilePath:cacheKey] atomically:true]; } } /** 读取本地二进制 SVGAData * */ - (NSData *)readLocalSVGADataWithCacheKey:(NSString *)cacheKey{ if ([[NSFileManager defaultManager] fileExistsAtPath:[self SVGADataCacheFilePath:cacheKey]]) { return [NSData dataWithContentsOfFile:[self SVGADataCacheFilePath:cacheKey]]; } return nil; } - (void)clearLocalSVGADataCache:(nonnull NSString *)cacheKey { NSString *cacheDir = [self SVGADataCacheFilePath:cacheKey]; [[NSFileManager defaultManager] removeItemAtPath:cacheDir error:NULL]; } - (void)clearLocalSVGADataCacheWithURLString:(nonnull NSString *)URLString { NSString *cacheDir = [self SVGADataCacheFilePath:[self MD5String:URLString]]; NSError *error = nil; [[NSFileManager defaultManager] removeItemAtPath:cacheDir error:&error]; } /** 下载SVGAData * */ - (void)downloadSVGADataWithURLString:(NSString *)URLString { [parseQueue addOperationWithBlock:^{ NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:URLString]]; NSString *cacheKeyMD5 = [self MD5String:URLString]; // 读取本地SVGAData缓存 NSData *data = [self readLocalSVGADataWithCacheKey:cacheKeyMD5]; if (data == nil) { [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error == nil && data != nil) { [self saveToLocalWithSVGAData:data cacheKey:cacheKeyMD5]; } }] resume]; } }]; } @end