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

1

5

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?

Cozenage answered 7/3, 2013 at 11:18 Comment(2)
Hi Taz, did you manage to make it work?Valladolid
Hey Geraud, no, I never managed to get this working. It appears that AVFoundation also doesn't support NSURLProtocol subclasses.Cozenage
F
5

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];
Fluoridate answered 24/4, 2014 at 6:4 Comment(5)
Were you able to play a video? is there any demo?Heathenism
I also do that. It works on simulator but not on device. I tested with iOS 8.1, xcode 6.1. Did you get it works on deviceWebworm
It works for me for streaming mp3/m4a, but mp4 video captures with iOS doesn't loadMinimalist
I did this a while ago, yeah it worked on the device without problems. I don't have a demo for it though, and I haven't touch this project for a while.Fluoridate
@Fluoridate Is it possible that it worked iOS 7 (while ago) but not anymore? From an open radar i've seen it looks like it could be this...Paring

© 2022 - 2024 — McMap. All rights reserved.