-
-
Save GregIngelmo/f0483abfb63b4c5934ac0daf4c21556f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ViewController.m | |
// AVPlayerCaching | |
// | |
// Created by Anurag Mishra on 5/19/14. | |
// Sample code to demonstrate how to cache a remote audio file while streaming it with AVPlayer | |
// | |
#import "ViewController.h" | |
#import <AVFoundation/AVFoundation.h> | |
#import <MobileCoreServices/MobileCoreServices.h> | |
@interface ViewController () <NSURLConnectionDataDelegate, AVAssetResourceLoaderDelegate> | |
@property (nonatomic, strong) NSMutableData *songData; | |
@property (nonatomic, strong) AVPlayer *player; | |
@property (nonatomic, strong) NSURLConnection *connection; | |
@property (nonatomic, strong) NSHTTPURLResponse *response; | |
@property (nonatomic, strong) NSMutableArray *pendingRequests; | |
@end | |
@implementation ViewController | |
- (void)viewDidLoad | |
{ | |
[super viewDidLoad]; | |
// Do any additional setup after loading the view, typically from a nib. | |
} | |
- (void)didReceiveMemoryWarning | |
{ | |
[super didReceiveMemoryWarning]; | |
// Dispose of any resources that can be recreated. | |
} | |
- (NSURL *)songURL | |
{ | |
return [NSURL URLWithString:@"http://sampleswap.org/mp3/artist/earthling/Chuck-Silva_Ninety-Nine-Percent-320.mp3"]; | |
} | |
- (NSURL *)songURLWithCustomScheme:(NSString *)scheme | |
{ | |
NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[self songURL] resolvingAgainstBaseURL:NO]; | |
components.scheme = scheme; | |
return [components URL]; | |
} | |
- (IBAction)playSong:(id)sender | |
{ | |
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[self songURLWithCustomScheme:@"streaming"] options:nil]; | |
[asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()]; | |
self.pendingRequests = [NSMutableArray array]; | |
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset]; | |
self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem]; | |
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:NULL]; | |
} | |
#pragma mark - NSURLConnection delegate | |
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response | |
{ | |
self.songData = [NSMutableData data]; | |
self.response = (NSHTTPURLResponse *)response; | |
[self processPendingRequests]; | |
} | |
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data | |
{ | |
[self.songData appendData:data]; | |
[self processPendingRequests]; | |
} | |
- (void)connectionDidFinishLoading:(NSURLConnection *)connection | |
{ | |
[self processPendingRequests]; | |
NSString *cachedFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"cached.mp3"]; | |
[self.songData writeToFile:cachedFilePath atomically:YES]; | |
} | |
#pragma mark - AVURLAsset resource loading | |
- (void)processPendingRequests | |
{ | |
NSMutableArray *requestsCompleted = [NSMutableArray array]; | |
for (AVAssetResourceLoadingRequest *loadingRequest in self.pendingRequests) | |
{ | |
[self fillInContentInformation:loadingRequest.contentInformationRequest]; | |
BOOL didRespondCompletely = [self respondWithDataForRequest:loadingRequest.dataRequest]; | |
if (didRespondCompletely) | |
{ | |
[requestsCompleted addObject:loadingRequest]; | |
[loadingRequest finishLoading]; | |
} | |
} | |
[self.pendingRequests removeObjectsInArray:requestsCompleted]; | |
} | |
- (void)fillInContentInformation:(AVAssetResourceLoadingContentInformationRequest *)contentInformationRequest | |
{ | |
if (contentInformationRequest == nil || self.response == nil) | |
{ | |
return; | |
} | |
NSString *mimeType = [self.response MIMEType]; | |
CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL); | |
contentInformationRequest.byteRangeAccessSupported = YES; | |
contentInformationRequest.contentType = CFBridgingRelease(contentType); | |
contentInformationRequest.contentLength = [self.response expectedContentLength]; | |
} | |
- (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest | |
{ | |
long long startOffset = dataRequest.requestedOffset; | |
if (dataRequest.currentOffset != 0) | |
{ | |
startOffset = dataRequest.currentOffset; | |
} | |
// Don't have any data at all for this request | |
if (self.songData.length < startOffset) | |
{ | |
return NO; | |
} | |
// This is the total data we have from startOffset to whatever has been downloaded so far | |
NSUInteger unreadBytes = self.songData.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:[self.songData subdataWithRange:NSMakeRange((NSUInteger)startOffset, numberOfBytesToRespondWith)]]; | |
long long endOffset = startOffset + dataRequest.requestedLength; | |
BOOL didRespondFully = self.songData.length >= endOffset; | |
return didRespondFully; | |
} | |
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest | |
{ | |
if (self.connection == nil) | |
{ | |
NSURL *interceptedURL = [loadingRequest.request URL]; | |
NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:interceptedURL resolvingAgainstBaseURL:NO]; | |
actualURLComponents.scheme = @"http"; | |
NSURLRequest *request = [NSURLRequest requestWithURL:[actualURLComponents URL]]; | |
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; | |
[self.connection setDelegateQueue:[NSOperationQueue mainQueue]]; | |
[self.connection start]; | |
} | |
[self.pendingRequests addObject:loadingRequest]; | |
return YES; | |
} | |
- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest | |
{ | |
[self.pendingRequests removeObject:loadingRequest]; | |
} | |
#pragma KVO | |
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context | |
{ | |
if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) | |
{ | |
[self.player play]; | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment