First iteration implementing video caching

This commit is contained in:
Laurin Quast 2018-03-01 23:38:38 +01:00
parent d5e012b749
commit c926d8ba6c
4 changed files with 193 additions and 6 deletions

View File

@ -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;

View File

@ -1,4 +1,5 @@
#import <React/RCTConvert.h>
#import "RCTVideoCache.h"
#import "RCTVideo.h"
#import <React/RCTBridgeModule.h>
#import <React/RCTEventDispatcher.h>
@ -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

31
ios/RCTVideoCache.h Normal file
View File

@ -0,0 +1,31 @@
#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

133
ios/RCTVideoCache.m Normal file
View File

@ -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