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

As you know,play a movie with MPMoviePlayerController object using
[[MPMoviePlayerController alloc] initWithContentURL: aURL];
now ,i want to achieve a custom NSURLProtocol in which i will decrypt a movie source that had be encrypt by AlgorithmDES.
Is that possibility? thanks for giving any ideas.need you help~

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

The solution is to proxy requests through a local HTTP server. I have accomplished this using CocoaHTTPServer.
Look at the HTTPAsyncFileResponse example.

There is one more solution as of iOS 7. You can use a AVAssetResourceLoaderDelegate for AVAssetResourceLoader. But this will only work with AVPlayer then.
There is a demo project by apple called AVARLDelegateDemo have a look at it and you should find what you need. (I think linking to it isn't a good idea, so just search for it in the Developer Library on developer.apple.com) Then use any custom URL scheme (without declaring a NSURLProtocol) and handle that URL scheme in the AVAssetResourceLoaderDelegate.
If there is a huge interest I could provide a proof of concept gist.

#property AVPlayerViewController *avPlayerVC;
#property NSData *yourDataSource
// initialise avPlayerVC
NSURL *dummyURL = [NSURL URLWithString:#"foobar://dummy.mov"];// a non-reachable URL will force the use of the resourceLoader
AVURLAsset *asset = [AVURLAsset assetWithURL:dummyURL];
[asset.resourceLoader setDelegate:self queue:dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)];
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
self.avPlayerVC.player = [AVPlayer playerWithPlayerItem:item];
self.avPlayerVC.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
// implement AVAssetResourceLoaderDelegate
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
loadingRequest.contentInformationRequest.contentType = (__bridge NSString *)kUTTypeQuickTimeMovie;
loadingRequest.contentInformationRequest.contentLength = self.yourDataSource.length;
loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
NSRange range = NSMakeRange((NSUInteger)loadingRequest.dataRequest.requestedOffset, loadingRequest.dataRequest.requestedLength);
[loadingRequest.dataRequest respondWithData:[self.yourDataSource subdataWithRange:range]];
[loadingRequest finishLoading];
return YES;
}
Notice the use of a dummy URL to force AVPlayer to use the AVAssetResourceLoaderDelegate methods instead of accessing the URL directly.

Related

AVPlayer URL custom loading for HTTPS

I'm trying to use AVPlayer with custom URL loading (a custom NSURLProtocol subclass). But it seems [NSURLProtocol registerClass] does not work with AVPlayer in real device (see this thread).
Now I'm trying to use AVAssetResourceLoaderDelegate to do the custom URL loading. However it is a bit confusing to me how the delegate will be triggered. The URL I deal with looks like this https://some_ip_address:port/resource/, but it seems like my delegate is not called for such URL. I tried to change the scheme to non-HTTP (e.g. "quic") and was able to trigger the delegate code but I really don't want to hack the scheme.
Here is the related code:
(delegate is implemented in a different file)
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
AVAssetResourceLoader *resourceLoader = asset.resourceLoader;
[resourceLoader setDelegate:delegate
queue:dispatch_queue_create("MyURLDelegate loader", nil)];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerViewController *controller = [[AVPlayerViewController alloc] init];
controller.player = player;
[player play];
[self presentViewController:controller animated:false completion:^{}];
With the above, I cannot see any methods are triggered in the delegate if url has the scheme of "https". What am I missing to allow the delegate to do custom URL loading for "https" URLs ?
Thanks
Just to follow up with an answer as I solved the problem now. Simply put, AVPlayer and AVAssetResourceLoader ignores the delegate if the URL has scheme of "http" or "https".
Yes, the workaround is to replace the URL scheme with some custom scheme, and replace it back inside the delegate callback when fetching data.
- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader
shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
URL string is loadingRequest.request.URL.absoluteString

MobileVLCKit Playing video streaming not displays

I have implemented MobileVLCKit in iOS by using MobileVLCKit framework. I have an issue,
When I declare the player #Interface the streaming and voice is working well.
#import <MobileVLCKit/MobileVLCKit.h>
#interface ViewController ()<VLCMediaPlayerDelegate>{
VLCMediaPlayer *vlcPlayer1
}
#end
But, declare the VLCMediaPlayer object at local function the video preview not displayed but, audio id playing.
- (void)viewDidLoad {
[super viewDidLoad];
VLCMediaPlayer *vlcPlayer1 = [[VLCMediaPlayer alloc] initWithOptions:nil];
vlcPlayer1.drawable = view;
media = [VLCMedia mediaWithURL:[NSURL URLWithString: UrlString]];
[vlcPlayer1 setMedia:media];
[vlcPlayer1 play];
}
How can I resolve the issue. Because, I need to create the view dynamically.
Try this:
[vlcplayer.media addOptions:#{ #"network-caching" : #300}];
If it doesn't work, replace 300 with a bigger value.
That may work.
So both of these questions/answers put me on the right path, but this is ultimately what worked for me.
NSURL* _uri = [NSURL URLWithString:uri];
NSArray* initOptions = [NSArray arrayWithObjects:#"--codec=avcodec", "--network-caching=10000", nil];
self.player = [[VLCMediaPlayer alloc] initWithOptions:initOptions];
self.player.media = [VLCMedia mediaWithURL:_uri];
It looks like the "addOptions" is valid, but my particular use case wasn't picking it up, and instead I had to actually initialize the VLCMediaPlayer with the options from the get go. Worked out nice because it actually fits much better with other JAVA/Android/CMD line VLC api's.

ios SystemSound will not respond to volume buttons

Ive used SystemSound in my app in order to play simple sound effects. In addition to this I play a musicvideo through the MPMoviePlayerController - now when I turn the volume up/down the music from the video responds as intended (lowering the volume up/down).
But the system sounds that are played does not respond to the volume. Im playing the system sounds when the user taps certain areas in the app. Heres a snippet of my code for that:
- (void)handleTap:(UITapGestureRecognizer *)recognizer {
SystemSoundID completeSound = nil;
//yellow folder in xcode doesnt need subdirectory param
//blue folder (true folder) will need to use subdirectory:#"dirname"
NSURL *sound_path = [[NSBundle mainBundle] URLForResource: target_sound_filename withExtension: #"wav"];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)sound_path, &completeSound);
AudioServicesPlaySystemSound(completeSound);
}
PS. I doublechecked that my "Settings->Sounds->Ringer and Alerts->Change With Buttons" is set to ON (as I read on some other SO answers that leaving this option OFF will cause systemsound to not respond to the volume buttons)
Further the reason for using systemsound is that it gave the most accurate and responsive results when playing multiple sounds (like in a game).
Id prefer to not use OpenAL if possible (even through 3rd party sound libraries like Finch or CocosDenshion)
Any ideas?
Use the AVAudioPlayer class for playing sounds that are controlled by the user's volume settings (non-system sounds).
You can retain instances of AVAudioPlayer for each sound file that you use regularly and simply call the play method. Use prepareToPlay to preload the buffers.
Cheers to Marcus for suggesting that i could retain instances of AVAudioPlayer for each sound file and use prepareToPlay to preload the sounds. It might be to help for others looking for the same solution so here is how I did it (feel free to comment if anyone have suggestions for improvements)
//top of viewcontroller.m
#property (nonatomic, strong) NSMutableDictionary *audioPlayers;
#synthesize audioPlayers = _audioPlayers;
//on viewDidLoad
self.audioPlayers = [NSMutableDictionary new];
//creating the instances and adding them to the nsmutabledictonary in order to retain them
//soundFile is just a NSString containing the name of the wav file
NSString *soundFile = [[NSBundle mainBundle] pathForResource:s ofType:#"wav"];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:soundFile] error:nil];
//audioPlayer.numberOfLoops = -1;
[audioPlayer prepareToPlay];
//add to dictonary with filename (omit extension) as key
[self.audioPlayers setObject:audioPlayer forKey:s];
//then i use the following to play the sound later on (i have it on a tap event)
//get pointer reference to the correct AVAudioPlayer instance for this sound, and play it
AVAudioPlayer *foo = [self.audioPlayers objectForKey:target_sound_filename];
[foo play];
//also im not sure how ARC will treat the strong property, im setting it to nil in dealloc atm.
-(void)dealloc {
self.audioPlayers = nil;
}

Unable to retrieve Shoutcast .pls stream

I'm trying to get a .pls stream from a shoutcast server to play in my ios app. So far i've been unsuccessful. I've red a lot of posts on stackoverflow but non of these were of any help.
Can anyone please explain to me, if its even possible, how to get .pls to stream?
all you need is to list the port of you radio, here is one working example:
in - (void)viewDidLoad
NSURL *vibes = [NSURL URLWithString:#"http://website.com:8002"];
vPlayer = [[AVPlayer alloc] initWithURL:vibes];
self.myViewVolume = [[MPVolumeView alloc] initWithFrame:CGRectMake(20, 330, 280, 50)];
[self.myViewVolume sizeToFit];
[self.view addSubview:self.myViewVolume];
you need to create an instance of AVPlayer in your .m file , here it is vPlayer
do not forget to add AVFoundation framework to you project, you can play and stop the stream with [player play] and [player stop]
One problem with AVPlayer is the lack of easy volume control, you can add one with mpViewVolume.
I am also working on radio app and by far AVPlayer is the best to play shoutcast streams.
#Walid Hussain
it worked for me using AVPlayer
link with AVFoundation.framework
//import needed
#import <AVFoundation/AVFoundation.h>
// declaration for the player
AVPlayer * radioPlayer;
// play
NSURL * url = [NSURL URLWithString:#"http://energy10.egihosting.com:9636"];
radioPlayer = [[AVPlayer playerWithURL:url] retain];
[radioPlayer play];

AVAUdioPlayer - changing current URL

In my audio player app, if I hit the fast forward button - the player must find the next song in the playlist and play it. What I do is I get the URL from the next song, and try to put it in the background (background is an instance of AVAudioPlayer*) url field, but that property is read only. So what I'm actually doing - I'm calling the initWithContentsOfURL method (again) to set the URL like this :
[self.background initWithContentsOfURL:
[[_playlist.collection objectAtIndex:currentIndex] songURL] error:nil];
Is this legit? I mean, the compiler tells me that the expression result is unused, but it actually works.
Is there another way of doing it? Thanks ;-)
For playing more than one URL, or effectively changing the URL, use the subclass AVQueuePlayer.
AVQueuePlayer is a subclass of AVPlayer you use to play a number of items in sequence.
Example:
NSString *fooVideoPath = [[NSBundle mainBundle] pathForResource:#"tommy" ofType:#"mov"];
NSString *barVideoPath = [[NSBundle mainBundle] pathForResource:#"pamela" ofType:#"mp4"];
AVPlayerItem *fooVideoItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:fooVideoPath]];
AVPlayerItem *barVideoItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:barVideoPath]];
self.queuePlayer = [AVQueuePlayer queuePlayerWithItems:[NSArray arrayWithObjects:fooVideoItem, barVideoItem,nil]];
[self.queuePlayer play];
// things happening...
[self.queuePlayer advanceToNextItem];
Have checked 4 Apple samples, they are using AVAudioPlayer to play one song only. However, your result looks very interesting and impressive! Please let us know, are you stopping the playback before reinitializing object with the same address, are you starting the new audio session ?
As for me, I'd not put the playback and app stability in a risk doing something not mentioned in the documentation but , but to be on the bright side would use the AVAudioPlayer class as it seems the most right, which gives us:
use the error variable to track the possible errors
stop the playing AVAudioPlayer instance, initialize a new instance of AVAudioPlayer and set it to the property letting an old-one to be deallocated automatically.
And you probably know yourself, that
self.background = [self.background initWithContentsOfURL::
will remove the warning for you.

Resources