Sound issue exiting - cocoa-touch

I have a problem where I have sound in Menu > Level one.
However when I exit Level one and go back to menu the sound won't stop!
What code do I need to terminate the sound?
This is the code I'm using:
- (IBAction) playsound {
NSString *path = [[NSBundle mainBundle] pathForResource:#"imsound" ofType:#"wav"];
AVAudioPlayer* myAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
myAudio.delegate = self;
myAudio.volume = 1.0;
myAudio.numberOfLoops = -1;
[myAudio play];
}

As the documentation states, -1 will cause it to play repeatedly until you send it a -stop. So the problem becomes "when should I tell it to stop." It's probably a good idea to do so (if it's playing) when you go back.
Because you don't hold a reference to the player (you're leaking it as soon as you leave the -playSound: method, you have no way to tell it to shut up. You should make it an instance variable / property so you can get to it whenever you need to. You're essentially pressing play then tossing the player into the back of someone's truck, then wanting it back so you can turn it off. Should've tied a string to it. ;-)
Proper memory management techniques and use of instance variables are the key causes of your current woes.

Related

Issues Playing A Sound Multiple Times

I'm using SpriteKit for my Mac OS project with Objective C and I'm trying to play a certain sound over and over again when contact between two nodes occurs. I don't want the player to wait for the sound to complete before playing it again. The sound is only about 1 second long, but it repeats as fast as every 0.5 seconds. I've tried two different methods and they both have issues. I'm probably not setting something up correctly.
Method #1 - SKAction
I tried getting one of the sprites to play the sound using the following code:
[playBarNode runAction:[SKAction playSoundFileNamed:#"metronome" waitForCompletion:NO]];
The sound plays perfectly on time, but the sound is modified. It sounds like reverb (echo) was applied to it and it has lost a lot of volume as well.
Method #2 - AVAudioPlayer
Here's the code I used to set this up:
-(void) initAudio {
NSString *path = [NSString stringWithFormat:#"%#/metronome.mp3", [[NSBundle mainBundle] resourcePath]];
NSURL *metronomeSound = [NSURL fileURLWithPath:path];
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:metronomeSound error:nil];
[_audioPlayer prepareToPlay];
}
Later on in code it is called like this:
[_audioPlayer play];
The issue with this one is that it seems to wait until it's completed playing the first time before playing the sound again. Basically it fails to play many times.
Am I setting this up incorrectly? How can I fix this? Thanks in advance.
Retry method 1, but instead of having the sprite play the sound, have the scene play the sound via
[self runAction:[SKAction playSoundFileNamed:#"metronome" waitForCompletion:NO]];
inside the game SKScene class

AVAudioPlayer - must you create a property for it to work? (Xcode)

This is the code I'm using now, and it's not working (nothing happens when I press the button that calls this method). Previously, I had a property for audioPlayer and it worked (all the audioPlayers below were self.audioPlayer obviously). The problem was that when I tried to play the sound twice, it would end the first sound playing.
This is no good because I'm making a soundboard and want sounds to be able to overlap. I thought I could just make audioPlayer a local variable instead of a property and all would be ok but now the sound doesn't work at all and I can't figure out why. In all tutorials I've found for AVAudioPlayer, a property is made but no one explains why. If this can't work, what alternatives do I have to make sounds that can overlap?
- (void)loadSound:(NSString *)sound ofType:(NSString *)type withDelegate:(BOOL)delegate {
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:sound
ofType:type]];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (delegate) audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
[audioPlayer play];
}
The reason you need a property or ivar is for the strong reference it provides. When using ARC any object without a strong pointer to it is fair game for deallocation, and in fact that is what you are seeing.
You are also correct that an AVAudioPlayer strong pointer will only allow one audio player to be referenced at a time.
The solution, if you choose to continue to use AVAudioPlayer is to use some sort of collection object to hold strong reference to all the player instances. You could use an NSMutableArray as shown here:
Edit I tweaked the code slightly so method that plays the sound takes an NSString soundName parameter.
#synthesize audioPlayers = _audioPlayers;
-(NSMutableArray *)audioPlayers{
if (!_audioPlayers){
_audioPlayers = [[NSMutableArray alloc] init];
}
return _audioPlayers;
}
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
[self.audioPlayers removeObject:player];
}
-(void)playSoundNamed:(NSString *)soundName{
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:soundName
ofType:#"wav"]];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (audioPlayer){
[audioPlayer setDelegate:self];
[audioPlayer prepareToPlay];
[audioPlayer play];
[self.audioPlayers addObject:audioPlayer];
}
}
Generally an AVAudioPlayer is overkill for an sound-effect/soundboard application. For quick sound "drops" you will likely find the audio toolbox framework, as outlined in my answer to this question.
From looking at the System Sound class reference, it seems like you
can only play one sound at a time.
It can only play one SystemSoundID at a time. So for example if you have soundOne and soundTwo. You can play soundOne while soundTwo is playing, but you cannot play more than one instance of either sound at a time.
What's the best way to be able to play sounds that can overlap while
still being efficient with the amount of code and memory?
Best is opinion.
If you need two instances of the same sound to play at the same time, then I would say the code posted in this answer would be the code to use. Due to the fact that each overlapping instance of the same sound requires creating a new resource, code like this with its audioPlayerDidFinishPlaying: is much more manageable(the memory can easily be reclaimed).
If overlapping instances of the same sound are not a deal-breaker then I think just using AudioServicesCreateSystemSoundID() to create one instance of each sound is more efficient.
I definitely would not try to manage the creation of and disposal of SystemSoundIDs with each press of a button. That would go wrong in a hurry. In that instance AVAudioPlayer is the clear winner on just maintainability alone.
I am assuming you are using ARC. The reason that the audio player doesn't work is because the AVAudioPlayer object is being released and then subsequently destroyed once the loadSound: method terminates. This is happening due to ARC's object management. Before ARC, the code:
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (delegate) audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
[audioPlayer play];
would play the sound as expected. However, the AVAudioPlayer object would still exist long after the loadSound: method terminates. That means every time you play a sound, you would be leaking memory.
A little something about properties
Properties were introduced to reduce the amount of code the developer had to write and maintain. Before properties, a developer would have to hand write the setters and getters for each of their instance variables. That's a lot of redundant code. A fortunate side-effect of properties was that they took care of a lot of the memory management code needed to write setters/getters for object-based instance variables. This meant that a lot of developers started using properties exclusively, even for variables that didn't need to be public.
Since ARC handles all the memory management details for you, properties should only be used for their original purpose, cutting down on the amount of redundant code. Traditional iVars will be strongly referenced by default, which means a simple assignment such as:
title = [NSString stringWithFormat:#"hello"];
is essentially the same as the code:
self.title = [NSString stringWithFormat:#"hello"];.
OK, back to your question.
If you are going to be creating AVAudioPlayer instances in the loadSound: method, you'll need to keep a strong reference to each AVAudioPlayer instance or else ARC will destroy it. I suggest adding the newly created AVAudioPlayer objects into a NSMutableArray array. If you adopt the AVAudioPlayerDelegate protocol, you can implement the audioPlayerDidFinishPlaying:successfully: method. In which you can remove the AVAudioPlayer object from the array, letting ARC know that it's OK to destroy the object.

Objective-C overwriting file alert

In my application, I want the user to be able to select a file/location to save data to. Thus, I'm using the following code:
NSSavePanel *newSavePanel = [NSSavePanel savePanel];
NSArray *newArray = [[NSArray alloc] initWithObjects:#"txt", nil]; //example file type
[newSavePanel setAllowedFileTypes:newArray];
NSInteger newInt;
newInt = [newSavePanel runModal];
My problem is that I want the save panel to alert the user and ask for confirmation to overwite if the file already exists. To do this, do I need to implement the delegate method panel:userEnteredFilename:confirmed: in which I place a [[NSFileManager defaultManager] fileExistsAtPath:] message, at which point I create an NSAlert, or is there a better way to go about doing this?
I haven't actually had time to test this, so if the behavior is already implemented in NSSavePanel, could someone let me know?
I may be wrong, but I think you get that behavior by default.
*edit - * yeah you get that out of the box, I used your code.

AVURLAsset for long sound on ipod

long time reader, first time asker...
I am making a music app which uses AVAssetReader to read mp3 data from the itunes library. I need precise timing, so when I create an AVURLAsset, I use the "AVURLAssetPreferPreciseDurationAndTimingKey" to extract timing data. This has some overhead (and I have no problems when I don't use it, but I need it!)
Every thing works fine on iphone(4) and ipad(1). I would like it to work on my ipod touch (2nd gen). But it doesn't: if the sound file is too long (> ~7 minutes) then the AVAssetReader cannot start reading and throws an error ( AVFoundationErrorDomain error -11800. )
It appears that I am hitting a wall in terms of the scanter resources of the ipod touch. Any ideas what is happening, or how to manage the overhead of creating the AVURLAsset so that it can handle long files?
(I tried running this with the performance tools, and I don't see a major spike in memory).
Thanks, Dan
Maybe you're starting to read too son? As far as I understand, for mp3 it will need to go trough the entire file in order to to enable precise timing. So, try delaying the reading.
You can also try registering as an observer for some of the AVAsset properties. iOS 4.3 has 'readable' property. I've never tried it, but my guess would be it's initially set to NO and as soon as AVAsset has finished loading it gets set to YES.
EDIT:
Actually, just looked into the docs. You're supposed to use AVAsynchronousKeyValueLoading protocol for that and Apple provides an example
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = [NSArray arrayWithObject:#"duration"];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
NSError *error = nil;
AVKeyValueStatus durationStatus = [asset statusOfValueForKey:#"duration" error:&error];
switch (durationStatus) {
case AVKeyValueStatusLoaded:
[self updateUserInterfaceForDuration];
break;
case AVKeyValueStatusFailed:
[self reportError:error forAsset:asset];
break;
case AVKeyValueStatusCancelled:
// Do whatever is appropriate for cancelation.
break;
}
}];
If 'duration' won't help try 'readable' (but like I mentioned before 'readable' requires 4.3). Maybe this will solve your issue.

Error while attempting to output data onto console in xcode

I am trying to output general data (source code) from a website, but it just sits there. Can't figure out if its the interface or the code. Would someone double-check for me?
#import "Lockerz_RedemptionViewController.h"
#implementation Lockerz_RedemptionViewController
-(IBAction)start: (id) sender {
while (1) {
NSMutableData *mydata = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://ptzplace.lockerz.com/"]];
NSString *output = [[NSString alloc] initWithData:mydata encoding:NSASCIIStringEncoding];
NSLog(output);
}
}
The reason your NSLog doesn't work is it should use format strings.
Replace:
NSLog(output);
With:
NSLog(#"%#",output);
For more info see http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Strings/Articles/FormatStrings.html
Why the while(1)? Are you intentionally trying to set up an infinite loop? You should just run this once, or maybe set up a periodic timer to reload it every few seconds, but certainly don't use an infinite loop for that... also it's been a while since I did anything with Cocoa networking, but you might want to look into NSURLRequest. You also may want to try NSData's dataWithContentsOfURL:options:error: and check the error parameter to better see what might be going wrong. Hope this helps you out.