Loading ios/RCTVideo.h +3 −1 Changes for ios/RCTVideo.h: 3 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -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 <RCTVideoPlayerViewControllerDelegate> @interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, DVAssetLoaderDelegatesDelegate> @property (nonatomic, copy) RCTBubblingEventBlock onVideoLoadStart; @property (nonatomic, copy) RCTBubblingEventBlock onVideoLoad; Loading ios/RCTVideo.m +25 −4 Changes for ios/RCTVideo.m: 25 added lines, 4 removed lines. Original line number Diff line number Diff line #import <React/RCTConvert.h> #import "RCTVideoCache.h" #import "RCTVideo.h" #import <React/RCTBridgeModule.h> #import <React/RCTEventDispatcher.h> Loading Loading @@ -47,6 +48,7 @@ static NSString *const timedMetadata = @"timedMetadata"; NSString * _resizeMode; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; RCTVideoCache * _videoCache; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher Loading @@ -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:) Loading Loading @@ -268,7 +271,7 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)setCache:(BOOL *)cache { // @TODO: Implement } - (void)setSrc:(NSDictionary *)source Loading Loading @@ -336,12 +339,18 @@ 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}]; [_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) { AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil]; handler([AVPlayerItem playerItemWithAsset:asset]); Loading Loading @@ -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 Loading ios/RCTVideoCache.h 0 → 100644 +31 −0 Changes for ios/RCTVideoCache.h: 31 added lines, 0 removed lines. Original line number Diff line number Diff line #import <Foundation/Foundation.h> #import <AVFoundation/AVFoundation.h> #import <SPTPersistentCache/SPTPersistentCache.h> #import <SPTPersistentCache/SPTPersistentCacheOptions.h> #import <CommonCrypto/CommonDigest.h> @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 ios/RCTVideoCache.m 0 → 100644 +133 −0 Changes for ios/RCTVideoCache.m: 133 added lines, 0 removed lines. Original line number Diff line number Diff line #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 Loading
ios/RCTVideo.h +3 −1 Changes for ios/RCTVideo.h: 3 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -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 <RCTVideoPlayerViewControllerDelegate> @interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, DVAssetLoaderDelegatesDelegate> @property (nonatomic, copy) RCTBubblingEventBlock onVideoLoadStart; @property (nonatomic, copy) RCTBubblingEventBlock onVideoLoad; Loading
ios/RCTVideo.m +25 −4 Changes for ios/RCTVideo.m: 25 added lines, 4 removed lines. Original line number Diff line number Diff line #import <React/RCTConvert.h> #import "RCTVideoCache.h" #import "RCTVideo.h" #import <React/RCTBridgeModule.h> #import <React/RCTEventDispatcher.h> Loading Loading @@ -47,6 +48,7 @@ static NSString *const timedMetadata = @"timedMetadata"; NSString * _resizeMode; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; RCTVideoCache * _videoCache; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher Loading @@ -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:) Loading Loading @@ -268,7 +271,7 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)setCache:(BOOL *)cache { // @TODO: Implement } - (void)setSrc:(NSDictionary *)source Loading Loading @@ -336,12 +339,18 @@ 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}]; [_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) { AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil]; handler([AVPlayerItem playerItemWithAsset:asset]); Loading Loading @@ -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 Loading
ios/RCTVideoCache.h 0 → 100644 +31 −0 Changes for ios/RCTVideoCache.h: 31 added lines, 0 removed lines. Original line number Diff line number Diff line #import <Foundation/Foundation.h> #import <AVFoundation/AVFoundation.h> #import <SPTPersistentCache/SPTPersistentCache.h> #import <SPTPersistentCache/SPTPersistentCacheOptions.h> #import <CommonCrypto/CommonDigest.h> @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
ios/RCTVideoCache.m 0 → 100644 +133 −0 Changes for ios/RCTVideoCache.m: 133 added lines, 0 removed lines. Original line number Diff line number Diff line #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