Detecting and intercepting video playback in UIWebView - ios

I would like to intercept a click in an UIWebView and then use the URL of the video. How is this possible? I found a somewhat similar post which pointed met to the
webView:shouldStartLoadWithRequest:navigationType:
delegate. I cant seem to get the loaded url of the video with this delegate.
I am trying to get the code working in iOS8

There is a hacky way of finding the URL by listening for the AVPlayerItemBecameCurrentNotification notification. This notification is fired when a UIWebView shows the media player, and it sends an AVPlayerItem as the notification's object.
For example:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemBecameCurrent:)
name:#"AVPlayerItemBecameCurrentNotification"
object:nil];
-(void)playerItemBecameCurrent:(NSNotification*)notification {
AVPlayerItem *playerItem = [notification object];
if(playerItem == nil) return;
// Break down the AVPlayerItem to get to the path
AVURLAsset *asset = (AVURLAsset*)[playerItem asset];
NSURL *url = [asset URL];
NSString *path = [url absoluteString];
}
This works for any video (and audio). However it is worth nothing that this is fired after the media player has loaded, so you can't stop the player from launching at this point (if that was your intention).

If the above solution doesn't work for anyone then try listening to AVPlayerItemNewAccessLogEntry rather than AVPlayerItemBecameCurrentNotification. AVPlayerItemNewAccessLogEntry seems to be called afterwards and is where I was able to set the info.

Related

How can I get current audio URL playing inside my app from WKWebView?

I have a WKWebView and I am checking if audio is playing using
[[AVAudioSession sharedInstance] isOtherAudioPlaying]
I tried to get some information about it using :
[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo]
but it was nil.
Then I tried:
[[MPMusicPlayerController systemMusicPlayer] nowPlayingItem]
and it was also nil.
How can I get URL of current playing audio?
You need to subscribe on notification :
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemBecameCurrent:)
name:#"AVPlayerItemBecameCurrentNotification"
object:nil];
And then do this inside method playerItemBecameCurrent
-(void)playerItemBecameCurrent:(NSNotification*)notification {
AVPlayerItem *playerItem = [notification object];
if(playerItem == nil) return;
// Break down the AVPlayerItem to get to the path
AVURLAsset *asset = (AVURLAsset*)[playerItem asset];
NSURL *url = [asset URL];
NSString *path = [url absoluteString];}
As you can already detect when the audio is being played I think that the solution for you would be implementing decidePolicyForNavigationAction method from the WKNavigationDelegate.
You can combine the information provided by this delegate method that will be called whenever a link is clicked associated with the audio detection the you have already done.

AVAssetResourceLoaderDelegate methods not working on device

I have been working on a simple AVPlayer to play encrypted HLS media.
I am using the AVAssetResourceLoaderDelegate to handle the key retrieving process so the encrypted media can be played with a valid key.
The program works perfectly on simulator, but it doesn't work at all on device.
Here are the codes:
- (void) playUrlByAVPlayer:(NSString *) videoUrl
{
NSURL *streamURL = [NSURL URLWithString:videoUrl];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:streamURL options:nil];
[asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
[self.playerLayer setFrame:self.view.frame];
[self.view.layer addSublayer:self.playerLayer];
[self.player play];
}
After some debugging, I realized that the delegate method shouldWaitForLoadingOfRequestedResource was never called on device.
I have read other relevant questions:
AVAssetResourceLoaderDelegate not being called
AVAssetResourceLoaderDelegate - Only requests first two bytes?
and I tried enclosing all codes within a dispatch_async, dispatch_get_main_queue block but there's no luck on solving mine.
Currently my codes above are not enclosed by any dispatch queue blocks.
Any thoughts on the problem?
If you take a look on Apple example code where they show bipbop.m3u8 HLS playback you will see that they are using masks for real http requests: "http:/host/bipbop.m3u8" => "custom_scheme:/host/bipbop.m3u8"
Same trick should be made with playlist subresources.
Otherwise avplayer ignores AVAssetResourceLoaderDelegate and load data directly.
You need to implement some kind of mapping:
NSString* videoUrl = #"fake_scheme://host/video.m3u8";
NSURL *streamURL = [NSURL URLWithString:videoUrl];
As I mentioned in the other thread as well, AVAssetResourceLoaderDelegate works only when we use a "Non Standard/Non Reserved" url scheme. HTTP, HTTPS etc are considered reserved URL schemes and iOS will not make a delegate call if the URL has one of those schemes. What I ended up doing was using my_own_http for the http urls and my_own_https for the https urls. It works well after I made that change. As you know this makes your playlist unusable on other deices.
In your delegate shouldWaitForLoadingOfRequestedResource change the URL scheme back to http:
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:loadingRequest.request.URL resolvingAgainstBaseURL:NO];
urlComponents.scheme = #"http";
NSMutableURLRequest *mutableLoadingRequest = [loadingRequest.request mutableCopy];
[mutableLoadingRequest setURL:urlComponents.URL];
Please find a demo for this working along with a ViewController Implementation
Run this on a Real device only as Simulator doesn't allows https/http on it.
https://github.com/ankit0812/FairplayTestProj

How to get the url of currently playing video in UIWebview

Is there any way to get the link of currently playing video.I am loading m.youtube.com .For some videos it is not even entering the delegates.I tried using a NStimer as well.But for some videos it is not the clicked url
There is a hacky way of doing it by listening for the AVPlayerItemBecameCurrentNotification notification. This notification is fired when a UIWebView shows the media player, and it sends an AVPlayerItem as the notification's object.
For example:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemBecameCurrent:)
name:#"AVPlayerItemBecameCurrentNotification"
object:nil];
-(void)playerItemBecameCurrent:(NSNotification*)notification {
AVPlayerItem *playerItem = [notification object];
if(playerItem == nil) return;
// Break down the AVPlayerItem to get to the path
AVURLAsset *asset = (AVURLAsset*)[playerItem asset];
NSURL *url = [asset URL];
NSString *path = [url absoluteString];
}
This works for any video (and audio). However, I noticed you mentioned YouTube - it's worth pointing out Apple WILL reject your app if it has the ability to download YouTube videos AT ALL, because it's against YouTube's Terms of Service.
This one is for swift version:
//Add NSNotificationcenter for current playing song
override func viewDidAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector:#selector(playerItemBecameCurrent), name:NSNotification.Name(rawValue: "AVPlayerItemBecameCurrentNotification"), object:nil)
}
//Fetch path from notificationcenter
#objc func playerItemBecameCurrent(notification:NSNotification){
let playerItem:AVPlayerItem = notification.object as! AVPlayerItem
let asset:AVURLAsset=playerItem.asset as! AVURLAsset
let url = asset.url
let path = url.absoluteString
print(path)
}

Get meta data displayed in MPNowPlayingInfoCenter's nowPlayingInfo(lock screen and remote control)

Thanks for noticing this question. I want to do something about music recommendation, and what I am doing now is leveraging MPNowPlayingInfoCenter's nowPlayingInfo, like this:
NSDictionary *metaData = [[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo];
NSString *songTitle = metaData[MPMediaItemPropertyTitle];
NSString *albumnTitle = metaData[MPMediaItemPropertyAlbumTitle];
NSString *artist = metaData[MPMediaItemPropertyArtist];
But it always returns nil when "Music" app is playing music in background.
I looked up the related documents, it says
MPNowPlayingInfoCenter provides an interface for setting the current now
playing information for the application.
The default center holds now playing info about the current application
Seems there is no way to get other app's nowPlayingInfo through MPNowPlayingInfoCenter. So are there any other ways to get other app's music meta data displayed in remote control/lock screen? Thanks!
You can get what iPod currently is playing
MPMusicPlayerController* player = [MPMusicPlayerController iPodMusicPlayer];
//get now playing item
MPMediaItem*item = [player nowPlayingItem];
// get the title of song
NSString* titleStr = [item valueForProperty:MPMediaItemPropertyTitle];
NSLog(#"titlestr %#",titleStr);

AVAudioPlayer working fine in 5.0, issues in 4.0-4.3.5

I have an app which plays and controls music across different ViewControllers. To do this, I created two instances of AVAudioPLayer in the app delegate's DidFinishLaunchingMethod:
NSString *path = [[NSBundle mainBundle] pathForResource:#"minnie1" ofType:#"mp3"];
NSURL *path1 = [[NSURL alloc] initFileURLWithPath:path];
menuLoop =[[AVAudioPlayer alloc] initWithContentsOfURL:path1 error:NULL];
[menuLoop setDelegate:self];
menuLoop.numberOfLoops = -1;
[menuLoop play];
NSString *path2 = [[NSBundle mainBundle] pathForResource:#"minnie2" ofType:#"mp3"];
NSURL *path3 = [[NSURL alloc] initFileURLWithPath:path2];
gameLoop=[[AVAudioPlayer alloc] initWithContentsOfURL:path3 error:NULL];
gameLoop.numberOfLoops = -1;
[gameLoop setDelegate:self];
[gameLoop prepareToPlay];
After this I call it in various viewControllers, to stop or restart using code like:
- (IBAction)playGameLoop{
NSLog(#"Begin playGameLoop");
FPAppDelegate *app = (FPAppDelegate *)[[UIApplication sharedApplication] delegate];
if ([FPAVManager audioEnabled] == NO){
//DO NOTHING
}
else {
if (app.gameLoop.playing ==YES ) {
//DO NOTHING
}
else { [app.gameLoop play];
NSLog(#"End playGameLoop");
}
}
The audio files play fine the first time and they stop when asked to stop. Though, on iOS4 devices, they won't start replaying when called again.
Thanks!
From the docs:
Calling this method [stop] or allowing a sound to finish playing,
undoes the setup performed upon calling the play or prepareToPlay
methods.
The stop method does not reset the value of the currentTime property
to 0. In other words, if you call stop during playback and then call
play, playback resumes at the point where it left off.
Where you are looping the sounds, I would expect to be able to call stop and then call play. But I have fuzzy recollection of running into this myself. I found that calling pause, rather than stop, was a better solution. Especially since I could then call play again to resume play. Calling stop always seemed to require calling prepareToPlay again before calling play, at least in my experience.
It's possible that there were some changes in the API implemetation between iOS4.x and iOS5, too. You should check the API diffs on the Developer web site to be sure.

Resources