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];
Related
I am having a hard time as a beginner to Objective-C with learning how and when a function is being called, as I am not seeing it explicitly stated. Below is some code for logging into, and playing a song from the Spotify SDK that I found online.
#import "AppDelegate.h"
#interface AppDelegate ()
#property (nonatomic, strong) SPTAuth *auth;
#property (nonatomic, strong) SPTAudioStreamingController *player;
#property (nonatomic, strong) UIViewController *authViewController;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.auth = [SPTAuth defaultInstance];
self.player = [SPTAudioStreamingController sharedInstance];
// The client ID you got from the developer site
self.auth.clientID = #"5bd669abf2a14fb59839c2c0570843fe";
// The redirect URL as you entered it at the developer site
self.auth.redirectURL = [NSURL URLWithString:#"spotlightmusic://returnafterlogin"];
// Setting the `sessionUserDefaultsKey` enables SPTAuth to automatically store the session object for future use.
self.auth.sessionUserDefaultsKey = #"current session";
// Set the scopes you need the user to authorize. `SPTAuthStreamingScope` is required for playing audio.
self.auth.requestedScopes = #[SPTAuthStreamingScope];
// Become the streaming controller delegate
self.player.delegate = self;
// Start up the streaming controller.
NSError *audioStreamingInitError;
NSAssert([self.player startWithClientId:self.auth.clientID error:&audioStreamingInitError],
#"There was a problem starting the Spotify SDK: %#", audioStreamingInitError.description);
// Start authenticating when the app is finished launching
dispatch_async(dispatch_get_main_queue(), ^{
[self startAuthenticationFlow];
});
return YES;
}
- (void)startAuthenticationFlow
{
// Check if we could use the access token we already have
if ([self.auth.session isValid]) {
// Use it to log in
[self.player loginWithAccessToken:self.auth.session.accessToken];
} else {
// Get the URL to the Spotify authorization portal
NSURL *authURL = [self.auth spotifyWebAuthenticationURL];
// Present in a SafariViewController
self.authViewController = [[SFSafariViewController alloc] initWithURL:authURL];
[self.window.rootViewController presentViewController:self.authViewController animated:YES completion:nil];
}
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary *)options
{
// If the incoming url is what we expect we handle it
if ([self.auth canHandleURL:url]) {
// Close the authentication window
[self.authViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
self.authViewController = nil;
// Parse the incoming url to a session object
[self.auth handleAuthCallbackWithTriggeredAuthURL:url callback:^(NSError *error, SPTSession *session) {
if (session) {
// login to the player
[self.player loginWithAccessToken:self.auth.session.accessToken];
}
}];
return YES;
}
return NO;
}
- (void)audioStreamingDidLogin:(SPTAudioStreamingController *)audioStreaming
{
[self.player playSpotifyURI:#"spotify:track:3DWOTqMQGp5q75fnVsWwaN" startingWithIndex:0 startingWithPosition:0 callback:^(NSError *error) {
if (error != nil) {
NSLog(#"*** failed to play: %#", error);
return;
}
}];
}
#end
I am wondering how exactly these functions are being called sequentially, and specifically how the audioStreamingDidLogin one is being run.
Additionally I was wondering how it would look to call that function from the view controller with some sort of input coming from the UI.
Any help with this logic would be greatly appreciated! Thanks.
Your question is closely tied to the Spotify framework being used. It is not a question of when Objective-C is executing something - the language has a standard sequential execution model - but how the framework is doing callbacks, e.g. audioStreamingDidLogin, to your code and utilising threads/GCD to do concurrent execution.
First you should read the Spotify framework documentation.
You can also place a breakpoint at the start of each method and then run under the debugger. When a breakpoint is hit check which thread has stopped and the stack trace. That should give you a good idea of execution flow and the concurrent threads being used.
HTH
UIApplicationDelegate method application:didFinishLaunchingWithOptions: is called first, followed by application:openURL:options:.
That first app delegate method sets self as the delegate for an AudioStreamingController. This is how audioStreamingDidLogin gets called. You're telling the streaming controller, "Tell me (self) when interesting things happen". (See the SPTAudioStreamingControllerDelegate docs for what else it might tell you about).
You probably wouldn't (shouldn't) call this function directly, especially if there's a chance that you might call it before auth is complete. Doing so would likely result in an error on the call to playSpotifyURI. If you're certain that the user is authenticated, then you don't need to call it. Just call what it calls: playSpotifyURI.
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.
I've an app with 3 view controllers. The first one generate a 5min track and when it goes to second view it keeps on playing in setVolume:2.0 and same goes for 3rd view controller.But on third ViewCOntroller there is a UIButton that re generate the first ViewController. And when it goes back to first view controller it starts that sound again and the previous one is already playing so it's a mix.Now the sound was init on first ViewController so how can I stop it on third ViewController? It there any code that stops all the sounds playing?
You can declare the AVAudioPlayer as a global variable in your first ViewController. The effect will be that every instance of your first ViewController will be able to share the same player.
AVAudioPlayer *player;
After that you can do something like this in ViewDidLoad:
if (!player) {
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
[player play];
} else if (!player.playing) {
[player play];
}
You can create a shared instance and be called from all viewController. So you can refer when to stop/play/load, etc.
#interface MainAVPlayer : AVAudioPlayer
+ (instancetype)shared;
#end
#implementation MainAVPlayer
+ (instancetype)shared {
static id _sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
#end
When accessing from other view controller call:
[[MainAVPlayer shared] initWithContentsOfURL:url error:&error];
[[MainAVPlayer shared] play];
p/s: there might be typo, writing from memory.
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'm building an iOS app with cocos2d 2, and I'm using SimpleAudioEngine to play some effects.
Is there a way to sequence multiple sounds to be played after the previous sound is complete?
For example in my code:
[[SimpleAudioEngine sharedEngine] playEffect:#"yay.wav"];
[[SimpleAudioEngine sharedEngine] playEffect:#"youDidIt.wav"];
When this code is run, yay.wav and youDidIt.wav play at the exact same time, over each other.
I want yay.wav to play and once complete, youDidIt.wav to play.
If not with SimpleAudioEngine, is there a way with AudioToolbox, or something else?
Thank you!
== UPDATE ==
I think I'm going to go with this method using AVFoundation:
AVPlayerItem *sound1 = [[AVPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"yay" ofType:#"wav"]]];
AVPlayerItem *sound2 = [[AVPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"YouDidIt" ofType:#"wav"]]];
AVQueuePlayer *player = [[AVQueuePlayer alloc] initWithItems: [NSArray arrayWithObjects:sound1, sound2, nil]];
[player play];
The easy way would be getting the track duration using -[CDSoundSource durationInSeconds] and then schedule the second effect playing after a proper delay:
[[SimpleAudioEngine sharedEngine] performSelector:#selector(playEffect:) withObject:#"youDidIt.wav" afterDelay:duration];
An easier way to get the audio duration would be patching SimpleAudioManager and add a method that queries its CDSoundEngine (a static global) for the audio duration:
- (float)soundDuration {
return [_engine bufferDurationInSeconds:_soundId];
}
The second approach would be polling on the status of the audio engine and wait for it to stop playing.
alGetSourcei(sourceId, AL_SOURCE_STATE, &state);
if (state == AL_PLAYING) {
...
The sourceId is the ALuint returned by playEffect.
It's simple, tested with Cocos2D 2.1:
Creates a property durationInSeconds on SimpleAudioEngine.h :
#property (readonly) float durationInSeconds;
Synthesize them :
#synthesize durationInSeconds;
Modify playEffect like this :
-(ALuint) playEffect:(NSString*) filePath pitch:(Float32) pitch pan:(Float32) pan gain:(Float32) gain {
int soundId = [bufferManager bufferForFile:filePath create:YES];
if (soundId != kCDNoBuffer) {
durationInSeconds = [soundEngine bufferDurationInSeconds:soundId];
return [soundEngine playSound:soundId sourceGroupId:0 pitch:pitch pan:pan gain:gain loop:false];
} else {
return CD_MUTE;
}
}
As you can see, I inserted durationInSeconds value directly from soundEngine bufferDurationInSeconds from the result of bufferManager.
Now, only ask for the value [SimpleAudioEngine sharedEngine].durationInSeconds and you've got the float duration in seconds of this sound.
Put a timer this seconds and after this, play the next iteration of your sound or put the flag OFF or something.