Playing a video with external display support

We have a client project that, at several points in the iPad app, displays videos that were embedded into the app. This is an older app, originally written before there were such things as iPad VGA adapters. One might hope that, in the absence of any programming directives telling it otherwise, the iPad would simply mirror its entire display to the video port. Failing that (as Apple in fact has done), you might hope that the MPMoviePlayerController would automatically support the external display. But that fails too.

We spent quite a bit of time trying to make the MPMoviePlayerController work on the external monitor, but had very little luck. If you put it on the second screen (i.e. the external one), the controls are there too, where they are unusable. If you don't put it on the second screen, then of course it gets no video. There appears to be no way to separate the controls from the playback area with this controller, so to continue to use it, you'd have to roll your own playback controls and hook them up with a fair bit of plumbing.

However, there is a much easier way. It turns out that UIWebView is savvy to external monitors (and has been for some time). So, by stuffing your video into a UIWebView, you get a nice UI with standard playback controls, both with or without an external screen.

There are a few details to handle, of course. The web view has no "done" button, so if you want your users to be able to do anything else with the app after visiting the video, you'll need to provide some way for them to get back out. A modal view controller — equipped with a navigation bar sporting a "Done" button — is one easy solution. You'll also probably want to resize the view to fill the screen, and disable scrolling on the UIWebView.

Let's take it step by step. First, given a frame (which you might set to self.view.bounds in the parent view controller), create a new controller containing a web view:

    UIViewController *controller = [[[UIViewController alloc] init] autorelease];
    UIWebView *webView = [[[UIWebView alloc] initWithFrame:frame] autorelease];
    controller.view = webView;

Then, to add a navigation bar with a Done button...

    controller.title = @"Sample Video";
    controller.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]
          initWithBarButtonSystemItem:UIBarButtonSystemItemDone
          target:self
          action:@selector(dismissView:)] autorelease];          
    UINavigationController *navController = [[[UINavigationController alloc]
          initWithRootViewController:controller] autorelease];

Now we can get the height of the navigation bar, which we'll need to adjust the height of the web view. The video itself is stuffed into the web view via a snippet of simple HTML, which also sets the background color and a few other things.

    float navBarHeight = navController.navigationBar.frame.size.height;
    NSString* html = [NSString stringWithFormat:
		  @""
		   "",
		  @"sample_iPod.m4v", frame.size.width, frame.size.height- navBarHeight];
    
    webView.frame = CGRectMake(frame.origin.x,
                               frame.origin.y,
                               frame.size.width,
                               frame.size.height - navBarHeight);
    [webView loadHTMLString:html baseURL:[[NSBundle mainBundle] resourceURL]];

Next, we want to disable scrolling on the web view, which otherwise just looks sloppy. We made a little helper method to do that; it simply iterates over the children of the web view, and turns off scrolling of any UIScrollView it finds.

- (void)disableScrollingForWebView:(UIWebView *)webView {
    for (UIView* view in webView.subviews) {
        if ([view isKindOfClass:[UIScrollView class]]) {
            ((UIScrollView*)view).scrollEnabled = NO;
        }
    }
}

Finally, back in our main code, we just call that helper method, and present the whole shebang modally:

    [self disableScrollingForWebView:webView];
    [self presentModalViewController:navController animated:YES];

That's it! The result is a beautiful video player, with or without an external display.

There's only one thing still missing: the "autoplay" attribute in the video tag doesn't appear to work. The video appears initially as just a static image, with a big round play button in the middle, and the user has to tap that to make it play. If you happen to know how to make it play right away (a wee bit of JavaScript perhaps?), please post to the comments below!

[EDIT: see next week's post for a few final refinements.]