I'm developing a iOS app using objective-c. When the application is launched a background music is played. The background music should continue playing when the user clicks help button. Also when the user goes back to the main screen from the help screen the background music should be continuously playing.
For me a new background music is getting played along with the old background music when I navigate from help to main menu. So, I am hearing two background music now.
Could anyone help me in solving this issue?
Regards,
Bharathi.
Your problem could be solved if you retained a reference to your audio player in your UIApplicationDelegate (or some other singleton that's kept around).
//in the .h
#property (nonatomic, strong) AVAudioPlayer *player;
//in the .m
- (void) playMusic
{
if (self.player == nil) {
NSString *path = [NSString stringWithFormat:#"%#/music.mp3", [[NSBundle mainBundle] resourcePath]];
NSURL *soundUrl = [NSURL fileURLWithPath:path];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];
}
if (!self.player.isPlaying) {
[self.player play];
}
}
That way you can call it wherever you need with a:
[(MyAppDelegate*)[UIApplication sharedApplication].delegate playMusic];
Though it might be to your advantage to keep around a SoundsManager class as a singleton in order to handle all the sounds that you'll need to track if you're going to need more than just this one.
Related
I'm building an app with multiple view controllers and I have coded an audio file to play at the start up of the app. That works fine and when I click on the button to view a different screen the audio file still plays without skipping a beat just like it's supposed to but my problem arises when I click on the button to go back to the main screen. When I click to go back to the main screen the audio file plays over itself reminding me of the song Row Row Your Boat. The app is re-reading that code that tells itself to play the audio file thus playing it all over again. My problem is that I can't figure out how to make it not do that. I have coded the app to stop the audio when clicking on the start game button, which is what I want it to do but not until then. I just need help getting the app to not play the audio file over itself when going back to the main screen. The audio file is coded to play infinitely until the "start" button is clicked. If anyone can make since out of what I'm trying to say then please help me code this thing correctly. Thanks to anyone who can make it work right.
Here my code:
-(void)viewDidLoad
{
NSString *introMusic = [[NSBundle mainBundle]pathForResource:#"invadingForces" ofType:#"mp3"];
audioPlayer0 = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:introMusic] error:NULL];
audioPlayer0.delegate = self;
audioPlayer0.numberOfLoops = -1;
[audioPlayer0 play];
}
The problem is that you start a sound in a local variable when your view is loaded, start it playing on endless repeat, and then forget about it. Then you close the view controller, leaving the now-forgotten audio player playing. Next time you invoke the view controller, it's viewDidLoad method creates another audio player and starts that one playing too, and then forgets about that one. Every time you open a new copy of that view controller, you'll start yet another sound player, adding another voice to your round of "row, row, row your boat."
The naive solution is to put the code that starts the sound player in the app delegate. Set up the AVAudioPlayer as a property of you app delegate. Create a startPlaying method and a stopPlaying method. In your didFinishLaunchingWithOptions method, call startPlaying.
It's cleaner app design not to put app functionality in your app delegate, but instead create a singleton to manage sound play. (Search on "iOS singleton design pattern" to learn more.) Create an appDidLaunch method in the singleton, and call appDidLaunch from didFinishLaunchingWithOptions to start playing your sound. That way the app delegate doesn't need to have app specific logic in it, but simply calls appDidLaunch and goes on it's way.
EDIT:
If you want to call a method in the app delegate, and your app delegate is declared as:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
Then you'd call it from another file like this:
First, import the app delegate's header:
#import "AppDelegate.h"
And the actual code to call your app delegate's stopPlaying method:
//Get a pointer to the application object.
UIApplication *theApp = [UIApplication sharedApplication];
//ask the application object for a pointer to the app delegate, and cast it
//to our custom "AppDelegate" class. If your app delegate uses a different
//class name, use that name here instead of "AppDelegate"
AppDelegate *theAppDelegate = (AppDelegate *)theApp.delegate;
[theAppDelegate stopPlaying];
Here's some example code to wrap an AVAudioPlayer in a singleton -
BWBackgroundMusic.h
#interface BWBackgroundMusic : NSObject
// singleton getter
+ (instancetype)sharedMusicPlayer;
/* public interface required to control the AVAudioPlayer instance is as follows -
start - plays from start - if playing stops and plays from start
stop - stops and returns play-head to start regardless of state
pause - stops and leaves play-head where it is - if already paused or stopped does nothing
continue - continues playing from where the play-head was left - if playing does nothing
replace audio track with new file - replaceBackgroundMusicWithFileOfName:
set background player to nil - destroyBackgroundMusic
NOTE:- change default track filename in .m #define */
// transport like methods
- (void)startBackgroundMusic;
- (void)stopBackgroundMusic;
- (void)pauseBackgroundMusic;
- (void)continueBackgroundMusic;
// audio source management
- (void)replaceBackgroundMusicWithFileOfName:(NSString*)audioFileName startPlaying:(BOOL)startPlaying;
- (void)destroyBackgroundMusic;
#end
BWBackgroundMusic.m
#import "BWBackgroundMusic.h"
#import <AVFoundation/AVFoundation.h> // must link to project first
#define DEFAULT_BACKGROUND_AUDIO_FILENAME #"invadingForces.mp3"
#interface BWBackgroundMusic ()
#property (strong, nonatomic) AVAudioPlayer *backgroundMusicPlayer;
#end
#implementation BWBackgroundMusic
#pragma mark Singleton getter
+ (instancetype)sharedMusicPlayer {
static BWBackgroundMusic *musicPlayer = nil;
static dispatch_once_t onceToken;
dispatch_once (&onceToken, ^{
musicPlayer = [[self alloc] init];
});
//NSLog(#"sample rate of file is %f",[musicPlayer currentSampleRate]);
return musicPlayer;
}
#pragma mark Initialiser
- (id)init {
//NSLog(#"sharedMusicPlayer from BWBackgroundMusic.h init called...");
if (self = [super init]) {
// self setup _backgroundMusicPlayer here...
// configure the audio player
NSURL *musicURL = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], DEFAULT_BACKGROUND_AUDIO_FILENAME]];
NSError *error;
if (_backgroundMusicPlayer == nil) {
_backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:musicURL error:&error];
}
if (_backgroundMusicPlayer == nil) {
NSLog(#"%#",[error description]);
} else {
[self makePlaybackInfinite];
[_backgroundMusicPlayer play];
}
}
return self;
}
#pragma mark Selfish methods
- (void)makePlaybackInfinite {
// access backing ivar directly because this is also called from init method
if (_backgroundMusicPlayer) {
_backgroundMusicPlayer.numberOfLoops = -1;
}
}
- (CGFloat)currentSampleRate {
NSDictionary *settingsDict = [self.backgroundMusicPlayer settings];
NSNumber *sampleRate = [settingsDict valueForKey:AVSampleRateKey];
return [sampleRate floatValue];
}
#pragma mark Transport like methods
- (void)startBackgroundMusic {
// plays from start - if playing stops and plays from start
if (self.backgroundMusicPlayer.isPlaying) {
[self.backgroundMusicPlayer stop];
self.backgroundMusicPlayer.currentTime = 0;
[self.backgroundMusicPlayer prepareToPlay];// this is not required as play calls this implicitly if not already prepared
[self.backgroundMusicPlayer play];
}
else {
self.backgroundMusicPlayer.currentTime = 0;
[self.backgroundMusicPlayer prepareToPlay];
[self.backgroundMusicPlayer play];
}
}
- (void)stopBackgroundMusic {
// stops and returns play-head to start regardless of state and prepares to play
if (self.backgroundMusicPlayer.isPlaying) {
[self.backgroundMusicPlayer stop];
self.backgroundMusicPlayer.currentTime = 0;
[self.backgroundMusicPlayer prepareToPlay];
}
else {
self.backgroundMusicPlayer.currentTime = 0;
[self.backgroundMusicPlayer prepareToPlay];
}
}
- (void)pauseBackgroundMusic {
// stops and leaves play-head where it is - if already paused or stopped does nothing
if (self.backgroundMusicPlayer.isPlaying) {
[self.backgroundMusicPlayer pause];
}
}
- (void)continueBackgroundMusic {
// continues playing from where the play-head was left - if playing does nothing
if (!self.backgroundMusicPlayer.isPlaying) {
[self.backgroundMusicPlayer play];
}
}
#pragma mark Content management
- (void)replaceBackgroundMusicWithFileOfName:(NSString*)audioFileName startPlaying:(BOOL)startPlaying {
// construct filepath
NSString *filePath = [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], audioFileName];
// make a url from the filepath
NSURL *fileUrl = [NSURL fileURLWithPath:filePath];
// construct player and prepare
NSError *error;
self.backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:&error];
[self.backgroundMusicPlayer prepareToPlay];
[self makePlaybackInfinite];
// if startplaying then play
if (startPlaying) {
[self.backgroundMusicPlayer play];
}
}
- (void)destroyBackgroundMusic {
// stop playing if playing
[self stopBackgroundMusic];
// destroy by setting background player to nil
self.backgroundMusicPlayer = nil;
}
#end
To use simply call [BWBackgroundMusic sharedMusicPlayer]; This will instantiate the singleton if not already instantiated, start the player automatically, and will loop infinitely by default.
Furthermore you can control it from any class that imports BWBackgroundMusic.h
For example to pause the player use
[[BWBackgroundMusic sharedMusicPlayer] pauseBackgroundMusic];
programming newbie here. Used this guide to set up my pages:http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/
And the code I use to play audio is:
NSString *path = [[NSBundle mainBundle] pathForResource:#"1" ofType:#"mp3"];
theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
My goal is to play different sound files when a button is pressed, each sound corresponding to the page number. The button is constant throughout the pages, just trying to change the code that connects it to a new sound every page.
You need to keep track of the current page. The delegate method pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: will tell you if user has changed the page or not so you can update your current page number variable. It will look like something,
-(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if(completed)
{
//update variable and play the audio file respectively
}
}
Not enough information was included in your code. But I'll explain a simple way to implement sound into an app that you can then build upon.
First:
Import the AudioToolbox framework into your project. Then, drag and drop the sound file into your project. Navigate to the view controller and import the header file:
#import <AVFoundation/AVFoundation.h>
Your view controllers header should look similar to this:
#interface ViewController : ParentClass {
AVAudioPlayer *_audioPlayer1;
}
Then, we will declare a button which we will place and hook up on the storyboard. Remember, IBOutlet is typically always weak.
#property (weak, nonatomic) IBOutlet UIButton *button1;
- (IBAction)playSound;
Now, in your play sound method, add this (Hopefully you've already realized to create a button in storyboard and link the outlet and IBAction):
- (IBAction)playSound
NSString *path = [[NSBundle mainBundle] pathForResource:"nameofsound" ofType:#"wav(or other ext)"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: path];
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
[_audioPlayer prepareToPlay];
[_audioPlayer play];
}
Now, as for the page thing: UITableView has a delegate called pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:. Utilize that. It would look theoretically it would like:
-(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
}
At that point I load all the sounds into an array with an index corresponding to the page number,
I've done a lot of reading and research in trying to come up with a solution to my problem and can't seem to make my code work the way I want, or the way it should. What I want to do is be able to have different buttons play different, short sounds, without overlapping each other if I decide to click another sound button before another sound button's audio is finished. I've successfully got to the point where they play their designated sound using SystemSoundID, but they still overlap when I click on multiple buttons.
Below is a sample of my code:
Interface file:
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
}
-(IBAction)Sound:(id)sender;
#end
Implementation file:
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
#implementation ViewController : UIViewController
- (IBAction)Sound:(id)sender
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"Sound" ofType:#"mp3"];
AVAudioPlayer* theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
[theAudio play];
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
I would appreciate the help more than you know, as I've spent quite some time trying to get it to work the way it should. If you have any questions for me please let me know! Thank you very much.
-Michael
I have the understanding that when you play an AudioSystemSound that it will play until completion. This method of playing Audio isn't a highly functional one and only really supposed to be used for simple "clicks" and "dings" etc. For a more comprehensive audio suite of features perhaps you could look at using the AVFoundation library with AVAudioPlayer where you can call "stop" to cease playing the called sound.
https://developer.apple.com/av-foundation/
Edited addition to the above answer:
In your .h file make sure you include;
#import AVFoundation;
and then create an AV player with;
AVAudioPlayer *playAudio
In your .m file add this block of code below in your method that needs to play the sound. Of course adjust the song/audiofile name and format appropriately;
NSString *backgroundMusic = [[NSBundle mainBundle]
pathForResource:#"insertSongNameHere" ofType:#"mp3"];
playAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:insertSongNameHere] error:NULL];
playAudio.numberOfLoops = -1;
[playAudio play];
Note: Number of loops being -1 means repeat forever. You can of course make that whatever number you like though. I hope that all helps, if not let me know and I'll see what I can do to assist!
Ive used SystemSound in my app in order to play simple sound effects. In addition to this I play a musicvideo through the MPMoviePlayerController - now when I turn the volume up/down the music from the video responds as intended (lowering the volume up/down).
But the system sounds that are played does not respond to the volume. Im playing the system sounds when the user taps certain areas in the app. Heres a snippet of my code for that:
- (void)handleTap:(UITapGestureRecognizer *)recognizer {
SystemSoundID completeSound = nil;
//yellow folder in xcode doesnt need subdirectory param
//blue folder (true folder) will need to use subdirectory:#"dirname"
NSURL *sound_path = [[NSBundle mainBundle] URLForResource: target_sound_filename withExtension: #"wav"];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)sound_path, &completeSound);
AudioServicesPlaySystemSound(completeSound);
}
PS. I doublechecked that my "Settings->Sounds->Ringer and Alerts->Change With Buttons" is set to ON (as I read on some other SO answers that leaving this option OFF will cause systemsound to not respond to the volume buttons)
Further the reason for using systemsound is that it gave the most accurate and responsive results when playing multiple sounds (like in a game).
Id prefer to not use OpenAL if possible (even through 3rd party sound libraries like Finch or CocosDenshion)
Any ideas?
Use the AVAudioPlayer class for playing sounds that are controlled by the user's volume settings (non-system sounds).
You can retain instances of AVAudioPlayer for each sound file that you use regularly and simply call the play method. Use prepareToPlay to preload the buffers.
Cheers to Marcus for suggesting that i could retain instances of AVAudioPlayer for each sound file and use prepareToPlay to preload the sounds. It might be to help for others looking for the same solution so here is how I did it (feel free to comment if anyone have suggestions for improvements)
//top of viewcontroller.m
#property (nonatomic, strong) NSMutableDictionary *audioPlayers;
#synthesize audioPlayers = _audioPlayers;
//on viewDidLoad
self.audioPlayers = [NSMutableDictionary new];
//creating the instances and adding them to the nsmutabledictonary in order to retain them
//soundFile is just a NSString containing the name of the wav file
NSString *soundFile = [[NSBundle mainBundle] pathForResource:s ofType:#"wav"];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:soundFile] error:nil];
//audioPlayer.numberOfLoops = -1;
[audioPlayer prepareToPlay];
//add to dictonary with filename (omit extension) as key
[self.audioPlayers setObject:audioPlayer forKey:s];
//then i use the following to play the sound later on (i have it on a tap event)
//get pointer reference to the correct AVAudioPlayer instance for this sound, and play it
AVAudioPlayer *foo = [self.audioPlayers objectForKey:target_sound_filename];
[foo play];
//also im not sure how ARC will treat the strong property, im setting it to nil in dealloc atm.
-(void)dealloc {
self.audioPlayers = nil;
}
In my audio player app, if I hit the fast forward button - the player must find the next song in the playlist and play it. What I do is I get the URL from the next song, and try to put it in the background (background is an instance of AVAudioPlayer*) url field, but that property is read only. So what I'm actually doing - I'm calling the initWithContentsOfURL method (again) to set the URL like this :
[self.background initWithContentsOfURL:
[[_playlist.collection objectAtIndex:currentIndex] songURL] error:nil];
Is this legit? I mean, the compiler tells me that the expression result is unused, but it actually works.
Is there another way of doing it? Thanks ;-)
For playing more than one URL, or effectively changing the URL, use the subclass AVQueuePlayer.
AVQueuePlayer is a subclass of AVPlayer you use to play a number of items in sequence.
Example:
NSString *fooVideoPath = [[NSBundle mainBundle] pathForResource:#"tommy" ofType:#"mov"];
NSString *barVideoPath = [[NSBundle mainBundle] pathForResource:#"pamela" ofType:#"mp4"];
AVPlayerItem *fooVideoItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:fooVideoPath]];
AVPlayerItem *barVideoItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:barVideoPath]];
self.queuePlayer = [AVQueuePlayer queuePlayerWithItems:[NSArray arrayWithObjects:fooVideoItem, barVideoItem,nil]];
[self.queuePlayer play];
// things happening...
[self.queuePlayer advanceToNextItem];
Have checked 4 Apple samples, they are using AVAudioPlayer to play one song only. However, your result looks very interesting and impressive! Please let us know, are you stopping the playback before reinitializing object with the same address, are you starting the new audio session ?
As for me, I'd not put the playback and app stability in a risk doing something not mentioned in the documentation but , but to be on the bright side would use the AVAudioPlayer class as it seems the most right, which gives us:
use the error variable to track the possible errors
stop the playing AVAudioPlayer instance, initialize a new instance of AVAudioPlayer and set it to the property letting an old-one to be deallocated automatically.
And you probably know yourself, that
self.background = [self.background initWithContentsOfURL::
will remove the warning for you.