Is it possible to send headers with an http request to an audio file when using AVPlayer? I need to be able to inspect the content of the header when received by the server
Consider using AVURLAsset
. For AVURLAsset
you can set up a resourceLoader delegate. Inside the delegate method you can issue a request manually a specify necessary headers.
The benefit of this approach is that you have a full control on data loading.
You have to use a custom url scheme in order to make this solution work (http and https will not trigger the delegate method!):
-(void) play {
NSURL * url = [URL URLWithString:@"mycustomscheme://tungsten.aaplimg.com/VOD/bipbop_adv_fmp4_example/master.m3u8"];
AVURLAsset * asset = [AVURLAsset URLAssetWithURL: options:nil];
[asset.resourceLoader setDelegate:self queue:dispatch_queue_create("TGLiveStreamController loader", nil)];
AVPlayerItem * playerItem = [AVPlayerItem playerItemWithAsset:asset];
// Use player item ...
...
}
#pragma mark - AVAssetResourceLoaderDelegate
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
dispatch_async(resourceLoader.delegateQueue, ^{
NSURL * url = [URL URLWithString:@"https://tungsten.aaplimg.com/VOD/bipbop_adv_fmp4_example/master.m3u8"];
NSMutableURLRequest *request = [loadingRequest.request mutableCopy];
request.URL = url;
// Add header
[request setValue:@"Foo" forHTTPHeaderField:@"Bar"];
NSURLResponse *response = nil;
NSError *firstError = nil;
// Issue request
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&firstError];
[loadingRequest.dataRequest respondWithData:data];
if (firstError) {
[loadingRequest finishLoadingWithError:firstError];
} else {
[loadingRequest finishLoading];
}
});
return YES;
}
Full code example is available at https://developer.apple.com/library/content/samplecode/sc1791/Introduction/Intro.html
You can use AVURLAssetHTTPHeaderFieldsKey
of AVURLAsset
's init option to modify request headers.
For example:
NSMutableDictionary * headers = [NSMutableDictionary dictionary];
[headers setObject:@"Your UA" forKey:@"User-Agent"];
AVURLAsset * asset = [AVURLAsset URLAssetWithURL:URL options:@{@"AVURLAssetHTTPHeaderFieldsKey" : headers}];
AVPlayerItem * item = [AVPlayerItem playerItemWithAsset:asset];
self.player = [[AVPlayer alloc] initWithPlayerItem:item];
Note: I found this key in sources of WebKit, but this is a Private option key, So your app may reject by AppStore if you use this.
You'll need to request the data yourself via a generic HTTP connection mechanism such as NSURLConnection
. If the NSHTTPURLResponse
's headers pass your test, then you should save it into the NSCachesDirectory
and pass off the URL to this resource to the AVPlayer
like so:
NSData *data = //your downloaded data.
NSString *filePath = //generate random path under NSCachesDirectory
[data writeToFile:filePath atomically:YES];
AVPlayer *player = [AVPlayer playerWithURL:[NSURL fileURLWithPath:filePath]];
//...
the full code could look like this
#pragma Mark Sound Stuff
- (void)playSound:(NSString *)filePath
{
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:filePath]];
[playerItem addObserver:self forKeyPath:@"status" options:0 context:0];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemDidFinishPlaying) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
self.audioPlayer = [[AVPlayer alloc] initWithPlayerItem:playerItem];
[self.audioPlayer play];
}
- (void)initSoundPrelisten
{
//NSLog(@"begin: %s", __FUNCTION__);
self.activityIndicator.hidden = NO;
[self.activityIndicator startAnimating];
// verification delegate : register
dataProtocol = [[StoreConnection alloc] init];
[dataProtocol setDelegate:self];
[dataProtocol requestDataFromServer:[NSString stringWithFormat:@"sound/%@/audio/sample", [self.sound objectForKey:@"globalId"]]];
}
- (void)dataSuccessful:(BOOL)success successData:(NSMutableData *)data;
{
NSLog(@"%s", __FUNCTION__);
if (success) {
//NSLog(@"sound data: %@", data);
NSString *cacheDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [cacheDirectory stringByAppendingPathComponent:@"sample.mp3"];
//NSLog(@"filePath: %@", filePath);
[data writeToFile:filePath atomically:YES];
[self playSound:filePath];
} else
{
UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"Store Error" message:[NSString stringWithFormat:@"An Error occured while trying to download sound. Please try again"] delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
alertView.tag = 1;
[alertView show];
}
}
Answer in Swift, AVURLAssetHTTPHeaderFieldsKey
option will work like a charm.
let headers: [String: String] = [
"custome_header": "custome value"
]
let asset = AVURLAsset(url: URL, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
let playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: item)
I spent weeks looking for a way to do this officially for HLS video streaming. For anyone looking for an approach that would work for both requests and responses for the playlist and chunk requests, the only way I was able to find that worked was by passing the playback request through a reverse proxy, with which allows you to intercept the request, add headers, send it to the real server, and then extract the headers from the response before returning it to the AVPlayer.
I made a simple example project (with lots of comments and documentation) here: https://github.com/kevinjameshunt/AVPlayer-HTTP-Headers-Example