Commit c926d8ba authored by Laurin Quast's avatar Laurin Quast
Browse files

First iteration implementing video caching

parent d5e012b7
Loading
Loading
Loading
Loading
+3 −1
Changes for ios/RCTVideo.h: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -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;
+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>
@@ -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,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]);
@@ -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

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