Identical Functions, both get executed, no Sound in one - objective-c

I'v googled the problem but couldn't find a similar case.
I've used this tutorial to play the testSound.mp3 - File when hitting a button on the iPad-Simulator:
http://mobileorchard.com/easy-audio-playback-with-avaudioplayer/
It works that way (it plays the sound), if the playSound-Method is in my ViewController.m, but not in my Sound.m (which has a identical method).
The Code gets executed (NSLog says: "Sound.m playSound executed"), but there is no sound at all.
I'd really appreciate some help here, guess I'm totally stuck... :(
Best regards,
- Teapot
// ViewController.h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "Sound.h"
#interface ViewController : UIViewController {
AVAudioPlayer *audioPlayer;
}
- (IBAction)pressButton:(id)sender;
- (void)playSound: (NSString*) soundFile volume : (NSInteger) volume repeats : (NSInteger) repeats;
#end
// ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading thea view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)pressButton:(id)sender {
NSLog (#"Method: pressButton");
[self playSound: #"testSound.mp3" volume: 2 repeats: 2 url : url]; //It works!
Sound *tempSound = [[Sound alloc] init];
[tempSound playSound: #"testSound.mp3" volume: 2 repeats: 2]; // Doesn't work. -> Says "Sound.m playSound executed", but there is no Sound.
}
- (void)playSound: (NSString*) soundFile volume : (NSInteger) volume repeats : (NSInteger) repeats {
NSLog(#"ViewControler playSound");
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = -1;
if (audioPlayer == nil){
NSLog([error description]);
NSLog(#"ViewController.m playSound NOT executed");
}
else{
[audioPlayer play];
NSLog(#"ViewController.m playSound executed");
}
}
#end
// Sound.h
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#interface Sound : NSObject {
AVAudioPlayer *audioPlayer;
}
- (void) playSound: (NSString*) soundFile volume : (NSInteger) volume repeats : (NSInteger) repeats;
#end
// Sound.m
#import "Sound.h"
#implementation Sound
- (void)playSound: (NSString*) soundFile volume : (NSInteger) volume repeats : (NSInteger) repeats {
NSLog(#"Sound playSound");
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = -1;
if (audioPlayer == nil){
NSLog([error description]);
NSLog(#"Sound.m playSound NOT executed");
}
else{
[audioPlayer play];
NSLog(#"Sound.m playSound executed");
}
}
#end

There are some inconsistencies in your code: playSound: has an NSString parameter, but AVAudioPlayer inside that method uses a NSURL. Then you set numberOfLoops = -1 (which means infinite repetition) instead of numberOfLoops = repeat.
But the main problem is that here (assuming that you compile with "Automatic Reference Counting")
Sound *tempSound = [[Sound alloc] init];
[tempSound playSound: #"testSound.mp3" volume: 2 repeats: 2];
the tempSound object is deallocated when the pressButton: is left, because no strong references to that object exist anymore.
If you add an instance variable (or property) sound to the view controller class, and assign to that
sound = [[Sound alloc] init];
[sound playSound: #"testSound.mp3" volume: 2 repeats: 2];
then it should work as expected.
Alternatively, you could prevent the Sound object from being deallocated too early by maintaining a "self reference" inside the object, which is removed only when the sound has finished playing:
#interface Sound () <AVAudioPlayerDelegate>
#property(strong, nonatomic) AVAudioPlayer *audioPlayer;
#property(strong, nonatomic) Sound *selfRef;
#end
#implementation Sound
- (void)playSound:(NSString *)soundFile volume:(NSInteger)volume repeats:(NSInteger)repeats
{
NSLog(#"Sound playSound");
NSURL *soundURL = [[NSBundle mainBundle] URLForResource:soundFile withExtension:nil];
NSError *error;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];
if (self.audioPlayer == nil) {
NSLog(#"%#", [error description]);
NSLog(#"Sound.m playSound NOT executed");
} else{
self.audioPlayer.numberOfLoops = repeats;
self.audioPlayer.delegate = self;
[self.audioPlayer play];
self.selfRef = self; // self reference to avoid deallocation
NSLog(#"Sound.m playSound executed");
}
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
self.selfRef = nil; // remove self reference
}
#end
Of course, you shouldn't do this with "infinite repetition"!

Related

Objective C protocol method is not called

I have created singleton class for AVAudioPlayer. I am able to call the methods in the class and everything works fine. When the song finishes,the method (void)audioPlayerDidFinishPlaying is called which in turn suppose to call the method ' processSuccessful' in my downloadPlay.m class. But, it is not calling the method 'processSuccessful'
My codes as follows
PlayerManager.h
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#protocol ProcessDataDelegate <NSObject>
#required
- (void) processSuccessful;
#end
#interface PlayerManager : NSObject<AVAudioPlayerDelegate,AVAudioSessionDelegate>
{
id <ProcessDataDelegate> delegate;
}
+ (PlayerManager *)sharedAudioPlayer;
#property (nonatomic,assign) id <ProcessDataDelegate>delegate;
#property (nonatomic, strong) AVAudioPlayer* player;
-(void)preparesong:(NSURL *)url;
-(void)stopsong;
-(void)pause;
-(void)playsong;
-(void)prepareToPlay;
-(BOOL)isPlaying;
-(BOOL)isPlayerExist;
#end
PlayerManager.m
#import "PlayerManager.h"
#interface PlayerManager()
#end
#implementation PlayerManager
#synthesize player;
#synthesize delegate;
static PlayerManager *sharedAudioPlayer = nil;
+ (PlayerManager *)sharedAudioPlayer {
static PlayerManager *sharedAudioPlayer = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedAudioPlayer = [[self alloc] init];
});
return sharedAudioPlayer ;
}
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags
{
if (flags & AVAudioSessionInterruptionOptionShouldResume)
{
[self.player play];
}
}
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player
{
}
#pragma mark - AVAudioPlayerDelegate
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
[[self delegate] processSuccessful];
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error
{
}
-(void)preparesong:(NSURL *)url
{
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
NSError *error;
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
if(!self.player)
{
NSLog(#"Error creating player: %#", error);
}
self.player.delegate = self;
[self.player prepareToPlay];
}
-(BOOL)isPlayerExist
{
if (player)
return YES;
return NO;
}
-(BOOL)isPlaying
{
if (player && player.playing)
return YES;
return NO;
}
-(void)prepareToPlay
{
if (player)
[self.player prepareToPlay];
}
-(void)playsong
{
if (player)
[self.player play];
}
-(void)pause
{
if (player.playing)
[self.player pause];
}
-(void)stopsong
{
if (player)
[self.player stop];
}
#end
downloadPlay.h
#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import "PlayerManager.h"
#interface downloadPlay: UIViewController <UITableViewDelegate,AVAudioPlayerDelegate,ProcessDataDelegate>
{
PlayerManager *protocolPlay;
}
#property (retain, nonatomic) IBOutlet UITableView *tblFiles;
......
- (void)startPlay:(id)sender;
........
#end
downloadPlay.m
import "downloadPlay.h"
#import "PlayerManager.h"
#interface downloadPlay ()
#end
#implementation downloadPlay
.....
- (void)processSuccessful
{
NSLog(#"This method suppose to be called from the method audioPlayerDidFinishPlaying - from PlayerManager");
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
protocolPlay = [[PlayerManager alloc]init];
[protocolPlay setDelegate:self];
}
- (void)startPlay
{
............
.........
NSURL *destinationURL = [self.docDirectoryURL URLByAppendingPathComponent:filename];
NSError* error = nil;
[[PlayerManager sharedAudioPlayer]stopsong];
[[PlayerManager sharedAudioPlayer ] preparesong:destinationURL ];
[[PlayerManager sharedAudioPlayer]playsong];
}
#end
In viewDidLoad method you are creating a different object by using
protocolPlay = [[PlayerManager alloc]init];
line and set the delegate of this object while you have to set the delegate of shared object.
Solution is:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[PlayerManager sharedAudioPlayer] setDelegate:self];
}

How can i make a random sound array with objective c?

So i've done the tutorial where you code a button so that when you press it a sound plays. I'm trying to modify it so that when the button is pressed, a random sound plays.
here is the code:
viewcontroller.h
#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
#interface STViewController : UIViewController
- (IBAction)playAudio:(id)sender;
#property (nonatomic, strong) NSArray *sounds;
#end
viewcontroller.m
#import <AVFoundation/AVFoundation.h>
#import "STViewController.h"
#interface STViewController ()
#property (weak, nonatomic) IBOutlet UIButton *playAudio;
#end
#implementation STViewController
- (IBAction)playAudio:(id)sender {
AVAudioPlayer *audioPlayer;
NSString *audioPath = [[NSBundle mainBundle] pathForResource:#"Woof" ofType:#"mp3"];
NSURL *audioURL = [NSURL fileURLWithPath:audioPath];
NSError *audioError = [[NSError alloc] init];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:&audioError];
if (!audioError) {
[audioPlayer play];
NSLog(#"Woof!");
}
else {
NSLog(#"Error!");
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I've been dabbling with something along these lines
- (NSArray *)sounds
{
NSArray *sounds = [NSArray arrayWithObjects:
#"Woof.mp3",
#"Meow.mp3",
#"tweet.mp3",
#"Squeak.mp3",
#"Moo.mp3",
#"Croak.mp3",
#"Toot.mp3",
#"Quack.mp3",
#"Blub.mp3",
#"OWOwOw.mp3",
#"Fox.mp3",
nil];
return sounds;
}
but i'm not really sure how to make it random or even implement in in the code that I have going right now. Anyone have any ideas?
Just make it random try below:-
NSMutableArray *array=[NSMutableArray
arrayWithObjects:
#"Woof.mp3",
#"Meow.mp3",
#"tweet.mp3",
#"Squeak.mp3",
#"Moo.mp3",
#"Croak.mp3",
#"Toot.mp3",
#"Quack.mp3",
#"Blub.mp3",
#"OWOwOw.mp3",
#"Fox.mp3",
nil];
// now use exchangeobject with index api
int i=0;
for(i=0;i<=[array count]; i++)
{
NSInteger rand=(arc4random() %10);
[array exchangeObjectAtIndex:i
withObjectAtIndex:rand];
}

Odd behavior in NSViewController

I've started a tiny project to get my head around NSViewControllers.
I have an AppController that handles a NSOpenPanel. Once I get a URL to a movie file, I pass it to a NSViewController subclass (NNMovieViewController). This is how I do it:
-(void)openMovieWithURL:(NSURL *)url {
NSError *error;
movie = [[QTMovie alloc] initWithURL:url error:&error];
[startButton setEnabled:YES];
[movieView setMovie:movie];
NSLog(#"button: %#", [startButton isEnabled]?#"YES":#"NO");
// logs "NO"
NSLog(#"movie: %#", movie);
// logs the correct movie object
NSLog(#"movieView: %#", [movieView movie]);
// logs "(null)"
}
The header file looks like this:
#import <Cocoa/Cocoa.h>
#import <QTKit/QTKit.h>
#interface NNMovieViewController : NSViewController {
QTMovie *movie;
BOOL playing;
IBOutlet QTMovieView *movieView;
IBOutlet NSButton *startButton;
}
-(IBAction)clickStart:(id)sender;
-(void)openMovieWithURL:(NSURL*)url;
#end
What am I missing? I re-did the whole thing in a project without a NSViewController and it just worked...
UPDATE
After I received the comments from Kreiri and Parag Bafna I tinkered a little bit more and found out that at the time I call [movieViewController openMovieWithURL:url]; inside my AppController the Outlets are not hooked up yet.
This is my AppController implementation:
#import "AppController.h"
#implementation AppController
#synthesize movieViewController;
- (void)awakeFromNib {
movieViewController = [[NNMovieViewController alloc] initWithNibName:#"NNMovieViewController" bundle:nil];
NSView *viewControllerView = [movieViewController view];
[view addSubview:viewControllerView];
}
- (IBAction)clickOpen:(id)sender {
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setCanChooseFiles:TRUE];
[dialog setCanChooseDirectories:FALSE];
[dialog setAllowsMultipleSelection:FALSE];
[dialog setAllowedFileTypes:[QTMovie movieFileTypes:0]];
if ([dialog runModal] == NSOKButton) {
NSURL *movieFileURL = [[dialog URLs] objectAtIndex:0];
[self openMovie:movieFileURL];
}
}
- (void)openMovie:(NSURL *)url {
NSLog(#"startButton: %#", [movieViewController movieView]);
// logs "null"
NSLog(#"startButton: %#", [movieViewController startButton]);
// logs "null"
NSLog(#"---------------------------------");
[movieViewController openMovieWithURL:url];
}
#end
Yes, silly me. In Interface Builder I hooked up my controls with the wrong object. I should have used File's Owner but instead I dragged in an NSObject and set its class to NNMovieViewController and connected the widgets to it.

Unrecognized Selector in AVplayer setNumberOfLoops Method

When calling the numberOfLoops method like so:
[_player setNumberOfLoops:-1];
I get the following error:
-[AVPlayer setNumberOfLoops:]: unrecognized selector sent to instance 0x7d52d30
How can this be fixed?
Code:
Header:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface ViewController : UIViewController {
}
#property (strong, nonatomic) AVAudioPlayer *player;
- (IBAction)playMusic:(id)sender;
#end
Implementation:
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)playMusic:(id)sender {
_player = [AVPlayer playerWithURL:[NSURL URLWithString:#"http://urlpath.wav"]];
[_player setNumberOfLoops:-1];
[_player prepareToPlay];
[_player play];
}
#end
Thank you for your time,
Yoni201.
You've created an instance of AVPlayer, not an instance of AVAudioPlayer. It looks like you want to be creating an AVAudioPlayer instead (as is indicated by your choice of that class for the actual player property on your class. AVAudioPlayer actually has the numberOfLoops property, while AVPlayer does not. For more information, see the documentation for AVAudioPlayer and AVPlayer.
AVPlayer doesn't have a numberOfLoops property. That is a property of `AVAudioPlayer. Don't ignore compiler warnings when you build your app.
Also, you defined _player to be an AVAudioPlayer but you alloc/init AVPlayer.
Change your code to:
NSError *error = nil;
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://urlpath.wav"] error:&error];
if (player) {
[player setNumberOfLoops:-1];
[player prepareToPlay];
[player play];
self.player = player;
} else {
NSLog(#"Error create audio player: %#", error);
}
I think that the method you are calling doesn't exist (acceosing to your error).
Try: _player.numberOfLoops=-1

Why is Xcode saying my class implementation is incomplete?

I have created a singleton for my MusicBackground. And I receive a line code of imcomplete implementation of this line #implementation MyBgMusic. Can anyone tell me why ? Below is the code:
#import "MyBgMusic.h"
static MyBgMusic *sharedMyManager = nil;
#implementation MyBgMusic
#synthesize player,playBgMusic;
#pragma mark -
#pragma mark Singleton Methods
+ (MyBgMusic*)sharedInstance {
static MyBgMusic *_sharedInstance;
if(!_sharedInstance) {
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[super allocWithZone:nil] init];
});
}
return _sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#if (!__has_feature(objc_arc))
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be released
}
- (id)autorelease {
return self;
}
- (void)dealloc
{
[MyBgMusic release];
[playBgMusic release];
[player release];
[super dealloc];
}
#endif
#pragma mark -
#pragma mark Custom Methods
- (void)viewDidLoad
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"music" ofType:#"mp3"];
self.player=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
player.delegate = self;
[player play];
player.numberOfLoops = -1;
[super viewDidLoad];
}
#end
For the M file, below is the code:
#import <Foundation/Foundation.h>
#import <AVFoundation/AVAudioPlayer.h>
#interface MyBgMusic : UIViewController <AVAudioPlayerDelegate> {
AVAudioPlayer *player;
UIButton *playBgMusic;
}
#property (nonatomic, retain) IBOutlet AVAudioPlayer *player;
#property (nonatomic, retain) IBOutlet UIButton *playBgMusic;
+ (id)sharedManager;
-(IBAction) toggleMusic;
#end
And how do I reference to my toggle button: Below is the code :
- (IBAction)toggleMusic {
if ([self.player isPlaying] == YES) {
[self.player stop];
} else {
[self.player play];
}
self.playBgMusic.enabled = YES;
}
It means that your MyBgMusic class isn't doing everything it promised to do in its header file, which includes being a UIViewController and implementing the AVAudioPlayerDelegate protocol. I'm not familiar with exactly what the AVAudioPlayerDelegate is, but it's quite possible that your class doesn't implement all of the required methods.
Also, you're declaring methods +(id)sharedManager and -(IBAction)toggleMusic, but I don't see them anywhere in the implementation file. That would be a case of promising something in the header and not implementing it in the class.
It would help if you posted the actual error message.
That error means your #implementation section does not contain everything described in the #interface section.
I can see two problems.
First you need to place this code:
- (IBAction)toggleMusic {
...
}
Somewhere in between #implementation and #end.
And you also need to rename the line + (MyBgMusic*)sharedInstance to + (id)sharedManager.
EDIT:
To access the toggle music method elsewhere in your code, you would do:
[[MyBgMusic sharedManager] toggleMusic];
Your +(id)sharedManagerimplementation is called +(id)sharedInstance. Just guessing, but it seems they are supposed to do the same.