Good day,
Through the use of an UIWebview I have now a working method to show a youtube video within my app (using the tag, finding the play button within the webview and firing the touch event on that).
Works like a charm. The video pops up and plays. However I would like to recieve an event when the video ends or the user clicks the done button.
On the internet I have found that there is an event: MPAVControllerItemPlaybackDidEndNotification where you can listen to. However this one does not get called.
After some further research I found that for Youtube Videos embedded through UIWebView another notification was called ( UIMoviePlayerControllerDidExitFullscreenNotification ). Unfortunately that one does not work either anymore. ( found it here )
Does anyone have any idea how I can do some action after the video is done playing or has been dismissed?
Thanks
Use the UIMoviePlayerControllerWillExitFullscreenNotification for getting notified once the user tapped on the DONE button. The UIMoviePlayerControllerDidExitFullscreenNotification seems indeed to be omitted on iOS6.
Mind that ...Did... vs. ...Will... difference!
For more on that subject, once again check my updated answer within that posting you referenced in your question.
Let's look at this scenario :
In your view, you have a button. When it's clicked, you want to play the video directly.
In order, to do so, you open the webview as a modal view of your view :
[self presentModalViewController:videoWebView animated:NO];
For your webview, you should use Youtube API to integrate and autoplay the video. See the proposed working example here : https://stackoverflow.com/a/15538968
You'll see that the video is launched in a modal view of your webview view. One way to detect when the video is dismissed (when the "done" button has been clicked) is to use the viewDidAppear on your webview view class. In this method you will then dismiss the webview view too but...when this view is launched at first, you don't want to dismiss it. You can add a boolean property to avoid that.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (_videoLaunched) {
[self dismissModalViewControllerAnimated:YES];
}
}
In the viewDidLoad method, set this property to NO and in the webViewDidFinishLoad method (delegate method of webview) set it to YES.
I think it answers one part of your question. Concerning the detection of the end of the video you have to modify you YT_Player.html file to listen to state changes.
ytPlayer = new YT.Player('media_area', {height: '100%', width: '100%', videoId: 'SbPFDcspRBA',
events: {'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange}
function onPlayerStateChange(e) {
var result = JSON.parse(event.data);
if (result.info == 0) { // Video end
window.location = "videomessage://end";
}
}
});
You will then catch the event in your webview view and dismiss it like this :
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = request.URL;
if ([[url scheme] isEqualToString:#"videomessage"]) {
[self dismissModalViewControllerAnimated:YES];
return YES;
}
return YES;
}
What you need here is something like this:
- (void)playerWillExitFullscreen:(NSNotification *)notification
{
//do something...
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerWillExitFullscreen:)
name:#"MPMoviePlayerWillExitFullscreenNotification" object:nil];
Related
iOS 7 bring the iAdAdditions category to UIViewController.
With it managing a banner is a matter of one line of code:
self.canDisplayBannerAds = YES;
But I wonder how to detect user touching the iAd banner. My need is to pause game behaviors (music, animations, timers...) while iAd is displayed full screen.
I have tried the following code:
- (void) viewWillDisappear:(BOOL)animated {
if ([self isPresentingFullScreenAd]) {
// view will disappear because of user action on iAd banner
}
else {
// view will disappear for any other reasons
}
}
- (void) viewWillAppear:(BOOL)animated {
if ([self isPresentingFullScreenAd]) {
// view will appear because full screen iAd β caused by previous user action on iAd banner β is dismissed
}
else {
// view will appear for other reasons
}
}
I have done some testings showing everything is OK. But I wonder if it's the correct way to implement it!
UPDATE
This is the solution I use in the production version of the application and everything is fine: no problems has showned.
You can use these specific delegates in your view controller, here's code ripped straight from my game where I toggle the audio/music off when a banner is clicked.
- (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner
willLeaveApplication:(BOOL)willLeave
{
[[CPAudioManager sharedInstance] toggleOnOffDependingOnSettings];
return YES;
}
-(void)bannerViewActionDidFinish:(ADBannerView *)banner
{
[[CPAudioManager sharedInstance] toggleOnOffDependingOnSettings];
}
For full screen interstitials the code is roughly the same, but you don't get such nice delegates.
You know when you are showing a full screen ad when you call requestInterstitialAdPresentation and it returns YES, e.g.
if( [self requestInterstitialAdPresentation]==YES ) {...}
You should pause your views, toggle your music off, and kill your banner view (probably).
Then, you know when the user has closed the interstitial in ViewDidAppear
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if( self.isPresentingFullScreenAd )
{
// we are about to stop presenting full screen ads
// unpause view
// enable banner ads
// turn music back on if it was playing before
}
else
{
// we are presenting the normal view or the full screen ad
}
}
I have an app with a tab bar controller at the root. The "home view" is a 3D rendered screen in OpenGL. There are certain 3D objects that you can click on that need to display a video. The video should be fullscreen and fade in and out.
To do this, I made the HomeViewController create a MPMoviePlayerViewController, assigned it a URL and then presented it from the tab bar controller. (I would have presented it from the HomeViewController but for some reason it didn't change its orientation properly--I'm sure it's related to all of the custom 3D stuff, and I didn't program it, so I just did a workaround by displaying it from a senior view.)
(Note that I am presenting the MPMoviePlayerViewController modally (not using the built-in presentModalMovieViewController or whatever) because Apple forces the transition to be the wacky screen-shift, and I wanted the dissolve.)
Now, that works fine. The modal window dissolves in, the video plays. You can play and pause it, fast forward, hit "Done" and the modal window goes away. Voila.
Now, here comes the totally weird bug: if you don't tap the video player and let the controls fade out (as they do after a second or two) the user cannot bring them back by tapping. It seems like the video controller stops responding to user input after that fade. Again, it works fine before they fade away. But after that point, I have to wait for the video to play all the way (at which point the modal window does, in fact, go away).
For reference, here is the relevant code to the modal video player:
-(void) startVideoWithURL:(NSURL *)videoURL {
if (!self.outsideMoviePlayerViewController) {
self.outsideMoviePlayerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:nil];
}
if (videoURL) {
[self stopAnimation];
self.outsideMoviePlayerViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
self.outsideMoviePlayerViewController.moviePlayer.contentURL = videoURL;
[[[AppController instance] getCustomTabBarViewController] presentModalViewController:self.outsideMoviePlayerViewController animated:YES];
// Move observation of the dismiss from the MoviePlayerViewController to self.
[[NSNotificationCenter defaultCenter] removeObserver:self.outsideMoviePlayerViewController
name:MPMoviePlayerPlaybackDidFinishNotification
object:self.outsideMoviePlayerViewController.moviePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:self.outsideMoviePlayerViewController.moviePlayer];
}
}
-(void) movieFinishedCallback:(NSNotification *)aNotification {
// Summary: Restart 3D animation, unregister from notifications, kill the modal video.
[self startAnimation];
MPMoviePlayerController *moviePlayer = [aNotification object];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:moviePlayer];
[[[AppController instance] getCustomTabBarViewController] dismissModalViewControllerAnimated:YES];
}
The only other reference I could find to an issue like this was some archived post on the Apple Support Communities, here:
https://discussions.apple.com/thread/2141156?start=0&tstart=0
In this thread, the issue poster figures it out himself, and states that the issue was resolved. Here's his explanation:
The problem occurs when CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE). After i changed to CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002, TRUE), the playback control can appears/disappear by tapping the screen.
Unfortunately, I'm not programming a game and nobody on my dev team calls CFRunLoopRunInMode anywhere in the code. The closest thing I found to this was in the animation code (in the same ViewController):
- (void)startAnimation
{
if (!animating)
{
NSLog(#"startAnimation called");
CADisplayLink *aDisplayLink = [[UIScreen mainScreen] displayLinkWithTarget:self selector:#selector(drawFrame)];
[aDisplayLink setFrameInterval:animationFrameInterval];
[aDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.displayLink = aDisplayLink;
animating = TRUE;
}
}
If anyone has any insights on what could be causing this, I would appreciate it. I figured that, at the very least, even if I figure it out myself tonight, this problem could go up on Stack Overflow and be archived for the sake of posterity.
Cheers.
I figured out what was causing this problem.
I noticed that the first video did play, while successive ones did not. I moved this code:
if (!self.outsideMoviePlayerViewController) {
self.outsideMoviePlayerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:nil];
}
This way the creation of the outsideMoviePlayerViewController was inside the next block, like so:
if (videoURL) {
self.outsideMoviePlayerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:nil];
[self stopAnimation];
self.outsideMoviePlayerViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
self.outsideMoviePlayerViewController.moviePlayer.contentURL = videoURL;
Now a new controller is created instead of recycling the controller each time I played a video. The bug went away. I'm not 100% sure why this happened, because there are a variety of things that occur when you display a modal view controller.
The bottom line is probably that I should have done this in the first place as part of the lazy loading paradigm instead of trying to keep the controller in memory.
I am using a UIWebView and don't want the navigation bar to appear unless the user taps anywhere on the screen that isn't a link.
So I have this code to display the navigation bar after a delay:
- (void)handleTapGesture:(UITapGestureRecognizer *)sender
{
....
[self performSelector:#selector(showNavigationBar) withObject:self afterDelay:0.2];
}
I'm not calling showNavigationBar immediately when the tap handler is invoked because the user might have tapped on a link in which case the tap hander is called before UIWebView shouldStartLoadWithRequest, so if I hid the navigation bar in shouldStartLoadWithRequest it would flash momentarily onto the screen.
So instead I set it to display after a delay which gives time for the following code to execute within shouldStartLoadWithRequest (and if the user didn't tap on a link shouldStartLoadWithRequest isn't called and the navigation bar is displayed, as it should be in that case).
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showNavigationBar) object:nil];
...
However this isn't working, I've increased the delay time to several seconds and can confirm cancelPreviousPerformRequestWithTarget is getting called before the navigation bar has been displayed, but when the specified time elapses the bar displays. cancelPreviousPerformRequestWithTarget is having no effect.
Does anybody know why its not working?
Your perform doesn't match your cancel. In the perform you're passing self as the object:
[self performSelector:#selector(showNavigationBar) withObject:self afterDelay:0.2];
In the cancel you're passing nil as the object:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showNavigationBar) object:nil];
They don't match, so the delayed perform should not be canceled.
In the documentation of that + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument method there is this sentence :
This method removes perform requests only in the current run loop, not all run loops.
If I'm interpreting it correctly it would mean that you need to cancel your action in the same run loop that you launched it. Which is clearly not what you want to do.
A way to go around this would be to have a flag that showNavigationBar would have to check to see if it should proceed or abort.
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showNavigationBar) object:self];
That worked for me ;)
Not sure why but works like a charm for me.
dispatch_async(dispatch_get_main_queue(), ^{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
});
In recent iOS versions apps have some kind of access to the media control buttons on the lock screen, like the Play/Pause button:
It looks like the buttons are supposed to work with the MPMusicPlayerController class, is that right? Is there a way to get the βrawβ events from the buttons? Because the music player only seems to offer an API to supply a bunch of MPMediaItems. What if my app is for example a radio that needs to handle the buttons differently?
After a bit more searching I have found this related question that makes things clear. The music player controller class is not really the right track, the trick is to subscribe for remote events in your controller:
- (void) viewDidAppear: (BOOL) animated
{
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (BOOL) canBecomeFirstResponder
{
return YES;
}
- (void) remoteControlReceivedWithEvent: (UIEvent*) event
{
// see [event subtype] for details
}
i open a url within a Webview and whilst loading i show a spinner in a alert view.
If the url tapped opens an internal app such as iTunes how can i detect the web view has left my app so that when the user returns the dialog has been dismissed.
i have used didFailLoadWithError and this didn't work?
Any ideas?
Thanks dan
FIXED- i forgot to set the delegate Doh!
UIWebViewDelegate method webView:shouldStartLoadWithRequest:navigationType: will ask your webview's delegate if it is allowed to open every single url before trying to open it. You could check for url type in that method and return NO if it's not http or https, then present an alert for the user and let them chose if they want to open it or stay within your app, or just log that app was left to open another one and return YES;
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *urlString = [NSString stringWithFormat:#"%#", request.URL];
NSArray *urlComponents = [urlString componentsSeparatedByString:#"://"];
NSString *urlType = [urlComponents objectAtIndex:0];
if ([urlType isEqualToString:#"http"] || [urlType isEqualToString:#"https"]) {
// present an alert or do whatever you want with this url...
return NO;
}
return YES;
}
First of all, using an alert view to display loading progress in a web view is probably a bad idea, because it blocks all user interaction until it finishes loading.
You already have code that allows the web view to handle certain URLs with the built-in apps, such as iTunes (it doesn't do that on its own), so I guess when you use [[UIApplication sharedApplication] openURL:...] to open the external URL, you could easily hide the spinner there as well.
You could use applicationWillResignActive to detect when the app goes into an inactive state. It goes in your app delegate:
- (void)applicationWillResignActive:(UIApplication *)application {
//close your UIWebView here
}
If you can't access your UIWebView from the delegate, you could register for the UIApplicationDidEnterBackgroundNotification notification from your UIViewController. Make sure you un-register at some point.
//register
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(closeWebView) name:UIApplicationDidEnterBackgroundNotification object:nil];
//un-register
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];