Files
featherVoice/Pods/SVGAPlayer/Source/SVGAParser.m
2025-08-08 10:49:36 +08:00

421 lines
18 KiB
Objective-C

//
// 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 <zlib.h>
#import <SSZipArchive/SSZipArchive.h>
#import <CommonCrypto/CommonDigest.h>
#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;
}
if ([[NSFileManager defaultManager] fileExistsAtPath:[self cacheDirectory:[self cacheKey:URLRequest.URL]]]) {
[self parseWithCacheKey:[self cacheKey:URLRequest.URL] completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) {
if (completionBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionBlock(videoItem);
}];
}
} failureBlock:^(NSError * _Nonnull error) {
[self clearCache:[self cacheKey:URLRequest.URL]];
if (failureBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
failureBlock(error);
}];
}
}];
return;
}
[[[NSURLSession sharedSession] dataTaskWithRequest:URLRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error == nil && data != nil) {
[self parseWithData:data cacheKey:[self cacheKey:URLRequest.URL] completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) {
if (completionBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionBlock(videoItem);
}];
}
} failureBlock:^(NSError * _Nonnull error) {
[self clearCache:[self cacheKey:URLRequest.URL]];
if (failureBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
failureBlock(error);
}];
}
}];
}
else {
if (failureBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
failureBlock(error);
}];
}
}
}] resume];
}
- (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];
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;
}
@end