Skip to content

Instantly share code, notes, and snippets.

@langyanduan
Forked from bclymer/AVPlayer Cache Layer
Created January 13, 2016 13:28
Show Gist options
  • Save langyanduan/5610b14edfab7b0b28f3 to your computer and use it in GitHub Desktop.
Save langyanduan/5610b14edfab7b0b28f3 to your computer and use it in GitHub Desktop.
//
// HudlHlsAvPlayerCache.m
// Hudl
//
// Created by Brian Clymer on 3/6/15.
// Copyright (c) 2015 Agile Sports Technologies, Inc. All rights reserved.
//
#import "HudlHlsAvPlayerCache.h"
@interface HudlHlsAvPlayerCache ()
@property (nonatomic, strong) NSMapTable *pendingRequests; // Dictionary of NSURLConnections to HudlAssetResponses
@property (nonatomic, strong) NSMutableSet *cachedFragments; // Set of NSStrings (file paths)
@property (nonatomic, copy) NSString *cachePath;
@end
@implementation HudlHlsAvPlayerCache
+ (instancetype)sharedInstance
{
static HudlHlsAvPlayerCache *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [HudlHlsAvPlayerCache new];
_sharedInstance.cachePath = ^NSString*() {
NSString *basePath = [appDelegate applicationCachesDirectory];
NSString *iden = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
basePath = [basePath stringByAppendingPathComponent:iden];
basePath = [basePath stringByAppendingPathComponent:@"hlsFragmentCache"];
if (![[NSFileManager defaultManager] fileExistsAtPath:basePath])
{
[[NSFileManager defaultManager] createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
return basePath;
}();
_sharedInstance.cachedFragments = [NSMutableSet setWithArray:[[NSFileManager defaultManager] contentsOfDirectoryAtPath:_sharedInstance.cachePath error:nil]];
_sharedInstance.pendingRequests = [NSMapTable new];
});
return _sharedInstance;
}
#pragma mark - NSURLConnection delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
HudlAssetResponse *assetResponse = [self.pendingRequests objectForKey:connection.originalRequest];
assetResponse.response = response;
assetResponse.loadingRequest.response = response;
[self fillInContentInformation:assetResponse.loadingRequest.contentInformationRequest response:assetResponse.response];
[self processPendingRequestsForResponse:assetResponse request:connection.originalRequest];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
HudlAssetResponse *assetResponse = [self.pendingRequests objectForKey:connection.originalRequest];
[assetResponse.data appendData:data];
[self processPendingRequestsForResponse:assetResponse request:connection.originalRequest];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
HudlAssetResponse *assetResponse = [self.pendingRequests objectForKey:connection.originalRequest];
assetResponse.finished = YES;
[self processPendingRequestsForResponse:assetResponse request:connection.originalRequest];
NSString *localName = [assetResponse.response.URL.absoluteString localStringFromRemoteString];
NSString *cachedFilePath = [self.cachePath stringByAppendingPathComponent:localName];
[self.cachedFragments addObject:localName];
[assetResponse.data writeToFile:cachedFilePath atomically:YES];
}
#pragma mark - AVURLAsset resource loading
- (void)processPendingRequestsForResponse:(HudlAssetResponse *)assetResponse request:(NSURLRequest *)request
{
BOOL didRespondCompletely = [self respondWithDataForRequest:assetResponse];
if (didRespondCompletely)
{
DDLogVerbose(@"Completed %@", request.URL.absoluteString);
[assetResponse.loadingRequest finishLoading];
[self.pendingRequests removeObjectForKey:request];
}
}
- (void)fillInContentInformation:(AVAssetResourceLoadingContentInformationRequest *)contentInformationRequest response:(NSURLResponse *)response
{
if (contentInformationRequest == nil || response == nil)
{
return;
}
contentInformationRequest.byteRangeAccessSupported = NO;
contentInformationRequest.contentType = [response MIMEType];
contentInformationRequest.contentLength = [response expectedContentLength];
}
- (BOOL)respondWithDataForRequest:(HudlAssetResponse *)assetResponse
{
AVAssetResourceLoadingDataRequest *dataRequest = assetResponse.loadingRequest.dataRequest;
long long startOffset = dataRequest.requestedOffset;
if (dataRequest.currentOffset != 0)
{
startOffset = dataRequest.currentOffset;
}
// Don't have any data at all for this request
if (assetResponse.data.length < startOffset)
{
return NO;
}
if (!assetResponse.finished)
{
return NO;
}
// This is the total data we have from startOffset to whatever has been downloaded so far
NSUInteger unreadBytes = assetResponse.data.length - (NSUInteger)startOffset;
// Respond with whatever is available if we can't satisfy the request fully yet
NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes);
[dataRequest respondWithData:[assetResponse.data subdataWithRange:NSMakeRange((NSUInteger)startOffset, numberOfBytesToRespondWith)]];
long long endOffset = startOffset + dataRequest.requestedLength;
BOOL didRespondFully = assetResponse.data.length >= endOffset;
DDLogVerbose(@"%@ - Requested %lli to %li, have %li", assetResponse.loadingRequest.request.URL.absoluteString, dataRequest.currentOffset, (long)dataRequest.requestedLength, (unsigned long)assetResponse.data.length);
return didRespondFully || assetResponse.finished;
}
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
{
DDLogVerbose(@"shouldWaitForLoadingOfRequestedResource %@", loadingRequest.request.URL.absoluteString);
// start downloading the fragment.
NSURL *interceptedURL = [loadingRequest.request URL];
NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:interceptedURL resolvingAgainstBaseURL:NO];
actualURLComponents.scheme = @"http";
NSString *localFile = [actualURLComponents.URL.absoluteString localStringFromRemoteString];
if ([self.cachedFragments containsObject:localFile] && ![localFile hasSuffix:@".ts"])
{
NSData *fileData = [[NSFileManager defaultManager] contentsAtPath:[self.cachePath stringByAppendingPathComponent:localFile]];
loadingRequest.contentInformationRequest.contentLength = fileData.length;
loadingRequest.contentInformationRequest.contentType = @"video/mpegts";
loadingRequest.contentInformationRequest.byteRangeAccessSupported = NO;
[loadingRequest.dataRequest respondWithData:[fileData subdataWithRange:NSMakeRange(loadingRequest.dataRequest.requestedOffset, MIN(loadingRequest.dataRequest.requestedLength, fileData.length))]];
[loadingRequest finishLoading];
DDLogVerbose(@"Responded with cached data for %@", actualURLComponents.URL.absoluteString);
return YES;
}
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:loadingRequest.request delegate:self startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
HudlAssetResponse *assetResponse = [HudlAssetResponse new];
assetResponse.data = [NSMutableData new];
assetResponse.loadingRequest = loadingRequest;
[self.pendingRequests setObject:assetResponse forKey:loadingRequest.request];
return YES;
}
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForRenewalOfRequestedResource:(AVAssetResourceRenewalRequest *)renewalRequest
{
DDLogVerbose(@"shouldWaitForRenewalOfRequestedResource %@", renewalRequest.request.URL.absoluteString);
return YES;
}
- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest
{
DDLogVerbose(@"Resource request cancelled for %@", loadingRequest.request.URL.absoluteString);
NSURLConnection *connectionForRequest = nil;
NSEnumerator *enumerator = self.pendingRequests.keyEnumerator;
BOOL found = NO;
while ((connectionForRequest = [enumerator nextObject]) && !found)
{
HudlAssetResponse *assetResponse = [self.pendingRequests objectForKey:connectionForRequest];
if (assetResponse.loadingRequest == loadingRequest)
{
[connectionForRequest cancel];
found = YES;
}
}
if (found)
{
[self.pendingRequests removeObjectForKey:connectionForRequest];
}
}
@end
@implementation HudlAssetResponse
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment