iOS SDK : playing music on the background and switching views - iphone

I am trying to play music in my application. The music works fine but after switching viewControllers and returning to the main menu, my music plays again! It means several identical sounds play together! How can I solve this? Here is my code :
- (void)viewDidLoad {
NSString *music = [[NSBundle mainBundle] pathForResource:#"1music" ofType:#"mp3"];
myMusic = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:music] error:NULL];
myMusic.delegate = self;
myMusic.numberOfLoops = -1;
[myMusic play];
}
- (IBAction) scoreView {
ScoreViewController *scoreView = [[ScoreViewController alloc] initWithNibName:#"ScoreViewController" bundle:nil];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:self.view cache:YES];
[self.view addSubview: scoreView.view];
[UIView commitAnimations];
}
EDITED CODE :
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
NSString * musicSonati = [[NSBundle mainBundle] pathForResource:#"music" ofType:#"mp3"];
myMusic = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:musicSonati] error:NULL];
myMusic.delegate = self;
myMusic.numberOfLoops = -1;
[myMusic play];
}
return self;
}
//toggle button
- (IBAction)MusicPlaying:(id)sender {
if ((isPlay = !isPlay))
{
UIImage *buttonImageNormal = [UIImage imageNamed:#"play.png"];
UIImage *stretchableButtonImageNormal = [buttonImageNormal stretchableImageWithLeftCapWidth:0 topCapHeight:0];
[MusicButton setBackgroundImage:stretchableButtonImageNormal forState:UIControlStateNormal];
[myMusic pause];
}else {
UIImage *buttonImageNormal = [UIImage imageNamed:#"pause.png"];
UIImage *stretchableButtonImageNormal = [buttonImageNormal stretchableImageWithLeftCapWidth:0 topCapHeight:0];
[MusicButton setBackgroundImage:stretchableButtonImageNormal forState:UIControlStateNormal];
NSLog(#"Music play");
[myMusic play];
}
}

If you have audio to play on multiple places then one simple way to achieve this is declare AVAudioPlayer instance in delegate with property synthesized.If you want to play this anywhere do this:-
MyAppDelegate *app_delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
if(app_delegate.audioPlayer)
{
[app_delegate.audioPlayer release];
app_delegate.audioPlayer = nil;
}
app_delegate.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
and if you want to stop sound do this:-
if(app_delegate.audioPlayer)
{
if([app_delegate.audioPlayer isPlaying])
{
[app_delegate.audioPlayer stop];
[app_delegate.audioPlayer setCurrentTime:0];
}
}
and to play new sound file release and nil same player if already allocated or alloc it again with new sound file url.

Make sure myMusic is correctly declared as a retained property and synthesized then try something like this:
-(void) commonInit {
NSString * musicSonati = [[NSBundle mainBundle] pathForResource:#"music" ofType:#"mp3"];
self.myMusic = [[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:musicSonati] error:NULL] autorelease];
self.myMusic.delegate = self;
self.myMusic.numberOfLoops = -1;
//You probably don't want to play music on init, rather defer to viewDidLoad.
// [self.myMusic play];
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self commonInit];
}
return self;
}
- (id)init {
self = [super init];
if (self) {
[self commonInit];
}
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
[self commonInit];
}
return self;
}
Then try this in your viewDidLoad:
- (void)viewDidLoad {
...
if (!self.myMusic.playing) {
[self.myMusic play];
}
...
}
Also, don't forget to release your player in dealloc!

First, viewDidLoad probably isn't a good place to be initializing your AVAudioPlayer, as the system may need to unload some of your components to reclaim memory (in viewDidUnload). You should probably be creating the AVAudioPlayer in your init method or similar.
So what's happening is you end up creating a second AVAudioPlayer when the focus comes back to your application, and you're actually losing the reference to your first one. (Thus, also leaking memory.)
If you want to start playing music when the view loads, you should additionally check its status before doing so:
- (void)viewDidLoad {
...
if (!myMusic.playing) {
[myMusic play];
}
...
}

Its seems you are developing game (from your function name After viewLoad (ScoreView). ;) )
Well, According to me, AvAudioplayer is not Good Option if you want to implement with Game.
Use cocosdenshion, Download the latest cocos2d folder, find the these 9 files, put it in your resource folder,
And You are done.. !!!
#import "SimpleAudioEngine.h" // wherever you want to call in head part of the file.
Step1 call short file.!
[[SimpleAudioEngine sharedEngine] playEffect:#"blast.mp3"];
step2 call background loop.
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:#"background.mp3" loop:YES];
NOTE: There are list of available function http://www.cocos2d-iphone.org/api-ref/0.99.5/interface_simple_audio_engine.html , Its very easy and simple !
Whole implementation will take, 20 minutes max !

Initialize your player in app delegate file and play your background music. Only set one variable in app delegate which checks music play true or false...and enjoy your app without restarting music......
In your code you initialize you player in view did load and init method... that's why you music restart.....just call it only once in app delegate applicationdidfinshLaunching() method....

Related

App crashing on playing a sound on button click method

I used uibuttons to create a grid view, now need to select and deselect the button on its click.
All is working fine but when I try to play a sound on a button click, app get crashed by printing the below message in console:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIButton prepareToPlay]: unrecognized selector sent to instance 0x7526a20'
To play the sound,I have taken the object of AVAudioPlayer claas in .h file and initializing it in view did load method and playing it on button click, here is my code
.h file
#import <UIKit/UIKit.h>
#import <AVFoundation/AVAudioPlayer.h>
#import <AudioUnit/AudioUnit.h>
#interface PlayViewController : UIViewController<AVAudioPlayerDelegate>
{
IBOutlet UIButton *resetBtn;
UIButton *btn[25];
AVAudioPlayer *tapSound;
}
-(IBAction)reserBtnClkd;
#property(nonatomic,retain) AVAudioPlayer *tapSound;
#end
in .m file
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"background.png"]]];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *path=[[NSBundle mainBundle] pathForResource:#"button-3" ofType:#"wav"];
AVAudioPlayer *Tapsound=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
self.tapSound=Tapsound;
[Tapsound release];
[pool release];
[self setTheIcons];
resetBtn.layer.cornerRadius=6.0;
resetBtn.backgroundColor=[UIColor colorWithRed:90/255.00 green:33.00/255.00 blue:179.00/255.00 alpha:1];
resetBtn.titleLabel.font=[UIFont fontWithName:#"Helvetica" size:22];
[resetBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
}
#pragma mark
#pragma mark<Creating The Grid View Of Icons>
#pragma mark
-(void) setTheIcons
{
int x=11;
int y=55;
for(int i=1;i<=25;i++)
{
btn[i]=[UIButton buttonWithType:UIButtonTypeCustom];
btn[i].frame=CGRectMake(x, y, 58,58);
[btn[i] addTarget:self action:#selector(btnClkd:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn[i]];
if(i%5==0)
{
x=11;
y=y+60;
}
else
{
x=x+60;
}
}
[self reserBtnClkd];
}
-(void)btnClkd:(UIButton*)sender
{
sender.selected=!sender.selected;
if(sender.selected)
{
// UIView *tempView=[[UIView alloc]initWithFrame:CGRectMake(12, 12, 34, 34)];
UIView *tempView=[[UIView alloc]initWithFrame:CGRectMake(0, 0, 58, 58)];
tempView.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"hover-effectNew.png"]];
tempView.userInteractionEnabled=NO;
[sender addSubview:tempView];
[tempView release];
}
else
{
NSArray *subviewToRemove=[sender subviews];
int i=1;
for(UIView *view in subviewToRemove)
{
if(i==2)
{
[view removeFromSuperview];
}
i++;
}
}
[self.tapSound prepareToPlay];
[self.tapSound setDelegate:self];
[self.tapSound play];
}
And the strange thing is that App crashed only when I play the sound on button click method, otherwise if I play it in view did load then it works without any issue, Scratching my head for two days but could not find any solution, please help me
I think that the problem is that your self.tapSound is an UIButton(you can see it in the crash log: '-[UIButton prepareToPlay]: unrecognized selector sent to instance 0x7526a20'.
Try debugging it step by step and see what happens
I think because self.tapSoundis referencing Tapsound, when you call [Tapsound release] it releases the property as well. I would recommend instantiating the property directly, like this:
/*note that you have declared tapSound in your .h
as a class variable in addition to being a property.
This means you can access it as both.
Wherever you would type self.tapSound, instead use simply tapSound
*/
tapSound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];

UIButton is dead after I change views and switch back

So I created a program where a sound occurs when the device is shaken. Theres a button, that when tapped stops the sound from being played at any time (This is the "reset" method). There's another button, that when pushed displays another view (This is the "infoView" method). The problem occurs when I go the "infoView" then go back to the first view. The sound still play when the device is shaken but the "reset" button becomes unresponsive. Here's what I have so far, any help would be appreciated.
PS. I'm wondering if it has anything to do with FirstResponders? I'm still trying to wrap my head around FirstResponders.
The .h
#import <UIKit/UIKit.h>
#import "AVfoundation/AVfoundation.h"
#interface OldTvViewController : UIViewController{
AVAudioPlayer *audioPlayer;
}
-(IBAction)reset;
-(IBAction)infoView:(id)sender;
#end
the .m
#import "OldTvViewController.h"
#import "AudioToolBox/AudioToolBox.h"
#import "infoViewController.h"
#implementation OldTvViewController
-(IBAction)infoView:(id)sender{
infoViewController *second = [[infoViewController alloc] initWithNibName:Nil bundle:Nil];
[self presentModalViewController:second animated:YES];
[second release];
}
-(BOOL) canBecomeFirstResponder{
return YES;
}
-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
if (event.subtype == UIEventSubtypeMotionShake) {
//Play throw sound
if(audioPlayer.playing){
[audioPlayer stop];
} else{
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/bomb.mp3",[[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = 0;
[audioPlayer play];
}
}
}
-(IBAction)reset{
[audioPlayer stop];
}
-(void)viewDidAppear:(BOOL)animated{
[self becomeFirstResponder];
[super viewDidAppear:animated];
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
#end
when You go to info view then where your all button available in that class you try this code.
-(void)viewWillDisapeers
{
[audioPlayer stop];
}
or add in this method
-(IBAction)infoView:(id)sender
{
infoViewController *second = [[infoViewController alloc] initWithNibName:Nil bundle:Nil];
[self presentModalViewController:second animated:YES];
[audioPlayer stop];
[second release];
}

Switching xib views without carrying commands from previous screen

I have been using storyboards since I began using xCode. Today I have been trying out the old way with xib files.
My first view has an accelerometer code that is still active when I am on the second view.
Is there a way to stop the second view controller from using code from the first view?
I am importing the second views header file into the first views implementation file, is that correct? If I remove that import I get errors.
//
// ViewController.m
#import "ViewController.h"
#import "ViewController2.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
-(void)viewWillAppear:(BOOL)animated{
[self startAccel];
//[self view];
}
-(void)viewWillDisappear:(BOOL)animated{
[self stopAccel];
//[self view];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return ((interfaceOrientation == UIInterfaceOrientationPortrait) || (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown));
}
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration{
double const kThreshold = 1.7;
// double const kThreshold = 2.0;
if ( fabsf(acceleration.x) > kThreshold
|| fabsf(acceleration.y) > kThreshold
|| fabsf(acceleration.z) > kThreshold){
int randomNumber = arc4random() % 3 + 1;
NSURL *soundURL = [NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:[NSString stringWithFormat:#"Sound%02d", randomNumber] ofType:#"wav"]];
AVAudioPlayer * soundPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:nil];
[soundPlayer prepareToPlay];
[soundPlayer play];
}
}
-(void)startAccel{
UIAccelerometer * accel = [UIAccelerometer sharedAccelerometer];
accel.delegate = self;
accel.updateInterval = .25;
}
-(void)stopAccel{
UIAccelerometer * accel = [UIAccelerometer sharedAccelerometer];
accel.delegate = nil;
}
-(IBAction)View2:(id)sender;{
ViewController2 *V2 = [[ViewController2 alloc]
initWithNibName:#"ViewController2"
bundle:nil];
[self.view addSubview:V2.view];
}
#end
The second controller doesn't really use code from the first but, if you called startAccelerometerUpdates while you were displaying your first view, you might want to stop them as you're about to present the second one if it doesn't need them.

MPMoviePlayerController causing a MP _playbackInterruptionDidEndNotification on appDidBecomeActive

I have a navigation controller that pushes another view which plays video files. Everything is working fine and on the simulator there is no problem at all. The problem is when I test the application on the iPhone, the movies play well but if I press the home button on the iPhone then I launch the app again I receive the following warning in Xcode Debugging area:
2011-11-21 20:23:05.216 KMW[324:707] MP _playbackInterruptionDidEndNotification :: NSConcreteNotification 0x164e90 {name = AVController_PlaybackInterruptionDidEndNotification;
object = ; userInfo = {
"AVController_InterruptionStatusNotificationParameter" = "non-resumable.SoloAmbientSound";
"AVController_InterruptorNameNotificationParameter" = "AudioSession-324"; }}, _state = 0
Although this warning is appearing I can continue using the App and watch movies without any problem but I am afraid that in a certain situation this warning causes a problem. Here is how I am setting up the view that plays the movies:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSString *path = [[NSBundle mainBundle] pathForResource:#"movieFileName" ofType:#"m4v"];
movieURL = [NSURL fileURLWithPath:path];
self.moviePlayer = [[[MPMoviePlayerController alloc] initWithContentURL:movieURL] autorelease];
[self.view addSubview:moviePlayer.view];
moviePlayer.view.frame = CGRectMake(0, 10, 320, 181);
[moviePlayer setControlStyle:MPMovieControlStyleNone];
[moviePlayer play];
}
- (void) viewWillDisappear:(BOOL)animated
{
[moviePlayer stop];
[moviePlayer.view removeFromSuperview];
}
- (void)dealloc
{
[movieURL release];
[moviePlayer release];
[super dealloc];
}
Any ideas about this strange warning?

How to change background image of a button and get it updated instantly?

Im developing a memory game. As i click on a button I want the background to switch. When I click on the card the second time i want the background to switch for a second and then go back to the original (if it wasnt the correct card). But the background of the buttons are not changed until i return from the method (buttonPressed). Why isnt it changed instantly? Please help me! :)
Here is the class that i am working with:
#import "Button.h"
#import <UIKit/UIKit.h>
#import <AVFoundation/AVAudioPlayer.h>
#class ZogajAppDelegate;
#interface PairPlayPlayViewController : UIViewController <AVAudioPlayerDelegate> {
ZogajAppDelegate *appDelegate;
AVAudioPlayer *av;
Boolean isPairActive;
Button *firstButton;
Button *secondButton;
}
#property (nonatomic, retain) AVAudioPlayer *av;
#property (nonatomic, retain) Button *firstButton;
#property (nonatomic, retain) Button *secondButton;
- (void)buttonPressed:(id)sender;
- (void)secondCard;
#end
and the .m file:
#import "PairPlayPlayViewController.h"
#import "ZogajAppDelegate.h"
#import "Button.h"
#import "AVFoundation/AVAudioPlayer.h"
#implementation PairPlayPlayViewController
#synthesize av, firstButton, secondButton;
/*
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
// Custom initialization
}
return self;
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
NSString *newAudioFile = [[NSBundle mainBundle] pathForResource:#"double_card" ofType:#"mp3"];
NSString *newAudioFile2 = [[NSBundle mainBundle] pathForResource:#"right_sound" ofType:#"mp3"];
av = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:newAudioFile] error:NULL];
[av prepareToPlay];
[av initWithContentsOfURL:[NSURL fileURLWithPath:newAudioFile2] error:NULL];
[av prepareToPlay];
//av = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:newAudioFile] error:NULL];
//[av prepareToPlay];
isPairActive = FALSE;
appDelegate = (ZogajAppDelegate *)[[UIApplication sharedApplication] delegate];
int position;
for(int i = 0; i < ((appDelegate.numberOfPairs*2)/5); i++){
for (int j = 0; j<5 ;j++){
Button *button = [[Button alloc] init];
//Sätter bild nummer..
position = (arc4random() % [appDelegate.cardsInPlay count]);
button.picNumber = [[appDelegate.cardsInPlay objectAtIndex:position]intValue];
NSLog(#"%d", button.picNumber);
[appDelegate.cardsInPlay removeObjectAtIndex:position];
button.frame = CGRectMake( 3 + (65 * j), 5 + (65 * i), 55, 55);
button.imageView.contentMode = UIViewContentModeScaleAspectFit;
button.imageView.clipsToBounds = YES;
[button setBackgroundImage:[UIImage imageNamed:#"back.png"] forState:UIControlStateNormal];
[button addTarget:self action:#selector(buttonPressed:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
}
for (int j = 0; j<((appDelegate.numberOfPairs*2) % 5) ;j++){
Button *button = [[Button alloc] init];
//Sätter bild nummer..
position = (arc4random() % [appDelegate.cardsInPlay count]);
button.picNumber = [[appDelegate.cardsInPlay objectAtIndex:position]intValue];
NSLog(#"%d", button.picNumber);
[appDelegate.cardsInPlay removeObjectAtIndex:position];
button.frame = CGRectMake( 3 + (65 * j), 5 + (65 * ((appDelegate.numberOfPairs*2)/5) + 1), 55, 55);
//button.imageView.contentMode = UIViewContentModeScaleAspectFit;
//button.imageView.clipsToBounds = YES;
[button setBackgroundImage:[UIImage imageNamed:#"back.png"] forState:UIControlStateNormal];
[button addTarget:self action:#selector(buttonPressed:)
forControlEvents:UIControlEventAllTouchEvents];
[self.view addSubview:button];
}
}
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
-(void)buttonPressed:(id)sender {
Button *button = [[Button alloc] init];
button = sender;
NSLog(#"%d och spela upp ljud", button.picNumber);
NSString *newAudioFile = [[NSBundle mainBundle] pathForResource:#"double_card" ofType:#"mp3"];
[av initWithContentsOfURL:[NSURL fileURLWithPath:newAudioFile] error:NULL];
[av play];
[button setBackgroundImage:[UIImage imageNamed:[NSString stringWithFormat:#"%d.png",button.picNumber]] forState:UIControlStateNormal];
[self.view addSubview:button];
[NSThread sleepForTimeInterval:1.0];
if(isPairActive == FALSE){
isPairActive = TRUE;
firstButton = button;
}
else {
secondButton = button;
[self secondCard];
}
return;
}
- (void)secondCard {
[NSThread sleepForTimeInterval:1.0];
NSString *newAudioFile = [[NSBundle mainBundle] pathForResource:#"double_card" ofType:#"mp3"];
isPairActive = FALSE;
if([appDelegate.dictionary objectForKey:[NSString stringWithFormat:#"%d",secondButton.picNumber]] == [NSString stringWithFormat:#"%d",firstButton.picNumber]){
NSString *newAudioFile2 = [[NSBundle mainBundle] pathForResource:#"right_sound" ofType:#"mp3"];
[av initWithContentsOfURL:[NSURL fileURLWithPath:newAudioFile2] error:NULL];
[av play];
}
else {
[firstButton setBackgroundImage:[UIImage imageNamed:#"back.png"] forState:UIControlStateNormal];
[secondButton setBackgroundImage:[UIImage imageNamed:#"back.png"] forState:UIControlStateNormal];
[av initWithContentsOfURL:[NSURL fileURLWithPath:newAudioFile] error:NULL];
[av play];
}
return;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
[av release];
[firstButton release];
[secondButton release];
}
#end
In buttonPressed: change the background, set an NSTimer to call you back, and return. In the NSTimer callback, set the background back to what you want.
Drawing doesn't happen at the moment you ask for it. That would give you horrible performance. All drawing is merged into the draw portion of the run loop. You won't get to that portion until you return from this IBAction method.
As you already know, the image will be changed after returning from the method. That's it.
You have to return from the method, so you have to change the image, perform selector that executes the rest of code you want to perform and return from the method.
UIKit can run only on main thread, so you have to return from the method, that's blocking it, and it will execute GUI manipulations then.
EDIT:
Instead of telling NSThread to sleep, perform the selector with delay.