13

In my app, I have a button which, when pressed, lets you watch a youtube video (a movie trailer). Within the app, without launching safari. Below you can see a code snippet. This code works pefrectly fine under iOS5. However, in iOS 6, the UIButton in findButtonInView is always nil. Any ideas what might be the reason?

youtubeWebView.delegate = self;
youtubeWebView.backgroundColor = [UIColor clearColor];

NSString* embedHTML = @" <html><head> <style type=\"text/css\"> body {background-color: transparent;  color: white; }</style></head><body style=\"margin:0\"><embed id=\"yt\" src=\"%@?version=3&app=youtube_gdata\" type=\"application/x-shockwave-flash\"width=\"%0.0f\" height=\"%0.0f\"></embed></body></html>";

NSURL *movieTrailer;

if(tmdbMovie) movieTrailer = tmdbMovie.trailer;

else movieTrailer = [NSURL URLWithString:movie.trailerURLString];

NSString *html = [NSString stringWithFormat:embedHTML,
                                            movieTrailer,
                                            youtubeWebView.frame.size.width,
                                            youtubeWebView.frame.size.height];

[youtubeWebView loadHTMLString:html baseURL:nil];

[self addSubview:youtubeWebView];


- (void)webViewDidFinishLoad:(UIWebView *)_webView {

    isWatchTrailerBusy = NO;

    [manager displayNetworkActivityIndicator:NO];

    //stop the activity indicator and enable the button

     UIButton *b = [self findButtonInView:_webView];

    //TODO this returns null in case of iOS 6, redirect to the youtube app

    if(b == nil) {

        NSURL *movieTrailer;

        if(tmdbMovie) {
            movieTrailer = tmdbMovie.trailer;
        } else {
            movieTrailer = [NSURL URLWithString:movie.trailerURLString];
        }

        [[UIApplication sharedApplication] openURL:movieTrailer];

    } else {
        [b sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
}


- (UIButton *)findButtonInView:(UIView *)view {

    UIButton *button = nil;
    if ([view isMemberOfClass:[UIButton class]]) {
        return (UIButton *)view;
    }

    if (view.subviews && [view.subviews count] > 0) {

        for (UIView *subview in view.subviews) {
            button = [self findButtonInView:subview];
            if (button) return button;
        }
    }

     return button;
}
rdurand
  • 7,342
  • 3
  • 39
  • 72
Asem H.
  • 743
  • 3
  • 7
  • 16
  • 1
    I am using new HTML iFrame APIs (which is BTW recommended way) to embed youtube video and this auto-play technique findButtonInView never worked for me (returns null). – msk Sep 17 '12 at 15:10
  • @MSK do you have a link to a tutorial or a code snippet on using iframes instead? thx – Asem H. Sep 17 '12 at 15:22
  • See my question and answer [here](http://stackoverflow.com/questions/10385791/youtube-video-autoplay-inside-uiwebview) – msk Sep 17 '12 at 15:30
  • thx. so this means It is impossible to have a button being pressed to watch a youtube video inside the app? – Asem H. Sep 17 '12 at 15:35
  • It never worked for me, and moreover it is a kind of hack which has no guarantee that it will work on next OS update. – msk Sep 17 '12 at 15:43

8 Answers8

11

Apple changed how YouTube videos are handled in iOS6. I also was using the findButtonInView method but that no longer works.

I've discovered the following seems to work (untested in iOS < 6):

- (void)viewDidLoad 
{
    [super viewDidLoad];
    self.webView.mediaPlaybackRequiresUserAction = NO;
}

- (void)playVideo
{
    self.autoPlay = YES;
    // Replace @"y8Kyi0WNg40" with your YouTube video id
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@", @"http://www.youtube.com/embed/", @"y8Kyi0WNg40"]]]];
}

// UIWebView delegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    if (self.autoPlay) {
        self.autoPlay = NO;
        [self clickVideo];
    }
}

- (void)clickVideo {
    [self.webView stringByEvaluatingJavaScriptFromString:@"\
        function pollToPlay() {\
            var vph5 = document.getElementById(\"video-player\");\
            if (vph5) {\
                vph5.playVideo();\
            } else {\
                setTimeout(pollToPlay, 100);\
            }\
        }\
        pollToPlay();\
     "];
}
dharmabruce
  • 439
  • 3
  • 5
  • I modified the javascript code to poll for the element until it is available before sending the click event. This solved the issue when I saw it on some devices. Please let me know if this helps! – dharmabruce Sep 22 '12 at 03:05
  • Well, hmmm, it's working for me on iPhone 4s, iPhone 5, and iPad. I'll update if I find a better way. – dharmabruce Sep 25 '12 at 15:31
  • 3
    I can confirm this DOES work for me on iOS6 on my iPhone4S! The only thing is, you have to wait a couple of seconds before it works, but it does work! – Bob de Graaf Sep 27 '12 at 14:36
  • I can confirm it works for me as well. Both, on iPad and iPhone (running iOS6) – Sid Oct 02 '12 at 19:01
  • I found one glitch though: when I tap the fullscreen button on iPad, playback stops, no idea why. Another thing: do you know how to pass parameters to the embedded player? I need to remove any decoration that's there by default (title overlay, related videos etc). – Lvsti Jan 08 '13 at 23:12
  • 2
    When we watch first video it is working. when I click on second video, the above code didn't work for me. Do i need to add any extra parameters? – Dee Mar 07 '13 at 06:32
  • It disabled autoplay option after I watch the first video and it is not enabling again for next video. Here is my console output: Autoplay: Skipping autoplay, disabled (for current item: 1, on player: 1) 2013-03-07 18:29:10.484 CCL[3824:907] [MPAVController] Autoplay: Disabling autoplay for pause 2013-03-07 18:29:10.486 CCL[3824:907] [MPAVController] Autoplay: Disabling autoplay 2013-03-07 18:29:12.411 CCL[3824:907] [MPAVController] Autoplay: Disabling autoplay for pause 2013-03-07 18:29:12.415 CCL[3824:907] [MPAVController] Autoplay: Disabling autoplay – Dee Mar 07 '13 at 13:02
8

dharmabruce solution works great on iOS 6, but in order to make it work on iOS 5.1, I had to substitute the javascript click() with playVideo(), and I had to set UIWebView's mediaPlaybackRequiresUserAction to NO

Here's the modified code:

- (void)viewDidLoad 
{
    [super viewDidLoad];
    self.webView.mediaPlaybackRequiresUserAction = NO;
}

- (void)playVideo
{
    self.autoPlay = YES;
    // Replace @"y8Kyi0WNg40" with your YouTube video id
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@", @"http://www.youtube.com/embed/", @"y8Kyi0WNg40"]]]];
}

// UIWebView delegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    if (self.autoPlay) {
        self.autoPlay = NO;
        [self clickVideo];
    }
}

- (void)clickVideo {
    [self.webView stringByEvaluatingJavaScriptFromString:@"\
        function pollToPlay() {\
            var vph5 = document.getElementById(\"video-player-html5\");\
            if (vph5) {\
                vph5.playVideo();\
            } else {\
                setTimeout(pollToPlay, 100);\
            }\
        }\
        pollToPlay();\
     "];
}
JimmY2K.
  • 81
  • 2
  • 1
    It is working for the first time. If I want to watch another video the above code is not working. Why? – Dee Mar 07 '13 at 12:51
3

I have solved the problems with dharmabruce and JimmY2K. solutions, with the fact that it only works the first time a video is played, as mentioned by Dee

Here is the code(including event when a video ends):

- (void)embedYouTube:(NSString *)urlString frame:(CGRect)frame {
    videoView = [[UIWebView alloc] init];
    videoView.frame = frame;
    videoView.delegate = self;

    [videoView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];// this format: http://www.youtube.com/embed/xxxxxx
    [self.view addSubview:videoView];

    //https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/MediaPlayer.framework/MPAVController.h
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(playbackDidEnd:)
                                                 name:@"MPAVControllerItemPlaybackDidEndNotification"//@"MPAVControllerPlaybackStateChangedNotification"
                                               object:nil];

}


- (void)webViewDidFinishLoad:(UIWebView *)webView {
    //http://stackoverflow.com/a/12504918/860488
    [videoView stringByEvaluatingJavaScriptFromString:@"\
                                    var intervalId = setInterval(function() { \
                                        var vph5 = document.getElementById(\"video-player\");\
                                        if (vph5) {\
                                            vph5.playVideo();\
                                            clearInterval(intervalId);\
                                        } \
                                    }, 100);"];
}

- (void)playbackDidEnd:(NSNotification *)note
{
    [videoView removeFromSuperview];
    videoView.delegate = nil;
    videoView = nil;
}
Morten Holmgaard
  • 7,484
  • 8
  • 63
  • 85
1

Untested and not sure if that is the problem here, becasue I don't know the URL you're using. But I stumbled right about following:

As of iOS 6, embedded YouTube URLs in the form of http://www.youtube.com/watch?v=oHg5SJYRHA0 will no longer work. These URLs are for viewing the video on the YouTube site, not for embedding in web pages. Instead, the format that should be used is described here: https://developers.google.com/youtube/player_parameters.

from http://thetecherra.com/2012/09/12/ios-6-gm-released-full-changelog-inside/

Marvin Emil Brach
  • 3,984
  • 1
  • 32
  • 62
0

I'm working on this same general problem and I'll share the approach I've come up with. I have some remaining kinks to work out for my particular situation, but the general approach may work for you, depending on your UI requirements.

If you structure your HTML embed code so that the YouTube player covers the entire bounds of the UIWebView, that UIWebView effectively becomes a big play button - a touch anywhere on it's surface will make the video launch into the native media player. To create your button, simply cover the UIWebView with a UIView that has userInteractionEnabled set to NO. Does it matter what the size the UIWebView is in this situation? Not really, since the video will open into the native player anyway.

If you want the user to perceive an area of your UI as being where the video is going to "play", then you can grab the thumbnail for the video from YouTube and position that wherever. If need be, you could put a second UIWebView behind that view, so if the user touches that view, the video would also launch - or just spread the other UIWebView out so that it is "behind" both of these views.

If you post a screenshot of your UI, I can make some more specific suggestions, but the basic idea is to turn the UIWebView into a button by putting a view in front of it that has user interaction disabled.

Rob Reuss
  • 1,400
  • 10
  • 20
0

I've found that the code below works for both iOS5 & iOS6. In my example, I have a text field that contains the url to the video. I first convert the formats to the same starting string 'http://m...". Then I check to see if the the format is the old format with the 'watch?v=' and then replace it with the new format. I've tested this on an iPhone with iOS5 and in the simulators in iOS5 & iOS6:

-(IBAction)loadVideoAction {
    [activityIndicator startAnimating];

    NSString *urlString = textFieldView.text;
    urlString = [urlString stringByReplacingOccurrencesOfString:@"http://www.youtube" withString:@"http://m.youtube"];
    urlString = [urlString stringByReplacingOccurrencesOfString:@"http://m.youtube.com/watch?v=" withString:@""];
    urlString = [NSString stringWithFormat:@"%@%@", @"http://www.youtube.com/embed/", urlString];
    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [activityIndicator stopAnimating];
}
fawsha1
  • 780
  • 8
  • 13
0

I believe the problem is that when webViewDidFinishLoad was triggered, the UIButton was not added to the view yet. I implemented a small delay to find the button after the callback is returned and it works for me on ios5 and ios6. drawback is that there is no guarantee the UIButton is ready to be found after the delay.

- (void)autoplay {
    UIButton *b = [self findButtonInView:webView];
    [b sendActionsForControlEvents:UIControlEventTouchUpInside]; }

- (void)webViewDidFinishLoad:(UIWebView *)_webView {
    [self performSelector:@selector(autoplay) withObject:nil afterDelay:0.3]; }

I had to use the url http://www.youtube.com/watch?v=videoid this is the only way it will work for me

tzl
  • 1,540
  • 2
  • 20
  • 31
-1
- (void) performTapInView: (UIView *) view {
    BOOL found = NO;
    if ([view gestureRecognizers] != nil) {
        for (UIGestureRecognizer *gesture in  [view gestureRecognizers]) {
            if ([gesture isKindOfClass: [UITapGestureRecognizer class]]) {
                if ([gesture.view respondsToSelector: @selector(_singleTapRecognized:)]) {
                    found = YES;
                    [gesture.view performSelector: @selector(_singleTapRecognized:) withObject: gesture afterDelay: 0.07];
                    break;
                }
            }
        }
    }

    if (!found) {
        for (UIView *v in view.subviews) {
            [self performTapInView: v];
        }
    }
}
#pragma mark - UIWebViewDelegate methods
- (void) webViewDidFinishLoad: (UIWebView *) webView {
    if (findWorking) {
        return;
    }
    findWorking = YES;
    [self performTapInView: webView];
}
Steve
  • 21
  • 3
  • iOS 6.0 use gesture to detect user tap now, after tap detected use html5 auto play. – Steve Sep 24 '12 at 06:54
  • 1
    I dont know if apple will approve this yet, will try to submit a version with this code today. Will keep you guys posted. – Steve Sep 24 '12 at 06:55
  • 1
    Steve you're lucky, my App just got rejected using this code. It said I used a non-public API called _singleTapRecognized: So I can advice people to not use this code! – Bob de Graaf Oct 07 '12 at 14:11
  • **Our app was just rejected as well!** This is Apple's reasoning: We found that your app uses one or more non-public APIs, which is not in compliance with the App Store Review Guidelines. The use of non-public APIs is not permissible because it can lead to a poor user experience should these APIs change. We found the following non-public API/s in your app: _singleTapRecognized: – vinzenzweber Dec 04 '12 at 07:36
  • Anyone who've checked it on – Ahmed Dec 19 '12 at 08:01