I'm building a very simple iOS iPad app that consists of a view and 4 buttons.
So basically you have in the Storyboard:
->ViewController
->View (this is added just for alignment and position sake, nothing else)
->View
->Button1
->Button2
->Button3
->Button4
When you press a button a movie will play in fullscreen mode, the same for all 4 buttons.
Once the movie is done, either because it finished or the user pressed "done",
[moviePlayer.view removeFromSuperview] is used to remove the movie, and everything returns to the initial state of the app, with the 4 buttons.
this is the code that plays the movie when a button is pressed
- (void) playMovie:(NSString *)fileName
ofType:(NSString *)fileType
{
NSString *filePath=[[NSBundle mainBundle] pathForResource:fileName ofType:fileType];
NSURL *fileUrl=[NSURL fileURLWithPath:filePath];
self.moviePlayer=[[MPMoviePlayerController alloc] initWithContentURL:fileUrl];
[self.view addSubview:self.moviePlayer.view];
self.moviePlayer.fullscreen = YES;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayBackDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:self.moviePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayBackDidFinish:)
name:MPMoviePlayerWillExitFullscreenNotification
object:self.moviePlayer];
[self.moviePlayer prepareToPlay];
[self.moviePlayer play];
}
And this is the code I use to stop and remove the movie:
- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
MPMoviePlayerController *player = [notification object];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:player];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerWillExitFullscreenNotification
object:player];
if ([player respondsToSelector:#selector(setFullscreen:animated:)])
{
[player stop];
[player setFullscreen:NO animated:YES];
[player.view removeFromSuperview];
}
}
The problem I encounter is this, after [player.view removeFromSuperview]; is performed, I return to the initial view, with no background image (it turns black) and no response from any of the buttons.
if I remove the view that contains the buttons, and add the buttons to the main view, it works as expected.
Sorry if this isn't too clear, I've been through books and lots of websites but don't seem to be able to get my head around this.
Cheers!
It looks like you are removing the "player" from the superview, but what you orginally added was a "movieplayer"...try removing that from the superview.
I changed the MPMoviePlayerController for a MPMoviePlayer*View*Controller and referenced the movie player within it, without using the setFullScreen and everything started working as expected.
So basically adding a view to the superview and the MPMoviePlayerController setFullScreen=YES are no good in this case.
Related
I want to play play video on current viewcontroller with full screen.
so i am using MPMoviePlayerViewController to play full screen video but problem is that it does not play smooth.
it lag little bit while playing video.
Here is My code which i am using to play full screen video which is store in my documents directory of phone/ipad
-(void)PlayVideo:(NSString *)videoFilePath
{
NSURL *videoURL = [NSURL fileURLWithPath:videoFilePath];
NSLog(#"videoURL: %#", videoURL);
MPMoviePlayerViewController *playerVC = [[MPMoviePlayerViewController alloc] initWithContentURL:videoURL];
// Remove the movie player view controller from the "playback did finish" notification observers
[[NSNotificationCenter defaultCenter] removeObserver:playerVC
name:MPMoviePlayerPlaybackDidFinishNotification
object:playerVC.moviePlayer];
// Register this class as an observer instead
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:playerVC.moviePlayer];
// Set the modal transition style of your choice
playerVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
// Present the movie player view controller
[self presentViewController:playerVC animated:NO completion:nil];
// Start playback
[playerVC.moviePlayer prepareToPlay];
[playerVC.moviePlayer play];
}
- (void)movieFinishedCallback:(NSNotification*)aNotification
{
// Obtain the reason why the movie playback finished
NSNumber *finishReason = [[aNotification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];
// Dismiss the view controller ONLY when the reason is not "playback ended"
if ([finishReason intValue] != MPMovieFinishReasonPlaybackEnded)
{
MPMoviePlayerController *moviePlayer = [aNotification object];
// Remove this class from the observers
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:moviePlayer];
// Dismiss the view controller
[self dismissViewControllerAnimated:YES completion:Nil];
}
}
Didn't find any issue with the code except in
[[NSNotificationCenter defaultCenter] removeObserver:playerVC
name:MPMoviePlayerPlaybackDidFinishNotification
object:playerVC.moviePlayer];
removeObserver should be self and not playerVC.
But ya, that will not cause any problem like you have mentioned.
I would suggest you try profiling your app, which might direct you to the code causing the issue. Probably there might be something that you are doing in the background when the video is playing which is causing it to lag.
I'm using a tabbarcontroller in which one of the views has an MPMoviePlayer. It works fine, except that if I change tab, the movie doesn't stop and keeps playing in the background. Then if I try to tab back to the movie tab, it crashes.
I think the only code I have to release the MPMoviePlayer is when it's finished playing, but I want it to be released when I change views instead. Then if I go back to the Movie tab, we start fresh.
In my .h file have set up as:
import < UIKit/UIKit.h>
import < MediaPlayer/MediaPlayer.h>
#interface SecondViewController : UIViewController {
MPMoviePlayerController *player;
}
#end
and in my .m file have:
- (void)viewDidLoad {
NSString *url = [[NSBundle mainBundle]
pathForResource:#"vid"
ofType:#"m4v"];
player = [[MPMoviePlayerController alloc]
initWithContentURL:[NSURL fileURLWithPath:url]];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(movieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:player];
//--called when the movie view and then add it to the View window--
player.view.frame = CGRectMake(10, 10, 300, 300);
[self.view addSubview:player.view];
//--play movie--
[player pause];
[super viewDidLoad];
}
//--called when the movie is done playing--
- (void) movieFinishedCallback:(NSNotification*) aNotification {
MPMoviePlayerController *moviePlayer = [aNotification object];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:moviePlayer];
[moviePlayer.view removeFromSuperview];
[player release];
}
Any suggestions? Thank you :)
If you really want to release MPMoviePlayer at tab switch, then do it in viewWillDisappear or viewDidDisappear. Now it's left alive at background, as you described. When you come back to tab, you try to create it again.
Difficult to say what would be the exact reason for crash, there seems to be several possibilities. Next time write a "Why did this crash" question with a call stack.
Maybe you could think about just pause/resume, so you wouldn't need to reallocate new moviePlayer every time user changes tabs? Do alloc/release in viewDidLoad and viewDidUnload, but play/pause in viewWillAppear and viewWillDisappear.
I am facing problem of memory leak and other MoviePlayer new initiation as my MoviePlayer doesn't respond to function, in which I am releasing that player on my done button.
(void) playMovieAtURL
{
MPMoviePlayerViewController *mpViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL URLWithString:videoURL]];
mpViewController.view.backgroundColor = [UIColor blackColor];
[self presentMoviePlayerViewControllerAnimated:mpViewController];
[mpViewController.view setCenter:self.view.center];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myMovieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:mpViewController];
}
// When the movie is done,release the controller. (Doesn't come in it.)
-(void)myMovieFinishedCallback:(NSNotification*)aNotification
{
MPMoviePlayerController* theMovie=[aNotification object];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:theMovie];
// Release the movie instance created in playMovieAtURL
[theMovie release];
}
Not sure it's your case, but this is what documentation says about MPMoviePlayerPlaybackDidFinishNotification:
This notification is not sent in cases
where the movie player is displaying
in fullscreen mode and the user taps
the Done button. In that instance, the
Done button causes movie playback to
pause while the player transitions out
of fullscreen mode. If you want to
detect this scenario in your code, you
should monitor other notifications
such as
MPMoviePlayerDidExitFullscreenNotification.
It seems that MPMoviePlayerPlaybackDidFinishNotification is called just when the movie stops by itself.
If you are using the Done button, you should use MPMoviePlayerDidExitFullscreenNotification instead.
I tried to solve it by passing nil and now it is returning me callbacks but still the movie won't releases, I will try ur suggestion also. Anyways my new code
-(void) playMovieAtURL
{
MPMoviePlayerViewController *mpViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL URLWithString:videoURL]];
mpViewController.view.backgroundColor = [UIColor blackColor];
[self presentMoviePlayerViewControllerAnimated:mpViewController];
[mpViewController.view setCenter:self.view.center];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myMovieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
}
// When the movie is done,release the controller.
-(void)myMovieFinishedCallback:(NSNotification*)aNotification
{
MPMoviePlayerController* theMovie=[aNotification object];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
// Release the movie instance created in playMovieAtURL
[theMovie release];
}
I'm having lots of trouble displaying a fullscreen movie in my iPad app and then allowing the user to dismiss it with either the Done button or the "un-fullscreen" button on the player controls.
Initially I was using MPMoviePlayerViewController for the movie presentation, but I wasn't receiving the enter/exit fullscreen notifications from its MPMoviePlayerController object, so I switched to doing it myself.
I can make the movie appear fullscreen (although the transition is janky), but when either the "Done" or "un-fullscreen" buttons are pressed, no action is taken by the player. I've posted my code below:
- (void)startPlayingMovieWithURLString:(NSString *)movieURLString {
// I get all of these callbacks **EXCEPT** the "willExitFullScreen:" callback.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(willEnterFullScreen:) name:MPMoviePlayerWillEnterFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(willExitFullScreen:) name:MPMoviePlayerWillExitFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didFinishPlayback:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
[self.moviePlayerController setContentURL:someExistingURL];
// "self" is a UIViewController subclass, and is presented as a "fullscreen" modal view controller from its parent
// I'm setting the movie player's view's frame to take up the full rectangle of my view controller, but really I want the movie to be completely removed when the user presses "done" (that is, removed from the view hierarchy). Not sure when/where to do this.
self.moviePlayerController.view.frame = self.view.frame;
[self.view addSubview:self.moviePlayerController.view];
[self.moviePlayerController setFullscreen:YES animated:YES];
}
And here is the code for my didFinish callback
- (void)didFinishPlayback:(NSNotification *)notification {
// This ends up recursively telling the player that playback ended, thus calling this method, thus…well you get the picture.
// What I'm trying to do here is just make the player go away and show my old UI again.
[self.moviePlayerController setFullscreen:NO animated:YES];
}
So obviously I am doing something wrong but I've been up and down the documentation and I can't figure out how to make the movie just go away. I figured it would be more intuitive than this. What am I doing wrong?
Here are how the events -> notifications work:
User presses 'Done' button
MPMoviePlayerWillExitFullscreenNotification
MPMoviePlayerDidExitFullscreenNotification
User presses 'Leave fullscreen' button on transport
MPMoviePlayerWillExitFullscreenNotification
MPMoviePlayerDidExitFullscreenNotification
Note that playback does not stop
Movie reaches end
MPMoviePlayerPlaybackDidFinishNotification with the MPMoviePlayerPlaybackDidFinishReasonUserInfoKey set to MPMovieFinishReasonPlaybackEnded
If you call setFullscreen:NO animated:YES on your MoviePlayerController instance from this notification, you'll then get the WillExit and DidExit notifications.
Note that you don't get the PlaybackDidFinish notification when the user presses the Done or Leave Fullscreen buttons.
So, typically, if you want to get rid of the MoviePlayer's view, you need to put [self.moviePlayerController.view removeFromSuperview] in the DidExitFullscreen notification handler. WillExitFullscreen is too soon.
Here's my code:
- (void)willEnterFullscreen:(NSNotification*)notification {
NSLog(#"willEnterFullscreen");
}
- (void)enteredFullscreen:(NSNotification*)notification {
NSLog(#"enteredFullscreen");
}
- (void)willExitFullscreen:(NSNotification*)notification {
NSLog(#"willExitFullscreen");
}
- (void)exitedFullscreen:(NSNotification*)notification {
NSLog(#"exitedFullscreen");
[self.movieController.view removeFromSuperview];
self.movieController = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)playbackFinished:(NSNotification*)notification {
NSNumber* reason = [[notification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];
switch ([reason intValue]) {
case MPMovieFinishReasonPlaybackEnded:
NSLog(#"playbackFinished. Reason: Playback Ended");
break;
case MPMovieFinishReasonPlaybackError:
NSLog(#"playbackFinished. Reason: Playback Error");
break;
case MPMovieFinishReasonUserExited:
NSLog(#"playbackFinished. Reason: User Exited");
break;
default:
break;
}
[self.movieController setFullscreen:NO animated:YES];
}
- (void)showMovie {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(willEnterFullscreen:) name:MPMoviePlayerWillEnterFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(willExitFullscreen:) name:MPMoviePlayerWillExitFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(enteredFullscreen:) name:MPMoviePlayerDidEnterFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(exitedFullscreen:) name:MPMoviePlayerDidExitFullscreenNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playbackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
NSURL* movieURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"tron" ofType:#"mov"]];
self.movieController = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
self.movieController.view.frame = self.view.frame;
[self.view addSubview:movieController.view];
[self.movieController setFullscreen:YES animated:YES];
[self.movieController play];
}
Yes. That's great. There are really notifications mentioned above...
However, there are no MPMoviePlayerPlaybackWillFinishNotification somewhy!!!
That's really a problem.
When you call the movie player as modal (no matter which of the following methods used presentViewController/presentModalViewController/presentVideoController), if you defined .fullScreen = YES, it's not expected to call MPMoviePlayerWillExitFullscreenNotification notification at all (obviously, because it's not cosidering we enter/exit from full screen, but only present/dismiss the controller).
But there are really no any notifications that the video is about to finish and close. That's needed (besides any other situations possible) to catch the moment when the transition of dismissing is started. (The transition, of course, starts before the MPMoviePlayerPlaybackDidFinishNotification called). And, at the same time, application:supportedInterfaceOrientationsForWindow: for previously shown controller is called before the notification, and there is no way to say the AppDelegate that our current controller must be shown already in another orientation.
So, since my video is fullscreen and also without any controls shown (this is kind of an intro, so I just until it finishes) my solution was just to have a timer which checks every short tick (0.1 seconds) what is the video current position... and it it's close to the end, then this is the moment for my own notification.
I have a couple of views that access the movie player. I've put the following code in a method in AppDelegate for these views. They send in the filename to play. The code works fine but I know a release is required somewhere. If I add the last line as a release or autorelease, the app will crash once the user presses done on the movieplayer.
MPMoviePlayerController *moviePlayer = [[MPMoviePlayerController alloc]
initWithContentURL:[NSURL fileURLWithPath:moviePath]];
moviePlayer.movieControlMode = MPMovieControlModeDefault;
[moviePlayer play];
//[moviePlayer release];
I get this error:
objc[51051]: FREED(id): message videoViewController sent to freed object=0x1069b30
Program received signal: “EXC_BAD_INSTRUCTION”.
How should I be releasing the player?
What I've found is that the MPMoviePlayerController has to be sent the stop message before you can safely release it. So I do it in handlePlaybackEnd - first I stop it, then I autorelease it. Calling release doesn't seem to work too well:
- (void) moviePlayBackDidFinish : (NSNotification *) notification
{
VideoPlayerController * player = notification.object;
[player stop];
[player autorelease];
}
The whole thing becomes a bit trickier in that the MPMoviePlayerPlaybackDidFinishNotification can get sent more than once, but calling stop/autorlease twice won't do you any good either. So you need to guard against that somehow.
Lastly, it seems to take a few iterations of the main run loop until you can safely create a new MPMoviePlayerController instance. If you do it too quickly, you'll get sound but no video. Great fun, huh?
To answer 4thSpace's comment on the answer above, you can remove the notification observer so you don't receive it multiple times:
- (void)moviePlayBackDidFinish:(NSNotification *)notification {
MPMoviePlayerController *theMovie = [notification object];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:theMovie];
[theMovie stop];
[theMovie release];
}
for iphone os 3.2
you need to call
[moviePlayer pause];
before calling
[moviePlayer stop];
Stopping and releasing was not enough for me if the player did not reach to its end.
My solution is setting the moviePlayer.initialPlaybackTime = -1
at the moviePlayBackDidFinish: before releasing it:
-(void)playMovie: (NSString *)urlString{
movieURL = [NSURL URLWithString:urlString];
moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
moviePlayer.initialPlaybackTime = 0;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayBackDidFinish: ) name:MPMoviePlayerPlaybackDidFinishNotification object:moviePlayer] ;
moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
moviePlayer.movieControlMode = MPMovieControlModeDefault;
moviePlayer.backgroundColor = [UIColor blackColor];
[moviePlayer play];
}
-(void)moviePlayBackDidFinish: (NSNotification*)notification{
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:moviePlayer] ;
moviePlayer.initialPlaybackTime = -1;
[moviePlayer stop];
[moviePlayer release];
}
I had the same problem and I just realized I set the notification method with object:nil (it was a copy paste).
I was having multiple notifications although I shouldn't have had any notifications at all.
Here is my new notification set up code that fixed all (see the object:moviePlayer):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlaybackDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:moviePlayer];
Hope that helps. Now all my code is working properly.
This seemed to reduce the memory significantly. However for IOS 4.1 it seems fine.
- (void)videoFinishedCallback:(NSNotification *)aNotification
{
thePlayer = [aNotification object];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification object:thePlayer];
thePlayer.initialPlaybackTime = -1;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 30200
[thePlayer pause];
#endif
[thePlayer stop];
[thePlayer release];
}