I have an app that presents images and/or movies in sequence. The problem is, I cannot dismiss one movie player and then present another. If I try, I get the message "Warning: Attempt to present MPMoviePlayerViewController on MyViewController while a presentation is in progress!" Unlike other animated present/dismiss methods, there is no completion handler, nor a non-animated version of present/dismiss.
Here is a simplified version of my code:
-(void) play
{
[[window rootViewController] presentMoviePlayerViewControllerAnimated:player];
[[_player moviePlayer] play];
}
-(void) videoNotification:(NSNotification *) notification
{
if([notification.name isEqualToString:MPMoviePlayerPlaybackDidFinishNotification])
{
[[window rootViewController] dismissMoviePlayerViewControllerAnimated];
[_canvasManager showNextCanvas]; //this calls play on the next canvas
}
}
Any thoughts/hints on how to achieve my goal?
After looking at dismissViewControllerAnimated:completion:, I was able to create a completion handler by creating a subclass of MPMoviePlayerViewController and overriding -(void)viewDidDisappear. viewDidDisappear gets called at the end of dismissMoviePlayerViewControllerAnimated.
#interface MyMoviePlayerViewController : MPMoviePlayerViewController
{
void (^_viewDidDisappearCallback)(void);
}
- (void)setViewDidDisappearCallback:(void (^)(void))callback;
#end
#implementation MyMoviePlayerViewController
- (id) initWithContentURL:videoPath
{
self = [super initWithContentURL:videoPath];
_viewDidDisappearCallback = nil;
return self;
}
- (void)setViewDidDisappearCallback:(void (^)(void))callback
{
_viewDidDisappearCallback = [callback copy];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if(_viewDidDisappearCallback != nil)
_viewDidDisappearCallback();
}
#end
Related
I am building an app and the scenario is something like this:
I have 3 view controllers VC1,VC2,VC3.
scenario1
I have an AVPlayer on VC2 that uses a "NSURL" saved in userDefaults to play a video.Let us say that the key to this url is "videoURL".Now if I pop VC2 and go back to VC1,dealloc() gets called and everything gets deallocated and the video stops playing.So,if I change the "videoURL" on VC1 and then push VC2 onto the navigation stack,I get a new AVPlayer with the previous one being deallocated.No problems till now.
scenario2
If I change "videoURL" on VC2,I get a new playerItem using "replaceCurrentItemWithPlayerItem" to play a new video.No problems here as well.
scenario3
Now if I push VC3 onto the stack,I don't know how to play a new URL if I pop back to VC3.The video doesn't stop playing when I push VC3,even if I set AVPlayer to nil before pushing VC3.And when I do stop the video and push VC3,I am facing memory issues.
What is the correct way to implement scenario3?
You're going to have to add logic to view controller 2's viewWillDisappear method to stop and release the AVPlayer, and move the logic that creates the AVPlayer and starts it playing to viewWillAppear (so that when you pop view controller 3 and go back to view controller 2 you can play view controller 2's video again.)
How you do that depends on how you're managing the AVPlayer. Post your VC2 code that sets up the AVPlayer and installs its playback layer in VC2's layer hierarchy. (Edit your original question to include that information. Don't post code in comments. It's pretty much unreadable.)
Here is what I am doing for the scenario3:
-(void)goToNextPage
{
//This stops the video....
[player replaceCurrentItemWithPlayerItem:nil];
}
Now I am creating a delegate.So now VC2 acts as a delegate of VC3.So when I am on VC3,I write a code to generate a new Url,and pass that to a property in VC2.
So I came to a conclusion that I don't need to deallocate AVPlayer in VC2 as long as VC2 is loaded in memory.Also,the observers associated with it do not need to be removed as well.
Here is the complete demo of AVPlayer and its observers.I'm sure it will help each and everyone who is getting started with AVFoundation and observers.
//VC1
-(IBAction)gotoPlayer:(id)sender
{
PlayerViewController *playerVC = [self.storyboard instantiateViewControllerWithIdentifier:#"PlayerViewController"];
[self.navigationController pushViewController:playerVC animated:YES];
}
//VC2.h
#interface PlayerViewController : UIViewController<ChangeUrlDelegate>
#property (nonatomic,strong) NSURL *url;
-(void)playWithURL:(NSURL*)url_;
#end
//VC2.m
-(void)viewDidLoad
{
[self setPlayerDataOnPageArrival];
}
-(void)viewDidAppear:(BOOL)animated
{
if(self.url)
[self playWithURL:self.url];
else{
NSString *path = [[NSBundle mainBundle]pathForResource:#"sample_mpeg4" ofType:#"mp4"];
PlayerSource *source_ = [[PlayerSource alloc]initWithURL:[[NSURL alloc] initFileURLWithPath:path]];
[self playWithURL:source_.sourceURL];
}
}
-(void)dealloc
{
NSLog(#"In playerViewController dealloc");
[playerItem_ removeObserver:self forKeyPath:#"status"];
[player_ removeTimeObserver:self.timeObserver];
[[NSNotificationCenter defaultCenter]removeObserver:self name:self.itemEndObserver object:playerItem_];
}
#pragma mark ChangeURLViewController delegate methods
-(void)newURL:(NSURL *)url_
{
self.url = url_;
}
#pragma mark player methods
-(void)setPlayerDataOnPageArrival{
NSString *path = [[NSBundle mainBundle]pathForResource:#"sample_mpeg4" ofType:#"mp4"];
PlayerSource *source_ = [[PlayerSource alloc]initWithURL:[[NSURL alloc] initFileURLWithPath:path]];
self.url = source_.sourceURL;
asset_ = [AVAsset assetWithURL:self.url];
playerItem_ = [AVPlayerItem playerItemWithAsset:asset_];
NSLog(#"observer added");
[playerItem_ addObserver:self forKeyPath:#"status" options:0 context:nil];
player_ = [AVPlayer playerWithPlayerItem:playerItem_];
playerLayer = [AVPlayerLayer playerLayerWithPlayer:player_];
}
-(void)playWithURL:(NSURL*)url_{
[playerLayer setFrame:self.playerView.frame];
if(self.playerView.layer.sublayers.count == 0)
[self.playerView.layer addSublayer:playerLayer];
else{
AVPlayerItem *newPlayerItem_ = [AVPlayerItem playerItemWithURL:url_];
[player_ replaceCurrentItemWithPlayerItem:newPlayerItem_];
NSLog(#"rate--->%f",player_.rate);
if(player_.rate == 0 && player_.status == 1)
player_.rate = 1;
}
}
-(void)addPlayerItemTimeObserver{
CMTime interval = CMTimeMakeWithSeconds(0.01,NSEC_PER_SEC);
// __weak PlayerViewController *weakSelf = self;
self.timeObserver = [player_ addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
//NSLog(#"in addPlayerItemTimeObserver");
CGFloat currentTime = CMTimeGetSeconds(time);
NSLog(#"currentTime--->%f",currentTime);
}];
}
-(void)addItemEndObserverForPlayerItem{
//When playback completes,an AVPlayerItem posts a notification called "AVPlayerItemDidPlayToEndTimeNotification"
self.itemEndObserver = AVPlayerItemDidPlayToEndTimeNotification;
[[NSNotificationCenter defaultCenter]addObserverForName:self.itemEndObserver object:nil queue:NULL usingBlock:^(NSNotification * _Nonnull note) {
NSLog(#"in item end observer");
}];
}
#pragma mark player status observer
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
NSLog(#"In observer");
if (playerItem_.status == AVPlayerItemStatusReadyToPlay) {
NSLog(#"success");
[self addPlayerItemTimeObserver];
[self addItemEndObserverForPlayerItem];
[player_ play];
}
else if(player_.status == AVPlayerItemStatusFailed){
NSLog(#"failed");
}
else if(player_.status == AVPlayerItemStatusUnknown){
NSLog(#"Unknown");
}
}
#pragma mark action methods
-(IBAction)goToNextPage:(id)sender{
[player_ replaceCurrentItemWithPlayerItem:nil];
ChangeURLViewController *changeUrlVC_ = [self.storyboard instantiateViewControllerWithIdentifier:#"ChangeURLViewController"];
changeUrlVC_.delegate=self;
self.url = nil;
[self.navigationController pushViewController:changeUrlVC_ animated:YES];
}
-(IBAction)changeURL:(id)sender{
NSString *path = [[NSBundle mainBundle]pathForResource:#"sample_mpeg4" ofType:#"mp4"];
PlayerSource *source_ = [[PlayerSource alloc]initWithURL:[[NSURL alloc] initFileURLWithPath:path]];
[self playWithURL:source_.sourceURL];
}
//VC3.h
#protocol ChangeUrlDelegate <NSObject>
-(void)newURL:(NSURL*)url_;
#end
#interface ChangeURLViewController : UIViewController
#property (weak,nonatomic) id <ChangeUrlDelegate> delegate;
#end
//VC3.m
-(void)dealloc{
NSLog(#"In changeURL dealloc");
}
-(IBAction)changeURLAction:(id)sender{
NSString *path = [[NSBundle mainBundle]pathForResource:#"sample_mpeg4" ofType:#"mp4"];
PlayerSource *source_ = [[PlayerSource alloc]initWithURL:[[NSURL alloc] initFileURLWithPath:path]];
NSLog(#"delegate---->%#",self.delegate);
[self.delegate newURL:source_.sourceURL];
[self.navigationController popViewControllerAnimated:YES];
}
NOTE:Note that you should use dealloc() in every view controller and it should get called overtime that view controller is removed from memory(popped off navigation stack).
My task is to create custom UIAlertView with multiline text input field and everything works pretty much well up to the point where I need to do some actions if dismiss button was tapped. As example I'm using: http://iphonedevelopment.blogspot.co.uk/2010/05/custom-alert-views.html
I have created very simple popup for testing purpose. Basically it's UIViewController xib with single button in it. The main class is demonstrated below:
#import "testViewController.h"
#interface testViewController ()
#end
#implementation testViewController
- (id)init
{
self = [super initWithNibName:#"testViewController" bundle:nil];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)showInView:(UIView *)view
{
[view addSubview:[self view]];
[[self view] setFrame: [view bounds]];
[[self view] setCenter:[view center]];
}
- (IBAction)onTap:(id)sender {
NSLog(#"All OK");
}
#end
then in root UIViewController I'm calling my custom alert:
- (IBAction)showAlert:(id)sender
{
dispatch_async(dispatch_get_main_queue(), ^{
testViewController *alert = [[testViewController alloc] init];
[alert showInView:[self view]];
});
}
So far I have tried Main thread or global queue or even synchronous dispatch, but everything ends up with break on:
int main(int argc, char * argv[]) {
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); <-- Thread 1:EXC_BAD_ACCESS (code=1, address=0xa000000c)
}
}
tried to add observer for the button but still no go..
Any help is much appreciated
Here what happing is "if you want to add one viewcontroller view to other viewcontroller's view then after adding of views you should convey to the compiler that second view controller is going to be child to the first"
i.e pre intimation of parent to child relationship has to be done.
now just modify your showAlert method with the following . it is working 100%.
- (IBAction)showAlert:(id)sender {
dispatch_async(dispatch_get_main_queue(), ^{
testViewController *alert = [[testViewController alloc] init];
[alert showInView:[self view]];
[self addChildViewController:alert];
[alert didMoveToParentViewController:self];
});
}
Check if your UIButton is assigned multiple IBoutlets or multiple IBActions. This a common issue which happens without our notice sometimes.
It turns out that my testViewController was released by ARC before I tap the button
I've built a custom UITabBarController with Storyboards/Segues and UIViewController containment. Here is a link to it: https://github.com/mhaddl/MHCustomTabBarController
The UIViewControllers which will be presented by the Container are stored in a NSMutableDictionary (keys are the segues' identifiers). Everything is working fine until the point is reached where I come back to a earlier presented ViewController. At this moment "dealloc" gets called on this ViewController before it is presented.
How can I prevent "dealloc" from getting called so it can be used to unsubscribe from Notifications, and nil delegates.
MHCustomTabBarController:
#implementation MHCustomTabBarController {
NSMutableDictionary *_viewControllersByIdentifier;
}
- (void)viewDidLoad {
[super viewDidLoad];
_viewControllersByIdentifier = [NSMutableDictionary dictionary];
}
-(void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.childViewControllers.count < 1) {
[self performSegueWithIdentifier:#"viewController1" sender:[self.buttons objectAtIndex:0]];
}
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
self.destinationViewController.view.frame = self.container.bounds;
}
#pragma mark - Segue
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if (![segue isKindOfClass:[MHTabBarSegue class]]) {
[super prepareForSegue:segue sender:sender];
return;
}
self.oldViewController = self.destinationViewController;
//if view controller isn't already contained in the viewControllers-Dictionary
if (![_viewControllersByIdentifier objectForKey:segue.identifier]) {
[_viewControllersByIdentifier setObject:segue.destinationViewController forKey:segue.identifier];
}
for (UIButton *aButton in self.buttons) {
[aButton setSelected:NO];
}
UIButton *button = (UIButton *)sender;
[button setSelected:YES];
self.destinationIdentifier = segue.identifier;
self.destinationViewController = [_viewControllersByIdentifier objectForKey:self.destinationIdentifier];
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([self.destinationIdentifier isEqual:identifier]) {
//Dont perform segue, if visible ViewController is already the destination ViewController
return NO;
}
return YES;
}
#pragma mark - Memory Warning
- (void)didReceiveMemoryWarning {
[[_viewControllersByIdentifier allKeys] enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
if (![self.destinationIdentifier isEqualToString:key]) {
[_viewControllersByIdentifier removeObjectForKey:key];
}
}];
}
#end
MHTabBarSegue:
#implementation MHTabBarSegue
- (void) perform {
MHCustomTabBarController *tabBarViewController = (MHCustomTabBarController *)self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *) tabBarViewController.destinationViewController;
//remove old viewController
if (tabBarViewController.oldViewController) {
[tabBarViewController.oldViewController willMoveToParentViewController:nil];
[tabBarViewController.oldViewController.view removeFromSuperview];
[tabBarViewController.oldViewController removeFromParentViewController];
}
destinationViewController.view.frame = tabBarViewController.container.bounds;
[tabBarViewController addChildViewController:destinationViewController];
[tabBarViewController.container addSubview:destinationViewController.view];
[destinationViewController didMoveToParentViewController:tabBarViewController];
}
#end
"At this moment "dealloc" gets called on this ViewController before it is presented." -- no, not really. Dealloc is being called on a controller that never gets on screen, not the one you came from initially or are going back to. The way your segue is set up, and the fact that you keep a reference to your controllers in the dictionary, means that they never get deallocated. Segues (other than unwinds) ALWAYS instantiate new view controllers, so what's happening is that a new instance of, say VC1 is created when you click on the first tab (and a segue is triggered), but you never do anything with that controller (which would be self.destinationViewController in the custom segue class) so it's deallocated as soon as the perform method exits.
Depending on where you setup any delegates or notification observers, this might not be a problem -- this controller that's created, and then immediately deallocated never has its viewDidLoad method called, so if you do those things in viewDidLoad, they won't ever happen for this transient view controller.
If you don't want this to happen, then you need to make your transitions in code without using segues.
I have an audio player in a ViewController. I call this ViewController when a song is selected in the DidSelectRow of a tableview in another viewcontroller called the AlbumViewController.The problem I face is when i select a song ,it pushes to audioplayer view controller and starts playing ,but when i press back and select another song from the album ,the previous song is not stopped ,it still plays while the new song is selected.Now the newly selected song pushes to another instance of audio player viewcontroller and plays . Whereas the previously selected song also plays along with it . And for each selection of song ,a new instance of audio player viewcontroller is pushed ,and all the selected songs are played together. My album controller which has the tableview with it's did select row code is as below
viewContObj=[[ViewController alloc]initWithNibName:#"ViewController" bundle:nil]; //viewcontroller=audio player controller
[self.navigationController pushViewController:viewContObj animated:YES];
viewContObj.url =[NSURL URLWithString:albumURLstring];
[viewContObj playLiveStream:nil];
and the audio player play method is as below.
-(IBAction)playLiveStream:(id)sender
{ if ( ![streamer isPlaying] )
{ [self createStreamer];
[self setButtonImageNamed:#"loadingbutton.png"];
[streamer start];
}
else
{ [self setButtonImageNamed:#"play.png"];
[streamer pause];
}}
Use singletone instance for ViewController and try like this
creating singletone instance in ViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
//1. Customize singletone
-(id)initWithCustomDetails:(NSString *)customDetails
{
self = [super init];
if(_sharedMySingleton)
{
[_sharedMySingleton.view removeFromSuperview];
_sharedMySingleton = nil;
}
_sharedMySingleton = self;
return self;
}
2. exact singletone method for creating instance
+(ViewController *) getViewControllerInstance
{
#synchronized([ViewController class])
{
if (!_sharedMySingleton)
{
_sharedMySingleton = [[self alloc] init];
}
return _sharedMySingleton;
}
return nil;
}
While creating instance use
viewContObj=[[ViewController alloc]initWithCustomDetails:#"details"];
instead of this method
viewContObj=[[ViewController alloc]initWithNibName:#"ViewController" bundle:nil];
//Exact singletone instance creation
viewContObj = [ViewController getViewControllerInstance];
and try..
put one ivar in .m file's #interface part like this
#property (strong, nonatomic) ViewController *playerView;
And in viewDidLoad method alloc init it as below
playerView=[[ViewController alloc]initWithNibName:#"ViewController" bundle:nil];
And in tableView: didSelectRowAtIndexPath: method do as below
playerView.url =[NSURL URLWithString:albumURLstring];
[playerView playLiveStream:nil];
[self.navigationController pushViewController:playerView animated:YES];
May this solve your problem :)
Add release after pushing
viewContObj=[[ViewController alloc]initWithNibName:#"ViewController" bundle:nil];
[self.navigationController pushViewController:viewContObj animated:YES];
viewContObj.url =[NSURL URLWithString:albumURLstring];
[viewContObj playLiveStream:nil];
[viewContObj release];
And in ViewController.m:
- (void)dealloc{
[streamer stop];
[streamer release];
[super dealloc];
}
Ok?
I've got an application with a navigation tree. Somewhere there is an audio player with an mp3.
UITabViewController // controlled by AppDelegate.m
-> UINavigationController
-> UITableViewController // controlled by TableViewController.m
-> UIViewController // with my AVAudioPlayer controlled by AudioViewController.m
In AudioViewController.m I have all my audio engine so i play/pause/stop it, and even kill it when pressing back button (using viewWillDisappear etc) - it plays only in this one view, not another, and it's good.
However there is an issue when i press home button while playback is on. It stops, but after going back to my app, it plays again from remembered time stamp.
I tried all this:
- (void)viewDidUnload
{
[super viewDidUnload];
[self audioStop:self];
}
- (void)viewWillDisappear:(BOOL)animated
{
[self audioStop:self];
[super viewWillDisappear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self audioStop:self];
}
- (void)viewWillUnload
{
[self audioStop:self];
[super viewWillUnload];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self audioStop:self];
}
But nothing happens.
Now i know that to manage if home button was pressed, i need to use applicationWillResignActive method from AppDelegate, but i have totally no idea how do i stop my audioplayer from here. I'm quite new to iOS and i'm not familiar with how delegates work, so please help me work this out.
Let you AppDelegate hold a reference for you UIViewcontroller some how and call it. Make sense?
AppDelegate
|
v
TableViewController (#property)
|
v
UIViewController (#property)
|
v
AudioPlayer (#property) --> [ stop];
Edit:
#interface TableViewController : UIViewController
{
AudioPlayer * audioPlayer;
}
#property(nonatomic, retain) AudioPlayer * audioPlayer;
... // other stuff
#end
#implementation TableViewController
#synthesize audioPlayer;
... // other stuff
#end
This way you can access the audio player from TableViewController object. If you hold TableViewController object this way it should be easy for you.
[tableViewController.audioPlayer someMethod];
something like that ..
When home button was pressed, you need to use applicationWillResignActive method from AppDelegate, How we can use this method , i will show you. You need to use NotificationObeserver in your view controller.
How To add NotificationOberserver
NotificationCenter.default.addObserver(self, selector: #selector(self.application_resign_active(notification:)), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
Now You can stop sound in this function : Like This
func application_resign_active(notification : NSNotification)
{
if my_audio_player.isPlaying{
print("Stop")
my_audio_player.stop()
}
}
my_audio_player is an instance of AudioPlayer
var my_audio_player = AVAudioPlayer()
You can also Check My Video , how i do this : https://www.youtube.com/watch?v=BxmhTAd1SU4