Prior to iOS 3.2 your only option for playing videos on iPhone was to use MPMoviePlayerController and only in full screen mode. It made sense because who really wants to look at stamp sized videos on his mobile phone.
The iPad changed all that and brought along several changes in how you can place videos. All those changes also made it into the iPhone SDKs. Here’s a pitfall that you might not have seen if you didn’t upgrade the SDK on one of your iPhone projects.
I had an app that played videos like this:
movieController = [[MPMoviePlayerController alloc] initWithContentURL:movieURL]; [movieController play]; |
The variable movieController is an instance variable. This is necessary because MPMoviePlayerController could not be released before it’s done playing without cutting off the video. So I used the notification that it sends upon ending playback to release the object and reclaim the memory.
// subscribed to MPMoviePlayerPlaybackDidFinishNotification - (void) movieFinished:(NSNotification *)notification { [movieController release]; movieController = nil; } |
I want to continue targeting devices with 3.0 and later, but my Xcode only has the 3.2 and 4.0 SDKs. You can build against 4.0 and set deployment target to 3.0. If you use the code above it still builds but confusingly you don’t get any picture any more, at least not in simulator.
The reason being that MPMoviePlayerController has been modified to output it’s playback into a UIView. This view is a property you can then incorporate into your own view hierarchy with any dimensions you like. In order to still get the full screen playback you had before you would be required to resort to view adding and controlling and other trickery. But way easier is to instead use the new MPMoviePlayerViewController.
Look again at the class name in case you don’t see the difference. The “View” is new between “Player” and “Controller”. Note that the name now contains ViewController, so you can present and dismiss it modally like you are used to. MPMoviePlayerViewController takes care of it’s own MPMoviePlayerController which you can access via a property. The notification still get’s called if you subscribed to it. So that’s still a good place to have some actions to execute as soon as video playback finishes.
Now the interesting part: how can you use the new view controller but still have your code working on iPhones which still run 3.x? You have to check if the new class is available and use it only if it can be resolved. If not we need to continue using the old method.
if (NSClassFromString(@"MPMoviePlayerViewController")) { MPMoviePlayerViewController *player = [[MPMoviePlayerViewController alloc] initWithContentURL:movieURL]; player.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; videoOverviewViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; [videoOverviewViewController presentModalViewController:player animated:YES]; [player release]; } else { movieController = [[MPMoviePlayerController alloc] initWithContentURL:movieURL]; [movieController play]; } |
Here are two interesting things: you can use NSClassFromString to check for runtime availability of classes. Of course the class is available if I build against 4.0 SDK, but when a user legally executes the app on 3.x (due to the lower deployment target) this code detects that the new class is not there. Non-existing classes return nil for NSClassFromString.
Also note that since the modal presenting causes the view controller to be retained we no longer need a special IVAR to keep track of it and can release it locally right after presenting it.
With this small modification I can build my code against the brand new 4.0 SDK but still have it run on iDevices that have not yet been upgraded from 3.x.
Or so I thought. For some reason that I still cannot explain the above code worked fine to play in my iPhone 4, but showed only a black screen on iPhones running 3.1.x. So I called upon Apple Developer Technical Support for a solution, which came a couple of days later. It still had a slight error for using a notification that never got sent, but I fixed that to arrive at the following solution which is now tested and working on 3.1.x, 3.2 and 4.0
In the header:
// IVARS MPMoviePlayerViewController *mViewPlayer; MPMoviePlayerController *mMPPlayer; |
In the implementation:
-(void)playVideo:(NSURL *)url { videoOverviewViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; if ([NSClassFromString(@"MPMoviePlayerController") instancesRespondToSelector:@selector(view)]) { // running iOS 3.2 or better mViewPlayer = [[MPMoviePlayerViewController alloc] initWithContentURL:url]; mViewPlayer.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; videoOverviewViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; [videoOverviewViewController presentModalViewController:mViewPlayer animated:YES]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(movieDidExitFullscreen:) name:MPMoviePlayerPlaybackDidFinishNotification object:[mViewPlayer moviePlayer]]; } else { mMPPlayer = [[MPMoviePlayerController alloc] initWithContentURL:url]; [mMPPlayer play]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:mMPPlayer]; } } - (void) movieDidExitFullscreen:(NSNotification*) aNotification { MPMoviePlayerController *player = [aNotification object]; [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:player]; [player pause]; [player stop]; [videoOverviewViewController dismissModalViewControllerAnimated:YES]; [mViewPlayer.view removeFromSuperview]; [mViewPlayer release]; mViewPlayer = nil; } - (void) moviePlayerDidFinish:(NSNotification*) aNotification { MPMoviePlayerController *player = [aNotification object]; [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:player]; [player pause]; [player stop]; [player release]; mMPPlayer = nil; } |
So there you have it, finally an officially recommended way to get full screen video playback working that’s both backwards compatible with 3.x but gives you the new video features present on 4.0, e.g. portrait playback.
John Muchow also explains how he solved this problem on his blog. His approach is slightly different but also quite interesting.
Categories: Recipes