Play iPhone Movies in Portrait Mode with MPMoviePlayerController using Public API’s

Mon, Jan 4

Editor’s Note: With the release of SDK’s supporting 3.2 and 4.x, this approach is no longer necessary. The new movie player can now be displayed in either direction, landscape or portrait. Read this post for a more information: Display iPhone Movies in Portrait Mode (Updated)

It’s been covered by a number of websites and blogs on how to play movies in portrait mode using MPMoviePlayerController. Problem is, every solution that I’ve been able to find uses private API’s to tell the player to flip the direction of play.

Other than the built-in movie player, another option is to use a UIWebview, however, there are a few drawbacks including no support for notifications on when a movie has been preloaded, which is handy for displaying a “please wait” loading message.

This post is introduces another approach to playing a movie in portrait mode without delving into private API’s, and I think you’ll agree, it’s quite clever.

Portrait Video Mode on the iPhone

I spend a fair amount of time working with video related applications and content, so when I saw a portrait video playing seamlessly inside Dog Tricks – Best of 101 Dog Tricks I was very interested to figure out how to do the same.

I wrote Michael Schneider of Hive Brain Software, developer of the above app and asked if he would share his secret. Michael was kind enough to pass along some great notes explaining how we got this to work. This post walks through a complete working example I wrote based on the information Michael shared with me.

The End Result

Let’s start by looking at a short video of the application I’ll build in this post running in the simulator:

Notice how the video is shown within the application with the tabbar controls still visible on the screen.

The 30,000 Foot View

The basic idea to make this work is simple, rotate the orientation of the original video, turn off the movie player controls and fire up the movie player as normal.

What you end up with is the movie player running landscape mode with your video content viewable in portrait mode. The reason for hiding the controls is that they would appear sideways. As an example, notice in the screenshot below that the video is viewable in portrait mode, however, the controls are still oriented for a landscape movie player:

Although a simple trick, it’s the little things that will make this approach appear seamless within your application. As an example of a nice effect, I’ll show how to overlay an image on top of the movieplayer so it appears as though the movie is playing directly inside your app.

There are three steps to make this all work:

- Change the orientation of the movie
- Turn off the movie player controls
- Overlay an image of your application UI on top of the movie player

Let’s see how to pull all the pieces together…

Video Orientation

The first step is to use a capable video editing tool to rotate your video to portait mode. I used Quicktime Pro (on Snow Leopard), the steps that I used follow:

- From the Window menu choose Show Movie Properties
- Click on the row for the video track
- Click the Visual Settings tab
- In the Flip/Rotate section, rotate the video counter-clockwise
(or clockwise depending on the orientation you set for your iPhone app).

Code for App Delegate

The app delegate code for this code example is nothing more than a Window object and a Tabbar controller. The interface and implementation files are below:

#import <UIKit/UIKit.h>
 
@class TabbarController;
 
@interface Test_appAppDelegate : NSObject <UIApplicationDelegate>
{
  UIWindow *window;
  TabbarController *tabbar;
}
 
@property (nonatomic, retain) UIWindow *window;
@property (nonatomic, retain) TabbarController *tabbar;
 
@end

The implementation file:

...
- (void)applicationDidFinishLaunching:(UIApplication *)application 
{   
  // Create and initialize the window
  window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
 
  // Create the tabbar
  tabbar = [[TabbarController alloc] init];
  [window addSubview:tabbar.view]; 
 
  [window makeKeyAndVisible];
 
}
...
Code for Tabbar Controller

The tabbar controller is code is nothing out of the ordinary, the interface file is shown below:

#import <UIKit/UIKit.h>
 
@interface TabbarController : UITabBarController  <UITabBarControllerDelegate>
{
}
 
@end

As you can see in the code below I’ve defined three tabs, each with a unique title:

@implementation TabbarController
 
- (id) init
{
  self = [super init];
  if (self) 
  {
    self.delegate = self;
 
    UIViewController *localViewController;   
 
    // New tabbar controller and array to contain the view controllers
    NSMutableArray *localViewControllersArray = [[NSMutableArray alloc] initWithCapacity:3];
 
    // Tab 1
    localViewController = [[Tabbar1ViewController alloc] initWithTitle:@"Tab 1"];    
    [localViewControllersArray addObject:localViewController];
    [localViewController release];
 
    // Tab 2
    localViewController = [[Tabbar2ViewController alloc] initWithTitle:@"Tab 2"];    
    [localViewControllersArray addObject:localViewController];
    [localViewController release];
 
    // Tab 3
    localViewController = [[Tabbar3ViewController alloc] initWithTitle:@"Tab 3"];    
    [localViewControllersArray addObject:localViewController];
    [localViewController release]; // Retained by above array
 
    self.viewControllers = localViewControllersArray;
    [localViewControllersArray release];
  }
 
  return self;
}
 
@end
Code for Tab 1

The only tabbar that has any working code for this example is the left-most tab, which plays the movie in portrait mode. The interface definition for this tab is shown below:

#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
 
@interface Tabbar1ViewController : UIViewController
{
  MPMoviePlayerController  *moviePlayer;
  NSURL  *movieURL;
  MovieOverlayViewController *overlay;
}
 
@end

Inside the implementation file, the first block of relevant code is the initialization, which sets up the view, the background color and the title for the tab:

- (id)initWithTitle:(NSString *)theTitle 
{
  if (self = [super init])
  {
    self.view = [[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];    
    self.view.backgroundColor = [UIColor blackColor];
    self.title = theTitle;
  }  
  return self;  
}

The next section of code to write is when the tab is about to appear, here we create the path to the movie, setup the movie player, register to receive a notification when the movie has finished playing, start the movie, and finally, overlay an image on top of the player (more on that below):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-(void)viewWillAppear:(BOOL)animated
{
  // Path to the movie
  NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"mov"];      
  movieURL = [NSURL fileURLWithPath:path];
 
  // Setup the player
  moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
 
  // Hide the controls
  [moviePlayer setMovieControlMode:MPMovieControlModeHidden];  
 
  // Register to receive a notification when the movie has finished playing
  [[NSNotificationCenter defaultCenter] addObserver:self 
                      selector:@selector(moviePlayBackDidFinish:) 
                      name:MPMoviePlayerPlaybackDidFinishNotification 
                      object:nil];
 
  // Start the movie  
  [moviePlayer play];
 
  // Overlay an image with center that is transparent for movie to show through  
  overlay = [[MovieOverlayViewController alloc] init];
   NSArray *windows = [[UIApplication sharedApplication] windows];
  UIWindow *moviePlayerWindow = [windows objectAtIndex:1];
  [moviePlayerWindow addSubview:overlay.view];  
}

The interesting code starts on line 21, this is where I define a new view controller, MovieOverlayViewController, which will overlay an image on top of the running movie player. This overlay will present the illusion that the movie is running directly inside the app.

Movie Image Overlay

To give the app the appearance of running natively in portrait mode, we’ll overlay an image that shows the tabbars along the bottom and a text banner across the top. I’ll also write code to detect when the user taps on the image and do the necessary checks to see if the user tapped in an area that would translate to one of the tabs, more on that momentarily.

The finished look on the device is below:

Notice in the figure below that actual image we will overlay is nothing more than a transparent image with some text across the top and a series of tabbar images across the bottom.

The movie overlay view controller for this app is quite trivial, it contains nothing more than a UIImageView:

@interface MovieOverlayViewController : UIViewController 
{
  UIImageView *overlay;
}

Inside the implementation file, we begin with the initialization and code for creating the UIImageView that will be overlayed on the movie player:

- (id)init
{
  if (self = [super init])
    self.view = [[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];    
  return self;  
}
 
-(void) viewWillAppear:(BOOL)animated
{
  overlay = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"overlay.png"]] autorelease];
  [self.view addSubview:overlay]; 
}

The other relevant code for this view controller is managing touches on the image. To provide the most realistic user experience, once the image is overlayed and the movie is playing, you will need to detect touches on the image where the tabs live. The code below will determine if a touched point is within the rectangle of each of the tabs, printing a message to the console on which tab was touched.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{	  
  // Detect touch anywhere
  UITouch *touch = [touches anyObject];
 
  // Where is the point touched
  CGPoint point = [touch locationInView:self.view]; 
  NSLog(@"pointx: %f pointy:%f", point.x, point.y);
 
  // Was a tab touched, if so, which one...
  if (CGRectContainsPoint(CGRectMake(1, 440, 106, 40), point))
    NSLog(@"tab 1 touched");
  else if (CGRectContainsPoint(CGRectMake(107, 440, 106, 40), point))
    NSLog(@"tab 2 touched");
  else if (CGRectContainsPoint(CGRectMake(214, 440, 106, 40), point))
    NSLog(@"tab 3 touched");
}

You could add code to the above example and when tab 2 or 3 is tapped, stop the movie, remove the overlay and switch to the view controller associated with the appropriate tab.

Xcode Portrait Mode Video Source Code

You can download the entire Xcode project here: Xcode Portrait Video.

Credits

Thanks again to Michael Schneider of Hive Brain Software for sharing his insight on this trick. You can check out more of Michael’s work on a series of self improvement applications at Relaxing Apps.

And Michael would like to pass on many thanks Scott Michaels at Atimi in Vancouver BC for sharing information on this approach at the 360iDev conference in San Jose.

17 comments

I was just struggling to find out how to do this two days ago. I decided then to use your approach (pre-rotating the source video), so I’ve read this too late for me. :D
Fortunately, I didn’t need to overlay anything either. Nevertheless, your post reassured me on my choice, so I wanted to thank you for sharing it with us. :D
Thank you very much! ;)

by RoberRM on Jan 4, 2010. Reply #

Thanks for this John. I never really considered attempting doing this outside of a UIWebView.

This is really cool, and it will be nice to not have to play with HTML/CSS to get things looking right.

by JD@maniacdev on Jan 6, 2010. Reply #

Good work. This is almost what i need. So two additions would be 1) not to have to pre-rotate the video and 2) can we scale the video so that it’s any size? I think maybe using the approach you suggest that it’s not possible. However, you hint towards others using private API’s. Can you reference in the article a good site for people that are using private API’s? thanks!

by NeedAppforThat on Feb 7, 2010. Reply #

I’ve read that the private method below will rotate the player:

[moviePlayer setOrientation:UIDeviceOrientationPortrait animated:NO];

However, I recommend that you don’t use this or any private API as Apple can/will reject applications using undocumented calls. Also, even if the code works today, no guarantee it will in future releases.

by John Muchow on Feb 7, 2010. Reply #

agreed. thanks for the concern. i guess i’d like to see what can be done by using any kind of method. that’s why i like the writeup of this page but also want to see the private API methods. The reason for the latter is that it sometimes is possible to get big companies to push apple on some of these cool features and they just might open up.

by NeedAppforThat on Feb 8, 2010. Reply #

Nice piece. However, if you download Movies from web (or it takes a bit to load), you don’t have yet 2 Windows when you use

[windows objectAtIndex:1];

I recommend to start a timer, AND add overlay after a short amount of time.

by ingconti on May 26, 2010. Reply #

Thanks for tutorial , how i can hide the bottom tab controller when video start ? i like to see fullscreen..

Regards

by luca on Jun 5, 2010. Reply #

You may have to make some code changes to support this – one option is that the UIViewController class has a method hidesBottomBarWhenPushed that you can set to returns YES if you want the tabbar hidden when the view controller is pushed onto a navigation controller.

by John Muchow on Jun 5, 2010. Reply #

nice.
i downloaded your xcode example
when i compile in sdk 3.2.3 with target 4.0
i get : setMovieControlMode is deprecated as warning

but more serious… when started it crash with

*** Terminating app due to uncaught exception ‘NSRangeException’, reason: ‘*** -[NSMutableArray objectAtIndex:]: index 1 beyond bounds [0 .. 0]‘

how to solve?

thx
chris

by chris on Aug 5, 2010. Reply #

Chris, I think the cause of the error is this line in viewWillAppear:

UIWindow *moviePlayerWindow = [windows objectAtIndex:1];

The order of windows must of changed with newer releases of the SDK. Rather than update this code, I would recommend you take a look at this more recent post that uses the latest versions of the Movie Player: Display iPhone Movies in Portrait Mode

by John Muchow on Aug 5, 2010. Reply #

@john muchow

thanks for info !!!!

chris

by chris on Aug 11, 2010. Reply #

Thank you very much !
Your tip gives me big help.
Thank you again.

by StoneRain on Sep 1, 2010. Reply #

I can’t figure out how to make it autorotate…

by Simon Tarr on Sep 21, 2010. Reply #

@john muchow

great with the info that the problem is this line:

UIWindow *moviePlayerWindow = [windows objectAtIndex:1];
The order of windows must of changed with newer releases of the SDK.

I take the overlay out and the project migrates to the ipad no problem. But then the cool overlay function stops working. Is there a way to fix this line?

Thanks for great code,
Mike

by Mike on Nov 24, 2010. Reply #

Is this an app or are you able to do this in a browser? I need to create a touch overlay on video in a browser window for Iphone.

by Matt on Jan 4, 2011. Reply #

The code shown is targeted at a native iPhone application versus a browser, unfortunately this isn’t applicable to the Safari browser on the iPhone.

by John Muchow on Jan 4, 2011. Reply #

Brilliant thanks. Really useful to have the project available for download and examination. Very thoughtful thank you.

by Max on Jun 10, 2011. Reply #

Leave a Comment