One of the things I hate about the Media Player framework is that although it offers easy-to-use audio playback, it does so horizontally. Users have to flip their phones onto the side and access the playback controls in landscape. It's not all that hard to force Media Player into portrait mode and today I'll show you how.
There's actually a orientation setter hidden within the player. The secret lies in tunneling down to that level. You'll need to add some class declarations to your project. The following allow you to access controllers that are normally obscured from SDK access, to provide both orientation control as well as overlay manipulation.
The overlay refers to the various buttons and sliders that are presented in the player during playback. These appears when the user taps the screen. I hate that, preferring that all controls display themselves as the audio begins playback.
@interface MPViewController : UIViewController @end @interface MPVideoViewController : MPViewController @end @interface MPFullScreenVideoViewController : MPVideoViewController - (BOOL)_canHideOverlay:(BOOL)fp8; @end @interface MPMoviePlayerVideoViewController : MPFullScreenVideoViewController - (void)_showOverlay; @end @interface MPMoviePlayerController (extended) - (id)videoViewController; - (void)setOrientation:(int)orientation animated:(BOOL)yorn; @end
To orient and display the overlay, I use the following code. This sets the orientation to portrait, adds the overlays with a time delay to allow the media player to set itself up and display. In all other respects, the media player works as you'd normally use it.
- (void) addOverlays: (MPMoviePlayerController *) theMovie
{
[[theMovie videoViewController] _showOverlay];
[[theMovie videoViewController] _canHideOverlay:NO];
}
- (void) startPlayback : (id) sender
{
NSString *path = [[NSBundle mainBundle] pathForResource:@"dream" ofType:@"m4a"];
MPMoviePlayerController* theMovie=[[[MPMoviePlayerController alloc] initWithContentURL:[NSURL fileURLWithPath:path]] retain];
[theMovie setOrientation:UIDeviceOrientationPortrait animated:NO];
theMovie.scalingMode=MPMovieScalingModeAspectFill;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myMovieFinishedCallback:) name:MPMoviePlayerPlaybackDidFinishNotification object:theMovie];
[self performSelector:@selector(addOverlays:) withObject:theMovie afterDelay:1.5f];
[theMovie play];
}
Together, the extended class definitions above and the code here create the vertical presentation shown at the start of this post.
Erica, when you say "allow you to access controllers that are normally obscured from SDK access", you should also include a caveat that, because these calls aren't in the SDK, they're subject to change at any time.
Until this functionality appears in the official SDK, your code remains a hack that could break with the next OS release.
I think that's pretty much understood by the readers of this blog, but if it is not, let me help your rest easy and be more clear: "Until this functionality appears in the official SDK, my code remains a hack that could break with the next OS release."
Am getting this warning Warning: "MPMoviePlayerController" may not respond to '-setOrientation:animated"
do i need to import any file
bye
anil
Hi Erica,
Thanks for the example. I tried it out, but no luck. Still goes to landscape. Could you provide a simple working example that can be downloaded?
Thanks,
Curtis
Many developers are struggling with playing audio in portrait format since the SDK apparently doesn't support it.
On way to address it is to use the quicktime player via mobile safari - but it has "cons" too.
Any updates on this?
oh, that's great.
thanks
Has anyone managed to make this work on the new 3.0 SDK? I've been looking at it, and the pointer to the videoViewController has moved and is accessible only from an @private internal variable (through the new MPMoviePlayerControllerInternal class).
I've tried to hack my way into this, but I'm still to new to Objective-C to really understand if I'm bashing my head against a wall!
@Lewis Kirkaldie,
I recently struggled with this problem as well. By using the run-time reflection methods I found that the "videoViewController" has simply been moved a layer further away, and the accessor methods we used before have been removed.
Luckily objective-c offers ways of circumventing this "security"; i.e. we can still access the pointers by using class_copyIvarList(..) and object_getInstanceVariable(..). Refer to the following example of how to find the view containing the video controls:
id internal;
object_getInstanceVariable(yourMoviePlayer, "_internal", (void*)&internal);
id videoViewController;
object_getInstanceVariable(internal, "_videoViewController", (void*)&videoViewController);
UIView* overlayView = [videoViewController _overlayView];
And there it is. Hopefully this will still get through appstore approval.
@shrt
This worked a charm - although for a while I was getting a null address returned for the _videoViewController.
The fix for this (once I became sure that I knew what I was doing and had not just fouled this up) was to wait until the movie player was ready to run (by waiting for MPMoviePlayerContentPreloadDidFinishNotification to fire and then trying to add the overlay).
It is possible that some other way will work, but this stopped the null address problem and a real videoViewController was being returned.
Yay!
Many thanks for your help.
Hello,
Would it be possible to update the sample
you provided in order to reflect the new outcomes
for the 3.0 SDK?
Many thanks
Cédric