diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h index 0289b31..ca15d8d 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.h @@ -9,6 +9,8 @@ #import #import +#import +@class MVMCoreErrorObject; //block returned when getting image //parameters are UIImage object for the image, NSData for gif images, UIImage object for the image, A BOOL to indicate if it is a fall back image. @@ -152,4 +154,13 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO // clears the image cache - (void)clearImageCache; +#pragma mark - Video Functions + +/// Loads an AVPlayerAsset from the shared asset cache. Uses default track keys of "duration" and "playable". +- (void)playerAssetFromFileName:(nonnull NSString *)filename onComplete:(void(^_Nonnull)(AVAsset * _Nullable, NSString * _Nonnull, MVMCoreErrorObject * _Nullable))completionHandler; + +/// Loads an AVPlayerAsset from the shared asset cache. +- (void)playerAssetFromFileName:(nonnull NSString *)filename trackKeys:(nonnull NSArray *)trackKeys onComplete:(void(^_Nonnull)(AVAsset * _Nullable, NSString * _Nonnull, MVMCoreErrorObject * _Nullable))completionHandler; + + @end diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m index 27eabb9..c402552 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m @@ -15,6 +15,8 @@ #import "MVMCoreConstants.h" #import "MVMCoreActionUtility.h" #import "MVMCoreLoggingHandler.h" +#import "MVMCoreDispatchUtility.h" +#import "MVMCoreErrorConstants.h" @interface MVMCoreCache () @@ -34,6 +36,9 @@ // For thread safety @property (strong, nonatomic) dispatch_queue_t imageCacheQueue; +@property (nonnull, strong, nonatomic) dispatch_queue_t videoQueue; + +@property (nullable, strong, nonatomic) NSCache *playerItemCache; @end @@ -58,6 +63,9 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; self.imageCacheQueue = dispatch_queue_create("imgCache", DISPATCH_QUEUE_CONCURRENT); self.imageBundles = [NSMutableArray array]; + + self.videoQueue = dispatch_queue_create("video_queue", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0)); + self.playerItemCache = [[NSCache alloc] init]; } return self; } @@ -664,6 +672,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; dispatch_barrier_async(self.imageCacheQueue, ^{ [self.imgCache removeAllObjects]; }); + [self.playerItemCache removeAllObjects]; } - (void)removeImageFromCache:(nonnull NSString *)imageName { @@ -691,4 +700,50 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; } } +#pragma mark - Video Functions + +- (void)playerAssetFromFileName:(NSString *)filename onComplete:(void(^)(AVAsset * _Nullable, NSString * _Nonnull, MVMCoreErrorObject * _Nullable))completionHandler { + [self playerAssetFromFileName:filename trackKeys:@[@"playable",@"duration"] onComplete:completionHandler]; +} + +- (void)playerAssetFromFileName:(NSString *)filename trackKeys:(NSArray *)trackKeys onComplete:(void(^)(AVAsset * _Nullable, NSString * _Nonnull, MVMCoreErrorObject * _Nullable))completionHandler { + [MVMCoreDispatchUtility performBlock:^{ + //get the video url, can be downloaded or assets + NSURL *url; + MVMCoreErrorObject *error = nil; + __block AVAsset *item = [self.playerItemCache objectForKey:filename]; + if (!item) { + if ([filename hasPrefix:@"http"]) { + url = [NSURL URLWithString:filename]; + } else { + url = [[MVMCoreGetterUtility bundleForMVMCore] URLForResource:filename withExtension:@"mp4"]; + } + if (url) { + item = [AVURLAsset URLAssetWithURL:url options:@{AVURLAssetPreferPreciseDurationAndTimingKey:@YES}]; + [self.playerItemCache setObject:item forKey:filename]; // Set initial vanilla AVURLAsset. + } else { + error = [[MVMCoreErrorObject alloc] initWithTitle:nil messageToLog:[NSString stringWithFormat:@"Invalid URL: %@", filename] code:0 domain:ErrorDomainNative location:nil]; + } + } else { + MVMCoreLog(@"Loading video from cache: %@", filename); + } + + if (!item) { + if (!error) { + error = [[MVMCoreErrorObject alloc] initWithTitle:nil messageToLog:[NSString stringWithFormat:@"Failed to initialize asset for URL: %@", filename] code:0 domain:ErrorDomainNative location:nil]; + } + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + completionHandler(nil, filename, error); + }]; + } else { + // Load the duration key up front off of the main thread. Later duration will be used for seek and loop calculations. + [item loadValuesAsynchronouslyForKeys:trackKeys completionHandler:^{ + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + completionHandler(item, filename, error); + }]; + }]; + } + } onQueue:self.videoQueue]; +} + @end