How to play movie with a URL using a custom NSURLProtocol?
UPDATE: I spoke to Apple about this and it's not possible to use MPMoviePlayerController with a NSURLProtocol subclass at the moment!
Hej,
I am not sure but it could be possible. I am currently working on something similar but haven't got it fully working. What I have found out is that MPMoviePlayerController interacts with my custom NSURLProtocol subclass BUT it seems to be important to take the HTTPHeaders of the NSURLRequest into account because they define a range of bytes the MPMoviePlayerController needs.
If you dump them in your NSURLProtocol subclass you will get something like this twice for the start:
2011-01-16 17:00:47.287 iPhoneApp[1177:5f03] Start loading from request: {
Range = "bytes=0-1";
}
So my GUESS is that as long as you can provide the correct range and return a mp4 file that can be played by the MPMoviePlayerController it should be possible!
EDIT: Here is a interesting link: Protecting resources in iPhone and iPad apps
Using NSURLProtocol to implement play while downloading for AVPlayer on iOS
OK, finally I found this:
How to play movie with a URL using a custom NSURLProtocol?
It seems that custom NSURLProtocol can not work with AVPlayer. So I give up.
iOS - Use AFNetworking with custom NSURLProtocol class
So, for other looking for information about this:
Along with the fact that AFURLSessionManager
doesn't use the standard NSURLProtocol
registrations, it also processes the array First-In-First-Out, not Last-In-First-Out like NSURLProtocol
.
Meaning, if you want to overwrite the behavior of the AFURLSessionManager
(say for testing purposes), you can't just add your NSURLProtocol
subclass to session.configuration.protocolClasses
, you must instead add it to the beginning of the array (or at least in front of the behavior you're overwriting/modifying).
Playing a stream of bytes on the iPhone MPMoviePlayerController
You could try implementing a custom URL protocol (see NSURLProtocol). Basically you create the protocol and register it, then any in-app requests to load a url having this protocol will be routed to your protocol instance. You'd likely have to mimic the responses that a HTTP server would send for a progressive-download of the file.
This wont work if MPMoviePlayerController uses lower-level CFNetwork calls vs. NSURLConnection to make its requests. This question implies that MPMoviePlayerController DOES make use of NSURLConnectino: How to play movie with a URL using a custom NSURLProtocol?
Also read the URL Loading System docs.
Cordova external app + local video
I had a similar project about a year ago. But i don't remember running into this issue because we were bundling our html/js/css assets with the app.
The issue is that your are trying to load file:/// protocol url from an html file served from http:/// protocol, which is not something that the native UIWebView is comfortable with.
You can bypass this by using a custom URL scheme like video://, but you need to write some native code which intercepts this requests and pipes the actual video back to URL loading system.
The end result:
Here is how i did it using Cordova 4.3.0 & a bit of ObjectiveC
- Create a new Objective C class named VideoURLProtocol which extends NSURLProtocol:
VideoURLProtocol.h:
#import <Foundation/Foundation.h>
@interface VideoURLProtocol : NSURLProtocol <NSURLConnectionDelegate>
@property (strong, nonatomic) NSURLConnection *connection;
@end
VideoURLProtocol.m:
#import "VideoURLProtocol.h"
@implementation VideoURLProtocol
@synthesize connection;
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
return [[[request URL] absoluteString] rangeOfString:@"video://"].location != NSNotFound;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.client URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
- (void)startLoading {
NSString *currentURL = [[self.request URL] absoluteString];
NSString *newURL = [currentURL stringByReplacingOccurrencesOfString:@"video://" withString:@"file:///"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:newURL]];
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
}
- (void)stopLoading {
[self.connection cancel];
self.connection = nil;
}
@end
Add the following line to the didFinishLaunchingWithOptions method of AppDelegate.m
.
.
// These lines should already be there
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
// This line
[NSURLProtocol registerClass:[VideoURLProtocol class]];
.
.And here's the javascript code that utilises this new URL scheme
document.addEventListener('deviceready', function(){
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem){
var caches = fileSystem.root.nativeURL.replace("Documents", "Library/Caches"),
videoPath = caches + "video.mp4";
new FileTransfer().download("http://clips.vorwaerts-gmbh.de/VfE_html5.mp4", videoPath, function(entry){
document.getElementsByTagName("video")[0].src = caches.replace("file:///", "video://") + "video.mp4"
}, function(error){
alert("unable to download file: " + error);
});
});
}, false);
Some additional points worth mentioning:
Notice in my javascript code i am downloading the file to "/Library/Caches" instead of the "/Documents" directory (the default location) this is because the "/Documents" directory gets backed up to iCloud & Apple rejects apps that try to backup more than ~100 MB. This is something I found the hard way after getting my App rejected.
You can see the space taken by your app under: Settings > iCloud > Storage > Manage Storage > {{Your iphone name}} > Show All Apps
You can autoplay videos by add the following line to your config.xml
<preference name="MediaPlaybackRequiresUserAction" value="false" />
You can also play videos inline by adding the following line to your config.xml in addition to this you also need to add webkit-playsinline="true" attribute to your video:
<preference name="AllowInlineMediaPlayback" value="true" />
<video controls="controls" autoplay="true" webkit-playsinline="true" preload="auto">
</video>
Here's a really interesting tutorial by Ray that explains NSURLProtocol in great detail: http://www.raywenderlich.com/59982/nsurlprotocol-tutorial
Can I create an NSURL that refers to in-memory NSData?
NSURL
supports the data:// URL-Scheme (RFC 2397).
This scheme allows you to build URLs in the form of
data://data:MIME-Type;base64,<data>
A working Cocoa example would be:
NSImage* img = [NSImage imageNamed:@"img"];
NSData* imgData = [img TIFFRepresentation];
NSString* dataFormatString = @"data:image/png;base64,%@";
NSString* dataString = [NSString stringWithFormat:dataFormatString, [imgData base64EncodedStringWithOptions:0]];
NSURL* dataURL = [NSURL URLWithString:dataString];
Passing around large binary blobs with data URLs might be a bit inefficient due to the nature of base64 encoding.
You could also implement a custom NSURLProtocol that specifically deals with your data.
Apple has some sample code that uses a custom protocol to pass around image objects: https://developer.apple.com/library/mac/samplecode/SpecialPictureProtocol/Introduction/Intro.html#//apple_ref/doc/uid/DTS10003816
Related Topics
Less Blur with 'Visual Effect View with Blur'
Cannot Show Automatic Strong Passwords for App Bundleid
Swift: Equivalent Objective-C Runtime Class
How to Add a Character at a Particular Index in String in Swift
Set Delegates to Nil Under Arc
_Unused Flag Behavior/Usage (Gcc with Objective-C)
Using Apple's Reachability Class in Swift
Firinstanceid/Warning Stop!! Will Reset Deviceid from Memory [Xcode:Console Log]
How to Mock Ajax Call with Nsurlprotocol
Could Not Attach to Pid:"####" Unable to Attach
Add a View on Top of the Keyboard Using Inputaccessoryview
Gem Native Extension Error While Installing Cocoapods
Can Swift Closures Be Set to a Default Value When Used as a Parameter in a Function
Detect and Use Optional External C Library at Runtime in Objective-C
Add a Scrollview to Existing View