MPMoviePlayerController not working in iOS4


#1

Hi all,

I just downloaded Xcode 3.2.3 with the iPhone 4.0 SDK. When I try to run the MediaPlayer example, the Audio File and the Short Sound work fine. However, when I try to play the Video file, nothing happens. I added an NSNotification to monitor when it started & finished playing, and it “appears” to be playing the movie, but the view never changes. Looking at the Developer Docs, it appears that there were some significant changes to MPMoviePlayerController in 4.0, which have somehow broken the standard “full screen” mode. I tried forcing it to full screen, and to autoplay, but it didn’t help. This fails with both the simulator and on the device (an iPhone 3G).

Also, the MPMoviePlayerContentPreloadDidFinishNotification (mentioned on p. 283) is deprecated in iOS4.

I found a website where someone mentions the same issues with MPMoviePlayerController:
http://stackoverflow.com/questions/2778429/mpmediaplayercontroller-on-iphone-sdk-4-0

Anyone else tried this?

-Chris


#2

Yes, they changed this in iOS4. We’re working on a correction for the second edition, which I plan to have ready for the printers in October. In the meantime, check out MPMoviePlayerViewController (notice the View in there).

The second edition will include the important new stuff from iOS 4 as well as more iPad stuff.


#3

Thanks for the update, Joe. Just wondering if there’s any plans for a FAQ about things to look out for so that we can keep developing for ios4 until the new edition comes out. The example here would be one of the entries in the FAQ.


#4

Hi,

Just a quick question about the second edition of the book. Will there be some way to ‘upgrade’ our current eBook to the new edition ?

Regards,

Stefaan


#5

For minor updates (read: fixes to problems), I believe there is some sort of update mechanism for the Kindle. I think we had to do that early on when the publisher totally botched the first version that went to the Kindle store (this angered me immensely).

However, the second edition of the book will be a different book altogether and not an “upgrade.” It will have substantially more content than the first edition. So far, I have added content for multitasking, blocks, categories, new MapKit stuff and UIPopoverController. I will also cover push notifications, Core Motion, UISplitViewController and a few other iPad things.


#6

Thanks Joe.

Really looking forward to the second edition. Once I’m finished with the book, I’ll create a review for our Cocoaheads Belgium website :slight_smile:

Regards,

Stefaan


#7

Joe discusses the approach of implementing MPMoviePlayerController within a custom view controller at http://forums.bignerdranch.com/viewtopic.php?f=71&t=382#p877.

Another approach I came up with is to use the new MPMoviePlayerViewController (which Joe also mentioned above). Here’s my first modification to MediaPlayerAppDelegate, which doesn’t use a custom view controller. Note it doesn’t retain the pre-3.2 version compatibility.

Starting with a minor modification to the header file, I replaced the MPMoviePlayerController *moviePlayer instance variable with:

	MPMoviePlayerViewController *movieController;

I then made the following mods to the implementation file:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
	...
	NSString *moviePath = [[NSBundle mainBundle] pathForResource:@"Layers" ofType:@"m4v"];
	if(moviePath) {
		NSURL *movieURL = [NSURL fileURLWithPath:moviePath];
		movieController = [[MPMoviePlayerViewController alloc] initWithContentURL];
			// NOTE: Replaced MPMoviePlayerController
	}
	...
}

- (IBAction)playVideoFile:(id)sender 
{ 
	// NOTE: Replaced [moviePlayer play];

	[[NSNotificationCenter defaultCenter]
		addObserver:self selector:@selector(moviePlayerPlaybackDidFinish:)
		name:MPMoviePlayerPlaybackDidFinishNotification
	        object:[movieController moviePlayer]];
	
	[window addSubview:[movieController view]];
}	

- (void)moviePlayerPlaybackDidFinish:(NSNotification *)note
{
	// Restore UI
	[[movieController view] removeFromSuperview];
	[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];
}

Unfortunately, and for reasons I don’t understand, it only works the first time the movie is played (at least for me). Second and subsequent plays don’t show the video while it’s playing (only audio); I have to tap the screen and then tap the scaling button to get the video to appear (on both simulator and device). To get the video to show on more than just the first viewing, I added the following to the top of moviePlayerPlaybackDidFinish:

	[[NSNotificationCenter defaultCenter] 
		removeObserver:self];
		name:MPMoviePlayerPlaybackDidFinishNotification 
		object:[movieController moviePlayer]];

	[[movieController moviePlayer] setScalingMode:MPMovieScalingModeAspectFit];
	[[movieController moviePlayer] stop];

On the simulator stop works well (although I don’t understand why it’s necessary because the very fact that we’re receiving a MPMoviePlayerPlaybackDidFinishNotification implies the player already stopped; in fact, calling stop like this re-fires the notification, thus the reason for the call to removeObserver:name:object: ahead of time). On my iPhone 4, however, the third viewing repeats the bad behavior. I tried setting various MPMoviePlayerController properties to reset the player, but the only way I could get a foolproof implementation was to recreate the MPMoviePlayerViewController each time the movie is run. (The call to setScalingMode: restores the movie view to AspectFit in the event the user left the video in an AspectFill mode; otherwise, the next time the movie is played, it’ll remain in AspectFill.)

To get that foolproof implementation, at the top of playVideoFile: I added the following (well, I actually wrapped this in a method called initMovieController and called that method from application:didFinishLaunchingWithOptions: and from playVideoFile: just to keep the code a bit tidier).

	if (!movieController) {
		NSString *moviePath = [[NSBundle mainBundle] pathForResource:@"Layers" ofType:@"m4v"];
		if(moviePath) {
			NSURL *movieURL = [NSURL fileURLWithPath:moviePath];
			movieController = [[MPMoviePlayerViewController alloc] initWithContentURL];
		}
	}

Then, removing the [[movieController moviePlayer] stop] from moviePlayerPlaybackDidFinish: (no need for it anymore), I added this to the bottom of moviePlayerPlaybackDidFinish:

	[movieController release];
	movieController = nil;	

To clean up the code a bit, I could have removed the initial MPMoviePlayerViewController alloc from application:didFinishLaunchingWithOptions: and done away with the if (!movieController) test, but leaving it in application:didFinishLaunchingWithOptions: has the effect of preloading it so it plays immediately when the user presses the Play Movie button.

It’s not the most pretty, correct, or elegant solution, but it’s a working MPMoviePlayerViewController implementation, by golly! (I look forward to Joe’s insightful 3-line implementation in the next edition of his book).

As a further exercise, I also reimplemented the notification code using the Blocks now available in iOS 4.0 (making the code incompatible with earlier versions). First, I moved the notification code into a block in playVideoFile:, and then deleted the moviePlayerPlaybackDidFinish: method. I trimmed down the NSNotificationCenter removeObserver: call because there is only one notification self is observing (technically, this notification would never be called since I am destroying the MPMoviePlayerViewController, but it seems like a good housekeeping thing to do). Note also the appearance of the wrapper/convenience method initMovieController mentioned earlier.

- (IBAction)playVideoFile:(id)sender 
{ 
	[self initMovieController];

	[[NSNotificationCenter defaultCenter]
		addObserverForName:MPMoviePlayerPlaybackDidFinishNotification
		object:[movieController moviePlayer]
		queue:nil
		usingBlock:^(NSNotification *notif) 
		{
			[[NSNotificationCenter defaultCenter] removeObserver:self];

			// Restore UI
			[[movieController moviePlayer] setScalingMode:MPMovieScalingModeAspectFit];
			[[movieController view] removeFromSuperview];
			[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];
			
			// Reset movie player
			[movieController release];
			movieController = nil;	
		}
	 ];
	
	[window addSubview:[movieController view]];
}	

To tie it all together, here’s the complete header file, MediaPlayerAppDelegate.h:

#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h> 
#import <AVFoundation/AVFoundation.h> 
#import <MediaPlayer/MediaPlayer.h> 

@interface MediaPlayerAppDelegate : NSObject <UIApplicationDelegate, AVAudioPlayerDelegate> 
{
	AVAudioPlayer *audioPlayer; 
	MPMoviePlayerViewController *movieController; 
	SystemSoundID shortSound; 

	IBOutlet UIButton *audioButton; 
	UIWindow *window;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
- (IBAction)playAudioFile:(id)sender; 
- (IBAction)playVideoFile:(id)sender; 
- (IBAction)playShortSound:(id)sender; 
- (void)initMovieController;

@end

And the complete implementation file, MediaPlayerAppDelegate.m:

#import "MediaPlayerAppDelegate.h"

@implementation MediaPlayerAppDelegate

@synthesize window;

#pragma mark -
#pragma mark Application Lifecycle

- (BOOL)application:(UIApplication *)application 
	didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
	// Init system sound
	NSString *soundPath = [[NSBundle mainBundle] pathForResource:@"Sound12" ofType:@"aif"];
	if (soundPath)
	{
		// Create a file URL with this path
		NSURL *soundURL = [NSURL fileURLWithPath:soundPath];
    
		// Register sound file located at that URL as a system sound
		OSStatus err = 
			AudioServicesCreateSystemSoundID((CFURLRef)soundURL, &shortSound);
		if (err != kAudioServicesNoError) 
			NSLog(@"Could not load %@, error code: %d", soundURL, err);
	}
	
	// Init audio
	NSString *musicPath = [[NSBundle mainBundle] pathForResource:@"Music" ofType:@"mp3"];
	if(musicPath)
	{
		NSURL *musicURL = [NSURL fileURLWithPath:musicPath];	
		audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:musicURL error:nil];
		[audioPlayer setDelegate:self];
	}	

	// Init (and preload) movie
	[self initMovieController];
	
	[window makeKeyAndVisible];
	return YES;
}

- (void)applicationWillTerminate:(UIApplication*)app 
{ 
    AudioServicesDisposeSystemSoundID(shortSound); 
} 

- (void)dealloc 
{
	[audioPlayer release];
	[movieController release];
	AudioServicesDisposeSystemSoundID(shortSound);
	[audioButton release];
	[window release];
	[super dealloc];
}

#pragma mark -
#pragma mark Audio

- (IBAction)playShortSound:(id)sender 
{ 
	AudioServicesPlaySystemSound(shortSound); 
	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
} 

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 
{ 
	[audioButton setTitle:@"Play Audio File" forState:UIControlStateNormal]; 
}

- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player 
{ 
	[audioPlayer play]; 
} 

- (IBAction)playAudioFile:(id)sender 
{ 
	if ([audioPlayer isPlaying]) { 
		// Stop playing audio and change text of button 
		[audioPlayer stop]; 
		[sender setTitle:@"Play Audio File" forState:UIControlStateNormal]; 
	} 
	else { 
		// Start playing audio and change text of button so user can tap to stop playback 
		[audioPlayer play]; 
		[sender setTitle:@"Stop Audio File" forState:UIControlStateNormal]; 
	} 
}

#pragma mark -
#pragma mark Video

- (void)initMovieController
{
	if (!movieController) {
		NSString *moviePath = [[NSBundle mainBundle] pathForResource:@"Layers" ofType:@"m4v"];
		if(moviePath) {
			NSURL *movieURL = [NSURL fileURLWithPath:moviePath];
			movieController = [[MPMoviePlayerViewController alloc] initWithContentURL];
		}
	}
}

- (IBAction)playVideoFile:(id)sender 
{ 
	[self initMovieController];

	[[NSNotificationCenter defaultCenter]
		addObserverForName:MPMoviePlayerPlaybackDidFinishNotification
		object:[movieController moviePlayer]
		queue:nil
		usingBlock:^(NSNotification *notif) 
		{
			[[NSNotificationCenter defaultCenter] removeObserver:self]; 

			// Restore UI
			[[movieController moviePlayer] setScalingMode:MPMovieScalingModeAspectFit];
			[[movieController view] removeFromSuperview];
			[[UIApplication sharedApplication] 
				setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];
			
			// Reset movie player
			[movieController release];
			movieController = nil;	
		}
	 ];
	
	[window addSubview:[movieController view]];
}	

@end