we are having problem with our background sound. If the sound is on when we change view and then go back to the menu it adds another loop of sound. If the sound is muted when we go back it starts again. Please help. This is my code.
// Meny.m
#import "Meny.h"
#import <AVFoundation/AVFoundation.h>
#interface Meny () {
AVAudioPlayer *audioPlayerBG;
}
#end
#implementation Meny
- (void)viewDidLoad {
[super viewDidLoad];
NSString *music = [[NSBundle mainBundle]pathForResource:#"TestSwoong" ofType:#"wav"];
audioPlayerBG = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:music] error:NULL];
audioPlayerBG.numberOfLoops = -1;
audioPlayerBG.volume = 0.5;
[audioPlayerBG play];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
// LjudKnapp stop
- (IBAction)stopBG:(id)sender {
[playBG setHidden:NO];
[pauseBG setHidden:YES];
[audioPlayerBG stop];
}
// LjudKnapp play
- (IBAction)playBG:(id)sender {
[pauseBG setHidden:NO];
[playBG setHidden:YES];
[audioPlayerBG play];
}
It appears that you need to brush up on what classes and instances are. Understanding classes and instances is fundamental if you're going to do object-oriented programming, which is what you do when you use Objective-C.
we change view and then go back to the menu it adds another loop of sound
That phrase suggests that when you "go back", you are not actually going back to the already existing Meny instance - instead, you are creating another Meny instance. So now, you see, you have two of them! Well, each Meny instance has its own AVAudioPlayer, which starts playing when the Meny instance is created — so now you have two audio players, and that's why you hear two loops playing simultaneously.
Related
Ive set a music track to play within the ViewController.h and .m files as shown below. I want to stop this when a new scene is loaded. (.h):
#interface ViewController : UIViewController<AVAudioPlayerDelegate>{
AVAudioPlayer *startingMusic;
}
#property (nonatomic, retain) AVAudioPlayer *startingMusic;
#end
and then .m
#synthesize startingMusic;
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *music = [[NSBundle mainBundle] pathForResource:musicTrack ofType:#"mp3"];
startingMusic=[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:music] error:NULL];
startingMusic.delegate=self;
startingMusic.numberOfLoops=-1;
[startingMusic play];
}
I then try and stop this from playing when the new scene loads is a different class called PlayLevel.m in an init method using this code.
ViewController *test = [[ViewController alloc] init];
[test.startingMusic stop];
However the music just continues to play when the new seen loads. Any suggestions?
When you do ViewController *test = [[ViewController alloc] init]; you created the new ViewController instance, it's mean you now have two ViewController's objects in the memory.
What you really need to do is call stop on your current one.
If you want to stop music from another controller which has no connection to your ViewController you can try doing this via notifications.
For example:
in your ViewController.h file, after imports:
static NSString * const kStopMusicNotificationName = #"kStopMusicNotificationName";
in your ViewController viewDidLoad:
[NSNotificationCenter.defaultCenter addObserverForName:kStopMusicNotificationName object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self.startingMusic stop];
}];
and when you need to stop music:
[NSNotificationCenter.defaultCenter postNotificationName:kStopMusicNotificationName object:nil];
You're going about this the wrong way around, you should really be stopping the music within ViewController and not from another class. ViewController should be self contained where ever possible.
How are you loading the 2nd scene? I imagine you're using a Segue?
If you are using a segue you can override the function -prepareForSegue which is a UIViewController function. Within -prepareForSegue stop the music then.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Stop Music
}
In this code, Audio is a class where all things concerning AVAudioPlayer are handled. Currently there is some music playing. I want to have this fade out completely in 1 second and then play another song. Simultaneously, I want a new scene to appear. So I want the new song to start right as the transition (also lasting 1 second) ends. However, I find myself always waiting for the music to stop, before the scene transition starts. I've tried many ways, with and without selectors, with and without the various sleep functions, but without any luck. I've also tried to use stopMusic instead of transitionMusic and then use playMusic: in the new scene, but I always find myself waiting for stopMusic to finish before the transition starts.
Here's a part of the first scene, which is run if a certain button on the screen is pressed:
Audio *musicPlayer = [[Audio alloc] init];
NewScene *newScene = [NewScene sceneWithSize:self.size];
SKTransition *reveal = [SKTransition pushWithDirection:SKTransitionDirectionLeft duration:1.0];
reveal.pausesOutgoingScene = NO;
reveal.pausesIncomingScene = NO;
[self.view presentScene:newScene transition:reveal];
[musicPlayer transitionMusic:#"NewSong"];
The method transitionMusic: is as follows:
-(void)transitionMusic:(NSString *)fileName {
[self performSelectorOnMainThread:#selector(stopMusic) withObject:nil waitUntilDone:YES];
[self playMusic:fileName];
}
And stopMusic and playMusic: are:
-(void)stopMusic {
while(BGM.volume > 0.05) {
[BGM setVolume:(BGM.volume - 0.05)];
[NSThread sleepForTimeInterval:0.05];
}
[BGM stop];
}
-(void)playMusic:(NSString *)fileName {
NSURL * backgroundMusicURL = [[NSBundle mainBundle] URLForResource:[NSString stringWithFormat:#"%#",fileName] withExtension:#"caf"];
BGM = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:nil];
[BGM setNumberOfLoops:-1];
[BGM setVolume:1.0];
[BGM prepareToPlay];
[BGM play];
}
Also, BGM is a static audioplayer declared in Audio.h. Can anyone point out what I'm doing wrong?
Your musicPlayer player object is dependent on your old scene. You need to create a singleton to handle your transition music independently from one scene to another.
BoxesOfBabies.com has a good tutorial on how to set up a singleton and use it to Control Music Across Multiple SKScene.
EDIT:
In your SKScene:
#import "MySingleton"
#implementation MainScene
{
MySingleton *_mySingleton;
}
In your init method:
_mySingleton = [MySingleton sharedInstance];
To start playing audio in any of your scenes:
[_mySingleton startPlayingSong1];
or
[_mySingleton startPlayingSong2];
whatever methods you have in your singleton.
I am trying to create AVAudioplayer programatically.The audio player successfully playing.But i have some issues.I am displaying 12 audio items in UITableview.If i click first item that should navigate audio player view controller.And i click the play button the song will play.If i click back button the song play continuously.But if i click again same item the Audio view controller the view will display initial state like progress bar should not move and current time and song duration are empty.but song play continuously.
And another issue is there. If i click first item that corresponding song will play.I should not click any pass button.i click back button and click second item.if i click second item the audio view controller will display.I click play button the song display.In background first song play and second song also play.This is my second issue.how to solve these two issues.please help me any body.
-(void)playOrPauseButtonPressed:(id)sender
{
if(playing==NO)
{
[playButton setBackgroundImage:[UIImage imageNamed:#"Pause.png"] forState:UIControlStateNormal];
// Here Pause.png is a image showing Pause Button.
NSError *err=nil;
AVAudioSession *audioSession=[AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
NSLog(#"%# %d",urlsArray,selectedIndex);
NSString *sourcePath=[urlsArray objectAtIndex:selectedIndex];
NSData *objectData=[NSData dataWithContentsOfURL:[NSURL URLWithString:sourcePath]];
audioPlayer = [[AVAudioPlayer alloc] initWithData:objectData error:&err];
audioPlayer.numberOfLoops = 0;
[audioPlayer prepareToPlay];
audioPlayer.delegate=self;
[audioPlayer play];
playing=YES;
}
else if (playing==YES)
{
[playButton setBackgroundImage:[UIImage imageNamed:#"play.png"] forState:UIControlStateNormal];
[audioPlayer pause];
playing=NO;
}
if (self.audioPlayer)
{
[self updateViewForPlayerInfo];
[self updateViewForPlayerState];
[self.audioPlayer setDelegate:self];
}
}
-(void)updateViewForPlayerInfo
{
self.songDuration.text = [NSString stringWithFormat:#"%d:%02d", (int)self.audioPlayer.duration / 60, (int)self.audioPlayer.duration % 60, nil];
NSLog(#"%f", self.audioPlayer.duration);
self.progressBar.maximumValue = self.audioPlayer.duration;
self.volumeSlider.value = self.audioPlayer.volume;
}
-(void)updateViewForPlayerState
{
[self updateCurrentTime];
if (self.updatedTimer)
{
[self.updatedTimer invalidate];
}
if (self.audioPlayer.playing)
{
self.updatedTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(updateCurrentTime) userInfo:self.audioPlayer repeats:YES];
}
}
-(void)updateCurrentTime
{
//NSLog(#"self.audioPlayer.currentTime = %f", self.audioPlayer.currentTime);
self.currentTime.text = [NSString stringWithFormat:#"%d:%02d", (int)self.audioPlayer.currentTime / 60, (int)self.audioPlayer.currentTime % 60, nil];
self.progressBar.value = self.audioPlayer.currentTime;
}
-(void)volumeSliderMoved:(UISlider *)sender
{
self.audioPlayer.volume=[sender value];
}
I think the problem is here
audioPlayer = [[AVAudioPlayer alloc] initWithData:objectData error:&err];
Each time you are creating the audioPlayer object. So my suggestion is check for audioPlayer before creating the object like this
if(audioPlayer)
{
audioPlayer.delegate=nil;
audioPlayer=nil;
}
Then create the object.
In short what's happening is the following:
you click on the table cell
you create an audio player view controller
You play the audio - your audio player itself is retained for some reason (Unclear why - are you using ARC?)
You click the back button - the Audio player view controller is released
you click the table cell again - and create a new audio player view controller with it's own player!
Basically - if you want the previous song continue playing even when your exiting the Audio player view controller - and to have only a single player for the audio - I would hold it in a singleton class that will manage the player , or as a property of the app delegate.
This way - when you enter the Audio Player view controller - It will check the following:
If a player doesn't exist - create it and assign it the item - ready to play
If a player exists but it's the same item - just update the controls to show it's current playing status.
If a player exists but it's another item - either don't update the controls - and when the user hits play - replace the item in the player and start updating the controls - or stop the song as you display the Audio player view controller. (Up to you - I don't know the design of your app obviously)
In working with AVAudioPlayer, I use a property to keep up with the player. Based on looking at your code it isn't clear to me that you are keeping the player around, or that you aren't instantiating a new instance every time. Based on your description, it sounds like you ARE instantiating a new instance - don't think you want that.
So in your .h file, something like this:
#property (nonatomic, strong) AVAudioPlayer *myAVAudioPlayer;
and then the corresponding #synthesize in your .m file.
When you want to move from one song to the next, simply stop playing current - then reload a new URL using an init for your self.myAVAudioPlayer property. You MUST then call the prepareToPlay method again for the AVAudioPlayer option.
You probably will make it easier on yourself by not managing the variables regarding playing or not (it looked like you were doing that from your post). Use your property and check by accessing the setter/getter for the property for state of the player:
if(!self.myAVAudioPlayer)
{
// allocate and initialize using the code you have
[self.myAVAudioPlayer prepareToPlay];
} else
if ([self.myAVAudioPlayer isPlaying])
{
//do whatever you need for isPlaying - looks like pause
} else
if (![self.myAVAudioPlayer isPlaying])
{
// do whatever you need to do if the player isn't playing when the button is pressed
// at this point you will likely just re-init with a new URL and reset other items
// don't forget to call -prepareToPlay again
}
Here is a link to another approach which takes advantage of AVPlayerItem --> click https://stackoverflow.com/a/22254588/3563814
I'm very new to stack overflow (with this being my first question) but I'm wondering if you guys can help.
I've made a bass synthesiser which works by playing back wav. files using avAudioPlayer, with each note having it's own player. I have placed multiple buttons on each note with different numbered tags and using a switch statement in order to give some control to the volume dynamics. There is also a segmented controller used to select from three different sets of samples using by selecting the prefix to the file name for the URL. The problem is if you try to play two buttons that play the same note and use the same avAudioPlayer the app crashes.
As this is a bass synthesiser you would expect it to be monophonic anyway, if only one button could be pressed at one time this could solve my issue. Does anybody have any idea how I would do this? Please use as basic terms as possible as I am not very familiar with coding. I will post the code for one note. (There are 13 for one octave).
#import "AJCViewController.h"
#interface AJCViewController ()
#end
#implementation AJCViewController
NSString* bassPrefix;
NSURL *url;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
bassPrefix = #"Z";
// define default prefix for sample playback.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)c1Pressed:(UIButton *)sender {
NSString *bass = [[NSString alloc] initWithFormat:#"%#C", bassPrefix];
NSString *path = [[NSBundle mainBundle] pathForResource:bass ofType:#"wav"];
[bass release];
c1AudioPointer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
c1AudioPointer.delegate = self;
// All buttons have tags, the buttons towards the top of the sceen having tag 0. The numbers buttons at the button 3. Switch statement varies volume based on where on the key it is pressed.
switch (sender.tag)
{
case 0:
[c1AudioPointer setVolume:0.4];
break;
case 1:
[c1AudioPointer setVolume:0.5];
break;
case 2:
[c1AudioPointer setVolume:0.6];
break;
case 3:
[c1AudioPointer setVolume:0.8];
break;
}
[c1AudioPointer play];
}
- (IBAction)c1Released:(UIButton *)sender {
//While loop ramps down volume gradually to prevent clipping
while (c1AudioPointer.volume > 0)
{
c1AudioPointer.volume = c1AudioPointer.volume - 0.1;
}
[c1AudioPointer stop];
[c1AudioPointer release];
}
//De-allocate memory to free up audio buffer, necessary if note held till sample ends
- (void)dealloc {
[super dealloc];
[c1AudioPointer release];
[cSharp1AudioPointer release];
[d1AudioPointer release];
[dSharp1AudioPointer release];
[e1AudioPointer release];
[f1AudioPointer release];
[fSharp1AudioPointer release];
[g1AudioPointer release];
[gSharp1AudioPointer release];
[a1AudioPointer release];
[bFlat1AudioPointer release];
[b1AudioPointer release];
[c2AudioPointer release];
}
- (IBAction)bassChoise:(UISegmentedControl *)sender {
//Switch statement selects sample group based on state of segmented controller
switch (self.bassChooserSegments.selectedSegmentIndex) {
case 0:
bassPrefix = #"Z";
break;
case 1:
bassPrefix = #"Y";
break;
case 2:
bassPrefix = #"X";
break;
default:
break;
}
}
#end
Thank you for your time. Any help or suggestions on what I should look up would be very much appreciated.
If I understand your question correctly, you want to play different levels of the same note, and that is crashing the app?
I would speculate that the cause of the crash is that you are releasing the pointer twice, where the underlying cause is that you start play at one level, followed by starting play at another level, leaking the memory of the first avplayer.
You probably don't want to re-allocate it when simply changing the level on the fly. You are leaking memory if you don't release first in any case.
You could add a test to see if the note is already playing, and only change the volume.
- (IBAction)c1Pressed:(UIButton *)sender {
BOOL isPlaying = YES;
if (nil == c1AudioPointer) { // Don't create new avaudioplayer if only changing the volume of currently playing note.
isPlaying = NO;
NSString *bass = [[NSString alloc] initWithFormat:#"%#C", bassPrefix];
NSString *path = [[NSBundle mainBundle] pathForResource:bass ofType:#"wav"];
[bass release];
c1AudioPointer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
c1AudioPointer.delegate = self;
}
// All buttons have tags, the buttons towards the top of the sceen having tag 0. The numbers buttons at the button 3. Switch statement varies volume based on where on the key it is pressed.
switch (sender.tag)
{
case 0:
[c1AudioPointer setVolume:0.4];
break;
case 1:
[c1AudioPointer setVolume:0.5];
break;
case 2:
[c1AudioPointer setVolume:0.6];
break;
case 3:
[c1AudioPointer setVolume:0.8];
break;
}
if (!isPlaying) {
[c1AudioPointer play];
}
}
If you do want to play the note again when sliding your finger for the different levels, then deallocate first.
btw. why don't you use ARC? You wouldn't run into this problem.
I have an app that has a piano built into it. Each key (Button1) when pressed down starts to play the note and when the user lifts their finger I want the note to stop playing. What happens is that the audio does not stop playing when the stop function is set to Touch Up Inside on the key (Button1). If I change the the Action from the piano key (Button1) to a different button (Button2) it stops playing as it should if I press the button 2.
.h
#import <AVFoundation/AVAudioPlayer.h>
#interface FifthViewController : UIViewController <AVAudioPlayerDelegate> {
AVAudioPlayer *theNote;
}
.m
#import <AVFoundation/AVAudioPlayer.h>
#import <AVFoundation/AVFoundation.h>
//White
-(IBAction)note1w:(id)sender { //Touch Down
[self whiteKey:self];
[self downKey:self];
NSString *path = [[NSBundle mainBundle] pathForResource:#"white1" ofType:#"aif"];
theNote = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theNote.delegate = self;
theNote.currentTime = 0;
[theNote play];
}
-(IBAction)stopMusic { //Touch Up Inside - This is the action that I change from the Button1 (The Piano Key) to Button2 (A Reg UIButton) and it works.
[theNote stop];
}
I tried your code in a project and it worked fine. I suspect you misconfigured the touchUp connection (perhaps connected touchUpOutside as I have accidentally done on occasion).
I ended up creating a work around to fix the problem. I already had a long press gesture recognizer in the app so I added to the recognizer state ended [self stopMusic]; and it ran fine. I couldn't replicate the problem in another app. It must be a conflict with other code that is being ran.