Skip to content

Instantly share code, notes, and snippets.

@bclymer
Created March 9, 2015 21:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save bclymer/909ab3095eb3d0d36125 to your computer and use it in GitHub Desktop.
Save bclymer/909ab3095eb3d0d36125 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
@dadheech115
Copy link

This connection request
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:loadingRequest.request delegate:self startImmediately:NO];

This will fail immediately as loadingRequest.request contains the malformed URL as of now.

So how is this working for you, can you please explain?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment