MPMoviePlayerController causing leak - ios

I am developing a very video heavy iPad only iOS app which uses ARC but it appears that I have a leak when I try using MPMoviePlayerController, instruments throws a memory leak on the line of code that allocates the memory for the video player object, any ideas? also the clean up of the video player doesn't seem to be happening when the videos completes playback.
Any help would be very much appreciated, been looking everywhere for an answer for this as you can tell the problem is very much an show stopper with the nature of the application.
the code:
#interface ViewController ()
#property(nonatomic,strong) MPMoviePlayerController * vidPlayer;
#end
#implementation ViewController
#synthesize vidPlayer;
- (void)viewDidLoad
{
#autoreleasepool {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self playVideoForFile:#"01_intro"];
}
}
-(void)playVideoForFile:(NSString*)p_fileName
{
NSString *path = [[NSBundle mainBundle] pathForResource:p_fileName ofType:#"mp4"];
NSURL *tempURI = [NSURL fileURLWithPath:path];
vidPlayer = [[MPMoviePlayerController alloc] initWithContentURL:tempURI];
[vidPlayer setControlStyle:MPMovieControlStyleNone];
[vidPlayer setAllowsAirPlay:NO];
[vidPlayer.view setFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.height,[[UIScreen mainScreen] bounds].size.width)];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(vidFinishedCallback:) name:MPMoviePlayerPlaybackDidFinishNotification object:vidPlayer];
[vidPlayer play];
[self.view addSubview:vidPlayer.view];
}
-(void)vidFinishedCallback:(NSNotification*)aNotification
{
[vidPlayer pause];
vidPlayer.initialPlaybackTime = -1;
[vidPlayer stop];
vidPlayer.initialPlaybackTime = -1;
[vidPlayer.view removeFromSuperview];
vidPlayer = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:vidPlayer];
}

I tried
MPMoviePlayerController *movieController = [notification object];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:movieController];
movieController = nil;
and this seems to work.

This is a hunch, but try calling removeObserver before vidPlayer is destroyed. The docs specify that you should "Be sure to invoke removeObserver: or removeObserver:name:object: before notificationObserver or any object specified in addObserver:selector:name:object: is deallocated." --NSNotification Center
Also, you may try removing the explicit autoreleasepool in viewDidLoad. It should not be necessary if it's just one vidPlayer per view, and there have been some recent issues discovered with ARC and autoreleasepools. See this bugfix

I had this problem and it was sending me grey! Every time a new video was instantiated, it was never released (using ARC) and as more instances of the viewController were created it eventually caused a clunky crash.
I too checked all the MPMoviePlayer calls ensuring it had stopped and set to nil.
The problem wasn't in the allocation and release of the MPMoviePlayerController, but in the delegate that was used to pass the view controller details.
In the child view controller, I had a delegate to check for the passed model data from a tableViewController:
#property (strong, nonatomic) id<MyViewControllerDelegate> delegate;
The assignment as a strong pointer caused a huge memory leak. By assigning this as 'weak' it cleared the problem up.
#property (weak, nonatomic) id<MyViewControllerDelegate> delegate;
As a general rule, for view controllers delegates, assign them as a weak property pointer.
Good luck. Hope this clears the leak up!

Related

Starting video playback from reuseable TableViewCell object

I have a TableView setup in the storyboard and have a prototype cell that I use to hold images and a button to start videos. The path of the video file is a property in each cell object and I would like to play the video when the button is clicked.
Before I began using the table (when I just had button manually drawn on the storyboard) i used the follow to start the movie player [self presentMoviePlayerViewControllerAnimated:mp]; but obviously now that's not possible.
I'm sure I'm missing something obvious...
The best solution for this is NSNotificationCenter. On Click, in your Cell just send notification with index of your row that you can save in button's tag variable.
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:#"playVideo" forKey:#"operationKey"];
[userInfo setValue: indexPath forKey:#"indexPathRow"];
[[NSNotificationCenter defaultCenter] postNotificationName: #"PlayVideoNotification" object:nil userInfo:userInfo];
In your ViewController register observer:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivePlayVideoNotification:) name:#"PlayVideoNotification" object:nil];
And do not forget unregister observer:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"PlayVideoNotification" object:nil];
In the method PlayVideoNotification do what you need:
- (void) receivePlayVideoNotification:(NSNotification *) notification
{
NSDictionary *userInfo = notification.userInfo;
NSString *operationKey = [userInfo objectForKey:#"operationKey"];//"playVideo"
NSString* indexPathRow = [userInfo objectForKey:#"indexPathRow"];
//todo
}
I think you should use custom cell to achieve your objective.
You can follow these steps:
Create a new class that is a subclass of UITableViewCell and assign it as the class of your custom cell.
In your custom class .h file make an IBOutlet for your button.
In your main class .m file use this method to play video
- (void)playBtnClicked:(id)sender {
MPMoviePlayerViewController *movieController = [[MPMoviePlayerViewController alloc] initWithContentURL:YourURL];
[self presentMoviePlayerViewControllerAnimated:movieController];
[movieController.moviePlayer play];
}
Use this code after you have configured your cell
[cell.btn_PlayVideo addTarget:self action:#selector(playBtnClicked:) forControlEvents:(UIControlEventTouchUpInside)];
Your should make sure that MediaPlayer.framework is included and MediaPlayer.h file is imported.
As far as I know you have to segue to another ViewController and start the moviePlayerViewController there.
I think you can't just get the IBAction when you click on a TableViewCell.
So try to segue in the InterFaceBuilder, build a ViewController with something like this:
ViewController.h:
#interface ViewController : UIViewController
#property (nonatomic, strong) NSURL *videoFileUrl;
#end
ViewController.m:
#implementation ViewController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
...
[self presentMoviePlayerViewControllerAnimated:mp];
}
#end

MPMoviePlayerController spams 'Received memory warning'

I'm adding a MPMoviePlayerController as background view and while it plays I get spams of the log-message 'Received memory warning'. I don't now why, and maybe there is a workaround or a better solution.
Here is my code:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[self navigationController]setNavigationBarHidden:YES animated:YES];
[moviePlayer play];
}
- (void) viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[moviePlayer pause];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
//self.view.backgroundColor = [UIColor appStyleLightOrangeColor];
//Add Video playback
NSBundle *bundle = [NSBundle mainBundle];
NSString *moviePath = [bundle pathForResource:#"happy-female-friends-smartphon" ofType:#"m4v"];
NSURL *movieURL = [NSURL fileURLWithPath:moviePath];
moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
moviePlayer.controlStyle = MPMovieControlStyleNone;
moviePlayer.shouldAutoplay = YES;
moviePlayer.repeatMode = MPMovieRepeatModeOne;
moviePlayer.fullscreen = YES;
moviePlayer.movieSourceType = MPMovieSourceTypeFile;
moviePlayer.scalingMode = MPMovieScalingModeAspectFill;
//set the frame of movie player
moviePlayer.view.frame = self.view.bounds;
[self.view insertSubview:moviePlayer.view atIndex:0];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(appBecameActive)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[self performSelector:#selector(animationCode) withObject:nil afterDelay:0.1f];
}
-(void)appBecameActive{
[moviePlayer play];
}
The log says it all. You are receiving a printed warning that you are using too much memory and your app will shutdown if you do not free up space. You need to take immediate steps to lower your memory line, so don't just shrug off those warnings. Yeah, your app might not crash immediately, but it is often a sign of a much bigger problem in your code setup.
Run the Allocations Instrument in XCode to see where the bulk of your memory is being used. I'd first check the size of that m4v video also. You should be streaming the video if it is a significant size. Additionally, ensure that you are not leaking memory using the Leaks instrument. But once again, when you receive the didReceiveMemoryWarning callback, take immediate action. Either pick up the notification in the AppDelegate or subscribe to the UIApplicationDidReceiveMemoryWarningNotification and release items/viewControllers that can be recreated later.
Here's the Memory Management Guide if you want to consult.
Thannks first to #tdevoy for your advices.
I finally got rid of the warning. It was the file type! I had to convert it to .3gpand now it works much smoother and without warnings.
On strange thing is I even use now 4mb more memory than before. But everything works great..

Why is my NSNotificationObserver deallocated while a message runs on it?

I have a situation in which it can happen, that the last strong reference to an observer is removed while the observer processes an incoming notification.
That leads to the observer being deallocated immediately. I would normally expect, that a currently running method can finish before an object is deallocated. And this is what happens during normal message dispatch.
A simplified version of the code:
TKLAppDelegate.h:
#import <UIKit/UIKit.h>
#import "TKLNotificationObserver.h"
#interface TKLAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) TKLNotificationObserver *observer;
#end
TKLAppDelegate.m:
#import "TKLAppDelegate.h"
#implementation TKLAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Create an observer and hold a strong reference to it in a property
self.observer = [[TKLNotificationObserver alloc] init];
// During the processing of this notification the observer will remove the only strong reference
// to it and will immediatly be dealloced, before ending processing.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotificationName" object:nil];
// Create an observer and hold a strong reference to it in a property
self.observer = [[TKLNotificationObserver alloc] init];
// During the manual calling of the same method the observer will not be dealloced, because ARC still
// holds a strong reference to the message reciever.
[self.observer notificationRecieved:nil];
return YES;
}
#end
TKLNotificationObserver.m:
#import "TKLNotificationObserver.h"
#import "TKLAppDelegate.h"
#implementation TKLNotificationObserver
- (id)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationRecieved:) name:#"NotificationName" object:nil];
}
return self;
}
- (void)notificationRecieved:(NSNotification *)notification {
[self doRemoveTheOnlyStrongReferenceOfThisObserver];
NSLog(#"returing from notification Observer");
}
- (void)doRemoveTheOnlyStrongReferenceOfThisObserver {
TKLAppDelegate * delegate = [[UIApplication sharedApplication] delegate];
delegate.observer = nil;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"NotificationName" object:nil];
NSLog(#"dealloc was called");
}
#end
Using the App Delegate in this way is no good style and only done for demonstration purposes, the real code does not involve the app delegate.
The output is:
dealloc was called
returing from notification Observer
returing from notification Observer
dealloc was called
That is in the first case dealloc is called before the notification processing finished. In the second case it behaves as I expected.
If I keep a strong reference to self inside notificationReceived the dealloc only happens after the processing. My expectation was, that ARC, the runtime or whoever else keeps this strong reference for me.
What is wrong with my code?
Or is something wrong with my expectation?
Is there any Apple- or Clang-provided documentation on this?
My expectation was, that ARC, the runtime or whoever else keeps this
strong reference for me.
That is not the case, as documented in the Clang/ARC documentation:
The self parameter variable of an Objective-C method is never actually
retained by the implementation. It is undefined behavior, or at least
dangerous, to cause an object to be deallocated during a message send
to that object.
Therefore, if calling doRemoveTheOnlyStrongReferenceOfThisObserver
can have the side-effect of releasing self, you would have to use
an temporary strong reference to avoid deallocation:
- (void)notificationRecieved:(NSNotification *)notification {
typeof(self) myself = self;
[self doRemoveTheOnlyStrongReferenceOfThisObserver];
NSLog(#"returing from notification Observer");
}
A better solution would probably to avoid this side-effect.
the first dealloc probably happens as you set the observer property of the appDelegate twice and therefore the first instance is dealloced as soon as you set it the second time

multiple instances of the same sound on iOS

i'm making a bubble popping game and hit a bit of a snag :/
i have a sound (pop.mp3), this sound needs to play EVERY time one of the bubbles are tapped
all the bubbles are buttons that call the same method (-(IBACTION)bubblePop)
i initialised an AVAudioPlayer in the Viewdidload method
and call it's play function inside the bubblePop method
this clearly did not work however because the bubble's pop sound only plays one at a time, it doest overlap like i would expect it to
does anybody know how i can resolve this?
Extra info
if i initialise the audio player inside bubblePop i get no sound at all
You should initialize AVPlayer inside bubblePop and keep strong reference to it.
Try this:
#interface ViewController()
#property (strong, nonatomic) NSMutableDictionary *players;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.players = [[NSMutableDictionary alloc] init];
}
- (void)bubblePop {
NSURL *URL = your sound URL;
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:URL];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
self.players[playerItem] = player;
[player play];
}
-(void)itemDidFinishPlaying:(AVPlayerItem *)sender {
[self.players removeObjectForKey:sender];
}
#end

iOS: Reset audioplayer when the app is no longer in the foreground

I'm playing an audio file in the background when the app is started. The function is called in viewDidLoad.
-(void)playsong
{
NSString *songUrl = [[NSBundle mainBundle] pathForResource:#"chant" ofType:#"m4a"];
NSData *songFile = [NSData dataWithContentsOfFile:songUrl];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithData:songFile error:&error ];
[audioPlayer play];
}
When the user presses the home button, and the song's current position is 10 secs, the song will stop playing.
But when the user opens the app again, the song starts from the same position.
I'd like it to be started from the beginning every time the app is opened.
Plus for memory reasons wouldn't it be better to deallocate the memory?
I tried to call the function from
- (void)viewWillAppear:(BOOL)animated
and then set it to nil in
- (void)viewWillDisappear:(BOOL)animated
but the method - (void)viewWillDisappear:(BOOL)animated isn't getting called.
If viewWillDisappear isn't working for you, try adding an observer in NSNotification center, which calls a method when the application didEnterBackground. Like so:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(enteredBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
Then add your code into this method:
-(void)enteredBackground:(NSNotification *)notification {
}
Hope this helps!
UPDATE
Okay, so from where you want to acces the audio object in the about us view, try this:
HomeViewController *HomeVC = [HomeViewController alloc] init];
HomeVC.audioPlayer...//then do what you want with it
As long as you have declared the audio object in the home VC then this should work.
Make sure you have imported the homeview controller in one of the about us view controller, files as well.

Resources