diff --git a/ios/RCTVideo.h b/ios/RCTVideo.h index 38d2ab4e..20a80d50 100644 --- a/ios/RCTVideo.h +++ b/ios/RCTVideo.h @@ -4,10 +4,12 @@ #import "UIView+FindUIViewController.h" #import "RCTVideoPlayerViewController.h" #import "RCTVideoPlayerViewControllerDelegate.h" +#import "RCTVideoCache.h" +#import "DVURLAsset.h" @class RCTEventDispatcher; -@interface RCTVideo : UIView +@interface RCTVideo : UIView @property (nonatomic, copy) RCTBubblingEventBlock onVideoLoadStart; @property (nonatomic, copy) RCTBubblingEventBlock onVideoLoad; diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index ba08bd8b..b8c965a1 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -1,4 +1,5 @@ #import +#import "RCTVideoCache.h" #import "RCTVideo.h" #import #import @@ -47,6 +48,7 @@ static NSString *const timedMetadata = @"timedMetadata"; NSString * _resizeMode; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; + RCTVideoCache * _videoCache; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -68,6 +70,7 @@ static NSString *const timedMetadata = @"timedMetadata"; _playInBackground = false; _playWhenInactive = false; _ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey + _videoCache = [RCTVideoCache sharedInstance]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) @@ -268,7 +271,7 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)setCache:(BOOL *)cache { - // @TODO: Implement + } - (void)setSrc:(NSDictionary *)source @@ -336,10 +339,16 @@ static NSString *const timedMetadata = @"timedMetadata"; [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]]; if (isNetwork) { - // @TODO: Check if item is cached an if so use the cached asset - NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; - AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:@{AVURLAssetHTTPCookiesKey : cookies}]; - handler([AVPlayerItem playerItemWithAsset:asset]); + [_videoCache getItemForUri:uri withCallback:^(AVAsset * _Nullable asset) { + if (asset) { + handler([AVPlayerItem playerItemWithAsset:asset]); + return; + } + NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; + DVURLAsset * dvAsset = [[DVURLAsset alloc] initWithURL:url options:@{AVURLAssetHTTPCookiesKey : cookies} networkTimeout: 10000]; + dvAsset.loaderDelegate = self; + handler([AVPlayerItem playerItemWithAsset:dvAsset]); + }]; return; } else if (isAsset) { @@ -751,6 +760,18 @@ static NSString *const timedMetadata = @"timedMetadata"; _playerLayer = nil; } +#pragma mark - DVAssetLoaderDelegate + +- (void)dvAssetLoaderDelegate:(DVAssetLoaderDelegate *)loaderDelegate + didLoadData:(NSData *)data + forURL:(NSURL *)url { + NSLog(@"File size is : %.2f MB",(float)data.length/1024.0f/1024.0f); + [_videoCache storeItem:data forUri:[url absoluteString] withCallback:^(BOOL success) { + + NSLog(@"data stored succesfully 🎉"); + }]; +} + #pragma mark - RCTVideoPlayerViewControllerDelegate - (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController diff --git a/ios/RCTVideoCache.h b/ios/RCTVideoCache.h new file mode 100644 index 00000000..cb904de9 --- /dev/null +++ b/ios/RCTVideoCache.h @@ -0,0 +1,31 @@ +#import +#import +#import +#import +#import + +@class SPTPersistentCache; +@class SPTPersistentCacheOptions; + +@interface RCTVideoCache : NSObject +{ + SPTPersistentCache *videoCache; + NSString * _Nullable cachePath; + NSString * temporaryCachePath; + NSString * _Nullable cacheIdentifier; +} + +@property(nonatomic, strong) SPTPersistentCache * _Nullable videoCache; +@property(nonatomic, strong) NSString * cachePath; +@property(nonatomic, strong) NSString * cacheIdentifier; +@property(nonatomic, strong) NSString * temporaryCachePath; + ++ (RCTVideoCache *)sharedInstance; +- (void)storeItem:(NSData *)data forUri:(NSString *)uri withCallback:(void(^)(BOOL))handler; +- (void)getItemForUri:(NSString *)url withCallback:(void(^)(AVAsset * _Nullable)) handler; +- (NSURL *)createUniqueTemporaryFileUrl:(NSString * _Nonnull)url withExtension:(NSString * _Nonnull) extension; +- (AVURLAsset *)getItemFromTemporaryStorage:(NSString *)key; +- (BOOL)saveDataToTemporaryStorage:(NSData *)data key:(NSString *)key; +- (void) createTemporaryPath; + +@end diff --git a/ios/RCTVideoCache.m b/ios/RCTVideoCache.m new file mode 100644 index 00000000..198742bc --- /dev/null +++ b/ios/RCTVideoCache.m @@ -0,0 +1,133 @@ +#import "RCTVideoCache.h" + +@implementation RCTVideoCache + +@synthesize videoCache; +@synthesize cachePath; +@synthesize cacheIdentifier; +@synthesize temporaryCachePath; + ++ (RCTVideoCache *) sharedInstance +{ + static RCTVideoCache *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + +- (id)init { + if (self = [super init]) { + self.cacheIdentifier = @"rct.video.cache"; + self.temporaryCachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:self.cacheIdentifier]; + self.cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:self.cacheIdentifier]; + SPTPersistentCacheOptions *options = [SPTPersistentCacheOptions new]; + options.cachePath = self.cachePath; + options.cacheIdentifier = self.cacheIdentifier; + options.defaultExpirationPeriod = 60 * 60 * 24 * 30; + options.garbageCollectionInterval = (NSUInteger)(1.5 * SPTPersistentCacheDefaultGCIntervalSec); + options.sizeConstraintBytes = 1024 * 1024 * 100; + options.useDirectorySeparation = NO; +#ifdef DEBUG + options.debugOutput = ^(NSString *string) { + NSLog(@"Video Cache: %@", string); + }; +#endif + [self createTemporaryPath]; + self.videoCache = [[SPTPersistentCache alloc] initWithOptions:options]; + } + return self; +} + +- (void) createTemporaryPath +{ + NSError * error = nil; + BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:self.temporaryCachePath + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (!success || error) { + NSLog(@"Error! %@", error); + } else { + NSLog(@"Success!"); + } +} + +- (void)storeItem:(NSData *)data forUri:(NSString *)uri withCallback:(void(^)(BOOL))handler; +{ + NSString * key = [[self generateHashForUrl:uri] stringByAppendingPathExtension: [uri pathExtension]]; + [self saveDataToTemporaryStorage:data key:key]; + [self.videoCache storeData:data forKey:key locked:NO withCallback:^(SPTPersistentCacheResponse * _Nonnull response) { + if (response.error) { + NSLog(@"An error occured while saving the video into the cache: %@", [response.error localizedDescription]); + handler(NO); + return; + } + handler(YES); + } onQueue:dispatch_get_main_queue()]; + return; +} + +- (AVURLAsset *)getItemFromTemporaryStorage:(NSString *)key { + NSString * temporaryFilePath =[self.temporaryCachePath stringByAppendingPathComponent:key]; + + BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:temporaryFilePath]; + if (!fileExists) { + NSLog(@"Temporary file does not exist"); + return nil; + } + NSLog(@"Temporary file does exist"); + NSURL * assetUrl = [[NSURL alloc] initFileURLWithPath:temporaryFilePath]; + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:assetUrl options:nil]; + return asset; +} + +- (BOOL)saveDataToTemporaryStorage:(NSData *)data key:(NSString *)key { + NSString * temporaryFilePath = [self.temporaryCachePath stringByAppendingPathComponent:key]; + NSLog(@"Temporary path %@", temporaryFilePath); + [data writeToFile:temporaryFilePath atomically:YES]; + return YES; +} + +- (void)getItemForUri:(NSString *)uri withCallback:(void(^)(AVAsset * _Nullable)) handler { + NSString * key = [[self generateHashForUrl:uri] stringByAppendingPathExtension: [uri pathExtension]]; + + NSLog(@"LOADING FROM TEMPORARY STORAGE %@", key); + AVURLAsset * temporaryAsset = [self getItemFromTemporaryStorage:key]; + if (temporaryAsset != nil) { + NSLog(@"FOUND IN TEMPORARY STORAGE"); + handler(temporaryAsset); + return; + } + NSLog(@"NOT FOUND IN TEMPORARY STORAGE"); + NSLog(@"LOAD FROM PERSISTENT STORAGE"); + + [self.videoCache loadDataForKey:key withCallback:^(SPTPersistentCacheResponse * _Nonnull response) { + if (response.record == nil || response.record.data == nil) { + NSLog(@"NOT FOUND IN PERSISTENT STORAGE"); + handler(nil); + return; + } + NSLog(@"FOUND IN PERSISTENT -> SAVE TO TEMPORARY"); + [self saveDataToTemporaryStorage:response.record.data key:key]; + handler([self getItemFromTemporaryStorage:key]); + } onQueue:dispatch_get_main_queue()]; +} + +- (NSString *) generateHashForUrl:(NSString *)string { + const char *cStr = [string UTF8String]; + unsigned char result[CC_MD5_DIGEST_LENGTH]; + 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] + ]; +} + +@end +