Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to play video using a subclass of NSURLProtocol, using either MPMovieController or AVFoundation?

I'm currently trying to play a video to a URL that has the custom scheme defined in a custom NSURLProtocol subclass. Initially I was using MPMoviePlayerController in an attempt to accomplish this, but after running into problems and checking stack overflow, I found that the MPMoviePlayerController does not handle NSURLProtocol subclasses as expected.

How to play movie with a URL using a custom NSURLProtocol?

As a result I decided to look at the AVFoundation framework, however, it seems that this also doesn't seem to work. I just wanted to know if this was possible, or am I trying to walk through walls?

Using AVFoundation, the approach I'm using is shown below. It's probably worth mentioning that this works when using a standard URL to a video hosted on the internet, but doesn't work with the custom NSURLProtocol.

// this doesn't work
//AVPlayer *player = [[AVPlayer alloc] initWithURL:[NSURL urlWithString:@"custom URL scheme"]];
// this works
AVPlayer *player = [[AVPlayer alloc] initWithURL:[NSURL urlWithString:@"some url to video on remote server"]];

AVPlayerLayer *layer = [AVAVPlayerLayer playerLayerWithPlayer:player];
// configure the layer
[self.view.layer addSublayer:layer];

[player play];

Is there something different that would need to be done in order to play from the defined NSURLProtocol subclass?

like image 785
Taz Avatar asked Mar 07 '13 11:03

Taz


1 Answers

Recently, I managed to get NSURLProtocol to work with MPMoviePlayerController. This is mainly useful because MPMoviePlayerController only accepts an NSURL so it didn't let us pass cookies or headers (for authentication). The use of NSURLProtocol allowed that. I thought I'd share some code here for the community for once.

Basically, by using NSURLProtocol, we can intercept the communication between MPMoviePlayerController and the streamed request, to inject cookies along the way, or possibly save the stream offline, or replace it with cat videos ect... To do this, you need to create a new class My extending NSURLProtocol:

MyURLProtocol.h:

#import <Foundation/Foundation.h>

@interface MyURLProtocol : NSURLProtocol
+ (void) register;
+ (void) injectURL:(NSString*) urlString cookie:(NSString*)cookie;
@end

MyURLProtocol.m:

#import "MyURLProtocol.h"

@interface MyURLProtocol() <NSURLConnectionDelegate> {
    NSMutableURLRequest* myRequest;
    NSURLConnection * connection;
}
@end

static NSString* injectedURL = nil;
static NSString* myCookie = nil;

@implementation MyURLProtocol
// register the class to intercept all HTTP calls
+ (void) register
{
    [NSURLProtocol registerClass:[self class]];
}

// public static function to call when injecting a cookie
+ (void) injectURL:(NSString*) urlString cookie:(NSString*)cookie
{
    injectedURL = urlString;
    myCookie = cookie;
}

// decide whether or not the call should be intercepted
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    if([[[request allHTTPHeaderFields] objectForKey:@"BANANA"] isEqualToString:@"DELICIOUS"])
    {
        return NO;
    }
    return [[[request URL] absoluteString] isEqualToString:injectedURL];
}

// required (don't know what this means)
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

// intercept the request and handle it yourself
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client {

    if (self = [super initWithRequest:request cachedResponse:cachedResponse client:client]) {
        myRequest = request.mutableCopy;
        [myRequest setValue:@"DELICIOUS" forHTTPHeaderField:@"BANANA"]; // add your own signature to the request
    }
    return self;
}

// load the request
- (void)startLoading {
    //  inject your cookie
    [myRequest setValue:myCookie forHTTPHeaderField:@"Cookie"];
    connection = [[NSURLConnection alloc] initWithRequest:myRequest delegate:self];
}

// overload didReceive data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [[self client] URLProtocol:self didLoadData:data];
}

// overload didReceiveResponse
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse *)response {
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:[myRequest cachePolicy]];
}

// overload didFinishLoading
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [[self client] URLProtocolDidFinishLoading:self];
}

// overload didFail
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    [[self client] URLProtocol:self didFailWithError:error];
}

// handle load cancelation
- (void)stopLoading {
    [connection cancel];
}

@end

usage:

[MyURLProtocol register];
[MyURLProtocol injectURL:@"http://server/your-video-url.mp4" cookie:@"mycookie=123"];
MPMoviePlayerController* moviePlayer = [[MPMoviePlayerController alloc]initWithContentURL:@"http://server/your-video-url.mp4"];
[moviePlayer play];
like image 149
jacklehamster Avatar answered Oct 14 '22 01:10

jacklehamster