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];
}
Related
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.
I am downloading the video and saved it in some directory so user can play that file afterwards.
It works well in all the cases like when download stopped and resume again due to some network fluctation. But sometimes file downloaded completely but not playing in MPMoviePlayerViewController.
I am using ASIHTTPRequest to download video file in background.
Observation: May be while downloading, network fluctuates some times and file may be corrupted.
Question: How can I came to know that downloaded file is corrupted? (via MPMoviePlayerViewControll)
Any suggestions? Below is the code to play:
#ACB... I used your code, but it always going in else condition:
playerViewController = [[MPMoviePlayerViewController alloc]initWithContentURL:url];
player = [playerViewController moviePlayer];
player.movieSourceType = MPMovieSourceTypeFile;
[player prepareToPlay];
if(player.loadState == MPMovieLoadStatePlayable)
{
playerViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentMoviePlayerViewControllerAnimated:playerViewController];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerInterruptByUser:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:playerViewController.moviePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:playerViewController.moviePlayer];
//[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerFinished:) name:UIApplicationDidEnterBackgroundNotification object:playerViewController.moviePlayer];
[player play];
}
else
{
corruptVideoAlert = [[UIAlertView alloc]initWithTitle:NSLocalizedString(#"Corrupt Video", nil) message:NSLocalizedString(#"This video is corrupted due to some network error. We suggest you to download again. Do you want to download it again?", nil) delegate:self cancelButtonTitle:NSLocalizedString(#"NO", nil) otherButtonTitles:NSLocalizedString(#"YES", nil),nil];
[corruptVideoAlert show];
[corruptVideoAlert release];
}
I found the similar problem, and I resolve this issue. Try below code:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerPlayingErrorNotification:) name:MPMoviePlayerPlaybackDidFinishNotification object:playerViewController.moviePlayer];
-(void)playerPlayingErrorNotification:(NSNotification*)notif
{
NSNumber* reason = [[notif userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];
switch ([reason intValue]) {
case MPMovieFinishReasonPlaybackEnded:
NSLog(#"Playback Ended");
break;
case MPMovieFinishReasonPlaybackError:
NSLog(#"Playback Error");
[self performSelector:#selector(CorruptVideoAlertView) withObject:nil afterDelay:1.0];
break;
case MPMovieFinishReasonUserExited:
NSLog(#"User Exited");
break;
default:
break;
}
}
Accept it if it works for you too. Hav a nice day.
Use
if(moviePlayer.loadState == MPMovieLoadStatePlayable)
and check if it is playable. You can use MPMoviePlayerLoadStateDidChangeNotification if required. For more details check apple documentation.
You can try it like this,
playerViewController = [[MPMoviePlayerViewController alloc]initWithContentURL:url];
player = [playerViewController moviePlayer];
player.movieSourceType = MPMovieSourceTypeFile;
[player prepareToPlay];
playerViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentMoviePlayerViewControllerAnimated:playerViewController];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerInterruptByUser:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:playerViewController.moviePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:playerViewController.moviePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(loadStateChanged:) name:MPMoviePlayerLoadStateDidChangeNotification object:playerViewController.moviePlayer];
//[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerFinished:) name:UIApplicationDidEnterBackgroundNotification object:playerViewController.moviePlayer];
In loadStateChanged: method do the following,
if(player.loadState == MPMovieLoadStatePlayable)
{
[player play];
}
else
{
corruptVideoAlert = [[UIAlertView alloc]initWithTitle:NSLocalizedString(#"Corrupt Video", nil) message:NSLocalizedString(#"This video is corrupted due to some network error. We suggest you to download again. Do you want to download it again?", nil) delegate:self cancelButtonTitle:NSLocalizedString(#"NO", nil) otherButtonTitles:NSLocalizedString(#"YES", nil),nil];
[corruptVideoAlert show];
[corruptVideoAlert release];
}
For my scenario, I ended up recording the expected content length of the video coming in and made sure that the file size matches up when you try to play the video.
In this sense, it was faster for me to decide whether or not I should even play the video. If the file sizes didn't match, I just restart the download.
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];
}
Here's the code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *musicURL = [NSURL URLWithString:#"http://live-three2.dmd2.ch/buureradio/buureradio.m3u"];
if([musicURL scheme])
{
MPMoviePlayerController *mp = [[MPMoviePlayerController alloc] initWithContentURL:musicURL];
if (mp)
{
// save the music player object
self.musicPlayer = mp;
[mp release];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(popBack:) name:#"MPMoviePlayerDidExitFullscreenNotification" object:nil];
// Play the music!
[self.musicPlayer play];
}
}
}
-(void)popBack:(NSNotification *)note
{
[self.navigationController popToRootViewControllerAnimated:YES];
}
The selector method never gets called. I just want to pop back to the root menu when the "Done" button is pressed on the movie player. I put an NSLog in the selector to check if it was even being called, nothing. The music plays fine. Any thoughts?
This should work
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(popBack:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
Can't figure out why I'm not getting my callback - any advice?
-(void) playMovieWithURL:(NSURL *)url {
[currentVC.view removeFromSuperview];
MPMoviePlayerViewController *movieControl = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
//register for playback finished call
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(movieFinished:) name:MPMoviePlayerDidExitFullscreenNotification object:movieControl];
[self presentMoviePlayerViewControllerAnimated:movieControl];
}
-(void) movieFinished:(NSNotification *)aNotification {
NSLog(#"received callback that movie finished");
MPMoviePlayerController *movie = [aNotification object];
[movie.view removeFromSuperview];
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerDidExitFullscreenNotification object:movie];
[movie release];
[self.view addSubview:currentVC.view];
}
Wild guess, but maybe you want MPMoviePlayerPlaybackDidFinishNotification instead of MPMoviePlayerDidExitFullscreenNotification ?
MPMoviePlayerController posts notifications
MPMoviePlayerViewController does NOT post notifications
So I suppose I'll just switch over to using MPMoviePlayerControllers in this particular case.
I don't know much about the MP API, but you're registering for the notification in a reasonable way. Are you sure that MPMoviePlayerDidExitFullscreenNotification is the notification you want? That (by name alone) doesn't appear to be equivalent to "movie finished".